From c415da3040718d2e6d27e7062aecf5b5fd1ed01c Mon Sep 17 00:00:00 2001 From: Robin Hawkes Date: Thu, 13 Feb 2014 19:22:43 +0000 Subject: [PATCH] Implemented orbit controls (closes #14 & #15) --- Gruntfile.js | 1 + src/client/controls/Controls.js | 15 +++++- src/client/controls/Keyboard.js | 83 +++++++++++++++++++++++++++++++++ src/client/controls/Mouse.js | 47 +++++++++++++++---- src/client/webgl/Camera.js | 25 ++++++++-- 5 files changed, 156 insertions(+), 15 deletions(-) create mode 100644 src/client/controls/Keyboard.js diff --git a/Gruntfile.js b/Gruntfile.js index 25c9a23..4d6206c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -51,6 +51,7 @@ module.exports = function(grunt) { 'src/client/data/DataOverpass.js', 'src/client/Grid.js', 'src/client/controls/Mouse.js', + 'src/client/controls/Keyboard.js', 'src/client/controls/Controls.js' ], dest: 'build/vizi.js' diff --git a/src/client/controls/Controls.js b/src/client/controls/Controls.js index 48e0593..91abc57 100644 --- a/src/client/controls/Controls.js +++ b/src/client/controls/Controls.js @@ -9,18 +9,22 @@ _.extend(this, VIZI.Mediator); this.mouse = undefined; + this.keyboard = undefined; }; Controls.prototype.init = function(camera) { this.mouse = VIZI.Mouse.getInstance(camera); + this.keyboard = VIZI.Keyboard.getInstance(); this.subscribe("update", this.onUpdate); + this.subscribe("orbitControlCap", this.orbitCapReset); return Q.fcall(function() {}); }; Controls.prototype.onUpdate = function() { var mouseState = this.mouse.state; + var keyboardState = this.keyboard.state; // Zoom if (mouseState.wheelDelta !== 0) { @@ -28,14 +32,23 @@ } // Pan - if (mouseState.buttons.left) { + if (mouseState.buttons.left && !keyboardState.keys.leftShift) { this.publish("panControl", mouseState.pos3dDelta); } + // Orbit + if (mouseState.buttons.left && keyboardState.keys.leftShift) { + this.publish("orbitControl", mouseState.downPos2dDelta, mouseState.camera.startTheta, mouseState.camera.startPhi); + } + // Zero deltas this.mouse.resetDelta(); }; + Controls.prototype.orbitCapReset = function() { + this.mouse.updateCamera(); + }; + var instance; // an emulation of static variables and methods diff --git a/src/client/controls/Keyboard.js b/src/client/controls/Keyboard.js new file mode 100644 index 0000000..2688b0e --- /dev/null +++ b/src/client/controls/Keyboard.js @@ -0,0 +1,83 @@ +/* globals window, _, VIZI */ +(function() { + "use strict"; + + VIZI.Keyboard = (function() { + var Keyboard = function() { + VIZI.Log("Inititialising mouse manager"); + + _.extend(this, VIZI.Mediator); + + this.state = { + keys: {} + }; + + this.initDOMEvents(); + }; + + Keyboard.prototype.initDOMEvents = function() { + var self = this; + + document.addEventListener("keydown", function(event) { + self.onKeyDown(event); + }, false); + + document.addEventListener("keyup", function(event) { + self.onKeyUp(event); + }, false); + }; + + Keyboard.prototype.onKeyDown = function(event) { + var key = this.keyCodeToString(event.keyCode); + + if (!key) { + return; + } + + this.state.keys[key] = true; + }; + + Keyboard.prototype.onKeyUp = function(event) { + var key = this.keyCodeToString(event.keyCode); + + if (!key) { + return; + } + + this.state.keys[key] = false; + }; + + Keyboard.prototype.keyCodeToString = function(keyCode) { + var key; + + switch (keyCode) { + case 16: + key = "leftShift"; + break; + default: + key = false; + } + + return key; + }; + + var instance; + + // an emulation of static variables and methods + var _static = { + name: "VIZI.Keyboard", + + // Method for getting an instance. It returns + // a singleton instance of a singleton object + getInstance: function() { + if ( instance === undefined ) { + instance = new Keyboard(); + } + + return instance; + } + }; + + return _static; + }()); +}()); \ No newline at end of file diff --git a/src/client/controls/Mouse.js b/src/client/controls/Mouse.js index 4da0275..ed6aaea 100644 --- a/src/client/controls/Mouse.js +++ b/src/client/controls/Mouse.js @@ -20,14 +20,15 @@ }, pos2d: new THREE.Vector2(), downPos2d: new THREE.Vector2(), + downPos2dDelta: new THREE.Vector2(), pos3d: new THREE.Vector3(), downPos3d: new THREE.Vector3(), pos2dDelta: new THREE.Vector2(), pos3dDelta: new THREE.Vector3(), wheelDelta: 0, camera: { - downTheta: this.camera.theta, - downPhi: this.camera.phi + startTheta: this.camera.theta, + startPhi: this.camera.phi } }; @@ -77,6 +78,9 @@ state.downPos2d.x = event.clientX; state.downPos2d.y = event.clientY; + state.pos2dDelta.x = 0; + state.pos2dDelta.y = 0; + var pos3d = this.mouseIn3d(state.downPos2d); state.pos3d.x = pos3d.x; @@ -87,8 +91,12 @@ state.downPos3d.y = pos3d.y; state.downPos3d.z = pos3d.z; - state.camera.downTheta = this.camera.theta; - state.camera.downPhi = this.camera.phi; + state.pos3dDelta.x = 0; + state.pos3dDelta.y = 0; + state.pos3dDelta.z = 0; + + state.camera.startTheta = this.camera.theta; + state.camera.startPhi = this.camera.phi; }; Mouse.prototype.onMouseMove = function(event) { @@ -104,13 +112,18 @@ var pos3d = this.mouseIn3d(state.pos2d); - state.pos3dDelta.x = state.downPos3d.x - pos3d.x; - state.pos3dDelta.y = state.downPos3d.y - pos3d.y; - state.pos3dDelta.z = state.downPos3d.z - pos3d.z; - state.pos3d.x = pos3d.x; state.pos3d.y = pos3d.y; state.pos3d.z = pos3d.z; + + if (state.buttons.left) { + state.downPos2dDelta.x = event.clientX - state.downPos2d.x; + state.downPos2dDelta.y = event.clientY - state.downPos2d.y; + + state.pos3dDelta.x = state.downPos3d.x - pos3d.x; + state.pos3dDelta.y = state.downPos3d.y - pos3d.y; + state.pos3dDelta.z = state.downPos3d.z - pos3d.z; + } }; Mouse.prototype.onMouseUp = function(event) { @@ -130,7 +143,9 @@ state.buttons.right = false; } - // TODO: Reset mouse down positions? + // Reset mouse down positions and deltas + state.downPos2dDelta.x = 0; + state.downPos2dDelta.y = 0; }; Mouse.prototype.onMouseWheel = function(event) { @@ -174,6 +189,20 @@ return pos; }; + // TODO: Tidy this up + Mouse.prototype.updateCamera = function() { + var state = this.state; + + state.downPos2d.x = state.pos2d.x; + state.downPos2d.y = state.pos2d.y; + + state.downPos2dDelta.x = 0; + state.downPos2dDelta.y = 0; + + state.camera.startTheta = this.camera.theta; + state.camera.startPhi = this.camera.phi; + }; + var instance; // an emulation of static variables and methods diff --git a/src/client/webgl/Camera.js b/src/client/webgl/Camera.js index 2c59267..a298b88 100644 --- a/src/client/webgl/Camera.js +++ b/src/client/webgl/Camera.js @@ -23,6 +23,7 @@ this.subscribe("resize", this.resize); this.subscribe("zoomControl", this.zoom); this.subscribe("panControl", this.pan); + this.subscribe("orbitControl", this.orbit); }; VIZI.Camera.prototype.createCamera = function() { @@ -83,11 +84,6 @@ }; VIZI.Camera.prototype.pan = function(delta3d) { - // TODO: Remove if it looks like this isn't breaking anything - // this.camera.position.x += delta3d.x; - // this.camera.position.z += delta3d.z; - // this.camera.updateMatrix(); - this.target.position.x += delta3d.x; this.target.position.z += delta3d.z; @@ -106,6 +102,25 @@ this.publish("render"); }; + VIZI.Camera.prototype.orbit = function(delta2d, theta, phi) { + // Round delta to next highest pixel to prevent jerkiness + this.theta = - ( delta2d.x * 0.5 ) + theta; + this.phi = ( delta2d.y * 0.5 ) + phi; + + // Cap orbit to bounds + this.phi = Math.min( 175, Math.max( 65, this.phi ) ); + + // Let controls know that the cap has been hit + if (this.phi === 175 || this.phi === 65) { + this.publish("orbitControlCap"); + } + + this.updatePosition(); + this.lookAtTarget(); + + this.publish("render"); + }; + VIZI.Camera.prototype.datChange = function() { this.updatePosition(); this.lookAtTarget();