Skip to content

Commit

Permalink
add simple example of non-recursive raycaster and dynamically created…
Browse files Browse the repository at this point in the history
… box

add geometry so that raycaster actually adds something

wait until next tick to satisfy Firefox; remove extra tick
  • Loading branch information
machenmusik committed Feb 23, 2017
1 parent b186b11 commit ae0e455
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 48 deletions.
1 change: 1 addition & 0 deletions examples/index.html
Expand Up @@ -233,6 +233,7 @@ <h2>Tests</h2>
<li><a href="test/physical/">Physically-Based Materials</a></li>
<li><a href="test/pivot/">Pivot</a></li>
<li><a href="test/raycaster/">Raycaster</a></li>
<li><a href="test/raycaster/simple.html">Raycaster (Simple)</a></li>
<li><a href="test/shaders/">Shaders</a></li>
<li><a href="test/text/">Text</a></li>
<li><a href="test/text/anchors.html">Text Anchors</a></li>
Expand Down
34 changes: 34 additions & 0 deletions examples/test/raycaster/simple.html
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Raycaster</title>
<meta name="description" content="Raycaster - A-Frame">
<script src="../../../dist/aframe-master.js"></script>
</head>
<body>
<a-scene>
<a-entity camera look-controls wasd-controls raycaster="recursive: false; interval: 1000"></a-entity>
</a-scene>

<script>
var el = document.createElement('a-entity');
el.setAttribute('geometry', 'primitive: box');
el.setAttribute('material', 'color: #000');
el.setAttribute('position', '0 0 -2');
document.querySelector('a-scene').appendChild(el);

document.querySelector('a-entity[geometry]').addEventListener('raycaster-intersected', function (evt) {
// getting two intersection events per tick; same element, different faces...
console.log('raycaster-intersected ' + evt.detail.el.outerHTML);
this.setAttribute('material', 'color: #7f7');
});

