Three.js Quaternion Camera
This is a three.js camera class based on my quaternion camera code, math is very similar with syntax changes due to vector and quaternion classes in three.js
based on https://github.com/mrdoob/three.js/blob/master/examples/js/controls/PointerLockControls.js
/**
* @author hammad mazhar / http://hamelot.co.uk/
* based on https://github.com/mrdoob/three.js/blob/master/examples/js/controls/PointerLockControls.js
*/
THREE.FreeCamera = function (camera) {
var scope = this;
camera.rotation.set(0, 0, 0);
camera.position.set(0, 1, 0);
camera.useQuaternion = true;
var MOVE = {
LEFT: {
value: 0,
name: "Left",
code: "L"
},
RIGHT: {
value: 1,
name: "Right",
code: "R"
},
FORWARD: {
value: 2,
name: "Forward",
code: "F"
},
BACK: {
value: 3,
name: "Back",
code: "B"
},
UP: {
value: 4,
name: "Up",
code: "U"
},
DOWN: {
value: 5,
name: "Down",
code: "D"
}
};
var camera_scale = .5;
var camera_direction = new THREE.Vector3(0, 0, 1);
var camera_up = new THREE.Vector3(0, 1, 0);
var camera_position_delta = new THREE.Vector3(0, 0, 0);
var camera_position = new THREE.Vector3(0, 0, 0);
var camera_look_at = new THREE.Vector3(0, 0, 2);
var max_pitch_rate = 5.0;
var max_heading_rate = 5.0;
var camera_pitch = 0.0;
var camera_heading = 0.0;
var mouse_delta_x = 0.0;
var mouse_delta_y = 0.0;
var mouse_pos_x = 0.0;
var mouse_pos_y = 0.0;
var move_camera = false;
var isOnObject = false;
var canJump = false;
var onMouseMove = function (event) {
if (scope.enabled === false) return;
var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
mouse_delta_x = mouse_pos_x - movementX;
mouse_delta_y = mouse_pos_y - movementY;
if (move_camera) {
ChangeHeading(.08 * mouse_delta_x);
ChangePitch(.08 * mouse_delta_y);
}
mouse_pos_x = movementX;
mouse_pos_y = movementY;
};
var onMouseDown = function (event) {
var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
mouse_pos_x = movementX;
mouse_pos_y = movementY;
move_camera = true;
}
var onMouseUp = function (event) {
var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
mouse_pos_x = movementX;
mouse_pos_y = movementY;
move_camera = false;
}
var ChangePitch = function (degrees) {
if (degrees < -max_pitch_rate) {
degrees = -max_pitch_rate;
} else if (degrees > max_pitch_rate) {
degrees = max_pitch_rate;
}
camera_pitch += degrees;
//Check bounds for the camera pitch
if (camera_pitch > 360.0) {
camera_pitch -= 360.0;
} else if (camera_pitch < -360.0) {
camera_pitch += 360.0;
}
}
var ChangeHeading = function (degrees) {
//Check bounds with the max heading rate so that we aren't moving too fast
if (degrees < -max_heading_rate) {
degrees = -max_heading_rate;
} else if (degrees > max_heading_rate) {
degrees = max_heading_rate;
}
//This controls how the heading is changed if the camera is pointed straight up or down
//The heading delta direction changes
if (camera_pitch > 90 && camera_pitch < 270 || (camera_pitch < -90 && camera_pitch > -270)) {
camera_heading -= degrees;
} else {
camera_heading += degrees;
}
//Check bounds for the camera heading
if (camera_heading > 360.0) {
camera_heading -= 360.0;
} else if (camera_heading < -360.0) {
camera_heading += 360.0;
}
}
var moveCamera = function (move) {
if (scope.enabled === false) return;
var t = new THREE.Vector3(0, 0, 0);
switch (move) {
case MOVE.UP:
t.copy(camera_up);
t.multiplyScalar(camera_scale);
camera_position_delta.add(t);
break;
case MOVE.DOWN:
t.copy(camera_up);
t.multiplyScalar(camera_scale);
camera_position_delta.sub(t);
break;
case MOVE.LEFT:
t.crossVectors(camera_direction, camera_up);
t.multiplyScalar(camera_scale);
camera_position_delta.sub(t);
break;
case MOVE.RIGHT:
t.crossVectors(camera_direction, camera_up);
t.multiplyScalar(camera_scale);
camera_position_delta.add(t);
break;
case MOVE.FORWARD:
t.copy(camera_direction);
t.multiplyScalar(camera_scale);
camera_position_delta.add(t);
break;
case MOVE.BACK:
t.copy(camera_direction);
t.multiplyScalar(camera_scale);
camera_position_delta.sub(t);
break;
}
}
var onKeyDown = function (event) {
switch (event.keyCode) {
case 81: //q
case 33: //PgUp
moveCamera(MOVE.DOWN);
break;
case 69: // e
case 34: // PgDown
moveCamera(MOVE.UP);
break;
case 38: // up
case 87: // w
moveCamera(MOVE.FORWARD);
break;
case 37: // left
case 65: // a
moveCamera(MOVE.LEFT);
break;
case 40: // down
case 83: // s
moveCamera(MOVE.BACK);
break;
case 39: // right
case 68: // d
moveCamera(MOVE.RIGHT);
break;
}
};
document.addEventListener('mousemove', onMouseMove, false);
document.addEventListener('mousedown', onMouseDown, false);
document.addEventListener('mouseup', onMouseUp, false);
document.addEventListener('keydown', onKeyDown, false);
this.enabled = false;
this.getObject = function () {
return camera;
};
this.isOnObject = function (boolean) {
isOnObject = boolean;
canJump = boolean;
};
this.getDirection = function () {
// assumes the camera itself is not rotated
return camera_direction;
}();
this.getLookAt = function (delta) {
return camera_look_at;
};
this.update = function (delta) {
var axis = new THREE.Vector3(0, 0, 0);
//console.log(camera_direction);
camera_direction.subVectors(camera_look_at, camera_position);
camera_direction.normalize();
axis.crossVectors(camera_direction, camera_up);
var pitch_quat = new THREE.Quaternion(0, 0, 0, 1);
var heading_quat = new THREE.Quaternion(0, 0, 0, 1);
var temp = new THREE.Quaternion(0, 0, 0, 1);
pitch_quat.setFromAxisAngle(axis, camera_pitch * Math.PI / 180.0);
heading_quat.setFromAxisAngle(camera_up, camera_heading * Math.PI / 180.0);
temp.multiplyQuaternions(pitch_quat, heading_quat);
camera_direction.applyQuaternion(temp);
camera_position.add(camera_position_delta);
camera_look_at.addVectors(camera_position, camera_direction);
camera.position.copy(camera_position);
camera.up.copy(camera_up);
camera.lookAt(camera_look_at);
if (move_camera == false) {
camera_pitch = camera_pitch * .5;
camera_heading = camera_heading * .5;
}
camera_position_delta.multiplyScalar(.8);
};
};