aframe-orbit-controls-component
Version:
Orbiting Controls component for A-Frame VR
261 lines (220 loc) • 7.9 kB
JavaScript
// To avoid recalculation at every mouse movement tick
var PI_2 = Math.PI / 2;
AFRAME.registerComponent('orbit-controls', {
schema: {
enabled: { default: true }
},
init: function () {
this.previousPosition = new THREE.Vector3();
this.deltaPosition = new THREE.Vector3();
this.setupMouseControls();
this.setupHMDControls();
this.bindMethods();
var targetID = this.el.getAttribute('target');
this.distance = this.el.getAttribute('distance');
this.target3D = document.getElementById(targetID.replace('#', '')).object3D;
window.target = this.target3D;
window.camera = this.el;
},
update: function () {
if (!this.data.enabled) { return; }
this.controls.update();
this.updateOrientation();
this.updatePosition();
},
play: function () {
this.previousPosition.set(0, 0, 0);
this.addEventListeners();
},
pause: function () {
this.removeEventListeners();
},
tick: function (t) {
this.update();
},
remove: function () {
this.pause();
},
bindMethods: function () {
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseMove = this.onMouseMove.bind(this);
this.releaseMouse = this.releaseMouse.bind(this);
this.onTouchStart = this.onTouchStart.bind(this);
this.onTouchMove = this.onTouchMove.bind(this);
this.onTouchEnd = this.onTouchEnd.bind(this);
},
setupMouseControls: function () {
// The canvas where the scene is painted
this.mouseDown = false;
this.pitchObject = new THREE.Object3D();
this.yawObject = new THREE.Object3D();
this.yawObject.position.y = 10;
this.yawObject.add(this.pitchObject);
},
setupHMDControls: function () {
this.dolly = new THREE.Object3D();
this.euler = new THREE.Euler();
this.controls = new THREE.VRControls(this.dolly);
this.zeroQuaternion = new THREE.Quaternion();
},
addEventListeners: function () {
var sceneEl = this.el.sceneEl;
var canvasEl = sceneEl.canvas;
// listen for canvas to load.
if (!canvasEl) {
sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
return;
}
// Mouse Events
canvasEl.addEventListener('mousedown', this.onMouseDown, false);
canvasEl.addEventListener('mousemove', this.onMouseMove, false);
canvasEl.addEventListener('mouseup', this.releaseMouse, false);
canvasEl.addEventListener('mouseout', this.releaseMouse, false);
// Touch events
canvasEl.addEventListener('touchstart', this.onTouchStart);
canvasEl.addEventListener('touchmove', this.onTouchMove);
canvasEl.addEventListener('touchend', this.onTouchEnd);
},
removeEventListeners: function () {
var sceneEl = document.querySelector('a-scene');
var canvasEl = sceneEl && sceneEl.canvas;
if (!canvasEl) { return; }
// Mouse Events
canvasEl.removeEventListener('mousedown', this.onMouseDown);
canvasEl.removeEventListener('mousemove', this.onMouseMove);
canvasEl.removeEventListener('mouseup', this.releaseMouse);
canvasEl.removeEventListener('mouseout', this.releaseMouse);
// Touch events
canvasEl.removeEventListener('touchstart', this.onTouchStart);
canvasEl.removeEventListener('touchmove', this.onTouchMove);
canvasEl.removeEventListener('touchend', this.onTouchEnd);
},
updateOrientation: (function () {
var hmdEuler = new THREE.Euler();
hmdEuler.order = 'YXZ';
return function () {
var pitchObject = this.pitchObject;
var yawObject = this.yawObject;
var hmdQuaternion = this.calculateHMDQuaternion();
hmdEuler.setFromQuaternion(hmdQuaternion);
this.el.setAttribute('rotation', {
x: THREE.Math.radToDeg(hmdEuler.x) + THREE.Math.radToDeg(pitchObject.rotation.x),
y: THREE.Math.radToDeg(hmdEuler.y) + THREE.Math.radToDeg(yawObject.rotation.y),
z: THREE.Math.radToDeg(hmdEuler.z) + THREE.Math.radToDeg(yawObject.rotation.z)
});
};
})(),
calculateHMDQuaternion: (function () {
var hmdQuaternion = new THREE.Quaternion();
return function () {
var dolly = this.dolly;
if (!this.zeroed && !dolly.quaternion.equals(this.zeroQuaternion)) {
this.zeroOrientation();
this.zeroed = true;
}
hmdQuaternion.copy(this.zeroQuaternion).multiply(dolly.quaternion);
return hmdQuaternion;
};
})(),
updatePosition: (function () {
var position = new THREE.Vector3();
var quaternion = new THREE.Quaternion();
var scale = new THREE.Vector3();
return function () {
var el = this.el;
var deltaPosition = this.calculateDeltaPosition();
var currentPosition = this.target3D.position;
this.el.object3D.matrixWorld.decompose(position, quaternion, scale);
deltaPosition.applyQuaternion(quaternion);
// Reset the Camera to 0
el.setAttribute('position', {
x: this.target3D.position.x,
y: this.target3D.position.y,
z: this.target3D.position.z
});
var targetCameraPosition = camera.object3D.translateOnAxis( new THREE.Vector3(0,0,1), this.distance ).position;
el.setAttribute('position', {
x: targetCameraPosition.x,
y: targetCameraPosition.y,
z: targetCameraPosition.z
});
};
})(),
calculateDeltaPosition: function () {
var dolly = this.dolly;
var deltaPosition = this.deltaPosition;
var previousPosition = this.previousPosition;
deltaPosition.copy(dolly.position);
deltaPosition.sub(previousPosition);
previousPosition.copy(dolly.position);
return deltaPosition;
},
updateHMDQuaternion: (function () {
var hmdQuaternion = new THREE.Quaternion();
return function () {
var dolly = this.dolly;
this.controls.update();
if (!this.zeroed && !dolly.quaternion.equals(this.zeroQuaternion)) {
this.zeroOrientation();
this.zeroed = true;
}
hmdQuaternion.copy(this.zeroQuaternion).multiply(dolly.quaternion);
return hmdQuaternion;
};
})(),
zeroOrientation: function () {
var euler = new THREE.Euler();
euler.setFromQuaternion(this.dolly.quaternion.clone().inverse());
// Cancel out roll and pitch. We want to only reset yaw
euler.z = 0;
euler.x = 0;
this.zeroQuaternion.setFromEuler(euler);
},
onMouseMove: function (event) {
var pitchObject = this.pitchObject;
var yawObject = this.yawObject;
var previousMouseEvent = this.previousMouseEvent;
if (!this.mouseDown || !this.data.enabled) { return; }
var movementX = event.movementX || event.mozMovementX;
var movementY = event.movementY || event.mozMovementY;
if (movementX === undefined || movementY === undefined) {
movementX = event.screenX - previousMouseEvent.screenX;
movementY = event.screenY - previousMouseEvent.screenY;
}
this.previousMouseEvent = event;
yawObject.rotation.y -= movementX * 0.002;
pitchObject.rotation.x -= movementY * 0.002;
pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, pitchObject.rotation.x));
},
onMouseDown: function (event) {
this.mouseDown = true;
this.previousMouseEvent = event;
},
releaseMouse: function () {
this.mouseDown = false;
},
onTouchStart: function (e) {
if (e.touches.length !== 1) { return; }
this.touchStart = {
x: e.touches[0].pageX,
y: e.touches[0].pageY
};
this.touchStarted = true;
},
onTouchMove: function (e) {
var deltaY;
var yawObject = this.yawObject;
if (!this.touchStarted) { return; }
deltaY = 2 * Math.PI * (e.touches[0].pageX - this.touchStart.x) /
this.el.sceneEl.canvas.clientWidth;
// Limits touch orientaion to to yaw (y axis)
yawObject.rotation.y -= deltaY * 0.5;
this.touchStart = {
x: e.touches[0].pageX,
y: e.touches[0].pageY
};
},
onTouchEnd: function () {
this.touchStarted = false;
}
});