import { Quadrant , QuadrantChangeCallback , QuadrantCol , QuadrantRow , QuadsKeeprSettings , Actor , } from "./types" ; template TActor export class QuadsKeepr < TActor extends Actor > { /**
     * The top boundary for all quadrants.
     */ public top : number ; /**
     * The right boundary for all quadrants.
     */ public right : number ; /**
     * The bottom boundary for all quadrants.
     */ public bottom : number ; /**
     * The left boundary for all quadrants.
     */ public left : number ; /**
     * How many rows of Quadrants there should be initially.
     */ private numRows : number ; /**
     * How many columns of Quadrants there should be initially.
     */ private numCols : number ; /**
     * Horizontal scrolling offset during gameplay.
     */ private offsetX : number ; /**
     * Vertical scrolling offset during gameplay.
     */ private offsetY : number ; /**
     * Whether to factor horizontal visual displacement for bounding boxes.
     */ private readonly checkOffsetX : boolean ; /**
     * Whether to factor vertical visual displacement for bounding boxes.
     */ private readonly checkOffsetY : boolean ; /**
     * Starting coordinates for columns.
     */ private readonly startLeft : number ; /**
     * Starting coordinates for rows.
     */ private readonly startTop : number ; /**
     * A QuadrantRow[] that holds each QuadrantRow in order.
     */ private quadrantRows : QuadrantRow < TActor > [ ] ; /**
     * A QuadrantCol[] that holds each QuadrantCol in order.
     */ private quadrantCols : QuadrantCol < TActor > [ ] ; /**
     * How wide Quadrants should be.
     */ private readonly quadrantWidth : number ; /**
     * How tall Quadrants should be.
     */ private readonly quadrantHeight : number ; /**
     * The groups Actors may be placed into within Quadrants.
     */ private readonly groupNames : string [ ] ; /**
     * Callback for when Quadrants are added, called on the area and direction.
     */ private readonly onAdd ? : QuadrantChangeCallback ; /**
     * Callback for when Quadrants are removed, called on the area and direction.
     */ private readonly onRemove ? : QuadrantChangeCallback ; param settings public constructor ( settings ) { if ( ! settings ) { throw new Error ( "No settings object given to QuadsKeepr." ) ; } this . numRows = ( settings . numRows || 0 ) | 0 || 2 ; this . numCols = ( settings . numCols || 0 ) | 0 || 2 ; this . quadrantWidth = ( settings . quadrantWidth || 0 ) | 0 || 2 ; this . quadrantHeight = ( settings . quadrantHeight || 0 ) | 0 || 2 ; this . groupNames = settings . groupNames || [ ] ; this . checkOffsetX = ! ! settings . checkOffsetX ; this . checkOffsetY = ! ! settings . checkOffsetY ; this . onAdd = settings . onAdd ; this . onRemove = settings . onRemove ; this . startLeft = ( settings . startLeft || 0 ) | 0 ; this . startTop = ( settings . startTop || 0 ) | 0 ; } returns public getQuadrantRows ( ) { return this . quadrantRows ; } returns public getQuadrantCols ( ) { return this . quadrantCols ; } returns public getNumRows ( ) { return this . numRows ; } returns public getNumCols ( ) { return this . numCols ; } returns public getQuadrantWidth ( ) { return this . quadrantWidth ; } returns public getQuadrantHeight ( ) { return this . quadrantHeight ; } /**
     * Completely resets all Quadrants. The grid structure of rows and columns
     * is remade with new Quadrants according to startLeft and startTop.
     */ public resetQuadrants ( ) { this . top = this . startTop ; this . right = this . startLeft + this . quadrantWidth * this . numCols ; this . bottom = this . startTop + this . quadrantHeight * this . numRows ; this . left = this . startLeft ; this . quadrantRows = [ ] ; this . quadrantCols = [ ] ; this . offsetX = 0 ; this . offsetY = 0 ; let top = this . startTop ; for ( let i = 0 ; i < this . numRows ; i += 1 ) { this . quadrantRows . push ( { left : this . startLeft , quadrants : [ ] , top , } ) ; top += this . quadrantHeight ; } let left = this . startLeft ; for ( let j = 0 ; j < this . numCols ; j += 1 ) { this . quadrantCols . push ( { left , quadrants : [ ] , top : this . startTop , } ) ; left += this . quadrantWidth ; } top = this . startTop ; for ( let i = 0 ; i < this . numRows ; i += 1 ) { left = this . startLeft ; for ( let j = 0 ; j < this . numCols ; j += 1 ) { const quadrant = this . createQuadrant ( left , top ) ; this . quadrantRows [ i ] . quadrants . push ( quadrant ) ; this . quadrantCols [ j ] . quadrants . push ( quadrant ) ; left += this . quadrantWidth ; } top += this . quadrantHeight ; } if ( this . onAdd ) { this . onAdd ( "xInc" , this . top , this . right , this . bottom , this . left ) ; } } param dxRaw param dyRaw public shiftQuadrants ( dxRaw = 0 , dyRaw = 0 ) { const dx = dxRaw | 0 ; const dy = dyRaw | 0 ; this . offsetX += dx ; this . offsetY += dy ; this . top += dy ; this . right += dx ; this . bottom += dy ; this . left += dx ; for ( let row = 0 ; row < this . numRows ; row += 1 ) { this . quadrantRows [ row ] . left += dx ; this . quadrantRows [ row ] . top += dy ; } for ( let col = 0 ; col < this . numCols ; col += 1 ) { this . quadrantCols [ col ] . left += dx ; this . quadrantCols [ col ] . top += dy ; } for ( let row = 0 ; row < this . numRows ; row += 1 ) { for ( let col = 0 ; col < this . numCols ; col += 1 ) { this . shiftQuadrant ( this . quadrantRows [ row ] . quadrants [ col ] , dx , dy ) ; } } this . adjustOffsets ( ) ; } param callUpdate returns public pushQuadrantRow ( callUpdate ? ) { const row = this . createQuadrantRow ( this . left , this . bottom ) ; this . numRows += 1 ; this . quadrantRows . push ( row ) ; for ( let i = 0 ; i < this . quadrantCols . length ; i += 1 ) { this . quadrantCols [ i ] . quadrants . push ( row . quadrants [ i ] ) ; } this . bottom += this . quadrantHeight ; if ( callUpdate && this . onAdd ) { this . onAdd ( "yInc" , this . bottom , this . right , this . bottom - this . quadrantHeight , this . left ) ; } return row ; } param callUpdate returns public pushQuadrantCol ( callUpdate ? ) { const col = this . createQuadrantCol ( this . right , this . top ) ; this . numCols += 1 ; this . quadrantCols . push ( col ) ; for ( let i = 0 ; i < this . quadrantRows . length ; i += 1 ) { this . quadrantRows [ i ] . quadrants . push ( col . quadrants [ i ] ) ; } this . right += this . quadrantWidth ; if ( callUpdate && this . onAdd ) { this . onAdd ( "xInc" , this . top , this . right - this . offsetY , this . bottom , this . right - this . quadrantWidth - this . offsetY ) ; } return col ; } param callUpdate returns public popQuadrantRow ( callUpdate ? ) { for ( const col of this . quadrantCols ) { col . quadrants . pop ( ) ; } this . numRows -= 1 ; this . quadrantRows . pop ( ) ; if ( callUpdate && this . onRemove ) { this . onRemove ( "yInc" , this . bottom , this . right , this . bottom - this . quadrantHeight , this . left ) ; } this . bottom -= this . quadrantHeight ; } aram public popQuadrantCol ( callUpdate ? ) { for ( const row of this . quadrantRows ) { row . quadrants . pop ( ) ; } this . numCols -= 1 ; this . quadrantCols . pop ( ) ; if ( callUpdate && this . onRemove ) { this . onRemove ( "xDec" , this . top , this . right - this . offsetY , this . bottom , this . right - this . quadrantWidth - this . offsetY ) ; } this . right -= this . quadrantWidth ; } paam returns public unshiftQuadrantRow ( callUpdate ? ) { const row = this . createQuadrantRow ( this . left , this . top - this . quadrantHeight ) ; this . numRows += 1 ; this . quadrantRows . unshift ( row ) ; for ( let i = 0 ; i < this . quadrantCols . length ; i += 1 ) { this . quadrantCols [ i ] . quadrants . unshift ( row . quadrants [ i ] ) ; } this . top -= this . quadrantHeight ; if ( callUpdate && this . onAdd ) { this . onAdd ( "yDec" , this . top , this . right , this . top + this . quadrantHeight , this . left ) ; } return row ; } para returns public unshiftQuadrantCol ( callUpdate ? ) { const col = this . createQuadrantCol ( this . left - this . quadrantWidth , this . top ) ; this . numCols += 1 ; this . quadrantCols . unshift ( col ) ; for ( let i = 0 ; i < this . quadrantRows . length ; i += 1 ) { this . quadrantRows [ i ] . quadrants . unshift ( col . quadrants [ i ] ) ; } this . left -= this . quadrantWidth ; if ( callUpdate && this . onAdd ) { this . onAdd ( "xDec" , this . top , this . left , this . bottom , this . left + this . quadrantWidth ) ; } return col ; } param allUpdate public shiftQuadrantRow ( callUpdate ? ) { for ( const col of this . quadrantCols ) { col . quadrants . shift ( ) ; } this . numRows -= 1 ; this . quadrantRows . pop ( ) ; if ( callUpdate && this . onRemove ) { this . onRemove ( "yInc" , this . top , this . right , this . top + this . quadrantHeight , this . left ) ; } this . top += this . quadrantHeight ; } param calUpdate public shiftQuadrantCol ( callUpdate ? ) { for ( const row of this . quadrantRows ) { row . quadrants . shift ( ) ; } this . numCols -= 1 ; this . quadrantCols . pop ( ) ; if ( callUpdate && this . onRemove ) { this . onRemove ( "xInc" , this . top , this . left + this . quadrantWidth , this . bottom , this . left ) ; } this . left += this . quadrantWidth ; } public clearAllQuadrants ( ) { for ( const row of this . quadrantRows ) { for ( const quadrant of row . quadrants ) { for ( const group of this . groupNames ) { quadrant . numactors [ group ] = 0 ; } } } } param actor param quadrant param group public setActorInQuadrant ( actor , quadrant , group ) { actor . quadrants [ actor . numQuadrants ] = quadrant ; actor . numQuadrants += 1 ; quadrant . actors [ group ] [ quadrant . numactors [ group ] ] = actor ; quadrant . numactors [ group ] += 1 ; if ( actor . changed ) { quadrant . changed = true ; } } param actors public determineGroupQuadrants ( actors ) { for ( const actor of actors ) { this . determineActorQuadrants ( actor ) ; } } param actor private determineActorQuadrants ( actor ) { const groupType = actor . groupType ; const rowStart = this . findQuadrantRowStart ( actor ) ; const colStart = this . findQuadrantColStart ( actor ) ; const rowEnd = this . findQuadrantRowEnd ( actor ) ; const colEnd = this . findQuadrantColEnd ( actor ) ; if ( actor . changed ) { this . markActorQuadrantsChanged ( actor ) ; } actor . numQuadrants = 0 ; for ( let row = rowStart ; row <= rowEnd ; row += 1 ) { for ( let col = colStart ; col <= colEnd ; col += 1 ) { this . setActorInQuadrant ( actor , this . quadrantRows [ row ] . quadrants [ col ] , groupType ) ; } } actor . changed = false ; } /**
     * Adjusts the offst measurements by checking if rows or columns have gone
     * over the limit, which requires rows or columns be removed and new ones
     * added.
     */ private adjustOffsets ( ) { while ( - this . offsetX > this . quadrantWidth ) { this . shiftQuadrantCol ( true ) ; this . pushQuadrantCol ( true ) ; this . offsetX += this . quadrantWidth ; } while ( this . offsetX > this . quadrantWidth ) { this . popQuadrantCol ( true ) ; this . unshiftQuadrantCol ( true ) ; this . offsetX -= this . quadrantWidth ; } while ( - this . offsetY > this . quadrantHeight ) { this . unshiftQuadrantRow ( true ) ; this . pushQuadrantRow ( true ) ; this . offsetY += this . quadrantHeight ; } while ( this . offsetY > this . quadrantHeight ) { this . popQuadrantRow ( true ) ; this . unshiftQuadrantRow ( true ) ; this . offsetY -= this . quadrantHeight ; } } param dx param dy private shiftQuadrant ( quadrant , dx , dy ) { quadrant . top += dy ; quadrant . right += dx ; quadrant . bottom += dy ; quadrant . left += dx ; quadrant . changed = true ; } param left param top returns private createQuadrant ( left , top ) { const quadrant = { bottom : top + this . quadrantHeight , changed : true , left , numactors : { } , right : left + this . quadrantWidth , actors : { } , top , } ; for ( const groupName of this . groupNames ) { quadrant . actors [ groupName ] = [ ] ; quadrant . numactors [ groupName ] = 0 ; } return quadrant ; } param left param top returns private createQuadrantRow ( left = 0 , top = 0 ) { const row = { left , quadrants : [ ] , top , } ; for ( let i = 0 ; i < this . numCols ; i += 1 ) { row . quadrants . push ( this . createQuadrant ( left , top ) ) ; left += this . quadrantWidth ; } return row ; } param left param top returns private createQuadrantCol ( left , top ) { const col = { left , quadrants : [ ] , top , } ; for ( let i = 0 ; i < this . numRows ; i += 1 ) { col . quadrants . push ( this . createQuadrant ( left , top ) ) ; top += this . quadrantHeight ; } return col ; } param actor returns private getTop ( actor ) { if ( this . checkOffsetY ) { return actor . top - Math . abs ( actor . offsetY ! ) ; } return actor . top ; } param actor returns private getRight ( actor ) { if ( this . checkOffsetX ) { return actor . right + Math . abs ( actor . offsetX ! ) ; } return actor . right ; } param actor returns private getBottom ( actor ) { if ( this . checkOffsetY ) { return actor . bottom + Math . abs ( actor . offsetY ! ) ; } return actor . bottom ; } param actor returns private getLeft ( actor ) { if ( this . checkOffsetX ) { return actor . left - Math . abs ( actor . offsetX ! ) ; } return actor . left ; } /**
     * Marks all Quadrants a Thig is contained within as changed.
     */ private markActorQuadrantsChanged ( actor ) { for ( let i = 0 ; i < actor . numQuadrants ; i += 1 ) { actor . quadrants [ i ] . changed = true ; } } param actor returns private findQuadrantRowStart ( actor ) { return Math . max ( Math . floor ( ( this . getTop ( actor ) - this . top ) / this . quadrantHeight ) - 1 , 0 ) ; } param actor returns private findQuadrantRowEnd ( actor ) { return Math . min ( Math . ceil ( ( this . getBottom ( actor ) - this . top ) / this . quadrantHeight ) , this . numRows - 1 ) ; } param actor returns private findQuadrantColStart ( actor ) { return Math . max ( Math . floor ( ( this . getLeft ( actor ) - this . left ) / this . quadrantWidth ) - 1 , 0 ) ; } param actor returns private findQuadrantColEnd ( actor ) { return Math . min ( Math . ceil ( ( this . getRight ( actor ) - this . left ) / this . quadrantWidth ) , this . numCols - 1 ) ; } }