├── .gitignore ├── README.md ├── data ├── Gale_HRSC_DEM_50m_300x285.bin ├── Gale_HRSC_DEM_50m_300x285.bin.aux.xml ├── Gale_HRSC_DEM_50m_300x285.hdr ├── Gale_HRSC_DEM_50m_overlap.tif ├── Gale_texture_high_4096px.jpg └── Gale_texture_mobile_2048px.jpg ├── img ├── cardboard-icon.png ├── oops.png ├── plain-plane-2.png ├── plain-plane.png ├── runserver.gif ├── surface-wireframe.png └── vr-mode.png ├── js ├── VRControls.js ├── VREffect.js ├── es6-promise.min.js ├── three.flycontrols.js ├── three.min.js ├── three.terrainloader.js ├── webvr-manager.js └── webvr-polyfill.js ├── three-demo-complete.html └── three-demo.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vr-interactives-three-js 2 | There are a lot of Three.js [examples](http://threejs.org/examples/) and [tutorials](http://learningthreejs.com/) out there, but very few examples of what you can do with real-world data. 3 | 4 | In this session you'll learn how we used NASA satellite imagery and elevation data to create a 3-D rendering of the [Gale Crater](http://graphics.latimes.com/mars-gale-crater-vr/) on Mars. 5 | 6 | ## Requirements and credits 7 | We'll make use of the [WebVR Boilerplate](https://github.com/borismus/webvr-boilerplate), which also uses the [WebVR polyfill](https://github.com/borismus/webvr-polyfill) to provide VR support when the WebVR spec isn't implemented. 8 | 9 | We'll also use a [terrain loader](http://blog.thematicmapping.org/2013/10/terrain-building-with-threejs.html), developed by [Bjørn Sandvik](http://blog.thematicmapping.org/). 10 | 11 | ## Preamble 12 | Before we start, we'll want to start up a small webserver that can serve our page and assets. Open the terminal on your computers and navigate to the directory we'll be using (on the lab computers. If you're on your own rig, [download a copy of this repository](https://github.com/datadesk/vr-interactives-three-js/archive/master.zip)) and enter the following at the prompt. 13 | 14 | ```bash 15 | $ cd /data/vr_interactives # or wherever you've downloaded this to 16 | $ python -m SimpleHTTPServer 17 | ``` 18 | ![](https://github.com/datadesk/vr-interactives-three-js/blob/master/img/runserver.gif) 19 | 20 | *Note: If you're using Python 3, you may have to run [http.server](http://angusjune.github.io/blog/2014/08/16/python-3-dot-x-no-module-named-simplehttpserver/) instead.* 21 | 22 | Now you'll be able to go to [http://localhost:8000/three-demo.html](http://localhost:8000/three-demo.html) and navigate to the page we're going to develop. That page will be blank for now. 23 | 24 | ## Let's get started 25 | Using Three.js can be compared a bit to filmmaking: you have a scene, lighting and a camera. The scene updates a certain number of times per second, otherwise known as "frames per second" (which we'll try to keep as close to 60 as we can, but will drop based on your computer and the complexity of the scene.) 26 | 27 | We'll build this in the file [three-demo.html](three-demo.html). Go ahead and open this file in a text editor. 28 | 29 | In the empty script tag on line 33, let's start by declaring some constants. We'll use this to set the size of the renderer on the screen, and the "size" of our world. 30 | 31 | ```javascript 32 | // Width and height of the browser window 33 | var WINDOW_WIDTH = window.innerWidth; 34 | var WINDOW_HEIGHT = window.innerHeight; 35 | 36 | // Width and height of the surface we're going to create 37 | var WORLD_WIDTH = 2000; 38 | var WORLD_HEIGHT = 1900; 39 | ``` 40 | 41 | Then, we're ready to create the scene and place the camera: 42 | 43 | ```javascript 44 | // Where our lights and cameras will go 45 | var scene = new THREE.Scene(); 46 | 47 | // Keeps track of time 48 | var clock = new THREE.Clock(); 49 | 50 | // How we will see the scene 51 | var camera = new THREE.PerspectiveCamera(75, WINDOW_WIDTH / WINDOW_HEIGHT, 1, 5000); 52 | ``` 53 | 54 | The [PerspectiveCamera(fieldOfView, aspect, near, far)](http://threejs.org/docs/#Reference/Cameras/PerspectiveCamera) functions similar to a standard photo or video camera you might be familiar with. The first parameter, `75` is the vertical field of view in degrees, followed by the aspect ratio (in this case the width and height of the window), along with the minimum and maximum ranges the camera can "see." Anything outside these ranges will not be rendered in the scene. 55 | 56 | We also want to position the camera, and tell it where to look. These settings position the camera slightly above the scene, looking at the mound in the center of the crater. 57 | 58 | ```javascript 59 | // Position the camera slightly above and in front of the scene 60 | camera.position.set(0, -199, 75); 61 | camera.up = new THREE.Vector3(0,0,1); 62 | 63 | // Look at the center of the scene 64 | camera.lookAt(scene.position); 65 | ``` 66 | 67 | Now let's create the renderer. We're going to be creating a [WebGL Renderer](http://threejs.org/docs/#Reference/Renderers/WebGLRenderer), which uses the WebGL API to render our graphics. 68 | 69 | ```javascript 70 | // Think of the renderer as the engine that drives the scene 71 | var renderer = new THREE.WebGLRenderer({antialias: true}); 72 | 73 | // Set the pixel ratio of the screen (for high DPI screens) 74 | // This line is actually really important. Otherwise you'll have lots of weird bugs and a generally bad time. 75 | renderer.setPixelRatio(window.devicePixelRatio); 76 | 77 | // Set the background of the scene to a orange/red 78 | renderer.setClearColor(0xffd4a6); 79 | 80 | // Set renderer to the size of the window 81 | renderer.setSize(WINDOW_WIDTH, WINDOW_HEIGHT); 82 | 83 | // Append the renderer to the DOM 84 | document.body.appendChild( renderer.domElement ); 85 | ``` 86 | 87 | The "clear color" of the renderer is set to a dusty orange/red, and the size is set to the size of the window. As the last step, the renderer is appended to the DOM. 88 | 89 | Now, we need to add a couple of helpers that allow VR-style stereo effects to our renderer, along with a manager to allow us to switch between VR and non-VR contexts. 90 | 91 | ```javascript 92 | // Apply VR stereo rendering to renderer 93 | var effect = new THREE.VREffect(renderer); 94 | effect.setSize(WINDOW_WIDTH, WINDOW_HEIGHT); 95 | 96 | // Create a VR manager helper to enter and exit VR mode 97 | var manager = new WebVRManager(renderer, effect); 98 | ``` 99 | 100 | If you try to load the scene now, you're just going to see a blank screen. That's because while we're initializing the scene, we're not actually rendering it. To do that, we need to write what's called a render loop. 101 | 102 | ```javascript 103 | // Render loop 104 | // This should go at the bottom of the script. 105 | function render() { 106 | 107 | // We'll add more up here later 108 | 109 | // Call the render function again 110 | requestAnimationFrame( render ); 111 | 112 | // Update the scene through the manager. 113 | manager.render(scene, camera); 114 | } 115 | 116 | render(); 117 | ``` 118 | 119 | Now, if you load the scene, you should see an orange/red window. There's nothing there, but we'll get to that in a bit. 120 | 121 | 122 | ## Prepping the DEM data 123 | ![Digital elevation model of Gale Crater. Brighter values are higher elevations. (NASA)](http://www.trbimg.com/img-562bfe79/turbine/la-mars-dem-map-20151024/600 "Digital elevation model of Gale Crater. Brighter values are higher elevations. (NASA)") 124 | 125 | A digital elevation model is a 3D representation of a terrain's surface, and in this case is a greyscale heightmap, where lighter colors represent higher elevations. 126 | 127 | The DEM data came as a GeoTIFF file (Gale_HRSC_DEM_50m_overlap.tif). Unfortunately, the TIFF file is huge (30 MB!), and TIFF isn't supported for display by most browsers anyway. We could use the incredibly useful [GDAL](http://www.gdal.org/) to convert it to a PNG image, where the height values are reduced to only 256 shades of grey. This would make our terrain blocky, however. 128 | 129 | We can, however, convert it to a format called ENVI, which stores our height values as 16-bit unsigned integers, offering 65,535 height values for each pixel in the heightmap. 130 | 131 | **We're not going to do this today**, but for the project we converted the heightmap into a 300x285 ENVI file using the following command. Just remember that we've stored the color values in the heightmap as numbers. 132 | 133 | ```bash 134 | $ gdal_translate -scale 600 1905 0 65535 -outsize 300 285 -ot UInt16 -of ENVI Gale_HRSC_DEM_50m.tif Gale_HRSC_DEM_50m.bin 135 | ``` 136 | 137 | If you'd like to read more about how to do this, Bjorn Sandvik has an [excellent explainer](http://blog.mastermaps.com/2013/10/terrain-building-with-threejs-part-1.html). 138 | 139 | You can see the files mentioned above in the 'data' folder. 140 | 141 | ## The fun part: Creating the planet 142 | Now, we get to create the planet surface from the DEM data. To do this, we set the URL of the data to load and initialize the loader. Let's also initialize a value for the surface. 143 | 144 | ```javascript 145 | // URL to our DEM resource 146 | var terrainURL = "data/Gale_HRSC_DEM_50m_300x285.bin"; 147 | 148 | // Utility to load the DEM data 149 | var terrainLoader = new THREE.TerrainLoader(); 150 | 151 | // We'll need this later 152 | var surface; 153 | 154 | // Create the plane geometry 155 | var geometry = new THREE.PlaneGeometry(WORLD_WIDTH, WORLD_HEIGHT, 299, 284); 156 | ``` 157 | 158 | In the line above, we also create a [PlaneGeometry(width, height, widthSegments, heightSegments)](http://threejs.org/docs/#Reference/Extras.Geometries/PlaneGeometry). You'll see four arguments - these are the width and height of the "world" that we defined above, and the number of vertices the plane will have. We set these two values to 299 and 284 - the same dimensions as the DEM data, except that it's zero-indexed. 159 | 160 | By the way, you won't see anything on the screen until we start actually rendering the scene. The screenshots in this tutorial are how it would look *if* it was rendering -- which we'll get to soon. 161 | 162 | ![](https://github.com/datadesk/vr-interactives-three-js/blob/master/img/plain-plane-2.png "Wireframe of the plane loaded into the scene. Each vertex will be adjusted to fit the corresponding height value in the digital elevation model.") 163 | 164 | Then we'll do something crazy. Remember how the height data in the DEM file was stored as a series of numbers? We can use the TerrainLoader to iterate over those values, and adjust each corresponding vertex in the plane. Thus we morph a flat plane to take the shape of the terrain in the data. Because at the scale of the scene we're making, the final shape would be pretty boring at its natural values, we exaggerate the height, settling in at a factor that feels comfortable. 165 | 166 | ```javascript 167 | // The terrainLoader loads the DEM file and defines a function to be called when the file is successfully downloaded. 168 | terrainLoader.load(terrainURL, function(data){ 169 | 170 | // Adjust each vertex in the plane to correspond to the height value in the DEM file. 171 | for (var i = 0, l = geometry.vertices.length; i < l; i++) { 172 | geometry.vertices[i].z = data[i] / 65535 * 100; 173 | } 174 | 175 | // Leave this space blank for now we'll come back to it in the next section. 176 | 177 | }); 178 | ``` 179 | 180 | 181 | 182 | ![](https://github.com/datadesk/vr-interactives-three-js/blob/master/img/surface-wireframe.png?raw=true "Here's what the surface looks like after transforming the height to match the DEM data.") 183 | 184 | So now we have a geometry for our surface, but we can't actually load this into the scene yet. Objects in Three.js, called "meshes", require both a geometry and a material. That is, you need to define a shape, and you need to define what that shape is made out of. Yes, it's a box, but what kind of box, is it? Cardboard? Metal? Is it painted? 185 | 186 | Let's do this next. 187 | 188 | Inside of the callback function, the line I said we'd be coming back to, we want to define a texture loader and set the URL, much in the way that we loaded the DEM data. 189 | 190 | ```javascript 191 | var textureLoader = new THREE.TextureLoader(); 192 | var textureURL = "data/Gale_texture_high_4096px.jpg"; 193 | ``` 194 | 195 | Then we load the texture similar to how we did the DEM file. This time, we define and set the material in the callback function, using the texture we loaded as a texture map. 196 | 197 | ```javascript 198 | // This goes inside the TerrainLoader callback function 199 | textureLoader.load(textureURL, function(texture) { 200 | // Lambert is a type non-reflective material 201 | var material = new THREE.MeshLambertMaterial({ 202 | map: texture 203 | }); 204 | 205 | // Leave this space blank, we're going to continue here in the next section 206 | 207 | }); 208 | ``` 209 | 210 | That's fine and all, but what the heck is a texturemap? Why are we doing all this? 211 | 212 | Remember the geometry is the shape of an object, and the material is what that object is made out of. Think of a texture map as a paint job, or "skin" on an object. It's basically an image mapped onto an object's surface. 213 | 214 | ![](http://www.trbimg.com/img-562bfce4/turbine/la-gale-crater-texture-20151024/600 "Color image of the Gale Crater created by a combination of images from the Viking spacecraft and the Mars Reconnaissance Orbiter. (NASA)") 215 | 216 | You might notice we have both our geometry and material now, so it's time to add them to the scene, **inside of the textureLoader callback** (so, where the "Leave this space blank" comment is). 217 | 218 | ```javascript 219 | // This goes in the TextureLoader callback 220 | // Create the surface mesh and add it to the scene 221 | surface = new THREE.Mesh(geometry, material); 222 | scene.add(surface); 223 | ``` 224 | 225 | It's important to place these in the callbacks, otherwise you might be trying to load a texture onto a shape that hasn't loaded yet, or vice versa. 226 | 227 | Now, let's take a look at the scene. There will be a wait while the resources load and process, but eventually you'll see this lovely scene. 228 | 229 | ![Our scene](https://github.com/datadesk/vr-interactives-three-js/blob/master/img/oops.png?raw=true "Lambert materials won't show up in a scene without lights.") 230 | 231 | What happened? We forgot to turn on the lights! Let's do that. 232 | 233 | ```javascript 234 | // Lights! 235 | var dirLight = new THREE.DirectionalLight( 0xffffff, 0.75); 236 | dirLight.position.set( -1, 1, 1).normalize(); 237 | 238 | var ambiLight = new THREE.AmbientLight(0x999999); 239 | 240 | // Add the lights to the scene 241 | scene.add(ambiLight); 242 | scene.add(dirLight); 243 | ``` 244 | 245 | That's better, right? 246 | 247 | Just like a film scene, Three.js scenes need lighting. You can think of a [DirectionalLight(hexColor, intensity)](http://threejs.org/docs/#Reference/Lights/DirectionalLight) as a spotlight that you can specify the direction of and where it's pointing, while [AmbientLight(hexColor)](http://threejs.org/docs/#Reference/Lights/AmbientLight) is more like the sun - it lights all objects in the scene, regardless of where they're positioned. 248 | 249 | Standing still in a scene isn't very fun, we can't even look around. To interact with a scene, we'll need to add controls. Controls basically move the camera around the scene according to user inputs. The different parameters we use below are specific to the FlyControls we'll be using. **Make sure to put this code before the `render()` loop:** 250 | 251 | ```javascript 252 | // WASD-style movement controls 253 | var controls = new THREE.FlyControls(camera); 254 | 255 | // Disable automatic forward movement 256 | controls.autoForward = false; 257 | 258 | // Click and drag to look around with the mouse 259 | controls.dragToLook = true; 260 | 261 | // Movement and roll speeds, adjust these and see what happens! 262 | controls.movementSpeed = 20; 263 | controls.rollSpeed = Math.PI / 12; 264 | ``` 265 | 266 | Reload the page and... nothing happened! To actually move around in the scene, we also need to add and update the controls in the renderer loop. Find the `render()` function from earlier and replace the code inside it like so: 267 | 268 | ```javascript 269 | // Render loop 270 | function render() { 271 | 272 | // Get the difference from when the clock was last updated and update the controls based on that value. 273 | var delta = clock.getDelta(); 274 | controls.update(delta); 275 | 276 | // Call the render function again 277 | requestAnimationFrame( render ); 278 | 279 | // Update the scene through the manager. 280 | manager.render(scene, camera); 281 | } 282 | ``` 283 | 284 | Now when you reload the scene you can look around by clicking and dragging with the mouse, and use the WASD keys to move around the crater. 285 | 286 | That's great and all but what if we want to try this on mobile? Instead, let's load controls conditionally, depending on the device we're on. This will load the VRControls if you load the page on your phone, which use the accelerometer to track movement. 287 | 288 | ```javascript 289 | // Detect mobile devices in the user agent 290 | var is_mobile= /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 291 | 292 | // Conditionally load VR or Fly controls, based on whether we're on a mobile device 293 | if (is_mobile) { 294 | var controls = new THREE.VRControls(camera); 295 | } else { 296 | // WASD-style movement controls 297 | var controls = new THREE.FlyControls(camera); 298 | 299 | // Disable automatic forward movement 300 | controls.autoForward = false; 301 | 302 | // Click and drag to look around with the mouse 303 | controls.dragToLook = true; 304 | 305 | // Movement and roll speeds, adjust these and see what happens! 306 | controls.movementSpeed = 20; 307 | controls.rollSpeed = Math.PI / 12; 308 | } 309 | ``` 310 | 311 | Now, if you visit this page on a mobile phone, you'll be able to look around the scene just by moving your device. 312 | 313 | ## VR Mode 314 | 315 | One great thing about the VR manager is it provides an interface to allow users to easily switch in and out of VR mode, so that you can use a cardboard or other VR device. This ability is automatically loaded based on the device that you're viewing the page on, so it'll show up on phones, but not your desktops. We can override this though. 316 | 317 | At the very top of the page you'll see a line ([line 20, in fact.](https://github.com/datadesk/vr-interactives-three-js/blob/master/three-demo.html#L20)) that says `FORCE_ENABLE_VR: false`. Change this option to `true`. 318 | 319 | ![](https://github.com/datadesk/vr-interactives-three-js/blob/master/img/cardboard-icon.png?raw=true "VR mode toggle icon.") 320 | 321 | Now, if you reload the page, you'll see a small cardboard icon in the lower right-hand side of the page. Click this, and you'll see the page distort as if you were viewing the page through a cardboard. 322 | 323 | Voila! 324 | 325 | ![](https://github.com/datadesk/vr-interactives-three-js/blob/master/img/vr-mode.png?raw=true "Viewing the scene distorted through VR mode.") 326 | 327 | 328 | ## Other Things you may want to do 329 | - Load in different surface detail and texture sizes based on the device. 330 | - Add in collision detection 331 | - Call out specific points in the crater 332 | - Progressively load terrain and texture 333 | 334 | 335 | ## So what can you do with this? 336 | A couple people have taken the techniques outlined here and integrated them into story pages 337 | - [Yaryna Serkez](https://twitter.com/iarynam) created a [3D map of the Carpathian Mountains](http://texty.org.ua/d/carpathians-3d/). 338 | - This isn't a three.js project, but Joe Fox used the same terrain extrusion techniques to create a map of a [mountain lion's movements in Griffith Park](http://www.latimes.com/projects/la-me-griffith-park-mountain-lion/). 339 | -------------------------------------------------------------------------------- /data/Gale_HRSC_DEM_50m_300x285.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/data/Gale_HRSC_DEM_50m_300x285.bin -------------------------------------------------------------------------------- /data/Gale_HRSC_DEM_50m_300x285.bin.aux.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | BAND 4 | 5 | 6 | 1 7 | 0 8 | 12 9 | ENVI Standard 10 | 0 11 | bsq 12 | 285 13 | 300 14 | 15 | 16 | Area 17 | Generic 18 | 19 | 20 | -3.27680000000000E+04 21 | 22 | 23 | -------------------------------------------------------------------------------- /data/Gale_HRSC_DEM_50m_300x285.hdr: -------------------------------------------------------------------------------- 1 | ENVI 2 | description = { 3 | Gale_HRSC_DEM_50m_300x285.bin} 4 | samples = 300 5 | lines = 285 6 | bands = 1 7 | header offset = 0 8 | file type = ENVI Standard 9 | data type = 12 10 | interleave = bsq 11 | byte order = 0 12 | map info = {Equirectangular, 1, 1, 8068200, -223599.999999997, 666.666666666667, 666.666666666667} 13 | coordinate system string = {PROJCS["Mars2000_Equidistant_Cylindrical_clon0",GEOGCS["GCS_Mars_2000_Sphere",DATUM["D_Mars_2000_Sphere",SPHEROID["Mars_2000_Sphere_IAU_IAG",3396190,0]],PRIMEM["Reference_Meridian",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Equidistant_Cylindrical"],PARAMETER["central_meridian",0],PARAMETER["standard_parallel_1",0],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["Meter",1]]} 14 | band names = { 15 | Band 1} 16 | -------------------------------------------------------------------------------- /data/Gale_HRSC_DEM_50m_overlap.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/data/Gale_HRSC_DEM_50m_overlap.tif -------------------------------------------------------------------------------- /data/Gale_texture_high_4096px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/data/Gale_texture_high_4096px.jpg -------------------------------------------------------------------------------- /data/Gale_texture_mobile_2048px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/data/Gale_texture_mobile_2048px.jpg -------------------------------------------------------------------------------- /img/cardboard-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/img/cardboard-icon.png -------------------------------------------------------------------------------- /img/oops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/img/oops.png -------------------------------------------------------------------------------- /img/plain-plane-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/img/plain-plane-2.png -------------------------------------------------------------------------------- /img/plain-plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/img/plain-plane.png -------------------------------------------------------------------------------- /img/runserver.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/img/runserver.gif -------------------------------------------------------------------------------- /img/surface-wireframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/img/surface-wireframe.png -------------------------------------------------------------------------------- /img/vr-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datadesk/vr-interactives-three-js/915f90440e9e81ae2b8a33e74b6e86e2a48e27a2/img/vr-mode.png -------------------------------------------------------------------------------- /js/VRControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | */ 5 | 6 | THREE.VRControls = function ( object, onError ) { 7 | 8 | var scope = this; 9 | 10 | var vrInputs = []; 11 | 12 | function filterInvalidDevices( devices ) { 13 | 14 | // Exclude Cardboard position sensor if Oculus exists. 15 | 16 | var oculusDevices = devices.filter( function ( device ) { 17 | 18 | return device.deviceName.toLowerCase().indexOf('oculus') !== -1; 19 | 20 | } ); 21 | 22 | if ( oculusDevices.length >= 1 ) { 23 | 24 | return devices.filter( function ( device ) { 25 | 26 | return device.deviceName.toLowerCase().indexOf('cardboard') === -1; 27 | 28 | } ); 29 | 30 | } else { 31 | 32 | return devices; 33 | 34 | } 35 | 36 | } 37 | 38 | function gotVRDevices( devices ) { 39 | 40 | devices = filterInvalidDevices( devices ); 41 | 42 | for ( var i = 0; i < devices.length; i ++ ) { 43 | 44 | if ( devices[ i ] instanceof PositionSensorVRDevice ) { 45 | 46 | vrInputs.push( devices[ i ] ); 47 | 48 | } 49 | 50 | } 51 | 52 | if ( onError ) onError( 'HMD not available' ); 53 | 54 | } 55 | 56 | if ( navigator.getVRDevices ) { 57 | 58 | navigator.getVRDevices().then( gotVRDevices ); 59 | 60 | } 61 | 62 | // the Rift SDK returns the position in meters 63 | // this scale factor allows the user to define how meters 64 | // are converted to scene units. 65 | 66 | this.scale = 1; 67 | 68 | this.update = function () { 69 | for ( var i = 0; i < vrInputs.length; i ++ ) { 70 | var vrInput = vrInputs[ i ]; 71 | var state = vrInput.getState(); 72 | if ( state.orientation !== null ) { 73 | // if (object.type !== "Object3D") { 74 | // object.quaternion.multiplyQuaternions(object.originalQuaternion, state.orientation); 75 | // } else { 76 | object.quaternion.copy( state.orientation ); 77 | // } 78 | } 79 | 80 | if ( state.position !== null ) { 81 | 82 | object.position.copy( state.position ).multiplyScalar( scope.scale ); 83 | 84 | } 85 | 86 | } 87 | 88 | }; 89 | 90 | this.resetSensor = function () { 91 | 92 | for ( var i = 0; i < vrInputs.length; i ++ ) { 93 | 94 | var vrInput = vrInputs[ i ]; 95 | 96 | if ( vrInput.resetSensor !== undefined ) { 97 | 98 | vrInput.resetSensor(); 99 | 100 | } else if ( vrInput.zeroSensor !== undefined ) { 101 | 102 | vrInput.zeroSensor(); 103 | 104 | } 105 | 106 | } 107 | 108 | }; 109 | 110 | this.zeroSensor = function () { 111 | 112 | THREE.warn( 'THREE.VRControls: .zeroSensor() is now .resetSensor().' ); 113 | this.resetSensor(); 114 | 115 | }; 116 | 117 | this.dispose = function () { 118 | 119 | vrInputs = []; 120 | 121 | }; 122 | }; 123 | -------------------------------------------------------------------------------- /js/VREffect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | * 5 | * WebVR Spec: http://mozvr.github.io/webvr-spec/webvr.html 6 | * 7 | * Firefox: http://mozvr.com/downloads/ 8 | * Chromium: https://drive.google.com/folderview?id=0BzudLt22BqGRbW9WTHMtOWMzNjQ&usp=sharing#list 9 | * 10 | */ 11 | 12 | THREE.VREffect = function ( renderer, onError ) { 13 | 14 | var vrHMD; 15 | var eyeTranslationL, eyeFOVL; 16 | var eyeTranslationR, eyeFOVR; 17 | 18 | function gotVRDevices( devices ) { 19 | 20 | for ( var i = 0; i < devices.length; i ++ ) { 21 | 22 | if ( devices[ i ] instanceof HMDVRDevice ) { 23 | 24 | vrHMD = devices[ i ]; 25 | 26 | if ( vrHMD.getEyeParameters !== undefined ) { 27 | 28 | var eyeParamsL = vrHMD.getEyeParameters( 'left' ); 29 | var eyeParamsR = vrHMD.getEyeParameters( 'right' ); 30 | 31 | eyeTranslationL = eyeParamsL.eyeTranslation; 32 | eyeTranslationR = eyeParamsR.eyeTranslation; 33 | eyeFOVL = eyeParamsL.recommendedFieldOfView; 34 | eyeFOVR = eyeParamsR.recommendedFieldOfView; 35 | 36 | } else { 37 | 38 | // TODO: This is an older code path and not spec compliant. 39 | // It should be removed at some point in the near future. 40 | eyeTranslationL = vrHMD.getEyeTranslation( 'left' ); 41 | eyeTranslationR = vrHMD.getEyeTranslation( 'right' ); 42 | eyeFOVL = vrHMD.getRecommendedEyeFieldOfView( 'left' ); 43 | eyeFOVR = vrHMD.getRecommendedEyeFieldOfView( 'right' ); 44 | 45 | } 46 | 47 | break; // We keep the first we encounter 48 | 49 | } 50 | 51 | } 52 | 53 | if ( vrHMD === undefined ) { 54 | 55 | if ( onError ) onError( 'HMD not available' ); 56 | 57 | } 58 | 59 | } 60 | 61 | if ( navigator.getVRDevices ) { 62 | 63 | navigator.getVRDevices().then( gotVRDevices ); 64 | 65 | } 66 | 67 | // 68 | 69 | this.scale = 1; 70 | 71 | this.setSize = function( width, height ) { 72 | 73 | renderer.setSize( width, height ); 74 | 75 | }; 76 | 77 | // fullscreen 78 | 79 | var isFullscreen = false; 80 | 81 | var canvas = renderer.domElement; 82 | var fullscreenchange = canvas.mozRequestFullScreen ? 'mozfullscreenchange' : 'webkitfullscreenchange'; 83 | 84 | document.addEventListener( fullscreenchange, function ( event ) { 85 | 86 | isFullscreen = document.mozFullScreenElement || document.webkitFullscreenElement; 87 | 88 | }, false ); 89 | 90 | this.setFullScreen = function ( boolean ) { 91 | 92 | if ( vrHMD === undefined ) return; 93 | if ( isFullscreen === boolean ) return; 94 | 95 | if ( canvas.mozRequestFullScreen ) { 96 | 97 | canvas.mozRequestFullScreen( { vrDisplay: vrHMD } ); 98 | 99 | } else if ( canvas.webkitRequestFullscreen ) { 100 | 101 | canvas.webkitRequestFullscreen( { vrDisplay: vrHMD } ); 102 | 103 | } 104 | 105 | }; 106 | 107 | // render 108 | 109 | var cameraL = new THREE.PerspectiveCamera(); 110 | var cameraR = new THREE.PerspectiveCamera(); 111 | 112 | this.render = function ( scene, camera ) { 113 | 114 | if ( vrHMD ) { 115 | 116 | var sceneL, sceneR; 117 | 118 | if ( Array.isArray( scene ) ) { 119 | 120 | sceneL = scene[ 0 ]; 121 | sceneR = scene[ 1 ]; 122 | 123 | } else { 124 | 125 | sceneL = scene; 126 | sceneR = scene; 127 | 128 | } 129 | 130 | var size = renderer.getSize(); 131 | size.width /= 2; 132 | 133 | renderer.enableScissorTest( true ); 134 | renderer.clear(); 135 | 136 | if ( camera.parent === null ) camera.updateMatrixWorld(); 137 | 138 | cameraL.projectionMatrix = fovToProjection( eyeFOVL, true, camera.near, camera.far ); 139 | cameraR.projectionMatrix = fovToProjection( eyeFOVR, true, camera.near, camera.far ); 140 | 141 | camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale ); 142 | camera.matrixWorld.decompose( cameraR.position, cameraR.quaternion, cameraR.scale ); 143 | 144 | cameraL.translateX( eyeTranslationL.x * this.scale ); 145 | cameraR.translateX( eyeTranslationR.x * this.scale ); 146 | 147 | // render left eye 148 | renderer.setViewport( 0, 0, size.width, size.height ); 149 | renderer.setScissor( 0, 0, size.width, size.height ); 150 | renderer.render( sceneL, cameraL ); 151 | 152 | // render right eye 153 | renderer.setViewport( size.width, 0, size.width, size.height ); 154 | renderer.setScissor( size.width, 0, size.width, size.height ); 155 | renderer.render( sceneR, cameraR ); 156 | 157 | renderer.enableScissorTest( false ); 158 | 159 | return; 160 | 161 | } 162 | 163 | // Regular render mode if not HMD 164 | 165 | if ( Array.isArray( scene ) ) scene = scene[ 0 ]; 166 | 167 | renderer.render( scene, camera ); 168 | 169 | }; 170 | 171 | // 172 | 173 | function fovToNDCScaleOffset( fov ) { 174 | 175 | var pxscale = 2.0 / ( fov.leftTan + fov.rightTan ); 176 | var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5; 177 | var pyscale = 2.0 / ( fov.upTan + fov.downTan ); 178 | var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5; 179 | return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] }; 180 | 181 | } 182 | 183 | function fovPortToProjection( fov, rightHanded, zNear, zFar ) { 184 | 185 | rightHanded = rightHanded === undefined ? true : rightHanded; 186 | zNear = zNear === undefined ? 0.01 : zNear; 187 | zFar = zFar === undefined ? 10000.0 : zFar; 188 | 189 | var handednessScale = rightHanded ? - 1.0 : 1.0; 190 | 191 | // start with an identity matrix 192 | var mobj = new THREE.Matrix4(); 193 | var m = mobj.elements; 194 | 195 | // and with scale/offset info for normalized device coords 196 | var scaleAndOffset = fovToNDCScaleOffset( fov ); 197 | 198 | // X result, map clip edges to [-w,+w] 199 | m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ]; 200 | m[ 0 * 4 + 1 ] = 0.0; 201 | m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale; 202 | m[ 0 * 4 + 3 ] = 0.0; 203 | 204 | // Y result, map clip edges to [-w,+w] 205 | // Y offset is negated because this proj matrix transforms from world coords with Y=up, 206 | // but the NDC scaling has Y=down (thanks D3D?) 207 | m[ 1 * 4 + 0 ] = 0.0; 208 | m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ]; 209 | m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale; 210 | m[ 1 * 4 + 3 ] = 0.0; 211 | 212 | // Z result (up to the app) 213 | m[ 2 * 4 + 0 ] = 0.0; 214 | m[ 2 * 4 + 1 ] = 0.0; 215 | m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale; 216 | m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar ); 217 | 218 | // W result (= Z in) 219 | m[ 3 * 4 + 0 ] = 0.0; 220 | m[ 3 * 4 + 1 ] = 0.0; 221 | m[ 3 * 4 + 2 ] = handednessScale; 222 | m[ 3 * 4 + 3 ] = 0.0; 223 | 224 | mobj.transpose(); 225 | 226 | return mobj; 227 | 228 | } 229 | 230 | function fovToProjection( fov, rightHanded, zNear, zFar ) { 231 | 232 | var DEG2RAD = Math.PI / 180.0; 233 | 234 | var fovPort = { 235 | upTan: Math.tan( fov.upDegrees * DEG2RAD ), 236 | downTan: Math.tan( fov.downDegrees * DEG2RAD ), 237 | leftTan: Math.tan( fov.leftDegrees * DEG2RAD ), 238 | rightTan: Math.tan( fov.rightDegrees * DEG2RAD ) 239 | }; 240 | 241 | return fovPortToProjection( fovPort, rightHanded, zNear, zFar ); 242 | 243 | } 244 | 245 | }; 246 | -------------------------------------------------------------------------------- /js/es6-promise.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * @overview es6-promise - a tiny implementation of Promises/A+. 3 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) 4 | * @license Licensed under MIT license 5 | * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE 6 | * @version 3.0.2 7 | */ 8 | 9 | (function(){"use strict";function lib$es6$promise$utils$$objectOrFunction(x){return typeof x==="function"||typeof x==="object"&&x!==null}function lib$es6$promise$utils$$isFunction(x){return typeof x==="function"}function lib$es6$promise$utils$$isMaybeThenable(x){return typeof x==="object"&&x!==null}var lib$es6$promise$utils$$_isArray;if(!Array.isArray){lib$es6$promise$utils$$_isArray=function(x){return Object.prototype.toString.call(x)==="[object Array]"}}else{lib$es6$promise$utils$$_isArray=Array.isArray}var lib$es6$promise$utils$$isArray=lib$es6$promise$utils$$_isArray;var lib$es6$promise$asap$$len=0;var lib$es6$promise$asap$$toString={}.toString;var lib$es6$promise$asap$$vertxNext;var lib$es6$promise$asap$$customSchedulerFn;var lib$es6$promise$asap$$asap=function asap(callback,arg){lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len]=callback;lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len+1]=arg;lib$es6$promise$asap$$len+=2;if(lib$es6$promise$asap$$len===2){if(lib$es6$promise$asap$$customSchedulerFn){lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush)}else{lib$es6$promise$asap$$scheduleFlush()}}};function lib$es6$promise$asap$$setScheduler(scheduleFn){lib$es6$promise$asap$$customSchedulerFn=scheduleFn}function lib$es6$promise$asap$$setAsap(asapFn){lib$es6$promise$asap$$asap=asapFn}var lib$es6$promise$asap$$browserWindow=typeof window!=="undefined"?window:undefined;var lib$es6$promise$asap$$browserGlobal=lib$es6$promise$asap$$browserWindow||{};var lib$es6$promise$asap$$BrowserMutationObserver=lib$es6$promise$asap$$browserGlobal.MutationObserver||lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver;var lib$es6$promise$asap$$isNode=typeof process!=="undefined"&&{}.toString.call(process)==="[object process]";var lib$es6$promise$asap$$isWorker=typeof Uint8ClampedArray!=="undefined"&&typeof importScripts!=="undefined"&&typeof MessageChannel!=="undefined";function lib$es6$promise$asap$$useNextTick(){return function(){process.nextTick(lib$es6$promise$asap$$flush)}}function lib$es6$promise$asap$$useVertxTimer(){return function(){lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush)}}function lib$es6$promise$asap$$useMutationObserver(){var iterations=0;var observer=new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush);var node=document.createTextNode("");observer.observe(node,{characterData:true});return function(){node.data=iterations=++iterations%2}}function lib$es6$promise$asap$$useMessageChannel(){var channel=new MessageChannel;channel.port1.onmessage=lib$es6$promise$asap$$flush;return function(){channel.port2.postMessage(0)}}function lib$es6$promise$asap$$useSetTimeout(){return function(){setTimeout(lib$es6$promise$asap$$flush,1)}}var lib$es6$promise$asap$$queue=new Array(1e3);function lib$es6$promise$asap$$flush(){for(var i=0;i 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 | -------------------------------------------------------------------------------- /js/three.terrainloader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * For loading binary data into a 3D terrain model 3 | * @author Bjorn Sandvik / http://thematicmapping.org/ 4 | */ 5 | 6 | THREE.TerrainLoader = function ( manager ) { 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | }; 9 | 10 | THREE.TerrainLoader.prototype = { 11 | constructor: THREE.TerrainLoader, 12 | load: function ( url, onLoad, onProgress, onError ) { 13 | var scope = this; 14 | var request = new XMLHttpRequest(); 15 | 16 | if ( onLoad !== undefined ) { 17 | request.addEventListener( 'load', function ( event ) { 18 | onLoad( new Uint16Array( event.target.response ) ); 19 | scope.manager.itemEnd( url ); 20 | }, false ); 21 | } 22 | if ( onProgress !== undefined ) { 23 | request.addEventListener( 'progress', function ( event ) { 24 | onProgress( event ); 25 | }, false ); 26 | } 27 | if ( onError !== undefined ) { 28 | request.addEventListener( 'error', function ( event ) { 29 | onError( event ); 30 | }, false ); 31 | } 32 | if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin; 33 | request.open( 'GET', url, true ); 34 | request.responseType = 'arraybuffer'; 35 | request.send( null ); 36 | scope.manager.itemStart( url ); 37 | }, 38 | setCrossOrigin: function ( value ) { 39 | this.crossOrigin = value; 40 | } 41 | }; -------------------------------------------------------------------------------- /js/webvr-polyfill.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o Util.MAX_TIMESTEP) { 484 | console.warn('Invalid timestamps detected. Time step between successive ' + 485 | 'gyroscope sensor samples is very small or not monotonic'); 486 | this.previousTimestampS = timestampS; 487 | return; 488 | } 489 | this.accelerometer.set(-accGravity.x, -accGravity.y, -accGravity.z); 490 | this.gyroscope.set(rotRate.alpha, rotRate.beta, rotRate.gamma); 491 | 492 | // With iOS and Firefox Android, rotationRate is reported in degrees, 493 | // so we first convert to radians. 494 | if (this.isIOS || this.isFirefoxAndroid) { 495 | this.gyroscope.multiplyScalar(Math.PI / 180); 496 | } 497 | 498 | this.filter.addAccelMeasurement(this.accelerometer, timestampS); 499 | this.filter.addGyroMeasurement(this.gyroscope, timestampS); 500 | 501 | this.previousTimestampS = timestampS; 502 | }; 503 | 504 | FusionPositionSensorVRDevice.prototype.onScreenOrientationChange_ = 505 | function(screenOrientation) { 506 | this.setScreenTransform_(); 507 | }; 508 | 509 | FusionPositionSensorVRDevice.prototype.setScreenTransform_ = function() { 510 | this.worldToScreenQ.set(0, 0, 0, 1); 511 | switch (window.orientation) { 512 | case 0: 513 | break; 514 | case 90: 515 | this.worldToScreenQ.setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.PI/2); 516 | break; 517 | case -90: 518 | this.worldToScreenQ.setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI/2); 519 | break; 520 | case 180: 521 | // TODO. 522 | break; 523 | } 524 | }; 525 | 526 | 527 | module.exports = FusionPositionSensorVRDevice; 528 | 529 | },{"./base.js":1,"./complementary-filter.js":3,"./pose-predictor.js":7,"./three-math.js":9,"./touch-panner.js":10,"./util.js":11}],5:[function(_dereq_,module,exports){ 530 | /* 531 | * Copyright 2015 Google Inc. All Rights Reserved. 532 | * Licensed under the Apache License, Version 2.0 (the "License"); 533 | * you may not use this file except in compliance with the License. 534 | * You may obtain a copy of the License at 535 | * 536 | * http://www.apache.org/licenses/LICENSE-2.0 537 | * 538 | * Unless required by applicable law or agreed to in writing, software 539 | * distributed under the License is distributed on an "AS IS" BASIS, 540 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 541 | * See the License for the specific language governing permissions and 542 | * limitations under the License. 543 | */ 544 | var WebVRPolyfill = _dereq_('./webvr-polyfill.js'); 545 | 546 | // Initialize a WebVRConfig just in case. 547 | window.WebVRConfig = window.WebVRConfig || {}; 548 | new WebVRPolyfill(); 549 | 550 | },{"./webvr-polyfill.js":12}],6:[function(_dereq_,module,exports){ 551 | /* 552 | * Copyright 2015 Google Inc. All Rights Reserved. 553 | * Licensed under the Apache License, Version 2.0 (the "License"); 554 | * you may not use this file except in compliance with the License. 555 | * You may obtain a copy of the License at 556 | * 557 | * http://www.apache.org/licenses/LICENSE-2.0 558 | * 559 | * Unless required by applicable law or agreed to in writing, software 560 | * distributed under the License is distributed on an "AS IS" BASIS, 561 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 562 | * See the License for the specific language governing permissions and 563 | * limitations under the License. 564 | */ 565 | var PositionSensorVRDevice = _dereq_('./base.js').PositionSensorVRDevice; 566 | var THREE = _dereq_('./three-math.js'); 567 | var Util = _dereq_('./util.js'); 568 | 569 | // How much to rotate per key stroke. 570 | var KEY_SPEED = 0.15; 571 | var KEY_ANIMATION_DURATION = 80; 572 | 573 | // How much to rotate for mouse events. 574 | var MOUSE_SPEED_X = 0.5; 575 | var MOUSE_SPEED_Y = 0.3; 576 | 577 | /** 578 | * A virtual position sensor, implemented using keyboard and 579 | * mouse APIs. This is designed as for desktops/laptops where no Device* 580 | * events work. 581 | */ 582 | function MouseKeyboardPositionSensorVRDevice() { 583 | this.deviceId = 'webvr-polyfill:mouse-keyboard'; 584 | this.deviceName = 'VR Position Device (webvr-polyfill:mouse-keyboard)'; 585 | 586 | // Attach to mouse and keyboard events. 587 | window.addEventListener('keydown', this.onKeyDown_.bind(this)); 588 | window.addEventListener('mousemove', this.onMouseMove_.bind(this)); 589 | window.addEventListener('mousedown', this.onMouseDown_.bind(this)); 590 | window.addEventListener('mouseup', this.onMouseUp_.bind(this)); 591 | 592 | this.phi = 0; 593 | this.theta = 0; 594 | 595 | // Variables for keyboard-based rotation animation. 596 | this.targetAngle = null; 597 | 598 | // State variables for calculations. 599 | this.euler = new THREE.Euler(); 600 | this.orientation = new THREE.Quaternion(); 601 | 602 | // Variables for mouse-based rotation. 603 | this.rotateStart = new THREE.Vector2(); 604 | this.rotateEnd = new THREE.Vector2(); 605 | this.rotateDelta = new THREE.Vector2(); 606 | } 607 | MouseKeyboardPositionSensorVRDevice.prototype = new PositionSensorVRDevice(); 608 | 609 | /** 610 | * Returns {orientation: {x,y,z,w}, position: null}. 611 | * Position is not supported for parity with other PositionSensors. 612 | */ 613 | MouseKeyboardPositionSensorVRDevice.prototype.getState = function() { 614 | this.euler.set(this.phi, this.theta, 0, 'YXZ'); 615 | this.orientation.setFromEuler(this.euler); 616 | 617 | return { 618 | hasOrientation: true, 619 | orientation: this.orientation, 620 | hasPosition: false, 621 | position: null 622 | } 623 | }; 624 | 625 | MouseKeyboardPositionSensorVRDevice.prototype.onKeyDown_ = function(e) { 626 | // Track WASD and arrow keys. 627 | if (e.keyCode == 38) { // Up key. 628 | this.animatePhi_(this.phi + KEY_SPEED); 629 | } else if (e.keyCode == 39) { // Right key. 630 | this.animateTheta_(this.theta - KEY_SPEED); 631 | } else if (e.keyCode == 40) { // Down key. 632 | this.animatePhi_(this.phi - KEY_SPEED); 633 | } else if (e.keyCode == 37) { // Left key. 634 | this.animateTheta_(this.theta + KEY_SPEED); 635 | } 636 | }; 637 | 638 | MouseKeyboardPositionSensorVRDevice.prototype.animateTheta_ = function(targetAngle) { 639 | this.animateKeyTransitions_('theta', targetAngle); 640 | }; 641 | 642 | MouseKeyboardPositionSensorVRDevice.prototype.animatePhi_ = function(targetAngle) { 643 | // Prevent looking too far up or down. 644 | targetAngle = Util.clamp(targetAngle, -Math.PI/2, Math.PI/2); 645 | this.animateKeyTransitions_('phi', targetAngle); 646 | }; 647 | 648 | /** 649 | * Start an animation to transition an angle from one value to another. 650 | */ 651 | MouseKeyboardPositionSensorVRDevice.prototype.animateKeyTransitions_ = function(angleName, targetAngle) { 652 | // If an animation is currently running, cancel it. 653 | if (this.angleAnimation) { 654 | clearInterval(this.angleAnimation); 655 | } 656 | var startAngle = this[angleName]; 657 | var startTime = new Date(); 658 | // Set up an interval timer to perform the animation. 659 | this.angleAnimation = setInterval(function() { 660 | // Once we're finished the animation, we're done. 661 | var elapsed = new Date() - startTime; 662 | if (elapsed >= KEY_ANIMATION_DURATION) { 663 | this[angleName] = targetAngle; 664 | clearInterval(this.angleAnimation); 665 | return; 666 | } 667 | // Linearly interpolate the angle some amount. 668 | var percent = elapsed / KEY_ANIMATION_DURATION; 669 | this[angleName] = startAngle + (targetAngle - startAngle) * percent; 670 | }.bind(this), 1000/60); 671 | }; 672 | 673 | MouseKeyboardPositionSensorVRDevice.prototype.onMouseDown_ = function(e) { 674 | this.rotateStart.set(e.clientX, e.clientY); 675 | this.isDragging = true; 676 | }; 677 | 678 | // Very similar to https://gist.github.com/mrflix/8351020 679 | MouseKeyboardPositionSensorVRDevice.prototype.onMouseMove_ = function(e) { 680 | if (!this.isDragging && !this.isPointerLocked_()) { 681 | return; 682 | } 683 | // Support pointer lock API. 684 | if (this.isPointerLocked_()) { 685 | var movementX = e.movementX || e.mozMovementX || 0; 686 | var movementY = e.movementY || e.mozMovementY || 0; 687 | this.rotateEnd.set(this.rotateStart.x - movementX, this.rotateStart.y - movementY); 688 | } else { 689 | this.rotateEnd.set(e.clientX, e.clientY); 690 | } 691 | // Calculate how much we moved in mouse space. 692 | this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart); 693 | this.rotateStart.copy(this.rotateEnd); 694 | 695 | // Keep track of the cumulative euler angles. 696 | var element = document.body; 697 | this.phi += 2 * Math.PI * this.rotateDelta.y / element.clientHeight * MOUSE_SPEED_Y; 698 | this.theta += 2 * Math.PI * this.rotateDelta.x / element.clientWidth * MOUSE_SPEED_X; 699 | 700 | // Prevent looking too far up or down. 701 | this.phi = Util.clamp(this.phi, -Math.PI/2, Math.PI/2); 702 | }; 703 | 704 | MouseKeyboardPositionSensorVRDevice.prototype.onMouseUp_ = function(e) { 705 | this.isDragging = false; 706 | }; 707 | 708 | MouseKeyboardPositionSensorVRDevice.prototype.isPointerLocked_ = function() { 709 | var el = document.pointerLockElement || document.mozPointerLockElement || 710 | document.webkitPointerLockElement; 711 | return el !== undefined; 712 | }; 713 | 714 | MouseKeyboardPositionSensorVRDevice.prototype.resetSensor = function() { 715 | console.error('Not implemented yet.'); 716 | }; 717 | 718 | module.exports = MouseKeyboardPositionSensorVRDevice; 719 | 720 | },{"./base.js":1,"./three-math.js":9,"./util.js":11}],7:[function(_dereq_,module,exports){ 721 | /* 722 | * Copyright 2015 Google Inc. All Rights Reserved. 723 | * Licensed under the Apache License, Version 2.0 (the "License"); 724 | * you may not use this file except in compliance with the License. 725 | * You may obtain a copy of the License at 726 | * 727 | * http://www.apache.org/licenses/LICENSE-2.0 728 | * 729 | * Unless required by applicable law or agreed to in writing, software 730 | * distributed under the License is distributed on an "AS IS" BASIS, 731 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 732 | * See the License for the specific language governing permissions and 733 | * limitations under the License. 734 | */ 735 | var THREE = _dereq_('./three-math.js'); 736 | 737 | var DEBUG = false; 738 | 739 | /** 740 | * Given an orientation and the gyroscope data, predicts the future orientation 741 | * of the head. This makes rendering appear faster. 742 | * 743 | * Also see: http://msl.cs.uiuc.edu/~lavalle/papers/LavYerKatAnt14.pdf 744 | * 745 | * @param {Number} predictionTimeS time from head movement to the appearance of 746 | * the corresponding image. 747 | */ 748 | function PosePredictor(predictionTimeS) { 749 | this.predictionTimeS = predictionTimeS; 750 | 751 | // The quaternion corresponding to the previous state. 752 | this.previousQ = new THREE.Quaternion(); 753 | // Previous time a prediction occurred. 754 | this.previousTimestampS = null; 755 | 756 | // The delta quaternion that adjusts the current pose. 757 | this.deltaQ = new THREE.Quaternion(); 758 | // The output quaternion. 759 | this.outQ = new THREE.Quaternion(); 760 | } 761 | 762 | PosePredictor.prototype.getPrediction = function(currentQ, gyro, timestampS) { 763 | if (!this.previousTimestampS) { 764 | this.previousQ.copy(currentQ); 765 | this.previousTimestampS = timestampS; 766 | return currentQ; 767 | } 768 | 769 | // Calculate axis and angle based on gyroscope rotation rate data. 770 | var axis = new THREE.Vector3(); 771 | axis.copy(gyro); 772 | axis.normalize(); 773 | 774 | var angularSpeed = gyro.length(); 775 | 776 | // If we're rotating slowly, don't do prediction. 777 | if (angularSpeed < THREE.Math.degToRad(20)) { 778 | if (DEBUG) { 779 | console.log('Moving slowly, at %s deg/s: no prediction', 780 | THREE.Math.radToDeg(angularSpeed).toFixed(1)); 781 | } 782 | this.outQ.copy(currentQ); 783 | this.previousQ.copy(currentQ); 784 | return this.outQ; 785 | } 786 | 787 | // Get the predicted angle based on the time delta and latency. 788 | var deltaT = timestampS - this.previousTimestampS; 789 | var predictAngle = angularSpeed * this.predictionTimeS; 790 | 791 | this.deltaQ.setFromAxisAngle(axis, predictAngle); 792 | this.outQ.copy(this.previousQ); 793 | this.outQ.multiply(this.deltaQ); 794 | 795 | this.previousQ.copy(currentQ); 796 | 797 | return this.outQ; 798 | }; 799 | 800 | 801 | module.exports = PosePredictor; 802 | 803 | },{"./three-math.js":9}],8:[function(_dereq_,module,exports){ 804 | function SensorSample(sample, timestampS) { 805 | this.set(sample, timestampS); 806 | }; 807 | 808 | SensorSample.prototype.set = function(sample, timestampS) { 809 | this.sample = sample; 810 | this.timestampS = timestampS; 811 | }; 812 | 813 | SensorSample.prototype.copy = function(sensorSample) { 814 | this.set(sensorSample.sample, sensorSample.timestampS); 815 | }; 816 | 817 | module.exports = SensorSample; 818 | 819 | },{}],9:[function(_dereq_,module,exports){ 820 | /* 821 | * A subset of THREE.js, providing mostly quaternion and euler-related 822 | * operations, manually lifted from 823 | * https://github.com/mrdoob/three.js/tree/master/src/math, as of 9c30286b38df039fca389989ff06ea1c15d6bad1 824 | */ 825 | 826 | // Only use if the real THREE is not provided. 827 | var THREE = window.THREE || {}; 828 | 829 | // If some piece of THREE is missing, fill it in here. 830 | if (!THREE.Quaternion || !THREE.Vector3 || !THREE.Vector2 || !THREE.Euler || !THREE.Math) { 831 | console.log('No THREE.js found.'); 832 | 833 | 834 | /*** START Quaternion ***/ 835 | 836 | /** 837 | * @author mikael emtinger / http://gomo.se/ 838 | * @author alteredq / http://alteredqualia.com/ 839 | * @author WestLangley / http://github.com/WestLangley 840 | * @author bhouston / http://exocortex.com 841 | */ 842 | 843 | THREE.Quaternion = function ( x, y, z, w ) { 844 | 845 | this._x = x || 0; 846 | this._y = y || 0; 847 | this._z = z || 0; 848 | this._w = ( w !== undefined ) ? w : 1; 849 | 850 | }; 851 | 852 | THREE.Quaternion.prototype = { 853 | 854 | constructor: THREE.Quaternion, 855 | 856 | _x: 0,_y: 0, _z: 0, _w: 0, 857 | 858 | get x () { 859 | 860 | return this._x; 861 | 862 | }, 863 | 864 | set x ( value ) { 865 | 866 | this._x = value; 867 | this.onChangeCallback(); 868 | 869 | }, 870 | 871 | get y () { 872 | 873 | return this._y; 874 | 875 | }, 876 | 877 | set y ( value ) { 878 | 879 | this._y = value; 880 | this.onChangeCallback(); 881 | 882 | }, 883 | 884 | get z () { 885 | 886 | return this._z; 887 | 888 | }, 889 | 890 | set z ( value ) { 891 | 892 | this._z = value; 893 | this.onChangeCallback(); 894 | 895 | }, 896 | 897 | get w () { 898 | 899 | return this._w; 900 | 901 | }, 902 | 903 | set w ( value ) { 904 | 905 | this._w = value; 906 | this.onChangeCallback(); 907 | 908 | }, 909 | 910 | set: function ( x, y, z, w ) { 911 | 912 | this._x = x; 913 | this._y = y; 914 | this._z = z; 915 | this._w = w; 916 | 917 | this.onChangeCallback(); 918 | 919 | return this; 920 | 921 | }, 922 | 923 | copy: function ( quaternion ) { 924 | 925 | this._x = quaternion.x; 926 | this._y = quaternion.y; 927 | this._z = quaternion.z; 928 | this._w = quaternion.w; 929 | 930 | this.onChangeCallback(); 931 | 932 | return this; 933 | 934 | }, 935 | 936 | setFromEuler: function ( euler, update ) { 937 | 938 | if ( euler instanceof THREE.Euler === false ) { 939 | 940 | throw new Error( 'THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.' ); 941 | } 942 | 943 | // http://www.mathworks.com/matlabcentral/fileexchange/ 944 | // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ 945 | // content/SpinCalc.m 946 | 947 | var c1 = Math.cos( euler._x / 2 ); 948 | var c2 = Math.cos( euler._y / 2 ); 949 | var c3 = Math.cos( euler._z / 2 ); 950 | var s1 = Math.sin( euler._x / 2 ); 951 | var s2 = Math.sin( euler._y / 2 ); 952 | var s3 = Math.sin( euler._z / 2 ); 953 | 954 | if ( euler.order === 'XYZ' ) { 955 | 956 | this._x = s1 * c2 * c3 + c1 * s2 * s3; 957 | this._y = c1 * s2 * c3 - s1 * c2 * s3; 958 | this._z = c1 * c2 * s3 + s1 * s2 * c3; 959 | this._w = c1 * c2 * c3 - s1 * s2 * s3; 960 | 961 | } else if ( euler.order === 'YXZ' ) { 962 | 963 | this._x = s1 * c2 * c3 + c1 * s2 * s3; 964 | this._y = c1 * s2 * c3 - s1 * c2 * s3; 965 | this._z = c1 * c2 * s3 - s1 * s2 * c3; 966 | this._w = c1 * c2 * c3 + s1 * s2 * s3; 967 | 968 | } else if ( euler.order === 'ZXY' ) { 969 | 970 | this._x = s1 * c2 * c3 - c1 * s2 * s3; 971 | this._y = c1 * s2 * c3 + s1 * c2 * s3; 972 | this._z = c1 * c2 * s3 + s1 * s2 * c3; 973 | this._w = c1 * c2 * c3 - s1 * s2 * s3; 974 | 975 | } else if ( euler.order === 'ZYX' ) { 976 | 977 | this._x = s1 * c2 * c3 - c1 * s2 * s3; 978 | this._y = c1 * s2 * c3 + s1 * c2 * s3; 979 | this._z = c1 * c2 * s3 - s1 * s2 * c3; 980 | this._w = c1 * c2 * c3 + s1 * s2 * s3; 981 | 982 | } else if ( euler.order === 'YZX' ) { 983 | 984 | this._x = s1 * c2 * c3 + c1 * s2 * s3; 985 | this._y = c1 * s2 * c3 + s1 * c2 * s3; 986 | this._z = c1 * c2 * s3 - s1 * s2 * c3; 987 | this._w = c1 * c2 * c3 - s1 * s2 * s3; 988 | 989 | } else if ( euler.order === 'XZY' ) { 990 | 991 | this._x = s1 * c2 * c3 - c1 * s2 * s3; 992 | this._y = c1 * s2 * c3 - s1 * c2 * s3; 993 | this._z = c1 * c2 * s3 + s1 * s2 * c3; 994 | this._w = c1 * c2 * c3 + s1 * s2 * s3; 995 | 996 | } 997 | 998 | if ( update !== false ) this.onChangeCallback(); 999 | 1000 | return this; 1001 | 1002 | }, 1003 | 1004 | setFromAxisAngle: function ( axis, angle ) { 1005 | 1006 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm 1007 | 1008 | // assumes axis is normalized 1009 | 1010 | var halfAngle = angle / 2, s = Math.sin( halfAngle ); 1011 | 1012 | this._x = axis.x * s; 1013 | this._y = axis.y * s; 1014 | this._z = axis.z * s; 1015 | this._w = Math.cos( halfAngle ); 1016 | 1017 | this.onChangeCallback(); 1018 | 1019 | return this; 1020 | 1021 | }, 1022 | 1023 | setFromRotationMatrix: function ( m ) { 1024 | 1025 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm 1026 | 1027 | // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) 1028 | 1029 | var te = m.elements, 1030 | 1031 | m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], 1032 | m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], 1033 | m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], 1034 | 1035 | trace = m11 + m22 + m33, 1036 | s; 1037 | 1038 | if ( trace > 0 ) { 1039 | 1040 | s = 0.5 / Math.sqrt( trace + 1.0 ); 1041 | 1042 | this._w = 0.25 / s; 1043 | this._x = ( m32 - m23 ) * s; 1044 | this._y = ( m13 - m31 ) * s; 1045 | this._z = ( m21 - m12 ) * s; 1046 | 1047 | } else if ( m11 > m22 && m11 > m33 ) { 1048 | 1049 | s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); 1050 | 1051 | this._w = ( m32 - m23 ) / s; 1052 | this._x = 0.25 * s; 1053 | this._y = ( m12 + m21 ) / s; 1054 | this._z = ( m13 + m31 ) / s; 1055 | 1056 | } else if ( m22 > m33 ) { 1057 | 1058 | s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); 1059 | 1060 | this._w = ( m13 - m31 ) / s; 1061 | this._x = ( m12 + m21 ) / s; 1062 | this._y = 0.25 * s; 1063 | this._z = ( m23 + m32 ) / s; 1064 | 1065 | } else { 1066 | 1067 | s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); 1068 | 1069 | this._w = ( m21 - m12 ) / s; 1070 | this._x = ( m13 + m31 ) / s; 1071 | this._y = ( m23 + m32 ) / s; 1072 | this._z = 0.25 * s; 1073 | 1074 | } 1075 | 1076 | this.onChangeCallback(); 1077 | 1078 | return this; 1079 | 1080 | }, 1081 | 1082 | setFromUnitVectors: function () { 1083 | 1084 | // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final 1085 | 1086 | // assumes direction vectors vFrom and vTo are normalized 1087 | 1088 | var v1, r; 1089 | 1090 | var EPS = 0.000001; 1091 | 1092 | return function ( vFrom, vTo ) { 1093 | 1094 | if ( v1 === undefined ) v1 = new THREE.Vector3(); 1095 | 1096 | r = vFrom.dot( vTo ) + 1; 1097 | 1098 | if ( r < EPS ) { 1099 | 1100 | r = 0; 1101 | 1102 | if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { 1103 | 1104 | v1.set( - vFrom.y, vFrom.x, 0 ); 1105 | 1106 | } else { 1107 | 1108 | v1.set( 0, - vFrom.z, vFrom.y ); 1109 | 1110 | } 1111 | 1112 | } else { 1113 | 1114 | v1.crossVectors( vFrom, vTo ); 1115 | 1116 | } 1117 | 1118 | this._x = v1.x; 1119 | this._y = v1.y; 1120 | this._z = v1.z; 1121 | this._w = r; 1122 | 1123 | this.normalize(); 1124 | 1125 | return this; 1126 | 1127 | } 1128 | 1129 | }(), 1130 | 1131 | inverse: function () { 1132 | 1133 | this.conjugate().normalize(); 1134 | 1135 | return this; 1136 | 1137 | }, 1138 | 1139 | conjugate: function () { 1140 | 1141 | this._x *= - 1; 1142 | this._y *= - 1; 1143 | this._z *= - 1; 1144 | 1145 | this.onChangeCallback(); 1146 | 1147 | return this; 1148 | 1149 | }, 1150 | 1151 | dot: function ( v ) { 1152 | 1153 | return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; 1154 | 1155 | }, 1156 | 1157 | lengthSq: function () { 1158 | 1159 | return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; 1160 | 1161 | }, 1162 | 1163 | length: function () { 1164 | 1165 | return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); 1166 | 1167 | }, 1168 | 1169 | normalize: function () { 1170 | 1171 | var l = this.length(); 1172 | 1173 | if ( l === 0 ) { 1174 | 1175 | this._x = 0; 1176 | this._y = 0; 1177 | this._z = 0; 1178 | this._w = 1; 1179 | 1180 | } else { 1181 | 1182 | l = 1 / l; 1183 | 1184 | this._x = this._x * l; 1185 | this._y = this._y * l; 1186 | this._z = this._z * l; 1187 | this._w = this._w * l; 1188 | 1189 | } 1190 | 1191 | this.onChangeCallback(); 1192 | 1193 | return this; 1194 | 1195 | }, 1196 | 1197 | multiply: function ( q, p ) { 1198 | 1199 | if ( p !== undefined ) { 1200 | 1201 | console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' ); 1202 | return this.multiplyQuaternions( q, p ); 1203 | 1204 | } 1205 | 1206 | return this.multiplyQuaternions( this, q ); 1207 | 1208 | }, 1209 | 1210 | multiplyQuaternions: function ( a, b ) { 1211 | 1212 | // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm 1213 | 1214 | var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; 1215 | var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; 1216 | 1217 | this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; 1218 | this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; 1219 | this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; 1220 | this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; 1221 | 1222 | this.onChangeCallback(); 1223 | 1224 | return this; 1225 | 1226 | }, 1227 | 1228 | multiplyVector3: function ( vector ) { 1229 | 1230 | console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' ); 1231 | return vector.applyQuaternion( this ); 1232 | 1233 | }, 1234 | 1235 | slerp: function ( qb, t ) { 1236 | 1237 | if ( t === 0 ) return this; 1238 | if ( t === 1 ) return this.copy( qb ); 1239 | 1240 | var x = this._x, y = this._y, z = this._z, w = this._w; 1241 | 1242 | // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ 1243 | 1244 | var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; 1245 | 1246 | if ( cosHalfTheta < 0 ) { 1247 | 1248 | this._w = - qb._w; 1249 | this._x = - qb._x; 1250 | this._y = - qb._y; 1251 | this._z = - qb._z; 1252 | 1253 | cosHalfTheta = - cosHalfTheta; 1254 | 1255 | } else { 1256 | 1257 | this.copy( qb ); 1258 | 1259 | } 1260 | 1261 | if ( cosHalfTheta >= 1.0 ) { 1262 | 1263 | this._w = w; 1264 | this._x = x; 1265 | this._y = y; 1266 | this._z = z; 1267 | 1268 | return this; 1269 | 1270 | } 1271 | 1272 | var halfTheta = Math.acos( cosHalfTheta ); 1273 | var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta ); 1274 | 1275 | if ( Math.abs( sinHalfTheta ) < 0.001 ) { 1276 | 1277 | this._w = 0.5 * ( w + this._w ); 1278 | this._x = 0.5 * ( x + this._x ); 1279 | this._y = 0.5 * ( y + this._y ); 1280 | this._z = 0.5 * ( z + this._z ); 1281 | 1282 | return this; 1283 | 1284 | } 1285 | 1286 | var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, 1287 | ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; 1288 | 1289 | this._w = ( w * ratioA + this._w * ratioB ); 1290 | this._x = ( x * ratioA + this._x * ratioB ); 1291 | this._y = ( y * ratioA + this._y * ratioB ); 1292 | this._z = ( z * ratioA + this._z * ratioB ); 1293 | 1294 | this.onChangeCallback(); 1295 | 1296 | return this; 1297 | 1298 | }, 1299 | 1300 | equals: function ( quaternion ) { 1301 | 1302 | return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); 1303 | 1304 | }, 1305 | 1306 | fromArray: function ( array, offset ) { 1307 | 1308 | if ( offset === undefined ) offset = 0; 1309 | 1310 | this._x = array[ offset ]; 1311 | this._y = array[ offset + 1 ]; 1312 | this._z = array[ offset + 2 ]; 1313 | this._w = array[ offset + 3 ]; 1314 | 1315 | this.onChangeCallback(); 1316 | 1317 | return this; 1318 | 1319 | }, 1320 | 1321 | toArray: function ( array, offset ) { 1322 | 1323 | if ( array === undefined ) array = []; 1324 | if ( offset === undefined ) offset = 0; 1325 | 1326 | array[ offset ] = this._x; 1327 | array[ offset + 1 ] = this._y; 1328 | array[ offset + 2 ] = this._z; 1329 | array[ offset + 3 ] = this._w; 1330 | 1331 | return array; 1332 | 1333 | }, 1334 | 1335 | onChange: function ( callback ) { 1336 | 1337 | this.onChangeCallback = callback; 1338 | 1339 | return this; 1340 | 1341 | }, 1342 | 1343 | onChangeCallback: function () {}, 1344 | 1345 | clone: function () { 1346 | 1347 | return new THREE.Quaternion( this._x, this._y, this._z, this._w ); 1348 | 1349 | } 1350 | 1351 | }; 1352 | 1353 | THREE.Quaternion.slerp = function ( qa, qb, qm, t ) { 1354 | 1355 | return qm.copy( qa ).slerp( qb, t ); 1356 | 1357 | } 1358 | 1359 | /*** END Quaternion ***/ 1360 | /*** START Vector2 ***/ 1361 | /** 1362 | * @author mrdoob / http://mrdoob.com/ 1363 | * @author philogb / http://blog.thejit.org/ 1364 | * @author egraether / http://egraether.com/ 1365 | * @author zz85 / http://www.lab4games.net/zz85/blog 1366 | */ 1367 | 1368 | THREE.Vector2 = function ( x, y ) { 1369 | 1370 | this.x = x || 0; 1371 | this.y = y || 0; 1372 | 1373 | }; 1374 | 1375 | THREE.Vector2.prototype = { 1376 | 1377 | constructor: THREE.Vector2, 1378 | 1379 | set: function ( x, y ) { 1380 | 1381 | this.x = x; 1382 | this.y = y; 1383 | 1384 | return this; 1385 | 1386 | }, 1387 | 1388 | setX: function ( x ) { 1389 | 1390 | this.x = x; 1391 | 1392 | return this; 1393 | 1394 | }, 1395 | 1396 | setY: function ( y ) { 1397 | 1398 | this.y = y; 1399 | 1400 | return this; 1401 | 1402 | }, 1403 | 1404 | setComponent: function ( index, value ) { 1405 | 1406 | switch ( index ) { 1407 | 1408 | case 0: this.x = value; break; 1409 | case 1: this.y = value; break; 1410 | default: throw new Error( 'index is out of range: ' + index ); 1411 | 1412 | } 1413 | 1414 | }, 1415 | 1416 | getComponent: function ( index ) { 1417 | 1418 | switch ( index ) { 1419 | 1420 | case 0: return this.x; 1421 | case 1: return this.y; 1422 | default: throw new Error( 'index is out of range: ' + index ); 1423 | 1424 | } 1425 | 1426 | }, 1427 | 1428 | copy: function ( v ) { 1429 | 1430 | this.x = v.x; 1431 | this.y = v.y; 1432 | 1433 | return this; 1434 | 1435 | }, 1436 | 1437 | add: function ( v, w ) { 1438 | 1439 | if ( w !== undefined ) { 1440 | 1441 | console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); 1442 | return this.addVectors( v, w ); 1443 | 1444 | } 1445 | 1446 | this.x += v.x; 1447 | this.y += v.y; 1448 | 1449 | return this; 1450 | 1451 | }, 1452 | 1453 | addVectors: function ( a, b ) { 1454 | 1455 | this.x = a.x + b.x; 1456 | this.y = a.y + b.y; 1457 | 1458 | return this; 1459 | 1460 | }, 1461 | 1462 | addScalar: function ( s ) { 1463 | 1464 | this.x += s; 1465 | this.y += s; 1466 | 1467 | return this; 1468 | 1469 | }, 1470 | 1471 | sub: function ( v, w ) { 1472 | 1473 | if ( w !== undefined ) { 1474 | 1475 | console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); 1476 | return this.subVectors( v, w ); 1477 | 1478 | } 1479 | 1480 | this.x -= v.x; 1481 | this.y -= v.y; 1482 | 1483 | return this; 1484 | 1485 | }, 1486 | 1487 | subVectors: function ( a, b ) { 1488 | 1489 | this.x = a.x - b.x; 1490 | this.y = a.y - b.y; 1491 | 1492 | return this; 1493 | 1494 | }, 1495 | 1496 | multiply: function ( v ) { 1497 | 1498 | this.x *= v.x; 1499 | this.y *= v.y; 1500 | 1501 | return this; 1502 | 1503 | }, 1504 | 1505 | multiplyScalar: function ( s ) { 1506 | 1507 | this.x *= s; 1508 | this.y *= s; 1509 | 1510 | return this; 1511 | 1512 | }, 1513 | 1514 | divide: function ( v ) { 1515 | 1516 | this.x /= v.x; 1517 | this.y /= v.y; 1518 | 1519 | return this; 1520 | 1521 | }, 1522 | 1523 | divideScalar: function ( scalar ) { 1524 | 1525 | if ( scalar !== 0 ) { 1526 | 1527 | var invScalar = 1 / scalar; 1528 | 1529 | this.x *= invScalar; 1530 | this.y *= invScalar; 1531 | 1532 | } else { 1533 | 1534 | this.x = 0; 1535 | this.y = 0; 1536 | 1537 | } 1538 | 1539 | return this; 1540 | 1541 | }, 1542 | 1543 | min: function ( v ) { 1544 | 1545 | if ( this.x > v.x ) { 1546 | 1547 | this.x = v.x; 1548 | 1549 | } 1550 | 1551 | if ( this.y > v.y ) { 1552 | 1553 | this.y = v.y; 1554 | 1555 | } 1556 | 1557 | return this; 1558 | 1559 | }, 1560 | 1561 | max: function ( v ) { 1562 | 1563 | if ( this.x < v.x ) { 1564 | 1565 | this.x = v.x; 1566 | 1567 | } 1568 | 1569 | if ( this.y < v.y ) { 1570 | 1571 | this.y = v.y; 1572 | 1573 | } 1574 | 1575 | return this; 1576 | 1577 | }, 1578 | 1579 | clamp: function ( min, max ) { 1580 | 1581 | // This function assumes min < max, if this assumption isn't true it will not operate correctly 1582 | 1583 | if ( this.x < min.x ) { 1584 | 1585 | this.x = min.x; 1586 | 1587 | } else if ( this.x > max.x ) { 1588 | 1589 | this.x = max.x; 1590 | 1591 | } 1592 | 1593 | if ( this.y < min.y ) { 1594 | 1595 | this.y = min.y; 1596 | 1597 | } else if ( this.y > max.y ) { 1598 | 1599 | this.y = max.y; 1600 | 1601 | } 1602 | 1603 | return this; 1604 | }, 1605 | 1606 | clampScalar: ( function () { 1607 | 1608 | var min, max; 1609 | 1610 | return function ( minVal, maxVal ) { 1611 | 1612 | if ( min === undefined ) { 1613 | 1614 | min = new THREE.Vector2(); 1615 | max = new THREE.Vector2(); 1616 | 1617 | } 1618 | 1619 | min.set( minVal, minVal ); 1620 | max.set( maxVal, maxVal ); 1621 | 1622 | return this.clamp( min, max ); 1623 | 1624 | }; 1625 | 1626 | } )(), 1627 | 1628 | floor: function () { 1629 | 1630 | this.x = Math.floor( this.x ); 1631 | this.y = Math.floor( this.y ); 1632 | 1633 | return this; 1634 | 1635 | }, 1636 | 1637 | ceil: function () { 1638 | 1639 | this.x = Math.ceil( this.x ); 1640 | this.y = Math.ceil( this.y ); 1641 | 1642 | return this; 1643 | 1644 | }, 1645 | 1646 | round: function () { 1647 | 1648 | this.x = Math.round( this.x ); 1649 | this.y = Math.round( this.y ); 1650 | 1651 | return this; 1652 | 1653 | }, 1654 | 1655 | roundToZero: function () { 1656 | 1657 | this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); 1658 | this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); 1659 | 1660 | return this; 1661 | 1662 | }, 1663 | 1664 | negate: function () { 1665 | 1666 | this.x = - this.x; 1667 | this.y = - this.y; 1668 | 1669 | return this; 1670 | 1671 | }, 1672 | 1673 | dot: function ( v ) { 1674 | 1675 | return this.x * v.x + this.y * v.y; 1676 | 1677 | }, 1678 | 1679 | lengthSq: function () { 1680 | 1681 | return this.x * this.x + this.y * this.y; 1682 | 1683 | }, 1684 | 1685 | length: function () { 1686 | 1687 | return Math.sqrt( this.x * this.x + this.y * this.y ); 1688 | 1689 | }, 1690 | 1691 | normalize: function () { 1692 | 1693 | return this.divideScalar( this.length() ); 1694 | 1695 | }, 1696 | 1697 | distanceTo: function ( v ) { 1698 | 1699 | return Math.sqrt( this.distanceToSquared( v ) ); 1700 | 1701 | }, 1702 | 1703 | distanceToSquared: function ( v ) { 1704 | 1705 | var dx = this.x - v.x, dy = this.y - v.y; 1706 | return dx * dx + dy * dy; 1707 | 1708 | }, 1709 | 1710 | setLength: function ( l ) { 1711 | 1712 | var oldLength = this.length(); 1713 | 1714 | if ( oldLength !== 0 && l !== oldLength ) { 1715 | 1716 | this.multiplyScalar( l / oldLength ); 1717 | } 1718 | 1719 | return this; 1720 | 1721 | }, 1722 | 1723 | lerp: function ( v, alpha ) { 1724 | 1725 | this.x += ( v.x - this.x ) * alpha; 1726 | this.y += ( v.y - this.y ) * alpha; 1727 | 1728 | return this; 1729 | 1730 | }, 1731 | 1732 | equals: function ( v ) { 1733 | 1734 | return ( ( v.x === this.x ) && ( v.y === this.y ) ); 1735 | 1736 | }, 1737 | 1738 | fromArray: function ( array, offset ) { 1739 | 1740 | if ( offset === undefined ) offset = 0; 1741 | 1742 | this.x = array[ offset ]; 1743 | this.y = array[ offset + 1 ]; 1744 | 1745 | return this; 1746 | 1747 | }, 1748 | 1749 | toArray: function ( array, offset ) { 1750 | 1751 | if ( array === undefined ) array = []; 1752 | if ( offset === undefined ) offset = 0; 1753 | 1754 | array[ offset ] = this.x; 1755 | array[ offset + 1 ] = this.y; 1756 | 1757 | return array; 1758 | 1759 | }, 1760 | 1761 | fromAttribute: function ( attribute, index, offset ) { 1762 | 1763 | if ( offset === undefined ) offset = 0; 1764 | 1765 | index = index * attribute.itemSize + offset; 1766 | 1767 | this.x = attribute.array[ index ]; 1768 | this.y = attribute.array[ index + 1 ]; 1769 | 1770 | return this; 1771 | 1772 | }, 1773 | 1774 | clone: function () { 1775 | 1776 | return new THREE.Vector2( this.x, this.y ); 1777 | 1778 | } 1779 | 1780 | }; 1781 | /*** END Vector2 ***/ 1782 | /*** START Vector3 ***/ 1783 | 1784 | /** 1785 | * @author mrdoob / http://mrdoob.com/ 1786 | * @author *kile / http://kile.stravaganza.org/ 1787 | * @author philogb / http://blog.thejit.org/ 1788 | * @author mikael emtinger / http://gomo.se/ 1789 | * @author egraether / http://egraether.com/ 1790 | * @author WestLangley / http://github.com/WestLangley 1791 | */ 1792 | 1793 | THREE.Vector3 = function ( x, y, z ) { 1794 | 1795 | this.x = x || 0; 1796 | this.y = y || 0; 1797 | this.z = z || 0; 1798 | 1799 | }; 1800 | 1801 | THREE.Vector3.prototype = { 1802 | 1803 | constructor: THREE.Vector3, 1804 | 1805 | set: function ( x, y, z ) { 1806 | 1807 | this.x = x; 1808 | this.y = y; 1809 | this.z = z; 1810 | 1811 | return this; 1812 | 1813 | }, 1814 | 1815 | setX: function ( x ) { 1816 | 1817 | this.x = x; 1818 | 1819 | return this; 1820 | 1821 | }, 1822 | 1823 | setY: function ( y ) { 1824 | 1825 | this.y = y; 1826 | 1827 | return this; 1828 | 1829 | }, 1830 | 1831 | setZ: function ( z ) { 1832 | 1833 | this.z = z; 1834 | 1835 | return this; 1836 | 1837 | }, 1838 | 1839 | setComponent: function ( index, value ) { 1840 | 1841 | switch ( index ) { 1842 | 1843 | case 0: this.x = value; break; 1844 | case 1: this.y = value; break; 1845 | case 2: this.z = value; break; 1846 | default: throw new Error( 'index is out of range: ' + index ); 1847 | 1848 | } 1849 | 1850 | }, 1851 | 1852 | getComponent: function ( index ) { 1853 | 1854 | switch ( index ) { 1855 | 1856 | case 0: return this.x; 1857 | case 1: return this.y; 1858 | case 2: return this.z; 1859 | default: throw new Error( 'index is out of range: ' + index ); 1860 | 1861 | } 1862 | 1863 | }, 1864 | 1865 | copy: function ( v ) { 1866 | 1867 | this.x = v.x; 1868 | this.y = v.y; 1869 | this.z = v.z; 1870 | 1871 | return this; 1872 | 1873 | }, 1874 | 1875 | add: function ( v, w ) { 1876 | 1877 | if ( w !== undefined ) { 1878 | 1879 | console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); 1880 | return this.addVectors( v, w ); 1881 | 1882 | } 1883 | 1884 | this.x += v.x; 1885 | this.y += v.y; 1886 | this.z += v.z; 1887 | 1888 | return this; 1889 | 1890 | }, 1891 | 1892 | addScalar: function ( s ) { 1893 | 1894 | this.x += s; 1895 | this.y += s; 1896 | this.z += s; 1897 | 1898 | return this; 1899 | 1900 | }, 1901 | 1902 | addVectors: function ( a, b ) { 1903 | 1904 | this.x = a.x + b.x; 1905 | this.y = a.y + b.y; 1906 | this.z = a.z + b.z; 1907 | 1908 | return this; 1909 | 1910 | }, 1911 | 1912 | sub: function ( v, w ) { 1913 | 1914 | if ( w !== undefined ) { 1915 | 1916 | console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); 1917 | return this.subVectors( v, w ); 1918 | 1919 | } 1920 | 1921 | this.x -= v.x; 1922 | this.y -= v.y; 1923 | this.z -= v.z; 1924 | 1925 | return this; 1926 | 1927 | }, 1928 | 1929 | subVectors: function ( a, b ) { 1930 | 1931 | this.x = a.x - b.x; 1932 | this.y = a.y - b.y; 1933 | this.z = a.z - b.z; 1934 | 1935 | return this; 1936 | 1937 | }, 1938 | 1939 | multiply: function ( v, w ) { 1940 | 1941 | if ( w !== undefined ) { 1942 | 1943 | console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' ); 1944 | return this.multiplyVectors( v, w ); 1945 | 1946 | } 1947 | 1948 | this.x *= v.x; 1949 | this.y *= v.y; 1950 | this.z *= v.z; 1951 | 1952 | return this; 1953 | 1954 | }, 1955 | 1956 | multiplyScalar: function ( scalar ) { 1957 | 1958 | this.x *= scalar; 1959 | this.y *= scalar; 1960 | this.z *= scalar; 1961 | 1962 | return this; 1963 | 1964 | }, 1965 | 1966 | multiplyVectors: function ( a, b ) { 1967 | 1968 | this.x = a.x * b.x; 1969 | this.y = a.y * b.y; 1970 | this.z = a.z * b.z; 1971 | 1972 | return this; 1973 | 1974 | }, 1975 | 1976 | applyEuler: function () { 1977 | 1978 | var quaternion; 1979 | 1980 | return function ( euler ) { 1981 | 1982 | if ( euler instanceof THREE.Euler === false ) { 1983 | 1984 | console.error( 'THREE.Vector3: .applyEuler() now expects a Euler rotation rather than a Vector3 and order.' ); 1985 | 1986 | } 1987 | 1988 | if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); 1989 | 1990 | this.applyQuaternion( quaternion.setFromEuler( euler ) ); 1991 | 1992 | return this; 1993 | 1994 | }; 1995 | 1996 | }(), 1997 | 1998 | applyAxisAngle: function () { 1999 | 2000 | var quaternion; 2001 | 2002 | return function ( axis, angle ) { 2003 | 2004 | if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); 2005 | 2006 | this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); 2007 | 2008 | return this; 2009 | 2010 | }; 2011 | 2012 | }(), 2013 | 2014 | applyMatrix3: function ( m ) { 2015 | 2016 | var x = this.x; 2017 | var y = this.y; 2018 | var z = this.z; 2019 | 2020 | var e = m.elements; 2021 | 2022 | this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; 2023 | this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; 2024 | this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; 2025 | 2026 | return this; 2027 | 2028 | }, 2029 | 2030 | applyMatrix4: function ( m ) { 2031 | 2032 | // input: THREE.Matrix4 affine matrix 2033 | 2034 | var x = this.x, y = this.y, z = this.z; 2035 | 2036 | var e = m.elements; 2037 | 2038 | this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ]; 2039 | this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ]; 2040 | this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ]; 2041 | 2042 | return this; 2043 | 2044 | }, 2045 | 2046 | applyProjection: function ( m ) { 2047 | 2048 | // input: THREE.Matrix4 projection matrix 2049 | 2050 | var x = this.x, y = this.y, z = this.z; 2051 | 2052 | var e = m.elements; 2053 | var d = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); // perspective divide 2054 | 2055 | this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * d; 2056 | this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * d; 2057 | this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * d; 2058 | 2059 | return this; 2060 | 2061 | }, 2062 | 2063 | applyQuaternion: function ( q ) { 2064 | 2065 | var x = this.x; 2066 | var y = this.y; 2067 | var z = this.z; 2068 | 2069 | var qx = q.x; 2070 | var qy = q.y; 2071 | var qz = q.z; 2072 | var qw = q.w; 2073 | 2074 | // calculate quat * vector 2075 | 2076 | var ix = qw * x + qy * z - qz * y; 2077 | var iy = qw * y + qz * x - qx * z; 2078 | var iz = qw * z + qx * y - qy * x; 2079 | var iw = - qx * x - qy * y - qz * z; 2080 | 2081 | // calculate result * inverse quat 2082 | 2083 | this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; 2084 | this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; 2085 | this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; 2086 | 2087 | return this; 2088 | 2089 | }, 2090 | 2091 | project: function () { 2092 | 2093 | var matrix; 2094 | 2095 | return function ( camera ) { 2096 | 2097 | if ( matrix === undefined ) matrix = new THREE.Matrix4(); 2098 | 2099 | matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) ); 2100 | return this.applyProjection( matrix ); 2101 | 2102 | }; 2103 | 2104 | }(), 2105 | 2106 | unproject: function () { 2107 | 2108 | var matrix; 2109 | 2110 | return function ( camera ) { 2111 | 2112 | if ( matrix === undefined ) matrix = new THREE.Matrix4(); 2113 | 2114 | matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) ); 2115 | return this.applyProjection( matrix ); 2116 | 2117 | }; 2118 | 2119 | }(), 2120 | 2121 | transformDirection: function ( m ) { 2122 | 2123 | // input: THREE.Matrix4 affine matrix 2124 | // vector interpreted as a direction 2125 | 2126 | var x = this.x, y = this.y, z = this.z; 2127 | 2128 | var e = m.elements; 2129 | 2130 | this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; 2131 | this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; 2132 | this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; 2133 | 2134 | this.normalize(); 2135 | 2136 | return this; 2137 | 2138 | }, 2139 | 2140 | divide: function ( v ) { 2141 | 2142 | this.x /= v.x; 2143 | this.y /= v.y; 2144 | this.z /= v.z; 2145 | 2146 | return this; 2147 | 2148 | }, 2149 | 2150 | divideScalar: function ( scalar ) { 2151 | 2152 | if ( scalar !== 0 ) { 2153 | 2154 | var invScalar = 1 / scalar; 2155 | 2156 | this.x *= invScalar; 2157 | this.y *= invScalar; 2158 | this.z *= invScalar; 2159 | 2160 | } else { 2161 | 2162 | this.x = 0; 2163 | this.y = 0; 2164 | this.z = 0; 2165 | 2166 | } 2167 | 2168 | return this; 2169 | 2170 | }, 2171 | 2172 | min: function ( v ) { 2173 | 2174 | if ( this.x > v.x ) { 2175 | 2176 | this.x = v.x; 2177 | 2178 | } 2179 | 2180 | if ( this.y > v.y ) { 2181 | 2182 | this.y = v.y; 2183 | 2184 | } 2185 | 2186 | if ( this.z > v.z ) { 2187 | 2188 | this.z = v.z; 2189 | 2190 | } 2191 | 2192 | return this; 2193 | 2194 | }, 2195 | 2196 | max: function ( v ) { 2197 | 2198 | if ( this.x < v.x ) { 2199 | 2200 | this.x = v.x; 2201 | 2202 | } 2203 | 2204 | if ( this.y < v.y ) { 2205 | 2206 | this.y = v.y; 2207 | 2208 | } 2209 | 2210 | if ( this.z < v.z ) { 2211 | 2212 | this.z = v.z; 2213 | 2214 | } 2215 | 2216 | return this; 2217 | 2218 | }, 2219 | 2220 | clamp: function ( min, max ) { 2221 | 2222 | // This function assumes min < max, if this assumption isn't true it will not operate correctly 2223 | 2224 | if ( this.x < min.x ) { 2225 | 2226 | this.x = min.x; 2227 | 2228 | } else if ( this.x > max.x ) { 2229 | 2230 | this.x = max.x; 2231 | 2232 | } 2233 | 2234 | if ( this.y < min.y ) { 2235 | 2236 | this.y = min.y; 2237 | 2238 | } else if ( this.y > max.y ) { 2239 | 2240 | this.y = max.y; 2241 | 2242 | } 2243 | 2244 | if ( this.z < min.z ) { 2245 | 2246 | this.z = min.z; 2247 | 2248 | } else if ( this.z > max.z ) { 2249 | 2250 | this.z = max.z; 2251 | 2252 | } 2253 | 2254 | return this; 2255 | 2256 | }, 2257 | 2258 | clampScalar: ( function () { 2259 | 2260 | var min, max; 2261 | 2262 | return function ( minVal, maxVal ) { 2263 | 2264 | if ( min === undefined ) { 2265 | 2266 | min = new THREE.Vector3(); 2267 | max = new THREE.Vector3(); 2268 | 2269 | } 2270 | 2271 | min.set( minVal, minVal, minVal ); 2272 | max.set( maxVal, maxVal, maxVal ); 2273 | 2274 | return this.clamp( min, max ); 2275 | 2276 | }; 2277 | 2278 | } )(), 2279 | 2280 | floor: function () { 2281 | 2282 | this.x = Math.floor( this.x ); 2283 | this.y = Math.floor( this.y ); 2284 | this.z = Math.floor( this.z ); 2285 | 2286 | return this; 2287 | 2288 | }, 2289 | 2290 | ceil: function () { 2291 | 2292 | this.x = Math.ceil( this.x ); 2293 | this.y = Math.ceil( this.y ); 2294 | this.z = Math.ceil( this.z ); 2295 | 2296 | return this; 2297 | 2298 | }, 2299 | 2300 | round: function () { 2301 | 2302 | this.x = Math.round( this.x ); 2303 | this.y = Math.round( this.y ); 2304 | this.z = Math.round( this.z ); 2305 | 2306 | return this; 2307 | 2308 | }, 2309 | 2310 | roundToZero: function () { 2311 | 2312 | this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); 2313 | this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); 2314 | this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); 2315 | 2316 | return this; 2317 | 2318 | }, 2319 | 2320 | negate: function () { 2321 | 2322 | this.x = - this.x; 2323 | this.y = - this.y; 2324 | this.z = - this.z; 2325 | 2326 | return this; 2327 | 2328 | }, 2329 | 2330 | dot: function ( v ) { 2331 | 2332 | return this.x * v.x + this.y * v.y + this.z * v.z; 2333 | 2334 | }, 2335 | 2336 | lengthSq: function () { 2337 | 2338 | return this.x * this.x + this.y * this.y + this.z * this.z; 2339 | 2340 | }, 2341 | 2342 | length: function () { 2343 | 2344 | return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); 2345 | 2346 | }, 2347 | 2348 | lengthManhattan: function () { 2349 | 2350 | return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); 2351 | 2352 | }, 2353 | 2354 | normalize: function () { 2355 | 2356 | return this.divideScalar( this.length() ); 2357 | 2358 | }, 2359 | 2360 | setLength: function ( l ) { 2361 | 2362 | var oldLength = this.length(); 2363 | 2364 | if ( oldLength !== 0 && l !== oldLength ) { 2365 | 2366 | this.multiplyScalar( l / oldLength ); 2367 | } 2368 | 2369 | return this; 2370 | 2371 | }, 2372 | 2373 | lerp: function ( v, alpha ) { 2374 | 2375 | this.x += ( v.x - this.x ) * alpha; 2376 | this.y += ( v.y - this.y ) * alpha; 2377 | this.z += ( v.z - this.z ) * alpha; 2378 | 2379 | return this; 2380 | 2381 | }, 2382 | 2383 | cross: function ( v, w ) { 2384 | 2385 | if ( w !== undefined ) { 2386 | 2387 | console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' ); 2388 | return this.crossVectors( v, w ); 2389 | 2390 | } 2391 | 2392 | var x = this.x, y = this.y, z = this.z; 2393 | 2394 | this.x = y * v.z - z * v.y; 2395 | this.y = z * v.x - x * v.z; 2396 | this.z = x * v.y - y * v.x; 2397 | 2398 | return this; 2399 | 2400 | }, 2401 | 2402 | crossVectors: function ( a, b ) { 2403 | 2404 | var ax = a.x, ay = a.y, az = a.z; 2405 | var bx = b.x, by = b.y, bz = b.z; 2406 | 2407 | this.x = ay * bz - az * by; 2408 | this.y = az * bx - ax * bz; 2409 | this.z = ax * by - ay * bx; 2410 | 2411 | return this; 2412 | 2413 | }, 2414 | 2415 | projectOnVector: function () { 2416 | 2417 | var v1, dot; 2418 | 2419 | return function ( vector ) { 2420 | 2421 | if ( v1 === undefined ) v1 = new THREE.Vector3(); 2422 | 2423 | v1.copy( vector ).normalize(); 2424 | 2425 | dot = this.dot( v1 ); 2426 | 2427 | return this.copy( v1 ).multiplyScalar( dot ); 2428 | 2429 | }; 2430 | 2431 | }(), 2432 | 2433 | projectOnPlane: function () { 2434 | 2435 | var v1; 2436 | 2437 | return function ( planeNormal ) { 2438 | 2439 | if ( v1 === undefined ) v1 = new THREE.Vector3(); 2440 | 2441 | v1.copy( this ).projectOnVector( planeNormal ); 2442 | 2443 | return this.sub( v1 ); 2444 | 2445 | } 2446 | 2447 | }(), 2448 | 2449 | reflect: function () { 2450 | 2451 | // reflect incident vector off plane orthogonal to normal 2452 | // normal is assumed to have unit length 2453 | 2454 | var v1; 2455 | 2456 | return function ( normal ) { 2457 | 2458 | if ( v1 === undefined ) v1 = new THREE.Vector3(); 2459 | 2460 | return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); 2461 | 2462 | } 2463 | 2464 | }(), 2465 | 2466 | angleTo: function ( v ) { 2467 | 2468 | var theta = this.dot( v ) / ( this.length() * v.length() ); 2469 | 2470 | // clamp, to handle numerical problems 2471 | 2472 | return Math.acos( THREE.Math.clamp( theta, - 1, 1 ) ); 2473 | 2474 | }, 2475 | 2476 | distanceTo: function ( v ) { 2477 | 2478 | return Math.sqrt( this.distanceToSquared( v ) ); 2479 | 2480 | }, 2481 | 2482 | distanceToSquared: function ( v ) { 2483 | 2484 | var dx = this.x - v.x; 2485 | var dy = this.y - v.y; 2486 | var dz = this.z - v.z; 2487 | 2488 | return dx * dx + dy * dy + dz * dz; 2489 | 2490 | }, 2491 | 2492 | setEulerFromRotationMatrix: function ( m, order ) { 2493 | 2494 | console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' ); 2495 | 2496 | }, 2497 | 2498 | setEulerFromQuaternion: function ( q, order ) { 2499 | 2500 | console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' ); 2501 | 2502 | }, 2503 | 2504 | getPositionFromMatrix: function ( m ) { 2505 | 2506 | console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' ); 2507 | 2508 | return this.setFromMatrixPosition( m ); 2509 | 2510 | }, 2511 | 2512 | getScaleFromMatrix: function ( m ) { 2513 | 2514 | console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' ); 2515 | 2516 | return this.setFromMatrixScale( m ); 2517 | }, 2518 | 2519 | getColumnFromMatrix: function ( index, matrix ) { 2520 | 2521 | console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' ); 2522 | 2523 | return this.setFromMatrixColumn( index, matrix ); 2524 | 2525 | }, 2526 | 2527 | setFromMatrixPosition: function ( m ) { 2528 | 2529 | this.x = m.elements[ 12 ]; 2530 | this.y = m.elements[ 13 ]; 2531 | this.z = m.elements[ 14 ]; 2532 | 2533 | return this; 2534 | 2535 | }, 2536 | 2537 | setFromMatrixScale: function ( m ) { 2538 | 2539 | var sx = this.set( m.elements[ 0 ], m.elements[ 1 ], m.elements[ 2 ] ).length(); 2540 | var sy = this.set( m.elements[ 4 ], m.elements[ 5 ], m.elements[ 6 ] ).length(); 2541 | var sz = this.set( m.elements[ 8 ], m.elements[ 9 ], m.elements[ 10 ] ).length(); 2542 | 2543 | this.x = sx; 2544 | this.y = sy; 2545 | this.z = sz; 2546 | 2547 | return this; 2548 | }, 2549 | 2550 | setFromMatrixColumn: function ( index, matrix ) { 2551 | 2552 | var offset = index * 4; 2553 | 2554 | var me = matrix.elements; 2555 | 2556 | this.x = me[ offset ]; 2557 | this.y = me[ offset + 1 ]; 2558 | this.z = me[ offset + 2 ]; 2559 | 2560 | return this; 2561 | 2562 | }, 2563 | 2564 | equals: function ( v ) { 2565 | 2566 | return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); 2567 | 2568 | }, 2569 | 2570 | fromArray: function ( array, offset ) { 2571 | 2572 | if ( offset === undefined ) offset = 0; 2573 | 2574 | this.x = array[ offset ]; 2575 | this.y = array[ offset + 1 ]; 2576 | this.z = array[ offset + 2 ]; 2577 | 2578 | return this; 2579 | 2580 | }, 2581 | 2582 | toArray: function ( array, offset ) { 2583 | 2584 | if ( array === undefined ) array = []; 2585 | if ( offset === undefined ) offset = 0; 2586 | 2587 | array[ offset ] = this.x; 2588 | array[ offset + 1 ] = this.y; 2589 | array[ offset + 2 ] = this.z; 2590 | 2591 | return array; 2592 | 2593 | }, 2594 | 2595 | fromAttribute: function ( attribute, index, offset ) { 2596 | 2597 | if ( offset === undefined ) offset = 0; 2598 | 2599 | index = index * attribute.itemSize + offset; 2600 | 2601 | this.x = attribute.array[ index ]; 2602 | this.y = attribute.array[ index + 1 ]; 2603 | this.z = attribute.array[ index + 2 ]; 2604 | 2605 | return this; 2606 | 2607 | }, 2608 | 2609 | clone: function () { 2610 | 2611 | return new THREE.Vector3( this.x, this.y, this.z ); 2612 | 2613 | } 2614 | 2615 | }; 2616 | /*** END Vector3 ***/ 2617 | /*** START Euler ***/ 2618 | /** 2619 | * @author mrdoob / http://mrdoob.com/ 2620 | * @author WestLangley / http://github.com/WestLangley 2621 | * @author bhouston / http://exocortex.com 2622 | */ 2623 | 2624 | THREE.Euler = function ( x, y, z, order ) { 2625 | 2626 | this._x = x || 0; 2627 | this._y = y || 0; 2628 | this._z = z || 0; 2629 | this._order = order || THREE.Euler.DefaultOrder; 2630 | 2631 | }; 2632 | 2633 | THREE.Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ]; 2634 | 2635 | THREE.Euler.DefaultOrder = 'XYZ'; 2636 | 2637 | THREE.Euler.prototype = { 2638 | 2639 | constructor: THREE.Euler, 2640 | 2641 | _x: 0, _y: 0, _z: 0, _order: THREE.Euler.DefaultOrder, 2642 | 2643 | get x () { 2644 | 2645 | return this._x; 2646 | 2647 | }, 2648 | 2649 | set x ( value ) { 2650 | 2651 | this._x = value; 2652 | this.onChangeCallback(); 2653 | 2654 | }, 2655 | 2656 | get y () { 2657 | 2658 | return this._y; 2659 | 2660 | }, 2661 | 2662 | set y ( value ) { 2663 | 2664 | this._y = value; 2665 | this.onChangeCallback(); 2666 | 2667 | }, 2668 | 2669 | get z () { 2670 | 2671 | return this._z; 2672 | 2673 | }, 2674 | 2675 | set z ( value ) { 2676 | 2677 | this._z = value; 2678 | this.onChangeCallback(); 2679 | 2680 | }, 2681 | 2682 | get order () { 2683 | 2684 | return this._order; 2685 | 2686 | }, 2687 | 2688 | set order ( value ) { 2689 | 2690 | this._order = value; 2691 | this.onChangeCallback(); 2692 | 2693 | }, 2694 | 2695 | set: function ( x, y, z, order ) { 2696 | 2697 | this._x = x; 2698 | this._y = y; 2699 | this._z = z; 2700 | this._order = order || this._order; 2701 | 2702 | this.onChangeCallback(); 2703 | 2704 | return this; 2705 | 2706 | }, 2707 | 2708 | copy: function ( euler ) { 2709 | 2710 | this._x = euler._x; 2711 | this._y = euler._y; 2712 | this._z = euler._z; 2713 | this._order = euler._order; 2714 | 2715 | this.onChangeCallback(); 2716 | 2717 | return this; 2718 | 2719 | }, 2720 | 2721 | setFromRotationMatrix: function ( m, order, update ) { 2722 | 2723 | var clamp = THREE.Math.clamp; 2724 | 2725 | // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) 2726 | 2727 | var te = m.elements; 2728 | var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; 2729 | var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; 2730 | var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; 2731 | 2732 | order = order || this._order; 2733 | 2734 | if ( order === 'XYZ' ) { 2735 | 2736 | this._y = Math.asin( clamp( m13, - 1, 1 ) ); 2737 | 2738 | if ( Math.abs( m13 ) < 0.99999 ) { 2739 | 2740 | this._x = Math.atan2( - m23, m33 ); 2741 | this._z = Math.atan2( - m12, m11 ); 2742 | 2743 | } else { 2744 | 2745 | this._x = Math.atan2( m32, m22 ); 2746 | this._z = 0; 2747 | 2748 | } 2749 | 2750 | } else if ( order === 'YXZ' ) { 2751 | 2752 | this._x = Math.asin( - clamp( m23, - 1, 1 ) ); 2753 | 2754 | if ( Math.abs( m23 ) < 0.99999 ) { 2755 | 2756 | this._y = Math.atan2( m13, m33 ); 2757 | this._z = Math.atan2( m21, m22 ); 2758 | 2759 | } else { 2760 | 2761 | this._y = Math.atan2( - m31, m11 ); 2762 | this._z = 0; 2763 | 2764 | } 2765 | 2766 | } else if ( order === 'ZXY' ) { 2767 | 2768 | this._x = Math.asin( clamp( m32, - 1, 1 ) ); 2769 | 2770 | if ( Math.abs( m32 ) < 0.99999 ) { 2771 | 2772 | this._y = Math.atan2( - m31, m33 ); 2773 | this._z = Math.atan2( - m12, m22 ); 2774 | 2775 | } else { 2776 | 2777 | this._y = 0; 2778 | this._z = Math.atan2( m21, m11 ); 2779 | 2780 | } 2781 | 2782 | } else if ( order === 'ZYX' ) { 2783 | 2784 | this._y = Math.asin( - clamp( m31, - 1, 1 ) ); 2785 | 2786 | if ( Math.abs( m31 ) < 0.99999 ) { 2787 | 2788 | this._x = Math.atan2( m32, m33 ); 2789 | this._z = Math.atan2( m21, m11 ); 2790 | 2791 | } else { 2792 | 2793 | this._x = 0; 2794 | this._z = Math.atan2( - m12, m22 ); 2795 | 2796 | } 2797 | 2798 | } else if ( order === 'YZX' ) { 2799 | 2800 | this._z = Math.asin( clamp( m21, - 1, 1 ) ); 2801 | 2802 | if ( Math.abs( m21 ) < 0.99999 ) { 2803 | 2804 | this._x = Math.atan2( - m23, m22 ); 2805 | this._y = Math.atan2( - m31, m11 ); 2806 | 2807 | } else { 2808 | 2809 | this._x = 0; 2810 | this._y = Math.atan2( m13, m33 ); 2811 | 2812 | } 2813 | 2814 | } else if ( order === 'XZY' ) { 2815 | 2816 | this._z = Math.asin( - clamp( m12, - 1, 1 ) ); 2817 | 2818 | if ( Math.abs( m12 ) < 0.99999 ) { 2819 | 2820 | this._x = Math.atan2( m32, m22 ); 2821 | this._y = Math.atan2( m13, m11 ); 2822 | 2823 | } else { 2824 | 2825 | this._x = Math.atan2( - m23, m33 ); 2826 | this._y = 0; 2827 | 2828 | } 2829 | 2830 | } else { 2831 | 2832 | console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order ) 2833 | 2834 | } 2835 | 2836 | this._order = order; 2837 | 2838 | if ( update !== false ) this.onChangeCallback(); 2839 | 2840 | return this; 2841 | 2842 | }, 2843 | 2844 | setFromQuaternion: function () { 2845 | 2846 | var matrix; 2847 | 2848 | return function ( q, order, update ) { 2849 | 2850 | if ( matrix === undefined ) matrix = new THREE.Matrix4(); 2851 | matrix.makeRotationFromQuaternion( q ); 2852 | this.setFromRotationMatrix( matrix, order, update ); 2853 | 2854 | return this; 2855 | 2856 | }; 2857 | 2858 | }(), 2859 | 2860 | setFromVector3: function ( v, order ) { 2861 | 2862 | return this.set( v.x, v.y, v.z, order || this._order ); 2863 | 2864 | }, 2865 | 2866 | reorder: function () { 2867 | 2868 | // WARNING: this discards revolution information -bhouston 2869 | 2870 | var q = new THREE.Quaternion(); 2871 | 2872 | return function ( newOrder ) { 2873 | 2874 | q.setFromEuler( this ); 2875 | this.setFromQuaternion( q, newOrder ); 2876 | 2877 | }; 2878 | 2879 | }(), 2880 | 2881 | equals: function ( euler ) { 2882 | 2883 | return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); 2884 | 2885 | }, 2886 | 2887 | fromArray: function ( array ) { 2888 | 2889 | this._x = array[ 0 ]; 2890 | this._y = array[ 1 ]; 2891 | this._z = array[ 2 ]; 2892 | if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; 2893 | 2894 | this.onChangeCallback(); 2895 | 2896 | return this; 2897 | 2898 | }, 2899 | 2900 | toArray: function () { 2901 | 2902 | return [ this._x, this._y, this._z, this._order ]; 2903 | 2904 | }, 2905 | 2906 | toVector3: function ( optionalResult ) { 2907 | 2908 | if ( optionalResult ) { 2909 | 2910 | return optionalResult.set( this._x, this._y, this._z ); 2911 | 2912 | } else { 2913 | 2914 | return new THREE.Vector3( this._x, this._y, this._z ); 2915 | 2916 | } 2917 | 2918 | }, 2919 | 2920 | onChange: function ( callback ) { 2921 | 2922 | this.onChangeCallback = callback; 2923 | 2924 | return this; 2925 | 2926 | }, 2927 | 2928 | onChangeCallback: function () {}, 2929 | 2930 | clone: function () { 2931 | 2932 | return new THREE.Euler( this._x, this._y, this._z, this._order ); 2933 | 2934 | } 2935 | 2936 | }; 2937 | /*** END Euler ***/ 2938 | /*** START Math ***/ 2939 | /** 2940 | * @author alteredq / http://alteredqualia.com/ 2941 | * @author mrdoob / http://mrdoob.com/ 2942 | */ 2943 | 2944 | THREE.Math = { 2945 | 2946 | generateUUID: function () { 2947 | 2948 | // http://www.broofa.com/Tools/Math.uuid.htm 2949 | 2950 | var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split( '' ); 2951 | var uuid = new Array( 36 ); 2952 | var rnd = 0, r; 2953 | 2954 | return function () { 2955 | 2956 | for ( var i = 0; i < 36; i ++ ) { 2957 | 2958 | if ( i == 8 || i == 13 || i == 18 || i == 23 ) { 2959 | 2960 | uuid[ i ] = '-'; 2961 | 2962 | } else if ( i == 14 ) { 2963 | 2964 | uuid[ i ] = '4'; 2965 | 2966 | } else { 2967 | 2968 | if ( rnd <= 0x02 ) rnd = 0x2000000 + ( Math.random() * 0x1000000 ) | 0; 2969 | r = rnd & 0xf; 2970 | rnd = rnd >> 4; 2971 | uuid[ i ] = chars[ ( i == 19 ) ? ( r & 0x3 ) | 0x8 : r ]; 2972 | 2973 | } 2974 | } 2975 | 2976 | return uuid.join( '' ); 2977 | 2978 | }; 2979 | 2980 | }(), 2981 | 2982 | // Clamp value to range 2983 | 2984 | clamp: function ( x, a, b ) { 2985 | 2986 | return ( x < a ) ? a : ( ( x > b ) ? b : x ); 2987 | 2988 | }, 2989 | 2990 | // Clamp value to range to range 2999 | 3000 | mapLinear: function ( x, a1, a2, b1, b2 ) { 3001 | 3002 | return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); 3003 | 3004 | }, 3005 | 3006 | // http://en.wikipedia.org/wiki/Smoothstep 3007 | 3008 | smoothstep: function ( x, min, max ) { 3009 | 3010 | if ( x <= min ) return 0; 3011 | if ( x >= max ) return 1; 3012 | 3013 | x = ( x - min ) / ( max - min ); 3014 | 3015 | return x * x * ( 3 - 2 * x ); 3016 | 3017 | }, 3018 | 3019 | smootherstep: function ( x, min, max ) { 3020 | 3021 | if ( x <= min ) return 0; 3022 | if ( x >= max ) return 1; 3023 | 3024 | x = ( x - min ) / ( max - min ); 3025 | 3026 | return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); 3027 | 3028 | }, 3029 | 3030 | // Random float from <0, 1> with 16 bits of randomness 3031 | // (standard Math.random() creates repetitive patterns when applied over larger space) 3032 | 3033 | random16: function () { 3034 | 3035 | return ( 65280 * Math.random() + 255 * Math.random() ) / 65535; 3036 | 3037 | }, 3038 | 3039 | // Random integer from interval 3040 | 3041 | randInt: function ( low, high ) { 3042 | 3043 | return Math.floor( this.randFloat( low, high ) ); 3044 | 3045 | }, 3046 | 3047 | // Random float from interval 3048 | 3049 | randFloat: function ( low, high ) { 3050 | 3051 | return low + Math.random() * ( high - low ); 3052 | 3053 | }, 3054 | 3055 | // Random float from <-range/2, range/2> interval 3056 | 3057 | randFloatSpread: function ( range ) { 3058 | 3059 | return range * ( 0.5 - Math.random() ); 3060 | 3061 | }, 3062 | 3063 | degToRad: function () { 3064 | 3065 | var degreeToRadiansFactor = Math.PI / 180; 3066 | 3067 | return function ( degrees ) { 3068 | 3069 | return degrees * degreeToRadiansFactor; 3070 | 3071 | }; 3072 | 3073 | }(), 3074 | 3075 | radToDeg: function () { 3076 | 3077 | var radianToDegreesFactor = 180 / Math.PI; 3078 | 3079 | return function ( radians ) { 3080 | 3081 | return radians * radianToDegreesFactor; 3082 | 3083 | }; 3084 | 3085 | }(), 3086 | 3087 | isPowerOfTwo: function ( value ) { 3088 | 3089 | return ( value & ( value - 1 ) ) === 0 && value !== 0; 3090 | 3091 | }, 3092 | 3093 | nextPowerOfTwo: function ( value ) { 3094 | 3095 | value --; 3096 | value |= value >> 1; 3097 | value |= value >> 2; 3098 | value |= value >> 4; 3099 | value |= value >> 8; 3100 | value |= value >> 16; 3101 | value ++; 3102 | 3103 | return value; 3104 | } 3105 | 3106 | }; 3107 | 3108 | /*** END Math ***/ 3109 | 3110 | } 3111 | 3112 | module.exports = THREE; 3113 | 3114 | },{}],10:[function(_dereq_,module,exports){ 3115 | /* 3116 | * Copyright 2015 Google Inc. All Rights Reserved. 3117 | * Licensed under the Apache License, Version 2.0 (the "License"); 3118 | * you may not use this file except in compliance with the License. 3119 | * You may obtain a copy of the License at 3120 | * 3121 | * http://www.apache.org/licenses/LICENSE-2.0 3122 | * 3123 | * Unless required by applicable law or agreed to in writing, software 3124 | * distributed under the License is distributed on an "AS IS" BASIS, 3125 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3126 | * See the License for the specific language governing permissions and 3127 | * limitations under the License. 3128 | */ 3129 | var THREE = _dereq_('./three-math.js'); 3130 | var Util = _dereq_('./util.js'); 3131 | 3132 | var ROTATE_SPEED = 0.5; 3133 | /** 3134 | * Provides a quaternion responsible for pre-panning the scene before further 3135 | * transformations due to device sensors. 3136 | */ 3137 | function TouchPanner() { 3138 | window.addEventListener('touchstart', this.onTouchStart_.bind(this)); 3139 | window.addEventListener('touchmove', this.onTouchMove_.bind(this)); 3140 | window.addEventListener('touchend', this.onTouchEnd_.bind(this)); 3141 | 3142 | this.isTouching = false; 3143 | this.rotateStart = new THREE.Vector2(); 3144 | this.rotateEnd = new THREE.Vector2(); 3145 | this.rotateDelta = new THREE.Vector2(); 3146 | 3147 | this.theta = 0; 3148 | this.orientation = new THREE.Quaternion(); 3149 | } 3150 | 3151 | TouchPanner.prototype.getOrientation = function() { 3152 | this.orientation.setFromEuler(new THREE.Euler(0, 0, this.theta)); 3153 | return this.orientation; 3154 | }; 3155 | 3156 | TouchPanner.prototype.resetSensor = function() { 3157 | this.theta = 0; 3158 | }; 3159 | 3160 | TouchPanner.prototype.onTouchStart_ = function(e) { 3161 | // Only respond if there is exactly one touch. 3162 | if (e.touches.length != 1) { 3163 | return; 3164 | } 3165 | this.rotateStart.set(e.touches[0].pageX, e.touches[0].pageY); 3166 | this.isTouching = true; 3167 | }; 3168 | 3169 | TouchPanner.prototype.onTouchMove_ = function(e) { 3170 | if (!this.isTouching) { 3171 | return; 3172 | } 3173 | this.rotateEnd.set(e.touches[0].pageX, e.touches[0].pageY); 3174 | this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart); 3175 | this.rotateStart.copy(this.rotateEnd); 3176 | 3177 | // On iOS, direction is inverted. 3178 | if (Util.isIOS()) { 3179 | this.rotateDelta.x *= -1; 3180 | } 3181 | 3182 | var element = document.body; 3183 | this.theta += 2 * Math.PI * this.rotateDelta.x / element.clientWidth * ROTATE_SPEED; 3184 | }; 3185 | 3186 | TouchPanner.prototype.onTouchEnd_ = function(e) { 3187 | this.isTouching = false; 3188 | }; 3189 | 3190 | module.exports = TouchPanner; 3191 | 3192 | },{"./three-math.js":9,"./util.js":11}],11:[function(_dereq_,module,exports){ 3193 | /* 3194 | * Copyright 2015 Google Inc. All Rights Reserved. 3195 | * Licensed under the Apache License, Version 2.0 (the "License"); 3196 | * you may not use this file except in compliance with the License. 3197 | * You may obtain a copy of the License at 3198 | * 3199 | * http://www.apache.org/licenses/LICENSE-2.0 3200 | * 3201 | * Unless required by applicable law or agreed to in writing, software 3202 | * distributed under the License is distributed on an "AS IS" BASIS, 3203 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3204 | * See the License for the specific language governing permissions and 3205 | * limitations under the License. 3206 | */ 3207 | var Util = window.Util || {}; 3208 | 3209 | Util.MIN_TIMESTEP = 0.001; 3210 | Util.MAX_TIMESTEP = 1; 3211 | 3212 | Util.clamp = function(value, min, max) { 3213 | return Math.min(Math.max(min, value), max); 3214 | }; 3215 | 3216 | Util.isIOS = function() { 3217 | return /iPad|iPhone|iPod/.test(navigator.platform); 3218 | }; 3219 | 3220 | Util.isFirefoxAndroid = function() { 3221 | return navigator.userAgent.indexOf('Firefox') !== -1 && navigator.userAgent.indexOf('Android') !== -1; 3222 | } 3223 | 3224 | // Helper method to validate the time steps of sensor timestamps. 3225 | Util.isTimestampDeltaValid = function(timestampDeltaS) { 3226 | if (isNaN(timestampDeltaS)) { 3227 | return false; 3228 | } 3229 | if (timestampDeltaS <= Util.MIN_TIMESTEP) { 3230 | return false; 3231 | } 3232 | if (timestampDeltaS > Util.MAX_TIMESTEP) { 3233 | return false; 3234 | } 3235 | return true; 3236 | } 3237 | 3238 | module.exports = Util; 3239 | 3240 | },{}],12:[function(_dereq_,module,exports){ 3241 | /* 3242 | * Copyright 2015 Google Inc. All Rights Reserved. 3243 | * Licensed under the Apache License, Version 2.0 (the "License"); 3244 | * you may not use this file except in compliance with the License. 3245 | * You may obtain a copy of the License at 3246 | * 3247 | * http://www.apache.org/licenses/LICENSE-2.0 3248 | * 3249 | * Unless required by applicable law or agreed to in writing, software 3250 | * distributed under the License is distributed on an "AS IS" BASIS, 3251 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3252 | * See the License for the specific language governing permissions and 3253 | * limitations under the License. 3254 | */ 3255 | 3256 | var CardboardHMDVRDevice = _dereq_('./cardboard-hmd-vr-device.js'); 3257 | //var OrientationPositionSensorVRDevice = require('./orientation-position-sensor-vr-device.js'); 3258 | var FusionPositionSensorVRDevice = _dereq_('./fusion-position-sensor-vr-device.js'); 3259 | var MouseKeyboardPositionSensorVRDevice = _dereq_('./mouse-keyboard-position-sensor-vr-device.js'); 3260 | // Uncomment to add positional tracking via webcam. 3261 | //var WebcamPositionSensorVRDevice = require('./webcam-position-sensor-vr-device.js'); 3262 | var HMDVRDevice = _dereq_('./base.js').HMDVRDevice; 3263 | var PositionSensorVRDevice = _dereq_('./base.js').PositionSensorVRDevice; 3264 | 3265 | function WebVRPolyfill() { 3266 | this.devices = []; 3267 | 3268 | if (!this.isWebVRAvailable()) { 3269 | this.enablePolyfill(); 3270 | } 3271 | } 3272 | 3273 | WebVRPolyfill.prototype.isWebVRAvailable = function() { 3274 | return ('getVRDevices' in navigator) || ('mozGetVRDevices' in navigator); 3275 | }; 3276 | 3277 | 3278 | WebVRPolyfill.prototype.enablePolyfill = function() { 3279 | // Initialize our virtual VR devices. 3280 | if (this.isCardboardCompatible()) { 3281 | this.devices.push(new CardboardHMDVRDevice()); 3282 | } 3283 | 3284 | // Polyfill using the right position sensor. 3285 | if (this.isMobile()) { 3286 | //this.devices.push(new OrientationPositionSensorVRDevice()); 3287 | this.devices.push(new FusionPositionSensorVRDevice()); 3288 | } else { 3289 | if (!WebVRConfig.MOUSE_KEYBOARD_CONTROLS_DISABLED) { 3290 | this.devices.push(new MouseKeyboardPositionSensorVRDevice()); 3291 | } 3292 | // Uncomment to add positional tracking via webcam. 3293 | //this.devices.push(new WebcamPositionSensorVRDevice()); 3294 | } 3295 | 3296 | // Provide navigator.getVRDevices. 3297 | navigator.getVRDevices = this.getVRDevices.bind(this); 3298 | 3299 | // Provide the CardboardHMDVRDevice and PositionSensorVRDevice objects. 3300 | window.HMDVRDevice = HMDVRDevice; 3301 | window.PositionSensorVRDevice = PositionSensorVRDevice; 3302 | }; 3303 | 3304 | WebVRPolyfill.prototype.getVRDevices = function() { 3305 | var devices = this.devices; 3306 | return new Promise(function(resolve, reject) { 3307 | try { 3308 | resolve(devices); 3309 | } catch (e) { 3310 | reject(e); 3311 | } 3312 | }); 3313 | }; 3314 | 3315 | /** 3316 | * Determine if a device is mobile. 3317 | */ 3318 | WebVRPolyfill.prototype.isMobile = function() { 3319 | return /Android/i.test(navigator.userAgent) || 3320 | /iPhone|iPad|iPod/i.test(navigator.userAgent); 3321 | }; 3322 | 3323 | WebVRPolyfill.prototype.isCardboardCompatible = function() { 3324 | // For now, support all iOS and Android devices. 3325 | // Also enable the WebVRConfig.FORCE_VR flag for debugging. 3326 | return this.isMobile() || WebVRConfig.FORCE_ENABLE_VR; 3327 | }; 3328 | 3329 | module.exports = WebVRPolyfill; 3330 | 3331 | },{"./base.js":1,"./cardboard-hmd-vr-device.js":2,"./fusion-position-sensor-vr-device.js":4,"./mouse-keyboard-position-sensor-vr-device.js":6}]},{},[5]); 3332 | -------------------------------------------------------------------------------- /three-demo-complete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Building VR interactives with Three.js 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /three-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Building VR interactives with Three.js 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | --------------------------------------------------------------------------------