aframe-motion-capture-components
Version:
A-Frame motion capture components
162 lines (139 loc) • 5.21 kB
JavaScript
/* globals AFRAME THREE */
/**
* Raycaster component.
*
* Pass options to three.js Raycaster including which objects to test.
* Poll for intersections.
* Emit event on origin entity and on target entity on intersect.
*
* @member {array} intersectedEls - List of currently intersected entities.
* @member {array} objects - Cached list of meshes to intersect.
* @member {number} prevCheckTime - Previous time intersection was checked. To help interval.
* @member {object} raycaster - three.js Raycaster.
*/
AFRAME.registerComponent('ui-raycaster', {
schema: {
far: {default: Infinity}, // Infinity.
interval: {default: 100},
near: {default: 0},
objects: {default: ''},
recursive: {default: true},
rotation: {default: 0}
},
init: function () {
this.direction = new THREE.Vector3();
this.intersectedEls = [];
this.objects = null;
this.prevCheckTime = undefined;
this.raycaster = new THREE.Raycaster();
this.updateOriginDirection();
this.refreshObjects = this.refreshObjects.bind(this);
},
play: function () {
this.el.sceneEl.addEventListener('child-attached', this.refreshObjects);
this.el.sceneEl.addEventListener('child-detached', this.refreshObjects);
},
pause: function () {
this.el.sceneEl.removeEventListener('child-attached', this.refreshObjects);
this.el.sceneEl.removeEventListener('child-detached', this.refreshObjects);
},
/**
* Create or update raycaster object.
*/
update: function () {
var data = this.data;
var raycaster = this.raycaster;
// Set raycaster properties.
raycaster.far = data.far;
raycaster.near = data.near;
this.refreshObjects();
},
/**
* Update list of objects to test for intersection.
*/
refreshObjects: function () {
var data = this.data;
var i;
var objectEls;
// Push meshes onto list of objects to intersect.
if (data.objects) {
objectEls = this.el.sceneEl.querySelectorAll(data.objects);
this.objects = [];
for (i = 0; i < objectEls.length; i++) {
this.objects.push(objectEls[i].object3D);
}
return;
}
// If objects not defined, intersect with everything.
this.objects = this.el.sceneEl.object3D.children;
},
/**
* Check for intersections and cleared intersections on an interval.
*/
tick: function (time) {
var el = this.el;
var data = this.data;
var intersectedEls;
var intersections;
var prevCheckTime = this.prevCheckTime;
var prevIntersectedEls;
// Only check for intersection if interval time has passed.
if (prevCheckTime && (time - prevCheckTime < data.interval)) { return; }
// Store old previously intersected entities.
prevIntersectedEls = this.intersectedEls.slice();
// Raycast.
this.updateOriginDirection();
intersections = this.raycaster.intersectObjects(this.objects, data.recursive);
// Only keep intersections against objects that have a reference to an entity.
intersections = intersections.filter(function hasEl (intersection) {
return !!intersection.object.el;
});
// Update intersectedEls.
intersectedEls = this.intersectedEls = intersections.map(function getEl (intersection) {
return intersection.object.el;
});
// Emit intersected on intersected entity per intersected entity.
intersections.forEach(function emitEvents (intersection) {
var intersectedEl = intersection.object.el;
intersectedEl.addState('hovered');
intersectedEl.emit('raycaster-intersected', {el: el, intersection: intersection});
});
// Emit all intersections at once on raycasting entity.
if (intersections.length) {
el.emit('raycaster-intersection', {
els: intersectedEls,
intersections: intersections
});
}
// Emit intersection cleared on both entities per formerly intersected entity.
prevIntersectedEls.forEach(function checkStillIntersected (intersectedEl) {
if (intersectedEls.indexOf(intersectedEl) !== -1) { return; }
intersectedEl.removeState('hovered');
el.emit('raycaster-intersection-cleared', {el: intersectedEl});
intersectedEl.emit('raycaster-intersected-cleared', {el: el});
});
},
/**
* Set origin and direction of raycaster using entity position and rotation.
*/
updateOriginDirection: (function () {
var directionHelper = new THREE.Quaternion();
var originVec3 = new THREE.Vector3();
var scaleDummy = new THREE.Vector3();
// Closure to make quaternion/vector3 objects private.
return function updateOriginDirection () {
var el = this.el;
var direction = this.direction;
var object3D = el.object3D;
// Update matrix world.
object3D.updateMatrixWorld();
// Grab the position and rotation.
object3D.matrixWorld.decompose(originVec3, directionHelper, scaleDummy);
// Apply rotation to a 0, 0, -1 vector.
direction.set(0, 0, -1);
direction.applyAxisAngle(new THREE.Vector3(1, 0, 0), (this.data.rotation / 360) * 2 * Math.PI);
direction.applyQuaternion(directionHelper);
this.raycaster.set(originVec3, direction);
};
})()
});