1+ /* global MutationObserver */
2+
13var registerComponent = require ( '../core/component' ) . registerComponent ;
24var THREE = require ( '../lib/three' ) ;
35var utils = require ( '../utils/' ) ;
46
57var bind = utils . bind ;
8+ var warn = utils . debug ( 'components:raycaster:warn' ) ;
69
710var dummyVec = new THREE . Vector3 ( ) ;
811
12+ var SAFE_SELECTOR_RE = / ^ [ \w \s - . [ \] # ] + $ / ;
13+
914/**
1015 * Raycaster component.
1116 *
@@ -38,13 +43,14 @@ module.exports.Component = registerComponent('raycaster', {
3843 this . lineEndVec3 = new THREE . Vector3 ( ) ;
3944 this . unitLineEndVec3 = new THREE . Vector3 ( ) ;
4045 this . intersectedEls = [ ] ;
41- this . objects = null ;
46+ this . objects = [ ] ;
4247 this . prevCheckTime = undefined ;
4348 this . prevIntersectedEls = [ ] ;
4449 this . raycaster = new THREE . Raycaster ( ) ;
4550 this . updateOriginDirection ( ) ;
46- this . refreshObjects = bind ( this . refreshObjects , this ) ;
47- this . refreshOnceChildLoaded = bind ( this . refreshOnceChildLoaded , this ) ;
51+ this . setDirty = bind ( this . setDirty , this ) ;
52+ this . observer = new MutationObserver ( this . setDirty ) ;
53+ this . dirty = true ;
4854 } ,
4955
5056 /**
@@ -70,19 +76,27 @@ module.exports.Component = registerComponent('raycaster', {
7076 el . removeAttribute ( 'line' ) ;
7177 }
7278
73- this . refreshObjects ( ) ;
79+ if ( data . objects !== oldData . objects && ! SAFE_SELECTOR_RE . test ( data . objects ) ) {
80+ warn ( 'Selector "' + data . objects + '" may not update automatically with DOM changes.' ) ;
81+ }
82+
83+ this . setDirty ( ) ;
7484 } ,
7585
7686 play : function ( ) {
77- this . el . sceneEl . addEventListener ( 'loaded' , this . refreshObjects ) ;
78- this . el . sceneEl . addEventListener ( 'child-attached' , this . refreshOnceChildLoaded ) ;
79- this . el . sceneEl . addEventListener ( 'child-detached' , this . refreshObjects ) ;
87+ this . observer . observe ( this . el . sceneEl , {
88+ childList : true ,
89+ attributes : true ,
90+ subtree : true
91+ } ) ;
92+ this . el . sceneEl . addEventListener ( 'object3dset' , this . setDirty ) ;
93+ this . el . sceneEl . addEventListener ( 'object3dremove' , this . setDirty ) ;
8094 } ,
8195
8296 pause : function ( ) {
83- this . el . sceneEl . removeEventListener ( 'loaded' , this . refreshObjects ) ;
84- this . el . sceneEl . removeEventListener ( 'child-attached ' , this . refreshOnceChildLoaded ) ;
85- this . el . sceneEl . removeEventListener ( 'child-detached ' , this . refreshObjects ) ;
97+ this . observer . disconnect ( ) ;
98+ this . el . sceneEl . removeEventListener ( 'object3dset ' , this . setDirty ) ;
99+ this . el . sceneEl . removeEventListener ( 'object3dremove ' , this . setDirty ) ;
86100 } ,
87101
88102 remove : function ( ) {
@@ -92,53 +106,20 @@ module.exports.Component = registerComponent('raycaster', {
92106 } ,
93107
94108 /**
95- * Update list of objects to test for intersection once child is loaded .
109+ * Mark the object list as dirty, to be refreshed before next raycast .
96110 */
97- refreshOnceChildLoaded : function ( evt ) {
98- var self = this ;
99- var childEl = evt . detail . el ;
100- if ( ! childEl ) { return ; }
101- if ( childEl . hasLoaded ) {
102- this . refreshObjects ( ) ;
103- } else {
104- childEl . addEventListener ( 'loaded' , function nowRefresh ( evt ) {
105- childEl . removeEventListener ( 'loaded' , nowRefresh ) ;
106- self . refreshObjects ( ) ;
107- } ) ;
108- }
111+ setDirty : function ( ) {
112+ this . dirty = true ;
109113 } ,
110114
111115 /**
112116 * Update list of objects to test for intersection.
113117 */
114118 refreshObjects : function ( ) {
115- var children ;
116119 var data = this . data ;
117- var i ;
118- var objects ;
119- // Target entities.
120- var targetEls = data . objects ? this . el . sceneEl . querySelectorAll ( data . objects ) : null ;
121-
122- // Push meshes onto list of objects to intersect.
123- if ( targetEls ) {
124- objects = [ ] ;
125- for ( i = 0 ; i < targetEls . length ; i ++ ) {
126- objects . push ( targetEls [ i ] . object3D ) ;
127- }
128- } else {
129- // If objects not defined, intersect with everything.
130- objects = this . el . sceneEl . object3D . children ;
131- }
132-
133- this . objects = [ ] ;
134- for ( i = 0 ; i < objects . length ; i ++ ) {
135- // A-Frame wraps everything in THREE.Group. Grab the children.
136- children = objects [ i ] . children ;
137-
138- // Add the object3D children for non-recursive raycasting.
139- // If no children, refresh after entity loaded.
140- if ( children ) { this . objects . push . apply ( this . objects , children ) ; }
141- }
120+ var els = data . objects ? this . el . sceneEl . querySelectorAll ( data . objects ) : null ;
121+ this . objects = flattenChildrenShallow ( els ) ;
122+ this . dirty = false ;
142123 } ,
143124
144125 /**
@@ -163,6 +144,8 @@ module.exports.Component = registerComponent('raycaster', {
163144 // Update check time.
164145 this . prevCheckTime = time ;
165146
147+ if ( this . dirty ) { this . refreshObjects ( ) ; }
148+
166149 // Store old previously intersected entities.
167150 copyArray ( this . prevIntersectedEls , this . intersectedEls ) ;
168151
@@ -299,6 +282,41 @@ module.exports.Component = registerComponent('raycaster', {
299282 } ) ( )
300283} ) ;
301284
285+ /**
286+ * Returns children of each element's object3D group. Children are flattened
287+ * by one level, removing the THREE.Group wrapper, so that non-recursive
288+ * raycasting remains useful.
289+ *
290+ * @param {Array<Element> } els
291+ * @return {Array<THREE.Object3D> }
292+ */
293+ function flattenChildrenShallow ( els ) {
294+ var groups = [ ] ;
295+ var objects = [ ] ;
296+ var children ;
297+ var i ;
298+
299+ // Push meshes onto list of objects to intersect.
300+ if ( els ) {
301+ for ( i = 0 ; i < els . length ; i ++ ) {
302+ groups . push ( els [ i ] . object3D ) ;
303+ }
304+ } else {
305+ // If objects not defined, intersect with everything.
306+ groups = this . el . sceneEl . object3D . children ;
307+ }
308+
309+ // Each entity's root is a THREE.Group. Return the group's chilrden.
310+ for ( i = 0 ; i < groups . length ; i ++ ) {
311+ children = groups [ i ] . children ;
312+ if ( children && children . length ) {
313+ objects . push . apply ( objects , children ) ;
314+ }
315+ }
316+
317+ return objects ;
318+ }
319+
302320/**
303321 * Copy contents of one array to another without allocating new array.
304322 */
0 commit comments