import Base from '../core/Base';
import Vector3 from '../math/Vector3';
import vendor from '../core/vendor';
var doc = typeof document === 'undefined' ? {} : document;
/**
* @constructor clay.plugin.FreeControl
* @example
* var control = new clay.plugin.FreeControl({
* target: camera,
* domElement: renderer.canvas
* });
* ...
* timeline.on('frame', function(frameTime) {
* control.update(frameTime);
* renderer.render(scene, camera);
* });
*/
var FreeControl = Base.extend(function() {
return /** @lends clay.plugin.FreeControl# */ {
/**
* Scene node to control, mostly it is a camera
* @type {clay.Node}
*/
target: null,
/**
* Target dom to bind with mouse events
* @type {HTMLElement}
*/
domElement: null,
/**
* Mouse move sensitivity
* @type {number}
*/
sensitivity: 1,
/**
* Target move speed
* @type {number}
*/
speed: 0.4,
/**
* Up axis
* @type {clay.Vector3}
*/
up: new Vector3(0, 1, 0),
/**
* If lock vertical movement
* @type {boolean}
*/
verticalMoveLock: false,
/**
* @type {clay.Timeline}
*/
timeline: null,
_moveForward: false,
_moveBackward: false,
_moveLeft: false,
_moveRight: false,
_offsetPitch: 0,
_offsetRoll: 0
};
}, function() {
this._lockChange = this._lockChange.bind(this);
this._keyDown = this._keyDown.bind(this);
this._keyUp = this._keyUp.bind(this);
this._mouseMove = this._mouseMove.bind(this);
if (this.domElement) {
this.init();
}
},
/** @lends clay.plugin.FreeControl.prototype */
{
/**
* init control
*/
init: function() {
// Use pointer lock
// http://www.html5rocks.com/en/tutorials/pointerlock/intro/
var el = this.domElement;
//Must request pointer lock after click event, can't not do it directly
//Why ? ?
vendor.addEventListener(el, 'click', this._requestPointerLock);
vendor.addEventListener(doc, 'pointerlockchange', this._lockChange);
vendor.addEventListener(doc, 'mozpointerlockchange', this._lockChange);
vendor.addEventListener(doc, 'webkitpointerlockchange', this._lockChange);
vendor.addEventListener(doc, 'keydown', this._keyDown);
vendor.addEventListener(doc, 'keyup', this._keyUp);
if (this.timeline) {
this.timeline.on('frame', this._detectMovementChange, this);
}
},
/**
* Dispose control
*/
dispose: function() {
var el = this.domElement;
el.exitPointerLock = el.exitPointerLock
|| el.mozExitPointerLock
|| el.webkitExitPointerLock;
if (el.exitPointerLock) {
el.exitPointerLock();
}
vendor.removeEventListener(el, 'click', this._requestPointerLock);
vendor.removeEventListener(doc, 'pointerlockchange', this._lockChange);
vendor.removeEventListener(doc, 'mozpointerlockchange', this._lockChange);
vendor.removeEventListener(doc, 'webkitpointerlockchange', this._lockChange);
vendor.removeEventListener(doc, 'keydown', this._keyDown);
vendor.removeEventListener(doc, 'keyup', this._keyUp);
if (this.timeline) {
this.timeline.off('frame', this._detectMovementChange);
}
},
_requestPointerLock: function() {
var el = this;
el.requestPointerLock = el.requestPointerLock
|| el.mozRequestPointerLock
|| el.webkitRequestPointerLock;
el.requestPointerLock();
},
/**
* Control update. Should be invoked every frame
* @param {number} frameTime Frame time
*/
update: function (frameTime) {
var target = this.target;
var position = this.target.position;
var xAxis = target.localTransform.x.normalize();
var zAxis = target.localTransform.z.normalize();
if (this.verticalMoveLock) {
zAxis.y = 0;
zAxis.normalize();
}
var speed = this.speed * frameTime / 20;
if (this._moveForward) {
// Opposite direction of z
position.scaleAndAdd(zAxis, -speed);
}
if (this._moveBackward) {
position.scaleAndAdd(zAxis, speed);
}
if (this._moveLeft) {
position.scaleAndAdd(xAxis, -speed / 2);
}
if (this._moveRight) {
position.scaleAndAdd(xAxis, speed / 2);
}
target.rotateAround(target.position, this.up, -this._offsetPitch * frameTime * Math.PI / 360);
var xAxis = target.localTransform.x;
target.rotateAround(target.position, xAxis, -this._offsetRoll * frameTime * Math.PI / 360);
this._offsetRoll = this._offsetPitch = 0;
},
_lockChange: function() {
if (
doc.pointerLockElement === this.domElement
|| doc.mozPointerLockElement === this.domElement
|| doc.webkitPointerLockElement === this.domElement
) {
vendor.addEventListener(doc, 'mousemove', this._mouseMove, false);
}
else {
vendor.removeEventListener(doc, 'mousemove', this._mouseMove);
}
},
_mouseMove: function(e) {
var dx = e.movementX || e.mozMovementX || e.webkitMovementX || 0;
var dy = e.movementY || e.mozMovementY || e.webkitMovementY || 0;
this._offsetPitch += dx * this.sensitivity / 200;
this._offsetRoll += dy * this.sensitivity / 200;
// Trigger change event to remind renderer do render
this.trigger('change');
},
_detectMovementChange: function (frameTime) {
if (this._moveForward || this._moveBackward || this._moveLeft || this._moveRight) {
this.trigger('change');
}
this.update(frameTime);
},
_keyDown: function(e) {
switch(e.keyCode) {
case 87: //w
case 37: //up arrow
this._moveForward = true;
break;
case 83: //s
case 40: //down arrow
this._moveBackward = true;
break;
case 65: //a
case 37: //left arrow
this._moveLeft = true;
break;
case 68: //d
case 39: //right arrow
this._moveRight = true;
break;
}
// Trigger change event to remind renderer do render
this.trigger('change');
},
_keyUp: function(e) {
switch(e.keyCode) {
case 87: //w
case 37: //up arrow
this._moveForward = false;
break;
case 83: //s
case 40: //down arrow
this._moveBackward = false;
break;
case 65: //a
case 37: //left arrow
this._moveLeft = false;
break;
case 68: //d
case 39: //right arrow
this._moveRight = false;
break;
}
}
});
export default FreeControl;