document.querySelector('a-entity[geometry]').addEventListener('raycaster-intersected-cleared', function (evt) {
// getting two intersection events per tick; same element, different faces...
console.log('raycaster-intersected-cleared ' + evt.detail.el.outerHTML);
this.setAttribute('material', 'color: #f77');
});
</script>
</body>
</html>
54 changes: 22 additions & 32 deletions src/components/raycaster.js
Expand Up @@ -33,15 +33,18 @@ module.exports.Component = registerComponent('raycaster', {
this.raycaster = new THREE.Raycaster();
this.updateOriginDirection();
this.refreshObjects = bind(this.refreshObjects, this);
this.refreshOnceChildLoaded = bind(this.refreshOnceChildLoaded, this);
},

play: function () {
this.el.sceneEl.addEventListener('child-attached', this.refreshObjects);
this.el.sceneEl.addEventListener('loaded', this.refreshObjects);
this.el.sceneEl.addEventListener('child-attached', this.refreshOnceChildLoaded);
this.el.sceneEl.addEventListener('child-detached', this.refreshObjects);
},

pause: function () {
this.el.sceneEl.removeEventListener('child-attached', this.refreshObjects);
this.el.sceneEl.removeEventListener('loaded', this.refreshObjects);
this.el.sceneEl.removeEventListener('child-attached', this.refreshOnceChildLoaded);
this.el.sceneEl.removeEventListener('child-detached', this.refreshObjects);
},

Expand All @@ -59,6 +62,21 @@ module.exports.Component = registerComponent('raycaster', {
this.refreshObjects();
},

/**
* Update list of objects to test for intersection once child is loaded.
*/
refreshOnceChildLoaded: function (evt) {
var self = this;
var el = evt.detail.el;
if (!el) { return; }
if (el.hasLoaded) { this.refreshObjects(); } else {
el.addEventListener('loaded', function nowRefresh (evt) {
el.removeEventListener('loaded', nowRefresh);
self.refreshObjects();
});
}
},

/**
* Update list of objects to test for intersection.
*/
Expand Down Expand Up @@ -89,35 +107,8 @@ module.exports.Component = registerComponent('raycaster', {
// Add the object3D's children so non-recursive raycasting will work correctly.
for (j = 0; j < children.length; j++) { this.objects.push(children[j]); }
} else {
// If there aren't any children, then at least add the object3D itself;
// in that case, only recursive raycasting will function properly.
// Unhappily, this is the default case :-/
object.needsRefresh = true;
this.objects.push(object);
}
}
},

/**
* Update Groups in list of objects to test for intersection.
*/
refreshGroupObjects: function () {
var object3D;
if (!this.objects) { return; }
for (var i = 0; i < this.objects.length; i++) {
object3D = this.objects[i];
if (!object3D.needsRefresh) { continue; }

// If the object is linked to an element and its object3D changed, use that
if (object3D.el && object3D.el.object3D !== object3D) {
object3D = object3D.el.object3D;
}

// If the object is a Group, and has one child, use that instead.
if (object3D.type === 'Group' && object3D.children.length === 1) {
this.objects[i] = object3D = object3D.children[0];
// Only do this check once (if it works).
object3D.needsRefresh = false;
// If there aren't any children, then until a refresh after geometry loads,
// raycast won't see this object... but that should happen automatically.
}
}
},
Expand All @@ -144,7 +135,6 @@ module.exports.Component = registerComponent('raycaster', {

// Raycast.
this.updateOriginDirection();
this.refreshGroupObjects();
intersections = this.raycaster.intersectObjects(this.objects, data.recursive);

// Only keep intersections against objects that have a reference to an entity.
Expand Down
33 changes: 17 additions & 16 deletions tests/components/raycaster.test.js
Expand Up @@ -104,18 +104,18 @@ suite('raycaster', function () {
var newEl = document.createElement('a-entity');
var numObjects = el.components.raycaster.objects.length;
var sceneEl = this.el.sceneEl;
// add some geometry so raycast will actually work
newEl.setAttribute('geometry', 'primitive: box');
sceneEl.addEventListener('child-attached', eventuallyDoAssert);
sceneEl.appendChild(newEl);

function eventuallyDoAssert () {
if (newEl.hasLoaded) { doAssert(); } else { newEl.addEventListener('loaded', doAssert); }
}
function doAssert () {
// FIXME: getting 5 not equaling 2
// I think the extra 3 are default injection of camera and two lights
assert.equal(el.components.raycaster.objects.length, numObjects + 1);
sceneEl.removeEventListener('child-attached', eventuallyDoAssert);
newEl.removeEventListener('loaded', doAssert);
assert.equal(el.components.raycaster.objects.length, numObjects + 1);
done();
}
});
Expand All @@ -125,6 +125,8 @@ suite('raycaster', function () {
var newEl = document.createElement('a-entity');
var numObjects = el.components.raycaster.objects.length;
var sceneEl = this.el.sceneEl;
// add some geometry so raycast will actually work
newEl.setAttribute('geometry', 'primitive: box');
sceneEl.addEventListener('child-detached', doAssert);
sceneEl.addEventListener('child-attached', eventuallyDoRemove);
sceneEl.appendChild(newEl);
Expand All @@ -134,8 +136,6 @@ suite('raycaster', function () {
}
function doRemove () { sceneEl.removeChild(newEl); }
function doAssert () {
// FIXME: getting 4 not equaling 1
// I think the extra 3 are default injection of camera and two lights
assert.equal(el.components.raycaster.objects.length, numObjects);
sceneEl.removeEventListener('child-attached', eventuallyDoRemove);
sceneEl.removeEventListener('child-detached', doAssert);
Expand All @@ -156,18 +156,16 @@ suite('raycaster', function () {
});

targetEl.setAttribute('geometry', 'primitive: box; depth: 1; height: 1; width: 1;');
targetEl.setAttribute('material', '');
targetEl.setAttribute('position', '0 0 -1');
targetEl.addEventListener('loaded', function finishSetup () {
done();
});
el.sceneEl.appendChild(targetEl);
// `npm run test:forefox` needs the timeout for the tests to succeed.
function finishSetup () { setTimeout(function () { done(); }, 0); }
if (targetEl.hasLoaded) { finishSetup(); } else { targetEl.addEventListener('loaded', finishSetup); }
});

test('can catch basic intersection', function (done) {
this.targetEl.addEventListener('raycaster-intersected', function () { done(); });
this.el.sceneEl.tick();
this.el.sceneEl.tick();
});

test('updates intersectedEls', function (done) {
Expand Down Expand Up @@ -246,14 +244,17 @@ suite('raycaster', function () {
});

targetEl.setAttribute('geometry', 'primitive: box; depth: 1; height: 1; width: 1;');
targetEl.setAttribute('material', '');
targetEl.setAttribute('position', '0 0 -1');
targetEl.addEventListener('loaded', function finishSetup () {
// The object to check raycast against should reference the entity.
assert.equal(targetEl, targetEl.object3D.children[0].el);
done();
});
el.sceneEl.appendChild(targetEl);
// `npm run test:forefox` needs the timeout for the tests to succeed.
function finishSetup () {
setTimeout(function () {
// The object to check raycast against should reference the entity.
assert.equal(targetEl, targetEl.object3D.children[0].el);
done();
}, 0);
}
if (targetEl.hasLoaded) { finishSetup(); } else { targetEl.addEventListener('loaded', finishSetup); }
});

test('can catch basic intersection', function (done) {
Expand Down

0 comments on commit ae0e455

Please sign in to comment.