├── .npmignore ├── .gitignore ├── test.js ├── example.js ├── example.ts ├── LICENSE ├── package.json ├── Development.md ├── README.md └── OrbitControls.js /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | Development.md 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | OrbitControls.js.new 4 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | 3 | const { version, peerDependencies } = require('./package.json') 4 | 5 | const orbitControlsMinorVersion = version.split('.')[1] 6 | const threeMinorVersion = peerDependencies.three.split('.')[1] 7 | 8 | assert.equal(orbitControlsMinorVersion, threeMinorVersion, 'minor version is the same as threejs release') 9 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | // To import package in your code use the following: 2 | // 3 | // const OrbitControls = require('three-orbitcontrols') 4 | // 5 | const OrbitControls = require('./OrbitControls.js') 6 | const THREE = require('three') 7 | 8 | const width = window.innerWidth 9 | const height = window.innerHeight 10 | 11 | const scene = new THREE.Scene() 12 | const camera = new THREE.PerspectiveCamera(75, width/height, 0.1, 1000) 13 | 14 | const renderer = new THREE.WebGLRenderer() 15 | renderer.setSize(width, height) 16 | document.body.appendChild(renderer.domElement) 17 | 18 | const geometry = new THREE.BoxGeometry(1, 1, 1) 19 | const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) 20 | const cube = new THREE.Mesh(geometry, material) 21 | scene.add(cube) 22 | 23 | camera.position.z = 5 24 | 25 | const controls = new OrbitControls(camera, renderer.domElement) 26 | 27 | function animate() { 28 | requestAnimationFrame(animate) 29 | 30 | cube.rotation.x += 0.01 31 | cube.rotation.y += 0.01 32 | 33 | renderer.render(scene, camera) 34 | } 35 | 36 | animate() 37 | -------------------------------------------------------------------------------- /example.ts: -------------------------------------------------------------------------------- 1 | // To import package in your code use the following: 2 | // 3 | // import * as OrbitControls from 'three-orbitcontrols'; 4 | // 5 | import * as OrbitControls from './OrbitControls.js'; 6 | import * as THREE from 'three'; 7 | 8 | const width = window.innerWidth; 9 | const height = window.innerHeight; 10 | 11 | const scene = new THREE.Scene(); 12 | const camera = new THREE.PerspectiveCamera(75, width/height, 0.1, 1000); 13 | 14 | const renderer = new THREE.WebGLRenderer(); 15 | renderer.setSize(width, height); 16 | document.body.appendChild(renderer.domElement); 17 | 18 | const geometry = new THREE.BoxGeometry(1, 1, 1); 19 | const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); 20 | const cube = new THREE.Mesh(geometry, material); 21 | scene.add(cube); 22 | 23 | camera.position.z = 5; 24 | 25 | const controls = new OrbitControls(camera, renderer.domElement); 26 | 27 | function animate() { 28 | requestAnimationFrame(animate); 29 | 30 | cube.rotation.x += 0.01; 31 | cube.rotation.y += 0.01; 32 | 33 | renderer.render(scene, camera); 34 | } 35 | 36 | animate(); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2010-2018 three.js authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-orbitcontrols", 3 | "version": "2.110.2", 4 | "description": "is the three.js OrbitControls from official repo examples", 5 | "main": "OrbitControls.js", 6 | "scripts": { 7 | "example_commonjs": "budo example.js --open", 8 | "example_typescript": "budo example.ts --open -- -p [tsify]", 9 | "deploy": "npm version patch", 10 | "predeploy": "npm test", 11 | "postversion": "git push origin v${npm_package_version}; npm publish; git push origin master", 12 | "test": "node test" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/fibo/three-orbitcontrols.git" 17 | }, 18 | "keywords": [ 19 | "three", 20 | "three.js", 21 | "3d", 22 | "camera", 23 | "controls" 24 | ], 25 | "author": { 26 | "name": "Gianluca Casati", 27 | "url": "http://g14n.info" 28 | }, 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/fibo/three-orbitcontrols/issues" 32 | }, 33 | "homepage": "http://g14n.info/three-orbitcontrols", 34 | "peerDependencies": { 35 | "three": ">= 0.110.0" 36 | }, 37 | "devDependencies": { 38 | "@types/node": "^12.12.5", 39 | "budo": "^11.6.3", 40 | "three": "^0.110.0", 41 | "tsify": "^4.0.1", 42 | "typescript": "^3.6.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Development.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | ## How to update code 4 | 5 | To update *OrbitControls.js* code follow instructions below. 6 | 7 | First of all, target latest three.js release number, for instance do 8 | 9 | ```bash 10 | THREEJS_RELEASE=96 11 | ``` 12 | 13 | which will set the download URL to something like 14 | 15 | ```bash 16 | wget https://raw.githubusercontent.com/mrdoob/three.js/r96/examples/js/controls/OrbitControls.js 17 | ``` 18 | 19 | Now you can launch 20 | 21 | ```bash 22 | rm OrbitControls.js* # clean up previous files 23 | wget https://raw.githubusercontent.com/mrdoob/three.js/r${THREEJS_RELEASE}/examples/js/controls/OrbitControls.js -O OrbitControls.js.new 24 | echo "/* three-orbitcontrols addendum */ var THREE = require('three');" > OrbitControls.js 25 | cat OrbitControls.js.new >> OrbitControls.js 26 | echo "/* three-orbitcontrols addendum */ module.exports = exports.default = THREE.OrbitControls;" >> OrbitControls.js 27 | rm OrbitControls.js.new # clean up downloaded file 28 | ``` 29 | 30 | Note that minor version in this package is in sync with three.js minor version, i.e. release number. 31 | Also update `peerDependencies` attribute in *package.json* with latest three.js version. 32 | There is a test that checks version numbers are in sync. 33 | 34 | When you are done, launch 35 | 36 | ```bash 37 | npm run deploy 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **DEPRECATED** 2 | 3 | [three.js] exposes real modules now via three/examples/jsm/... 4 | For example to import the Orbit, do 5 | 6 | ```js 7 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls" 8 | ``` 9 | 10 | # three-orbitcontrols 11 | 12 | > is the [three.js] OrbitControls from official repo examples 13 | 14 | ## Installation 15 | 16 | To install with npm do 17 | 18 | ```bash 19 | npm install three 20 | npm install three-orbitcontrols 21 | ``` 22 | 23 | ## Usage 24 | 25 | All credit goes to [OrbitControls.js][original_orbitcontrols] contributors. 26 | See also [official OrbitControls documentation][orbitcontrols_documentation]. 27 | 28 | I have just **stolen** the code and modified to export it as a module so you can do something like 29 | 30 | ```javascript 31 | const THREE = require('three') 32 | const OrbitControls = require('three-orbitcontrols') 33 | // ES6 also works, i.e. 34 | // import OrbitControls from 'three-orbitcontrols' 35 | 36 | // Init THREE scene (add your code) 37 | 38 | const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000) 39 | camera.position.z = 5 40 | 41 | const renderer = new THREE.WebGLRenderer({ canvas }) 42 | 43 | const controls = new OrbitControls(camera, renderer.domElement) 44 | controls.enableDamping = true 45 | controls.dampingFactor = 0.25 46 | controls.enableZoom = false 47 | ``` 48 | 49 | Please note that: 50 | 51 | 1. You call `OrbitControls` directly instead of `THREE.OrbitControls`. 52 | 2. This package does not depend directly on [three.js], which is declared as a peer dependency. 53 | 54 | See also examples: 55 | 56 | - [CommonJS example](https://github.com/fibo/three-orbitcontrols/tree/master/example.js): clone this repo, install deps and launch `npm run example_commonjs`. 57 | - [TypeScript example](https://github.com/fibo/three-orbitcontrols/tree/master/example.ts): clone this repo, install deps and launch `npm run example_typescript`. 58 | 59 | ## Changelog 60 | 61 | See [OrbiControls.js history here](https://github.com/mrdoob/three.js/commits/master/examples/js/controls/OrbitControls.js). 62 | 63 | Please also note that this repo's minor version equals [three.js] release number. 64 | 65 | ## Motivation 66 | 67 | There is another package similar to this one: [three-orbit-controls]. 68 | I decided to create another package with a different approach, see [this issue for the rationale](https://github.com/mattdesl/three-orbit-controls/issues/17). 69 | 70 | I am using this package for my [3d tic tac toe canvas](https://github.com/fibo/tris3d-canvas): see also online [demo](http://g14n.info/tris3d-canvas/example/). 71 | 72 | 75 | 76 | ## License 77 | 78 | License is the same as [three.js], i.e. [MIT]. 79 | 80 | [original_orbitcontrols]: https://github.com/mrdoob/three.js/tree/master/examples/js/controls/OrbitControls.js "OrbitControls.js" 81 | [orbitcontrols_documentation]: https://threejs.org/docs/#examples/controls/OrbitControls "OrbitControls documentation" 82 | [three.js]: http://threejs.org/ "three.js" 83 | [MIT]: https://github.com/mrdoob/three.js/blob/master/LICENSE "three.js license" 84 | [three-orbit-controls]: https://www.npmjs.com/package/three-orbit-controls "three-orbit-controls" 85 | -------------------------------------------------------------------------------- /OrbitControls.js: -------------------------------------------------------------------------------- 1 | /* three-orbitcontrols addendum */ var THREE = require('three'); 2 | /** 3 | * @author qiao / https://github.com/qiao 4 | * @author mrdoob / http://mrdoob.com 5 | * @author alteredq / http://alteredqualia.com/ 6 | * @author WestLangley / http://github.com/WestLangley 7 | * @author erich666 / http://erichaines.com 8 | * @author ScieCode / http://github.com/sciecode 9 | */ 10 | 11 | // This set of controls performs orbiting, dollying (zooming), and panning. 12 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 13 | // 14 | // Orbit - left mouse / touch: one-finger move 15 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 16 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 17 | 18 | THREE.OrbitControls = function ( object, domElement ) { 19 | 20 | if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); 21 | if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); 22 | 23 | this.object = object; 24 | this.domElement = domElement; 25 | 26 | // Set to false to disable this control 27 | this.enabled = true; 28 | 29 | // "target" sets the location of focus, where the object orbits around 30 | this.target = new THREE.Vector3(); 31 | 32 | // How far you can dolly in and out ( PerspectiveCamera only ) 33 | this.minDistance = 0; 34 | this.maxDistance = Infinity; 35 | 36 | // How far you can zoom in and out ( OrthographicCamera only ) 37 | this.minZoom = 0; 38 | this.maxZoom = Infinity; 39 | 40 | // How far you can orbit vertically, upper and lower limits. 41 | // Range is 0 to Math.PI radians. 42 | this.minPolarAngle = 0; // radians 43 | this.maxPolarAngle = Math.PI; // radians 44 | 45 | // How far you can orbit horizontally, upper and lower limits. 46 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 47 | this.minAzimuthAngle = - Infinity; // radians 48 | this.maxAzimuthAngle = Infinity; // radians 49 | 50 | // Set to true to enable damping (inertia) 51 | // If damping is enabled, you must call controls.update() in your animation loop 52 | this.enableDamping = false; 53 | this.dampingFactor = 0.05; 54 | 55 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 56 | // Set to false to disable zooming 57 | this.enableZoom = true; 58 | this.zoomSpeed = 1.0; 59 | 60 | // Set to false to disable rotating 61 | this.enableRotate = true; 62 | this.rotateSpeed = 1.0; 63 | 64 | // Set to false to disable panning 65 | this.enablePan = true; 66 | this.panSpeed = 1.0; 67 | this.screenSpacePanning = false; // if true, pan in screen-space 68 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 69 | 70 | // Set to true to automatically rotate around the target 71 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 72 | this.autoRotate = false; 73 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 74 | 75 | // Set to false to disable use of the keys 76 | this.enableKeys = true; 77 | 78 | // The four arrow keys 79 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 80 | 81 | // Mouse buttons 82 | this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; 83 | 84 | // Touch fingers 85 | this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; 86 | 87 | // for reset 88 | this.target0 = this.target.clone(); 89 | this.position0 = this.object.position.clone(); 90 | this.zoom0 = this.object.zoom; 91 | 92 | // 93 | // public methods 94 | // 95 | 96 | this.getPolarAngle = function () { 97 | 98 | return spherical.phi; 99 | 100 | }; 101 | 102 | this.getAzimuthalAngle = function () { 103 | 104 | return spherical.theta; 105 | 106 | }; 107 | 108 | this.saveState = function () { 109 | 110 | scope.target0.copy( scope.target ); 111 | scope.position0.copy( scope.object.position ); 112 | scope.zoom0 = scope.object.zoom; 113 | 114 | }; 115 | 116 | this.reset = function () { 117 | 118 | scope.target.copy( scope.target0 ); 119 | scope.object.position.copy( scope.position0 ); 120 | scope.object.zoom = scope.zoom0; 121 | 122 | scope.object.updateProjectionMatrix(); 123 | scope.dispatchEvent( changeEvent ); 124 | 125 | scope.update(); 126 | 127 | state = STATE.NONE; 128 | 129 | }; 130 | 131 | // this method is exposed, but perhaps it would be better if we can make it private... 132 | this.update = function () { 133 | 134 | var offset = new THREE.Vector3(); 135 | 136 | // so camera.up is the orbit axis 137 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 138 | var quatInverse = quat.clone().inverse(); 139 | 140 | var lastPosition = new THREE.Vector3(); 141 | var lastQuaternion = new THREE.Quaternion(); 142 | 143 | return function update() { 144 | 145 | var position = scope.object.position; 146 | 147 | offset.copy( position ).sub( scope.target ); 148 | 149 | // rotate offset to "y-axis-is-up" space 150 | offset.applyQuaternion( quat ); 151 | 152 | // angle from z-axis around y-axis 153 | spherical.setFromVector3( offset ); 154 | 155 | if ( scope.autoRotate && state === STATE.NONE ) { 156 | 157 | rotateLeft( getAutoRotationAngle() ); 158 | 159 | } 160 | 161 | if ( scope.enableDamping ) { 162 | 163 | spherical.theta += sphericalDelta.theta * scope.dampingFactor; 164 | spherical.phi += sphericalDelta.phi * scope.dampingFactor; 165 | 166 | } else { 167 | 168 | spherical.theta += sphericalDelta.theta; 169 | spherical.phi += sphericalDelta.phi; 170 | 171 | } 172 | 173 | // restrict theta to be between desired limits 174 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 175 | 176 | // restrict phi to be between desired limits 177 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 178 | 179 | spherical.makeSafe(); 180 | 181 | 182 | spherical.radius *= scale; 183 | 184 | // restrict radius to be between desired limits 185 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 186 | 187 | // move target to panned location 188 | 189 | if ( scope.enableDamping === true ) { 190 | 191 | scope.target.addScaledVector( panOffset, scope.dampingFactor ); 192 | 193 | } else { 194 | 195 | scope.target.add( panOffset ); 196 | 197 | } 198 | 199 | offset.setFromSpherical( spherical ); 200 | 201 | // rotate offset back to "camera-up-vector-is-up" space 202 | offset.applyQuaternion( quatInverse ); 203 | 204 | position.copy( scope.target ).add( offset ); 205 | 206 | scope.object.lookAt( scope.target ); 207 | 208 | if ( scope.enableDamping === true ) { 209 | 210 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 211 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 212 | 213 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 214 | 215 | } else { 216 | 217 | sphericalDelta.set( 0, 0, 0 ); 218 | 219 | panOffset.set( 0, 0, 0 ); 220 | 221 | } 222 | 223 | scale = 1; 224 | 225 | // update condition is: 226 | // min(camera displacement, camera rotation in radians)^2 > EPS 227 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 228 | 229 | if ( zoomChanged || 230 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 231 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 232 | 233 | scope.dispatchEvent( changeEvent ); 234 | 235 | lastPosition.copy( scope.object.position ); 236 | lastQuaternion.copy( scope.object.quaternion ); 237 | zoomChanged = false; 238 | 239 | return true; 240 | 241 | } 242 | 243 | return false; 244 | 245 | }; 246 | 247 | }(); 248 | 249 | this.dispose = function () { 250 | 251 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 252 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 253 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 254 | 255 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 256 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 257 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 258 | 259 | document.removeEventListener( 'mousemove', onMouseMove, false ); 260 | document.removeEventListener( 'mouseup', onMouseUp, false ); 261 | 262 | scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); 263 | 264 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 265 | 266 | }; 267 | 268 | // 269 | // internals 270 | // 271 | 272 | var scope = this; 273 | 274 | var changeEvent = { type: 'change' }; 275 | var startEvent = { type: 'start' }; 276 | var endEvent = { type: 'end' }; 277 | 278 | var STATE = { 279 | NONE: - 1, 280 | ROTATE: 0, 281 | DOLLY: 1, 282 | PAN: 2, 283 | TOUCH_ROTATE: 3, 284 | TOUCH_PAN: 4, 285 | TOUCH_DOLLY_PAN: 5, 286 | TOUCH_DOLLY_ROTATE: 6 287 | }; 288 | 289 | var state = STATE.NONE; 290 | 291 | var EPS = 0.000001; 292 | 293 | // current position in spherical coordinates 294 | var spherical = new THREE.Spherical(); 295 | var sphericalDelta = new THREE.Spherical(); 296 | 297 | var scale = 1; 298 | var panOffset = new THREE.Vector3(); 299 | var zoomChanged = false; 300 | 301 | var rotateStart = new THREE.Vector2(); 302 | var rotateEnd = new THREE.Vector2(); 303 | var rotateDelta = new THREE.Vector2(); 304 | 305 | var panStart = new THREE.Vector2(); 306 | var panEnd = new THREE.Vector2(); 307 | var panDelta = new THREE.Vector2(); 308 | 309 | var dollyStart = new THREE.Vector2(); 310 | var dollyEnd = new THREE.Vector2(); 311 | var dollyDelta = new THREE.Vector2(); 312 | 313 | function getAutoRotationAngle() { 314 | 315 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 316 | 317 | } 318 | 319 | function getZoomScale() { 320 | 321 | return Math.pow( 0.95, scope.zoomSpeed ); 322 | 323 | } 324 | 325 | function rotateLeft( angle ) { 326 | 327 | sphericalDelta.theta -= angle; 328 | 329 | } 330 | 331 | function rotateUp( angle ) { 332 | 333 | sphericalDelta.phi -= angle; 334 | 335 | } 336 | 337 | var panLeft = function () { 338 | 339 | var v = new THREE.Vector3(); 340 | 341 | return function panLeft( distance, objectMatrix ) { 342 | 343 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 344 | v.multiplyScalar( - distance ); 345 | 346 | panOffset.add( v ); 347 | 348 | }; 349 | 350 | }(); 351 | 352 | var panUp = function () { 353 | 354 | var v = new THREE.Vector3(); 355 | 356 | return function panUp( distance, objectMatrix ) { 357 | 358 | if ( scope.screenSpacePanning === true ) { 359 | 360 | v.setFromMatrixColumn( objectMatrix, 1 ); 361 | 362 | } else { 363 | 364 | v.setFromMatrixColumn( objectMatrix, 0 ); 365 | v.crossVectors( scope.object.up, v ); 366 | 367 | } 368 | 369 | v.multiplyScalar( distance ); 370 | 371 | panOffset.add( v ); 372 | 373 | }; 374 | 375 | }(); 376 | 377 | // deltaX and deltaY are in pixels; right and down are positive 378 | var pan = function () { 379 | 380 | var offset = new THREE.Vector3(); 381 | 382 | return function pan( deltaX, deltaY ) { 383 | 384 | var element = scope.domElement; 385 | 386 | if ( scope.object.isPerspectiveCamera ) { 387 | 388 | // perspective 389 | var position = scope.object.position; 390 | offset.copy( position ).sub( scope.target ); 391 | var targetDistance = offset.length(); 392 | 393 | // half of the fov is center to top of screen 394 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 395 | 396 | // we use only clientHeight here so aspect ratio does not distort speed 397 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 398 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 399 | 400 | } else if ( scope.object.isOrthographicCamera ) { 401 | 402 | // orthographic 403 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 404 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 405 | 406 | } else { 407 | 408 | // camera neither orthographic nor perspective 409 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 410 | scope.enablePan = false; 411 | 412 | } 413 | 414 | }; 415 | 416 | }(); 417 | 418 | function dollyIn( dollyScale ) { 419 | 420 | if ( scope.object.isPerspectiveCamera ) { 421 | 422 | scale /= dollyScale; 423 | 424 | } else if ( scope.object.isOrthographicCamera ) { 425 | 426 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 427 | scope.object.updateProjectionMatrix(); 428 | zoomChanged = true; 429 | 430 | } else { 431 | 432 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 433 | scope.enableZoom = false; 434 | 435 | } 436 | 437 | } 438 | 439 | function dollyOut( dollyScale ) { 440 | 441 | if ( scope.object.isPerspectiveCamera ) { 442 | 443 | scale *= dollyScale; 444 | 445 | } else if ( scope.object.isOrthographicCamera ) { 446 | 447 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 448 | scope.object.updateProjectionMatrix(); 449 | zoomChanged = true; 450 | 451 | } else { 452 | 453 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 454 | scope.enableZoom = false; 455 | 456 | } 457 | 458 | } 459 | 460 | // 461 | // event callbacks - update the object state 462 | // 463 | 464 | function handleMouseDownRotate( event ) { 465 | 466 | rotateStart.set( event.clientX, event.clientY ); 467 | 468 | } 469 | 470 | function handleMouseDownDolly( event ) { 471 | 472 | dollyStart.set( event.clientX, event.clientY ); 473 | 474 | } 475 | 476 | function handleMouseDownPan( event ) { 477 | 478 | panStart.set( event.clientX, event.clientY ); 479 | 480 | } 481 | 482 | function handleMouseMoveRotate( event ) { 483 | 484 | rotateEnd.set( event.clientX, event.clientY ); 485 | 486 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 487 | 488 | var element = scope.domElement; 489 | 490 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 491 | 492 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 493 | 494 | rotateStart.copy( rotateEnd ); 495 | 496 | scope.update(); 497 | 498 | } 499 | 500 | function handleMouseMoveDolly( event ) { 501 | 502 | dollyEnd.set( event.clientX, event.clientY ); 503 | 504 | dollyDelta.subVectors( dollyEnd, dollyStart ); 505 | 506 | if ( dollyDelta.y > 0 ) { 507 | 508 | dollyIn( getZoomScale() ); 509 | 510 | } else if ( dollyDelta.y < 0 ) { 511 | 512 | dollyOut( getZoomScale() ); 513 | 514 | } 515 | 516 | dollyStart.copy( dollyEnd ); 517 | 518 | scope.update(); 519 | 520 | } 521 | 522 | function handleMouseMovePan( event ) { 523 | 524 | panEnd.set( event.clientX, event.clientY ); 525 | 526 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 527 | 528 | pan( panDelta.x, panDelta.y ); 529 | 530 | panStart.copy( panEnd ); 531 | 532 | scope.update(); 533 | 534 | } 535 | 536 | function handleMouseUp( /*event*/ ) { 537 | 538 | // no-op 539 | 540 | } 541 | 542 | function handleMouseWheel( event ) { 543 | 544 | if ( event.deltaY < 0 ) { 545 | 546 | dollyOut( getZoomScale() ); 547 | 548 | } else if ( event.deltaY > 0 ) { 549 | 550 | dollyIn( getZoomScale() ); 551 | 552 | } 553 | 554 | scope.update(); 555 | 556 | } 557 | 558 | function handleKeyDown( event ) { 559 | 560 | var needsUpdate = false; 561 | 562 | switch ( event.keyCode ) { 563 | 564 | case scope.keys.UP: 565 | pan( 0, scope.keyPanSpeed ); 566 | needsUpdate = true; 567 | break; 568 | 569 | case scope.keys.BOTTOM: 570 | pan( 0, - scope.keyPanSpeed ); 571 | needsUpdate = true; 572 | break; 573 | 574 | case scope.keys.LEFT: 575 | pan( scope.keyPanSpeed, 0 ); 576 | needsUpdate = true; 577 | break; 578 | 579 | case scope.keys.RIGHT: 580 | pan( - scope.keyPanSpeed, 0 ); 581 | needsUpdate = true; 582 | break; 583 | 584 | } 585 | 586 | if ( needsUpdate ) { 587 | 588 | // prevent the browser from scrolling on cursor keys 589 | event.preventDefault(); 590 | 591 | scope.update(); 592 | 593 | } 594 | 595 | 596 | } 597 | 598 | function handleTouchStartRotate( event ) { 599 | 600 | if ( event.touches.length == 1 ) { 601 | 602 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 603 | 604 | } else { 605 | 606 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 607 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 608 | 609 | rotateStart.set( x, y ); 610 | 611 | } 612 | 613 | } 614 | 615 | function handleTouchStartPan( event ) { 616 | 617 | if ( event.touches.length == 1 ) { 618 | 619 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 620 | 621 | } else { 622 | 623 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 624 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 625 | 626 | panStart.set( x, y ); 627 | 628 | } 629 | 630 | } 631 | 632 | function handleTouchStartDolly( event ) { 633 | 634 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 635 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 636 | 637 | var distance = Math.sqrt( dx * dx + dy * dy ); 638 | 639 | dollyStart.set( 0, distance ); 640 | 641 | } 642 | 643 | function handleTouchStartDollyPan( event ) { 644 | 645 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 646 | 647 | if ( scope.enablePan ) handleTouchStartPan( event ); 648 | 649 | } 650 | 651 | function handleTouchStartDollyRotate( event ) { 652 | 653 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 654 | 655 | if ( scope.enableRotate ) handleTouchStartRotate( event ); 656 | 657 | } 658 | 659 | function handleTouchMoveRotate( event ) { 660 | 661 | if ( event.touches.length == 1 ) { 662 | 663 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 664 | 665 | } else { 666 | 667 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 668 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 669 | 670 | rotateEnd.set( x, y ); 671 | 672 | } 673 | 674 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 675 | 676 | var element = scope.domElement; 677 | 678 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 679 | 680 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 681 | 682 | rotateStart.copy( rotateEnd ); 683 | 684 | } 685 | 686 | function handleTouchMovePan( event ) { 687 | 688 | if ( event.touches.length == 1 ) { 689 | 690 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 691 | 692 | } else { 693 | 694 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 695 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 696 | 697 | panEnd.set( x, y ); 698 | 699 | } 700 | 701 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 702 | 703 | pan( panDelta.x, panDelta.y ); 704 | 705 | panStart.copy( panEnd ); 706 | 707 | } 708 | 709 | function handleTouchMoveDolly( event ) { 710 | 711 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 712 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 713 | 714 | var distance = Math.sqrt( dx * dx + dy * dy ); 715 | 716 | dollyEnd.set( 0, distance ); 717 | 718 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 719 | 720 | dollyIn( dollyDelta.y ); 721 | 722 | dollyStart.copy( dollyEnd ); 723 | 724 | } 725 | 726 | function handleTouchMoveDollyPan( event ) { 727 | 728 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 729 | 730 | if ( scope.enablePan ) handleTouchMovePan( event ); 731 | 732 | } 733 | 734 | function handleTouchMoveDollyRotate( event ) { 735 | 736 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 737 | 738 | if ( scope.enableRotate ) handleTouchMoveRotate( event ); 739 | 740 | } 741 | 742 | function handleTouchEnd( /*event*/ ) { 743 | 744 | // no-op 745 | 746 | } 747 | 748 | // 749 | // event handlers - FSM: listen for events and reset state 750 | // 751 | 752 | function onMouseDown( event ) { 753 | 754 | if ( scope.enabled === false ) return; 755 | 756 | // Prevent the browser from scrolling. 757 | 758 | event.preventDefault(); 759 | 760 | // Manually set the focus since calling preventDefault above 761 | // prevents the browser from setting it automatically. 762 | 763 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 764 | 765 | switch ( event.button ) { 766 | 767 | case 0: 768 | 769 | switch ( scope.mouseButtons.LEFT ) { 770 | 771 | case THREE.MOUSE.ROTATE: 772 | 773 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 774 | 775 | if ( scope.enablePan === false ) return; 776 | 777 | handleMouseDownPan( event ); 778 | 779 | state = STATE.PAN; 780 | 781 | } else { 782 | 783 | if ( scope.enableRotate === false ) return; 784 | 785 | handleMouseDownRotate( event ); 786 | 787 | state = STATE.ROTATE; 788 | 789 | } 790 | 791 | break; 792 | 793 | case THREE.MOUSE.PAN: 794 | 795 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 796 | 797 | if ( scope.enableRotate === false ) return; 798 | 799 | handleMouseDownRotate( event ); 800 | 801 | state = STATE.ROTATE; 802 | 803 | } else { 804 | 805 | if ( scope.enablePan === false ) return; 806 | 807 | handleMouseDownPan( event ); 808 | 809 | state = STATE.PAN; 810 | 811 | } 812 | 813 | break; 814 | 815 | default: 816 | 817 | state = STATE.NONE; 818 | 819 | } 820 | 821 | break; 822 | 823 | 824 | case 1: 825 | 826 | switch ( scope.mouseButtons.MIDDLE ) { 827 | 828 | case THREE.MOUSE.DOLLY: 829 | 830 | if ( scope.enableZoom === false ) return; 831 | 832 | handleMouseDownDolly( event ); 833 | 834 | state = STATE.DOLLY; 835 | 836 | break; 837 | 838 | 839 | default: 840 | 841 | state = STATE.NONE; 842 | 843 | } 844 | 845 | break; 846 | 847 | case 2: 848 | 849 | switch ( scope.mouseButtons.RIGHT ) { 850 | 851 | case THREE.MOUSE.ROTATE: 852 | 853 | if ( scope.enableRotate === false ) return; 854 | 855 | handleMouseDownRotate( event ); 856 | 857 | state = STATE.ROTATE; 858 | 859 | break; 860 | 861 | case THREE.MOUSE.PAN: 862 | 863 | if ( scope.enablePan === false ) return; 864 | 865 | handleMouseDownPan( event ); 866 | 867 | state = STATE.PAN; 868 | 869 | break; 870 | 871 | default: 872 | 873 | state = STATE.NONE; 874 | 875 | } 876 | 877 | break; 878 | 879 | } 880 | 881 | if ( state !== STATE.NONE ) { 882 | 883 | document.addEventListener( 'mousemove', onMouseMove, false ); 884 | document.addEventListener( 'mouseup', onMouseUp, false ); 885 | 886 | scope.dispatchEvent( startEvent ); 887 | 888 | } 889 | 890 | } 891 | 892 | function onMouseMove( event ) { 893 | 894 | if ( scope.enabled === false ) return; 895 | 896 | event.preventDefault(); 897 | 898 | switch ( state ) { 899 | 900 | case STATE.ROTATE: 901 | 902 | if ( scope.enableRotate === false ) return; 903 | 904 | handleMouseMoveRotate( event ); 905 | 906 | break; 907 | 908 | case STATE.DOLLY: 909 | 910 | if ( scope.enableZoom === false ) return; 911 | 912 | handleMouseMoveDolly( event ); 913 | 914 | break; 915 | 916 | case STATE.PAN: 917 | 918 | if ( scope.enablePan === false ) return; 919 | 920 | handleMouseMovePan( event ); 921 | 922 | break; 923 | 924 | } 925 | 926 | } 927 | 928 | function onMouseUp( event ) { 929 | 930 | if ( scope.enabled === false ) return; 931 | 932 | handleMouseUp( event ); 933 | 934 | document.removeEventListener( 'mousemove', onMouseMove, false ); 935 | document.removeEventListener( 'mouseup', onMouseUp, false ); 936 | 937 | scope.dispatchEvent( endEvent ); 938 | 939 | state = STATE.NONE; 940 | 941 | } 942 | 943 | function onMouseWheel( event ) { 944 | 945 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 946 | 947 | event.preventDefault(); 948 | event.stopPropagation(); 949 | 950 | scope.dispatchEvent( startEvent ); 951 | 952 | handleMouseWheel( event ); 953 | 954 | scope.dispatchEvent( endEvent ); 955 | 956 | } 957 | 958 | function onKeyDown( event ) { 959 | 960 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 961 | 962 | handleKeyDown( event ); 963 | 964 | } 965 | 966 | function onTouchStart( event ) { 967 | 968 | if ( scope.enabled === false ) return; 969 | 970 | event.preventDefault(); 971 | 972 | switch ( event.touches.length ) { 973 | 974 | case 1: 975 | 976 | switch ( scope.touches.ONE ) { 977 | 978 | case THREE.TOUCH.ROTATE: 979 | 980 | if ( scope.enableRotate === false ) return; 981 | 982 | handleTouchStartRotate( event ); 983 | 984 | state = STATE.TOUCH_ROTATE; 985 | 986 | break; 987 | 988 | case THREE.TOUCH.PAN: 989 | 990 | if ( scope.enablePan === false ) return; 991 | 992 | handleTouchStartPan( event ); 993 | 994 | state = STATE.TOUCH_PAN; 995 | 996 | break; 997 | 998 | default: 999 | 1000 | state = STATE.NONE; 1001 | 1002 | } 1003 | 1004 | break; 1005 | 1006 | case 2: 1007 | 1008 | switch ( scope.touches.TWO ) { 1009 | 1010 | case THREE.TOUCH.DOLLY_PAN: 1011 | 1012 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1013 | 1014 | handleTouchStartDollyPan( event ); 1015 | 1016 | state = STATE.TOUCH_DOLLY_PAN; 1017 | 1018 | break; 1019 | 1020 | case THREE.TOUCH.DOLLY_ROTATE: 1021 | 1022 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1023 | 1024 | handleTouchStartDollyRotate( event ); 1025 | 1026 | state = STATE.TOUCH_DOLLY_ROTATE; 1027 | 1028 | break; 1029 | 1030 | default: 1031 | 1032 | state = STATE.NONE; 1033 | 1034 | } 1035 | 1036 | break; 1037 | 1038 | default: 1039 | 1040 | state = STATE.NONE; 1041 | 1042 | } 1043 | 1044 | if ( state !== STATE.NONE ) { 1045 | 1046 | scope.dispatchEvent( startEvent ); 1047 | 1048 | } 1049 | 1050 | } 1051 | 1052 | function onTouchMove( event ) { 1053 | 1054 | if ( scope.enabled === false ) return; 1055 | 1056 | event.preventDefault(); 1057 | event.stopPropagation(); 1058 | 1059 | switch ( state ) { 1060 | 1061 | case STATE.TOUCH_ROTATE: 1062 | 1063 | if ( scope.enableRotate === false ) return; 1064 | 1065 | handleTouchMoveRotate( event ); 1066 | 1067 | scope.update(); 1068 | 1069 | break; 1070 | 1071 | case STATE.TOUCH_PAN: 1072 | 1073 | if ( scope.enablePan === false ) return; 1074 | 1075 | handleTouchMovePan( event ); 1076 | 1077 | scope.update(); 1078 | 1079 | break; 1080 | 1081 | case STATE.TOUCH_DOLLY_PAN: 1082 | 1083 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1084 | 1085 | handleTouchMoveDollyPan( event ); 1086 | 1087 | scope.update(); 1088 | 1089 | break; 1090 | 1091 | case STATE.TOUCH_DOLLY_ROTATE: 1092 | 1093 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1094 | 1095 | handleTouchMoveDollyRotate( event ); 1096 | 1097 | scope.update(); 1098 | 1099 | break; 1100 | 1101 | default: 1102 | 1103 | state = STATE.NONE; 1104 | 1105 | } 1106 | 1107 | } 1108 | 1109 | function onTouchEnd( event ) { 1110 | 1111 | if ( scope.enabled === false ) return; 1112 | 1113 | handleTouchEnd( event ); 1114 | 1115 | scope.dispatchEvent( endEvent ); 1116 | 1117 | state = STATE.NONE; 1118 | 1119 | } 1120 | 1121 | function onContextMenu( event ) { 1122 | 1123 | if ( scope.enabled === false ) return; 1124 | 1125 | event.preventDefault(); 1126 | 1127 | } 1128 | 1129 | // 1130 | 1131 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1132 | 1133 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 1134 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1135 | 1136 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1137 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1138 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1139 | 1140 | scope.domElement.addEventListener( 'keydown', onKeyDown, false ); 1141 | 1142 | // make sure element can receive keys. 1143 | 1144 | if ( scope.domElement.tabIndex === - 1 ) { 1145 | 1146 | scope.domElement.tabIndex = 0; 1147 | 1148 | } 1149 | 1150 | // force an update at start 1151 | 1152 | this.update(); 1153 | 1154 | }; 1155 | 1156 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1157 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 1158 | 1159 | 1160 | // This set of controls performs orbiting, dollying (zooming), and panning. 1161 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 1162 | // This is very similar to OrbitControls, another set of touch behavior 1163 | // 1164 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate 1165 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 1166 | // Pan - left mouse, or arrow keys / touch: one-finger move 1167 | 1168 | THREE.MapControls = function ( object, domElement ) { 1169 | 1170 | THREE.OrbitControls.call( this, object, domElement ); 1171 | 1172 | this.mouseButtons.LEFT = THREE.MOUSE.PAN; 1173 | this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; 1174 | 1175 | this.touches.ONE = THREE.TOUCH.PAN; 1176 | this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; 1177 | 1178 | }; 1179 | 1180 | THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1181 | THREE.MapControls.prototype.constructor = THREE.MapControls; 1182 | /* three-orbitcontrols addendum */ module.exports = exports.default = THREE.OrbitControls; 1183 | --------------------------------------------------------------------------------