@@ -144,7 +144,10 @@ class Circle {
144144}
145145
146146class QuadTree {
147- constructor ( boundary , capacity ) {
147+ DEFAULT_CAPACITY = 8 ;
148+ MAX_DEPTH = 8 ;
149+
150+ constructor ( boundary , capacity = this . DEFAULT_CAPACITY , _depth = 0 ) {
148151 if ( ! boundary ) {
149152 throw TypeError ( 'boundary is null or undefined' ) ;
150153 }
@@ -157,10 +160,13 @@ class QuadTree {
157160 if ( capacity < 1 ) {
158161 throw RangeError ( 'capacity must be greater than 0' ) ;
159162 }
163+
160164 this . boundary = boundary ;
161165 this . capacity = capacity ;
162166 this . points = [ ] ;
163167 this . divided = false ;
168+
169+ this . depth = _depth ;
164170 }
165171
166172 get children ( ) {
@@ -177,7 +183,6 @@ class QuadTree {
177183 }
178184
179185 static create ( ) {
180- let DEFAULT_CAPACITY = 8 ;
181186 if ( arguments . length === 0 ) {
182187 if ( typeof width === "undefined" ) {
183188 throw new TypeError ( "No global width defined" ) ;
@@ -186,119 +191,148 @@ class QuadTree {
186191 throw new TypeError ( "No global height defined" ) ;
187192 }
188193 let bounds = new Rectangle ( width / 2 , height / 2 , width , height ) ;
189- return new QuadTree ( bounds , DEFAULT_CAPACITY ) ;
194+ return new QuadTree ( bounds , this . DEFAULT_CAPACITY ) ;
190195 }
191196 if ( arguments [ 0 ] instanceof Rectangle ) {
192- let capacity = arguments [ 1 ] || DEFAULT_CAPACITY ;
197+ let capacity = arguments [ 1 ] || this . DEFAULT_CAPACITY ;
193198 return new QuadTree ( arguments [ 0 ] , capacity ) ;
194199 }
195200 if ( typeof arguments [ 0 ] === "number" &&
196201 typeof arguments [ 1 ] === "number" &&
197202 typeof arguments [ 2 ] === "number" &&
198203 typeof arguments [ 3 ] === "number" ) {
199- let capacity = arguments [ 4 ] || DEFAULT_CAPACITY ;
204+ let capacity = arguments [ 4 ] || this . DEFAULT_CAPACITY ;
200205 return new QuadTree ( new Rectangle ( arguments [ 0 ] , arguments [ 1 ] , arguments [ 2 ] , arguments [ 3 ] ) , capacity ) ;
201206 }
202207 throw new TypeError ( 'Invalid parameters' ) ;
203208 }
204209
205- toJSON ( isChild ) {
206- let obj = { points : this . points } ;
210+ toJSON ( ) {
211+ let obj = { } ;
212+
207213 if ( this . divided ) {
208- if ( this . northeast . points . length > 0 ) {
209- obj . ne = this . northeast . toJSON ( true ) ;
214+ if ( this . northeast . divided || this . northeast . points . length > 0 ) {
215+ obj . ne = this . northeast . toJSON ( ) ;
210216 }
211- if ( this . northwest . points . length > 0 ) {
212- obj . nw = this . northwest . toJSON ( true ) ;
217+ if ( this . northwest . divided || this . northwest . points . length > 0 ) {
218+ obj . nw = this . northwest . toJSON ( ) ;
213219 }
214- if ( this . southeast . points . length > 0 ) {
215- obj . se = this . southeast . toJSON ( true ) ;
220+ if ( this . southeast . divided || this . southeast . points . length > 0 ) {
221+ obj . se = this . southeast . toJSON ( ) ;
216222 }
217- if ( this . southwest . points . length > 0 ) {
218- obj . sw = this . southwest . toJSON ( true ) ;
223+ if ( this . southwest . divided || this . southwest . points . length > 0 ) {
224+ obj . sw = this . southwest . toJSON ( ) ;
219225 }
226+ } else {
227+ obj . points = this . points ;
220228 }
221- if ( ! isChild ) {
229+
230+ if ( this . depth === 0 ) {
222231 obj . capacity = this . capacity ;
223232 obj . x = this . boundary . x ;
224233 obj . y = this . boundary . y ;
225234 obj . w = this . boundary . w ;
226235 obj . h = this . boundary . h ;
227236 }
237+
228238 return obj ;
229239 }
230240
231- static fromJSON ( obj , x , y , w , h , capacity ) {
241+ static fromJSON ( obj , x , y , w , h , capacity , depth ) {
232242 if ( typeof x === "undefined" ) {
233243 if ( "x" in obj ) {
234244 x = obj . x ;
235245 y = obj . y ;
236246 w = obj . w ;
237247 h = obj . h ;
238248 capacity = obj . capacity ;
249+ depth = 0 ;
239250 } else {
240251 throw TypeError ( "JSON missing boundary information" ) ;
241252 }
242253 }
243- let qt = new QuadTree ( new Rectangle ( x , y , w , h ) , capacity ) ;
244- qt . points = obj . points ;
254+
255+ let qt = new QuadTree ( new Rectangle ( x , y , w , h ) , capacity , depth ) ;
256+
257+ qt . points = obj . points ?? null ;
258+ qt . divided = qt . points === null ; // points are set to null on subdivide
259+
245260 if (
246261 "ne" in obj ||
247262 "nw" in obj ||
248263 "se" in obj ||
249264 "sw" in obj
250265 ) {
251- let x = qt . boundary . x ;
252- let y = qt . boundary . y ;
253- let w = qt . boundary . w / 2 ;
254- let h = qt . boundary . h / 2 ;
266+ const x = qt . boundary . x ;
267+ const y = qt . boundary . y ;
268+ const w = qt . boundary . w / 2 ;
269+ const h = qt . boundary . h / 2 ;
255270
256271 if ( "ne" in obj ) {
257- qt . northeast = QuadTree . fromJSON ( obj . ne , x + w / 2 , y - h / 2 , w , h , capacity ) ;
272+ qt . northeast = QuadTree . fromJSON ( obj . ne , x + w / 2 , y - h / 2 , w , h , capacity , depth + 1 ) ;
258273 } else {
259- qt . northeast = new QuadTree ( qt . boundary . subdivide ( 'ne' ) , capacity ) ;
274+ qt . northeast = new QuadTree ( qt . boundary . subdivide ( 'ne' ) , capacity , depth + 1 ) ;
260275 }
261276 if ( "nw" in obj ) {
262- qt . northwest = QuadTree . fromJSON ( obj . nw , x - w / 2 , y - h / 2 , w , h , capacity ) ;
277+ qt . northwest = QuadTree . fromJSON ( obj . nw , x - w / 2 , y - h / 2 , w , h , capacity , depth + 1 ) ;
263278 } else {
264- qt . northwest = new QuadTree ( qt . boundary . subdivide ( 'nw' ) , capacity ) ;
279+ qt . northwest = new QuadTree ( qt . boundary . subdivide ( 'nw' ) , capacity , depth + 1 ) ;
265280 }
266281 if ( "se" in obj ) {
267- qt . southeast = QuadTree . fromJSON ( obj . se , x + w / 2 , y + h / 2 , w , h , capacity ) ;
282+ qt . southeast = QuadTree . fromJSON ( obj . se , x + w / 2 , y + h / 2 , w , h , capacity , depth + 1 ) ;
268283 } else {
269- qt . southeast = new QuadTree ( qt . boundary . subdivide ( 'se' ) , capacity ) ;
284+ qt . southeast = new QuadTree ( qt . boundary . subdivide ( 'se' ) , capacity , depth + 1 ) ;
270285 }
271286 if ( "sw" in obj ) {
272- qt . southwest = QuadTree . fromJSON ( obj . sw , x - w / 2 , y + h / 2 , w , h , capacity ) ;
287+ qt . southwest = QuadTree . fromJSON ( obj . sw , x - w / 2 , y + h / 2 , w , h , capacity , depth + 1 ) ;
273288 } else {
274- qt . southwest = new QuadTree ( qt . boundary . subdivide ( 'sw' ) , capacity ) ;
289+ qt . southwest = new QuadTree ( qt . boundary . subdivide ( 'sw' ) , capacity , depth + 1 ) ;
275290 }
276-
277- qt . divided = true ;
278291 }
292+
279293 return qt ;
280294 }
281295
282296 subdivide ( ) {
283- this . northeast = new QuadTree ( this . boundary . subdivide ( 'ne' ) , this . capacity ) ;
284- this . northwest = new QuadTree ( this . boundary . subdivide ( 'nw' ) , this . capacity ) ;
285- this . southeast = new QuadTree ( this . boundary . subdivide ( 'se' ) , this . capacity ) ;
286- this . southwest = new QuadTree ( this . boundary . subdivide ( 'sw' ) , this . capacity ) ;
297+ this . northeast = new QuadTree ( this . boundary . subdivide ( 'ne' ) , this . capacity , this . depth + 1 ) ;
298+ this . northwest = new QuadTree ( this . boundary . subdivide ( 'nw' ) , this . capacity , this . depth + 1 ) ;
299+ this . southeast = new QuadTree ( this . boundary . subdivide ( 'se' ) , this . capacity , this . depth + 1 ) ;
300+ this . southwest = new QuadTree ( this . boundary . subdivide ( 'sw' ) , this . capacity , this . depth + 1 ) ;
287301
288302 this . divided = true ;
303+
304+ // Move points to children.
305+ // This improves performance by placing points
306+ // in the smallest available rectangle.
307+ for ( const p of this . points ) {
308+ const inserted =
309+ this . northeast . insert ( p ) ||
310+ this . northwest . insert ( p ) ||
311+ this . southeast . insert ( p ) ||
312+ this . southwest . insert ( p ) ;
313+
314+ if ( ! inserted ) {
315+ throw RangeError ( 'capacity must be greater than 0' ) ;
316+ }
317+ }
318+
319+ this . points = null ;
289320 }
290321
291322 insert ( point ) {
292323 if ( ! this . boundary . contains ( point ) ) {
293324 return false ;
294325 }
295326
296- if ( this . points . length < this . capacity ) {
297- this . points . push ( point ) ;
298- return true ;
299- }
300-
301327 if ( ! this . divided ) {
328+ if (
329+ this . points . length < this . capacity ||
330+ this . depth === this . MAX_DEPTH
331+ ) {
332+ this . points . push ( point ) ;
333+ return true ;
334+ }
335+
302336 this . subdivide ( ) ;
303337 }
304338
@@ -319,16 +353,18 @@ class QuadTree {
319353 return found ;
320354 }
321355
322- for ( let p of this . points ) {
323- if ( range . contains ( p ) ) {
324- found . push ( p ) ;
325- }
326- }
327356 if ( this . divided ) {
328357 this . northwest . query ( range , found ) ;
329358 this . northeast . query ( range , found ) ;
330359 this . southwest . query ( range , found ) ;
331360 this . southeast . query ( range , found ) ;
361+ return found ;
362+ }
363+
364+ for ( const p of this . points ) {
365+ if ( range . contains ( p ) ) {
366+ found . push ( p ) ;
367+ }
332368 }
333369
334370 return found ;
@@ -346,46 +382,50 @@ class QuadTree {
346382 kNearest ( searchPoint , maxCount , sqMaxDistance , furthestSqDistance , foundSoFar ) {
347383 let found = [ ] ;
348384
349- this . children . sort ( ( a , b ) => a . boundary . sqDistanceFrom ( searchPoint ) - b . boundary . sqDistanceFrom ( searchPoint ) )
350- . forEach ( ( child ) => {
351- const sqDist = child . boundary . sqDistanceFrom ( searchPoint ) ;
352- if ( sqDist > sqMaxDistance ) {
353- return ;
354- } else if ( foundSoFar < maxCount || sqDist < furthestSqDistance ) {
355- const result = child . kNearest ( searchPoint , maxCount , sqMaxDistance , furthestSqDistance , foundSoFar ) ;
356- const childPoints = result . found ;
357- found = found . concat ( childPoints ) ;
358- foundSoFar += childPoints . length ;
359- furthestSqDistance = result . furthestSqDistance ;
360- }
361- } ) ;
362-
363- this . points
364- . sort ( ( a , b ) => a . sqDistanceFrom ( searchPoint ) - b . sqDistanceFrom ( searchPoint ) )
365- . forEach ( ( p ) => {
366- const sqDist = p . sqDistanceFrom ( searchPoint ) ;
367- if ( sqDist > sqMaxDistance ) {
368- return ;
369- } else if ( foundSoFar < maxCount || sqDist < furthestSqDistance ) {
370- found . push ( p ) ;
371- furthestSqDistance = Math . max ( sqDist , furthestSqDistance ) ;
372- foundSoFar ++ ;
373- }
374- } ) ;
385+ if ( this . divided ) {
386+ this . children
387+ . sort ( ( a , b ) => a . boundary . sqDistanceFrom ( searchPoint ) - b . boundary . sqDistanceFrom ( searchPoint ) )
388+ . forEach ( ( child ) => {
389+ const sqDistance = child . boundary . sqDistanceFrom ( searchPoint ) ;
390+ if ( sqDistance > sqMaxDistance ) {
391+ return ;
392+ } else if ( foundSoFar < maxCount || sqDistance < furthestSqDistance ) {
393+ const result = child . kNearest ( searchPoint , maxCount , sqMaxDistance , furthestSqDistance , foundSoFar ) ;
394+ const childPoints = result . found ;
395+ found = found . concat ( childPoints ) ;
396+ foundSoFar += childPoints . length ;
397+ furthestSqDistance = result . furthestSqDistance ;
398+ }
399+ } ) ;
400+ } else {
401+ this . points
402+ . sort ( ( a , b ) => a . sqDistanceFrom ( searchPoint ) - b . sqDistanceFrom ( searchPoint ) )
403+ . forEach ( ( p ) => {
404+ const sqDistance = p . sqDistanceFrom ( searchPoint ) ;
405+ if ( sqDistance > sqMaxDistance ) {
406+ return ;
407+ } else if ( foundSoFar < maxCount || sqDistance < furthestSqDistance ) {
408+ found . push ( p ) ;
409+ furthestSqDistance = Math . max ( sqDistance , furthestSqDistance ) ;
410+ foundSoFar ++ ;
411+ }
412+ } ) ;
413+ }
375414
376415 return {
377416 found : found . sort ( ( a , b ) => a . sqDistanceFrom ( searchPoint ) - b . sqDistanceFrom ( searchPoint ) ) . slice ( 0 , maxCount ) ,
378- furthestDistance : Math . sqrt ( furthestSqDistance ) ,
417+ furthestSqDistance : Math . sqrt ( furthestSqDistance ) ,
379418 } ;
380419 }
381420
382421 forEach ( fn ) {
383- this . points . forEach ( fn ) ;
384422 if ( this . divided ) {
385423 this . northeast . forEach ( fn ) ;
386424 this . northwest . forEach ( fn ) ;
387425 this . southeast . forEach ( fn ) ;
388426 this . southwest . forEach ( fn ) ;
427+ } else {
428+ this . points . forEach ( fn ) ;
389429 }
390430 }
391431
@@ -411,14 +451,16 @@ class QuadTree {
411451 }
412452
413453 get length ( ) {
414- let count = this . points . length ;
415454 if ( this . divided ) {
416- count += this . northwest . length ;
417- count += this . northeast . length ;
418- count += this . southwest . length ;
419- count += this . southeast . length ;
455+ return (
456+ this . northwest . length +
457+ this . northeast . length +
458+ this . southwest . length +
459+ this . southeast . length
460+ ) ;
420461 }
421- return count ;
462+
463+ return this . points . length ;
422464 }
423465}
424466
0 commit comments