├── .gitignore ├── README.md ├── Rotation.js ├── gulpfile.js ├── index.html └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Three.js-Object-Rotation-with-Quaternion 2 | ======================================== 3 | 4 | Example of rotating an object, not camera, using quaternion rotation. 5 | 6 | I was looking for ways to rotate an object, not the camera, in a 3d scene using a trackball like control. I found code on [StackOverflow](http://stackoverflow.com/questions/23223431/how-to-rotate-object-in-three-jsr66-not-use-trackball-which-is-control-the-cam) which was very close to what I was looking for. This is that example code updated to work more like I wanted. 7 | 8 | **The rotation now works as I expect. Phew.** 9 | 10 | **Now with momentum** Try spinning the cube fast. 11 | 12 | Putting this code out there in case it helps anyone or could be improved. 13 | 14 | View an example here: http://projects.defmech.com/ThreeJSObjectRotationWithQuaternion/ 15 | -------------------------------------------------------------------------------- /Rotation.js: -------------------------------------------------------------------------------- 1 | window.log = function() 2 | { 3 | if (this.console) 4 | { 5 | console.log(Array.prototype.slice.call(arguments)); 6 | } 7 | }; 8 | 9 | // Namespace 10 | var Defmech = Defmech || 11 | {}; 12 | 13 | Defmech.RotationWithQuaternion = (function() 14 | { 15 | 'use_strict'; 16 | 17 | var container; 18 | 19 | var camera, scene, renderer; 20 | 21 | var cube, plane; 22 | 23 | var mouseDown = false; 24 | var rotateStartPoint = new THREE.Vector3(0, 0, 1); 25 | var rotateEndPoint = new THREE.Vector3(0, 0, 1); 26 | 27 | var curQuaternion; 28 | var windowHalfX = window.innerWidth / 2; 29 | var windowHalfY = window.innerHeight / 2; 30 | var rotationSpeed = 2; 31 | var lastMoveTimestamp, 32 | moveReleaseTimeDelta = 50; 33 | 34 | var startPoint = { 35 | x: 0, 36 | y: 0 37 | }; 38 | 39 | var deltaX = 0, 40 | deltaY = 0; 41 | 42 | var setup = function() 43 | { 44 | container = document.createElement('div'); 45 | document.body.appendChild(container); 46 | 47 | var info = document.createElement('div'); 48 | info.style.position = 'absolute'; 49 | info.style.top = '10px'; 50 | info.style.width = '100%'; 51 | info.style.textAlign = 'center'; 52 | info.innerHTML = 'Drag to spin the cube'; 53 | container.appendChild(info); 54 | 55 | camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000); 56 | camera.position.y = 150; 57 | camera.position.z = 500; 58 | 59 | scene = new THREE.Scene(); 60 | 61 | // Cube 62 | 63 | var boxGeometry = new THREE.BoxGeometry(200, 200, 200); 64 | 65 | for (var i = 0; i < boxGeometry.faces.length; i += 2) 66 | { 67 | 68 | var color = { 69 | h: (1 / (boxGeometry.faces.length)) * i, 70 | s: 0.5, 71 | l: 0.5 72 | }; 73 | 74 | boxGeometry.faces[i].color.setHSL(color.h, color.s, color.l); 75 | boxGeometry.faces[i + 1].color.setHSL(color.h, color.s, color.l); 76 | 77 | } 78 | 79 | var cubeMaterial = new THREE.MeshBasicMaterial( 80 | { 81 | vertexColors: THREE.FaceColors, 82 | overdraw: 0.5 83 | }); 84 | 85 | cube = new THREE.Mesh(boxGeometry, cubeMaterial); 86 | cube.position.y = 200; 87 | scene.add(cube); 88 | 89 | // Plane 90 | 91 | var planeGeometry = new THREE.PlaneGeometry(200, 200); 92 | planeGeometry.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2)); 93 | 94 | var planeMaterial = new THREE.MeshBasicMaterial( 95 | { 96 | color: 0xe0e0e0, 97 | overdraw: 0.5 98 | }); 99 | 100 | plane = new THREE.Mesh(planeGeometry, planeMaterial); 101 | scene.add(plane); 102 | 103 | renderer = new THREE.CanvasRenderer(); 104 | renderer.setClearColor(0xf0f0f0); 105 | renderer.setSize(window.innerWidth, window.innerHeight); 106 | 107 | container.appendChild(renderer.domElement); 108 | 109 | document.addEventListener('mousedown', onDocumentMouseDown, false); 110 | 111 | window.addEventListener('resize', onWindowResize, false); 112 | 113 | animate(); 114 | }; 115 | 116 | function onWindowResize() 117 | { 118 | windowHalfX = window.innerWidth / 2; 119 | windowHalfY = window.innerHeight / 2; 120 | 121 | camera.aspect = window.innerWidth / window.innerHeight; 122 | camera.updateProjectionMatrix(); 123 | 124 | renderer.setSize(window.innerWidth, window.innerHeight); 125 | } 126 | 127 | function onDocumentMouseDown(event) 128 | { 129 | event.preventDefault(); 130 | 131 | document.addEventListener('mousemove', onDocumentMouseMove, false); 132 | document.addEventListener('mouseup', onDocumentMouseUp, false); 133 | 134 | mouseDown = true; 135 | 136 | startPoint = { 137 | x: event.clientX, 138 | y: event.clientY 139 | }; 140 | 141 | rotateStartPoint = rotateEndPoint = projectOnTrackball(0, 0); 142 | } 143 | 144 | function onDocumentMouseMove(event) 145 | { 146 | deltaX = event.x - startPoint.x; 147 | deltaY = event.y - startPoint.y; 148 | 149 | handleRotation(); 150 | 151 | startPoint.x = event.x; 152 | startPoint.y = event.y; 153 | 154 | lastMoveTimestamp = new Date(); 155 | } 156 | 157 | function onDocumentMouseUp(event) 158 | { 159 | if (new Date().getTime() - lastMoveTimestamp.getTime() > moveReleaseTimeDelta) 160 | { 161 | deltaX = event.x - startPoint.x; 162 | deltaY = event.y - startPoint.y; 163 | } 164 | 165 | mouseDown = false; 166 | 167 | document.removeEventListener('mousemove', onDocumentMouseMove, false); 168 | document.removeEventListener('mouseup', onDocumentMouseUp, false); 169 | } 170 | 171 | function projectOnTrackball(touchX, touchY) 172 | { 173 | var mouseOnBall = new THREE.Vector3(); 174 | 175 | mouseOnBall.set( 176 | clamp(touchX / windowHalfX, -1, 1), clamp(-touchY / windowHalfY, -1, 1), 177 | 0.0 178 | ); 179 | 180 | var length = mouseOnBall.length(); 181 | 182 | if (length > 1.0) 183 | { 184 | mouseOnBall.normalize(); 185 | } 186 | else 187 | { 188 | mouseOnBall.z = Math.sqrt(1.0 - length * length); 189 | } 190 | 191 | return mouseOnBall; 192 | } 193 | 194 | function rotateMatrix(rotateStart, rotateEnd) 195 | { 196 | var axis = new THREE.Vector3(), 197 | quaternion = new THREE.Quaternion(); 198 | 199 | var angle = Math.acos(rotateStart.dot(rotateEnd) / rotateStart.length() / rotateEnd.length()); 200 | 201 | if (angle) 202 | { 203 | axis.crossVectors(rotateStart, rotateEnd).normalize(); 204 | angle *= rotationSpeed; 205 | quaternion.setFromAxisAngle(axis, angle); 206 | } 207 | return quaternion; 208 | } 209 | 210 | function clamp(value, min, max) 211 | { 212 | return Math.min(Math.max(value, min), max); 213 | } 214 | 215 | function animate() 216 | { 217 | requestAnimationFrame(animate); 218 | render(); 219 | } 220 | 221 | function render() 222 | { 223 | if (!mouseDown) 224 | { 225 | var drag = 0.95; 226 | var minDelta = 0.05; 227 | 228 | if (deltaX < -minDelta || deltaX > minDelta) 229 | { 230 | deltaX *= drag; 231 | } 232 | else 233 | { 234 | deltaX = 0; 235 | } 236 | 237 | if (deltaY < -minDelta || deltaY > minDelta) 238 | { 239 | deltaY *= drag; 240 | } 241 | else 242 | { 243 | deltaY = 0; 244 | } 245 | 246 | handleRotation(); 247 | } 248 | 249 | renderer.render(scene, camera); 250 | } 251 | 252 | var handleRotation = function() 253 | { 254 | rotateEndPoint = projectOnTrackball(deltaX, deltaY); 255 | 256 | var rotateQuaternion = rotateMatrix(rotateStartPoint, rotateEndPoint); 257 | curQuaternion = cube.quaternion; 258 | curQuaternion.multiplyQuaternions(rotateQuaternion, curQuaternion); 259 | curQuaternion.normalize(); 260 | cube.setRotationFromQuaternion(curQuaternion); 261 | 262 | rotateEndPoint = rotateStartPoint; 263 | }; 264 | 265 | // PUBLIC INTERFACE 266 | return { 267 | init: function() 268 | { 269 | setup(); 270 | } 271 | }; 272 | })(); 273 | 274 | document.onreadystatechange = function() 275 | { 276 | if (document.readyState === 'complete') 277 | { 278 | Defmech.RotationWithQuaternion.init(); 279 | } 280 | }; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | livereload = require('gulp-livereload'); 3 | 4 | gulp.task('dev', function() 5 | { 6 | var server = livereload(); 7 | 8 | gulp.watch('*.js').on('change', function(file) 9 | { 10 | server.changed(file.path); 11 | }); 12 | 13 | gulp.watch('*.html').on('change', function(file) 14 | { 15 | server.changed(file.path); 16 | }); 17 | }); 18 | 19 | // Default task 20 | gulp.task('default', ['dev']); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | three.js Object Rotation with Quaternion 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stereo_3d", 3 | "preferGlobal": true, 4 | "version": "0.0.1", 5 | "author": "Dave Lochhead ", 6 | "description": "Baillie Giford - Global Discovery", 7 | "dependencies": {}, 8 | "devDependencies": { 9 | "gulp": "~3.6.2", 10 | "gulp-livereload": "~1.3.1" 11 | } 12 | } 13 | --------------------------------------------------------------------------------