SpawnTerritoryPoints.ts
1 import { PolygonPoint, QuickHull } from '../libraries/quickHull' 2 import { hachureLines, Line, Point } from '../libraries/hachureLines' 3 import { RoaringBitmap32, SerializationFormat } from 'roaring' 4 import { GeometryType, IGeometryGenerator, TerritoryPointsParameters } from './Interfaces' 5 import * as Buffer from 'buffer' 6 7 export type LinePoint = [ number, number ] 8 const maxDifference = Math.pow( 2, 16 ) 9 10 export class SpawnTerritoryPoints implements IGeometryGenerator { 11 minX: number = Number.MAX_SAFE_INTEGER 12 maxX: number = Number.MIN_SAFE_INTEGER 13 14 minY: number = Number.MAX_SAFE_INTEGER 15 maxY: number = Number.MIN_SAFE_INTEGER 16 17 data: RoaringBitmap32 = new RoaringBitmap32() 18 distance: number 19 20 constructor( points: Array<LinePoint>, distance: number = 100 ) { 21 this.distance = distance 22 let polygonPoints = points.map( ( point: LinePoint ) => { 23 return { 24 x: point[ 0 ], 25 y: point[ 1 ] 26 } 27 } ) 28 29 let vertices : Array<Point> = QuickHull( polygonPoints ).map( ( point: PolygonPoint ) : Point => { 30 this.minX = Math.min( point.x, this.minX ) 31 this.minY = Math.min( point.y, this.minY ) 32 33 this.maxX = Math.max( point.x, this.maxX ) 34 this.maxY = Math.max( point.y, this.maxY ) 35 36 return [ point.x, point.y ] 37 } ) 38 39 /* 40 Due to how data is packed we can only process data difference fitting into 16-bit value (two values fit into 32-bit integer). 41 Hence, check to see if there is data that would not be able to be processed. 42 */ 43 let xDifference = Math.abs( this.maxX - this.minX ) 44 let yDifference = Math.abs( this.maxY - this.minY ) 45 if ( xDifference > maxDifference || yDifference > maxDifference ) { 46 throw new Error( `Unable to process spawn territory with points: ${JSON.stringify( points )}, due to ${JSON.stringify( { xDifference, yDifference, maxDifference } )}` ) 47 } 48 49 this.distance = this.getAdjustedDistance( xDifference, yDifference, distance ) 50 51 let minimumDistance: number = this.distance / 2 52 hachureLines( vertices, this.distance, 0 ).forEach( ( line: Line ) : void => { 53 return this.getLinePoints( line[ 0 ], line[ 1 ], this.distance, minimumDistance, this.data, this.minX, this.minY ) 54 } ) 55 } 56 57 private getAdjustedDistance( xDifference : number, yDifference : number, distance: number ) : number { 58 let smallAreaSize = distance * 4 59 if ( xDifference < smallAreaSize || yDifference < smallAreaSize ) { 60 return Math.floor( distance * 0.4 ) 61 } 62 63 let extraLargeSize = distance * 100 64 if ( xDifference > extraLargeSize && yDifference > extraLargeSize ) { 65 return Math.floor( distance * 2 ) 66 } 67 68 let largeSize = distance * 70 69 if ( xDifference > largeSize && yDifference > largeSize ) { 70 return Math.floor( distance * 1.5 ) 71 } 72 73 let mediumSize = distance * 50 74 if ( xDifference > mediumSize && yDifference > mediumSize ) { 75 return Math.floor( distance * 1.2 ) 76 } 77 78 return distance 79 } 80 81 /* 82 Per https://math.stackexchange.com/questions/175896/finding-a-point-along-a-line-a-certain-distance-away-from-another-point 83 */ 84 private getLinePoints( start: LinePoint, finish: LinePoint, distance: number, minimumDistance: number, data: RoaringBitmap32, minX: number, minY: number ) : void { 85 let lineSize = Math.hypot( finish[ 0 ] - start[ 0 ], finish[ 1 ] - start[ 1 ] ) 86 87 this.addPoint( start[ 0 ], start[ 1 ], data, minX, minY ) 88 89 if ( lineSize < distance && lineSize > minimumDistance ) { 90 return this.addPoint( finish[ 0 ], finish[ 1 ], data, minX, minY ) 91 } 92 93 let currentDistance = distance 94 95 while ( currentDistance < lineSize ) { 96 let ratio = currentDistance / lineSize 97 let x = Math.round( ( 1 - ratio ) * start[ 0 ] + ratio * finish[ 0 ] ) 98 let y = Math.round( ( 1 - ratio ) * start[ 1 ] + ratio * finish[ 1 ] ) 99 100 this.addPoint( x, y, data, minX, minY ) 101 102 currentDistance += distance 103 } 104 } 105 106 private addPoint( originX: number, originY: number, data: RoaringBitmap32, minX: number, minY: number ) : void { 107 let x = originX - minX 108 let y = originY - minY 109 110 data.add( x + ( y << 16 ) ) 111 } 112 113 generatePoints(): Buffer { 114 // @ts-ignore 115 return this.data.serialize( SerializationFormat.croaring ) 116 } 117 118 getName(): string { 119 return 'SpawnTerritory' 120 } 121 122 getParameters(): TerritoryPointsParameters { 123 let centerX = Math.abs( this.maxX - this.minX ) / 2 + this.minX 124 let centerY = Math.abs( this.maxY - this.minY ) / 2 + this.minY 125 126 return { 127 centerX, 128 centerY, 129 distance: this.distance, 130 maxX: this.maxX, 131 maxY: this.maxY, 132 minX: this.minX, 133 minY: this.minY, 134 size: this.data.size, 135 } 136 } 137 138 getSize(): number { 139 return this.data.size 140 } 141 142 getType(): GeometryType { 143 return GeometryType.SpawnTerritory 144 } 145 146 }