├── .gitignore ├── README.md ├── simple-scene ├── zelda.jpg ├── snowy-night-street.jpg ├── index.html └── js │ └── scripts.js ├── perf-testing ├── index.html ├── snowing.html └── js │ ├── scripts.js │ └── snowing.js ├── three-particles ├── index.html ├── snowing.html └── js │ ├── snowing.js │ └── scripts.js ├── package.json ├── three.js.shader.html ├── three ├── DeviceOrientationControls.js └── OrbitControls.js └── obj_loader ├── webgl_loader_obj.html └── js └── OBJLoader.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | These are some simple three.js experiements I make. Nothing to see, move along... -------------------------------------------------------------------------------- /simple-scene/zelda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiya/three.js-samples/HEAD/simple-scene/zelda.jpg -------------------------------------------------------------------------------- /simple-scene/snowy-night-street.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiya/three.js-samples/HEAD/simple-scene/snowy-night-street.jpg -------------------------------------------------------------------------------- /simple-scene/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Particles 6 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /perf-testing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Particles 6 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /three-particles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Particles 6 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three.js-samples", 3 | "version": "0.0.1", 4 | "description": "A collection of three.js experiments.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/shiya/three.js-samples.git" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/shiya/three.js-samples/issues" 17 | }, 18 | "homepage": "https://github.com/shiya/three.js-samples#readme" 19 | } 20 | -------------------------------------------------------------------------------- /perf-testing/snowing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Particles 6 | 23 | 24 | 25 |
26 |
Source Code
27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /three-particles/snowing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Particles 6 | 23 | 24 | 25 |
26 |
Source Code
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /three-particles/js/snowing.js: -------------------------------------------------------------------------------- 1 | var scene, camera, renderer, controls; 2 | var canvas = document.getElementById('canvas'); 3 | 4 | // particles set up 5 | var particleCount = 1000; 6 | var particles = []; 7 | 8 | init(); 9 | animate(); 10 | 11 | function init() { 12 | 13 | scene = new THREE.Scene(); 14 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1100 ); 15 | controls = new THREE.DeviceOrientationControls( camera ); 16 | renderer = new THREE.WebGLRenderer(); 17 | renderer.setClearColor( 0x111727, 1); 18 | renderer.setSize( window.innerWidth, window.innerHeight ); 19 | 20 | // create a random set of particles 21 | for (var i = 0; i < particleCount; i++) { 22 | 23 | particles[i] = new THREE.Mesh( 24 | new THREE.SphereGeometry(5, 32, 32), 25 | new THREE.MeshBasicMaterial({ 26 | color: 0xFFFFFF, 27 | transparent: true, 28 | opacity: 0.5 29 | }) 30 | ); 31 | 32 | //randomize positions 33 | var px = Math.random() * window.innerWidth * 2 - window.innerWidth; 34 | var py = Math.random() * window.innerHeight * 2 - window.innerHeight; 35 | var pz = Math.random() * window.innerWidth * 2 - window.innerWidth; 36 | 37 | particles[i].position.x = px; 38 | particles[i].position.y = py; 39 | particles[i].position.z = pz; 40 | 41 | particles[i].direction = { 42 | x: Math.random(), 43 | y: Math.random() 44 | } 45 | particles[i].velocity = { 46 | y: Math.random() 47 | } 48 | 49 | scene.add(particles[i]); 50 | } 51 | 52 | canvas.appendChild( renderer.domElement ); 53 | 54 | renderer.render( scene, camera ); 55 | } 56 | 57 | function animate() { 58 | 59 | requestAnimationFrame( animate ); 60 | controls.update(); 61 | 62 | for (var i = 0; i < particleCount; i++) { 63 | particles[i].position.y -= particles[i].velocity.y * 3; 64 | if (particles[i].position.y < -window.innerHeight || 65 | particles[i].position.y > window.innerHeight) { 66 | particles[i].position.y = window.innerHeight; 67 | } 68 | } 69 | 70 | renderer.render( scene, camera ); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /three.js.shader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 |
15 | 16 | 17 | 24 | 25 | 34 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /perf-testing/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var scene, camera, renderer, controls; 3 | var canvas = document.getElementById('canvas'); 4 | 5 | // particles set up 6 | var particleCount = 100; 7 | var particles = []; 8 | 9 | init(); 10 | animate(); 11 | 12 | function fillScene() { 13 | 14 | var particleGeometry = new THREE.SphereGeometry(10, 32, 32); // size, number of polys to form this circle 15 | var particleMaterial = new THREE.MeshBasicMaterial({ 16 | color: 0xFFFFFF, 17 | transparent: true, 18 | opacity: 0.5 19 | }); 20 | 21 | // create a random set of particles 22 | for (var i = 0; i < particleCount; i++) { 23 | 24 | particles[i] = new THREE.Mesh( particleGeometry, particleMaterial ); 25 | 26 | //randomize positions 27 | particles[i].position.x = Math.random() * window.innerWidth * 2 - window.innerWidth;; 28 | particles[i].position.y = Math.random() * window.innerHeight * 2 - window.innerHeight; 29 | particles[i].position.z = Math.random() * window.innerWidth * 2 - window.innerWidth; 30 | 31 | particles[i]. direction = { 32 | x: Math.random(), 33 | y: Math.random() 34 | } 35 | 36 | scene.add(particles[i]); 37 | } 38 | } 39 | 40 | function init() { 41 | scene = new THREE.Scene(); 42 | 43 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 ); 44 | camera.position.z = 1000; 45 | 46 | renderer = new THREE.WebGLRenderer(); 47 | renderer.setClearColor( 0x31AED1, 1); 48 | renderer.setSize( window.innerWidth, window.innerHeight ); 49 | 50 | controls = new THREE.OrbitControls( camera, renderer.domElement ); 51 | controls.enableDamping = true; 52 | controls.dampingFactor = 0.25; 53 | controls.enableZoom = false; 54 | 55 | fillScene(); 56 | canvas.appendChild( renderer.domElement ); 57 | renderer.render( scene, camera ); 58 | } 59 | 60 | function animate() { 61 | requestAnimationFrame( animate ); 62 | controls.update(); 63 | 64 | for (var i = 0; i < particleCount; i++) { 65 | particles[i].position.x += particles[i].direction.x; 66 | particles[i].position.y += particles[i].direction.y; 67 | 68 | // if edge is reached, bounce back 69 | if (particles[i].position.x < -window.innerWidth || 70 | particles[i].position.x > window.innerWidth) { 71 | particles[i].direction.x = -particles[i].direction.x; 72 | } 73 | if (particles[i].position.y < -window.innerHeight || 74 | particles[i].position.y > window.innerHeight) { 75 | particles[i].direction.y = -particles[i].direction.y; 76 | } 77 | } 78 | 79 | renderer.render( scene, camera ); 80 | } 81 | })(); 82 | -------------------------------------------------------------------------------- /three-particles/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var scene, camera, renderer, controls; 3 | var canvas = document.getElementById('canvas'); 4 | 5 | // particles set up 6 | var particleCount = 100; 7 | var particles = []; 8 | 9 | init(); 10 | animate(); 11 | 12 | function fillScene() { 13 | 14 | var particleGeometry = new THREE.SphereGeometry(10, 32, 32); // size, number of polys to form this circle 15 | var particleMaterial = new THREE.MeshBasicMaterial({ 16 | color: 0xFFFFFF, 17 | transparent: true, 18 | opacity: 0.5 19 | }); 20 | 21 | // create a random set of particles 22 | for (var i = 0; i < particleCount; i++) { 23 | 24 | particles[i] = new THREE.Mesh( particleGeometry, particleMaterial ); 25 | 26 | //randomize positions 27 | particles[i].position.x = Math.random() * window.innerWidth * 2 - window.innerWidth;; 28 | particles[i].position.y = Math.random() * window.innerHeight * 2 - window.innerHeight; 29 | particles[i].position.z = Math.random() * window.innerWidth * 2 - window.innerWidth; 30 | 31 | particles[i]. direction = { 32 | x: Math.random(), 33 | y: Math.random() 34 | } 35 | 36 | scene.add(particles[i]); 37 | } 38 | } 39 | 40 | function init() { 41 | scene = new THREE.Scene(); 42 | 43 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 ); 44 | camera.position.z = 1000; 45 | 46 | renderer = new THREE.WebGLRenderer(); 47 | renderer.setClearColor( 0x31AED1, 1); 48 | renderer.setSize( window.innerWidth, window.innerHeight ); 49 | 50 | controls = new THREE.OrbitControls( camera, renderer.domElement ); 51 | controls.enableDamping = true; 52 | controls.dampingFactor = 0.25; 53 | controls.enableZoom = false; 54 | 55 | fillScene(); 56 | canvas.appendChild( renderer.domElement ); 57 | renderer.render( scene, camera ); 58 | } 59 | 60 | function animate() { 61 | requestAnimationFrame( animate ); 62 | controls.update(); 63 | 64 | for (var i = 0; i < particleCount; i++) { 65 | particles[i].position.x += particles[i].direction.x; 66 | particles[i].position.y += particles[i].direction.y; 67 | 68 | // if edge is reached, bounce back 69 | if (particles[i].position.x < -window.innerWidth || 70 | particles[i].position.x > window.innerWidth) { 71 | particles[i].direction.x = -particles[i].direction.x; 72 | } 73 | if (particles[i].position.y < -window.innerHeight || 74 | particles[i].position.y > window.innerHeight) { 75 | particles[i].direction.y = -particles[i].direction.y; 76 | } 77 | } 78 | 79 | renderer.render( scene, camera ); 80 | } 81 | })(); 82 | -------------------------------------------------------------------------------- /simple-scene/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var scene, camera, renderer, controls, diretionalLight, hemisphereLight; 3 | var canvas = document.getElementById('canvas'); 4 | 5 | // particles set up 6 | var particleCount = 100; 7 | var particles = []; 8 | 9 | init(); 10 | animate(); 11 | 12 | function fillScene() { 13 | 14 | 15 | // Add stuff to your scene 16 | 17 | var geometry = new THREE.BoxGeometry( 100, 100, 100 ); 18 | var material = new THREE.MeshPhongMaterial( {color: 0x00ff00} ); 19 | var cube = new THREE.Mesh( geometry, material ); 20 | scene.add( cube ); 21 | 22 | } 23 | 24 | function init() { 25 | scene = new THREE.Scene(); 26 | 27 | // LIGHTS 28 | hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.6 ); 29 | hemiLight.color.setHSL( 0.6, 1, 0.6 ); 30 | hemiLight.groundColor.setHSL( 0.095, 1, 0.75 ); 31 | hemiLight.position.set( 0, 500, 0 ); 32 | scene.add( hemiLight ); 33 | // 34 | dirLight = new THREE.DirectionalLight( 0xffffff, 1 ); 35 | dirLight.color.setHSL( 0.1, 1, 0.95 ); 36 | dirLight.position.set( -1, 1.75, 1 ); 37 | dirLight.position.multiplyScalar( 50 ); 38 | scene.add( dirLight ); 39 | 40 | dirLight.castShadow = true; 41 | 42 | dirLight.shadow.mapSize.width = 2048; 43 | dirLight.shadow.mapSize.height = 2048; 44 | 45 | var d = 50; 46 | dirLight.shadow.camera.left = -d; 47 | dirLight.shadow.camera.right = d; 48 | dirLight.shadow.camera.top = d; 49 | dirLight.shadow.camera.bottom = -d; 50 | 51 | dirLight.shadow.camera.far = 3500; 52 | dirLight.shadow.bias = -0.0001; 53 | 54 | // ground 55 | var groundGeo = new THREE.PlaneBufferGeometry( 10000, 10000 ); 56 | var groundMat = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x050505 } ); 57 | groundMat.color.setHex( 0x0C73B8 ); 58 | var ground = new THREE.Mesh( groundGeo, groundMat ); 59 | ground.rotation.x = -Math.PI/2; 60 | ground.position.y = -33; 61 | scene.add( ground ); 62 | ground.receiveShadow = true; 63 | 64 | // camera 65 | 66 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 ); 67 | camera.position.z = 1000; 68 | 69 | renderer = new THREE.WebGLRenderer(); 70 | renderer.setClearColor( 0xBFE6F3, 1); 71 | renderer.setSize( window.innerWidth, window.innerHeight ); 72 | 73 | controls = new THREE.OrbitControls( camera, renderer.domElement ); 74 | controls.enableDamping = true; 75 | controls.dampingFactor = 0.25; 76 | controls.enableZoom = false; 77 | 78 | fillScene(); 79 | canvas.appendChild( renderer.domElement ); 80 | renderer.render( scene, camera ); 81 | } 82 | 83 | function animate() { 84 | requestAnimationFrame( animate ); 85 | controls.update(); 86 | 87 | renderer.render( scene, camera ); 88 | } 89 | })(); -------------------------------------------------------------------------------- /perf-testing/js/snowing.js: -------------------------------------------------------------------------------- 1 | var scene, camera, renderer, controls; 2 | var canvas = document.getElementById('canvas'); 3 | 4 | // particles set up 5 | var particleCount = 1000; 6 | var particles = []; 7 | 8 | init(); 9 | animate(); 10 | 11 | function init() { 12 | 13 | scene = new THREE.Scene(); 14 | 15 | // camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1100 ); 16 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 ); 17 | camera.position.z = 1000; 18 | 19 | // controls = new THREE.DeviceOrientationControls( camera ); 20 | 21 | 22 | renderer = new THREE.WebGLRenderer(); 23 | renderer.setClearColor( 0x111727, 1 ); 24 | renderer.setSize( window.innerWidth, window.innerHeight ); 25 | 26 | controls = new THREE.OrbitControls( camera, renderer.domElement ); 27 | controls.enableDamping = true; 28 | controls.dampingFactor = 0.25; 29 | controls.enableZoom = false; 30 | 31 | // ambient light 32 | var hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.6 ); 33 | hemiLight.color.setHex( 0xffffff ); 34 | hemiLight.groundColor.setHex( 0xffffff ); 35 | hemiLight.position.set( 0, 500, 0 ); 36 | scene.add( hemiLight ); 37 | 38 | // point light (stree lamp) 39 | var pointLight = new THREE.PointLight( 0xffffff, 1, 700, 2 ); 40 | pointLight.position.set(200, 200, 300); 41 | scene.add(pointLight); 42 | 43 | // ground 44 | var groundGeo = new THREE.PlaneBufferGeometry( 10000, 10000 ); 45 | var groundMat = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x050505 } ); 46 | groundMat.color.setHex( 0xbfd2eb ) 47 | var ground = new THREE.Mesh( groundGeo, groundMat ); 48 | ground.rotation.x = -Math.PI/2; 49 | ground.position.y = -33; 50 | scene.add( ground ); 51 | ground.receiveShadow = true; 52 | 53 | // create a random set of particles 54 | for (var i = 0; i < particleCount; i++) { 55 | 56 | particles[i] = new THREE.Mesh( new THREE.SphereGeometry(5, 32, 32), new THREE.MeshPhongMaterial({color: 0xbfd2eb})); 57 | 58 | //randomize positions 59 | var px = Math.random() * window.innerWidth * 2 - window.innerWidth; 60 | var py = Math.random() * window.innerHeight * 2 - window.innerHeight; 61 | var pz = Math.random() * window.innerWidth * 2 - window.innerWidth; 62 | 63 | particles[i].position.x = px; 64 | particles[i].position.y = py; 65 | particles[i].position.z = pz; 66 | 67 | particles[i].direction = { 68 | x: Math.random(), 69 | y: Math.random() 70 | } 71 | particles[i].velocity = { 72 | y: Math.random() 73 | } 74 | 75 | scene.add(particles[i]); 76 | } 77 | 78 | canvas.appendChild( renderer.domElement ); 79 | 80 | renderer.render( scene, camera ); 81 | } 82 | 83 | function animate() { 84 | 85 | requestAnimationFrame( animate ); 86 | controls.update(); 87 | 88 | for (var i = 0; i < particleCount; i++) { 89 | particles[i].position.y -= particles[i].velocity.y * 3; 90 | if (particles[i].position.y < -window.innerHeight || 91 | particles[i].position.y > window.innerHeight) { 92 | particles[i].position.y = window.innerHeight; 93 | } 94 | } 95 | 96 | renderer.render( scene, camera ); 97 | 98 | } 99 | -------------------------------------------------------------------------------- /three/DeviceOrientationControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author richt / http://richt.me 3 | * @author WestLangley / http://github.com/WestLangley 4 | * 5 | * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html) 6 | */ 7 | 8 | THREE.DeviceOrientationControls = function( object ) { 9 | 10 | var scope = this; 11 | 12 | this.object = object; 13 | this.object.rotation.reorder( "YXZ" ); 14 | 15 | this.enabled = true; 16 | 17 | this.deviceOrientation = {}; 18 | this.screenOrientation = 0; 19 | 20 | this.alpha = 0; 21 | this.alphaOffsetAngle = 0; 22 | 23 | 24 | var onDeviceOrientationChangeEvent = function( event ) { 25 | 26 | scope.deviceOrientation = event; 27 | 28 | }; 29 | 30 | var onScreenOrientationChangeEvent = function() { 31 | 32 | scope.screenOrientation = window.orientation || 0; 33 | 34 | }; 35 | 36 | // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y'' 37 | 38 | var setObjectQuaternion = function() { 39 | 40 | var zee = new THREE.Vector3( 0, 0, 1 ); 41 | 42 | var euler = new THREE.Euler(); 43 | 44 | var q0 = new THREE.Quaternion(); 45 | 46 | var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis 47 | 48 | return function( quaternion, alpha, beta, gamma, orient ) { 49 | 50 | euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us 51 | 52 | quaternion.setFromEuler( euler ); // orient the device 53 | 54 | quaternion.multiply( q1 ); // camera looks out the back of the device, not the top 55 | 56 | quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation 57 | 58 | } 59 | 60 | }(); 61 | 62 | this.connect = function() { 63 | 64 | onScreenOrientationChangeEvent(); // run once on load 65 | 66 | window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); 67 | window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); 68 | 69 | scope.enabled = true; 70 | 71 | }; 72 | 73 | this.disconnect = function() { 74 | 75 | window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); 76 | window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); 77 | 78 | scope.enabled = false; 79 | 80 | }; 81 | 82 | this.update = function() { 83 | 84 | if ( scope.enabled === false ) return; 85 | 86 | var alpha = scope.deviceOrientation.alpha ? THREE.Math.degToRad( scope.deviceOrientation.alpha ) + this.alphaOffsetAngle : 0; // Z 87 | var beta = scope.deviceOrientation.beta ? THREE.Math.degToRad( scope.deviceOrientation.beta ) : 0; // X' 88 | var gamma = scope.deviceOrientation.gamma ? THREE.Math.degToRad( scope.deviceOrientation.gamma ) : 0; // Y'' 89 | var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O 90 | 91 | setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient ); 92 | this.alpha = alpha; 93 | 94 | }; 95 | 96 | this.updateAlphaOffsetAngle = function( angle ) { 97 | 98 | this.alphaOffsetAngle = angle; 99 | this.update(); 100 | 101 | }; 102 | 103 | this.dispose = function() { 104 | 105 | this.disconnect(); 106 | 107 | }; 108 | 109 | this.connect(); 110 | 111 | }; 112 | -------------------------------------------------------------------------------- /obj_loader/webgl_loader_obj.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | three.js webgl - loaders - OBJ loader 6 | 7 | 8 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /obj_loader/js/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | this.materials = null; 10 | 11 | this.regexp = { 12 | // v float float float 13 | vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 14 | // vn float float float 15 | normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 16 | // vt float float 17 | uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 18 | // f vertex vertex vertex 19 | face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/, 20 | // f vertex/uv vertex/uv vertex/uv 21 | face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/, 22 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 23 | face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, 24 | // f vertex//normal vertex//normal vertex//normal 25 | face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/, 26 | // o object_name | g group_name 27 | object_pattern : /^[og]\s*(.+)?/, 28 | // s boolean 29 | smoothing_pattern : /^s\s+(\d+|on|off)/, 30 | // mtllib file_reference 31 | material_library_pattern : /^mtllib /, 32 | // usemtl material_name 33 | material_use_pattern : /^usemtl / 34 | }; 35 | 36 | }; 37 | 38 | THREE.OBJLoader.prototype = { 39 | 40 | constructor: THREE.OBJLoader, 41 | 42 | load: function ( url, onLoad, onProgress, onError ) { 43 | 44 | var scope = this; 45 | 46 | var loader = new THREE.XHRLoader( scope.manager ); 47 | loader.setPath( this.path ); 48 | loader.load( url, function ( text ) { 49 | 50 | onLoad( scope.parse( text ) ); 51 | 52 | }, onProgress, onError ); 53 | 54 | }, 55 | 56 | setPath: function ( value ) { 57 | 58 | this.path = value; 59 | 60 | }, 61 | 62 | setMaterials: function ( materials ) { 63 | 64 | this.materials = materials; 65 | 66 | }, 67 | 68 | _createParserState : function () { 69 | 70 | var state = { 71 | objects : [], 72 | object : {}, 73 | 74 | vertices : [], 75 | normals : [], 76 | uvs : [], 77 | 78 | materialLibraries : [], 79 | 80 | startObject: function ( name, fromDeclaration ) { 81 | 82 | // If the current object (initial from reset) is not from a g/o declaration in the parsed 83 | // file. We need to use it for the first parsed g/o to keep things in sync. 84 | if ( this.object && this.object.fromDeclaration === false ) { 85 | 86 | this.object.name = name; 87 | this.object.fromDeclaration = ( fromDeclaration !== false ); 88 | return; 89 | 90 | } 91 | 92 | if ( this.object && typeof this.object._finalize === 'function' ) { 93 | 94 | this.object._finalize(); 95 | 96 | } 97 | 98 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 99 | 100 | this.object = { 101 | name : name || '', 102 | fromDeclaration : ( fromDeclaration !== false ), 103 | 104 | geometry : { 105 | vertices : [], 106 | normals : [], 107 | uvs : [] 108 | }, 109 | materials : [], 110 | smooth : true, 111 | 112 | startMaterial : function( name, libraries ) { 113 | 114 | var previous = this._finalize( false ); 115 | 116 | // New usemtl declaration overwrites an inherited material, except if faces were declared 117 | // after the material, then it must be preserved for proper MultiMaterial continuation. 118 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { 119 | 120 | this.materials.splice( previous.index, 1 ); 121 | 122 | } 123 | 124 | var material = { 125 | index : this.materials.length, 126 | name : name || '', 127 | mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), 128 | smooth : ( previous !== undefined ? previous.smooth : this.smooth ), 129 | groupStart : ( previous !== undefined ? previous.groupEnd : 0 ), 130 | groupEnd : -1, 131 | groupCount : -1, 132 | inherited : false, 133 | 134 | clone : function( index ) { 135 | return { 136 | index : ( typeof index === 'number' ? index : this.index ), 137 | name : this.name, 138 | mtllib : this.mtllib, 139 | smooth : this.smooth, 140 | groupStart : this.groupEnd, 141 | groupEnd : -1, 142 | groupCount : -1, 143 | inherited : false 144 | }; 145 | } 146 | }; 147 | 148 | this.materials.push( material ); 149 | 150 | return material; 151 | 152 | }, 153 | 154 | currentMaterial : function() { 155 | 156 | if ( this.materials.length > 0 ) { 157 | return this.materials[ this.materials.length - 1 ]; 158 | } 159 | 160 | return undefined; 161 | 162 | }, 163 | 164 | _finalize : function( end ) { 165 | 166 | var lastMultiMaterial = this.currentMaterial(); 167 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) { 168 | 169 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 170 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 171 | lastMultiMaterial.inherited = false; 172 | 173 | } 174 | 175 | // Guarantee at least one empty material, this makes the creation later more straight forward. 176 | if ( end !== false && this.materials.length === 0 ) { 177 | this.materials.push({ 178 | name : '', 179 | smooth : this.smooth 180 | }); 181 | } 182 | 183 | return lastMultiMaterial; 184 | 185 | } 186 | }; 187 | 188 | // Inherit previous objects material. 189 | // Spec tells us that a declared material must be set to all objects until a new material is declared. 190 | // If a usemtl declaration is encountered while this new object is being parsed, it will 191 | // overwrite the inherited material. Exception being that there was already face declarations 192 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation. 193 | 194 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) { 195 | 196 | var declared = previousMaterial.clone( 0 ); 197 | declared.inherited = true; 198 | this.object.materials.push( declared ); 199 | 200 | } 201 | 202 | this.objects.push( this.object ); 203 | 204 | }, 205 | 206 | finalize : function() { 207 | 208 | if ( this.object && typeof this.object._finalize === 'function' ) { 209 | 210 | this.object._finalize(); 211 | 212 | } 213 | 214 | }, 215 | 216 | parseVertexIndex: function ( value, len ) { 217 | 218 | var index = parseInt( value, 10 ); 219 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 220 | 221 | }, 222 | 223 | parseNormalIndex: function ( value, len ) { 224 | 225 | var index = parseInt( value, 10 ); 226 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 227 | 228 | }, 229 | 230 | parseUVIndex: function ( value, len ) { 231 | 232 | var index = parseInt( value, 10 ); 233 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; 234 | 235 | }, 236 | 237 | addVertex: function ( a, b, c ) { 238 | 239 | var src = this.vertices; 240 | var dst = this.object.geometry.vertices; 241 | 242 | dst.push( src[ a + 0 ] ); 243 | dst.push( src[ a + 1 ] ); 244 | dst.push( src[ a + 2 ] ); 245 | dst.push( src[ b + 0 ] ); 246 | dst.push( src[ b + 1 ] ); 247 | dst.push( src[ b + 2 ] ); 248 | dst.push( src[ c + 0 ] ); 249 | dst.push( src[ c + 1 ] ); 250 | dst.push( src[ c + 2 ] ); 251 | 252 | }, 253 | 254 | addVertexLine: function ( a ) { 255 | 256 | var src = this.vertices; 257 | var dst = this.object.geometry.vertices; 258 | 259 | dst.push( src[ a + 0 ] ); 260 | dst.push( src[ a + 1 ] ); 261 | dst.push( src[ a + 2 ] ); 262 | 263 | }, 264 | 265 | addNormal : function ( a, b, c ) { 266 | 267 | var src = this.normals; 268 | var dst = this.object.geometry.normals; 269 | 270 | dst.push( src[ a + 0 ] ); 271 | dst.push( src[ a + 1 ] ); 272 | dst.push( src[ a + 2 ] ); 273 | dst.push( src[ b + 0 ] ); 274 | dst.push( src[ b + 1 ] ); 275 | dst.push( src[ b + 2 ] ); 276 | dst.push( src[ c + 0 ] ); 277 | dst.push( src[ c + 1 ] ); 278 | dst.push( src[ c + 2 ] ); 279 | 280 | }, 281 | 282 | addUV: function ( a, b, c ) { 283 | 284 | var src = this.uvs; 285 | var dst = this.object.geometry.uvs; 286 | 287 | dst.push( src[ a + 0 ] ); 288 | dst.push( src[ a + 1 ] ); 289 | dst.push( src[ b + 0 ] ); 290 | dst.push( src[ b + 1 ] ); 291 | dst.push( src[ c + 0 ] ); 292 | dst.push( src[ c + 1 ] ); 293 | 294 | }, 295 | 296 | addUVLine: function ( a ) { 297 | 298 | var src = this.uvs; 299 | var dst = this.object.geometry.uvs; 300 | 301 | dst.push( src[ a + 0 ] ); 302 | dst.push( src[ a + 1 ] ); 303 | 304 | }, 305 | 306 | addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 307 | 308 | var vLen = this.vertices.length; 309 | 310 | var ia = this.parseVertexIndex( a, vLen ); 311 | var ib = this.parseVertexIndex( b, vLen ); 312 | var ic = this.parseVertexIndex( c, vLen ); 313 | var id; 314 | 315 | if ( d === undefined ) { 316 | 317 | this.addVertex( ia, ib, ic ); 318 | 319 | } else { 320 | 321 | id = this.parseVertexIndex( d, vLen ); 322 | 323 | this.addVertex( ia, ib, id ); 324 | this.addVertex( ib, ic, id ); 325 | 326 | } 327 | 328 | if ( ua !== undefined ) { 329 | 330 | var uvLen = this.uvs.length; 331 | 332 | ia = this.parseUVIndex( ua, uvLen ); 333 | ib = this.parseUVIndex( ub, uvLen ); 334 | ic = this.parseUVIndex( uc, uvLen ); 335 | 336 | if ( d === undefined ) { 337 | 338 | this.addUV( ia, ib, ic ); 339 | 340 | } else { 341 | 342 | id = this.parseUVIndex( ud, uvLen ); 343 | 344 | this.addUV( ia, ib, id ); 345 | this.addUV( ib, ic, id ); 346 | 347 | } 348 | 349 | } 350 | 351 | if ( na !== undefined ) { 352 | 353 | // Normals are many times the same. If so, skip function call and parseInt. 354 | var nLen = this.normals.length; 355 | ia = this.parseNormalIndex( na, nLen ); 356 | 357 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 358 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 359 | 360 | if ( d === undefined ) { 361 | 362 | this.addNormal( ia, ib, ic ); 363 | 364 | } else { 365 | 366 | id = this.parseNormalIndex( nd, nLen ); 367 | 368 | this.addNormal( ia, ib, id ); 369 | this.addNormal( ib, ic, id ); 370 | 371 | } 372 | 373 | } 374 | 375 | }, 376 | 377 | addLineGeometry: function ( vertices, uvs ) { 378 | 379 | this.object.geometry.type = 'Line'; 380 | 381 | var vLen = this.vertices.length; 382 | var uvLen = this.uvs.length; 383 | 384 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 385 | 386 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); 387 | 388 | } 389 | 390 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { 391 | 392 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); 393 | 394 | } 395 | 396 | } 397 | 398 | }; 399 | 400 | state.startObject( '', false ); 401 | 402 | return state; 403 | 404 | }, 405 | 406 | parse: function ( text ) { 407 | 408 | console.time( 'OBJLoader' ); 409 | 410 | var state = this._createParserState(); 411 | 412 | if ( text.indexOf( '\r\n' ) !== - 1 ) { 413 | 414 | // This is faster than String.split with regex that splits on both 415 | text = text.replace( '\r\n', '\n' ); 416 | 417 | } 418 | 419 | var lines = text.split( '\n' ); 420 | var line = '', lineFirstChar = '', lineSecondChar = ''; 421 | var lineLength = 0; 422 | var result = []; 423 | 424 | // Faster to just trim left side of the line. Use if available. 425 | var trimLeft = ( typeof ''.trimLeft === 'function' ); 426 | 427 | for ( var i = 0, l = lines.length; i < l; i ++ ) { 428 | 429 | line = lines[ i ]; 430 | 431 | line = trimLeft ? line.trimLeft() : line.trim(); 432 | 433 | lineLength = line.length; 434 | 435 | if ( lineLength === 0 ) continue; 436 | 437 | lineFirstChar = line.charAt( 0 ); 438 | 439 | // @todo invoke passed in handler if any 440 | if ( lineFirstChar === '#' ) continue; 441 | 442 | if ( lineFirstChar === 'v' ) { 443 | 444 | lineSecondChar = line.charAt( 1 ); 445 | 446 | if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) { 447 | 448 | // 0 1 2 3 449 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 450 | 451 | state.vertices.push( 452 | parseFloat( result[ 1 ] ), 453 | parseFloat( result[ 2 ] ), 454 | parseFloat( result[ 3 ] ) 455 | ); 456 | 457 | } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) { 458 | 459 | // 0 1 2 3 460 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 461 | 462 | state.normals.push( 463 | parseFloat( result[ 1 ] ), 464 | parseFloat( result[ 2 ] ), 465 | parseFloat( result[ 3 ] ) 466 | ); 467 | 468 | } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) { 469 | 470 | // 0 1 2 471 | // ["vt 0.1 0.2", "0.1", "0.2"] 472 | 473 | state.uvs.push( 474 | parseFloat( result[ 1 ] ), 475 | parseFloat( result[ 2 ] ) 476 | ); 477 | 478 | } else { 479 | 480 | throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" ); 481 | 482 | } 483 | 484 | } else if ( lineFirstChar === "f" ) { 485 | 486 | if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) { 487 | 488 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 489 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 490 | // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined] 491 | 492 | state.addFace( 493 | result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ], 494 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 495 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 496 | ); 497 | 498 | } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) { 499 | 500 | // f vertex/uv vertex/uv vertex/uv 501 | // 0 1 2 3 4 5 6 7 8 502 | // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined] 503 | 504 | state.addFace( 505 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 506 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 507 | ); 508 | 509 | } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) { 510 | 511 | // f vertex//normal vertex//normal vertex//normal 512 | // 0 1 2 3 4 5 6 7 8 513 | // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined] 514 | 515 | state.addFace( 516 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 517 | undefined, undefined, undefined, undefined, 518 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 519 | ); 520 | 521 | } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) { 522 | 523 | // f vertex vertex vertex 524 | // 0 1 2 3 4 525 | // ["f 1 2 3", "1", "2", "3", undefined] 526 | 527 | state.addFace( 528 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 529 | ); 530 | 531 | } else { 532 | 533 | throw new Error( "Unexpected face line: '" + line + "'" ); 534 | 535 | } 536 | 537 | } else if ( lineFirstChar === "l" ) { 538 | 539 | var lineParts = line.substring( 1 ).trim().split( " " ); 540 | var lineVertices = [], lineUVs = []; 541 | 542 | if ( line.indexOf( "/" ) === - 1 ) { 543 | 544 | lineVertices = lineParts; 545 | 546 | } else { 547 | 548 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 549 | 550 | var parts = lineParts[ li ].split( "/" ); 551 | 552 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 553 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 554 | 555 | } 556 | 557 | } 558 | state.addLineGeometry( lineVertices, lineUVs ); 559 | 560 | } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) { 561 | 562 | // o object_name 563 | // or 564 | // g group_name 565 | 566 | var name = result[ 0 ].substr( 1 ).trim(); 567 | state.startObject( name ); 568 | 569 | } else if ( this.regexp.material_use_pattern.test( line ) ) { 570 | 571 | // material 572 | 573 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 574 | 575 | } else if ( this.regexp.material_library_pattern.test( line ) ) { 576 | 577 | // mtl file 578 | 579 | state.materialLibraries.push( line.substring( 7 ).trim() ); 580 | 581 | } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) { 582 | 583 | // smooth shading 584 | 585 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 586 | // but does not define a usemtl for each face set. 587 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 588 | // This requires some care to not create extra material on each smooth value for "normal" obj files. 589 | // where explicit usemtl defines geometry groups. 590 | // Example asset: examples/models/obj/cerberus/Cerberus.obj 591 | 592 | var value = result[ 1 ].trim().toLowerCase(); 593 | state.object.smooth = ( value === '1' || value === 'on' ); 594 | 595 | var material = state.object.currentMaterial(); 596 | if ( material ) { 597 | 598 | material.smooth = state.object.smooth; 599 | 600 | } 601 | 602 | } else { 603 | 604 | // Handle null terminated files without exception 605 | if ( line === '\0' ) continue; 606 | 607 | throw new Error( "Unexpected line: '" + line + "'" ); 608 | 609 | } 610 | 611 | } 612 | 613 | state.finalize(); 614 | 615 | var container = new THREE.Group(); 616 | container.materialLibraries = [].concat( state.materialLibraries ); 617 | 618 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 619 | 620 | var object = state.objects[ i ]; 621 | var geometry = object.geometry; 622 | var materials = object.materials; 623 | var isLine = ( geometry.type === 'Line' ); 624 | 625 | // Skip o/g line declarations that did not follow with any faces 626 | if ( geometry.vertices.length === 0 ) continue; 627 | 628 | var buffergeometry = new THREE.BufferGeometry(); 629 | 630 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 631 | 632 | if ( geometry.normals.length > 0 ) { 633 | 634 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 635 | 636 | } else { 637 | 638 | buffergeometry.computeVertexNormals(); 639 | 640 | } 641 | 642 | if ( geometry.uvs.length > 0 ) { 643 | 644 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 645 | 646 | } 647 | 648 | // Create materials 649 | 650 | var createdMaterials = []; 651 | 652 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 653 | 654 | var sourceMaterial = materials[mi]; 655 | var material = undefined; 656 | 657 | if ( this.materials !== null ) { 658 | 659 | material = this.materials.create( sourceMaterial.name ); 660 | 661 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 662 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { 663 | 664 | var materialLine = new THREE.LineBasicMaterial(); 665 | materialLine.copy( material ); 666 | material = materialLine; 667 | 668 | } 669 | 670 | } 671 | 672 | if ( ! material ) { 673 | 674 | material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); 675 | material.name = sourceMaterial.name; 676 | 677 | } 678 | 679 | material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading; 680 | 681 | createdMaterials.push(material); 682 | 683 | } 684 | 685 | // Create mesh 686 | 687 | var mesh; 688 | 689 | if ( createdMaterials.length > 1 ) { 690 | 691 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 692 | 693 | var sourceMaterial = materials[mi]; 694 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 695 | 696 | } 697 | 698 | var multiMaterial = new THREE.MultiMaterial( createdMaterials ); 699 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.Line( buffergeometry, multiMaterial ) ); 700 | 701 | } else { 702 | 703 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.Line( buffergeometry, createdMaterials[ 0 ] ) ); 704 | } 705 | 706 | mesh.name = object.name; 707 | 708 | container.add( mesh ); 709 | 710 | } 711 | 712 | console.timeEnd( 'OBJLoader' ); 713 | 714 | return container; 715 | 716 | } 717 | 718 | }; 719 | -------------------------------------------------------------------------------- /three/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finter swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | // 84 | // public methods 85 | // 86 | 87 | this.getPolarAngle = function () { 88 | 89 | return spherical.phi; 90 | 91 | }; 92 | 93 | this.getAzimuthalAngle = function () { 94 | 95 | return spherical.theta; 96 | 97 | }; 98 | 99 | this.reset = function () { 100 | 101 | scope.target.copy( scope.target0 ); 102 | scope.object.position.copy( scope.position0 ); 103 | scope.object.zoom = scope.zoom0; 104 | 105 | scope.object.updateProjectionMatrix(); 106 | scope.dispatchEvent( changeEvent ); 107 | 108 | scope.update(); 109 | 110 | state = STATE.NONE; 111 | 112 | }; 113 | 114 | // this method is exposed, but perhaps it would be better if we can make it private... 115 | this.update = function() { 116 | 117 | var offset = new THREE.Vector3(); 118 | 119 | // so camera.up is the orbit axis 120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 121 | var quatInverse = quat.clone().inverse(); 122 | 123 | var lastPosition = new THREE.Vector3(); 124 | var lastQuaternion = new THREE.Quaternion(); 125 | 126 | return function update () { 127 | 128 | var position = scope.object.position; 129 | 130 | offset.copy( position ).sub( scope.target ); 131 | 132 | // rotate offset to "y-axis-is-up" space 133 | offset.applyQuaternion( quat ); 134 | 135 | // angle from z-axis around y-axis 136 | spherical.setFromVector3( offset ); 137 | 138 | if ( scope.autoRotate && state === STATE.NONE ) { 139 | 140 | rotateLeft( getAutoRotationAngle() ); 141 | 142 | } 143 | 144 | spherical.theta += sphericalDelta.theta; 145 | spherical.phi += sphericalDelta.phi; 146 | 147 | // restrict theta to be between desired limits 148 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 149 | 150 | // restrict phi to be between desired limits 151 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 152 | 153 | spherical.makeSafe(); 154 | 155 | 156 | spherical.radius *= scale; 157 | 158 | // restrict radius to be between desired limits 159 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 160 | 161 | // move target to panned location 162 | scope.target.add( panOffset ); 163 | 164 | offset.setFromSpherical( spherical ); 165 | 166 | // rotate offset back to "camera-up-vector-is-up" space 167 | offset.applyQuaternion( quatInverse ); 168 | 169 | position.copy( scope.target ).add( offset ); 170 | 171 | scope.object.lookAt( scope.target ); 172 | 173 | if ( scope.enableDamping === true ) { 174 | 175 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 176 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 177 | 178 | } else { 179 | 180 | sphericalDelta.set( 0, 0, 0 ); 181 | 182 | } 183 | 184 | scale = 1; 185 | panOffset.set( 0, 0, 0 ); 186 | 187 | // update condition is: 188 | // min(camera displacement, camera rotation in radians)^2 > EPS 189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 190 | 191 | if ( zoomChanged || 192 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 194 | 195 | scope.dispatchEvent( changeEvent ); 196 | 197 | lastPosition.copy( scope.object.position ); 198 | lastQuaternion.copy( scope.object.quaternion ); 199 | zoomChanged = false; 200 | 201 | return true; 202 | 203 | } 204 | 205 | return false; 206 | 207 | }; 208 | 209 | }(); 210 | 211 | this.dispose = function() { 212 | 213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 215 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 216 | 217 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 218 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 219 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 220 | 221 | document.removeEventListener( 'mousemove', onMouseMove, false ); 222 | document.removeEventListener( 'mouseup', onMouseUp, false ); 223 | 224 | window.removeEventListener( 'keydown', onKeyDown, false ); 225 | 226 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 227 | 228 | }; 229 | 230 | // 231 | // internals 232 | // 233 | 234 | var scope = this; 235 | 236 | var changeEvent = { type: 'change' }; 237 | var startEvent = { type: 'start' }; 238 | var endEvent = { type: 'end' }; 239 | 240 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 241 | 242 | var state = STATE.NONE; 243 | 244 | var EPS = 0.000001; 245 | 246 | // current position in spherical coordinates 247 | var spherical = new THREE.Spherical(); 248 | var sphericalDelta = new THREE.Spherical(); 249 | 250 | var scale = 1; 251 | var panOffset = new THREE.Vector3(); 252 | var zoomChanged = false; 253 | 254 | var rotateStart = new THREE.Vector2(); 255 | var rotateEnd = new THREE.Vector2(); 256 | var rotateDelta = new THREE.Vector2(); 257 | 258 | var panStart = new THREE.Vector2(); 259 | var panEnd = new THREE.Vector2(); 260 | var panDelta = new THREE.Vector2(); 261 | 262 | var dollyStart = new THREE.Vector2(); 263 | var dollyEnd = new THREE.Vector2(); 264 | var dollyDelta = new THREE.Vector2(); 265 | 266 | function getAutoRotationAngle() { 267 | 268 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 269 | 270 | } 271 | 272 | function getZoomScale() { 273 | 274 | return Math.pow( 0.95, scope.zoomSpeed ); 275 | 276 | } 277 | 278 | function rotateLeft( angle ) { 279 | 280 | sphericalDelta.theta -= angle; 281 | 282 | } 283 | 284 | function rotateUp( angle ) { 285 | 286 | sphericalDelta.phi -= angle; 287 | 288 | } 289 | 290 | var panLeft = function() { 291 | 292 | var v = new THREE.Vector3(); 293 | 294 | return function panLeft( distance, objectMatrix ) { 295 | 296 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 297 | v.multiplyScalar( - distance ); 298 | 299 | panOffset.add( v ); 300 | 301 | }; 302 | 303 | }(); 304 | 305 | var panUp = function() { 306 | 307 | var v = new THREE.Vector3(); 308 | 309 | return function panUp( distance, objectMatrix ) { 310 | 311 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 312 | v.multiplyScalar( distance ); 313 | 314 | panOffset.add( v ); 315 | 316 | }; 317 | 318 | }(); 319 | 320 | // deltaX and deltaY are in pixels; right and down are positive 321 | var pan = function() { 322 | 323 | var offset = new THREE.Vector3(); 324 | 325 | return function pan ( deltaX, deltaY ) { 326 | 327 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 328 | 329 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 330 | 331 | // perspective 332 | var position = scope.object.position; 333 | offset.copy( position ).sub( scope.target ); 334 | var targetDistance = offset.length(); 335 | 336 | // half of the fov is center to top of screen 337 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 338 | 339 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 340 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 341 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 342 | 343 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 344 | 345 | // orthographic 346 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 347 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 348 | 349 | } else { 350 | 351 | // camera neither orthographic nor perspective 352 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 353 | scope.enablePan = false; 354 | 355 | } 356 | 357 | }; 358 | 359 | }(); 360 | 361 | function dollyIn( dollyScale ) { 362 | 363 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 364 | 365 | scale /= dollyScale; 366 | 367 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 368 | 369 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 370 | scope.object.updateProjectionMatrix(); 371 | zoomChanged = true; 372 | 373 | } else { 374 | 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 376 | scope.enableZoom = false; 377 | 378 | } 379 | 380 | } 381 | 382 | function dollyOut( dollyScale ) { 383 | 384 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 385 | 386 | scale *= dollyScale; 387 | 388 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 389 | 390 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 391 | scope.object.updateProjectionMatrix(); 392 | zoomChanged = true; 393 | 394 | } else { 395 | 396 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 397 | scope.enableZoom = false; 398 | 399 | } 400 | 401 | } 402 | 403 | // 404 | // event callbacks - update the object state 405 | // 406 | 407 | function handleMouseDownRotate( event ) { 408 | 409 | //console.log( 'handleMouseDownRotate' ); 410 | 411 | rotateStart.set( event.clientX, event.clientY ); 412 | 413 | } 414 | 415 | function handleMouseDownDolly( event ) { 416 | 417 | //console.log( 'handleMouseDownDolly' ); 418 | 419 | dollyStart.set( event.clientX, event.clientY ); 420 | 421 | } 422 | 423 | function handleMouseDownPan( event ) { 424 | 425 | //console.log( 'handleMouseDownPan' ); 426 | 427 | panStart.set( event.clientX, event.clientY ); 428 | 429 | } 430 | 431 | function handleMouseMoveRotate( event ) { 432 | 433 | //console.log( 'handleMouseMoveRotate' ); 434 | 435 | rotateEnd.set( event.clientX, event.clientY ); 436 | rotateDelta.subVectors( rotateEnd, rotateStart ); 437 | 438 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 439 | 440 | // rotating across whole screen goes 360 degrees around 441 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 442 | 443 | // rotating up and down along whole screen attempts to go 360, but limited to 180 444 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 445 | 446 | rotateStart.copy( rotateEnd ); 447 | 448 | scope.update(); 449 | 450 | } 451 | 452 | function handleMouseMoveDolly( event ) { 453 | 454 | //console.log( 'handleMouseMoveDolly' ); 455 | 456 | dollyEnd.set( event.clientX, event.clientY ); 457 | 458 | dollyDelta.subVectors( dollyEnd, dollyStart ); 459 | 460 | if ( dollyDelta.y > 0 ) { 461 | 462 | dollyIn( getZoomScale() ); 463 | 464 | } else if ( dollyDelta.y < 0 ) { 465 | 466 | dollyOut( getZoomScale() ); 467 | 468 | } 469 | 470 | dollyStart.copy( dollyEnd ); 471 | 472 | scope.update(); 473 | 474 | } 475 | 476 | function handleMouseMovePan( event ) { 477 | 478 | //console.log( 'handleMouseMovePan' ); 479 | 480 | panEnd.set( event.clientX, event.clientY ); 481 | 482 | panDelta.subVectors( panEnd, panStart ); 483 | 484 | pan( panDelta.x, panDelta.y ); 485 | 486 | panStart.copy( panEnd ); 487 | 488 | scope.update(); 489 | 490 | } 491 | 492 | function handleMouseUp( event ) { 493 | 494 | //console.log( 'handleMouseUp' ); 495 | 496 | } 497 | 498 | function handleMouseWheel( event ) { 499 | 500 | //console.log( 'handleMouseWheel' ); 501 | 502 | if ( event.deltaY < 0 ) { 503 | 504 | dollyOut( getZoomScale() ); 505 | 506 | } else if ( event.deltaY > 0 ) { 507 | 508 | dollyIn( getZoomScale() ); 509 | 510 | } 511 | 512 | scope.update(); 513 | 514 | } 515 | 516 | function handleKeyDown( event ) { 517 | 518 | //console.log( 'handleKeyDown' ); 519 | 520 | switch ( event.keyCode ) { 521 | 522 | case scope.keys.UP: 523 | pan( 0, scope.keyPanSpeed ); 524 | scope.update(); 525 | break; 526 | 527 | case scope.keys.BOTTOM: 528 | pan( 0, - scope.keyPanSpeed ); 529 | scope.update(); 530 | break; 531 | 532 | case scope.keys.LEFT: 533 | pan( scope.keyPanSpeed, 0 ); 534 | scope.update(); 535 | break; 536 | 537 | case scope.keys.RIGHT: 538 | pan( - scope.keyPanSpeed, 0 ); 539 | scope.update(); 540 | break; 541 | 542 | } 543 | 544 | } 545 | 546 | function handleTouchStartRotate( event ) { 547 | 548 | //console.log( 'handleTouchStartRotate' ); 549 | 550 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 551 | 552 | } 553 | 554 | function handleTouchStartDolly( event ) { 555 | 556 | //console.log( 'handleTouchStartDolly' ); 557 | 558 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 559 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 560 | 561 | var distance = Math.sqrt( dx * dx + dy * dy ); 562 | 563 | dollyStart.set( 0, distance ); 564 | 565 | } 566 | 567 | function handleTouchStartPan( event ) { 568 | 569 | //console.log( 'handleTouchStartPan' ); 570 | 571 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 572 | 573 | } 574 | 575 | function handleTouchMoveRotate( event ) { 576 | 577 | //console.log( 'handleTouchMoveRotate' ); 578 | 579 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 580 | rotateDelta.subVectors( rotateEnd, rotateStart ); 581 | 582 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 583 | 584 | // rotating across whole screen goes 360 degrees around 585 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 586 | 587 | // rotating up and down along whole screen attempts to go 360, but limited to 180 588 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 589 | 590 | rotateStart.copy( rotateEnd ); 591 | 592 | scope.update(); 593 | 594 | } 595 | 596 | function handleTouchMoveDolly( event ) { 597 | 598 | //console.log( 'handleTouchMoveDolly' ); 599 | 600 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 601 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 602 | 603 | var distance = Math.sqrt( dx * dx + dy * dy ); 604 | 605 | dollyEnd.set( 0, distance ); 606 | 607 | dollyDelta.subVectors( dollyEnd, dollyStart ); 608 | 609 | if ( dollyDelta.y > 0 ) { 610 | 611 | dollyOut( getZoomScale() ); 612 | 613 | } else if ( dollyDelta.y < 0 ) { 614 | 615 | dollyIn( getZoomScale() ); 616 | 617 | } 618 | 619 | dollyStart.copy( dollyEnd ); 620 | 621 | scope.update(); 622 | 623 | } 624 | 625 | function handleTouchMovePan( event ) { 626 | 627 | //console.log( 'handleTouchMovePan' ); 628 | 629 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 630 | 631 | panDelta.subVectors( panEnd, panStart ); 632 | 633 | pan( panDelta.x, panDelta.y ); 634 | 635 | panStart.copy( panEnd ); 636 | 637 | scope.update(); 638 | 639 | } 640 | 641 | function handleTouchEnd( event ) { 642 | 643 | //console.log( 'handleTouchEnd' ); 644 | 645 | } 646 | 647 | // 648 | // event handlers - FSM: listen for events and reset state 649 | // 650 | 651 | function onMouseDown( event ) { 652 | 653 | if ( scope.enabled === false ) return; 654 | 655 | event.preventDefault(); 656 | 657 | if ( event.button === scope.mouseButtons.ORBIT ) { 658 | 659 | if ( scope.enableRotate === false ) return; 660 | 661 | handleMouseDownRotate( event ); 662 | 663 | state = STATE.ROTATE; 664 | 665 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 666 | 667 | if ( scope.enableZoom === false ) return; 668 | 669 | handleMouseDownDolly( event ); 670 | 671 | state = STATE.DOLLY; 672 | 673 | } else if ( event.button === scope.mouseButtons.PAN ) { 674 | 675 | if ( scope.enablePan === false ) return; 676 | 677 | handleMouseDownPan( event ); 678 | 679 | state = STATE.PAN; 680 | 681 | } 682 | 683 | if ( state !== STATE.NONE ) { 684 | 685 | document.addEventListener( 'mousemove', onMouseMove, false ); 686 | document.addEventListener( 'mouseup', onMouseUp, false ); 687 | 688 | scope.dispatchEvent( startEvent ); 689 | 690 | } 691 | 692 | } 693 | 694 | function onMouseMove( event ) { 695 | 696 | if ( scope.enabled === false ) return; 697 | 698 | event.preventDefault(); 699 | 700 | if ( state === STATE.ROTATE ) { 701 | 702 | if ( scope.enableRotate === false ) return; 703 | 704 | handleMouseMoveRotate( event ); 705 | 706 | } else if ( state === STATE.DOLLY ) { 707 | 708 | if ( scope.enableZoom === false ) return; 709 | 710 | handleMouseMoveDolly( event ); 711 | 712 | } else if ( state === STATE.PAN ) { 713 | 714 | if ( scope.enablePan === false ) return; 715 | 716 | handleMouseMovePan( event ); 717 | 718 | } 719 | 720 | } 721 | 722 | function onMouseUp( event ) { 723 | 724 | if ( scope.enabled === false ) return; 725 | 726 | handleMouseUp( event ); 727 | 728 | document.removeEventListener( 'mousemove', onMouseMove, false ); 729 | document.removeEventListener( 'mouseup', onMouseUp, false ); 730 | 731 | scope.dispatchEvent( endEvent ); 732 | 733 | state = STATE.NONE; 734 | 735 | } 736 | 737 | function onMouseWheel( event ) { 738 | 739 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 740 | 741 | event.preventDefault(); 742 | event.stopPropagation(); 743 | 744 | handleMouseWheel( event ); 745 | 746 | scope.dispatchEvent( startEvent ); // not sure why these are here... 747 | scope.dispatchEvent( endEvent ); 748 | 749 | } 750 | 751 | function onKeyDown( event ) { 752 | 753 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 754 | 755 | handleKeyDown( event ); 756 | 757 | } 758 | 759 | function onTouchStart( event ) { 760 | 761 | if ( scope.enabled === false ) return; 762 | 763 | switch ( event.touches.length ) { 764 | 765 | case 1: // one-fingered touch: rotate 766 | 767 | if ( scope.enableRotate === false ) return; 768 | 769 | handleTouchStartRotate( event ); 770 | 771 | state = STATE.TOUCH_ROTATE; 772 | 773 | break; 774 | 775 | case 2: // two-fingered touch: dolly 776 | 777 | if ( scope.enableZoom === false ) return; 778 | 779 | handleTouchStartDolly( event ); 780 | 781 | state = STATE.TOUCH_DOLLY; 782 | 783 | break; 784 | 785 | case 3: // three-fingered touch: pan 786 | 787 | if ( scope.enablePan === false ) return; 788 | 789 | handleTouchStartPan( event ); 790 | 791 | state = STATE.TOUCH_PAN; 792 | 793 | break; 794 | 795 | default: 796 | 797 | state = STATE.NONE; 798 | 799 | } 800 | 801 | if ( state !== STATE.NONE ) { 802 | 803 | scope.dispatchEvent( startEvent ); 804 | 805 | } 806 | 807 | } 808 | 809 | function onTouchMove( event ) { 810 | 811 | if ( scope.enabled === false ) return; 812 | 813 | event.preventDefault(); 814 | event.stopPropagation(); 815 | 816 | switch ( event.touches.length ) { 817 | 818 | case 1: // one-fingered touch: rotate 819 | 820 | if ( scope.enableRotate === false ) return; 821 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 822 | 823 | handleTouchMoveRotate( event ); 824 | 825 | break; 826 | 827 | case 2: // two-fingered touch: dolly 828 | 829 | if ( scope.enableZoom === false ) return; 830 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 831 | 832 | handleTouchMoveDolly( event ); 833 | 834 | break; 835 | 836 | case 3: // three-fingered touch: pan 837 | 838 | if ( scope.enablePan === false ) return; 839 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 840 | 841 | handleTouchMovePan( event ); 842 | 843 | break; 844 | 845 | default: 846 | 847 | state = STATE.NONE; 848 | 849 | } 850 | 851 | } 852 | 853 | function onTouchEnd( event ) { 854 | 855 | if ( scope.enabled === false ) return; 856 | 857 | handleTouchEnd( event ); 858 | 859 | scope.dispatchEvent( endEvent ); 860 | 861 | state = STATE.NONE; 862 | 863 | } 864 | 865 | function onContextMenu( event ) { 866 | 867 | event.preventDefault(); 868 | 869 | } 870 | 871 | // 872 | 873 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 874 | 875 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 876 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 877 | 878 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 879 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 880 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 881 | 882 | window.addEventListener( 'keydown', onKeyDown, false ); 883 | 884 | // force an update at start 885 | 886 | this.update(); 887 | 888 | }; 889 | 890 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 891 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 892 | 893 | Object.defineProperties( THREE.OrbitControls.prototype, { 894 | 895 | center: { 896 | 897 | get: function () { 898 | 899 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 900 | return this.target; 901 | 902 | } 903 | 904 | }, 905 | 906 | // backward compatibility 907 | 908 | noZoom: { 909 | 910 | get: function () { 911 | 912 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 913 | return ! this.enableZoom; 914 | 915 | }, 916 | 917 | set: function ( value ) { 918 | 919 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 920 | this.enableZoom = ! value; 921 | 922 | } 923 | 924 | }, 925 | 926 | noRotate: { 927 | 928 | get: function () { 929 | 930 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 931 | return ! this.enableRotate; 932 | 933 | }, 934 | 935 | set: function ( value ) { 936 | 937 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 938 | this.enableRotate = ! value; 939 | 940 | } 941 | 942 | }, 943 | 944 | noPan: { 945 | 946 | get: function () { 947 | 948 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 949 | return ! this.enablePan; 950 | 951 | }, 952 | 953 | set: function ( value ) { 954 | 955 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 956 | this.enablePan = ! value; 957 | 958 | } 959 | 960 | }, 961 | 962 | noKeys: { 963 | 964 | get: function () { 965 | 966 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 967 | return ! this.enableKeys; 968 | 969 | }, 970 | 971 | set: function ( value ) { 972 | 973 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 974 | this.enableKeys = ! value; 975 | 976 | } 977 | 978 | }, 979 | 980 | staticMoving : { 981 | 982 | get: function () { 983 | 984 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 985 | return ! this.enableDamping; 986 | 987 | }, 988 | 989 | set: function ( value ) { 990 | 991 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 992 | this.enableDamping = ! value; 993 | 994 | } 995 | 996 | }, 997 | 998 | dynamicDampingFactor : { 999 | 1000 | get: function () { 1001 | 1002 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1003 | return this.dampingFactor; 1004 | 1005 | }, 1006 | 1007 | set: function ( value ) { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1010 | this.dampingFactor = value; 1011 | 1012 | } 1013 | 1014 | } 1015 | 1016 | } ); 1017 | --------------------------------------------------------------------------------