@@ -24,6 +24,22 @@ import { Sprite } from './effects/sprite.js';
2424const min = Math . min ,
2525 max = Math . max ;
2626
27+ const cache = {
28+ getShapesAtPoint : {
29+ results : [ ] ,
30+ hitOptions : { } ,
31+ context : {
32+ x : 0 ,
33+ y : 0 ,
34+ visibleOnly : true ,
35+ results : null ,
36+ } ,
37+ single : [ ] ,
38+ output : [ ] ,
39+ empty : [ ] ,
40+ } ,
41+ } ;
42+
2743/**
2844 * @name Two.Group
2945 * @class
@@ -363,6 +379,111 @@ export class Group extends Shape {
363379 }
364380 }
365381
382+ static IsVisible = function ( element , visibleOnly ) {
383+ if ( ! visibleOnly ) {
384+ return true ;
385+ }
386+
387+ let current = element ;
388+ while ( current ) {
389+ if ( typeof current . visible === 'boolean' && ! current . visible ) {
390+ return false ;
391+ }
392+ if ( typeof current . opacity === 'number' && current . opacity <= 0 ) {
393+ return false ;
394+ }
395+ current = current . parent ;
396+ }
397+
398+ return true ;
399+ } ;
400+
401+ static VisitForHitTest = function (
402+ group ,
403+ context ,
404+ includeGroups ,
405+ filter ,
406+ hitOptions ,
407+ tolerance ,
408+ stopOnFirst
409+ ) {
410+ const children = group && group . children ;
411+ if ( ! children ) {
412+ return false ;
413+ }
414+
415+ const results = context . results ;
416+ for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
417+ const child = children [ i ] ;
418+
419+ if ( ! child ) {
420+ continue ;
421+ }
422+
423+ if ( ! Group . IsVisible ( child , context . visibleOnly ) ) {
424+ continue ;
425+ }
426+
427+ const rect =
428+ typeof child . getBoundingClientRect === 'function'
429+ ? child . getBoundingClientRect ( )
430+ : null ;
431+
432+ if ( rect && ! boundsContains ( rect , context . x , context . y , tolerance ) ) {
433+ continue ;
434+ }
435+
436+ if ( child instanceof Group ) {
437+ if (
438+ includeGroups &&
439+ ( ! filter || filter ( child ) ) &&
440+ typeof child . contains === 'function' &&
441+ child . contains ( context . x , context . y , hitOptions )
442+ ) {
443+ results . push ( child ) ;
444+ if ( stopOnFirst ) {
445+ return true ;
446+ }
447+ }
448+ if (
449+ Group . VisitForHitTest (
450+ child ,
451+ context ,
452+ includeGroups ,
453+ filter ,
454+ hitOptions ,
455+ tolerance ,
456+ stopOnFirst
457+ )
458+ ) {
459+ return true ;
460+ }
461+ continue ;
462+ }
463+
464+ if ( ! ( child instanceof Shape ) ) {
465+ continue ;
466+ }
467+
468+ if ( filter && ! filter ( child ) ) {
469+ continue ;
470+ }
471+
472+ if ( typeof child . contains !== 'function' ) {
473+ continue ;
474+ }
475+
476+ if ( child . contains ( context . x , context . y , hitOptions ) ) {
477+ results . push ( child ) ;
478+ if ( stopOnFirst ) {
479+ return true ;
480+ }
481+ }
482+ }
483+
484+ return false ;
485+ } ;
486+
366487 /**
367488 * @name Two.Group#copy
368489 * @function
@@ -490,127 +611,66 @@ export class Group extends Shape {
490611
491612 getShapesAtPoint ( x , y , options ) {
492613 const opts = options || { } ;
493- const mode =
494- opts . mode === 'deepest' || opts . deepest ? 'deepest' : 'all' ;
614+ const { results, hitOptions, context, single, empty } =
615+ cache . getShapesAtPoint ;
616+
617+ results . length = 0 ;
618+
619+ const mode = opts . mode === 'deepest' || opts . deepest ? 'deepest' : 'all' ;
495620 const visibleOnly = opts . visibleOnly !== false ;
496621 const includeGroups = ! ! opts . includeGroups ;
497622 const filter = typeof opts . filter === 'function' ? opts . filter : null ;
498- const tolerance =
499- typeof opts . tolerance === 'number' ? opts . tolerance : 0 ;
500-
501- const hitOptions = { } ;
623+ const tolerance = typeof opts . tolerance === 'number' ? opts . tolerance : 0 ;
502624
503625 if ( typeof opts . precision === 'number' ) {
504626 hitOptions . precision = opts . precision ;
627+ } else {
628+ delete hitOptions . precision ;
505629 }
506630 if ( typeof opts . fill !== 'undefined' ) {
507631 hitOptions . fill = opts . fill ;
632+ } else {
633+ delete hitOptions . fill ;
508634 }
509635 if ( typeof opts . stroke !== 'undefined' ) {
510636 hitOptions . stroke = opts . stroke ;
637+ } else {
638+ delete hitOptions . stroke ;
511639 }
512640 hitOptions . tolerance = tolerance ;
513641 hitOptions . ignoreVisibility = ! visibleOnly ;
514642
515643 const stopOnFirst = mode === 'deepest' ;
516- const results = [ ] ;
517-
518- const isVisible = ( element ) => {
519- if ( ! visibleOnly ) {
520- return true ;
521- }
522-
523- let current = element ;
524- while ( current ) {
525- if ( typeof current . visible === 'boolean' && ! current . visible ) {
526- return false ;
527- }
528- if (
529- typeof current . opacity === 'number' &&
530- current . opacity <= 0
531- ) {
532- return false ;
533- }
534- current = current . parent ;
535- }
536-
537- return true ;
538- } ;
539-
540- const visit = ( group ) => {
541- const children = group && group . children ;
542- if ( ! children ) {
543- return false ;
544- }
545-
546- for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
547- const child = children [ i ] ;
548-
549- if ( ! child ) {
550- continue ;
551- }
552-
553- if ( ! isVisible ( child ) ) {
554- continue ;
555- }
556-
557- const rect =
558- typeof child . getBoundingClientRect === 'function'
559- ? child . getBoundingClientRect ( )
560- : null ;
561-
562- if ( rect && ! boundsContains ( rect , x , y , tolerance ) ) {
563- continue ;
564- }
565-
566- if ( child instanceof Group ) {
567- if (
568- includeGroups &&
569- ( ! filter || filter ( child ) ) &&
570- typeof child . contains === 'function' &&
571- child . contains ( x , y , hitOptions )
572- ) {
573- results . push ( child ) ;
574- if ( stopOnFirst ) {
575- return true ;
576- }
577- }
578- if ( visit ( child ) ) {
579- return true ;
580- }
581- continue ;
582- }
583-
584- if ( ! ( child instanceof Shape ) ) {
585- continue ;
586- }
587-
588- if ( filter && ! filter ( child ) ) {
589- continue ;
590- }
591-
592- if ( typeof child . contains !== 'function' ) {
593- continue ;
594- }
595-
596- if ( child . contains ( x , y , hitOptions ) ) {
597- results . push ( child ) ;
598- if ( stopOnFirst ) {
599- return true ;
600- }
601- }
602- }
603-
604- return false ;
605- } ;
606-
607- visit ( this ) ;
644+ context . x = x ;
645+ context . y = y ;
646+ context . visibleOnly = visibleOnly ;
647+ context . results = results ;
648+
649+ Group . VisitForHitTest (
650+ this ,
651+ context ,
652+ includeGroups ,
653+ filter ,
654+ hitOptions ,
655+ tolerance ,
656+ stopOnFirst
657+ ) ;
608658
609659 if ( stopOnFirst ) {
610- return results . length > 0 ? [ results [ 0 ] ] : [ ] ;
660+ if ( results . length > 0 ) {
661+ const first = results [ 0 ] ;
662+ results . length = 0 ;
663+ single [ 0 ] = first ;
664+ single . length = 1 ;
665+ return single ;
666+ }
667+ empty . length = 0 ;
668+ return empty ;
611669 }
612670
613- return results ;
671+ const hits = results . slice ( ) ;
672+ results . length = 0 ;
673+ return hits ;
614674 }
615675
616676 /**
0 commit comments