├── .gitignore ├── .gitmodules ├── examples ├── ex3-terrain │ ├── assets │ │ └── sky1.jpg │ ├── models │ │ ├── tree.mtl │ │ └── tree.obj │ ├── index.html │ └── main.js ├── ex1-webvr-sphere │ ├── shaders │ │ ├── default-fragment.glsl │ │ └── default-vertex.glsl │ ├── index.html │ └── main.js ├── ex4-world │ ├── index.html │ └── main.js ├── ex2-import-model │ ├── index.html │ └── main.js ├── ex5-animation │ ├── index.html │ ├── animation.css │ └── crow.svg └── ex6-aframe │ └── index.html ├── assets └── default.css ├── models ├── tree2.mtl ├── tree1.obj └── tree2.obj ├── bower.json ├── README.md └── lib ├── PointerLockControls.js ├── VRControls.js ├── StereoEffect.js ├── DDSLoader.js ├── FirstPersonControls.js ├── FlyControls.js ├── transformSVGPath.js ├── OBJLoader.js ├── OBJMTLLoader.js ├── MTLLoader.js ├── DeviceOrientationController.js ├── TrackballControls.js └── OrbitControls.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ShaderLoader"] 2 | path = lib/ShaderLoader 3 | url = git@github.com:donmccurdy/ShaderLoader.git 4 | -------------------------------------------------------------------------------- /examples/ex3-terrain/assets/sky1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/sandbox-webgl/master/examples/ex3-terrain/assets/sky1.jpg -------------------------------------------------------------------------------- /assets/default.css: -------------------------------------------------------------------------------- 1 | /* Page styles 2 | ********************************/ 3 | 4 | body { 5 | margin: 0; 6 | overflow: hidden; 7 | background: #000; 8 | } 9 | 10 | #container { 11 | width: 100%; 12 | height: 100%; 13 | } 14 | -------------------------------------------------------------------------------- /models/tree2.mtl: -------------------------------------------------------------------------------- 1 | newmtl phong2SG 2 | illum 4 3 | Kd 0.41 0.72 0.42 4 | Ka 0.00 0.00 0.00 5 | Tf 1.00 1.00 1.00 6 | Ni 1.00 7 | Ks 0.50 0.50 0.50 8 | Ns 18.00 9 | newmtl phong3SG 10 | illum 4 11 | Kd 0.66 0.45 0.27 12 | Ka 0.00 0.00 0.00 13 | Tf 1.00 1.00 1.00 14 | Ni 1.00 15 | Ks 0.50 0.50 0.50 16 | Ns 18.00 17 | -------------------------------------------------------------------------------- /examples/ex3-terrain/models/tree.mtl: -------------------------------------------------------------------------------- 1 | newmtl phong2SG 2 | illum 4 3 | Kd 0.41 0.72 0.42 4 | Ka 0.00 0.00 0.00 5 | Tf 1.00 1.00 1.00 6 | Ni 1.00 7 | Ks 0.00 0.00 0.00 8 | Ns 18.00 9 | newmtl phong3SG 10 | illum 4 11 | Kd 0.66 0.45 0.27 12 | Ka 0.00 0.00 0.00 13 | Tf 1.00 1.00 1.00 14 | Ni 1.00 15 | Ks 0.00 0.00 0.00 16 | Ns 18.00 17 | -------------------------------------------------------------------------------- /examples/ex1-webvr-sphere/shaders/default-fragment.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | varying vec3 vNormal; 6 | 7 | void main() 8 | { 9 | vec3 light = vec3(0.5, 0.2, 1.0); 10 | light = normalize(light); 11 | float dProd = max(0.0, dot(vNormal, light)); 12 | 13 | gl_FragColor = vec4(dProd, dProd, dProd, 1.0); 14 | } -------------------------------------------------------------------------------- /examples/ex1-webvr-sphere/shaders/default-vertex.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision highp float; 3 | #endif 4 | 5 | uniform float amplitude; 6 | attribute float displacement; 7 | varying vec3 vNormal; 8 | 9 | void main() 10 | { 11 | vNormal = normal; 12 | vec3 newPosition = position + normal * vec3(displacement * amplitude); 13 | gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0); 14 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl-demo-1", 3 | "version": "0.0.0", 4 | "authors": [ 5 | "Don McCurdy " 6 | ], 7 | "license": "MIT", 8 | "private": true, 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "threejs": "*", 18 | "fetch": "~0.7.0", 19 | "stats.js": "*", 20 | "THREE.Terrain": "~1.2.0", 21 | "three.map-controls": "~0.5.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGL Sandbox 2 | --- 3 | 4 | Various experiments in WebGL. A lot of the code is cobbled together from various tutorials and demos. 5 | 6 | ## Installation 7 | 8 | Install Bower dependencies: 9 | 10 | ```bash 11 | bower install 12 | ``` 13 | 14 | Install live-reload server, and run in root directory: 15 | 16 | ```bash 17 | npm install -g live-server 18 | live-server . 19 | ``` 20 | 21 | Your browser should open automatically to http://localhost:8080. 22 | From there you can navigate to any of the examples. 23 | 24 | ![example 3](https://dl.dropboxusercontent.com/u/42869844/LTS/webgl-sandbox-1.png) 25 | -------------------------------------------------------------------------------- /examples/ex4-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGL Demo 4 - World Map 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/ex1-webvr-sphere/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGL Demo 1 - Shaded Sphere 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/ex2-import-model/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGL Demo 2 - Model Import 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/ex5-animation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGL Demo 4 - CSS3 Animation 5 | 6 | 7 | Crow Animation 8 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/ex6-aframe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello, World! • A-Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 22 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/ex5-animation/animation.css: -------------------------------------------------------------------------------- 1 | 2 | /* Head 3 | ******************************************/ 4 | 5 | #head { 6 | animation: nod 10s infinite; 7 | } 8 | 9 | @keyframes nod { 10 | 0% { transform: translateY(0px); } 11 | 30% { transform: translateY(12px); } 12 | 40% { transform: translateY(0px); } 13 | 50% { transform: translateY(0px); } 14 | 70% { transform: translateY(24px); } 15 | 85% { transform: translateY(0px); } 16 | 100% { transform: translateY(0px); } 17 | } 18 | 19 | /* Wing Front 20 | ******************************************/ 21 | 22 | #wing-front { 23 | animation: wing-front-fidget 8s infinite; 24 | } 25 | 26 | @keyframes wing-front-fidget { 27 | 0% { transform: translateY(0px); } 28 | 50% { transform: translateY(10px); } 29 | 65% { transform: translateY(12px); } 30 | 66% { transform: translateY(10px); } 31 | 67% { transform: translateY(11px); } 32 | 68% { transform: translateY(12px); } 33 | 69% { transform: translateY(11px); } 34 | 100% { transform: translateY(0px); } 35 | } 36 | 37 | /* Wing Back 38 | ******************************************/ 39 | 40 | #wing-back { 41 | animation: wing-back-fidget 5s infinite; 42 | } 43 | 44 | @keyframes wing-back-fidget { 45 | 0% { transform: translateY(0px); } 46 | 50% { transform: translateY(10px); } 47 | 65% { transform: translateY(12px); } 48 | 66% { transform: translateY(10px); } 49 | 67% { transform: translateY(11px); } 50 | 68% { transform: translateY(12px); } 51 | 69% { transform: translateY(11px); } 52 | 100% { transform: translateY(0px); } 53 | } 54 | -------------------------------------------------------------------------------- /lib/PointerLockControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.PointerLockControls = function ( camera ) { 6 | 7 | var scope = this; 8 | 9 | camera.rotation.set( 0, 0, 0 ); 10 | 11 | var pitchObject = new THREE.Object3D(); 12 | pitchObject.add( camera ); 13 | 14 | var yawObject = new THREE.Object3D(); 15 | yawObject.position.y = 10; 16 | yawObject.add( pitchObject ); 17 | 18 | var PI_2 = Math.PI / 2; 19 | 20 | var onMouseMove = function ( event ) { 21 | 22 | if ( scope.enabled === false ) return; 23 | 24 | var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; 25 | var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; 26 | 27 | yawObject.rotation.y -= movementX * 0.002; 28 | pitchObject.rotation.x -= movementY * 0.002; 29 | 30 | pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) ); 31 | 32 | }; 33 | 34 | document.addEventListener( 'mousemove', onMouseMove, false ); 35 | 36 | this.enabled = false; 37 | 38 | this.getObject = function () { 39 | 40 | return yawObject; 41 | 42 | }; 43 | 44 | this.getDirection = function() { 45 | 46 | // assumes the camera itself is not rotated 47 | 48 | var direction = new THREE.Vector3( 0, 0, -1 ); 49 | var rotation = new THREE.Euler( 0, 0, 0, "YXZ" ); 50 | 51 | return function( v ) { 52 | 53 | rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 ); 54 | 55 | v.copy( direction ).applyEuler( rotation ); 56 | 57 | return v; 58 | 59 | } 60 | 61 | }(); 62 | 63 | }; 64 | -------------------------------------------------------------------------------- /lib/VRControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | */ 5 | 6 | THREE.VRControls = function ( object, callback ) { 7 | 8 | var scope = this; 9 | 10 | var vrInput; 11 | 12 | var onVRDevices = function ( devices ) { 13 | 14 | for ( var i = 0; i < devices.length; i ++ ) { 15 | 16 | var device = devices[ i ]; 17 | 18 | if ( device instanceof PositionSensorVRDevice ) { 19 | 20 | vrInput = devices[ i ]; 21 | return; // We keep the first we encounter 22 | 23 | } 24 | 25 | } 26 | 27 | if ( callback !== undefined ) { 28 | 29 | callback( 'HMD not available' ); 30 | 31 | } 32 | 33 | }; 34 | 35 | if ( navigator.getVRDevices !== undefined ) { 36 | 37 | navigator.getVRDevices().then( onVRDevices ); 38 | 39 | } else if ( callback !== undefined ) { 40 | 41 | callback( 'Your browser is not VR Ready' ); 42 | 43 | } 44 | 45 | // the Rift SDK returns the position in meters 46 | // this scale factor allows the user to define how meters 47 | // are converted to scene units. 48 | this.scale = 1; 49 | 50 | this.update = function () { 51 | 52 | if ( vrInput === undefined ) return; 53 | 54 | var state = vrInput.getState(); 55 | 56 | if ( state.orientation !== null ) { 57 | 58 | object.quaternion.copy( state.orientation ); 59 | 60 | } 61 | 62 | if ( state.position !== null ) { 63 | 64 | object.position.copy( state.position ).multiplyScalar( scope.scale ); 65 | 66 | } 67 | 68 | }; 69 | 70 | this.zeroSensor = function () { 71 | 72 | if ( vrInput === undefined ) return; 73 | 74 | vrInput.zeroSensor(); 75 | 76 | }; 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /examples/ex3-terrain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGL Demo 3 - Terrain 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 72 | 73 | 74 | 75 |
76 | 77 |
78 | Click to play 79 |
80 | (W, A, S, D = Move, SPACE = Jump, MOUSE = Look around) 81 |
82 | 83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /examples/ex2-import-model/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebGL Demo 3 | * 4 | * Based on: http://aerotwist.com/tutorials/getting-started-with-three-js/ 5 | */ 6 | 7 | (function (THREE, Stats) { 8 | 9 | /* Scene 10 | ********************************/ 11 | 12 | var WIDTH = window.innerWidth, 13 | HEIGHT = window.innerHeight; 14 | 15 | var VIEW_ANGLE = 45, 16 | ASPECT = WIDTH / HEIGHT, 17 | NEAR = 0.1, 18 | FAR = 10000; 19 | 20 | var container = document.querySelector('#container'); 21 | 22 | var renderer = new THREE.WebGLRenderer(), 23 | camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR); 24 | 25 | var scene = new THREE.Scene(); 26 | 27 | scene.add(camera); 28 | 29 | camera.position.z = 25; 30 | 31 | renderer.setSize(WIDTH, HEIGHT); 32 | 33 | container.appendChild(renderer.domElement); 34 | 35 | /* Resources 36 | ********************************/ 37 | 38 | var manager = new THREE.LoadingManager(); 39 | manager.onProgress = function (item, loaded, total) { 40 | console.log(item, loaded, total); 41 | }; 42 | 43 | var loader = new THREE.OBJMTLLoader(manager); 44 | 45 | /* Stats 46 | ********************************/ 47 | 48 | var stats = new Stats(); 49 | stats.setMode(0); // 0: fps, 1: ms 50 | 51 | // align top-left 52 | stats.domElement.style.position = 'absolute'; 53 | stats.domElement.style.left = '0px'; 54 | stats.domElement.style.top = '0px'; 55 | 56 | document.body.appendChild(stats.domElement); 57 | 58 | /* Responsive layout 59 | ********************************/ 60 | 61 | window.addEventListener('resize', function () { 62 | WIDTH = window.innerWidth; 63 | HEIGHT = window.innerHeight; 64 | ASPECT = WIDTH / HEIGHT; 65 | 66 | camera.aspect = ASPECT; 67 | camera.updateProjectionMatrix(); 68 | renderer.setSize(WIDTH, HEIGHT); 69 | }, false); 70 | 71 | /* Model 72 | ********************************/ 73 | 74 | loader.load( 75 | '../../models/tree2.obj', 76 | '../../models/tree2.mtl', 77 | function (object) { 78 | object.position.y = -2.5; 79 | scene.add(object); 80 | } 81 | ); 82 | 83 | /* View controller 84 | ********************************/ 85 | 86 | var controls = new THREE.OrbitControls(camera, renderer.domElement); 87 | 88 | /* Lights 89 | ********************************/ 90 | 91 | var pointLight1 = new THREE.PointLight(0xFFFFFF, 0.5), 92 | pointLight2 = new THREE.PointLight(0xFFFFFF, 0.8); 93 | 94 | pointLight1.position.x = 10; 95 | pointLight1.position.y = 50; 96 | pointLight1.position.z = 130; 97 | 98 | pointLight2.position.y = 130; 99 | 100 | scene.add(pointLight1); 101 | scene.add(pointLight2); 102 | 103 | /* Animation loop 104 | ********************************/ 105 | 106 | function update () { 107 | stats.begin(); 108 | controls.update(); 109 | renderer.render(scene, camera); 110 | stats.end(); 111 | window.requestAnimationFrame(update); 112 | } 113 | window.requestAnimationFrame(update); 114 | 115 | /* Debugging exports 116 | ********************************/ 117 | 118 | }(window.THREE, window.Stats)); 119 | -------------------------------------------------------------------------------- /examples/ex5-animation/crow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/ex1-webvr-sphere/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebGL Demo 3 | * 4 | * Based on: http://aerotwist.com/tutorials/getting-started-with-three-js/ 5 | */ 6 | 7 | (function (THREE, ShaderLoader, DeviceOrientationController) { 8 | 9 | /* Scene 10 | ********************************/ 11 | 12 | var WIDTH = window.innerWidth, 13 | HEIGHT = window.innerHeight; 14 | 15 | var VIEW_ANGLE = 45, 16 | ASPECT = WIDTH / HEIGHT, 17 | NEAR = 0.1, 18 | FAR = 10000; 19 | 20 | var container = document.querySelector('#container'); 21 | 22 | var renderer = new THREE.WebGLRenderer(), 23 | camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR), 24 | effect = new THREE.StereoEffect(renderer); 25 | 26 | effect.setSize(WIDTH, HEIGHT); 27 | effect.eyeSeparation = 2; // it burns! 28 | 29 | var scene = new THREE.Scene(); 30 | 31 | scene.add(camera); 32 | 33 | camera.position.z = 300; 34 | 35 | renderer.setSize(WIDTH, HEIGHT); 36 | 37 | container.appendChild(renderer.domElement); 38 | 39 | /* Responsive layout 40 | ********************************/ 41 | 42 | window.addEventListener('resize', function () { 43 | WIDTH = window.innerWidth; 44 | HEIGHT = window.innerHeight; 45 | ASPECT = WIDTH / HEIGHT; 46 | 47 | camera.aspect = ASPECT; 48 | camera.updateProjectionMatrix(); 49 | renderer.setSize(WIDTH, HEIGHT); 50 | effect.setSize(WIDTH, HEIGHT); 51 | }, false); 52 | 53 | /* Load shaders 54 | ********************************/ 55 | 56 | var shaders = new ShaderLoader('shaders', 'shaderChunks'); 57 | shaders.shaderSetLoaded = init; 58 | shaders.load('default-vertex', 'VERT', 'vertex'); 59 | shaders.load('default-fragment', 'FRAG', 'fragment'); 60 | 61 | function init () { 62 | 63 | /* Material 64 | ********************************/ 65 | 66 | var uniforms = { amplitude: { type: 'f', value: 0 } }, 67 | attributes = { displacement: {type: 'f', value: []} }, 68 | shaderMaterial = new THREE.ShaderMaterial({ 69 | uniforms: uniforms, 70 | attributes: attributes, 71 | vertexShader: shaders.vs.VERT, 72 | fragmentShader: shaders.fs.FRAG 73 | }); 74 | 75 | /* Model 76 | ********************************/ 77 | 78 | var radius = 50, 79 | segments = 16, 80 | rings = 16; 81 | 82 | var sphere = new THREE.Mesh( 83 | new THREE.SphereGeometry(radius, segments, rings), 84 | shaderMaterial 85 | ); 86 | 87 | scene.add(sphere); 88 | 89 | /* View controller 90 | ********************************/ 91 | 92 | var controls = new DeviceOrientationController(sphere, renderer.domElement); 93 | controls.connect(); 94 | 95 | /* Spikey spikes 96 | ********************************/ 97 | 98 | var verts = sphere.geometry.vertices, 99 | values = attributes.displacement.value; 100 | 101 | for (var v = 0; v < verts.length; v++) { 102 | values.push(Math.random() * 30); 103 | } 104 | 105 | /* Lights 106 | ********************************/ 107 | 108 | var pointLight = new THREE.PointLight(0xFFFFFF); 109 | 110 | pointLight.position.x = 10; 111 | pointLight.position.y = 50; 112 | pointLight.position.z = 130; 113 | 114 | scene.add(pointLight); 115 | 116 | /* Animation loop 117 | ********************************/ 118 | 119 | var frame = 0; 120 | function update () { 121 | uniforms.amplitude.value = Math.sin(frame); 122 | frame += 0.1; 123 | controls.update(); 124 | effect.render(scene, camera); 125 | window.requestAnimationFrame(update); 126 | } 127 | window.requestAnimationFrame(update); 128 | 129 | /* Debugging exports 130 | ********************************/ 131 | 132 | window.effect = effect; 133 | 134 | } 135 | 136 | }(window.THREE, window.ShaderLoader, window.DeviceOrientationController)); 137 | -------------------------------------------------------------------------------- /examples/ex4-world/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebGL Demo 3 | * 4 | * Based on: http://aerotwist.com/tutorials/getting-started-with-three-js/ 5 | */ 6 | 7 | (function (THREE, Stats) { 8 | 9 | /* Scene 10 | ********************************/ 11 | 12 | var container = document.querySelector('#container'), 13 | WIDTH = container.clientWidth, 14 | HEIGHT = container.clientHeight, 15 | ASPECT = WIDTH / HEIGHT, 16 | VIEW_ANGLE = 45, 17 | NEAR = 0.1, 18 | FAR = 10000; 19 | 20 | var controls, 21 | scene = new THREE.Scene(), 22 | renderer = new THREE.WebGLRenderer(), 23 | camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR); 24 | 25 | camera.position.z = -200; 26 | camera.up.set(0, -1, 0); 27 | scene.add(camera); 28 | 29 | renderer.setSize(WIDTH, HEIGHT); 30 | renderer.setClearColor(0xF0F0F0); 31 | container.appendChild(renderer.domElement); 32 | 33 | controls = new THREE.MapControls(camera, renderer.domElement); 34 | 35 | /* Countries 36 | ********************************/ 37 | 38 | var COLORS = [0x588C7E, 0xF2E394, 0xF2AE72, 0xD96459, 0x8C4646], 39 | EXTRUDE_AMOUNT = 1; 40 | 41 | window.fetch('models/countries.json') 42 | .then(function(response) { 43 | return response.json(); 44 | }).then(function(countries) { 45 | countries.forEach(loadCountry); 46 | }).catch(function(ex) { 47 | console.log('Countries could not be loaded.', ex); 48 | }); 49 | 50 | function loadCountry (country) { 51 | var mesh, color, material, 52 | paths = THREE.transformSVGPath(country.feature); 53 | 54 | for (var i = 0; i < paths.length; i++) { 55 | paths[i] = paths[i].extrude({ 56 | amount: EXTRUDE_AMOUNT, 57 | bevelEnabled: false 58 | }); 59 | 60 | if (i > 0) paths[0].merge(paths[i]); 61 | } 62 | 63 | color = COLORS[Math.floor((Math.random() * COLORS.length))]; 64 | material = new THREE.MeshPhongMaterial({color: color, opacity: 1.0}); 65 | 66 | mesh = new THREE.Mesh(paths[0], material); 67 | mesh.name = country.data.name; 68 | mesh.position.set(-475, 50, 20); 69 | scene.add(mesh); 70 | } 71 | 72 | /* Stats 73 | ********************************/ 74 | 75 | var stats = new Stats(); 76 | 77 | // align top-left 78 | stats.domElement.style.position = 'absolute'; 79 | stats.domElement.style.left = '0px'; 80 | stats.domElement.style.top = '0px'; 81 | 82 | document.body.appendChild(stats.domElement); 83 | 84 | /* Responsive layout 85 | ********************************/ 86 | 87 | window.addEventListener('resize', function () { 88 | WIDTH = container.clientWidth; 89 | HEIGHT = container.clientHeight; 90 | ASPECT = WIDTH / HEIGHT; 91 | 92 | camera.aspect = ASPECT; 93 | camera.updateProjectionMatrix(); 94 | renderer.setSize(WIDTH, HEIGHT); 95 | }, false); 96 | 97 | /* Click events 98 | ********************************/ 99 | 100 | var raycaster = new THREE.Raycaster(), 101 | mouse = new THREE.Vector2(); 102 | 103 | renderer.domElement.addEventListener('click', function (event) { 104 | mouse.x = 2 * event.clientX / WIDTH - 1; 105 | mouse.y = -2 * event.clientY / HEIGHT + 1; 106 | raycaster.setFromCamera(mouse, camera); 107 | 108 | raycaster 109 | .intersectObjects(scene.children) 110 | .forEach(function (intersect) { 111 | console.log(' --> %s', intersect.object.name); 112 | }); 113 | }); 114 | 115 | /* Lights 116 | ********************************/ 117 | 118 | var pointLight2 = new THREE.PointLight(0xFFFFFF, 0.8); 119 | pointLight2.position.z = -2000; 120 | scene.add(pointLight2); 121 | 122 | /* Animation 123 | ********************************/ 124 | 125 | function update () { 126 | stats.begin(); 127 | controls.update(); 128 | renderer.render(scene, camera); 129 | stats.end(); 130 | window.requestAnimationFrame(update); 131 | } 132 | window.requestAnimationFrame(update); 133 | 134 | }(window.THREE, window.Stats)); 135 | -------------------------------------------------------------------------------- /lib/StereoEffect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @authod mrdoob / http://mrdoob.com/ 4 | * @authod arodic / http://aleksandarrodic.com/ 5 | * @authod fonserbc / http://fonserbc.github.io/ 6 | * 7 | * Off-axis stereoscopic effect based on http://paulbourke.net/stereographics/stereorender/ 8 | */ 9 | 10 | THREE.StereoEffect = function ( renderer ) { 11 | 12 | // API 13 | 14 | var scope = this; 15 | 16 | this.eyeSeparation = 3; 17 | this.focalLength = 15; // Distance to the non-parallax or projection plane 18 | 19 | Object.defineProperties( this, { 20 | separation: { 21 | get: function () { 22 | return scope.eyeSeparation; 23 | }, 24 | set: function ( value ) { 25 | console.warn( 'THREE.StereoEffect: .separation is now .eyeSeparation.' ); 26 | scope.eyeSeparation = value; 27 | } 28 | }, 29 | targetDistance: { 30 | get: function () { 31 | return scope.focalLength; 32 | }, 33 | set: function ( value ) { 34 | console.warn( 'THREE.StereoEffect: .targetDistance is now .focalLength.' ); 35 | scope.focalLength = value; 36 | } 37 | } 38 | } ); 39 | 40 | // internals 41 | 42 | var _width, _height; 43 | 44 | var _position = new THREE.Vector3(); 45 | var _quaternion = new THREE.Quaternion(); 46 | var _scale = new THREE.Vector3(); 47 | 48 | var _cameraL = new THREE.PerspectiveCamera(); 49 | var _cameraR = new THREE.PerspectiveCamera(); 50 | 51 | var _fov; 52 | var _outer, _inner, _top, _bottom; 53 | var _ndfl, _halfFocalWidth, _halfFocalHeight; 54 | var _innerFactor, _outerFactor; 55 | 56 | // initialization 57 | 58 | renderer.autoClear = false; 59 | 60 | this.setSize = function ( width, height ) { 61 | 62 | _width = width / 2; 63 | _height = height; 64 | 65 | renderer.setSize( width, height ); 66 | 67 | }; 68 | 69 | this.render = function ( scene, camera ) { 70 | 71 | scene.updateMatrixWorld(); 72 | 73 | if ( camera.parent === undefined ) camera.updateMatrixWorld(); 74 | 75 | camera.matrixWorld.decompose( _position, _quaternion, _scale ); 76 | 77 | // Effective fov of the camera 78 | 79 | _fov = THREE.Math.radToDeg( 2 * Math.atan( Math.tan( THREE.Math.degToRad( camera.fov ) * 0.5 ) / camera.zoom ) ); 80 | 81 | _ndfl = camera.near / this.focalLength; 82 | _halfFocalHeight = Math.tan( THREE.Math.degToRad( _fov ) * 0.5 ) * this.focalLength; 83 | _halfFocalWidth = _halfFocalHeight * 0.5 * camera.aspect; 84 | 85 | _top = _halfFocalHeight * _ndfl; 86 | _bottom = -_top; 87 | _innerFactor = ( _halfFocalWidth + this.eyeSeparation / 2.0 ) / ( _halfFocalWidth * 2.0 ); 88 | _outerFactor = 1.0 - _innerFactor; 89 | 90 | _outer = _halfFocalWidth * 2.0 * _ndfl * _outerFactor; 91 | _inner = _halfFocalWidth * 2.0 * _ndfl * _innerFactor; 92 | 93 | // left 94 | 95 | _cameraL.projectionMatrix.makeFrustum( 96 | -_outer, 97 | _inner, 98 | _bottom, 99 | _top, 100 | camera.near, 101 | camera.far 102 | ); 103 | 104 | _cameraL.position.copy( _position ); 105 | _cameraL.quaternion.copy( _quaternion ); 106 | _cameraL.translateX( - this.eyeSeparation / 2.0 ); 107 | 108 | // right 109 | 110 | _cameraR.projectionMatrix.makeFrustum( 111 | -_inner, 112 | _outer, 113 | _bottom, 114 | _top, 115 | camera.near, 116 | camera.far 117 | ); 118 | 119 | _cameraR.position.copy( _position ); 120 | _cameraR.quaternion.copy( _quaternion ); 121 | _cameraR.translateX( this.eyeSeparation / 2.0 ); 122 | 123 | // 124 | 125 | renderer.clear(); 126 | renderer.enableScissorTest( true ); 127 | 128 | renderer.setScissor( 0, 0, _width, _height ); 129 | renderer.setViewport( 0, 0, _width, _height ); 130 | renderer.render( scene, _cameraL ); 131 | 132 | renderer.setScissor( _width, 0, _width, _height ); 133 | renderer.setViewport( _width, 0, _width, _height ); 134 | renderer.render( scene, _cameraR ); 135 | 136 | renderer.enableScissorTest( false ); 137 | 138 | }; 139 | 140 | }; 141 | -------------------------------------------------------------------------------- /lib/DDSLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.DDSLoader = function () { 6 | this._parser = THREE.DDSLoader.parse; 7 | }; 8 | 9 | THREE.DDSLoader.prototype = Object.create( THREE.CompressedTextureLoader.prototype ); 10 | THREE.DDSLoader.prototype.constructor = THREE.DDSLoader; 11 | 12 | THREE.DDSLoader.parse = function ( buffer, loadMipmaps ) { 13 | 14 | var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 }; 15 | 16 | // Adapted from @toji's DDS utils 17 | // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js 18 | 19 | // All values and structures referenced from: 20 | // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ 21 | 22 | var DDS_MAGIC = 0x20534444; 23 | 24 | var DDSD_CAPS = 0x1, 25 | DDSD_HEIGHT = 0x2, 26 | DDSD_WIDTH = 0x4, 27 | DDSD_PITCH = 0x8, 28 | DDSD_PIXELFORMAT = 0x1000, 29 | DDSD_MIPMAPCOUNT = 0x20000, 30 | DDSD_LINEARSIZE = 0x80000, 31 | DDSD_DEPTH = 0x800000; 32 | 33 | var DDSCAPS_COMPLEX = 0x8, 34 | DDSCAPS_MIPMAP = 0x400000, 35 | DDSCAPS_TEXTURE = 0x1000; 36 | 37 | var DDSCAPS2_CUBEMAP = 0x200, 38 | DDSCAPS2_CUBEMAP_POSITIVEX = 0x400, 39 | DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800, 40 | DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000, 41 | DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000, 42 | DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000, 43 | DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000, 44 | DDSCAPS2_VOLUME = 0x200000; 45 | 46 | var DDPF_ALPHAPIXELS = 0x1, 47 | DDPF_ALPHA = 0x2, 48 | DDPF_FOURCC = 0x4, 49 | DDPF_RGB = 0x40, 50 | DDPF_YUV = 0x200, 51 | DDPF_LUMINANCE = 0x20000; 52 | 53 | function fourCCToInt32( value ) { 54 | 55 | return value.charCodeAt(0) + 56 | (value.charCodeAt(1) << 8) + 57 | (value.charCodeAt(2) << 16) + 58 | (value.charCodeAt(3) << 24); 59 | 60 | } 61 | 62 | function int32ToFourCC( value ) { 63 | 64 | return String.fromCharCode( 65 | value & 0xff, 66 | (value >> 8) & 0xff, 67 | (value >> 16) & 0xff, 68 | (value >> 24) & 0xff 69 | ); 70 | } 71 | 72 | function loadARGBMip( buffer, dataOffset, width, height ) { 73 | var dataLength = width*height*4; 74 | var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength ); 75 | var byteArray = new Uint8Array( dataLength ); 76 | var dst = 0; 77 | var src = 0; 78 | for ( var y = 0; y < height; y++ ) { 79 | for ( var x = 0; x < width; x++ ) { 80 | var b = srcBuffer[src]; src++; 81 | var g = srcBuffer[src]; src++; 82 | var r = srcBuffer[src]; src++; 83 | var a = srcBuffer[src]; src++; 84 | byteArray[dst] = r; dst++; //r 85 | byteArray[dst] = g; dst++; //g 86 | byteArray[dst] = b; dst++; //b 87 | byteArray[dst] = a; dst++; //a 88 | } 89 | } 90 | return byteArray; 91 | } 92 | 93 | var FOURCC_DXT1 = fourCCToInt32("DXT1"); 94 | var FOURCC_DXT3 = fourCCToInt32("DXT3"); 95 | var FOURCC_DXT5 = fourCCToInt32("DXT5"); 96 | 97 | var headerLengthInt = 31; // The header length in 32 bit ints 98 | 99 | // Offsets into the header array 100 | 101 | var off_magic = 0; 102 | 103 | var off_size = 1; 104 | var off_flags = 2; 105 | var off_height = 3; 106 | var off_width = 4; 107 | 108 | var off_mipmapCount = 7; 109 | 110 | var off_pfFlags = 20; 111 | var off_pfFourCC = 21; 112 | var off_RGBBitCount = 22; 113 | var off_RBitMask = 23; 114 | var off_GBitMask = 24; 115 | var off_BBitMask = 25; 116 | var off_ABitMask = 26; 117 | 118 | var off_caps = 27; 119 | var off_caps2 = 28; 120 | var off_caps3 = 29; 121 | var off_caps4 = 30; 122 | 123 | // Parse header 124 | 125 | var header = new Int32Array( buffer, 0, headerLengthInt ); 126 | 127 | if ( header[ off_magic ] !== DDS_MAGIC ) { 128 | 129 | console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' ); 130 | return dds; 131 | 132 | } 133 | 134 | if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) { 135 | 136 | console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' ); 137 | return dds; 138 | 139 | } 140 | 141 | var blockBytes; 142 | 143 | var fourCC = header[ off_pfFourCC ]; 144 | 145 | var isRGBAUncompressed = false; 146 | 147 | switch ( fourCC ) { 148 | 149 | case FOURCC_DXT1: 150 | 151 | blockBytes = 8; 152 | dds.format = THREE.RGB_S3TC_DXT1_Format; 153 | break; 154 | 155 | case FOURCC_DXT3: 156 | 157 | blockBytes = 16; 158 | dds.format = THREE.RGBA_S3TC_DXT3_Format; 159 | break; 160 | 161 | case FOURCC_DXT5: 162 | 163 | blockBytes = 16; 164 | dds.format = THREE.RGBA_S3TC_DXT5_Format; 165 | break; 166 | 167 | default: 168 | 169 | if( header[off_RGBBitCount] ==32 170 | && header[off_RBitMask]&0xff0000 171 | && header[off_GBitMask]&0xff00 172 | && header[off_BBitMask]&0xff 173 | && header[off_ABitMask]&0xff000000 ) { 174 | isRGBAUncompressed = true; 175 | blockBytes = 64; 176 | dds.format = THREE.RGBAFormat; 177 | } else { 178 | console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) ); 179 | return dds; 180 | } 181 | } 182 | 183 | dds.mipmapCount = 1; 184 | 185 | if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) { 186 | 187 | dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] ); 188 | 189 | } 190 | 191 | //TODO: Verify that all faces of the cubemap are present with DDSCAPS2_CUBEMAP_POSITIVEX, etc. 192 | 193 | dds.isCubemap = header[ off_caps2 ] & DDSCAPS2_CUBEMAP ? true : false; 194 | 195 | dds.width = header[ off_width ]; 196 | dds.height = header[ off_height ]; 197 | 198 | var dataOffset = header[ off_size ] + 4; 199 | 200 | // Extract mipmaps buffers 201 | 202 | var width = dds.width; 203 | var height = dds.height; 204 | 205 | var faces = dds.isCubemap ? 6 : 1; 206 | 207 | for ( var face = 0; face < faces; face ++ ) { 208 | 209 | for ( var i = 0; i < dds.mipmapCount; i ++ ) { 210 | 211 | if( isRGBAUncompressed ) { 212 | var byteArray = loadARGBMip( buffer, dataOffset, width, height ); 213 | var dataLength = byteArray.length; 214 | } else { 215 | var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes; 216 | var byteArray = new Uint8Array( buffer, dataOffset, dataLength ); 217 | } 218 | 219 | var mipmap = { "data": byteArray, "width": width, "height": height }; 220 | dds.mipmaps.push( mipmap ); 221 | 222 | dataOffset += dataLength; 223 | 224 | width = Math.max( width * 0.5, 1 ); 225 | height = Math.max( height * 0.5, 1 ); 226 | 227 | } 228 | 229 | width = dds.width; 230 | height = dds.height; 231 | 232 | } 233 | 234 | return dds; 235 | 236 | }; 237 | 238 | -------------------------------------------------------------------------------- /lib/FirstPersonControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author alteredq / http://alteredqualia.com/ 4 | * @author paulirish / http://paulirish.com/ 5 | */ 6 | 7 | THREE.FirstPersonControls = function ( object, domElement ) { 8 | 9 | this.object = object; 10 | this.target = new THREE.Vector3( 0, 0, 0 ); 11 | 12 | this.domElement = ( domElement !== undefined ) ? domElement : document; 13 | 14 | this.movementSpeed = 1.0; 15 | this.lookSpeed = 0.005; 16 | 17 | this.lookVertical = true; 18 | this.autoForward = false; 19 | // this.invertVertical = false; 20 | 21 | this.activeLook = true; 22 | 23 | this.heightSpeed = false; 24 | this.heightCoef = 1.0; 25 | this.heightMin = 0.0; 26 | this.heightMax = 1.0; 27 | 28 | this.constrainVertical = false; 29 | this.verticalMin = 0; 30 | this.verticalMax = Math.PI; 31 | 32 | this.autoSpeedFactor = 0.0; 33 | 34 | this.mouseX = 0; 35 | this.mouseY = 0; 36 | 37 | this.lat = 0; 38 | this.lon = 0; 39 | this.phi = 0; 40 | this.theta = 0; 41 | 42 | this.moveForward = false; 43 | this.moveBackward = false; 44 | this.moveLeft = false; 45 | this.moveRight = false; 46 | this.freeze = false; 47 | 48 | this.mouseDragOn = false; 49 | 50 | this.viewHalfX = 0; 51 | this.viewHalfY = 0; 52 | 53 | if ( this.domElement !== document ) { 54 | 55 | this.domElement.setAttribute( 'tabindex', -1 ); 56 | 57 | } 58 | 59 | // 60 | 61 | this.handleResize = function () { 62 | 63 | if ( this.domElement === document ) { 64 | 65 | this.viewHalfX = window.innerWidth / 2; 66 | this.viewHalfY = window.innerHeight / 2; 67 | 68 | } else { 69 | 70 | this.viewHalfX = this.domElement.offsetWidth / 2; 71 | this.viewHalfY = this.domElement.offsetHeight / 2; 72 | 73 | } 74 | 75 | }; 76 | 77 | this.onMouseDown = function ( event ) { 78 | 79 | if ( this.domElement !== document ) { 80 | 81 | this.domElement.focus(); 82 | 83 | } 84 | 85 | event.preventDefault(); 86 | event.stopPropagation(); 87 | 88 | if ( this.activeLook ) { 89 | 90 | switch ( event.button ) { 91 | 92 | //case 0: this.moveForward = true; break; 93 | //case 2: this.moveBackward = true; break; 94 | 95 | } 96 | 97 | } 98 | 99 | this.mouseDragOn = true; 100 | 101 | }; 102 | 103 | this.onMouseUp = function ( event ) { 104 | 105 | event.preventDefault(); 106 | event.stopPropagation(); 107 | 108 | if ( this.activeLook ) { 109 | 110 | switch ( event.button ) { 111 | 112 | //case 0: this.moveForward = false; break; 113 | //case 2: this.moveBackward = false; break; 114 | 115 | } 116 | 117 | } 118 | 119 | this.mouseDragOn = false; 120 | 121 | }; 122 | 123 | this.onMouseMove = function ( event ) { 124 | 125 | if ( this.domElement === document ) { 126 | 127 | this.mouseX = event.pageX - this.viewHalfX; 128 | this.mouseY = event.pageY - this.viewHalfY; 129 | 130 | } else { 131 | 132 | this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX; 133 | this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY; 134 | 135 | } 136 | 137 | }; 138 | 139 | this.onKeyDown = function ( event ) { 140 | 141 | //event.preventDefault(); 142 | 143 | switch ( event.keyCode ) { 144 | 145 | case 38: /*up*/ 146 | case 87: /*W*/ this.moveForward = true; break; 147 | 148 | case 37: /*left*/ 149 | case 65: /*A*/ this.moveLeft = true; break; 150 | 151 | case 40: /*down*/ 152 | case 83: /*S*/ this.moveBackward = true; break; 153 | 154 | case 39: /*right*/ 155 | case 68: /*D*/ this.moveRight = true; break; 156 | 157 | case 82: /*R*/ this.moveUp = true; break; 158 | case 70: /*F*/ this.moveDown = true; break; 159 | 160 | case 81: /*Q*/ this.freeze = !this.freeze; break; 161 | 162 | } 163 | 164 | }; 165 | 166 | this.onKeyUp = function ( event ) { 167 | 168 | switch( event.keyCode ) { 169 | 170 | case 38: /*up*/ 171 | case 87: /*W*/ this.moveForward = false; break; 172 | 173 | case 37: /*left*/ 174 | case 65: /*A*/ this.moveLeft = false; break; 175 | 176 | case 40: /*down*/ 177 | case 83: /*S*/ this.moveBackward = false; break; 178 | 179 | case 39: /*right*/ 180 | case 68: /*D*/ this.moveRight = false; break; 181 | 182 | case 82: /*R*/ this.moveUp = false; break; 183 | case 70: /*F*/ this.moveDown = false; break; 184 | 185 | } 186 | 187 | }; 188 | 189 | this.update = function( delta ) { 190 | 191 | if ( this.freeze ) { 192 | 193 | return; 194 | 195 | } 196 | 197 | if ( this.heightSpeed ) { 198 | 199 | var y = THREE.Math.clamp( this.object.position.y, this.heightMin, this.heightMax ); 200 | var heightDelta = y - this.heightMin; 201 | 202 | this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef ); 203 | 204 | } else { 205 | 206 | this.autoSpeedFactor = 0.0; 207 | 208 | } 209 | 210 | var actualMoveSpeed = delta * this.movementSpeed; 211 | 212 | if ( this.moveForward || ( this.autoForward && !this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) ); 213 | if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed ); 214 | 215 | if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed ); 216 | if ( this.moveRight ) this.object.translateX( actualMoveSpeed ); 217 | 218 | if ( this.moveUp ) this.object.translateY( actualMoveSpeed ); 219 | if ( this.moveDown ) this.object.translateY( - actualMoveSpeed ); 220 | 221 | var actualLookSpeed = delta * this.lookSpeed; 222 | 223 | if ( !this.activeLook ) { 224 | 225 | actualLookSpeed = 0; 226 | 227 | } 228 | 229 | var verticalLookRatio = 1; 230 | 231 | if ( this.constrainVertical ) { 232 | 233 | verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin ); 234 | 235 | } 236 | 237 | this.lon += this.mouseX * actualLookSpeed; 238 | if( this.lookVertical ) this.lat -= this.mouseY * actualLookSpeed * verticalLookRatio; 239 | 240 | this.lat = Math.max( - 85, Math.min( 85, this.lat ) ); 241 | this.phi = THREE.Math.degToRad( 90 - this.lat ); 242 | 243 | this.theta = THREE.Math.degToRad( this.lon ); 244 | 245 | if ( this.constrainVertical ) { 246 | 247 | this.phi = THREE.Math.mapLinear( this.phi, 0, Math.PI, this.verticalMin, this.verticalMax ); 248 | 249 | } 250 | 251 | var targetPosition = this.target, 252 | position = this.object.position; 253 | 254 | targetPosition.x = position.x + 100 * Math.sin( this.phi ) * Math.cos( this.theta ); 255 | targetPosition.y = position.y + 100 * Math.cos( this.phi ); 256 | targetPosition.z = position.z + 100 * Math.sin( this.phi ) * Math.sin( this.theta ); 257 | 258 | this.object.lookAt( targetPosition ); 259 | 260 | }; 261 | 262 | 263 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 264 | 265 | this.domElement.addEventListener( 'mousemove', bind( this, this.onMouseMove ), false ); 266 | this.domElement.addEventListener( 'mousedown', bind( this, this.onMouseDown ), false ); 267 | this.domElement.addEventListener( 'mouseup', bind( this, this.onMouseUp ), false ); 268 | 269 | window.addEventListener( 'keydown', bind( this, this.onKeyDown ), false ); 270 | window.addEventListener( 'keyup', bind( this, this.onKeyUp ), false ); 271 | 272 | function bind( scope, fn ) { 273 | 274 | return function () { 275 | 276 | fn.apply( scope, arguments ); 277 | 278 | }; 279 | 280 | }; 281 | 282 | this.handleResize(); 283 | 284 | }; 285 | -------------------------------------------------------------------------------- /lib/FlyControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author James Baicoianu / http://www.baicoianu.com/ 3 | */ 4 | 5 | THREE.FlyControls = function ( object, domElement ) { 6 | 7 | this.object = object; 8 | 9 | this.domElement = ( domElement !== undefined ) ? domElement : document; 10 | if ( domElement ) this.domElement.setAttribute( 'tabindex', -1 ); 11 | 12 | // API 13 | 14 | this.movementSpeed = 1.0; 15 | this.rollSpeed = 0.005; 16 | 17 | this.dragToLook = false; 18 | this.autoForward = false; 19 | 20 | // disable default target object behavior 21 | 22 | // internals 23 | 24 | this.tmpQuaternion = new THREE.Quaternion(); 25 | 26 | this.mouseStatus = 0; 27 | 28 | this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 }; 29 | this.moveVector = new THREE.Vector3( 0, 0, 0 ); 30 | this.rotationVector = new THREE.Vector3( 0, 0, 0 ); 31 | 32 | this.handleEvent = function ( event ) { 33 | 34 | if ( typeof this[ event.type ] == 'function' ) { 35 | 36 | this[ event.type ]( event ); 37 | 38 | } 39 | 40 | }; 41 | 42 | this.keydown = function( event ) { 43 | 44 | if ( event.altKey ) { 45 | 46 | return; 47 | 48 | } 49 | 50 | //event.preventDefault(); 51 | 52 | switch ( event.keyCode ) { 53 | 54 | case 16: /* shift */ this.movementSpeedMultiplier = .1; break; 55 | 56 | case 87: /*W*/ this.moveState.forward = 1; break; 57 | case 83: /*S*/ this.moveState.back = 1; break; 58 | 59 | case 65: /*A*/ this.moveState.left = 1; break; 60 | case 68: /*D*/ this.moveState.right = 1; break; 61 | 62 | case 82: /*R*/ this.moveState.up = 1; break; 63 | case 70: /*F*/ this.moveState.down = 1; break; 64 | 65 | case 38: /*up*/ this.moveState.pitchUp = 1; break; 66 | case 40: /*down*/ this.moveState.pitchDown = 1; break; 67 | 68 | case 37: /*left*/ this.moveState.yawLeft = 1; break; 69 | case 39: /*right*/ this.moveState.yawRight = 1; break; 70 | 71 | case 81: /*Q*/ this.moveState.rollLeft = 1; break; 72 | case 69: /*E*/ this.moveState.rollRight = 1; break; 73 | 74 | } 75 | 76 | this.updateMovementVector(); 77 | this.updateRotationVector(); 78 | 79 | }; 80 | 81 | this.keyup = function( event ) { 82 | 83 | switch( event.keyCode ) { 84 | 85 | case 16: /* shift */ this.movementSpeedMultiplier = 1; break; 86 | 87 | case 87: /*W*/ this.moveState.forward = 0; break; 88 | case 83: /*S*/ this.moveState.back = 0; break; 89 | 90 | case 65: /*A*/ this.moveState.left = 0; break; 91 | case 68: /*D*/ this.moveState.right = 0; break; 92 | 93 | case 82: /*R*/ this.moveState.up = 0; break; 94 | case 70: /*F*/ this.moveState.down = 0; break; 95 | 96 | case 38: /*up*/ this.moveState.pitchUp = 0; break; 97 | case 40: /*down*/ this.moveState.pitchDown = 0; break; 98 | 99 | case 37: /*left*/ this.moveState.yawLeft = 0; break; 100 | case 39: /*right*/ this.moveState.yawRight = 0; break; 101 | 102 | case 81: /*Q*/ this.moveState.rollLeft = 0; break; 103 | case 69: /*E*/ this.moveState.rollRight = 0; break; 104 | 105 | } 106 | 107 | this.updateMovementVector(); 108 | this.updateRotationVector(); 109 | 110 | }; 111 | 112 | this.mousedown = function( event ) { 113 | 114 | if ( this.domElement !== document ) { 115 | 116 | this.domElement.focus(); 117 | 118 | } 119 | 120 | event.preventDefault(); 121 | event.stopPropagation(); 122 | 123 | if ( this.dragToLook ) { 124 | 125 | this.mouseStatus ++; 126 | 127 | } else { 128 | 129 | switch ( event.button ) { 130 | 131 | case 0: this.moveState.forward = 1; break; 132 | case 2: this.moveState.back = 1; break; 133 | 134 | } 135 | 136 | this.updateMovementVector(); 137 | 138 | } 139 | 140 | }; 141 | 142 | this.mousemove = function( event ) { 143 | 144 | if ( !this.dragToLook || this.mouseStatus > 0 ) { 145 | 146 | var container = this.getContainerDimensions(); 147 | var halfWidth = container.size[ 0 ] / 2; 148 | var halfHeight = container.size[ 1 ] / 2; 149 | 150 | this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth; 151 | this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight; 152 | 153 | this.updateRotationVector(); 154 | 155 | } 156 | 157 | }; 158 | 159 | this.mouseup = function( event ) { 160 | 161 | event.preventDefault(); 162 | event.stopPropagation(); 163 | 164 | if ( this.dragToLook ) { 165 | 166 | this.mouseStatus --; 167 | 168 | this.moveState.yawLeft = this.moveState.pitchDown = 0; 169 | 170 | } else { 171 | 172 | switch ( event.button ) { 173 | 174 | case 0: this.moveState.forward = 0; break; 175 | case 2: this.moveState.back = 0; break; 176 | 177 | } 178 | 179 | this.updateMovementVector(); 180 | 181 | } 182 | 183 | this.updateRotationVector(); 184 | 185 | }; 186 | 187 | this.update = function( delta ) { 188 | 189 | var moveMult = delta * this.movementSpeed; 190 | var rotMult = delta * this.rollSpeed; 191 | 192 | this.object.translateX( this.moveVector.x * moveMult ); 193 | this.object.translateY( this.moveVector.y * moveMult ); 194 | this.object.translateZ( this.moveVector.z * moveMult ); 195 | 196 | this.tmpQuaternion.set( this.rotationVector.x * rotMult, this.rotationVector.y * rotMult, this.rotationVector.z * rotMult, 1 ).normalize(); 197 | this.object.quaternion.multiply( this.tmpQuaternion ); 198 | 199 | // expose the rotation vector for convenience 200 | this.object.rotation.setFromQuaternion( this.object.quaternion, this.object.rotation.order ); 201 | 202 | 203 | }; 204 | 205 | this.updateMovementVector = function() { 206 | 207 | var forward = ( this.moveState.forward || ( this.autoForward && !this.moveState.back ) ) ? 1 : 0; 208 | 209 | this.moveVector.x = ( -this.moveState.left + this.moveState.right ); 210 | this.moveVector.y = ( -this.moveState.down + this.moveState.up ); 211 | this.moveVector.z = ( -forward + this.moveState.back ); 212 | 213 | //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] ); 214 | 215 | }; 216 | 217 | this.updateRotationVector = function() { 218 | 219 | this.rotationVector.x = ( -this.moveState.pitchDown + this.moveState.pitchUp ); 220 | this.rotationVector.y = ( -this.moveState.yawRight + this.moveState.yawLeft ); 221 | this.rotationVector.z = ( -this.moveState.rollRight + this.moveState.rollLeft ); 222 | 223 | //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] ); 224 | 225 | }; 226 | 227 | this.getContainerDimensions = function() { 228 | 229 | if ( this.domElement != document ) { 230 | 231 | return { 232 | size : [ this.domElement.offsetWidth, this.domElement.offsetHeight ], 233 | offset : [ this.domElement.offsetLeft, this.domElement.offsetTop ] 234 | }; 235 | 236 | } else { 237 | 238 | return { 239 | size : [ window.innerWidth, window.innerHeight ], 240 | offset : [ 0, 0 ] 241 | }; 242 | 243 | } 244 | 245 | }; 246 | 247 | function bind( scope, fn ) { 248 | 249 | return function () { 250 | 251 | fn.apply( scope, arguments ); 252 | 253 | }; 254 | 255 | }; 256 | 257 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 258 | 259 | this.domElement.addEventListener( 'mousemove', bind( this, this.mousemove ), false ); 260 | this.domElement.addEventListener( 'mousedown', bind( this, this.mousedown ), false ); 261 | this.domElement.addEventListener( 'mouseup', bind( this, this.mouseup ), false ); 262 | 263 | window.addEventListener( 'keydown', bind( this, this.keydown ), false ); 264 | window.addEventListener( 'keyup', bind( this, this.keyup ), false ); 265 | 266 | this.updateMovementVector(); 267 | this.updateRotationVector(); 268 | 269 | }; 270 | -------------------------------------------------------------------------------- /lib/transformSVGPath.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | (function (THREE) { 6 | 7 | var DEGS_TO_RADS = Math.PI / 180; 8 | 9 | var DIGIT_0 = 48, 10 | DIGIT_9 = 57, 11 | COMMA = 44, 12 | SPACE = 32, 13 | PERIOD = 46, 14 | MINUS = 45; 15 | 16 | THREE.transformSVGPath = function (pathStr) { 17 | 18 | var paths = []; 19 | var path = new THREE.Shape(); 20 | 21 | var idx = 1, len = pathStr.length, activeCmd, 22 | x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null, 23 | x1 = 0, x2 = 0, y1 = 0, y2 = 0, 24 | rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy; 25 | 26 | function eatNum() { 27 | var sidx, c, isFloat = false, s; 28 | // eat delims 29 | while (idx < len) { 30 | c = pathStr.charCodeAt(idx); 31 | if (c !== COMMA && c !== SPACE) 32 | break; 33 | idx++; 34 | } 35 | if (c === MINUS) 36 | sidx = idx++; 37 | else 38 | sidx = idx; 39 | // eat number 40 | while (idx < len) { 41 | c = pathStr.charCodeAt(idx); 42 | if (DIGIT_0 <= c && c <= DIGIT_9) { 43 | idx++; 44 | continue; 45 | } 46 | else if (c === PERIOD) { 47 | idx++; 48 | isFloat = true; 49 | continue; 50 | } 51 | 52 | s = pathStr.substring(sidx, idx); 53 | return isFloat ? parseFloat(s) : parseInt(s); 54 | } 55 | 56 | s = pathStr.substring(sidx); 57 | return isFloat ? parseFloat(s) : parseInt(s); 58 | } 59 | 60 | function nextIsNum() { 61 | var c; 62 | // do permanently eat any delims... 63 | while (idx < len) { 64 | c = pathStr.charCodeAt(idx); 65 | if (c !== COMMA && c !== SPACE) 66 | break; 67 | idx++; 68 | } 69 | c = pathStr.charCodeAt(idx); 70 | return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9)); 71 | } 72 | 73 | var canRepeat; 74 | var enteredSub = false; 75 | var zSeen = false; 76 | activeCmd = pathStr[0]; 77 | 78 | while (idx <= len) { 79 | canRepeat = true; 80 | switch (activeCmd) { 81 | // moveto commands, become lineto's if repeated 82 | case 'M': 83 | enteredSub = false; 84 | x = eatNum(); 85 | y = eatNum(); 86 | path.moveTo(x, y); 87 | activeCmd = 'L'; 88 | break; 89 | case 'm': 90 | x += eatNum(); 91 | y += eatNum(); 92 | path.moveTo(x, y); 93 | activeCmd = 'l'; 94 | break; 95 | case 'Z': 96 | case 'z': 97 | // z is a special case. This ends a segment and starts 98 | // a new path. Since the three.js path is continuous 99 | // we should start a new path here. This also draws a 100 | // line from the current location to the start location. 101 | canRepeat = false; 102 | if (x !== firstX || y !== firstY) 103 | path.lineTo(firstX, firstY); 104 | 105 | paths.push(path); 106 | 107 | // reset the elements 108 | firstX = null; 109 | firstY = null; 110 | 111 | // avoid x,y being set incorrectly 112 | enteredSub = true; 113 | 114 | path = new THREE.Shape(); 115 | 116 | zSeen = true; 117 | 118 | break; 119 | // - lines! 120 | case 'L': 121 | case 'H': 122 | case 'V': 123 | nx = (activeCmd === 'V') ? x : eatNum(); 124 | ny = (activeCmd === 'H') ? y : eatNum(); 125 | path.lineTo(nx, ny); 126 | x = nx; 127 | y = ny; 128 | break; 129 | case 'l': 130 | case 'h': 131 | case 'v': 132 | nx = (activeCmd === 'v') ? x : (x + eatNum()); 133 | ny = (activeCmd === 'h') ? y : (y + eatNum()); 134 | path.lineTo(nx, ny); 135 | x = nx; 136 | y = ny; 137 | break; 138 | // - cubic bezier 139 | case 'C': 140 | x1 = eatNum(); y1 = eatNum(); 141 | case 'S': 142 | if (activeCmd === 'S') { 143 | x1 = 2 * x - x2; y1 = 2 * y - y2; 144 | } 145 | x2 = eatNum(); 146 | y2 = eatNum(); 147 | nx = eatNum(); 148 | ny = eatNum(); 149 | path.bezierCurveTo(x1, y1, x2, y2, nx, ny); 150 | x = nx; y = ny; 151 | break; 152 | case 'c': 153 | x1 = x + eatNum(); 154 | y1 = y + eatNum(); 155 | case 's': 156 | if (activeCmd === 's') { 157 | x1 = 2 * x - x2; 158 | y1 = 2 * y - y2; 159 | } 160 | x2 = x + eatNum(); 161 | y2 = y + eatNum(); 162 | nx = x + eatNum(); 163 | ny = y + eatNum(); 164 | path.bezierCurveTo(x1, y1, x2, y2, nx, ny); 165 | x = nx; y = ny; 166 | break; 167 | // - quadratic bezier 168 | case 'Q': 169 | x1 = eatNum(); y1 = eatNum(); 170 | case 'T': 171 | if (activeCmd === 'T') { 172 | x1 = 2 * x - x1; 173 | y1 = 2 * y - y1; 174 | } 175 | nx = eatNum(); 176 | ny = eatNum(); 177 | path.quadraticCurveTo(x1, y1, nx, ny); 178 | x = nx; 179 | y = ny; 180 | break; 181 | case 'q': 182 | x1 = x + eatNum(); 183 | y1 = y + eatNum(); 184 | case 't': 185 | if (activeCmd === 't') { 186 | x1 = 2 * x - x1; 187 | y1 = 2 * y - y1; 188 | } 189 | nx = x + eatNum(); 190 | ny = y + eatNum(); 191 | path.quadraticCurveTo(x1, y1, nx, ny); 192 | x = nx; y = ny; 193 | break; 194 | // - elliptical arc 195 | case 'A': 196 | rx = eatNum(); 197 | ry = eatNum(); 198 | xar = eatNum() * DEGS_TO_RADS; 199 | laf = eatNum(); 200 | sf = eatNum(); 201 | nx = eatNum(); 202 | ny = eatNum(); 203 | if (rx !== ry) { 204 | console.warn("Forcing elliptical arc to be a circular one :(", 205 | rx, ry); 206 | } 207 | // SVG implementation notes does all the math for us! woo! 208 | // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes 209 | // step1, using x1 as x1' 210 | x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2; 211 | y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2; 212 | // step 2, using x2 as cx' 213 | var norm = Math.sqrt( 214 | (rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) / 215 | (rx*rx * y1*y1 + ry*ry * x1*x1)); 216 | if (laf === sf) 217 | norm = -norm; 218 | x2 = norm * rx * y1 / ry; 219 | y2 = norm * -ry * x1 / rx; 220 | // step 3 221 | cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2; 222 | cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2; 223 | 224 | var u = new THREE.Vector2(1, 0), 225 | v = new THREE.Vector2((x1 - x2) / rx, 226 | (y1 - y2) / ry); 227 | var startAng = Math.acos(u.dot(v) / u.length() / v.length()); 228 | if (u.x * v.y - u.y * v.x < 0) 229 | startAng = -startAng; 230 | 231 | // we can reuse 'v' from start angle as our 'u' for delta angle 232 | u.x = (-x1 - x2) / rx; 233 | u.y = (-y1 - y2) / ry; 234 | 235 | var deltaAng = Math.acos(v.dot(u) / v.length() / u.length()); 236 | // This normalization ends up making our curves fail to triangulate... 237 | if (v.x * u.y - v.y * u.x < 0) 238 | deltaAng = -deltaAng; 239 | if (!sf && deltaAng > 0) 240 | deltaAng -= Math.PI * 2; 241 | if (sf && deltaAng < 0) 242 | deltaAng += Math.PI * 2; 243 | 244 | path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf); 245 | x = nx; 246 | y = ny; 247 | break; 248 | 249 | case ' ': 250 | // if it's an empty space, just skip it, and see if we can find a real command 251 | break; 252 | 253 | default: 254 | throw new Error("weird path command: " + activeCmd); 255 | } 256 | if (firstX === null && !enteredSub) { 257 | firstX = x; 258 | firstY = y; 259 | } 260 | 261 | // just reissue the command 262 | if (canRepeat && nextIsNum()) 263 | continue; 264 | activeCmd = pathStr[idx++]; 265 | } 266 | 267 | if (zSeen) { 268 | return paths; 269 | } else { 270 | paths.push(path); 271 | return paths; 272 | } 273 | }; 274 | 275 | }(window.THREE)); 276 | -------------------------------------------------------------------------------- /models/tree1.obj: -------------------------------------------------------------------------------- 1 | # This file uses centimeters as units for non-parametric coordinates. 2 | 3 | g default 4 | v -0.267218 1.016066 0.319433 5 | v 0.265039 1.016066 0.319433 6 | v -0.267218 1.202931 0.319433 7 | v 0.265039 1.202931 0.319433 8 | v -0.267218 1.202931 -0.313171 9 | v 0.265039 1.202931 -0.313171 10 | v -0.267218 1.016066 -0.313171 11 | v 0.265039 1.016066 -0.313171 12 | v -2.523151 1.376151 2.514558 13 | v 2.520971 1.376151 2.514558 14 | v 2.520971 1.376151 -2.508296 15 | v -2.523151 1.376151 -2.508296 16 | v -1.768978 4.308054 1.763565 17 | v 1.766799 4.308054 1.763565 18 | v 1.766799 4.308054 -1.757303 19 | v -1.768978 4.308054 -1.757303 20 | v -2.269070 4.424837 2.261548 21 | v 2.266890 4.424837 2.261548 22 | v 2.266890 4.424837 -2.255286 23 | v -2.269070 4.424837 -2.255286 24 | v -1.283486 7.176241 1.280119 25 | v 1.281306 7.176241 1.280119 26 | v 1.281306 7.176241 -1.273858 27 | v -1.283486 7.176241 -1.273858 28 | v -1.667818 7.273163 1.662831 29 | v 1.665638 7.273163 1.662831 30 | v 1.665638 7.273163 -1.656570 31 | v -1.667818 7.273163 -1.656570 32 | v -1.667818 7.273163 1.662831 33 | v 1.665638 7.273163 1.662831 34 | v 1.665638 7.273163 -1.656570 35 | v -1.667818 7.273163 -1.656570 36 | v -0.046813 10.550065 -0.087930 37 | vt 0.375000 0.000000 38 | vt 0.625000 0.000000 39 | vt 0.375000 0.250000 40 | vt 0.625000 0.250000 41 | vt 0.375000 0.500000 42 | vt 0.625000 0.500000 43 | vt 0.375000 0.750000 44 | vt 0.625000 0.750000 45 | vt 0.375000 1.000000 46 | vt 0.625000 1.000000 47 | vt 0.875000 0.000000 48 | vt 0.875000 0.250000 49 | vt 0.125000 0.000000 50 | vt 0.125000 0.250000 51 | vt 0.375000 0.250000 52 | vt 0.625000 0.250000 53 | vt 0.625000 0.500000 54 | vt 0.375000 0.500000 55 | vt 0.375000 0.250000 56 | vt 0.625000 0.250000 57 | vt 0.625000 0.500000 58 | vt 0.375000 0.500000 59 | vt 0.375000 0.250000 60 | vt 0.625000 0.250000 61 | vt 0.625000 0.500000 62 | vt 0.375000 0.500000 63 | vt 0.375000 0.250000 64 | vt 0.625000 0.250000 65 | vt 0.625000 0.500000 66 | vt 0.375000 0.500000 67 | vt 0.375000 0.250000 68 | vt 0.625000 0.250000 69 | vt 0.625000 0.500000 70 | vt 0.375000 0.500000 71 | vt 0.375000 0.250000 72 | vt 0.625000 0.250000 73 | vt 0.625000 0.500000 74 | vt 0.375000 0.500000 75 | vt 0.468750 0.437500 76 | vn 0.000000 0.000000 1.000000 77 | vn 0.000000 0.000000 1.000000 78 | vn 0.000000 -0.996002 0.089331 79 | vn 0.000000 -0.996002 0.089331 80 | vn 0.000000 -0.996002 -0.089331 81 | vn 0.000000 -0.996002 -0.089331 82 | vn 0.000000 0.000000 -1.000000 83 | vn 0.000000 0.000000 -1.000000 84 | vn 0.000000 -1.000000 0.000000 85 | vn 0.000000 -1.000000 0.000000 86 | vn 0.000000 -1.000000 0.000000 87 | vn 0.000000 -1.000000 0.000000 88 | vn 1.000000 0.000000 0.000000 89 | vn 1.000000 0.000000 0.000000 90 | vn 0.088865 -0.996044 0.000000 91 | vn 0.088865 -0.996044 0.000000 92 | vn -1.000000 0.000000 0.000000 93 | vn -1.000000 0.000000 0.000000 94 | vn -0.088865 -0.996044 0.000000 95 | vn -0.088865 -0.996044 0.000000 96 | vn 0.000000 -0.996901 0.078667 97 | vn 0.000000 -0.996901 0.078667 98 | vn 0.076559 -0.997065 0.000000 99 | vn 0.076559 -0.997065 0.000000 100 | vn 0.000000 -0.996901 -0.078667 101 | vn 0.000000 -0.996901 -0.078667 102 | vn -0.076559 -0.997065 0.000000 103 | vn -0.076559 -0.997065 0.000000 104 | vn 0.000000 0.248134 0.968726 105 | vn 0.000000 0.248134 0.968726 106 | vn 0.000000 0.047354 0.998878 107 | vn 0.000000 0.047354 0.998878 108 | vn 0.968473 0.249120 0.000000 109 | vn 0.968473 0.249120 0.000000 110 | vn 0.998857 0.047788 0.000000 111 | vn 0.998857 0.047788 0.000000 112 | vn 0.000000 0.248134 -0.968726 113 | vn 0.000000 0.248134 -0.968726 114 | vn 0.000000 0.047354 -0.998878 115 | vn 0.000000 0.047354 -0.998878 116 | vn -0.968473 0.249120 0.000000 117 | vn -0.968473 0.249120 0.000000 118 | vn -0.998857 0.047788 0.000000 119 | vn -0.998857 0.047788 0.000000 120 | vn 0.000000 -0.973587 0.228318 121 | vn 0.000000 -0.973587 0.228318 122 | vn 0.227405 -0.973800 0.000000 123 | vn 0.227405 -0.973800 0.000000 124 | vn 0.000000 -0.973587 -0.228318 125 | vn 0.000000 -0.973587 -0.228318 126 | vn -0.227405 -0.973800 0.000000 127 | vn -0.227405 -0.973800 0.000000 128 | vn 0.000000 0.335967 0.941874 129 | vn 0.000000 0.335967 0.941874 130 | vn 0.000000 0.202218 0.979340 131 | vn 0.000000 0.202218 0.979340 132 | vn 0.941423 0.337228 0.000000 133 | vn 0.941423 0.337228 0.000000 134 | vn 0.979142 0.203176 0.000000 135 | vn 0.979142 0.203176 0.000000 136 | vn 0.000000 0.335967 -0.941874 137 | vn 0.000000 0.335967 -0.941874 138 | vn 0.000000 0.202218 -0.979341 139 | vn 0.000000 0.202218 -0.979341 140 | vn -0.941423 0.337228 0.000000 141 | vn -0.941423 0.337228 0.000000 142 | vn -0.979142 0.203176 0.000000 143 | vn -0.979142 0.203176 0.000000 144 | vn 0.000000 -0.969396 0.245501 145 | vn 0.000000 -0.969396 0.245501 146 | vn 0.244527 -0.969642 0.000000 147 | vn 0.244527 -0.969643 0.000000 148 | vn 0.000000 -0.969396 -0.245501 149 | vn 0.000000 -0.969396 -0.245501 150 | vn -0.244527 -0.969642 0.000000 151 | vn -0.244527 -0.969642 0.000000 152 | vn 0.000000 0.000000 0.000000 153 | vn 0.000000 0.000000 0.000000 154 | vn 0.000000 0.000000 0.000000 155 | vn 0.000000 0.000000 0.000000 156 | vn 0.000000 0.000000 0.000000 157 | vn 0.000000 0.000000 0.000000 158 | vn 0.000000 0.000000 0.000000 159 | vn 0.000000 0.000000 0.000000 160 | vn 0.000000 0.000000 0.000000 161 | vn 0.000000 0.000000 0.000000 162 | vn 0.000000 0.000000 0.000000 163 | vn 0.000000 0.000000 0.000000 164 | vn 0.000000 0.000000 0.000000 165 | vn 0.000000 0.000000 0.000000 166 | vn 0.000000 0.000000 0.000000 167 | vn 0.000000 0.000000 0.000000 168 | vn 0.000000 0.471234 0.882008 169 | vn 0.000000 0.471234 0.882008 170 | vn 0.000000 0.471234 0.882008 171 | vn 0.886278 0.463153 0.000000 172 | vn 0.886278 0.463153 0.000000 173 | vn 0.886278 0.463153 0.000000 174 | vn 0.000000 0.431775 -0.901981 175 | vn 0.000000 0.431775 -0.901981 176 | vn 0.000000 0.431775 -0.901981 177 | vn -0.896328 0.443392 0.000000 178 | vn -0.896328 0.443392 0.000000 179 | vn -0.896328 0.443392 0.000000 180 | g Tree3 181 | f 1/1/1 2/2/2 4/4/3 3/3/4 182 | f 5/5/5 6/6/6 8/8/7 7/7/8 183 | f 7/7/9 8/8/10 2/10/11 1/9/12 184 | f 2/2/13 8/11/14 6/12/15 4/4/16 185 | f 7/13/17 1/1/18 3/3/19 5/14/20 186 | f 3/3/4 4/4/3 10/16/21 9/15/22 187 | f 4/4/16 6/6/15 11/17/23 10/16/24 188 | f 6/6/6 5/5/5 12/18/25 11/17/26 189 | f 5/5/20 3/3/19 9/15/27 12/18/28 190 | f 9/15/29 10/16/30 14/20/31 13/19/32 191 | f 10/16/33 11/17/34 15/21/35 14/20/36 192 | f 11/17/37 12/18/38 16/22/39 15/21/40 193 | f 12/18/41 9/15/42 13/19/43 16/22/44 194 | f 13/19/32 14/20/31 18/24/45 17/23/46 195 | f 14/20/36 15/21/35 19/25/47 18/24/48 196 | f 15/21/40 16/22/39 20/26/49 19/25/50 197 | f 16/22/44 13/19/43 17/23/51 20/26/52 198 | f 17/23/53 18/24/54 22/28/55 21/27/56 199 | f 18/24/57 19/25/58 23/29/59 22/28/60 200 | f 19/25/61 20/26/62 24/30/63 23/29/64 201 | f 20/26/65 17/23/66 21/27/67 24/30/68 202 | f 21/27/56 22/28/55 26/32/69 25/31/70 203 | f 22/28/60 23/29/59 27/33/71 26/32/72 204 | f 23/29/64 24/30/63 28/34/73 27/33/74 205 | f 24/30/68 21/27/67 25/31/75 28/34/76 206 | f 25/31/77 26/32/78 30/36/79 29/35/80 207 | f 26/32/81 27/33/82 31/37/83 30/36/84 208 | f 27/33/85 28/34/86 32/38/87 31/37/88 209 | f 28/34/89 25/31/90 29/35/91 32/38/92 210 | f 29/35/93 30/36/94 33/39/95 211 | f 30/36/96 31/37/97 33/39/98 212 | f 31/37/99 32/38/100 33/39/101 213 | f 32/38/102 29/35/103 33/39/104 214 | g default 215 | v -0.529927 -0.000000 0.534616 216 | v 0.564866 -0.000000 0.534616 217 | v -0.529927 1.562279 0.534616 218 | v 0.564866 1.562279 0.534616 219 | v -0.529927 1.562279 -0.533972 220 | v 0.564866 1.562279 -0.533972 221 | v -0.529927 -0.000000 -0.533972 222 | v 0.564866 -0.000000 -0.533972 223 | vt 0.375000 0.000000 224 | vt 0.625000 0.000000 225 | vt 0.375000 0.250000 226 | vt 0.625000 0.250000 227 | vt 0.375000 0.500000 228 | vt 0.625000 0.500000 229 | vt 0.375000 0.750000 230 | vt 0.625000 0.750000 231 | vt 0.375000 1.000000 232 | vt 0.625000 1.000000 233 | vt 0.875000 0.000000 234 | vt 0.875000 0.250000 235 | vt 0.125000 0.000000 236 | vt 0.125000 0.250000 237 | vn 0.000000 0.000000 1.000000 238 | vn 0.000000 0.000000 1.000000 239 | vn 0.000000 0.000000 1.000000 240 | vn 0.000000 0.000000 1.000000 241 | vn 0.000000 1.000000 0.000000 242 | vn 0.000000 1.000000 0.000000 243 | vn 0.000000 1.000000 0.000000 244 | vn 0.000000 1.000000 0.000000 245 | vn 0.000000 0.000000 -1.000000 246 | vn 0.000000 0.000000 -1.000000 247 | vn 0.000000 0.000000 -1.000000 248 | vn 0.000000 0.000000 -1.000000 249 | vn 0.000000 -1.000000 0.000000 250 | vn 0.000000 -1.000000 0.000000 251 | vn 0.000000 -1.000000 0.000000 252 | vn 0.000000 -1.000000 0.000000 253 | vn 1.000000 0.000000 0.000000 254 | vn 1.000000 0.000000 0.000000 255 | vn 1.000000 0.000000 0.000000 256 | vn 1.000000 0.000000 0.000000 257 | vn -1.000000 0.000000 0.000000 258 | vn -1.000000 0.000000 0.000000 259 | vn -1.000000 0.000000 0.000000 260 | vn -1.000000 0.000000 0.000000 261 | g Trunk 262 | f 34/40/105 35/41/106 37/43/107 36/42/108 263 | f 36/42/109 37/43/110 39/45/111 38/44/112 264 | f 38/44/113 39/45/114 41/47/115 40/46/116 265 | f 40/46/117 41/47/118 35/49/119 34/48/120 266 | f 35/41/121 41/50/122 39/51/123 37/43/124 267 | f 40/52/125 34/40/126 36/42/127 38/53/128 268 | -------------------------------------------------------------------------------- /lib/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 | }; 10 | 11 | THREE.OBJLoader.prototype = { 12 | 13 | constructor: THREE.OBJLoader, 14 | 15 | load: function ( url, onLoad, onProgress, onError ) { 16 | 17 | var scope = this; 18 | 19 | var loader = new THREE.XHRLoader( scope.manager ); 20 | loader.setCrossOrigin( this.crossOrigin ); 21 | loader.load( url, function ( text ) { 22 | 23 | onLoad( scope.parse( text ) ); 24 | 25 | }, onProgress, onError ); 26 | 27 | }, 28 | 29 | parse: function ( text ) { 30 | 31 | console.time( 'OBJLoader' ); 32 | 33 | var object, objects = []; 34 | var geometry, material; 35 | 36 | function parseVertexIndex( value ) { 37 | 38 | var index = parseInt( value ); 39 | 40 | return ( index >= 0 ? index - 1 : index + vertices.length / 3 ) * 3; 41 | 42 | } 43 | 44 | function parseNormalIndex( value ) { 45 | 46 | var index = parseInt( value ); 47 | 48 | return ( index >= 0 ? index - 1 : index + normals.length / 3 ) * 3; 49 | 50 | } 51 | 52 | function parseUVIndex( value ) { 53 | 54 | var index = parseInt( value ); 55 | 56 | return ( index >= 0 ? index - 1 : index + uvs.length / 2 ) * 2; 57 | 58 | } 59 | 60 | function addVertex( a, b, c ) { 61 | 62 | geometry.vertices.push( 63 | vertices[ a ], vertices[ a + 1 ], vertices[ a + 2 ], 64 | vertices[ b ], vertices[ b + 1 ], vertices[ b + 2 ], 65 | vertices[ c ], vertices[ c + 1 ], vertices[ c + 2 ] 66 | ); 67 | 68 | } 69 | 70 | function addNormal( a, b, c ) { 71 | 72 | geometry.normals.push( 73 | normals[ a ], normals[ a + 1 ], normals[ a + 2 ], 74 | normals[ b ], normals[ b + 1 ], normals[ b + 2 ], 75 | normals[ c ], normals[ c + 1 ], normals[ c + 2 ] 76 | ); 77 | 78 | } 79 | 80 | function addUV( a, b, c ) { 81 | 82 | geometry.uvs.push( 83 | uvs[ a ], uvs[ a + 1 ], 84 | uvs[ b ], uvs[ b + 1 ], 85 | uvs[ c ], uvs[ c + 1 ] 86 | ); 87 | 88 | } 89 | 90 | function addFace( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 91 | 92 | var ia = parseVertexIndex( a ); 93 | var ib = parseVertexIndex( b ); 94 | var ic = parseVertexIndex( c ); 95 | 96 | if ( d === undefined ) { 97 | 98 | addVertex( ia, ib, ic ); 99 | 100 | } else { 101 | 102 | var id = parseVertexIndex( d ); 103 | 104 | addVertex( ia, ib, id ); 105 | addVertex( ib, ic, id ); 106 | 107 | } 108 | 109 | if ( ua !== undefined ) { 110 | 111 | var ia = parseUVIndex( ua ); 112 | var ib = parseUVIndex( ub ); 113 | var ic = parseUVIndex( uc ); 114 | 115 | if ( d === undefined ) { 116 | 117 | addUV( ia, ib, ic ); 118 | 119 | } else { 120 | 121 | var id = parseUVIndex( ud ); 122 | 123 | addUV( ia, ib, id ); 124 | addUV( ib, ic, id ); 125 | 126 | } 127 | 128 | } 129 | 130 | if ( na !== undefined ) { 131 | 132 | var ia = parseNormalIndex( na ); 133 | var ib = parseNormalIndex( nb ); 134 | var ic = parseNormalIndex( nc ); 135 | 136 | if ( d === undefined ) { 137 | 138 | addNormal( ia, ib, ic ); 139 | 140 | } else { 141 | 142 | var id = parseNormalIndex( nd ); 143 | 144 | addNormal( ia, ib, id ); 145 | addNormal( ib, ic, id ); 146 | 147 | } 148 | 149 | } 150 | 151 | } 152 | 153 | // create mesh if no objects in text 154 | 155 | if ( /^o /gm.test( text ) === false ) { 156 | 157 | geometry = { 158 | vertices: [], 159 | normals: [], 160 | uvs: [] 161 | }; 162 | 163 | material = { 164 | name: '' 165 | }; 166 | 167 | object = { 168 | name: '', 169 | geometry: geometry, 170 | material: material 171 | }; 172 | 173 | objects.push( object ); 174 | 175 | } 176 | 177 | var vertices = []; 178 | var normals = []; 179 | var uvs = []; 180 | 181 | // v float float float 182 | 183 | var vertex_pattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; 184 | 185 | // vn float float float 186 | 187 | var normal_pattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; 188 | 189 | // vt float float 190 | 191 | var uv_pattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; 192 | 193 | // f vertex vertex vertex ... 194 | 195 | var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/; 196 | 197 | // f vertex/uv vertex/uv vertex/uv ... 198 | 199 | var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/; 200 | 201 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... 202 | 203 | var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; 204 | 205 | // f vertex//normal vertex//normal vertex//normal ... 206 | 207 | var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/ 208 | 209 | // 210 | 211 | var lines = text.split( '\n' ); 212 | 213 | for ( var i = 0; i < lines.length; i ++ ) { 214 | 215 | var line = lines[ i ]; 216 | line = line.trim(); 217 | 218 | var result; 219 | 220 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 221 | 222 | continue; 223 | 224 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { 225 | 226 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 227 | 228 | vertices.push( 229 | parseFloat( result[ 1 ] ), 230 | parseFloat( result[ 2 ] ), 231 | parseFloat( result[ 3 ] ) 232 | ); 233 | 234 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { 235 | 236 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 237 | 238 | normals.push( 239 | parseFloat( result[ 1 ] ), 240 | parseFloat( result[ 2 ] ), 241 | parseFloat( result[ 3 ] ) 242 | ); 243 | 244 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { 245 | 246 | // ["vt 0.1 0.2", "0.1", "0.2"] 247 | 248 | uvs.push( 249 | parseFloat( result[ 1 ] ), 250 | parseFloat( result[ 2 ] ) 251 | ); 252 | 253 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { 254 | 255 | // ["f 1 2 3", "1", "2", "3", undefined] 256 | 257 | addFace( 258 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 259 | ); 260 | 261 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { 262 | 263 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] 264 | 265 | addFace( 266 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 267 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 268 | ); 269 | 270 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { 271 | 272 | // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] 273 | 274 | addFace( 275 | result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ], 276 | result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ], 277 | result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] 278 | ); 279 | 280 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { 281 | 282 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] 283 | 284 | addFace( 285 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 286 | undefined, undefined, undefined, undefined, 287 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 288 | ); 289 | 290 | } else if ( /^o /.test( line ) ) { 291 | 292 | geometry = { 293 | vertices: [], 294 | normals: [], 295 | uvs: [] 296 | }; 297 | 298 | material = { 299 | name: '' 300 | }; 301 | 302 | object = { 303 | name: line.substring( 2 ).trim(), 304 | geometry: geometry, 305 | material: material 306 | }; 307 | 308 | objects.push( object ) 309 | 310 | } else if ( /^g /.test( line ) ) { 311 | 312 | // group 313 | 314 | } else if ( /^usemtl /.test( line ) ) { 315 | 316 | // material 317 | 318 | material.name = line.substring( 7 ).trim(); 319 | 320 | } else if ( /^mtllib /.test( line ) ) { 321 | 322 | // mtl file 323 | 324 | } else if ( /^s /.test( line ) ) { 325 | 326 | // smooth shading 327 | 328 | } else { 329 | 330 | // console.log( "THREE.OBJLoader: Unhandled line " + line ); 331 | 332 | } 333 | 334 | } 335 | 336 | var container = new THREE.Object3D(); 337 | 338 | for ( var i = 0, l = objects.length; i < l; i ++ ) { 339 | 340 | var object = objects[ i ]; 341 | var geometry = object.geometry; 342 | 343 | var buffergeometry = new THREE.BufferGeometry(); 344 | 345 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 346 | 347 | if ( geometry.normals.length > 0 ) { 348 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 349 | } 350 | 351 | if ( geometry.uvs.length > 0 ) { 352 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 353 | } 354 | 355 | var material = new THREE.MeshLambertMaterial(); 356 | material.name = object.material.name; 357 | 358 | var mesh = new THREE.Mesh( buffergeometry, material ); 359 | mesh.name = object.name; 360 | 361 | container.add( mesh ); 362 | 363 | } 364 | 365 | console.timeEnd( 'OBJLoader' ); 366 | 367 | return container; 368 | 369 | } 370 | 371 | }; 372 | -------------------------------------------------------------------------------- /lib/OBJMTLLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a Wavefront .obj file with materials 3 | * 4 | * @author mrdoob / http://mrdoob.com/ 5 | * @author angelxuanchang 6 | */ 7 | 8 | THREE.OBJMTLLoader = function ( manager ) { 9 | 10 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 11 | 12 | }; 13 | 14 | THREE.OBJMTLLoader.prototype = { 15 | 16 | constructor: THREE.OBJMTLLoader, 17 | 18 | load: function ( url, mtlurl, onLoad, onProgress, onError ) { 19 | 20 | var scope = this; 21 | 22 | var mtlLoader = new THREE.MTLLoader( url.substr( 0, url.lastIndexOf( "/" ) + 1 ) ); 23 | mtlLoader.crossOrigin = scope.crossOrigin; 24 | mtlLoader.load( mtlurl, function ( materials ) { 25 | 26 | var materialsCreator = materials; 27 | materialsCreator.preload(); 28 | 29 | var loader = new THREE.XHRLoader( scope.manager ); 30 | loader.setCrossOrigin( scope.crossOrigin ); 31 | loader.load( url, function ( text ) { 32 | 33 | var object = scope.parse( text ); 34 | 35 | object.traverse( function ( object ) { 36 | 37 | if ( object instanceof THREE.Mesh ) { 38 | 39 | if ( object.material.name ) { 40 | 41 | var material = materialsCreator.create( object.material.name ); 42 | 43 | if ( material ) object.material = material; 44 | 45 | } 46 | 47 | } 48 | 49 | } ); 50 | 51 | onLoad( object ); 52 | 53 | }, onProgress, onError ); 54 | 55 | }, onProgress, onError ); 56 | 57 | }, 58 | 59 | /** 60 | * Parses loaded .obj file 61 | * @param data - content of .obj file 62 | * @param mtllibCallback - callback to handle mtllib declaration (optional) 63 | * @return {THREE.Object3D} - Object3D (with default material) 64 | */ 65 | 66 | parse: function ( data, mtllibCallback ) { 67 | 68 | function vector( x, y, z ) { 69 | 70 | return new THREE.Vector3( x, y, z ); 71 | 72 | } 73 | 74 | function uv( u, v ) { 75 | 76 | return new THREE.Vector2( u, v ); 77 | 78 | } 79 | 80 | function face3( a, b, c, normals ) { 81 | 82 | return new THREE.Face3( a, b, c, normals ); 83 | 84 | } 85 | 86 | var face_offset = 0; 87 | 88 | function meshN( meshName, materialName ) { 89 | 90 | if ( vertices.length > 0 ) { 91 | 92 | geometry.vertices = vertices; 93 | 94 | geometry.mergeVertices(); 95 | geometry.computeFaceNormals(); 96 | geometry.computeBoundingSphere(); 97 | 98 | object.add( mesh ); 99 | 100 | geometry = new THREE.Geometry(); 101 | mesh = new THREE.Mesh( geometry, material ); 102 | 103 | } 104 | 105 | if ( meshName !== undefined ) mesh.name = meshName; 106 | 107 | if ( materialName !== undefined ) { 108 | 109 | material = new THREE.MeshLambertMaterial(); 110 | material.name = materialName; 111 | 112 | mesh.material = material; 113 | 114 | } 115 | 116 | } 117 | 118 | var group = new THREE.Group(); 119 | var object = group; 120 | 121 | var geometry = new THREE.Geometry(); 122 | var material = new THREE.MeshLambertMaterial(); 123 | var mesh = new THREE.Mesh( geometry, material ); 124 | 125 | var vertices = []; 126 | var normals = []; 127 | var uvs = []; 128 | 129 | function add_face( a, b, c, normals_inds ) { 130 | 131 | if ( normals_inds === undefined ) { 132 | 133 | geometry.faces.push( face3( 134 | parseInt( a ) - (face_offset + 1), 135 | parseInt( b ) - (face_offset + 1), 136 | parseInt( c ) - (face_offset + 1) 137 | ) ); 138 | 139 | } else { 140 | 141 | geometry.faces.push( face3( 142 | parseInt( a ) - (face_offset + 1), 143 | parseInt( b ) - (face_offset + 1), 144 | parseInt( c ) - (face_offset + 1), 145 | [ 146 | normals[ parseInt( normals_inds[ 0 ] ) - 1 ].clone(), 147 | normals[ parseInt( normals_inds[ 1 ] ) - 1 ].clone(), 148 | normals[ parseInt( normals_inds[ 2 ] ) - 1 ].clone() 149 | ] 150 | ) ); 151 | 152 | } 153 | 154 | } 155 | 156 | function add_uvs( a, b, c ) { 157 | 158 | geometry.faceVertexUvs[ 0 ].push( [ 159 | uvs[ parseInt( a ) - 1 ].clone(), 160 | uvs[ parseInt( b ) - 1 ].clone(), 161 | uvs[ parseInt( c ) - 1 ].clone() 162 | ] ); 163 | 164 | } 165 | 166 | function handle_face_line(faces, uvs, normals_inds) { 167 | 168 | if ( faces[ 3 ] === undefined ) { 169 | 170 | add_face( faces[ 0 ], faces[ 1 ], faces[ 2 ], normals_inds ); 171 | 172 | if (!(uvs === undefined) && uvs.length > 0) { 173 | add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ); 174 | } 175 | 176 | } else { 177 | 178 | if (!(normals_inds === undefined) && normals_inds.length > 0) { 179 | 180 | add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ], [ normals_inds[ 0 ], normals_inds[ 1 ], normals_inds[ 3 ] ]); 181 | add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ], [ normals_inds[ 1 ], normals_inds[ 2 ], normals_inds[ 3 ] ]); 182 | 183 | } else { 184 | 185 | add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ]); 186 | add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ]); 187 | 188 | } 189 | 190 | if (!(uvs === undefined) && uvs.length > 0) { 191 | 192 | add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ); 193 | add_uvs( uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ); 194 | 195 | } 196 | 197 | } 198 | 199 | } 200 | 201 | 202 | // v float float float 203 | 204 | var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 205 | 206 | // vn float float float 207 | 208 | var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 209 | 210 | // vt float float 211 | 212 | var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 213 | 214 | // f vertex vertex vertex ... 215 | 216 | var face_pattern1 = /f( +\d+)( +\d+)( +\d+)( +\d+)?/; 217 | 218 | // f vertex/uv vertex/uv vertex/uv ... 219 | 220 | var face_pattern2 = /f( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))?/; 221 | 222 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... 223 | 224 | var face_pattern3 = /f( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))?/; 225 | 226 | // f vertex//normal vertex//normal vertex//normal ... 227 | 228 | var face_pattern4 = /f( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))?/ 229 | 230 | // 231 | 232 | var lines = data.split( "\n" ); 233 | 234 | for ( var i = 0; i < lines.length; i ++ ) { 235 | 236 | var line = lines[ i ]; 237 | line = line.trim(); 238 | 239 | var result; 240 | 241 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 242 | 243 | continue; 244 | 245 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { 246 | 247 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 248 | 249 | vertices.push( vector( 250 | parseFloat( result[ 1 ] ), 251 | parseFloat( result[ 2 ] ), 252 | parseFloat( result[ 3 ] ) 253 | ) ); 254 | 255 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { 256 | 257 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 258 | 259 | normals.push( vector( 260 | parseFloat( result[ 1 ] ), 261 | parseFloat( result[ 2 ] ), 262 | parseFloat( result[ 3 ] ) 263 | ) ); 264 | 265 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { 266 | 267 | // ["vt 0.1 0.2", "0.1", "0.2"] 268 | 269 | uvs.push( uv( 270 | parseFloat( result[ 1 ] ), 271 | parseFloat( result[ 2 ] ) 272 | ) ); 273 | 274 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { 275 | 276 | // ["f 1 2 3", "1", "2", "3", undefined] 277 | 278 | handle_face_line([ result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] ]); 279 | 280 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { 281 | 282 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] 283 | 284 | handle_face_line( 285 | [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces 286 | [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //uv 287 | ); 288 | 289 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { 290 | 291 | // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] 292 | 293 | handle_face_line( 294 | [ result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ] ], //faces 295 | [ result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ] ], //uv 296 | [ result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] ] //normal 297 | ); 298 | 299 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { 300 | 301 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] 302 | 303 | handle_face_line( 304 | [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces 305 | [ ], //uv 306 | [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //normal 307 | ); 308 | 309 | } else if ( /^o /.test( line ) ) { 310 | 311 | // object 312 | 313 | meshN(); 314 | face_offset = face_offset + vertices.length; 315 | vertices = []; 316 | object = new THREE.Object3D(); 317 | object.name = line.substring( 2 ).trim(); 318 | group.add( object ); 319 | 320 | } else if ( /^g /.test( line ) ) { 321 | 322 | // group 323 | 324 | meshN( line.substring( 2 ).trim(), undefined ); 325 | 326 | } else if ( /^usemtl /.test( line ) ) { 327 | 328 | // material 329 | 330 | meshN( undefined, line.substring( 7 ).trim() ); 331 | 332 | } else if ( /^mtllib /.test( line ) ) { 333 | 334 | // mtl file 335 | 336 | if ( mtllibCallback ) { 337 | 338 | var mtlfile = line.substring( 7 ); 339 | mtlfile = mtlfile.trim(); 340 | mtllibCallback( mtlfile ); 341 | 342 | } 343 | 344 | } else if ( /^s /.test( line ) ) { 345 | 346 | // Smooth shading 347 | 348 | } else { 349 | 350 | console.log( "THREE.OBJMTLLoader: Unhandled line " + line ); 351 | 352 | } 353 | 354 | } 355 | 356 | //Add last object 357 | meshN(undefined, undefined); 358 | 359 | return group; 360 | 361 | } 362 | 363 | }; 364 | 365 | THREE.EventDispatcher.prototype.apply( THREE.OBJMTLLoader.prototype ); 366 | -------------------------------------------------------------------------------- /models/tree2.obj: -------------------------------------------------------------------------------- 1 | # This file uses centimeters as units for non-parametric coordinates. 2 | 3 | mtllib Tree2.mtl 4 | g default 5 | v -0.060571 0.674952 0.008813 6 | v -1.542523 0.660364 1.557314 7 | v 1.554478 0.660364 1.557314 8 | v 1.554478 0.660364 -1.539688 9 | v -1.542523 0.660364 -1.539688 10 | v -1.542523 0.660364 1.557314 11 | v 1.554478 0.660364 1.557314 12 | v 1.554478 0.660364 -1.539688 13 | v -1.542523 0.660364 -1.539688 14 | v -1.542523 0.660364 1.557314 15 | v 1.554478 0.660364 1.557314 16 | v 1.554478 0.660364 -1.539688 17 | v -1.542523 0.660364 -1.539688 18 | v -1.542523 0.660364 1.557314 19 | v 1.554478 0.660364 1.557314 20 | v 1.554478 0.660364 -1.539688 21 | v -1.542523 0.660364 -1.539688 22 | v -0.902941 2.648854 0.917732 23 | v 0.914896 2.648854 0.917732 24 | v 0.914896 2.648854 -0.900106 25 | v -0.902941 2.648854 -0.900106 26 | v -1.239491 2.543598 1.254281 27 | v 1.251446 2.543598 1.254281 28 | v 1.251446 2.543598 -1.236656 29 | v -1.239491 2.543598 -1.236656 30 | v -1.239491 2.543598 1.254281 31 | v 1.251446 2.543598 1.254281 32 | v 1.251446 2.543598 -1.236656 33 | v -1.239491 2.543598 -1.236656 34 | v -0.595784 4.512259 0.610574 35 | v 0.607739 4.512259 0.610574 36 | v 0.607739 4.512259 -0.592949 37 | v -0.595784 4.512259 -0.592949 38 | v -0.784232 4.465292 0.799023 39 | v 0.796188 4.465292 0.799023 40 | v 0.796188 4.465292 -0.781398 41 | v -0.784232 4.465292 -0.781398 42 | v -0.011988 6.234612 0.007992 43 | vt 0.468750 0.437500 44 | vt 0.375000 0.250000 45 | vt 0.625000 0.250000 46 | vt 0.625000 0.500000 47 | vt 0.375000 0.500000 48 | vt 0.375000 0.250000 49 | vt 0.625000 0.250000 50 | vt 0.625000 0.500000 51 | vt 0.375000 0.500000 52 | vt 0.375000 0.250000 53 | vt 0.625000 0.250000 54 | vt 0.625000 0.500000 55 | vt 0.375000 0.500000 56 | vt 0.375000 0.250000 57 | vt 0.625000 0.250000 58 | vt 0.625000 0.500000 59 | vt 0.375000 0.500000 60 | vt 0.375000 0.250000 61 | vt 0.625000 0.250000 62 | vt 0.625000 0.500000 63 | vt 0.375000 0.500000 64 | vt 0.375000 0.250000 65 | vt 0.625000 0.250000 66 | vt 0.625000 0.500000 67 | vt 0.375000 0.500000 68 | vt 0.375000 0.250000 69 | vt 0.625000 0.250000 70 | vt 0.625000 0.500000 71 | vt 0.375000 0.500000 72 | vt 0.375000 0.250000 73 | vt 0.625000 0.250000 74 | vt 0.625000 0.500000 75 | vt 0.375000 0.500000 76 | vt 0.375000 0.250000 77 | vt 0.625000 0.250000 78 | vt 0.625000 0.500000 79 | vt 0.375000 0.500000 80 | vt 0.468750 0.437500 81 | vn 0.000000 -0.999956 -0.009420 82 | vn 0.000000 -0.999956 -0.009420 83 | vn 0.000000 -0.999956 -0.009420 84 | vn -0.009032 -0.999959 0.000000 85 | vn -0.009032 -0.999959 0.000000 86 | vn -0.009032 -0.999959 0.000000 87 | vn 0.000000 -0.999956 0.009420 88 | vn 0.000000 -0.999956 0.009420 89 | vn 0.000000 -0.999956 0.009420 90 | vn 0.009843 -0.999952 0.000000 91 | vn 0.009843 -0.999952 0.000000 92 | vn 0.009843 -0.999952 0.000000 93 | vn 0.000000 0.000000 0.000000 94 | vn 0.000000 0.000000 0.000000 95 | vn 0.000000 0.000000 0.000000 96 | vn 0.000000 0.000000 0.000000 97 | vn 0.000000 0.000000 0.000000 98 | vn 0.000000 0.000000 0.000000 99 | vn 0.000000 0.000000 0.000000 100 | vn 0.000000 0.000000 0.000000 101 | vn 0.000000 0.000000 0.000000 102 | vn 0.000000 0.000000 0.000000 103 | vn 0.000000 0.000000 0.000000 104 | vn 0.000000 0.000000 0.000000 105 | vn 0.000000 0.000000 0.000000 106 | vn 0.000000 0.000000 0.000000 107 | vn 0.000000 0.000000 0.000000 108 | vn 0.000000 0.000000 0.000000 109 | vn 0.000000 0.000000 0.000000 110 | vn 0.000000 0.000000 0.000000 111 | vn 0.000000 0.000000 0.000000 112 | vn 0.000000 0.000000 0.000000 113 | vn 0.000000 0.000000 0.000000 114 | vn 0.000000 0.000000 0.000000 115 | vn 0.000000 0.000000 0.000000 116 | vn 0.000000 0.000000 0.000000 117 | vn 0.000000 0.000000 0.000000 118 | vn 0.000000 0.000000 0.000000 119 | vn 0.000000 0.000000 0.000000 120 | vn 0.000000 0.000000 0.000000 121 | vn 0.000000 0.000000 0.000000 122 | vn 0.000000 0.000000 0.000000 123 | vn 0.000000 0.000000 0.000000 124 | vn 0.000000 0.000000 0.000000 125 | vn 0.000000 0.000000 0.000000 126 | vn 0.000000 0.000000 0.000000 127 | vn 0.000000 0.000000 0.000000 128 | vn 0.000000 0.000000 0.000000 129 | vn 0.000000 0.000000 0.000000 130 | vn 0.000000 0.000000 0.000000 131 | vn 0.000000 0.000000 0.000000 132 | vn 0.000000 0.000000 0.000000 133 | vn 0.000000 0.000000 0.000000 134 | vn 0.000000 0.000000 0.000000 135 | vn 0.000000 0.000000 0.000000 136 | vn 0.000000 0.000000 0.000000 137 | vn 0.000000 0.000000 0.000000 138 | vn 0.000000 0.000000 0.000000 139 | vn 0.000000 0.000000 0.000000 140 | vn 0.000000 0.000000 0.000000 141 | vn 0.000000 0.306194 0.951969 142 | vn 0.000000 0.306194 0.951969 143 | vn 0.000000 0.306194 0.951969 144 | vn 0.000000 0.306194 0.951969 145 | vn 0.951969 0.306194 0.000000 146 | vn 0.951969 0.306194 0.000000 147 | vn 0.951969 0.306194 0.000000 148 | vn 0.951969 0.306194 0.000000 149 | vn 0.000000 0.306194 -0.951969 150 | vn 0.000000 0.306194 -0.951969 151 | vn 0.000000 0.306194 -0.951969 152 | vn 0.000000 0.306194 -0.951969 153 | vn -0.951969 0.306194 0.000000 154 | vn -0.951969 0.306194 0.000000 155 | vn -0.951969 0.306194 0.000000 156 | vn -0.951969 0.306194 0.000000 157 | vn 0.000000 -0.954412 -0.298491 158 | vn 0.000000 -0.954412 -0.298491 159 | vn 0.000000 -0.954412 -0.298491 160 | vn 0.000000 -0.954412 -0.298491 161 | vn -0.298491 -0.954412 0.000000 162 | vn -0.298491 -0.954412 0.000000 163 | vn -0.298492 -0.954412 0.000000 164 | vn -0.298492 -0.954412 0.000000 165 | vn 0.000000 -0.954412 0.298491 166 | vn 0.000000 -0.954412 0.298491 167 | vn 0.000000 -0.954412 0.298491 168 | vn 0.000000 -0.954412 0.298491 169 | vn 0.298491 -0.954412 0.000000 170 | vn 0.298491 -0.954412 0.000000 171 | vn 0.298492 -0.954412 0.000000 172 | vn 0.298492 -0.954412 0.000000 173 | vn 0.000000 0.000000 0.000000 174 | vn 0.000000 0.000000 0.000000 175 | vn 0.000000 0.000000 0.000000 176 | vn 0.000000 0.000000 0.000000 177 | vn 0.000000 0.000000 0.000000 178 | vn 0.000000 0.000000 0.000000 179 | vn 0.000000 0.000000 0.000000 180 | vn 0.000000 0.000000 0.000000 181 | vn 0.000000 0.000000 0.000000 182 | vn 0.000000 0.000000 0.000000 183 | vn 0.000000 0.000000 0.000000 184 | vn 0.000000 0.000000 0.000000 185 | vn 0.000000 0.000000 0.000000 186 | vn 0.000000 0.000000 0.000000 187 | vn 0.000000 0.000000 0.000000 188 | vn 0.000000 0.000000 0.000000 189 | vn 0.000000 0.310785 0.950480 190 | vn 0.000000 0.310785 0.950480 191 | vn 0.000000 0.235412 0.971896 192 | vn 0.000000 0.235412 0.971896 193 | vn 0.950480 0.310785 0.000000 194 | vn 0.950480 0.310785 0.000000 195 | vn 0.971896 0.235412 0.000000 196 | vn 0.971896 0.235412 0.000000 197 | vn 0.000000 0.310785 -0.950480 198 | vn 0.000000 0.310785 -0.950480 199 | vn 0.000000 0.235412 -0.971896 200 | vn 0.000000 0.235412 -0.971896 201 | vn -0.950480 0.310785 0.000000 202 | vn -0.950480 0.310785 0.000000 203 | vn -0.971896 0.235412 0.000000 204 | vn -0.971896 0.235412 0.000000 205 | vn 0.000000 -0.970317 -0.241837 206 | vn 0.000000 -0.970317 -0.241837 207 | vn -0.241835 -0.970317 0.000000 208 | vn -0.241835 -0.970317 0.000000 209 | vn 0.000000 -0.970317 0.241837 210 | vn 0.000000 -0.970317 0.241837 211 | vn 0.241835 -0.970317 0.000000 212 | vn 0.241835 -0.970317 0.000000 213 | vn 0.000000 0.408148 0.912916 214 | vn 0.000000 0.408148 0.912916 215 | vn 0.000000 0.408148 0.912916 216 | vn 0.909602 0.415481 0.000000 217 | vn 0.909602 0.415481 0.000000 218 | vn 0.909602 0.415481 0.000000 219 | vn 0.000000 0.407442 -0.913231 220 | vn 0.000000 0.407442 -0.913231 221 | vn 0.000000 0.407442 -0.913231 222 | vn -0.916506 0.400022 0.000000 223 | vn -0.916506 0.400022 0.000000 224 | vn -0.916506 0.400022 0.000000 225 | g Treetop 226 | usemtl phong2SG 227 | f 1/1/1 3/3/2 2/2/3 228 | f 1/1/4 4/4/5 3/3/6 229 | f 1/1/7 5/5/8 4/4/9 230 | f 1/1/10 2/2/11 5/5/12 231 | f 2/2/13 3/3/14 7/7/15 6/6/16 232 | f 3/3/17 4/4/18 8/8/19 7/7/20 233 | f 4/4/21 5/5/22 9/9/23 8/8/24 234 | f 5/5/25 2/2/26 6/6/27 9/9/28 235 | f 6/6/29 7/7/30 11/11/31 10/10/32 236 | f 7/7/33 8/8/34 12/12/35 11/11/36 237 | f 8/8/37 9/9/38 13/13/39 12/12/40 238 | f 9/9/41 6/6/42 10/10/43 13/13/44 239 | f 10/10/45 11/11/46 15/15/47 14/14/48 240 | f 11/11/49 12/12/50 16/16/51 15/15/52 241 | f 12/12/53 13/13/54 17/17/55 16/16/56 242 | f 13/13/57 10/10/58 14/14/59 17/17/60 243 | f 14/14/61 15/15/62 19/19/63 18/18/64 244 | f 15/15/65 16/16/66 20/20/67 19/19/68 245 | f 16/16/69 17/17/70 21/21/71 20/20/72 246 | f 17/17/73 14/14/74 18/18/75 21/21/76 247 | f 18/18/77 19/19/78 23/23/79 22/22/80 248 | f 19/19/81 20/20/82 24/24/83 23/23/84 249 | f 20/20/85 21/21/86 25/25/87 24/24/88 250 | f 21/21/89 18/18/90 22/22/91 25/25/92 251 | f 22/22/93 23/23/94 27/27/95 26/26/96 252 | f 23/23/97 24/24/98 28/28/99 27/27/100 253 | f 24/24/101 25/25/102 29/29/103 28/28/104 254 | f 25/25/105 22/22/106 26/26/107 29/29/108 255 | f 26/26/109 27/27/110 31/31/111 30/30/112 256 | f 27/27/113 28/28/114 32/32/115 31/31/116 257 | f 28/28/117 29/29/118 33/33/119 32/32/120 258 | f 29/29/121 26/26/122 30/30/123 33/33/124 259 | f 30/30/112 31/31/111 35/35/125 34/34/126 260 | f 31/31/116 32/32/115 36/36/127 35/35/128 261 | f 32/32/120 33/33/119 37/37/129 36/36/130 262 | f 33/33/124 30/30/123 34/34/131 37/37/132 263 | f 34/34/133 35/35/134 38/38/135 264 | f 35/35/136 36/36/137 38/38/138 265 | f 36/36/139 37/37/140 38/38/141 266 | f 37/37/142 34/34/143 38/38/144 267 | g default 268 | v -0.221618 -0.013483 0.243760 269 | v 0.233828 -0.013483 0.243760 270 | v -0.221618 0.986517 0.243760 271 | v 0.233828 0.986517 0.243760 272 | v -0.221618 0.986517 -0.249541 273 | v 0.233828 0.986517 -0.249541 274 | v -0.221618 -0.013483 -0.249541 275 | v 0.233828 -0.013483 -0.249541 276 | vt 0.375000 0.000000 277 | vt 0.625000 0.000000 278 | vt 0.375000 0.250000 279 | vt 0.625000 0.250000 280 | vt 0.375000 0.500000 281 | vt 0.625000 0.500000 282 | vt 0.375000 0.750000 283 | vt 0.625000 0.750000 284 | vt 0.375000 1.000000 285 | vt 0.625000 1.000000 286 | vt 0.875000 0.000000 287 | vt 0.875000 0.250000 288 | vt 0.125000 0.000000 289 | vt 0.125000 0.250000 290 | vn 0.000000 0.000000 1.000000 291 | vn 0.000000 0.000000 1.000000 292 | vn 0.000000 0.000000 1.000000 293 | vn 0.000000 0.000000 1.000000 294 | vn 0.000000 1.000000 0.000000 295 | vn 0.000000 1.000000 0.000000 296 | vn 0.000000 1.000000 0.000000 297 | vn 0.000000 1.000000 0.000000 298 | vn 0.000000 0.000000 -1.000000 299 | vn 0.000000 0.000000 -1.000000 300 | vn 0.000000 0.000000 -1.000000 301 | vn 0.000000 0.000000 -1.000000 302 | vn 0.000000 -1.000000 0.000000 303 | vn 0.000000 -1.000000 0.000000 304 | vn 0.000000 -1.000000 0.000000 305 | vn 0.000000 -1.000000 0.000000 306 | vn 1.000000 0.000000 0.000000 307 | vn 1.000000 0.000000 0.000000 308 | vn 1.000000 0.000000 0.000000 309 | vn 1.000000 0.000000 0.000000 310 | vn -1.000000 0.000000 0.000000 311 | vn -1.000000 0.000000 0.000000 312 | vn -1.000000 0.000000 0.000000 313 | vn -1.000000 0.000000 0.000000 314 | g trunk 315 | usemtl phong3SG 316 | f 39/39/145 40/40/146 42/42/147 41/41/148 317 | f 41/41/149 42/42/150 44/44/151 43/43/152 318 | f 43/43/153 44/44/154 46/46/155 45/45/156 319 | f 45/45/157 46/46/158 40/48/159 39/47/160 320 | f 40/40/161 46/49/162 44/50/163 42/42/164 321 | f 45/51/165 39/39/166 41/41/167 43/52/168 322 | -------------------------------------------------------------------------------- /examples/ex3-terrain/models/tree.obj: -------------------------------------------------------------------------------- 1 | # This file uses centimeters as units for non-parametric coordinates. 2 | 3 | mtllib tree.mtl 4 | g default 5 | v -0.060571 0.674952 0.008813 6 | v -1.542523 0.660364 1.557314 7 | v 1.554478 0.660364 1.557314 8 | v 1.554478 0.660364 -1.539688 9 | v -1.542523 0.660364 -1.539688 10 | v -1.542523 0.660364 1.557314 11 | v 1.554478 0.660364 1.557314 12 | v 1.554478 0.660364 -1.539688 13 | v -1.542523 0.660364 -1.539688 14 | v -1.542523 0.660364 1.557314 15 | v 1.554478 0.660364 1.557314 16 | v 1.554478 0.660364 -1.539688 17 | v -1.542523 0.660364 -1.539688 18 | v -1.542523 0.660364 1.557314 19 | v 1.554478 0.660364 1.557314 20 | v 1.554478 0.660364 -1.539688 21 | v -1.542523 0.660364 -1.539688 22 | v -0.902941 2.648854 0.917732 23 | v 0.914896 2.648854 0.917732 24 | v 0.914896 2.648854 -0.900106 25 | v -0.902941 2.648854 -0.900106 26 | v -1.239491 2.543598 1.254281 27 | v 1.251446 2.543598 1.254281 28 | v 1.251446 2.543598 -1.236656 29 | v -1.239491 2.543598 -1.236656 30 | v -1.239491 2.543598 1.254281 31 | v 1.251446 2.543598 1.254281 32 | v 1.251446 2.543598 -1.236656 33 | v -1.239491 2.543598 -1.236656 34 | v -0.595784 4.512259 0.610574 35 | v 0.607739 4.512259 0.610574 36 | v 0.607739 4.512259 -0.592949 37 | v -0.595784 4.512259 -0.592949 38 | v -0.784232 4.465292 0.799023 39 | v 0.796188 4.465292 0.799023 40 | v 0.796188 4.465292 -0.781398 41 | v -0.784232 4.465292 -0.781398 42 | v -0.011988 6.234612 0.007992 43 | vt 0.468750 0.437500 44 | vt 0.375000 0.250000 45 | vt 0.625000 0.250000 46 | vt 0.625000 0.500000 47 | vt 0.375000 0.500000 48 | vt 0.375000 0.250000 49 | vt 0.625000 0.250000 50 | vt 0.625000 0.500000 51 | vt 0.375000 0.500000 52 | vt 0.375000 0.250000 53 | vt 0.625000 0.250000 54 | vt 0.625000 0.500000 55 | vt 0.375000 0.500000 56 | vt 0.375000 0.250000 57 | vt 0.625000 0.250000 58 | vt 0.625000 0.500000 59 | vt 0.375000 0.500000 60 | vt 0.375000 0.250000 61 | vt 0.625000 0.250000 62 | vt 0.625000 0.500000 63 | vt 0.375000 0.500000 64 | vt 0.375000 0.250000 65 | vt 0.625000 0.250000 66 | vt 0.625000 0.500000 67 | vt 0.375000 0.500000 68 | vt 0.375000 0.250000 69 | vt 0.625000 0.250000 70 | vt 0.625000 0.500000 71 | vt 0.375000 0.500000 72 | vt 0.375000 0.250000 73 | vt 0.625000 0.250000 74 | vt 0.625000 0.500000 75 | vt 0.375000 0.500000 76 | vt 0.375000 0.250000 77 | vt 0.625000 0.250000 78 | vt 0.625000 0.500000 79 | vt 0.375000 0.500000 80 | vt 0.468750 0.437500 81 | vn 0.000000 -0.999956 -0.009420 82 | vn 0.000000 -0.999956 -0.009420 83 | vn 0.000000 -0.999956 -0.009420 84 | vn -0.009032 -0.999959 0.000000 85 | vn -0.009032 -0.999959 0.000000 86 | vn -0.009032 -0.999959 0.000000 87 | vn 0.000000 -0.999956 0.009420 88 | vn 0.000000 -0.999956 0.009420 89 | vn 0.000000 -0.999956 0.009420 90 | vn 0.009843 -0.999952 0.000000 91 | vn 0.009843 -0.999952 0.000000 92 | vn 0.009843 -0.999952 0.000000 93 | vn 0.000000 0.000000 0.000000 94 | vn 0.000000 0.000000 0.000000 95 | vn 0.000000 0.000000 0.000000 96 | vn 0.000000 0.000000 0.000000 97 | vn 0.000000 0.000000 0.000000 98 | vn 0.000000 0.000000 0.000000 99 | vn 0.000000 0.000000 0.000000 100 | vn 0.000000 0.000000 0.000000 101 | vn 0.000000 0.000000 0.000000 102 | vn 0.000000 0.000000 0.000000 103 | vn 0.000000 0.000000 0.000000 104 | vn 0.000000 0.000000 0.000000 105 | vn 0.000000 0.000000 0.000000 106 | vn 0.000000 0.000000 0.000000 107 | vn 0.000000 0.000000 0.000000 108 | vn 0.000000 0.000000 0.000000 109 | vn 0.000000 0.000000 0.000000 110 | vn 0.000000 0.000000 0.000000 111 | vn 0.000000 0.000000 0.000000 112 | vn 0.000000 0.000000 0.000000 113 | vn 0.000000 0.000000 0.000000 114 | vn 0.000000 0.000000 0.000000 115 | vn 0.000000 0.000000 0.000000 116 | vn 0.000000 0.000000 0.000000 117 | vn 0.000000 0.000000 0.000000 118 | vn 0.000000 0.000000 0.000000 119 | vn 0.000000 0.000000 0.000000 120 | vn 0.000000 0.000000 0.000000 121 | vn 0.000000 0.000000 0.000000 122 | vn 0.000000 0.000000 0.000000 123 | vn 0.000000 0.000000 0.000000 124 | vn 0.000000 0.000000 0.000000 125 | vn 0.000000 0.000000 0.000000 126 | vn 0.000000 0.000000 0.000000 127 | vn 0.000000 0.000000 0.000000 128 | vn 0.000000 0.000000 0.000000 129 | vn 0.000000 0.000000 0.000000 130 | vn 0.000000 0.000000 0.000000 131 | vn 0.000000 0.000000 0.000000 132 | vn 0.000000 0.000000 0.000000 133 | vn 0.000000 0.000000 0.000000 134 | vn 0.000000 0.000000 0.000000 135 | vn 0.000000 0.000000 0.000000 136 | vn 0.000000 0.000000 0.000000 137 | vn 0.000000 0.000000 0.000000 138 | vn 0.000000 0.000000 0.000000 139 | vn 0.000000 0.000000 0.000000 140 | vn 0.000000 0.000000 0.000000 141 | vn 0.000000 0.306194 0.951969 142 | vn 0.000000 0.306194 0.951969 143 | vn 0.000000 0.306194 0.951969 144 | vn 0.000000 0.306194 0.951969 145 | vn 0.951969 0.306194 0.000000 146 | vn 0.951969 0.306194 0.000000 147 | vn 0.951969 0.306194 0.000000 148 | vn 0.951969 0.306194 0.000000 149 | vn 0.000000 0.306194 -0.951969 150 | vn 0.000000 0.306194 -0.951969 151 | vn 0.000000 0.306194 -0.951969 152 | vn 0.000000 0.306194 -0.951969 153 | vn -0.951969 0.306194 0.000000 154 | vn -0.951969 0.306194 0.000000 155 | vn -0.951969 0.306194 0.000000 156 | vn -0.951969 0.306194 0.000000 157 | vn 0.000000 -0.954412 -0.298491 158 | vn 0.000000 -0.954412 -0.298491 159 | vn 0.000000 -0.954412 -0.298491 160 | vn 0.000000 -0.954412 -0.298491 161 | vn -0.298491 -0.954412 0.000000 162 | vn -0.298491 -0.954412 0.000000 163 | vn -0.298492 -0.954412 0.000000 164 | vn -0.298492 -0.954412 0.000000 165 | vn 0.000000 -0.954412 0.298491 166 | vn 0.000000 -0.954412 0.298491 167 | vn 0.000000 -0.954412 0.298491 168 | vn 0.000000 -0.954412 0.298491 169 | vn 0.298491 -0.954412 0.000000 170 | vn 0.298491 -0.954412 0.000000 171 | vn 0.298492 -0.954412 0.000000 172 | vn 0.298492 -0.954412 0.000000 173 | vn 0.000000 0.000000 0.000000 174 | vn 0.000000 0.000000 0.000000 175 | vn 0.000000 0.000000 0.000000 176 | vn 0.000000 0.000000 0.000000 177 | vn 0.000000 0.000000 0.000000 178 | vn 0.000000 0.000000 0.000000 179 | vn 0.000000 0.000000 0.000000 180 | vn 0.000000 0.000000 0.000000 181 | vn 0.000000 0.000000 0.000000 182 | vn 0.000000 0.000000 0.000000 183 | vn 0.000000 0.000000 0.000000 184 | vn 0.000000 0.000000 0.000000 185 | vn 0.000000 0.000000 0.000000 186 | vn 0.000000 0.000000 0.000000 187 | vn 0.000000 0.000000 0.000000 188 | vn 0.000000 0.000000 0.000000 189 | vn 0.000000 0.310785 0.950480 190 | vn 0.000000 0.310785 0.950480 191 | vn 0.000000 0.235412 0.971896 192 | vn 0.000000 0.235412 0.971896 193 | vn 0.950480 0.310785 0.000000 194 | vn 0.950480 0.310785 0.000000 195 | vn 0.971896 0.235412 0.000000 196 | vn 0.971896 0.235412 0.000000 197 | vn 0.000000 0.310785 -0.950480 198 | vn 0.000000 0.310785 -0.950480 199 | vn 0.000000 0.235412 -0.971896 200 | vn 0.000000 0.235412 -0.971896 201 | vn -0.950480 0.310785 0.000000 202 | vn -0.950480 0.310785 0.000000 203 | vn -0.971896 0.235412 0.000000 204 | vn -0.971896 0.235412 0.000000 205 | vn 0.000000 -0.970317 -0.241837 206 | vn 0.000000 -0.970317 -0.241837 207 | vn -0.241835 -0.970317 0.000000 208 | vn -0.241835 -0.970317 0.000000 209 | vn 0.000000 -0.970317 0.241837 210 | vn 0.000000 -0.970317 0.241837 211 | vn 0.241835 -0.970317 0.000000 212 | vn 0.241835 -0.970317 0.000000 213 | vn 0.000000 0.408148 0.912916 214 | vn 0.000000 0.408148 0.912916 215 | vn 0.000000 0.408148 0.912916 216 | vn 0.909602 0.415481 0.000000 217 | vn 0.909602 0.415481 0.000000 218 | vn 0.909602 0.415481 0.000000 219 | vn 0.000000 0.407442 -0.913231 220 | vn 0.000000 0.407442 -0.913231 221 | vn 0.000000 0.407442 -0.913231 222 | vn -0.916506 0.400022 0.000000 223 | vn -0.916506 0.400022 0.000000 224 | vn -0.916506 0.400022 0.000000 225 | g Treetop 226 | usemtl phong2SG 227 | f 1/1/1 3/3/2 2/2/3 228 | f 1/1/4 4/4/5 3/3/6 229 | f 1/1/7 5/5/8 4/4/9 230 | f 1/1/10 2/2/11 5/5/12 231 | f 2/2/13 3/3/14 7/7/15 6/6/16 232 | f 3/3/17 4/4/18 8/8/19 7/7/20 233 | f 4/4/21 5/5/22 9/9/23 8/8/24 234 | f 5/5/25 2/2/26 6/6/27 9/9/28 235 | f 6/6/29 7/7/30 11/11/31 10/10/32 236 | f 7/7/33 8/8/34 12/12/35 11/11/36 237 | f 8/8/37 9/9/38 13/13/39 12/12/40 238 | f 9/9/41 6/6/42 10/10/43 13/13/44 239 | f 10/10/45 11/11/46 15/15/47 14/14/48 240 | f 11/11/49 12/12/50 16/16/51 15/15/52 241 | f 12/12/53 13/13/54 17/17/55 16/16/56 242 | f 13/13/57 10/10/58 14/14/59 17/17/60 243 | f 14/14/61 15/15/62 19/19/63 18/18/64 244 | f 15/15/65 16/16/66 20/20/67 19/19/68 245 | f 16/16/69 17/17/70 21/21/71 20/20/72 246 | f 17/17/73 14/14/74 18/18/75 21/21/76 247 | f 18/18/77 19/19/78 23/23/79 22/22/80 248 | f 19/19/81 20/20/82 24/24/83 23/23/84 249 | f 20/20/85 21/21/86 25/25/87 24/24/88 250 | f 21/21/89 18/18/90 22/22/91 25/25/92 251 | f 22/22/93 23/23/94 27/27/95 26/26/96 252 | f 23/23/97 24/24/98 28/28/99 27/27/100 253 | f 24/24/101 25/25/102 29/29/103 28/28/104 254 | f 25/25/105 22/22/106 26/26/107 29/29/108 255 | f 26/26/109 27/27/110 31/31/111 30/30/112 256 | f 27/27/113 28/28/114 32/32/115 31/31/116 257 | f 28/28/117 29/29/118 33/33/119 32/32/120 258 | f 29/29/121 26/26/122 30/30/123 33/33/124 259 | f 30/30/112 31/31/111 35/35/125 34/34/126 260 | f 31/31/116 32/32/115 36/36/127 35/35/128 261 | f 32/32/120 33/33/119 37/37/129 36/36/130 262 | f 33/33/124 30/30/123 34/34/131 37/37/132 263 | f 34/34/133 35/35/134 38/38/135 264 | f 35/35/136 36/36/137 38/38/138 265 | f 36/36/139 37/37/140 38/38/141 266 | f 37/37/142 34/34/143 38/38/144 267 | g default 268 | v -0.221618 -0.013483 0.243760 269 | v 0.233828 -0.013483 0.243760 270 | v -0.221618 0.986517 0.243760 271 | v 0.233828 0.986517 0.243760 272 | v -0.221618 0.986517 -0.249541 273 | v 0.233828 0.986517 -0.249541 274 | v -0.221618 -0.013483 -0.249541 275 | v 0.233828 -0.013483 -0.249541 276 | vt 0.375000 0.000000 277 | vt 0.625000 0.000000 278 | vt 0.375000 0.250000 279 | vt 0.625000 0.250000 280 | vt 0.375000 0.500000 281 | vt 0.625000 0.500000 282 | vt 0.375000 0.750000 283 | vt 0.625000 0.750000 284 | vt 0.375000 1.000000 285 | vt 0.625000 1.000000 286 | vt 0.875000 0.000000 287 | vt 0.875000 0.250000 288 | vt 0.125000 0.000000 289 | vt 0.125000 0.250000 290 | vn 0.000000 0.000000 1.000000 291 | vn 0.000000 0.000000 1.000000 292 | vn 0.000000 0.000000 1.000000 293 | vn 0.000000 0.000000 1.000000 294 | vn 0.000000 1.000000 0.000000 295 | vn 0.000000 1.000000 0.000000 296 | vn 0.000000 1.000000 0.000000 297 | vn 0.000000 1.000000 0.000000 298 | vn 0.000000 0.000000 -1.000000 299 | vn 0.000000 0.000000 -1.000000 300 | vn 0.000000 0.000000 -1.000000 301 | vn 0.000000 0.000000 -1.000000 302 | vn 0.000000 -1.000000 0.000000 303 | vn 0.000000 -1.000000 0.000000 304 | vn 0.000000 -1.000000 0.000000 305 | vn 0.000000 -1.000000 0.000000 306 | vn 1.000000 0.000000 0.000000 307 | vn 1.000000 0.000000 0.000000 308 | vn 1.000000 0.000000 0.000000 309 | vn 1.000000 0.000000 0.000000 310 | vn -1.000000 0.000000 0.000000 311 | vn -1.000000 0.000000 0.000000 312 | vn -1.000000 0.000000 0.000000 313 | vn -1.000000 0.000000 0.000000 314 | g trunk 315 | usemtl phong3SG 316 | f 39/39/145 40/40/146 42/42/147 41/41/148 317 | f 41/41/149 42/42/150 44/44/151 43/43/152 318 | f 43/43/153 44/44/154 46/46/155 45/45/156 319 | f 45/45/157 46/46/158 40/48/159 39/47/160 320 | f 40/40/161 46/49/162 44/50/163 42/42/164 321 | f 45/51/165 39/39/166 41/41/167 43/52/168 322 | -------------------------------------------------------------------------------- /lib/MTLLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a Wavefront .mtl file specifying materials 3 | * 4 | * @author angelxuanchang 5 | */ 6 | 7 | THREE.MTLLoader = function( baseUrl, options, crossOrigin ) { 8 | 9 | this.baseUrl = baseUrl; 10 | this.options = options; 11 | this.crossOrigin = crossOrigin; 12 | 13 | }; 14 | 15 | THREE.MTLLoader.prototype = { 16 | 17 | constructor: THREE.MTLLoader, 18 | 19 | load: function ( url, onLoad, onProgress, onError ) { 20 | 21 | var scope = this; 22 | 23 | var loader = new THREE.XHRLoader(); 24 | loader.setCrossOrigin( this.crossOrigin ); 25 | loader.load( url, function ( text ) { 26 | 27 | onLoad( scope.parse( text ) ); 28 | 29 | }, onProgress, onError ); 30 | 31 | }, 32 | 33 | /** 34 | * Parses loaded MTL file 35 | * @param text - Content of MTL file 36 | * @return {THREE.MTLLoader.MaterialCreator} 37 | */ 38 | parse: function ( text ) { 39 | 40 | var lines = text.split( "\n" ); 41 | var info = {}; 42 | var delimiter_pattern = /\s+/; 43 | var materialsInfo = {}; 44 | 45 | for ( var i = 0; i < lines.length; i ++ ) { 46 | 47 | var line = lines[ i ]; 48 | line = line.trim(); 49 | 50 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 51 | 52 | // Blank line or comment ignore 53 | continue; 54 | 55 | } 56 | 57 | var pos = line.indexOf( ' ' ); 58 | 59 | var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; 60 | key = key.toLowerCase(); 61 | 62 | var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ""; 63 | value = value.trim(); 64 | 65 | if ( key === "newmtl" ) { 66 | 67 | // New material 68 | 69 | info = { name: value }; 70 | materialsInfo[ value ] = info; 71 | 72 | } else if ( info ) { 73 | 74 | if ( key === "ka" || key === "kd" || key === "ks" ) { 75 | 76 | var ss = value.split( delimiter_pattern, 3 ); 77 | info[ key ] = [ parseFloat( ss[0] ), parseFloat( ss[1] ), parseFloat( ss[2] ) ]; 78 | 79 | } else { 80 | 81 | info[ key ] = value; 82 | 83 | } 84 | 85 | } 86 | 87 | } 88 | 89 | var materialCreator = new THREE.MTLLoader.MaterialCreator( this.baseUrl, this.options ); 90 | materialCreator.crossOrigin = this.crossOrigin 91 | materialCreator.setMaterials( materialsInfo ); 92 | return materialCreator; 93 | 94 | } 95 | 96 | }; 97 | 98 | /** 99 | * Create a new THREE-MTLLoader.MaterialCreator 100 | * @param baseUrl - Url relative to which textures are loaded 101 | * @param options - Set of options on how to construct the materials 102 | * side: Which side to apply the material 103 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide 104 | * wrap: What type of wrapping to apply for textures 105 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping 106 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 107 | * Default: false, assumed to be already normalized 108 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's 109 | * Default: false 110 | * invertTransparency: If transparency need to be inverted (inversion is needed if d = 0 is fully opaque) 111 | * Default: false (d = 1 is fully opaque) 112 | * @constructor 113 | */ 114 | 115 | THREE.MTLLoader.MaterialCreator = function( baseUrl, options ) { 116 | 117 | this.baseUrl = baseUrl; 118 | this.options = options; 119 | this.materialsInfo = {}; 120 | this.materials = {}; 121 | this.materialsArray = []; 122 | this.nameLookup = {}; 123 | 124 | this.side = ( this.options && this.options.side )? this.options.side: THREE.FrontSide; 125 | this.wrap = ( this.options && this.options.wrap )? this.options.wrap: THREE.RepeatWrapping; 126 | 127 | }; 128 | 129 | THREE.MTLLoader.MaterialCreator.prototype = { 130 | 131 | constructor: THREE.MTLLoader.MaterialCreator, 132 | 133 | setMaterials: function( materialsInfo ) { 134 | 135 | this.materialsInfo = this.convert( materialsInfo ); 136 | this.materials = {}; 137 | this.materialsArray = []; 138 | this.nameLookup = {}; 139 | 140 | }, 141 | 142 | convert: function( materialsInfo ) { 143 | 144 | if ( !this.options ) return materialsInfo; 145 | 146 | var converted = {}; 147 | 148 | for ( var mn in materialsInfo ) { 149 | 150 | // Convert materials info into normalized form based on options 151 | 152 | var mat = materialsInfo[ mn ]; 153 | 154 | var covmat = {}; 155 | 156 | converted[ mn ] = covmat; 157 | 158 | for ( var prop in mat ) { 159 | 160 | var save = true; 161 | var value = mat[ prop ]; 162 | var lprop = prop.toLowerCase(); 163 | 164 | switch ( lprop ) { 165 | 166 | case 'kd': 167 | case 'ka': 168 | case 'ks': 169 | 170 | // Diffuse color (color under white light) using RGB values 171 | 172 | if ( this.options && this.options.normalizeRGB ) { 173 | 174 | value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; 175 | 176 | } 177 | 178 | if ( this.options && this.options.ignoreZeroRGBs ) { 179 | 180 | if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 1 ] === 0 ) { 181 | 182 | // ignore 183 | 184 | save = false; 185 | 186 | } 187 | } 188 | 189 | break; 190 | 191 | case 'd': 192 | 193 | // According to MTL format (http://paulbourke.net/dataformats/mtl/): 194 | // d is dissolve for current material 195 | // factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent) 196 | 197 | if ( this.options && this.options.invertTransparency ) { 198 | 199 | value = 1 - value; 200 | 201 | } 202 | 203 | break; 204 | 205 | default: 206 | 207 | break; 208 | } 209 | 210 | if ( save ) { 211 | 212 | covmat[ lprop ] = value; 213 | 214 | } 215 | 216 | } 217 | 218 | } 219 | 220 | return converted; 221 | 222 | }, 223 | 224 | preload: function () { 225 | 226 | for ( var mn in this.materialsInfo ) { 227 | 228 | this.create( mn ); 229 | 230 | } 231 | 232 | }, 233 | 234 | getIndex: function( materialName ) { 235 | 236 | return this.nameLookup[ materialName ]; 237 | 238 | }, 239 | 240 | getAsArray: function() { 241 | 242 | var index = 0; 243 | 244 | for ( var mn in this.materialsInfo ) { 245 | 246 | this.materialsArray[ index ] = this.create( mn ); 247 | this.nameLookup[ mn ] = index; 248 | index ++; 249 | 250 | } 251 | 252 | return this.materialsArray; 253 | 254 | }, 255 | 256 | create: function ( materialName ) { 257 | 258 | if ( this.materials[ materialName ] === undefined ) { 259 | 260 | this.createMaterial_( materialName ); 261 | 262 | } 263 | 264 | return this.materials[ materialName ]; 265 | 266 | }, 267 | 268 | createMaterial_: function ( materialName ) { 269 | 270 | // Create material 271 | 272 | var mat = this.materialsInfo[ materialName ]; 273 | var params = { 274 | 275 | name: materialName, 276 | side: this.side 277 | 278 | }; 279 | 280 | for ( var prop in mat ) { 281 | 282 | var value = mat[ prop ]; 283 | 284 | switch ( prop.toLowerCase() ) { 285 | 286 | // Ns is material specular exponent 287 | 288 | case 'kd': 289 | 290 | // Diffuse color (color under white light) using RGB values 291 | 292 | params[ 'diffuse' ] = new THREE.Color().fromArray( value ); 293 | 294 | break; 295 | 296 | case 'ka': 297 | 298 | // Ambient color (color under shadow) using RGB values 299 | 300 | params[ 'ambient' ] = new THREE.Color().fromArray( value ); 301 | 302 | break; 303 | 304 | case 'ks': 305 | 306 | // Specular color (color when light is reflected from shiny surface) using RGB values 307 | params[ 'specular' ] = new THREE.Color().fromArray( value ); 308 | 309 | break; 310 | 311 | case 'map_kd': 312 | 313 | // Diffuse texture map 314 | 315 | params[ 'map' ] = this.loadTexture( this.baseUrl + value ); 316 | params[ 'map' ].wrapS = this.wrap; 317 | params[ 'map' ].wrapT = this.wrap; 318 | 319 | break; 320 | 321 | case 'ns': 322 | 323 | // The specular exponent (defines the focus of the specular highlight) 324 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. 325 | 326 | params['shininess'] = value; 327 | 328 | break; 329 | 330 | case 'd': 331 | 332 | // According to MTL format (http://paulbourke.net/dataformats/mtl/): 333 | // d is dissolve for current material 334 | // factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent) 335 | 336 | if ( value < 1 ) { 337 | 338 | params['transparent'] = true; 339 | params['opacity'] = value; 340 | 341 | } 342 | 343 | break; 344 | 345 | default: 346 | break; 347 | 348 | } 349 | 350 | } 351 | 352 | if ( params[ 'diffuse' ] ) { 353 | 354 | if ( !params[ 'ambient' ]) params[ 'ambient' ] = params[ 'diffuse' ]; 355 | params[ 'color' ] = params[ 'diffuse' ]; 356 | 357 | } 358 | 359 | this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); 360 | return this.materials[ materialName ]; 361 | 362 | }, 363 | 364 | 365 | loadTexture: function ( url, mapping, onLoad, onError ) { 366 | 367 | var texture; 368 | var loader = THREE.Loader.Handlers.get( url ); 369 | 370 | if ( loader !== null ) { 371 | 372 | texture = loader.load( url, onLoad ); 373 | 374 | } else { 375 | 376 | texture = new THREE.Texture(); 377 | 378 | loader = new THREE.ImageLoader(); 379 | loader.crossOrigin = this.crossOrigin; 380 | loader.load( url, function ( image ) { 381 | 382 | texture.image = THREE.MTLLoader.ensurePowerOfTwo_( image ); 383 | texture.needsUpdate = true; 384 | 385 | if ( onLoad ) onLoad( texture ); 386 | 387 | } ); 388 | 389 | } 390 | 391 | if ( mapping !== undefined ) texture.mapping = mapping; 392 | 393 | return texture; 394 | 395 | } 396 | 397 | }; 398 | 399 | THREE.MTLLoader.ensurePowerOfTwo_ = function ( image ) { 400 | 401 | if ( ! THREE.Math.isPowerOfTwo( image.width ) || ! THREE.Math.isPowerOfTwo( image.height ) ) { 402 | 403 | var canvas = document.createElement( "canvas" ); 404 | canvas.width = THREE.MTLLoader.nextHighestPowerOfTwo_( image.width ); 405 | canvas.height = THREE.MTLLoader.nextHighestPowerOfTwo_( image.height ); 406 | 407 | var ctx = canvas.getContext("2d"); 408 | ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height ); 409 | return canvas; 410 | 411 | } 412 | 413 | return image; 414 | 415 | }; 416 | 417 | THREE.MTLLoader.nextHighestPowerOfTwo_ = function( x ) { 418 | 419 | --x; 420 | 421 | for ( var i = 1; i < 32; i <<= 1 ) { 422 | 423 | x = x | x >> i; 424 | 425 | } 426 | 427 | return x + 1; 428 | 429 | }; 430 | 431 | THREE.EventDispatcher.prototype.apply( THREE.MTLLoader.prototype ); 432 | -------------------------------------------------------------------------------- /examples/ex3-terrain/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebGL Demo 3 | * 4 | * Based on: http://aerotwist.com/tutorials/getting-started-with-three-js/ 5 | */ 6 | 7 | (function (THREE, Stats) { 8 | 9 | var blocker = document.getElementById( 'blocker' ); 10 | var instructions = document.getElementById( 'instructions' ); 11 | 12 | var camera, scene, renderer, 13 | controls, raycaster, terrain, water; 14 | 15 | var canJump = true; 16 | 17 | /* Scene 18 | ********************************/ 19 | 20 | var WIDTH = window.innerWidth, 21 | HEIGHT = window.innerHeight; 22 | 23 | var VIEW_ANGLE = 45, 24 | ASPECT = WIDTH / HEIGHT, 25 | NEAR = 0.1, 26 | FAR = 10000; 27 | 28 | var container = blocker; 29 | 30 | /* PointerLock 31 | ********************************/ 32 | 33 | var havePointerLock = 'pointerLockElement' in document 34 | || 'mozPointerLockElement' in document 35 | || 'webkitPointerLockElement' in document; 36 | 37 | if ( havePointerLock ) { 38 | 39 | var element = document.body; 40 | 41 | var pointerlockchange = function () { 42 | 43 | if ( document.pointerLockElement === element 44 | || document.mozPointerLockElement === element 45 | || document.webkitPointerLockElement === element ) { 46 | 47 | controlsEnabled = true; 48 | controls.enabled = true; 49 | 50 | blocker.style.display = 'none'; 51 | 52 | } else { 53 | 54 | controls.enabled = false; 55 | 56 | blocker.style.display = '-webkit-box'; 57 | blocker.style.display = '-moz-box'; 58 | blocker.style.display = 'box'; 59 | 60 | instructions.style.display = ''; 61 | 62 | } 63 | 64 | }; 65 | 66 | var pointerlockerror = function () { 67 | instructions.style.display = ''; 68 | }; 69 | 70 | // Hook pointer lock state change events 71 | document.addEventListener( 'pointerlockchange', pointerlockchange, false ); 72 | document.addEventListener( 'mozpointerlockchange', pointerlockchange, false ); 73 | document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false ); 74 | 75 | document.addEventListener( 'pointerlockerror', pointerlockerror, false ); 76 | document.addEventListener( 'mozpointerlockerror', pointerlockerror, false ); 77 | document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false ); 78 | 79 | instructions.addEventListener( 'click', function () { 80 | 81 | instructions.style.display = 'none'; 82 | 83 | // Ask the browser to lock the pointer 84 | element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock; 85 | 86 | if ( /Firefox/i.test( navigator.userAgent ) ) { 87 | 88 | var fullscreenchange = function () { 89 | 90 | if ( document.fullscreenElement === element 91 | || document.mozFullscreenElement === element 92 | || document.mozFullScreenElement === element ) { 93 | 94 | document.removeEventListener( 'fullscreenchange', fullscreenchange ); 95 | document.removeEventListener( 'mozfullscreenchange', fullscreenchange ); 96 | 97 | element.requestPointerLock(); 98 | } 99 | 100 | }; 101 | 102 | document.addEventListener( 'fullscreenchange', fullscreenchange, false ); 103 | document.addEventListener( 'mozfullscreenchange', fullscreenchange, false ); 104 | 105 | element.requestFullscreen = element.requestFullscreen 106 | || element.mozRequestFullscreen 107 | || element.mozRequestFullScreen 108 | || element.webkitRequestFullscreen; 109 | 110 | element.requestFullscreen(); 111 | 112 | } else { 113 | 114 | element.requestPointerLock(); 115 | 116 | } 117 | 118 | }, false ); 119 | 120 | } else { 121 | 122 | instructions.innerHTML = 'Your browser doesn\'t seem to support Pointer Lock API'; 123 | 124 | } 125 | 126 | 127 | /* Resources 128 | ********************************/ 129 | 130 | var manager = new THREE.LoadingManager(); 131 | manager.onProgress = function (item, loaded, total) { 132 | console.log(item, loaded, total); 133 | }; 134 | 135 | var loader = new THREE.OBJMTLLoader(manager); 136 | 137 | /* Stats 138 | ********************************/ 139 | 140 | var stats = new Stats(); 141 | stats.setMode(0); // 0: fps, 1: ms 142 | 143 | // align top-left 144 | stats.domElement.style.position = 'absolute'; 145 | stats.domElement.style.left = '0px'; 146 | stats.domElement.style.top = '0px'; 147 | 148 | document.body.appendChild(stats.domElement); 149 | 150 | /* Init 151 | ********************************/ 152 | 153 | init(); 154 | animate(); 155 | 156 | var controlsEnabled = false; 157 | 158 | var moveForward = false; 159 | var moveBackward = false; 160 | var moveLeft = false; 161 | var moveRight = false; 162 | 163 | var prevTime = window.performance.now(); 164 | var velocity = new THREE.Vector3(); 165 | 166 | function init() { 167 | 168 | renderer = new THREE.WebGLRenderer(); 169 | renderer.setSize(WIDTH, HEIGHT); 170 | 171 | camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR); 172 | camera.position.y = 125; 173 | camera.position.z = 125; 174 | 175 | scene = new THREE.Scene(); 176 | scene.fog = new THREE.Fog( 0xffffff, 0, 750 ); 177 | scene.add(camera); 178 | 179 | container.appendChild(renderer.domElement); 180 | 181 | /* Lighting 182 | ********************************/ 183 | 184 | var light = new THREE.HemisphereLight( 0xeeeeff, 0x777788, 0.75 ); 185 | light.position.set( 0.5, 1, 0.75 ); 186 | scene.add( light ); 187 | 188 | controls = new THREE.PointerLockControls( camera ); 189 | scene.add( controls.getObject() ); 190 | 191 | /* Keyboard Events 192 | ********************************/ 193 | 194 | var onKeyDown = function ( event ) { 195 | 196 | switch ( event.keyCode ) { 197 | 198 | case 38: // up 199 | case 87: // w 200 | moveForward = true; 201 | break; 202 | 203 | case 37: // left 204 | case 65: // a 205 | moveLeft = true; break; 206 | 207 | case 40: // down 208 | case 83: // s 209 | moveBackward = true; 210 | break; 211 | 212 | case 39: // right 213 | case 68: // d 214 | moveRight = true; 215 | break; 216 | 217 | case 32: // space 218 | if ( canJump === true ) velocity.y += 350; 219 | canJump = false; 220 | break; 221 | 222 | } 223 | 224 | }; 225 | 226 | var onKeyUp = function ( event ) { 227 | 228 | switch( event.keyCode ) { 229 | 230 | case 38: // up 231 | case 87: // w 232 | moveForward = false; 233 | break; 234 | 235 | case 37: // left 236 | case 65: // a 237 | moveLeft = false; 238 | break; 239 | 240 | case 40: // down 241 | case 83: // s 242 | moveBackward = false; 243 | break; 244 | 245 | case 39: // right 246 | case 68: // d 247 | moveRight = false; 248 | break; 249 | 250 | } 251 | 252 | }; 253 | 254 | document.addEventListener( 'keydown', onKeyDown, false ); 255 | document.addEventListener( 'keyup', onKeyUp, false ); 256 | 257 | raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 ); 258 | 259 | /* Terrain 260 | ********************************/ 261 | 262 | var xSegments = 32, 263 | ySegments = 32; 264 | 265 | terrain = THREE.Terrain({ 266 | easing: THREE.Terrain.Linear, 267 | frequency: 10, 268 | heightmap: THREE.Terrain.DiamondSquare, 269 | material: new THREE.MeshLambertMaterial({color: 0xFAFAFA}), 270 | maxHeight: 150, 271 | minHeight: 0, 272 | steps: 3, 273 | useBufferGeometry: false, 274 | xSegments: xSegments, 275 | xSize: 512, 276 | ySegments: ySegments, 277 | ySize: 512 278 | }); 279 | 280 | scene.add(terrain); 281 | 282 | /* Scenery 283 | ********************************/ 284 | 285 | loader.load('models/tree.obj', 'models/tree.mtl', function (object) { 286 | var trees = THREE.Terrain.ScatterMeshes(terrain.children[0].geometry, { 287 | mesh: object, 288 | w: xSegments, 289 | h: ySegments, 290 | spread: 0.4, 291 | randomness: Math.random 292 | }); 293 | 294 | trees.rotateX(-1 * Math.PI / 2); // lolwut 295 | 296 | scene.add(trees); 297 | }); 298 | 299 | /* Sky 300 | ********************************/ 301 | 302 | THREE.ImageUtils.loadTexture('assets/sky1.jpg', undefined, function(t1) { 303 | var skyDome = new THREE.Mesh( 304 | new THREE.SphereGeometry(8192, 16, 16, 0, Math.PI*2, 0, Math.PI*0.5), 305 | new THREE.MeshBasicMaterial({map: t1, side: THREE.BackSide, fog: false}) 306 | ); 307 | skyDome.position.y = -99; 308 | scene.add(skyDome); 309 | }); 310 | 311 | 312 | /* Water 313 | ********************************/ 314 | 315 | water = new THREE.Mesh( 316 | new THREE.PlaneGeometry(16384+1024, 16384+1024, 16, 16), 317 | new THREE.MeshLambertMaterial({color: 0x006ba0, transparent: true, opacity: 0.6}) 318 | ); 319 | water.position.y = -99; 320 | water.rotation.x = -0.5 * Math.PI; 321 | scene.add(water); 322 | 323 | /* Renderer 324 | ********************************/ 325 | 326 | renderer = new THREE.WebGLRenderer(); 327 | renderer.setClearColor( 0xffffff ); 328 | renderer.setPixelRatio( window.devicePixelRatio ); 329 | renderer.setSize( window.innerWidth, window.innerHeight ); 330 | document.body.appendChild( renderer.domElement ); 331 | 332 | window.addEventListener( 'resize', onWindowResize, false ); 333 | 334 | } 335 | 336 | 337 | /* Resize 338 | ********************************/ 339 | 340 | function onWindowResize() { 341 | 342 | camera.aspect = window.innerWidth / window.innerHeight; 343 | camera.updateProjectionMatrix(); 344 | 345 | renderer.setSize( window.innerWidth, window.innerHeight ); 346 | 347 | } 348 | 349 | 350 | /* Animation 351 | ********************************/ 352 | 353 | function animate() { 354 | 355 | window.requestAnimationFrame( animate ); 356 | 357 | stats.begin(); 358 | 359 | if ( controlsEnabled ) { 360 | raycaster.ray.origin.copy( controls.getObject().position ); 361 | raycaster.ray.origin.y -= 10; 362 | 363 | var intersections = raycaster.intersectObjects( [terrain, water] ); 364 | 365 | var isOnObject = intersections.length > 0; 366 | 367 | var time = window.performance.now(); 368 | var delta = ( time - prevTime ) / 1000; 369 | 370 | velocity.x -= velocity.x * 10.0 * delta; 371 | velocity.z -= velocity.z * 10.0 * delta; 372 | 373 | velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass 374 | 375 | if ( moveForward ) velocity.z -= 400.0 * delta; 376 | if ( moveBackward ) velocity.z += 400.0 * delta; 377 | 378 | if ( moveLeft ) velocity.x -= 400.0 * delta; 379 | if ( moveRight ) velocity.x += 400.0 * delta; 380 | 381 | if ( isOnObject === true ) { 382 | velocity.y = Math.max( 0, velocity.y ); 383 | 384 | canJump = true; 385 | } 386 | 387 | controls.getObject().translateX( velocity.x * delta ); 388 | controls.getObject().translateY( velocity.y * delta ); 389 | controls.getObject().translateZ( velocity.z * delta ); 390 | 391 | if ( controls.getObject().position.y < 10 ) { 392 | 393 | velocity.y = 0; 394 | controls.getObject().position.y = 10; 395 | 396 | canJump = true; 397 | 398 | } 399 | 400 | prevTime = time; 401 | 402 | } 403 | 404 | renderer.render( scene, camera ); 405 | 406 | stats.end(); 407 | 408 | } 409 | 410 | /* Debugging exports 411 | ********************************/ 412 | 413 | }(window.THREE, window.Stats)); 414 | -------------------------------------------------------------------------------- /lib/DeviceOrientationController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ------- 3 | * threeVR (https://github.com/richtr/threeVR) 4 | * ------- 5 | * 6 | * W3C Device Orientation control (http://www.w3.org/TR/orientation-event/) 7 | * with manual user drag (rotate) and pinch (zoom) override handling 8 | * 9 | * Author: Rich Tibbett (http://github.com/richtr) 10 | * License: The MIT License 11 | * 12 | **/ 13 | 14 | var DeviceOrientationController = function ( object, domElement ) { 15 | 16 | this.object = object; 17 | this.element = domElement || document; 18 | 19 | this.freeze = true; 20 | 21 | this.enableManualDrag = true; // enable manual user drag override control by default 22 | this.enableManualZoom = true; // enable manual user zoom override control by default 23 | 24 | this.useQuaternions = true; // use quaternions for orientation calculation by default 25 | 26 | this.deviceOrientation = {}; 27 | this.screenOrientation = window.orientation || 0; 28 | 29 | // Manual rotate override components 30 | var startX = 0, startY = 0, 31 | currentX = 0, currentY = 0, 32 | scrollSpeedX, scrollSpeedY, 33 | tmpQuat = new THREE.Quaternion(); 34 | 35 | // Manual zoom override components 36 | var zoomStart = 1, zoomCurrent = 1, 37 | zoomP1 = new THREE.Vector2(), 38 | zoomP2 = new THREE.Vector2(), 39 | tmpFOV; 40 | 41 | var CONTROLLER_STATE = { 42 | AUTO: 0, 43 | MANUAL_ROTATE: 1, 44 | MANUAL_ZOOM: 2 45 | }; 46 | 47 | var appState = CONTROLLER_STATE.AUTO; 48 | 49 | var CONTROLLER_EVENT = { 50 | CALIBRATE_COMPASS: 'compassneedscalibration', 51 | SCREEN_ORIENTATION: 'orientationchange', 52 | MANUAL_CONTROL: 'userinteraction', // userinteractionstart, userinteractionend 53 | ZOOM_CONTROL: 'zoom', // zoomstart, zoomend 54 | ROTATE_CONTROL: 'rotate', // rotatestart, rotateend 55 | }; 56 | 57 | // Consistent Object Field-Of-View fix components 58 | var startClientHeight = window.innerHeight, 59 | startFOVFrustrumHeight = 2000 * Math.tan( THREE.Math.degToRad( ( this.object.fov || 75 ) / 2 ) ), 60 | relativeFOVFrustrumHeight, relativeVerticalFOV; 61 | 62 | var deviceQuat = new THREE.Quaternion(); 63 | 64 | var fireEvent = function () { 65 | var eventData; 66 | 67 | return function ( name ) { 68 | eventData = arguments || {}; 69 | 70 | eventData.type = name; 71 | eventData.target = this; 72 | 73 | this.dispatchEvent( eventData ); 74 | }.bind( this ); 75 | }.bind( this )(); 76 | 77 | this.constrainObjectFOV = function () { 78 | relativeFOVFrustrumHeight = startFOVFrustrumHeight * ( window.innerHeight / startClientHeight ); 79 | 80 | relativeVerticalFOV = THREE.Math.radToDeg( 2 * Math.atan( relativeFOVFrustrumHeight / 2000 ) ); 81 | 82 | this.object.fov = relativeVerticalFOV; 83 | }.bind( this ); 84 | 85 | this.onDeviceOrientationChange = function ( event ) { 86 | this.deviceOrientation = event; 87 | }.bind( this ); 88 | 89 | this.onScreenOrientationChange = function () { 90 | this.screenOrientation = window.orientation || 0; 91 | 92 | fireEvent( CONTROLLER_EVENT.SCREEN_ORIENTATION ); 93 | }.bind( this ); 94 | 95 | this.onCompassNeedsCalibration = function ( event ) { 96 | event.preventDefault(); 97 | 98 | fireEvent( CONTROLLER_EVENT.CALIBRATE_COMPASS ); 99 | }.bind( this ); 100 | 101 | this.onDocumentMouseDown = function ( event ) { 102 | if ( this.enableManualDrag !== true ) return; 103 | 104 | event.preventDefault(); 105 | 106 | appState = CONTROLLER_STATE.MANUAL_ROTATE; 107 | 108 | this.freeze = true; 109 | 110 | tmpQuat.copy( this.object.quaternion ); 111 | 112 | startX = currentX = event.pageX; 113 | startY = currentY = event.pageY; 114 | 115 | // Set consistent scroll speed based on current viewport width/height 116 | scrollSpeedX = ( 1200 / window.innerWidth ) * 0.2; 117 | scrollSpeedY = ( 800 / window.innerHeight ) * 0.2; 118 | 119 | this.element.addEventListener( 'mousemove', this.onDocumentMouseMove, false ); 120 | this.element.addEventListener( 'mouseup', this.onDocumentMouseUp, false ); 121 | 122 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'start' ); 123 | fireEvent( CONTROLLER_EVENT.ROTATE_CONTROL + 'start' ); 124 | }.bind( this ); 125 | 126 | this.onDocumentMouseMove = function ( event ) { 127 | currentX = event.pageX; 128 | currentY = event.pageY; 129 | }.bind( this ); 130 | 131 | this.onDocumentMouseUp = function ( event ) { 132 | this.element.removeEventListener( 'mousemove', this.onDocumentMouseMove, false ); 133 | this.element.removeEventListener( 'mouseup', this.onDocumentMouseUp, false ); 134 | 135 | appState = CONTROLLER_STATE.AUTO; 136 | 137 | this.freeze = false; 138 | 139 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'end' ); 140 | fireEvent( CONTROLLER_EVENT.ROTATE_CONTROL + 'end' ); 141 | }.bind( this ); 142 | 143 | this.onDocumentTouchStart = function ( event ) { 144 | event.preventDefault(); 145 | event.stopPropagation(); 146 | 147 | switch ( event.touches.length ) { 148 | case 1: // ROTATE 149 | if ( this.enableManualDrag !== true ) return; 150 | 151 | appState = CONTROLLER_STATE.MANUAL_ROTATE; 152 | 153 | this.freeze = true; 154 | 155 | tmpQuat.copy( this.object.quaternion ); 156 | 157 | startX = currentX = event.touches[ 0 ].pageX; 158 | startY = currentY = event.touches[ 0 ].pageY; 159 | 160 | // Set consistent scroll speed based on current viewport width/height 161 | scrollSpeedX = ( 1200 / window.innerWidth ) * 0.1; 162 | scrollSpeedY = ( 800 / window.innerHeight ) * 0.1; 163 | 164 | this.element.addEventListener( 'touchmove', this.onDocumentTouchMove, false ); 165 | this.element.addEventListener( 'touchend', this.onDocumentTouchEnd, false ); 166 | 167 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'start' ); 168 | fireEvent( CONTROLLER_EVENT.ROTATE_CONTROL + 'start' ); 169 | 170 | break; 171 | 172 | case 2: // ZOOM 173 | if ( this.enableManualZoom !== true ) return; 174 | 175 | appState = CONTROLLER_STATE.MANUAL_ZOOM; 176 | 177 | this.freeze = true; 178 | 179 | tmpFOV = this.object.fov; 180 | 181 | zoomP1.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 182 | zoomP2.set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY ); 183 | 184 | zoomStart = zoomCurrent = zoomP1.distanceTo( zoomP2 ); 185 | 186 | this.element.addEventListener( 'touchmove', this.onDocumentTouchMove, false ); 187 | this.element.addEventListener( 'touchend', this.onDocumentTouchEnd, false ); 188 | 189 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'start' ); 190 | fireEvent( CONTROLLER_EVENT.ZOOM_CONTROL + 'start' ); 191 | 192 | break; 193 | } 194 | }.bind( this ); 195 | 196 | this.onDocumentTouchMove = function ( event ) { 197 | switch( event.touches.length ) { 198 | case 1: 199 | currentX = event.touches[ 0 ].pageX; 200 | currentY = event.touches[ 0 ].pageY; 201 | break; 202 | 203 | case 2: 204 | zoomP1.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 205 | zoomP2.set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY ); 206 | break; 207 | } 208 | }.bind( this ); 209 | 210 | this.onDocumentTouchEnd = function ( event ) { 211 | this.element.removeEventListener( 'touchmove', this.onDocumentTouchMove, false ); 212 | this.element.removeEventListener( 'touchend', this.onDocumentTouchEnd, false ); 213 | 214 | if ( appState === CONTROLLER_STATE.MANUAL_ROTATE ) { 215 | 216 | appState = CONTROLLER_STATE.AUTO; // reset control state 217 | 218 | this.freeze = false; 219 | 220 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'end' ); 221 | fireEvent( CONTROLLER_EVENT.ROTATE_CONTROL + 'end' ); 222 | 223 | } else if ( appState === CONTROLLER_STATE.MANUAL_ZOOM ) { 224 | 225 | this.constrainObjectFOV(); // re-instate original object FOV 226 | 227 | appState = CONTROLLER_STATE.AUTO; // reset control state 228 | 229 | this.freeze = false; 230 | 231 | fireEvent( CONTROLLER_EVENT.MANUAL_CONTROL + 'end' ); 232 | fireEvent( CONTROLLER_EVENT.ZOOM_CONTROL + 'end' ); 233 | 234 | } 235 | }.bind( this ); 236 | 237 | var createQuaternion = function () { 238 | 239 | var finalQuaternion = new THREE.Quaternion(); 240 | 241 | var deviceEuler = new THREE.Euler(); 242 | 243 | var screenTransform = new THREE.Quaternion(); 244 | 245 | var worldTransform = new THREE.Quaternion( - Math.sqrt(0.5), 0, 0, Math.sqrt(0.5) ); // - PI/2 around the x-axis 246 | 247 | var minusHalfAngle = 0; 248 | 249 | return function ( alpha, beta, gamma, screenOrientation ) { 250 | 251 | deviceEuler.set( beta, alpha, - gamma, 'YXZ' ); 252 | 253 | finalQuaternion.setFromEuler( deviceEuler ); 254 | 255 | minusHalfAngle = - screenOrientation / 2; 256 | 257 | screenTransform.set( 0, Math.sin( minusHalfAngle ), 0, Math.cos( minusHalfAngle ) ); 258 | 259 | finalQuaternion.multiply( screenTransform ); 260 | 261 | finalQuaternion.multiply( worldTransform ); 262 | 263 | return finalQuaternion; 264 | 265 | } 266 | 267 | }(); 268 | 269 | var createRotationMatrix = function () { 270 | 271 | var finalMatrix = new THREE.Matrix4(); 272 | 273 | var deviceEuler = new THREE.Euler(); 274 | var screenEuler = new THREE.Euler(); 275 | var worldEuler = new THREE.Euler( - Math.PI / 2, 0, 0, 'YXZ' ); // - PI/2 around the x-axis 276 | 277 | var screenTransform = new THREE.Matrix4(); 278 | 279 | var worldTransform = new THREE.Matrix4(); 280 | worldTransform.makeRotationFromEuler(worldEuler); 281 | 282 | return function (alpha, beta, gamma, screenOrientation) { 283 | 284 | deviceEuler.set( beta, alpha, - gamma, 'YXZ' ); 285 | 286 | finalMatrix.identity(); 287 | 288 | finalMatrix.makeRotationFromEuler( deviceEuler ); 289 | 290 | screenEuler.set( 0, - screenOrientation, 0, 'YXZ' ); 291 | 292 | screenTransform.identity(); 293 | 294 | screenTransform.makeRotationFromEuler( screenEuler ); 295 | 296 | finalMatrix.multiply( screenTransform ); 297 | 298 | finalMatrix.multiply( worldTransform ); 299 | 300 | return finalMatrix; 301 | 302 | } 303 | 304 | }(); 305 | 306 | this.updateManualMove = function () { 307 | 308 | var lat, lon; 309 | var phi, theta; 310 | 311 | var rotation = new THREE.Euler( 0, 0, 0, 'YXZ' ); 312 | 313 | var rotQuat = new THREE.Quaternion(); 314 | var objQuat = new THREE.Quaternion(); 315 | 316 | var tmpZ, objZ, realZ; 317 | 318 | var zoomFactor, minZoomFactor = 1; // maxZoomFactor = Infinity 319 | 320 | return function () { 321 | 322 | objQuat.copy( tmpQuat ); 323 | 324 | if ( appState === CONTROLLER_STATE.MANUAL_ROTATE ) { 325 | 326 | lat = ( startY - currentY ) * scrollSpeedY; 327 | lon = ( startX - currentX ) * scrollSpeedX; 328 | 329 | phi = THREE.Math.degToRad( lat ); 330 | theta = THREE.Math.degToRad( lon ); 331 | 332 | rotQuat.set( 0, Math.sin( theta / 2 ), 0, Math.cos( theta / 2 ) ); 333 | 334 | objQuat.multiply( rotQuat ); 335 | 336 | rotQuat.set( Math.sin( phi / 2 ), 0, 0, Math.cos( phi / 2 ) ); 337 | 338 | objQuat.multiply( rotQuat ); 339 | 340 | // Remove introduced z-axis rotation and add device's current z-axis rotation 341 | 342 | tmpZ = rotation.setFromQuaternion( tmpQuat, 'YXZ' ).z; 343 | objZ = rotation.setFromQuaternion( objQuat, 'YXZ' ).z; 344 | realZ = rotation.setFromQuaternion( deviceQuat || tmpQuat, 'YXZ' ).z; 345 | 346 | rotQuat.set( 0, 0, Math.sin( ( realZ - tmpZ ) / 2 ), Math.cos( ( realZ - tmpZ ) / 2 ) ); 347 | 348 | tmpQuat.multiply( rotQuat ); 349 | 350 | rotQuat.set( 0, 0, Math.sin( ( realZ - objZ ) / 2 ), Math.cos( ( realZ - objZ ) / 2 ) ); 351 | 352 | objQuat.multiply( rotQuat ); 353 | 354 | this.object.quaternion.copy( objQuat ); 355 | 356 | } else if ( appState === CONTROLLER_STATE.MANUAL_ZOOM ) { 357 | 358 | zoomCurrent = zoomP1.distanceTo( zoomP2 ); 359 | 360 | zoomFactor = zoomStart / zoomCurrent; 361 | 362 | if ( zoomFactor <= minZoomFactor ) { 363 | 364 | this.object.fov = tmpFOV * zoomFactor; 365 | 366 | this.object.updateProjectionMatrix(); 367 | 368 | } 369 | 370 | // Add device's current z-axis rotation 371 | 372 | if ( deviceQuat ) { 373 | 374 | tmpZ = rotation.setFromQuaternion( tmpQuat, 'YXZ' ).z; 375 | realZ = rotation.setFromQuaternion( deviceQuat, 'YXZ' ).z; 376 | 377 | rotQuat.set( 0, 0, Math.sin( ( realZ - tmpZ ) / 2 ), Math.cos( ( realZ - tmpZ ) / 2 ) ); 378 | 379 | tmpQuat.multiply( rotQuat ); 380 | 381 | this.object.quaternion.copy( tmpQuat ); 382 | 383 | } 384 | 385 | } 386 | 387 | }; 388 | 389 | }(); 390 | 391 | this.updateDeviceMove = function () { 392 | 393 | var alpha, beta, gamma, orient; 394 | 395 | var deviceMatrix; 396 | 397 | return function () { 398 | 399 | alpha = THREE.Math.degToRad( this.deviceOrientation.alpha || 0 ); // Z 400 | beta = THREE.Math.degToRad( this.deviceOrientation.beta || 0 ); // X' 401 | gamma = THREE.Math.degToRad( this.deviceOrientation.gamma || 0 ); // Y'' 402 | orient = THREE.Math.degToRad( this.screenOrientation || 0 ); // O 403 | 404 | // only process non-zero 3-axis data 405 | if ( alpha !== 0 && beta !== 0 && gamma !== 0) { 406 | 407 | if ( this.useQuaternions ) { 408 | 409 | deviceQuat = createQuaternion( alpha, beta, gamma, orient ); 410 | 411 | } else { 412 | 413 | deviceMatrix = createRotationMatrix( alpha, beta, gamma, orient ); 414 | 415 | deviceQuat.setFromRotationMatrix( deviceMatrix ); 416 | 417 | } 418 | 419 | if ( this.freeze ) return; 420 | 421 | //this.object.quaternion.slerp( deviceQuat, 0.07 ); // smoothing 422 | this.object.quaternion.copy( deviceQuat ); 423 | 424 | } 425 | 426 | }; 427 | 428 | }(); 429 | 430 | this.update = function () { 431 | this.updateDeviceMove(); 432 | 433 | if ( appState !== CONTROLLER_STATE.AUTO ) { 434 | this.updateManualMove(); 435 | } 436 | }; 437 | 438 | this.connect = function () { 439 | window.addEventListener( 'resize', this.constrainObjectFOV, false ); 440 | 441 | window.addEventListener( 'orientationchange', this.onScreenOrientationChange, false ); 442 | window.addEventListener( 'deviceorientation', this.onDeviceOrientationChange, false ); 443 | 444 | window.addEventListener( 'compassneedscalibration', this.onCompassNeedsCalibration, false ); 445 | 446 | this.element.addEventListener( 'mousedown', this.onDocumentMouseDown, false ); 447 | this.element.addEventListener( 'touchstart', this.onDocumentTouchStart, false ); 448 | 449 | this.freeze = false; 450 | }; 451 | 452 | this.disconnect = function () { 453 | this.freeze = true; 454 | 455 | window.removeEventListener( 'resize', this.constrainObjectFOV, false ); 456 | 457 | window.removeEventListener( 'orientationchange', this.onScreenOrientationChange, false ); 458 | window.removeEventListener( 'deviceorientation', this.onDeviceOrientationChange, false ); 459 | 460 | window.removeEventListener( 'compassneedscalibration', this.onCompassNeedsCalibration, false ); 461 | 462 | this.element.removeEventListener( 'mousedown', this.onDocumentMouseDown, false ); 463 | this.element.removeEventListener( 'touchstart', this.onDocumentTouchStart, false ); 464 | }; 465 | 466 | }; 467 | 468 | DeviceOrientationController.prototype = Object.create( THREE.EventDispatcher.prototype ); 469 | -------------------------------------------------------------------------------- /lib/TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | * @author Mark Lundin / http://mark-lundin.com 4 | */ 5 | 6 | THREE.TrackballControls = function ( object, domElement ) { 7 | 8 | var _this = this; 9 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; 10 | 11 | this.object = object; 12 | this.domElement = ( domElement !== undefined ) ? domElement : document; 13 | 14 | // API 15 | 16 | this.enabled = true; 17 | 18 | this.screen = { left: 0, top: 0, width: 0, height: 0 }; 19 | 20 | this.rotateSpeed = 1.0; 21 | this.zoomSpeed = 1.2; 22 | this.panSpeed = 0.3; 23 | 24 | this.noRotate = false; 25 | this.noZoom = false; 26 | this.noPan = false; 27 | this.noRoll = false; 28 | 29 | this.staticMoving = false; 30 | this.dynamicDampingFactor = 0.2; 31 | 32 | this.minDistance = 0; 33 | this.maxDistance = Infinity; 34 | 35 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 36 | 37 | // internals 38 | 39 | this.target = new THREE.Vector3(); 40 | 41 | var EPS = 0.000001; 42 | 43 | var lastPosition = new THREE.Vector3(); 44 | 45 | var _state = STATE.NONE, 46 | _prevState = STATE.NONE, 47 | 48 | _eye = new THREE.Vector3(), 49 | 50 | _rotateStart = new THREE.Vector3(), 51 | _rotateEnd = new THREE.Vector3(), 52 | 53 | _zoomStart = new THREE.Vector2(), 54 | _zoomEnd = new THREE.Vector2(), 55 | 56 | _touchZoomDistanceStart = 0, 57 | _touchZoomDistanceEnd = 0, 58 | 59 | _panStart = new THREE.Vector2(), 60 | _panEnd = new THREE.Vector2(); 61 | 62 | // for reset 63 | 64 | this.target0 = this.target.clone(); 65 | this.position0 = this.object.position.clone(); 66 | this.up0 = this.object.up.clone(); 67 | 68 | // events 69 | 70 | var changeEvent = { type: 'change' }; 71 | var startEvent = { type: 'start'}; 72 | var endEvent = { type: 'end'}; 73 | 74 | 75 | // methods 76 | 77 | this.handleResize = function () { 78 | 79 | if ( this.domElement === document ) { 80 | 81 | this.screen.left = 0; 82 | this.screen.top = 0; 83 | this.screen.width = window.innerWidth; 84 | this.screen.height = window.innerHeight; 85 | 86 | } else { 87 | 88 | var box = this.domElement.getBoundingClientRect(); 89 | // adjustments come from similar code in the jquery offset() function 90 | var d = this.domElement.ownerDocument.documentElement; 91 | this.screen.left = box.left + window.pageXOffset - d.clientLeft; 92 | this.screen.top = box.top + window.pageYOffset - d.clientTop; 93 | this.screen.width = box.width; 94 | this.screen.height = box.height; 95 | 96 | } 97 | 98 | }; 99 | 100 | this.handleEvent = function ( event ) { 101 | 102 | if ( typeof this[ event.type ] == 'function' ) { 103 | 104 | this[ event.type ]( event ); 105 | 106 | } 107 | 108 | }; 109 | 110 | var getMouseOnScreen = ( function () { 111 | 112 | var vector = new THREE.Vector2(); 113 | 114 | return function ( pageX, pageY ) { 115 | 116 | vector.set( 117 | ( pageX - _this.screen.left ) / _this.screen.width, 118 | ( pageY - _this.screen.top ) / _this.screen.height 119 | ); 120 | 121 | return vector; 122 | 123 | }; 124 | 125 | }() ); 126 | 127 | var getMouseProjectionOnBall = ( function () { 128 | 129 | var vector = new THREE.Vector3(); 130 | var objectUp = new THREE.Vector3(); 131 | var mouseOnBall = new THREE.Vector3(); 132 | 133 | return function ( pageX, pageY ) { 134 | 135 | mouseOnBall.set( 136 | ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5), 137 | ( _this.screen.height * 0.5 + _this.screen.top - pageY ) / (_this.screen.height*.5), 138 | 0.0 139 | ); 140 | 141 | var length = mouseOnBall.length(); 142 | 143 | if ( _this.noRoll ) { 144 | 145 | if ( length < Math.SQRT1_2 ) { 146 | 147 | mouseOnBall.z = Math.sqrt( 1.0 - length*length ); 148 | 149 | } else { 150 | 151 | mouseOnBall.z = .5 / length; 152 | 153 | } 154 | 155 | } else if ( length > 1.0 ) { 156 | 157 | mouseOnBall.normalize(); 158 | 159 | } else { 160 | 161 | mouseOnBall.z = Math.sqrt( 1.0 - length * length ); 162 | 163 | } 164 | 165 | _eye.copy( _this.object.position ).sub( _this.target ); 166 | 167 | vector.copy( _this.object.up ).setLength( mouseOnBall.y ) 168 | vector.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) ); 169 | vector.add( _eye.setLength( mouseOnBall.z ) ); 170 | 171 | return vector; 172 | 173 | }; 174 | 175 | }() ); 176 | 177 | this.rotateCamera = (function(){ 178 | 179 | var axis = new THREE.Vector3(), 180 | quaternion = new THREE.Quaternion(); 181 | 182 | 183 | return function () { 184 | 185 | var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() ); 186 | 187 | if ( angle ) { 188 | 189 | axis.crossVectors( _rotateStart, _rotateEnd ).normalize(); 190 | 191 | angle *= _this.rotateSpeed; 192 | 193 | quaternion.setFromAxisAngle( axis, -angle ); 194 | 195 | _eye.applyQuaternion( quaternion ); 196 | _this.object.up.applyQuaternion( quaternion ); 197 | 198 | _rotateEnd.applyQuaternion( quaternion ); 199 | 200 | if ( _this.staticMoving ) { 201 | 202 | _rotateStart.copy( _rotateEnd ); 203 | 204 | } else { 205 | 206 | quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) ); 207 | _rotateStart.applyQuaternion( quaternion ); 208 | 209 | } 210 | 211 | } 212 | } 213 | 214 | }()); 215 | 216 | this.zoomCamera = function () { 217 | 218 | if ( _state === STATE.TOUCH_ZOOM_PAN ) { 219 | 220 | var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 221 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 222 | _eye.multiplyScalar( factor ); 223 | 224 | } else { 225 | 226 | var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 227 | 228 | if ( factor !== 1.0 && factor > 0.0 ) { 229 | 230 | _eye.multiplyScalar( factor ); 231 | 232 | if ( _this.staticMoving ) { 233 | 234 | _zoomStart.copy( _zoomEnd ); 235 | 236 | } else { 237 | 238 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 239 | 240 | } 241 | 242 | } 243 | 244 | } 245 | 246 | }; 247 | 248 | this.panCamera = (function(){ 249 | 250 | var mouseChange = new THREE.Vector2(), 251 | objectUp = new THREE.Vector3(), 252 | pan = new THREE.Vector3(); 253 | 254 | return function () { 255 | 256 | mouseChange.copy( _panEnd ).sub( _panStart ); 257 | 258 | if ( mouseChange.lengthSq() ) { 259 | 260 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 261 | 262 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); 263 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); 264 | 265 | _this.object.position.add( pan ); 266 | _this.target.add( pan ); 267 | 268 | if ( _this.staticMoving ) { 269 | 270 | _panStart.copy( _panEnd ); 271 | 272 | } else { 273 | 274 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 275 | 276 | } 277 | 278 | } 279 | } 280 | 281 | }()); 282 | 283 | this.checkDistances = function () { 284 | 285 | if ( !_this.noZoom || !_this.noPan ) { 286 | 287 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) { 288 | 289 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) ); 290 | 291 | } 292 | 293 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 294 | 295 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 296 | 297 | } 298 | 299 | } 300 | 301 | }; 302 | 303 | this.update = function () { 304 | 305 | _eye.subVectors( _this.object.position, _this.target ); 306 | 307 | if ( !_this.noRotate ) { 308 | 309 | _this.rotateCamera(); 310 | 311 | } 312 | 313 | if ( !_this.noZoom ) { 314 | 315 | _this.zoomCamera(); 316 | 317 | } 318 | 319 | if ( !_this.noPan ) { 320 | 321 | _this.panCamera(); 322 | 323 | } 324 | 325 | _this.object.position.addVectors( _this.target, _eye ); 326 | 327 | _this.checkDistances(); 328 | 329 | _this.object.lookAt( _this.target ); 330 | 331 | if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) { 332 | 333 | _this.dispatchEvent( changeEvent ); 334 | 335 | lastPosition.copy( _this.object.position ); 336 | 337 | } 338 | 339 | }; 340 | 341 | this.reset = function () { 342 | 343 | _state = STATE.NONE; 344 | _prevState = STATE.NONE; 345 | 346 | _this.target.copy( _this.target0 ); 347 | _this.object.position.copy( _this.position0 ); 348 | _this.object.up.copy( _this.up0 ); 349 | 350 | _eye.subVectors( _this.object.position, _this.target ); 351 | 352 | _this.object.lookAt( _this.target ); 353 | 354 | _this.dispatchEvent( changeEvent ); 355 | 356 | lastPosition.copy( _this.object.position ); 357 | 358 | }; 359 | 360 | // listeners 361 | 362 | function keydown( event ) { 363 | 364 | if ( _this.enabled === false ) return; 365 | 366 | window.removeEventListener( 'keydown', keydown ); 367 | 368 | _prevState = _state; 369 | 370 | if ( _state !== STATE.NONE ) { 371 | 372 | return; 373 | 374 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) { 375 | 376 | _state = STATE.ROTATE; 377 | 378 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) { 379 | 380 | _state = STATE.ZOOM; 381 | 382 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) { 383 | 384 | _state = STATE.PAN; 385 | 386 | } 387 | 388 | } 389 | 390 | function keyup( event ) { 391 | 392 | if ( _this.enabled === false ) return; 393 | 394 | _state = _prevState; 395 | 396 | window.addEventListener( 'keydown', keydown, false ); 397 | 398 | } 399 | 400 | function mousedown( event ) { 401 | 402 | if ( _this.enabled === false ) return; 403 | 404 | event.preventDefault(); 405 | event.stopPropagation(); 406 | 407 | if ( _state === STATE.NONE ) { 408 | 409 | _state = event.button; 410 | 411 | } 412 | 413 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 414 | 415 | _rotateStart.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) ); 416 | _rotateEnd.copy( _rotateStart ); 417 | 418 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 419 | 420 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 421 | _zoomEnd.copy(_zoomStart); 422 | 423 | } else if ( _state === STATE.PAN && !_this.noPan ) { 424 | 425 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 426 | _panEnd.copy(_panStart) 427 | 428 | } 429 | 430 | document.addEventListener( 'mousemove', mousemove, false ); 431 | document.addEventListener( 'mouseup', mouseup, false ); 432 | 433 | _this.dispatchEvent( startEvent ); 434 | 435 | } 436 | 437 | function mousemove( event ) { 438 | 439 | if ( _this.enabled === false ) return; 440 | 441 | event.preventDefault(); 442 | event.stopPropagation(); 443 | 444 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 445 | 446 | _rotateEnd.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) ); 447 | 448 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 449 | 450 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 451 | 452 | } else if ( _state === STATE.PAN && !_this.noPan ) { 453 | 454 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 455 | 456 | } 457 | 458 | } 459 | 460 | function mouseup( event ) { 461 | 462 | if ( _this.enabled === false ) return; 463 | 464 | event.preventDefault(); 465 | event.stopPropagation(); 466 | 467 | _state = STATE.NONE; 468 | 469 | document.removeEventListener( 'mousemove', mousemove ); 470 | document.removeEventListener( 'mouseup', mouseup ); 471 | _this.dispatchEvent( endEvent ); 472 | 473 | } 474 | 475 | function mousewheel( event ) { 476 | 477 | if ( _this.enabled === false ) return; 478 | 479 | event.preventDefault(); 480 | event.stopPropagation(); 481 | 482 | var delta = 0; 483 | 484 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 485 | 486 | delta = event.wheelDelta / 40; 487 | 488 | } else if ( event.detail ) { // Firefox 489 | 490 | delta = - event.detail / 3; 491 | 492 | } 493 | 494 | _zoomStart.y += delta * 0.01; 495 | _this.dispatchEvent( startEvent ); 496 | _this.dispatchEvent( endEvent ); 497 | 498 | } 499 | 500 | function touchstart( event ) { 501 | 502 | if ( _this.enabled === false ) return; 503 | 504 | switch ( event.touches.length ) { 505 | 506 | case 1: 507 | _state = STATE.TOUCH_ROTATE; 508 | _rotateStart.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 509 | _rotateEnd.copy( _rotateStart ); 510 | break; 511 | 512 | case 2: 513 | _state = STATE.TOUCH_ZOOM_PAN; 514 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 515 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 516 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 517 | 518 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 519 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 520 | _panStart.copy( getMouseOnScreen( x, y ) ); 521 | _panEnd.copy( _panStart ); 522 | break; 523 | 524 | default: 525 | _state = STATE.NONE; 526 | 527 | } 528 | _this.dispatchEvent( startEvent ); 529 | 530 | 531 | } 532 | 533 | function touchmove( event ) { 534 | 535 | if ( _this.enabled === false ) return; 536 | 537 | event.preventDefault(); 538 | event.stopPropagation(); 539 | 540 | switch ( event.touches.length ) { 541 | 542 | case 1: 543 | _rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 544 | break; 545 | 546 | case 2: 547 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 548 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 549 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); 550 | 551 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 552 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 553 | _panEnd.copy( getMouseOnScreen( x, y ) ); 554 | break; 555 | 556 | default: 557 | _state = STATE.NONE; 558 | 559 | } 560 | 561 | } 562 | 563 | function touchend( event ) { 564 | 565 | if ( _this.enabled === false ) return; 566 | 567 | switch ( event.touches.length ) { 568 | 569 | case 1: 570 | _rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 571 | _rotateStart.copy( _rotateEnd ); 572 | break; 573 | 574 | case 2: 575 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; 576 | 577 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 578 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 579 | _panEnd.copy( getMouseOnScreen( x, y ) ); 580 | _panStart.copy( _panEnd ); 581 | break; 582 | 583 | } 584 | 585 | _state = STATE.NONE; 586 | _this.dispatchEvent( endEvent ); 587 | 588 | } 589 | 590 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 591 | 592 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 593 | 594 | this.domElement.addEventListener( 'mousewheel', mousewheel, false ); 595 | this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox 596 | 597 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 598 | this.domElement.addEventListener( 'touchend', touchend, false ); 599 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 600 | 601 | window.addEventListener( 'keydown', keydown, false ); 602 | window.addEventListener( 'keyup', keyup, false ); 603 | 604 | this.handleResize(); 605 | 606 | // force an update at start 607 | this.update(); 608 | 609 | }; 610 | 611 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 612 | THREE.TrackballControls.prototype.constructor = THREE.TrackballControls; 613 | -------------------------------------------------------------------------------- /lib/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 | /*global THREE, console */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 12 | // supported. 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 arrow keys / touch: three finter swipe 17 | // 18 | // This is a drop-in replacement for (most) TrackballControls used in examples. 19 | // That is, include this js file and wherever you see: 20 | // controls = new THREE.TrackballControls( camera ); 21 | // controls.target.z = 150; 22 | // Simple substitute "OrbitControls" and the control should work as-is. 23 | 24 | THREE.OrbitControls = function ( object, domElement ) { 25 | 26 | this.object = object; 27 | this.domElement = ( domElement !== undefined ) ? domElement : document; 28 | 29 | // API 30 | 31 | // Set to false to disable this control 32 | this.enabled = true; 33 | 34 | // "target" sets the location of focus, where the control orbits around 35 | // and where it pans with respect to. 36 | this.target = new THREE.Vector3(); 37 | 38 | // center is old, deprecated; use "target" instead 39 | this.center = this.target; 40 | 41 | // This option actually enables dollying in and out; left as "zoom" for 42 | // backwards compatibility 43 | this.noZoom = false; 44 | this.zoomSpeed = 1.0; 45 | 46 | // Limits to how far you can dolly in and out 47 | this.minDistance = 0; 48 | this.maxDistance = Infinity; 49 | 50 | // Set to true to disable this control 51 | this.noRotate = false; 52 | this.rotateSpeed = 1.0; 53 | 54 | // Set to true to disable this control 55 | this.noPan = false; 56 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 57 | 58 | // Set to true to automatically rotate around the target 59 | this.autoRotate = false; 60 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 61 | 62 | // How far you can orbit vertically, upper and lower limits. 63 | // Range is 0 to Math.PI radians. 64 | this.minPolarAngle = 0; // radians 65 | this.maxPolarAngle = Math.PI; // radians 66 | 67 | // How far you can orbit horizontally, upper and lower limits. 68 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 69 | this.minAzimuthAngle = - Infinity; // radians 70 | this.maxAzimuthAngle = Infinity; // radians 71 | 72 | // Set to true to disable use of the keys 73 | this.noKeys = false; 74 | 75 | // The four arrow keys 76 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 77 | 78 | // Mouse buttons 79 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 80 | 81 | //////////// 82 | // internals 83 | 84 | var scope = this; 85 | 86 | var EPS = 0.000001; 87 | 88 | var rotateStart = new THREE.Vector2(); 89 | var rotateEnd = new THREE.Vector2(); 90 | var rotateDelta = new THREE.Vector2(); 91 | 92 | var panStart = new THREE.Vector2(); 93 | var panEnd = new THREE.Vector2(); 94 | var panDelta = new THREE.Vector2(); 95 | var panOffset = new THREE.Vector3(); 96 | 97 | var offset = new THREE.Vector3(); 98 | 99 | var dollyStart = new THREE.Vector2(); 100 | var dollyEnd = new THREE.Vector2(); 101 | var dollyDelta = new THREE.Vector2(); 102 | 103 | var theta; 104 | var phi; 105 | var phiDelta = 0; 106 | var thetaDelta = 0; 107 | var scale = 1; 108 | var pan = new THREE.Vector3(); 109 | 110 | var lastPosition = new THREE.Vector3(); 111 | var lastQuaternion = new THREE.Quaternion(); 112 | 113 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 114 | 115 | var state = STATE.NONE; 116 | 117 | // for reset 118 | 119 | this.target0 = this.target.clone(); 120 | this.position0 = this.object.position.clone(); 121 | 122 | // so camera.up is the orbit axis 123 | 124 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 125 | var quatInverse = quat.clone().inverse(); 126 | 127 | // events 128 | 129 | var changeEvent = { type: 'change' }; 130 | var startEvent = { type: 'start'}; 131 | var endEvent = { type: 'end'}; 132 | 133 | this.rotateLeft = function ( angle ) { 134 | 135 | if ( angle === undefined ) { 136 | 137 | angle = getAutoRotationAngle(); 138 | 139 | } 140 | 141 | thetaDelta -= angle; 142 | 143 | }; 144 | 145 | this.rotateUp = function ( angle ) { 146 | 147 | if ( angle === undefined ) { 148 | 149 | angle = getAutoRotationAngle(); 150 | 151 | } 152 | 153 | phiDelta -= angle; 154 | 155 | }; 156 | 157 | // pass in distance in world space to move left 158 | this.panLeft = function ( distance ) { 159 | 160 | var te = this.object.matrix.elements; 161 | 162 | // get X column of matrix 163 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 164 | panOffset.multiplyScalar( - distance ); 165 | 166 | pan.add( panOffset ); 167 | 168 | }; 169 | 170 | // pass in distance in world space to move up 171 | this.panUp = function ( distance ) { 172 | 173 | var te = this.object.matrix.elements; 174 | 175 | // get Y column of matrix 176 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 177 | panOffset.multiplyScalar( distance ); 178 | 179 | pan.add( panOffset ); 180 | 181 | }; 182 | 183 | // pass in x,y of change desired in pixel space, 184 | // right and down are positive 185 | this.pan = function ( deltaX, deltaY ) { 186 | 187 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 188 | 189 | if ( scope.object.fov !== undefined ) { 190 | 191 | // perspective 192 | var position = scope.object.position; 193 | var offset = position.clone().sub( scope.target ); 194 | var targetDistance = offset.length(); 195 | 196 | // half of the fov is center to top of screen 197 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 198 | 199 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 200 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); 201 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); 202 | 203 | } else if ( scope.object.top !== undefined ) { 204 | 205 | // orthographic 206 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); 207 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); 208 | 209 | } else { 210 | 211 | // camera neither orthographic or perspective 212 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 213 | 214 | } 215 | 216 | }; 217 | 218 | this.dollyIn = function ( dollyScale ) { 219 | 220 | if ( dollyScale === undefined ) { 221 | 222 | dollyScale = getZoomScale(); 223 | 224 | } 225 | 226 | scale /= dollyScale; 227 | 228 | }; 229 | 230 | this.dollyOut = function ( dollyScale ) { 231 | 232 | if ( dollyScale === undefined ) { 233 | 234 | dollyScale = getZoomScale(); 235 | 236 | } 237 | 238 | scale *= dollyScale; 239 | 240 | }; 241 | 242 | this.update = function () { 243 | 244 | var position = this.object.position; 245 | 246 | offset.copy( position ).sub( this.target ); 247 | 248 | // rotate offset to "y-axis-is-up" space 249 | offset.applyQuaternion( quat ); 250 | 251 | // angle from z-axis around y-axis 252 | 253 | theta = Math.atan2( offset.x, offset.z ); 254 | 255 | // angle from y-axis 256 | 257 | phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 258 | 259 | if ( this.autoRotate && state === STATE.NONE ) { 260 | 261 | this.rotateLeft( getAutoRotationAngle() ); 262 | 263 | } 264 | 265 | theta += thetaDelta; 266 | phi += phiDelta; 267 | 268 | // restrict theta to be between desired limits 269 | theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); 270 | 271 | // restrict phi to be between desired limits 272 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 273 | 274 | // restrict phi to be betwee EPS and PI-EPS 275 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 276 | 277 | var radius = offset.length() * scale; 278 | 279 | // restrict radius to be between desired limits 280 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 281 | 282 | // move target to panned location 283 | this.target.add( pan ); 284 | 285 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 286 | offset.y = radius * Math.cos( phi ); 287 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 288 | 289 | // rotate offset back to "camera-up-vector-is-up" space 290 | offset.applyQuaternion( quatInverse ); 291 | 292 | position.copy( this.target ).add( offset ); 293 | 294 | this.object.lookAt( this.target ); 295 | 296 | thetaDelta = 0; 297 | phiDelta = 0; 298 | scale = 1; 299 | pan.set( 0, 0, 0 ); 300 | 301 | // update condition is: 302 | // min(camera displacement, camera rotation in radians)^2 > EPS 303 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 304 | 305 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS 306 | || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { 307 | 308 | this.dispatchEvent( changeEvent ); 309 | 310 | lastPosition.copy( this.object.position ); 311 | lastQuaternion.copy (this.object.quaternion ); 312 | 313 | } 314 | 315 | }; 316 | 317 | 318 | this.reset = function () { 319 | 320 | state = STATE.NONE; 321 | 322 | this.target.copy( this.target0 ); 323 | this.object.position.copy( this.position0 ); 324 | 325 | this.update(); 326 | 327 | }; 328 | 329 | this.getPolarAngle = function () { 330 | 331 | return phi; 332 | 333 | }; 334 | 335 | this.getAzimuthalAngle = function () { 336 | 337 | return theta 338 | 339 | }; 340 | 341 | function getAutoRotationAngle() { 342 | 343 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 344 | 345 | } 346 | 347 | function getZoomScale() { 348 | 349 | return Math.pow( 0.95, scope.zoomSpeed ); 350 | 351 | } 352 | 353 | function onMouseDown( event ) { 354 | 355 | if ( scope.enabled === false ) return; 356 | event.preventDefault(); 357 | 358 | if ( event.button === scope.mouseButtons.ORBIT ) { 359 | if ( scope.noRotate === true ) return; 360 | 361 | state = STATE.ROTATE; 362 | 363 | rotateStart.set( event.clientX, event.clientY ); 364 | 365 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 366 | if ( scope.noZoom === true ) return; 367 | 368 | state = STATE.DOLLY; 369 | 370 | dollyStart.set( event.clientX, event.clientY ); 371 | 372 | } else if ( event.button === scope.mouseButtons.PAN ) { 373 | if ( scope.noPan === true ) return; 374 | 375 | state = STATE.PAN; 376 | 377 | panStart.set( event.clientX, event.clientY ); 378 | 379 | } 380 | 381 | if ( state !== STATE.NONE ) { 382 | document.addEventListener( 'mousemove', onMouseMove, false ); 383 | document.addEventListener( 'mouseup', onMouseUp, false ); 384 | scope.dispatchEvent( startEvent ); 385 | } 386 | 387 | } 388 | 389 | function onMouseMove( event ) { 390 | 391 | if ( scope.enabled === false ) return; 392 | 393 | event.preventDefault(); 394 | 395 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 396 | 397 | if ( state === STATE.ROTATE ) { 398 | 399 | if ( scope.noRotate === true ) return; 400 | 401 | rotateEnd.set( event.clientX, event.clientY ); 402 | rotateDelta.subVectors( rotateEnd, rotateStart ); 403 | 404 | // rotating across whole screen goes 360 degrees around 405 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 406 | 407 | // rotating up and down along whole screen attempts to go 360, but limited to 180 408 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 409 | 410 | rotateStart.copy( rotateEnd ); 411 | 412 | } else if ( state === STATE.DOLLY ) { 413 | 414 | if ( scope.noZoom === true ) return; 415 | 416 | dollyEnd.set( event.clientX, event.clientY ); 417 | dollyDelta.subVectors( dollyEnd, dollyStart ); 418 | 419 | if ( dollyDelta.y > 0 ) { 420 | 421 | scope.dollyIn(); 422 | 423 | } else { 424 | 425 | scope.dollyOut(); 426 | 427 | } 428 | 429 | dollyStart.copy( dollyEnd ); 430 | 431 | } else if ( state === STATE.PAN ) { 432 | 433 | if ( scope.noPan === true ) return; 434 | 435 | panEnd.set( event.clientX, event.clientY ); 436 | panDelta.subVectors( panEnd, panStart ); 437 | 438 | scope.pan( panDelta.x, panDelta.y ); 439 | 440 | panStart.copy( panEnd ); 441 | 442 | } 443 | 444 | if ( state !== STATE.NONE ) scope.update(); 445 | 446 | } 447 | 448 | function onMouseUp( /* event */ ) { 449 | 450 | if ( scope.enabled === false ) return; 451 | 452 | document.removeEventListener( 'mousemove', onMouseMove, false ); 453 | document.removeEventListener( 'mouseup', onMouseUp, false ); 454 | scope.dispatchEvent( endEvent ); 455 | state = STATE.NONE; 456 | 457 | } 458 | 459 | function onMouseWheel( event ) { 460 | 461 | if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return; 462 | 463 | event.preventDefault(); 464 | event.stopPropagation(); 465 | 466 | var delta = 0; 467 | 468 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 469 | 470 | delta = event.wheelDelta; 471 | 472 | } else if ( event.detail !== undefined ) { // Firefox 473 | 474 | delta = - event.detail; 475 | 476 | } 477 | 478 | if ( delta > 0 ) { 479 | 480 | scope.dollyOut(); 481 | 482 | } else { 483 | 484 | scope.dollyIn(); 485 | 486 | } 487 | 488 | scope.update(); 489 | scope.dispatchEvent( startEvent ); 490 | scope.dispatchEvent( endEvent ); 491 | 492 | } 493 | 494 | function onKeyDown( event ) { 495 | 496 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; 497 | 498 | switch ( event.keyCode ) { 499 | 500 | case scope.keys.UP: 501 | scope.pan( 0, scope.keyPanSpeed ); 502 | scope.update(); 503 | break; 504 | 505 | case scope.keys.BOTTOM: 506 | scope.pan( 0, - scope.keyPanSpeed ); 507 | scope.update(); 508 | break; 509 | 510 | case scope.keys.LEFT: 511 | scope.pan( scope.keyPanSpeed, 0 ); 512 | scope.update(); 513 | break; 514 | 515 | case scope.keys.RIGHT: 516 | scope.pan( - scope.keyPanSpeed, 0 ); 517 | scope.update(); 518 | break; 519 | 520 | } 521 | 522 | } 523 | 524 | function touchstart( event ) { 525 | 526 | if ( scope.enabled === false ) return; 527 | 528 | switch ( event.touches.length ) { 529 | 530 | case 1: // one-fingered touch: rotate 531 | 532 | if ( scope.noRotate === true ) return; 533 | 534 | state = STATE.TOUCH_ROTATE; 535 | 536 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 537 | break; 538 | 539 | case 2: // two-fingered touch: dolly 540 | 541 | if ( scope.noZoom === true ) return; 542 | 543 | state = STATE.TOUCH_DOLLY; 544 | 545 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 546 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 547 | var distance = Math.sqrt( dx * dx + dy * dy ); 548 | dollyStart.set( 0, distance ); 549 | break; 550 | 551 | case 3: // three-fingered touch: pan 552 | 553 | if ( scope.noPan === true ) return; 554 | 555 | state = STATE.TOUCH_PAN; 556 | 557 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 558 | break; 559 | 560 | default: 561 | 562 | state = STATE.NONE; 563 | 564 | } 565 | 566 | if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); 567 | 568 | } 569 | 570 | function touchmove( event ) { 571 | 572 | if ( scope.enabled === false ) return; 573 | 574 | event.preventDefault(); 575 | event.stopPropagation(); 576 | 577 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 578 | 579 | switch ( event.touches.length ) { 580 | 581 | case 1: // one-fingered touch: rotate 582 | 583 | if ( scope.noRotate === true ) return; 584 | if ( state !== STATE.TOUCH_ROTATE ) return; 585 | 586 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 587 | rotateDelta.subVectors( rotateEnd, rotateStart ); 588 | 589 | // rotating across whole screen goes 360 degrees around 590 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 591 | // rotating up and down along whole screen attempts to go 360, but limited to 180 592 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 593 | 594 | rotateStart.copy( rotateEnd ); 595 | 596 | scope.update(); 597 | break; 598 | 599 | case 2: // two-fingered touch: dolly 600 | 601 | if ( scope.noZoom === true ) return; 602 | if ( state !== STATE.TOUCH_DOLLY ) return; 603 | 604 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 605 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 606 | var distance = Math.sqrt( dx * dx + dy * dy ); 607 | 608 | dollyEnd.set( 0, distance ); 609 | dollyDelta.subVectors( dollyEnd, dollyStart ); 610 | 611 | if ( dollyDelta.y > 0 ) { 612 | 613 | scope.dollyOut(); 614 | 615 | } else { 616 | 617 | scope.dollyIn(); 618 | 619 | } 620 | 621 | dollyStart.copy( dollyEnd ); 622 | 623 | scope.update(); 624 | break; 625 | 626 | case 3: // three-fingered touch: pan 627 | 628 | if ( scope.noPan === true ) return; 629 | if ( state !== STATE.TOUCH_PAN ) return; 630 | 631 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 632 | panDelta.subVectors( panEnd, panStart ); 633 | 634 | scope.pan( panDelta.x, panDelta.y ); 635 | 636 | panStart.copy( panEnd ); 637 | 638 | scope.update(); 639 | break; 640 | 641 | default: 642 | 643 | state = STATE.NONE; 644 | 645 | } 646 | 647 | } 648 | 649 | function touchend( /* event */ ) { 650 | 651 | if ( scope.enabled === false ) return; 652 | 653 | scope.dispatchEvent( endEvent ); 654 | state = STATE.NONE; 655 | 656 | } 657 | 658 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 659 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 660 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 661 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 662 | 663 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 664 | this.domElement.addEventListener( 'touchend', touchend, false ); 665 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 666 | 667 | window.addEventListener( 'keydown', onKeyDown, false ); 668 | 669 | // force an update at start 670 | this.update(); 671 | 672 | }; 673 | 674 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 675 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 676 | --------------------------------------------------------------------------------