aframe-look-at-component
Version:
Rotate an entity to face towards another entity in A-Frame
127 lines (108 loc) • 3.78 kB
JavaScript
var debug = AFRAME.utils.debug;
var coordinates = AFRAME.utils.coordinates;
var warn = debug('components:look-at:warn');
var isCoordinates = coordinates.isCoordinates || coordinates.isCoordinate;
delete AFRAME.components['look-at'];
/**
* Look-at component.
*
* Modifies rotation to either track another entity OR do a one-time turn towards a position
* vector.
*
* If tracking an object via setting the component value via a selector, look-at will register
* a behavior to the scene to update rotation on every tick.
*/
AFRAME.registerComponent('look-at', {
schema: {
default: '0 0 0',
parse: function (value) {
// A static position to look at.
if (isCoordinates(value) || typeof value === 'object') {
return coordinates.parse(value);
}
// A selector to a target entity.
return value;
},
stringify: function (data) {
if (typeof data === 'object') {
return coordinates.stringify(data);
}
return data;
}
},
init: function () {
this.target3D = null;
this.vector = new THREE.Vector3();
this.cameraListener = AFRAME.utils.bind(this.cameraListener, this);
this.el.addEventListener('componentinitialized', this.cameraListener);
this.el.addEventListener('componentremoved', this.cameraListener);
},
/**
* If tracking an object, this will be called on every tick.
* If looking at a position vector, this will only be called once (until further updates).
*/
update: function () {
var self = this;
var target = self.data;
var targetEl;
// No longer looking at anything (i.e., look-at="").
if (!target || (typeof target === 'object' && !Object.keys(target).length)) {
return self.remove();
}
// Look at a position.
if (typeof target === 'object') {
return this.lookAt(new THREE.Vector3(target.x, target.y, target.z));
}
// Assume target is a string.
// Query for the element, grab its object3D, then register a behavior on the scene to
// track the target on every tick.
targetEl = self.el.sceneEl.querySelector(target);
if (!targetEl) {
warn('"' + target + '" does not point to a valid entity to look-at');
return;
}
if (!targetEl.hasLoaded) {
return targetEl.addEventListener('loaded', function () {
self.beginTracking(targetEl);
});
}
return self.beginTracking(targetEl);
},
tick: (function () {
var vec3 = new THREE.Vector3();
return function (t) {
// Track target object position. Depends on parent object keeping global transforms up
// to state with updateMatrixWorld(). In practice, this is handled by the renderer.
var target3D = this.target3D;
if (target3D) {
target3D.getWorldPosition(vec3);
this.lookAt(vec3);
}
}
})(),
remove: function () {
this.el.removeEventListener('componentinitialized', this.cameraListener);
this.el.removeEventListener('componentremoved', this.cameraListener);
},
beginTracking: function (targetEl) {
this.target3D = targetEl.object3D;
},
cameraListener: function (e) {
if (e.detail && e.detail.name === 'camera') {
this.update();
}
},
lookAt: function (position) {
var vector = this.vector;
var object3D = this.el.object3D;
if (this.el.getObject3D('camera')) {
// Flip the vector to -z, looking away from target for camera entities. When using
// lookat from THREE camera objects, this is applied for you, but since the camera is
// nested into a Object3D, we need to apply this manually.
vector.subVectors(object3D.position, position).add(object3D.position);
} else {
vector.copy(position);
}
object3D.lookAt(vector);
}
});