Skip to content

Commit

Permalink
raycaster fixes and perf improvements (fixes aframevr#3428, aframevr#…
Browse files Browse the repository at this point in the history
…3429) (aframevr#3250)

* remove object allocations from cursor component

* only emit raycaster intersection/intersected events if newly intersected

* fix disabling of raycaster.showLine

* fix disabling raycaster.line getting overridden by tick line updater

* remove arrow fn
  • Loading branch information
ngokevin authored and dmarcos committed Dec 4, 2017
1 parent 931210e commit 9687531
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 51 deletions.
16 changes: 13 additions & 3 deletions src/components/cursor.js
Expand Up @@ -61,6 +61,9 @@ module.exports.Component = registerComponent('cursor', {
self.canvasBounds = self.el.sceneEl.canvas.getBoundingClientRect();
}, 200);

this.eventDetail = {};
this.intersectedEventDetail = {cursorEl: this.el};

// Bind methods.
this.onCursorDown = bind(this.onCursorDown, this);
this.onCursorUp = bind(this.onCursorUp, this);
Expand Down Expand Up @@ -235,7 +238,8 @@ module.exports.Component = registerComponent('cursor', {
// If intersected entity has changed since the cursorDown, still emit mouseUp on the
// previously cursorUp entity.
if (this.cursorDownEl && this.cursorDownEl !== this.intersectedEl) {
this.cursorDownEl.emit(EVENTS.MOUSEUP, {cursorEl: this.el, intersection: null});
this.intersectedEventDetail.intersection = null;
this.cursorDownEl.emit(EVENTS.MOUSEUP, this.intersectedEventDetail);
}

if (!this.data.fuse && this.intersectedEl && this.cursorDownEl === this.intersectedEl) {
Expand Down Expand Up @@ -329,8 +333,14 @@ module.exports.Component = registerComponent('cursor', {
var el = this.el;
var intersectedEl = this.intersectedEl;
var intersection = this.intersection;
el.emit(evtName, {intersectedEl: intersectedEl, intersection: intersection});

this.eventDetail.intersectedEl = intersectedEl;
this.eventDetail.intersection = intersection;
el.emit(evtName, this.eventDetail);

if (!intersectedEl) { return; }
intersectedEl.emit(evtName, {cursorEl: el, intersection: intersection});

this.intersectedEventDetail.intersection = intersection;
intersectedEl.emit(evtName, this.intersectedEventDetail);
}
});
77 changes: 47 additions & 30 deletions src/components/raycaster.js
Expand Up @@ -53,6 +53,8 @@ module.exports.Component = registerComponent('raycaster', {
this.clearedIntersectedEls = [];
this.unitLineEndVec3 = new THREE.Vector3();
this.intersectedEls = [];
this.newIntersectedEls = [];
this.newIntersections = [];
this.objects = [];
this.prevCheckTime = undefined;
this.prevIntersectedEls = [];
Expand All @@ -61,10 +63,12 @@ module.exports.Component = registerComponent('raycaster', {
this.setDirty = this.setDirty.bind(this);
this.observer = new MutationObserver(this.setDirty);
this.dirty = true;
this.intersectionClearedDetail = {clearedEls: this.clearedIntersectedEls};
this.lineEndVec3 = new THREE.Vector3();
this.otherLineEndVec3 = new THREE.Vector3();
this.lineData = {end: this.lineEndVec3};

this.intersectedClearedDetail = {el: this.el};
this.intersectionClearedDetail = {clearedEls: this.clearedIntersectedEls};
},

/**
Expand All @@ -82,10 +86,11 @@ module.exports.Component = registerComponent('raycaster', {
// Draw line.
if (data.showLine &&
(data.far !== oldData.far || data.origin !== oldData.origin ||
data.direction !== oldData.direction || data.showLine !== oldData.showLine)) {
data.direction !== oldData.direction || !oldData.showLine)) {
this.unitLineEndVec3.copy(data.origin).add(data.direction).normalize();
this.drawLine();
}

if (!data.showLine && oldData.showLine) {
el.removeAttribute('line');
}
Expand Down Expand Up @@ -179,8 +184,11 @@ module.exports.Component = registerComponent('raycaster', {
var intersectedEls = this.intersectedEls;
var intersection;
var lineLength;
var newIntersectedEls = this.newIntersectedEls;
var newIntersections = this.newIntersections;
var prevIntersectedEls = this.prevIntersectedEls;
var rawIntersections;
var self = this;

if (!this.data.enabled) { return; }

Expand All @@ -196,6 +204,7 @@ module.exports.Component = registerComponent('raycaster', {

// Only keep intersections against objects that have a reference to an entity.
intersections.length = 0;
intersectedEls.length = 0;
for (i = 0; i < rawIntersections.length; i++) {
intersection = rawIntersections[i];
// Don't intersect with own line.
Expand All @@ -204,53 +213,61 @@ module.exports.Component = registerComponent('raycaster', {
}
if (intersection.object.el) {
intersections.push(intersection);
intersectedEls.push(intersection.object.el);
}
}

// Update intersectedEls.
intersectedEls.length = intersections.length;
for (i = 0; i < intersections.length; i++) {
intersectedEls[i] = intersections[i].object.el;
}

// Emit intersected on intersected entity per intersected entity.
// Get newly intersected entities.
newIntersections.length = 0;
newIntersectedEls.length = 0;
for (i = 0; i < intersections.length; i++) {
intersections[i].object.el.emit('raycaster-intersected', {
el: el,
intersection: intersections[i]
});
}

// Emit all intersections at once on raycasting entity.
if (intersections.length) {
el.emit('raycaster-intersection', {
els: intersectedEls,
intersections: intersections
});
if (prevIntersectedEls.indexOf(intersections[i].object.el) === -1) {
newIntersections.push(intersections[i]);
newIntersectedEls.push(intersections[i].object.el);
}
}

// Emit intersection cleared on both entities per formerly intersected entity.
clearedIntersectedEls.length = 0;
for (i = 0; i < prevIntersectedEls.length; i++) {
if (intersectedEls.indexOf(prevIntersectedEls[i]) !== -1) { continue; }
prevIntersectedEls[i].emit('raycaster-intersected-cleared', {el: el});
prevIntersectedEls[i].emit('raycaster-intersected-cleared',
this.intersectedClearedDetail);
clearedIntersectedEls.push(prevIntersectedEls[i]);
}
if (clearedIntersectedEls.length) {
el.emit('raycaster-intersection-cleared', this.intersectionClearedDetail);
}

// Emit intersected on intersected entity per intersected entity.
for (i = 0; i < newIntersectedEls.length; i++) {
newIntersectedEls[i].emit('raycaster-intersected', {
el: el,
intersection: newIntersections[i]
});
}

// Emit all intersections at once on raycasting entity.
if (newIntersections.length) {
el.emit('raycaster-intersection', {
els: newIntersectedEls,
intersections: newIntersections
});
}

// Update line length.
if (data.showLine) {
if (intersections.length) {
if (intersections[0].object.el === el && intersections[1]) {
lineLength = intersections[1].distance;
} else {
lineLength = intersections[0].distance;
setTimeout(function () {
if (self.data.showLine) {
if (intersections.length) {
if (intersections[0].object.el === el && intersections[1]) {
lineLength = intersections[1].distance;
} else {
lineLength = intersections[0].distance;
}
}
self.drawLine(lineLength);
}
this.drawLine(lineLength);
}
});
};
})(),

Expand Down
74 changes: 56 additions & 18 deletions tests/components/raycaster.test.js
Expand Up @@ -184,6 +184,7 @@ suite('raycaster', function () {
el.setAttribute('position', '0 0 1');
el.setAttribute('raycaster', {near: 0.1, far: 10});

targetEl.setAttribute('id', 'target');
targetEl.setAttribute('geometry', 'primitive: box');
targetEl.setAttribute('position', '0 0 -1');
targetEl.addEventListener('loaded', function () {
Expand Down Expand Up @@ -217,6 +218,35 @@ suite('raycaster', function () {
component.tick();
});

test('does not re-emit raycaster-intersection if no new intersections', function (done) {
var count = 0;
var raycasterEl = el;
raycasterEl.addEventListener('raycaster-intersection', function () {
count++;
});
component.tick();
component.tick();
setTimeout(() => {
assert.equal(count, 1);
done();
});
});

test('does not re-emit raycaster-intersected if previously intersecting', function (done) {
var count = 0;
targetEl.addEventListener('raycaster-intersected', function (evt) {
count++;
});
component.tick();
component.tick();
component.tick();
setTimeout(() => {
// 2 because the raycaster hits the box in two points.
assert.equal(count, 2);
done();
});
});

test('emits event on intersected entity with details', function (done) {
var raycasterEl = el;
targetEl.addEventListener('raycaster-intersected', function (evt) {
Expand Down Expand Up @@ -244,7 +274,7 @@ suite('raycaster', function () {
var raycasterEl = el;
targetEl.addEventListener('raycaster-intersected', function () {
// Point raycaster somewhere else.
raycasterEl.setAttribute('rotation', '90 0 0');
raycasterEl.setAttribute('rotation', '-90 0 0');
targetEl.addEventListener('raycaster-intersected-cleared', function (evt) {
assert.equal(evt.detail.el, raycasterEl);
done();
Expand Down Expand Up @@ -433,14 +463,18 @@ suite('raycaster', function () {
el.sceneEl.object3D.updateMatrixWorld();
component.refreshObjects();
component.tick();
line = el.getAttribute('line');
assert.equal(new THREE.Vector3().copy(line.start).sub(line.end).length(), 24.5);
box.parentNode.removeChild(box);
setTimeout(() => {
component.tick();
line = el.getAttribute('line');
assert.equal(new THREE.Vector3().copy(line.start).sub(line.end).length(), 1000);
done();
assert.equal(new THREE.Vector3().copy(line.start).sub(line.end).length(), 24.5);
box.parentNode.removeChild(box);
setTimeout(() => {
component.tick();
setTimeout(() => {
line = el.getAttribute('line');
assert.equal(new THREE.Vector3().copy(line.start).sub(line.end).length(), 1000);
done();
});
});
});
});
});
Expand All @@ -458,7 +492,7 @@ suite('raycaster', function () {

rayEl2 = document.createElement('a-entity');
rayEl2.setAttribute('raycaster', {
direction: '0 -1 -1',
direction: '0 0 -1',
origin: '0 0 0',
showLine: true,
objects: '#target'
Expand All @@ -482,19 +516,23 @@ suite('raycaster', function () {
component.tick();
rayEl2.components.raycaster.tick();
rayEl2.components.raycaster.tick();
// ensure component and geometry are unaffected by other raycaster
line = el.getAttribute('line');
assert.equal(new THREE.Vector3().copy(line.start).sub(line.end).length(), 24.5);
lineEnd.fromArray(lineArray, 3);
assert.equal(lineStart.fromArray(lineArray).sub(lineEnd).length(), 24.5);
box.parentNode.removeChild(box);
setTimeout(() => {
component.tick();
// Ensure component and geometry are unaffected by other raycaster
line = el.getAttribute('line');
assert.equal(new THREE.Vector3().copy(line.start).sub(line.end).length(), 1000);
assert.equal(new THREE.Vector3().copy(line.start).sub(line.end).length(), 24.5);
lineEnd.fromArray(lineArray, 3);
assert.equal(lineStart.fromArray(lineArray).sub(lineEnd).length(), 1000);
done();
assert.equal(lineStart.fromArray(lineArray).sub(lineEnd).length(), 24.5);
box.parentNode.removeChild(box);
setTimeout(() => {
component.tick();
setTimeout(() => {
line = el.getAttribute('line');
assert.equal(new THREE.Vector3().copy(line.start).sub(line.end).length(), 1000);
lineEnd.fromArray(lineArray, 3);
assert.equal(lineStart.fromArray(lineArray).sub(lineEnd).length(), 1000);
done();
});
});
});
});
});
Expand Down

0 comments on commit 9687531

Please sign in to comment.