├── about ├── icon.png ├── capture.jpg ├── small-tile.jpg ├── snapshot1.jpg ├── snapshot2.jpg ├── snapshot1-1280x800.jpg ├── snapshot2-1280x800.jpg └── ChromeWebStore_Badge_v2_496x150.png ├── devtools.html ├── assets ├── headset-ao.png ├── chaperone-texture.png ├── save.svg ├── local.svg ├── delete.svg ├── reset1.svg ├── eye.svg ├── reset2.svg ├── translate.svg ├── activate.svg ├── sitting.svg ├── rotate.svg ├── world.svg └── headset.obj ├── archive ├── Archive-1.0.5.zip ├── Archive-1.1.0.zip ├── Archive-1.1.1.zip ├── Archive-1.1.2.zip ├── Archive-1.1.3.zip └── Archive-1.1.4.zip ├── manifest.json ├── utils.js ├── LICENSE ├── common.js ├── README.md ├── devtools.js ├── content-script.js ├── CHANGELOG.md ├── background.js ├── panel.html ├── panel.js ├── polyfill.js ├── OBJLoader.js ├── OrbitControls.js └── TransformControls.js /about/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/about/icon.png -------------------------------------------------------------------------------- /about/capture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/about/capture.jpg -------------------------------------------------------------------------------- /about/small-tile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/about/small-tile.jpg -------------------------------------------------------------------------------- /about/snapshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/about/snapshot1.jpg -------------------------------------------------------------------------------- /about/snapshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/about/snapshot2.jpg -------------------------------------------------------------------------------- /devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/headset-ao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/assets/headset-ao.png -------------------------------------------------------------------------------- /archive/Archive-1.0.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/archive/Archive-1.0.5.zip -------------------------------------------------------------------------------- /archive/Archive-1.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/archive/Archive-1.1.0.zip -------------------------------------------------------------------------------- /archive/Archive-1.1.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/archive/Archive-1.1.1.zip -------------------------------------------------------------------------------- /archive/Archive-1.1.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/archive/Archive-1.1.2.zip -------------------------------------------------------------------------------- /archive/Archive-1.1.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/archive/Archive-1.1.3.zip -------------------------------------------------------------------------------- /archive/Archive-1.1.4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/archive/Archive-1.1.4.zip -------------------------------------------------------------------------------- /about/snapshot1-1280x800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/about/snapshot1-1280x800.jpg -------------------------------------------------------------------------------- /about/snapshot2-1280x800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/about/snapshot2-1280x800.jpg -------------------------------------------------------------------------------- /assets/chaperone-texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/assets/chaperone-texture.png -------------------------------------------------------------------------------- /about/ChromeWebStore_Badge_v2_496x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/WebVR-Extension/HEAD/about/ChromeWebStore_Badge_v2_496x150.png -------------------------------------------------------------------------------- /assets/save.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/local.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/reset1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebVR API Emulation", 3 | "short_name": "WebVR Emu", 4 | "version": "1.1.4", 5 | "minimum_chrome_version": "10.0", 6 | "devtools_page": "devtools.html", 7 | "description": "WebVR API Emulation", 8 | "background": { 9 | "scripts": [ "common.js", "background.js" ] 10 | }, 11 | "permissions": [ 12 | "tabs", 13 | "", 14 | "storage" 15 | ], 16 | "content_scripts": [{ 17 | "matches": [""], 18 | "js": [ "polyfill.js", "content-script.js" ], 19 | "run_at": "document_start", 20 | "all_frames": true 21 | } ], 22 | "browser_action": { 23 | "default_icon": { 24 | }, 25 | "default_title": "WebVR" 26 | }, 27 | "manifest_version": 2, 28 | "web_accessible_resources": [ 29 | "polyfill.js" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /assets/reset2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/translate.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/activate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | function throttle(fn, threshold, scope) { 2 | threshold || (threshold = 250); 3 | var last, 4 | deferTimer; 5 | return function () { 6 | var context = scope || this; 7 | 8 | var now = +new Date, 9 | args = arguments; 10 | if (last && now < last + threshold) { 11 | clearTimeout(deferTimer); 12 | deferTimer = setTimeout(function () { 13 | last = now; 14 | fn.apply(context, args); 15 | }, threshold); 16 | } else { 17 | last = now; 18 | fn.apply(context, args); 19 | } 20 | }; 21 | } 22 | 23 | function debounce(fn, delay) { 24 | var timer = null; 25 | return function () { 26 | var context = this, args = arguments; 27 | clearTimeout(timer); 28 | timer = setTimeout(function () { 29 | fn.apply(context, args); 30 | }, delay); 31 | }; 32 | } 33 | 34 | var ge = (function(){ 35 | 36 | var map = new Map(); 37 | 38 | return function( id ) { 39 | var el = map.get( id ); 40 | if( el ) { 41 | } else { 42 | el = document.getElementById( id ); 43 | map.set( id, el ); 44 | } 45 | return el; 46 | } 47 | 48 | })(); 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jaume Sanchez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | function log() { 2 | 3 | if( !verbose ) return; 4 | 5 | var args = Array.from( arguments ); 6 | args.unshift( 'background: #007AA3; color: #ffffff; text-shadow: 0 -1px #000; padding: 4px 0 4px 0; line-height: 0' ); 7 | args.unshift( `%c WebVREmu ` ); 8 | 9 | console.log.apply( console, args ); 10 | 11 | } 12 | 13 | function fixSettings( settings ) { 14 | 15 | if( settings === undefined ) return defaultSettings; 16 | 17 | var res = {} 18 | 19 | Object.keys( defaultSettings ).forEach( f => { 20 | 21 | res[ f ] = ( settings[ f ] !== undefined ) ? settings[ f ] : defaultSettings[ f ]; 22 | 23 | } ); 24 | 25 | return res; 26 | 27 | } 28 | 29 | // chrome.storage can store Objects directly 30 | 31 | function loadSettings() { 32 | 33 | return new Promise( ( resolve, reject ) => { 34 | 35 | chrome.storage.local.get( 'settings', obj => { 36 | resolve( fixSettings( obj.settings ) ); 37 | } ); 38 | 39 | } ); 40 | 41 | } 42 | 43 | function saveSettings( settings ) { 44 | 45 | return new Promise( ( resolve, reject ) => { 46 | 47 | chrome.storage.local.set( { 'settings': settings }, obj => { 48 | resolve( obj ); 49 | } ); 50 | 51 | } ); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /assets/sitting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebVR API Extension 2 | Chrome DevTools extension that enables users and developers to run WebVR 1.0 content without having a supported HMD headset or even a compatible browser/platform. 3 | It currently emulates an HTC Vive. 4 | 5 | 6 | 7 | It can also help develop even if one owns an HMD, by making repetitive actions that require putting the HMD on and off easier to check through the DevTools panel. 8 | 9 | ![Snapshot 1](/about/capture.jpg) 10 | 11 | [Here's a video showing it in action](https://www.youtube.com/watch?v=D8dj0bk3cWQ) 12 | 13 | ![Snapshot 1](/about/snapshot1.jpg) 14 | ![Snapshot 2](/about/snapshot2.jpg) 15 | 16 | **This extension completely polyfills the WebVR API. 17 | If you have a working WebVR implementation, it will be replaced!** 18 | 19 | This is a work in progress in a very early stage. 20 | It can't be turned off except by disabling or removing the extension 21 | 22 | #### License #### 23 | 24 | MIT licensed 25 | 26 | Copyright (C) 2016 Jaume Sanchez Elias, http://www.clicktorelease.com 27 | -------------------------------------------------------------------------------- /devtools.js: -------------------------------------------------------------------------------- 1 | var verbose = false; 2 | 3 | chrome.devtools.panels.create( "WebVR", "icon.png", "panel.html", initialise ); 4 | 5 | function log( ...args ) { 6 | 7 | if( !verbose ) return; 8 | 9 | var strArgs = [ 10 | '"%c WebVREmu | DevTools "', 11 | '"background: #007AA3; color: #ffffff; text-shadow: 0 -1px #000; padding: 4px 0 4px 0; line-height: 0;"', 12 | ...args.map( v => JSON.stringify( v ) ) 13 | ]; 14 | 15 | chrome.devtools.inspectedWindow.eval( 16 | `console.log(${strArgs});`, 17 | ( result, isException ) => { if( isException ) { console.log( result, isException ) } } 18 | ); 19 | 20 | } 21 | 22 | log( 'Ready' ); 23 | 24 | var port = chrome.runtime.connect( null, { name: `devtools` } ); 25 | var tabId = chrome.devtools.inspectedWindow.tabId 26 | 27 | function post( msg ) { 28 | 29 | msg.tabId = tabId; 30 | port.postMessage( msg ); 31 | 32 | } 33 | 34 | post( { action: 'start' } ); 35 | 36 | port.onDisconnect.addListener( function() { 37 | console.log( 'disconnect' ); 38 | } ); 39 | 40 | var settings = {}; 41 | var panelWindow = null; 42 | 43 | port.onMessage.addListener( function( msg ) { 44 | 45 | switch( msg.action ) { 46 | case 'settings': 47 | settings = msg.settings; 48 | if( panelWindow ) panelWindow.updateSettings( settings ); 49 | break; 50 | case 'pose': 51 | if( panelWindow ) panelWindow.updatePose( msg.position, msg.rotation ); 52 | break; 53 | } 54 | 55 | } ); 56 | 57 | function initialise( panel ) { 58 | 59 | panel.onShown.addListener( function ( wnd ) { 60 | 61 | panelWindow = wnd; 62 | panelWindow.updateSettings( settings ); 63 | 64 | } ); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /assets/rotate.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /content-script.js: -------------------------------------------------------------------------------- 1 | var verbose = true; 2 | 3 | function log() { 4 | 5 | if( !verbose ) return; 6 | 7 | console.log.apply( 8 | console, [ 9 | `%c WebVREmu `, 10 | 'background: #007AA3; color: #ffffff; text-shadow: 0 -1px #000; padding: 4px 0 4px 0; line-height: 0', 11 | ...arguments 12 | ] 13 | ); 14 | 15 | } 16 | 17 | log( 'Polyfill', window.location.toString() ); 18 | 19 | var port = chrome.runtime.connect( { name: 'contentScript' } ); 20 | 21 | function post( msg ) { 22 | 23 | port.postMessage( msg ); 24 | 25 | } 26 | 27 | post( { action: 'script-ready' } ); 28 | 29 | var cloneInto = cloneInto || null; 30 | 31 | function createCustomEvent(type, detail) { 32 | // Use cloneInto on Firefox (which prevents the security 33 | // sandbox from raising exception when the javascript 34 | // code in the webpage content is going to access to the 35 | // event properties). 36 | detail = cloneInto ? cloneInto(detail, window) : detail; 37 | return new CustomEvent( type, { detail }); 38 | } 39 | 40 | port.onMessage.addListener( function( msg ) { 41 | 42 | switch( msg.action ) { 43 | case 'pose': 44 | var e = createCustomEvent( 'webvr-pose', { 45 | position: msg.position, 46 | rotation: msg.rotation 47 | } ); 48 | window.dispatchEvent( e ); 49 | break; 50 | case 'hmd-activate': 51 | var e = createCustomEvent( 'webvr-hmd-activate', { 52 | state: msg.value 53 | } ); 54 | window.dispatchEvent( e ); 55 | break; 56 | } 57 | 58 | } ); 59 | 60 | window.addEventListener( 'webvr-ready', function() { 61 | 62 | post( { action: 'page-ready' } ); 63 | 64 | } ); 65 | 66 | window.addEventListener( 'webvr-resetpose', function() { 67 | 68 | post( { action: 'reset-pose' } ); 69 | 70 | } ); 71 | 72 | var source = '(' + injectedScript + ')();'; 73 | 74 | var script = document.createElement('script'); 75 | script.textContent = source; 76 | (document.head||document.documentElement).appendChild(script); 77 | script.parentNode.removeChild(script); 78 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | For "Chrome DevTools extension to emulate WebVR API" 3 | (Added, Changed, Deprecated, Removed, Fixed, Security) 4 | https://github.com/spite/WebVR-Extension 5 | 6 | ## [Unreleased] 7 | 8 | ## [1.1.4] - 2016-11-11 9 | ### Added 10 | - Support for VRFrameData and VRDisplay.getFrameData by @spite (see https://github.com/spite/WebVR-Extension/issues/21) 11 | 12 | ## [1.1.3] - 2016-11-2 13 | ### Fixed 14 | - VRLayers handling by @spite 15 | 16 | ## [1.1.2] - 2016-10-28 17 | ### Added 18 | - Support for VRDisplay.getLayers() by @AVGP 19 | 20 | ## [1.1.1] - 2016-09-25 21 | ### Added 22 | - Activate/deactivate HMD feature by @spite 23 | - VRDisplay.getLayers 24 | 25 | ### Fixed 26 | - vrdisplay and reason in VRDisplayEvent by @spite 27 | 28 | ## [1.1.0] - 2016-09-13 29 | ### Added 30 | - Options for persistence on reload by @spite 31 | - Options for sharing pose across tabs by @spite 32 | - Store and restore poses by @spite 33 | - Added link to repo and changelog by @spite 34 | - Action buttons on the bottom bar by @spite 35 | 36 | ### Changed 37 | - Switched message passing to CustomEvent dispatching by @spite 38 | - Revamped UI and styles by @spite 39 | - Snapping translation units from 1m to 10cm by @spite 40 | 41 | ### Fixed 42 | - TransformControls with OrthographicCamera by @mrdoob 43 | - Emulated HMDs had externalDisplay disabled by @spite 44 | - Ctrl key release by @spite 45 | 46 | ## [1.0.5] - 2016-08-30 47 | ### Added 48 | - cancelAnimationFrame by @johnmaf 49 | 50 | ### Fixed 51 | - Populate VRDisplay's righteye offset from model's right eye offset by @johnmaf 52 | - Return value for requestAnimationFrame by @johnmaf 53 | 54 | ## [1.0.4] - 2016-08-30 55 | ### Fixed 56 | - Fixed default quaternions by @spite 57 | 58 | ## [1.0.3] - 2016-07-12 59 | ### Added 60 | - Emit event vrdisplaypresentchange by @spite 61 | - VRDisplay.exitPresent by @spite 62 | 63 | ### Changed 64 | - Removed Float32Array allocation in VRDisplay.getPose by @spite 65 | - Profiles for Vive, Rift and Cardboard by @spite 66 | 67 | ### Greetings 68 | - @edankwan for helping debug issue 69 | 70 | ## [1.0.2] - 2016-07-08 71 | ### Added 72 | - Support for iframes by @spite 73 | - Initial Polyfill for legacy API by @spite 74 | 75 | ## [1.0.1] - 2016-07-1 76 | ### Fixed 77 | - Drawing order by @mrdoob. 78 | 79 | ### Changed 80 | - Camera position by @mrdoob. 81 | - Default StageParameters dimensions by @spite 82 | - Changes to TransformControls by @mrdoob. 83 | 84 | ### Added 85 | - Change log 86 | 87 | ## [1.0.0] - 2016-06-30 88 | ### Added 89 | - Everything. First release to the Chrome Store 90 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | var verbose = false; 2 | 3 | var extensionId = chrome.runtime.id; 4 | log( 'Background', extensionId ); 5 | 6 | var settings = {}; 7 | var script = ''; 8 | 9 | var defaultSettings = { 10 | 11 | persist: true, 12 | individualPose: true, 13 | poses: [], 14 | poseCount: 0 15 | 16 | } 17 | 18 | var defaultPose = { 19 | position: { x: 0, y: 0, z: 0 }, 20 | rotation: { x: 0, y: 0, z: 0, w: 1 } 21 | }; 22 | var globalPose = { 23 | position: { x: 0, y: 0, z: 0 }, 24 | rotation: { x: 0, y: 0, z: 0, w: 1 } 25 | }; 26 | var poses = {}; 27 | 28 | loadSettings().then( res => { 29 | settings = res; 30 | notifySettings(); 31 | log( 'Script and settings loaded', settings ); 32 | } ); 33 | 34 | function notifySettings() { 35 | 36 | Object.keys( connections ).forEach( tab => { 37 | if( connections[ tab ].devtools ) { 38 | connections[ tab ].devtools.postMessage( { 39 | action: 'settings', 40 | settings: settings 41 | } ) 42 | } 43 | } ); 44 | 45 | } 46 | 47 | function notifyPoseToInstance( pose, tabId ) { 48 | 49 | var msg = { 50 | action: 'pose', 51 | position: pose.position, 52 | rotation: pose.rotation 53 | }; 54 | 55 | // there should always be at least a content script 56 | // but may be the extension has been reloaded 57 | 58 | if( connections[ tabId ].contentScript) { 59 | connections[ tabId ].contentScript.postMessage( msg ); 60 | } 61 | 62 | if( connections[ tabId ].devtools) { 63 | connections[ tabId ].devtools.postMessage( msg ); 64 | } 65 | 66 | } 67 | 68 | function notifyPose( tabId ) { 69 | 70 | if( settings.individualPose ) { 71 | 72 | notifyPoseToInstance( poses[ tabId ], tabId ); 73 | 74 | } else { 75 | 76 | Object.keys( connections ).forEach( tab => { 77 | notifyPoseToInstance( globalPose, tab ); 78 | } ); 79 | 80 | } 81 | 82 | } 83 | 84 | var connections = {}; 85 | 86 | chrome.runtime.onConnect.addListener( function( port ) { 87 | 88 | log( 'New connection (chrome.runtime.onConnect) from', port.name, port.sender.frameId, port ); 89 | 90 | var name = port.name; 91 | 92 | if( name === 'devtools' ){ 93 | port.postMessage( { 94 | action: 'settings', 95 | settings: settings 96 | } ); 97 | } 98 | 99 | function listener( msg, sender, reply ) { 100 | 101 | var tabId; 102 | 103 | if( msg.tabId ) tabId = msg.tabId 104 | else tabId = sender.sender.tab.id; 105 | 106 | if( !connections[ tabId ] ) connections[ tabId ] = {}; 107 | connections[ tabId ][ name ] = port; 108 | 109 | if( !poses[ tabId ] ) { 110 | poses[ tabId ] = defaultPose; 111 | } 112 | 113 | if( name === 'panel' ) { 114 | switch( msg.action ) { 115 | case 'save-settings': 116 | settings = msg.settings; 117 | saveSettings( msg.settings ); 118 | notifySettings(); 119 | break; 120 | case 'save-pose': 121 | settings.poseCount++; 122 | settings.poses.push( { 123 | name: `Pose ${settings.poseCount}`, 124 | position: msg.pose.position, 125 | rotation: msg.pose.rotation 126 | } ); 127 | saveSettings( settings ); 128 | notifySettings(); 129 | break; 130 | case 'remove-pose': 131 | settings.poses.splice( msg.id, 1 ); 132 | saveSettings( settings ); 133 | notifySettings(); 134 | break; 135 | case 'pose': 136 | globalPose.position = msg.position; 137 | globalPose.rotation = msg.rotation; 138 | poses[ tabId ] = { 139 | position: msg.position, 140 | rotation: msg.rotation 141 | }; 142 | notifyPose( tabId ); 143 | break; 144 | case 'hmd-activate': 145 | connections[ tabId ].contentScript.postMessage( { action: 'hmd-activate', value: msg.value } ); 146 | break; 147 | } 148 | } 149 | 150 | if( name === 'contentScript' ) { 151 | if( msg.action === 'page-ready' ) { 152 | if( !settings.persist ) { 153 | poses[ tabId ] = defaultPose; 154 | } 155 | notifyPose( tabId ); 156 | } 157 | } 158 | 159 | if( msg.action === 'reset-pose' ) { 160 | globalPose.position = defaultPose.position; 161 | globalPose.rotation = defaultPose.rotation; 162 | poses[ tabId ] = defaultPose; 163 | notifyPose( tabId ); 164 | } 165 | 166 | log( 'port.onMessage', port.name, msg ); 167 | 168 | } 169 | 170 | port.onMessage.addListener( listener ); 171 | 172 | port.onDisconnect.addListener( function() { 173 | 174 | port.onMessage.removeListener( listener ); 175 | 176 | log( name, 'disconnect (chrome.runtime.onDisconnect)' ); 177 | 178 | Object.keys( connections ).forEach( c => { 179 | if( connections[ c ][ name ] === port ) { 180 | connections[ c ][ name ] = null; 181 | delete connections[ c ][ name ]; 182 | } 183 | if ( Object.keys( connections[ c ] ).length === 0 ) { 184 | connections[ c ] = null; 185 | delete connections[ c ]; 186 | } 187 | } ) 188 | 189 | 190 | } ); 191 | 192 | port.postMessage( { action: 'ack' } ); 193 | 194 | return true; 195 | 196 | }); 197 | -------------------------------------------------------------------------------- /panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 |
49 |

Click for Disclaimer and instructions

50 | 62 |
63 | 64 |
65 |
66 |

Headset 67 | 70 | 71 | 72 |

73 |

74 |
75 |

76 | 77 | 78 |

79 | 80 | 81 |
82 | 83 | 84 | 85 |

86 |
87 |
88 | 89 |
90 | 91 |
92 |
93 |

Position 0 0 0
Orientation 0 0 0 0

94 |
95 |
96 | Transformation 97 | 98 | in 99 | 100 | space 101 |
102 |
103 | 104 |
105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /assets/world.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /panel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var port = chrome.runtime.connect( null, { name: `panel` } ); 4 | var tabId = chrome.devtools.inspectedWindow.tabId; 5 | 6 | function post( msg ) { 7 | 8 | msg.tabId = tabId; 9 | port.postMessage( msg ); 10 | 11 | } 12 | 13 | var settings = {}; 14 | var display = null; 15 | 16 | var positionSpans = []; 17 | positionSpans[ 0 ] = ge( 'position-x' ); 18 | positionSpans[ 1 ] = ge( 'position-y' ); 19 | positionSpans[ 2 ] = ge( 'position-z' ); 20 | 21 | var orientationSpans = []; 22 | orientationSpans[ 0 ] = ge( 'orientation-x' ); 23 | orientationSpans[ 1 ] = ge( 'orientation-y' ); 24 | orientationSpans[ 2 ] = ge( 'orientation-z' ); 25 | orientationSpans[ 3 ] = ge( 'orientation-w' ); 26 | 27 | var container = ge( 'canvas-container' ); 28 | 29 | var renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } ); 30 | renderer.setPixelRatio( window.devicePixelRatio ); 31 | container.appendChild( renderer.domElement ); 32 | 33 | var scene = new THREE.Scene(); 34 | var camera = new THREE.OrthographicCamera( 1 / - 2, 1 / 2, 1 / 2, 1 / - 2, - 5, 10 ); 35 | camera.position.set( 2, 2, 2 ); 36 | camera.lookAt( scene.position ); 37 | 38 | var textureLoader = new THREE.TextureLoader(); 39 | var chaperoneTexture = textureLoader.load( 'assets/chaperone-texture.png', invalidate ); 40 | chaperoneTexture.wrapS = chaperoneTexture.wrapT = THREE.RepeatWrapping; 41 | 42 | var roomGeometry = new THREE.BoxGeometry( 5, 2, 3 ); 43 | 44 | var size = 0.4; 45 | var w = 5.2, d = 3.1, h = 2; 46 | var sw = Math.round( 2 * w ); 47 | var sd = Math.round( 2 * d ); 48 | var sh = Math.round( 2 * h ); 49 | roomGeometry.faceVertexUvs[ 0 ][ 0 ][ 0 ].y = sh; 50 | roomGeometry.faceVertexUvs[ 0 ][ 0 ][ 2 ].y = sh; 51 | roomGeometry.faceVertexUvs[ 0 ][ 0 ][ 2 ].x = sd; 52 | 53 | roomGeometry.faceVertexUvs[ 0 ][ 1 ][ 1 ].x = sd; 54 | roomGeometry.faceVertexUvs[ 0 ][ 1 ][ 2 ].x = sd; 55 | roomGeometry.faceVertexUvs[ 0 ][ 1 ][ 2 ].y = sh; 56 | 57 | roomGeometry.faceVertexUvs[ 0 ][ 2 ][ 0 ].y = sh; 58 | roomGeometry.faceVertexUvs[ 0 ][ 2 ][ 2 ].y = sh; 59 | roomGeometry.faceVertexUvs[ 0 ][ 2 ][ 2 ].x = sd; 60 | 61 | roomGeometry.faceVertexUvs[ 0 ][ 3 ][ 1 ].x = sd; 62 | roomGeometry.faceVertexUvs[ 0 ][ 3 ][ 2 ].x = sd; 63 | roomGeometry.faceVertexUvs[ 0 ][ 3 ][ 2 ].y = sh; 64 | 65 | roomGeometry.faceVertexUvs[ 0 ][ 8 ][ 0 ].y = sh; 66 | roomGeometry.faceVertexUvs[ 0 ][ 8 ][ 2 ].y = sh; 67 | roomGeometry.faceVertexUvs[ 0 ][ 8 ][ 2 ].x = sw; 68 | 69 | roomGeometry.faceVertexUvs[ 0 ][ 9 ][ 1 ].x = sw; 70 | roomGeometry.faceVertexUvs[ 0 ][ 9 ][ 2 ].x = sw; 71 | roomGeometry.faceVertexUvs[ 0 ][ 9 ][ 2 ].y = sh; 72 | 73 | roomGeometry.faceVertexUvs[ 0 ][ 10 ][ 0 ].y = sh; 74 | roomGeometry.faceVertexUvs[ 0 ][ 10 ][ 2 ].y = sh; 75 | roomGeometry.faceVertexUvs[ 0 ][ 10 ][ 2 ].x = sw; 76 | 77 | roomGeometry.faceVertexUvs[ 0 ][ 11 ][ 1 ].x = sw; 78 | roomGeometry.faceVertexUvs[ 0 ][ 11 ][ 2 ].x = sw; 79 | roomGeometry.faceVertexUvs[ 0 ][ 11 ][ 2 ].y = sh; 80 | 81 | roomGeometry.uvsNeedUpdate = true; 82 | 83 | var room = new THREE.Mesh( roomGeometry, new THREE.MeshBasicMaterial( { 84 | map: chaperoneTexture, 85 | side: THREE.BackSide, 86 | transparent: true 87 | } ) ); 88 | room.position.y = 1; 89 | room.renderOrder = -1; 90 | scene.add( room ); 91 | 92 | var loader = new THREE.OBJLoader(); 93 | var hmd = new THREE.Object3D(); 94 | scene.add( hmd ); 95 | 96 | loader.load( 'assets/headset.obj', function( obj ) { 97 | 98 | var model = obj.children[ 0 ]; 99 | model.rotation.y = Math.PI; 100 | model.scale.set( .5, .5, .5 ); 101 | model.geometry.center(); 102 | model.material = new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader().load( 'assets/headset-ao.png' ), side: THREE.DoubleSide } ); 103 | model.material.map.minFilter = THREE.LinearFilter; 104 | hmd.add( model ); 105 | 106 | } ); 107 | 108 | var invalidated = true; 109 | function invalidate() { 110 | invalidated = true; 111 | } 112 | 113 | var controls = new THREE.OrbitControls( camera, container ); 114 | controls.addEventListener( 'change', invalidate ); 115 | controls.target.set( 0, 0, 0 ); 116 | 117 | var control = new THREE.TransformControls( camera, renderer.domElement ); 118 | control.addEventListener( 'change', onPoseChange ); 119 | control.attach( hmd ); 120 | control.setMode( 'translate' ); 121 | control.setSpace( 'local' ); 122 | scene.add( control ); 123 | 124 | function onPoseChange() { 125 | 126 | post( { 127 | action: 'pose', 128 | position: { x: hmd.position.x, y: hmd.position.y, z: hmd.position.z }, 129 | rotation: { x: hmd.quaternion.x, y: hmd.quaternion.y, z: hmd.quaternion.z, w: hmd.quaternion.w } 130 | } ); 131 | 132 | invalidate(); 133 | 134 | } 135 | 136 | function onWindowResize() { 137 | 138 | var w = container.clientWidth; 139 | var h = container.clientHeight; 140 | 141 | renderer.setSize( w, h ); 142 | 143 | var a = w / h; 144 | w = 7; 145 | h = w / a; 146 | 147 | camera.left = w / - 2; 148 | camera.right = w / 2; 149 | camera.top = h / 2; 150 | camera.bottom = h / - 2; 151 | 152 | camera.updateProjectionMatrix(); 153 | 154 | invalidate(); 155 | 156 | } 157 | 158 | window.addEventListener( 'resize', onWindowResize ); 159 | 160 | function render() { 161 | 162 | if( invalidated ) { 163 | 164 | positionSpans[ 0 ].textContent = hmd.position.x.toFixed( 2 ); 165 | positionSpans[ 1 ].textContent = hmd.position.y.toFixed( 2 ); 166 | positionSpans[ 2 ].textContent = hmd.position.z.toFixed( 2 ); 167 | 168 | orientationSpans[ 0 ].textContent = hmd.quaternion.x.toFixed( 2 ); 169 | orientationSpans[ 1 ].textContent = hmd.quaternion.y.toFixed( 2 ); 170 | orientationSpans[ 2 ].textContent = hmd.quaternion.z.toFixed( 2 ); 171 | orientationSpans[ 2 ].textContent = hmd.quaternion.w.toFixed( 2 ); 172 | 173 | control.scale.setScalar( 1.5 / camera.zoom ); 174 | control.update(); 175 | 176 | renderer.render( scene, camera ); 177 | invalidated = false; 178 | 179 | } 180 | 181 | requestAnimationFrame( render ); 182 | 183 | } 184 | 185 | window.addEventListener( 'keydown', function ( event ) { 186 | 187 | switch ( event.keyCode ) { 188 | 189 | case 81: // Q 190 | control.setSpace( control.space === "local" ? "world" : "local" ); 191 | if( control.space === "local" ) { 192 | ge( 'local-button' ).classList.add( 'active' ); 193 | ge( 'world-button' ).classList.remove( 'active' ); 194 | } else { 195 | ge( 'local-button' ).classList.remove( 'active' ); 196 | ge( 'world-button' ).classList.add( 'active' ); 197 | } 198 | break; 199 | 200 | case 17: // Ctrl 201 | control.setTranslationSnap( .1 ); 202 | control.setRotationSnap( THREE.Math.degToRad( 15 ) ); 203 | break; 204 | 205 | case 87: // W 206 | control.setMode( "translate" ); 207 | ge( 'translate-button' ).classList.add( 'active' ); 208 | ge( 'rotate-button' ).classList.remove( 'active' ); 209 | break; 210 | 211 | case 69: // E 212 | control.setMode( "rotate" ); 213 | ge( 'translate-button' ).classList.remove( 'active' ); 214 | ge( 'rotate-button' ).classList.add( 'active' ); 215 | break; 216 | 217 | case 187: 218 | case 107: // +, =, num+ 219 | control.setSize( control.size + 0.01 ); 220 | break; 221 | 222 | case 189: 223 | case 109: // -, _, num- 224 | control.setSize( Math.max( control.size - 0.01, 0.01 ) ); 225 | break; 226 | 227 | } 228 | 229 | }); 230 | 231 | ge( 'translate-button' ).addEventListener( 'click', e => { 232 | 233 | ge( 'translate-button' ).classList.toggle( 'active' ); 234 | if( ge( 'translate-button' ).classList.contains( 'active' ) ){ 235 | control.setMode( "translate" ); 236 | ge( 'rotate-button' ).classList.remove( 'active' ); 237 | } else { 238 | control.setMode( "rotate" ); 239 | ge( 'rotate-button' ).classList.add( 'active' ); 240 | } 241 | 242 | } ); 243 | 244 | ge( 'rotate-button' ).addEventListener( 'click', e => { 245 | 246 | ge( 'rotate-button' ).classList.toggle( 'active' ); 247 | if( ge( 'rotate-button' ).classList.contains( 'active' ) ){ 248 | control.setMode( "rotate" ); 249 | ge( 'translate-button' ).classList.remove( 'active' ); 250 | } else { 251 | control.setMode( "translate" ); 252 | ge( 'translate-button' ).classList.add( 'active' ); 253 | } 254 | 255 | } ); 256 | 257 | ge( 'local-button' ).addEventListener( 'click', e => { 258 | 259 | ge( 'local-button' ).classList.toggle( 'active' ); 260 | if( ge( 'local-button' ).classList.contains( 'active' ) ){ 261 | control.setSpace( 'local' ); 262 | ge( 'world-button' ).classList.remove( 'active' ); 263 | } else { 264 | control.setSpace( 'world' ); 265 | ge( 'world-button' ).classList.add( 'active' ); 266 | } 267 | 268 | } ); 269 | 270 | ge( 'world-button' ).addEventListener( 'click', e => { 271 | 272 | ge( 'world-button' ).classList.toggle( 'active' ); 273 | if( ge( 'world-button' ).classList.contains( 'active' ) ){ 274 | control.setSpace( 'world' ); 275 | ge( 'local-button' ).classList.remove( 'active' ); 276 | } else { 277 | control.setSpace( 'local' ); 278 | ge( 'local-button' ).classList.add( 'active' ); 279 | } 280 | 281 | } ); 282 | 283 | window.addEventListener( 'keyup', function ( event ) { 284 | 285 | switch ( event.keyCode ) { 286 | 287 | case 17: // Ctrl 288 | control.setTranslationSnap( null ); 289 | control.setRotationSnap( null ); 290 | break; 291 | 292 | } 293 | 294 | }); 295 | 296 | window.updatePose = function( position, rotation ) { 297 | 298 | hmd.position.set( position.x, position.y, position.z ); 299 | hmd.quaternion.set( rotation.x, rotation.y, rotation.z, rotation.w ); 300 | invalidate(); 301 | 302 | }; 303 | 304 | ge( 'reset-room-btn' ).addEventListener( 'click', function() { 305 | 306 | camera.position.set( 2, 2, 2 ); 307 | camera.lookAt( scene.position ); 308 | controls.reset(); 309 | controls.center.set( 0, 0, 0 ); 310 | controls.zoom = 1; 311 | controls.update(); 312 | 313 | invalidate(); 314 | 315 | } ); 316 | 317 | ge( 'reset-pose-btn' ).addEventListener( 'click', function() { 318 | 319 | post( { action: 'reset-pose' } ); 320 | 321 | } ); 322 | 323 | ge( 'save-pose-btn' ).addEventListener( 'click', function() { 324 | 325 | post( { 326 | action: 'save-pose', 327 | pose: { 328 | position: { x: hmd.position.x, y: hmd.position.y, z: hmd.position.z }, 329 | rotation: { x: hmd.quaternion.x, y: hmd.quaternion.y, z: hmd.quaternion.z, w: hmd.quaternion.w } 330 | } 331 | } ); 332 | 333 | } ); 334 | 335 | function updateSettings( s ) { 336 | 337 | settings = s; 338 | ge( 'persist-pose-option' ).checked = settings.persist; 339 | ge( 'individual-pose-option' ).checked = settings.individualPose; 340 | 341 | while( ge( 'saved-poses' ).firstChild ) { 342 | ge( 'saved-poses' ).removeChild( ge( 'saved-poses' ).firstChild ); 343 | } 344 | 345 | settings.poses.forEach( ( pose, i ) => { 346 | var p = document.createElement( 'p' ); 347 | var op = document.createElement( 'span' ); 348 | op.textContent = pose.name; 349 | op.addEventListener( 'click', function( e ) { 350 | updatePose( pose.position, pose.rotation ); 351 | control.detach( hmd ); 352 | control.attach( hmd ); 353 | control.update(); 354 | onPoseChange(); 355 | } ); 356 | /*var ren = document.createElement( 'span' ); 357 | ren.textContent = 'Rename'; 358 | ren.addEventListener( 'click', function( e ) { 359 | } );*/ 360 | var del = document.createElement( 'object' ); 361 | del.className = 'remove-entry'; 362 | del.setAttribute( 'type', 'image/svg+xml' ); 363 | del.setAttribute( 'data', 'assets/delete.svg' ); 364 | del.addEventListener( 'click', function( e ) { 365 | post( { 366 | action: 'remove-pose', 367 | id: i 368 | } ); 369 | e.stopPropagation(); 370 | e.preventDefault(); 371 | return false; 372 | } ); 373 | p.appendChild( op ); 374 | //p.appendChild( ren ); 375 | p.appendChild( del ); 376 | ge( 'saved-poses' ).appendChild( p ); 377 | }); 378 | 379 | } 380 | 381 | ge( 'persist-pose-option' ).addEventListener( 'change', saveSettings ); 382 | ge( 'individual-pose-option' ).addEventListener( 'change', saveSettings ); 383 | 384 | function saveSettings() { 385 | 386 | settings.persist = ge( 'persist-pose-option' ).checked; 387 | settings.individualPose = ge( 'individual-pose-option' ).checked; 388 | 389 | post( { action: 'save-settings', settings: settings } ); 390 | 391 | } 392 | 393 | ge( 'select-pose-btn' ).addEventListener( 'click', e => { 394 | ge( 'saved-poses' ).classList.toggle( 'collapsed' ); 395 | e.stopPropagation(); 396 | e.preventDefault(); 397 | return false; 398 | } ); 399 | 400 | ge( 'disclaimer' ).addEventListener( 'click', e => { 401 | ge( 'disclaimer-content' ).classList.toggle( 'collapsed' ); 402 | }) 403 | 404 | ge( 'activate-hmd-btn' ).addEventListener( 'click', e => { 405 | 406 | ge( 'activate-hmd-btn' ).classList.toggle( 'active' ); 407 | post( { action: 'hmd-activate', value: ge( 'activate-hmd-btn' ).classList.contains( 'active' ) } ); 408 | 409 | } ); 410 | 411 | window.addEventListener( 'click', e => { 412 | 413 | ge( 'saved-poses' ).classList.add( 'collapsed' ); 414 | 415 | } ); 416 | 417 | var links = document.querySelectorAll( 'a[rel=external]' ); 418 | for( var j = 0; j < links.length; j++ ) { 419 | var a = links[ j ]; 420 | a.addEventListener( 'click', function( e ) { 421 | window.open( this.href, '_blank' ); 422 | e.preventDefault(); 423 | }, false ); 424 | } 425 | 426 | onWindowResize(); 427 | render(); 428 | -------------------------------------------------------------------------------- /polyfill.js: -------------------------------------------------------------------------------- 1 | function injectedScript() { 2 | 3 | 'use strict'; 4 | 5 | var ViveData = { 6 | name: 'Emulated HTC Vive DVT', 7 | resolution: { width: 1512, height: 1680 }, 8 | features: { canPresent: true, hasExternalDisplay: true, hasOrientation: true, hasPosition: true }, 9 | leftEye: { offset: -0.032, up: 41.653, down: 48.008, left: 43.977, right: 35.575 }, 10 | rightEye: { offset: 0.032, up: 41.653, down: 48.008, left: 35.575, right: 43.977 } 11 | } 12 | 13 | var RiftData = { 14 | name: 'Emulated Oculus Rift CV1', 15 | resolution: { width: 1332, height: 1586 }, 16 | features: { canPresent: true, hasExternalDisplay: true, hasOrientation: true, hasPosition: true }, 17 | leftEye: { offset: -0.032, up: 55.814, down: 55.728, left: 54.429, right: 51.288 }, 18 | rightEye: { offset: 0.032, up: 55.687, down: 55.658, left: 51.110, right: 54.397 } 19 | } 20 | 21 | var CardboardData = { 22 | name: 'Emulated Google, Inc. Cardboard v1', 23 | resolution: { width: 960, height: 1080 }, 24 | features: { canPresent: true, hasExternalDisplay: false, hasOrientation: true, hasPosition: false }, 25 | leftEye: { offset: -0.030, up: 40, down: 40, left: 40, right: 40 }, 26 | rightEye: { offset: 0.030, up: 40, down: 40, left: 40, right: 40 } 27 | } 28 | 29 | var startDate = Date.now(); 30 | var startPerfNow = performance.now(); 31 | 32 | // WebVR 1.0 33 | 34 | function VRDisplayCapabilities () { 35 | 36 | this.canPresent = true; 37 | this.hasExternalDisplay = true; 38 | this.hasOrientation = true; 39 | this.hasPosition = true; 40 | this.maxLayers = 1 41 | 42 | } 43 | 44 | function VRStageParameters() { 45 | 46 | this.sittingToStandingTransform = new Float32Array( [ 47 | 1, 0, 0, 0, 48 | 0, 1, 0, 0, 49 | 0, 0, 1, 0, 50 | 0, 0, 0, 1 51 | ] ); 52 | 53 | this.sizeX = 5; 54 | this.sizeZ = 3; 55 | 56 | } 57 | 58 | function VRPose() { 59 | 60 | this.timestamp = startDate + ( performance.now() - startPerfNow ); 61 | this.position = new Float32Array( [ 0, 0, 0 ] ); 62 | this.linearVelocity = new Float32Array( [ 0, 0, 0 ] ); 63 | this.linearAcceleration = null; 64 | this.orientation = new Float32Array( [ 0, 0, 0, 1 ] ); 65 | this.angularVelocity = new Float32Array( [ 0, 0, 0 ] ); 66 | this.angularAcceleration = null; 67 | 68 | } 69 | 70 | function VRFieldOfView() { 71 | 72 | this.upDegrees = 0; 73 | this.downDegrees = 0; 74 | this.leftDegrees = 0; 75 | this.rightDegrees = 0; 76 | 77 | } 78 | 79 | function VREyeParameters() { 80 | 81 | this.offset = new Float32Array( [ 0, 0, 0 ] ); 82 | this.fieldOfView = new VRFieldOfView(); 83 | this.renderWidth = 0; 84 | this.renderHeight = 0; 85 | 86 | } 87 | 88 | function VRLayer() { 89 | 90 | this.leftBounds = null; 91 | this.rightBounds = null; 92 | this.source = null; 93 | 94 | } 95 | 96 | function VRFrameData() { 97 | 98 | this.leftProjectionMatrix = new Float32Array( 16 ); 99 | this.leftViewMatrix = new Float32Array( 16 ); 100 | this.rightProjectionMatrix = new Float32Array( 16 ); 101 | this.rightViewMatrix = new Float32Array( 16 ); 102 | this.pose = null; 103 | 104 | } 105 | 106 | // from webvr-polyfill 107 | 108 | var frameDataFromPose = (function() { 109 | 110 | var piOver180 = Math.PI / 180.0; 111 | var rad45 = Math.PI * 0.25; 112 | 113 | // Borrowed from glMatrix. 114 | function mat4_perspectiveFromFieldOfView(out, fov, near, far) { 115 | 116 | var upTan = Math.tan(fov ? (fov.upDegrees * piOver180) : rad45), 117 | downTan = Math.tan(fov ? (fov.downDegrees * piOver180) : rad45), 118 | leftTan = Math.tan(fov ? (fov.leftDegrees * piOver180) : rad45), 119 | rightTan = Math.tan(fov ? (fov.rightDegrees * piOver180) : rad45), 120 | xScale = 2.0 / (leftTan + rightTan), 121 | yScale = 2.0 / (upTan + downTan); 122 | 123 | out[0] = xScale; 124 | out[1] = 0.0; 125 | out[2] = 0.0; 126 | out[3] = 0.0; 127 | out[4] = 0.0; 128 | out[5] = yScale; 129 | out[6] = 0.0; 130 | out[7] = 0.0; 131 | out[8] = -((leftTan - rightTan) * xScale * 0.5); 132 | out[9] = ((upTan - downTan) * yScale * 0.5); 133 | out[10] = far / (near - far); 134 | out[11] = -1.0; 135 | out[12] = 0.0; 136 | out[13] = 0.0; 137 | out[14] = (far * near) / (near - far); 138 | out[15] = 0.0; 139 | return out; 140 | 141 | } 142 | 143 | function mat4_fromRotationTranslation(out, q, v) { 144 | 145 | // Quaternion math 146 | var x = q[0], y = q[1], z = q[2], w = q[3], 147 | x2 = x + x, 148 | y2 = y + y, 149 | z2 = z + z, 150 | 151 | xx = x * x2, 152 | xy = x * y2, 153 | xz = x * z2, 154 | yy = y * y2, 155 | yz = y * z2, 156 | zz = z * z2, 157 | wx = w * x2, 158 | wy = w * y2, 159 | wz = w * z2; 160 | 161 | out[0] = 1 - (yy + zz); 162 | out[1] = xy + wz; 163 | out[2] = xz - wy; 164 | out[3] = 0; 165 | out[4] = xy - wz; 166 | out[5] = 1 - (xx + zz); 167 | out[6] = yz + wx; 168 | out[7] = 0; 169 | out[8] = xz + wy; 170 | out[9] = yz - wx; 171 | out[10] = 1 - (xx + yy); 172 | out[11] = 0; 173 | out[12] = v[0]; 174 | out[13] = v[1]; 175 | out[14] = v[2]; 176 | out[15] = 1; 177 | 178 | return out; 179 | 180 | }; 181 | 182 | function mat4_translate(out, a, v) { 183 | 184 | var x = v[0], y = v[1], z = v[2], 185 | a00, a01, a02, a03, 186 | a10, a11, a12, a13, 187 | a20, a21, a22, a23; 188 | 189 | if (a === out) { 190 | out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; 191 | out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; 192 | out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; 193 | out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; 194 | } else { 195 | a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; 196 | a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; 197 | a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; 198 | 199 | out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; 200 | out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; 201 | out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; 202 | 203 | out[12] = a00 * x + a10 * y + a20 * z + a[12]; 204 | out[13] = a01 * x + a11 * y + a21 * z + a[13]; 205 | out[14] = a02 * x + a12 * y + a22 * z + a[14]; 206 | out[15] = a03 * x + a13 * y + a23 * z + a[15]; 207 | } 208 | 209 | return out; 210 | 211 | }; 212 | 213 | function mat4_invert(out, a) { 214 | 215 | var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], 216 | a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], 217 | a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], 218 | a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], 219 | 220 | b00 = a00 * a11 - a01 * a10, 221 | b01 = a00 * a12 - a02 * a10, 222 | b02 = a00 * a13 - a03 * a10, 223 | b03 = a01 * a12 - a02 * a11, 224 | b04 = a01 * a13 - a03 * a11, 225 | b05 = a02 * a13 - a03 * a12, 226 | b06 = a20 * a31 - a21 * a30, 227 | b07 = a20 * a32 - a22 * a30, 228 | b08 = a20 * a33 - a23 * a30, 229 | b09 = a21 * a32 - a22 * a31, 230 | b10 = a21 * a33 - a23 * a31, 231 | b11 = a22 * a33 - a23 * a32, 232 | 233 | // Calculate the determinant 234 | det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; 235 | 236 | if (!det) { 237 | return null; 238 | } 239 | det = 1.0 / det; 240 | 241 | out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; 242 | out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; 243 | out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; 244 | out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; 245 | out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; 246 | out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; 247 | out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; 248 | out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; 249 | out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; 250 | out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; 251 | out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; 252 | out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; 253 | out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; 254 | out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; 255 | out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; 256 | out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; 257 | 258 | return out; 259 | 260 | }; 261 | 262 | var defaultOrientation = new Float32Array([0, 0, 0, 1]); 263 | var defaultPosition = new Float32Array([0, 0, 0]); 264 | 265 | function updateEyeMatrices(projection, view, pose, parameters, vrDisplay) { 266 | 267 | mat4_perspectiveFromFieldOfView(projection, parameters ? parameters.fieldOfView : null, vrDisplay.depthNear, vrDisplay.depthFar); 268 | 269 | var orientation = pose.orientation || defaultOrientation; 270 | var position = pose.position || defaultPosition; 271 | 272 | mat4_fromRotationTranslation(view, orientation, position); 273 | if (parameters) 274 | mat4_translate(view, view, parameters.offset); 275 | mat4_invert(view, view); 276 | 277 | } 278 | 279 | return function(frameData, pose, vrDisplay) { 280 | 281 | if (!frameData || !pose) 282 | return false; 283 | 284 | frameData.pose = pose; 285 | frameData.timestamp = pose.timestamp; 286 | 287 | updateEyeMatrices( 288 | frameData.leftProjectionMatrix, frameData.leftViewMatrix, 289 | pose, vrDisplay.getEyeParameters("left"), vrDisplay); 290 | updateEyeMatrices( 291 | frameData.rightProjectionMatrix, frameData.rightViewMatrix, 292 | pose, vrDisplay.getEyeParameters("right"), vrDisplay); 293 | 294 | return true; 295 | 296 | }; 297 | 298 | })(); 299 | 300 | function createVRDisplayEvent( type, display, reason ) { 301 | 302 | var event = new CustomEvent( type ); 303 | event.display = display; 304 | event.reason = reason; 305 | 306 | return event; 307 | 308 | } 309 | 310 | function VRDisplay( model ) { 311 | 312 | this.depthFar = 1000; 313 | this.depthNear = .1; 314 | this.displayId = 1; 315 | this.displayName = model.name; 316 | this.isConnected = true; 317 | this.isPresenting = false; 318 | 319 | this.layers = []; 320 | 321 | this.stageParameters = new VRStageParameters(); 322 | 323 | this.capabilities = new VRDisplayCapabilities(); 324 | this.capabilities.canPresent = model.features.canPresent; 325 | this.capabilities.hasExternalDisplay = model.features.hasExternalDisplay; 326 | this.capabilities.hasOrientation = model.features.hasOrientation; 327 | this.capabilities.hasPosition = model.features.hasPosition; 328 | 329 | this.pose = new VRPose(); 330 | 331 | this.leftEyeParameters = new VREyeParameters(); 332 | this.leftEyeParameters.fieldOfView.upDegrees = model.leftEye.up; 333 | this.leftEyeParameters.fieldOfView.downDegrees = model.leftEye.down; 334 | this.leftEyeParameters.fieldOfView.leftDegrees = model.leftEye.left; 335 | this.leftEyeParameters.fieldOfView.rightDegrees = model.leftEye.right; 336 | this.leftEyeParameters.renderWidth = model.resolution.width; 337 | this.leftEyeParameters.renderHeight = model.resolution.height; 338 | this.leftEyeParameters.offset[ 0 ] = model.leftEye.offset; 339 | 340 | this.rightEyeParameters = new VREyeParameters(); 341 | this.rightEyeParameters.fieldOfView.upDegrees = model.rightEye.up; 342 | this.rightEyeParameters.fieldOfView.downDegrees = model.rightEye.down; 343 | this.rightEyeParameters.fieldOfView.leftDegrees = model.rightEye.left; 344 | this.rightEyeParameters.fieldOfView.rightDegrees = model.rightEye.right; 345 | this.rightEyeParameters.renderWidth = model.resolution.width; 346 | this.rightEyeParameters.renderHeight = model.resolution.height; 347 | this.rightEyeParameters.offset[ 0 ] = model.rightEye.offset; 348 | 349 | window.addEventListener( 'webvr-pose', function( e ) { 350 | 351 | this.pose.linearVelocity[ 0 ] = e.detail.position.x - this.pose.position[ 0 ]; 352 | this.pose.linearVelocity[ 1 ] = e.detail.position.y - this.pose.position[ 1 ]; 353 | this.pose.linearVelocity[ 2 ] = e.detail.position.z - this.pose.position[ 2 ]; 354 | 355 | this.pose.position[ 0 ] = e.detail.position.x; 356 | this.pose.position[ 1 ] = e.detail.position.y; 357 | this.pose.position[ 2 ] = e.detail.position.z; 358 | 359 | this.pose.orientation[ 0 ] = e.detail.rotation.x; 360 | this.pose.orientation[ 1 ] = e.detail.rotation.y; 361 | this.pose.orientation[ 2 ] = e.detail.rotation.z; 362 | this.pose.orientation[ 3 ] = e.detail.rotation.w; 363 | 364 | }.bind( this ) ); 365 | 366 | window.addEventListener( 'webvr-hmd-activate', function( e ) { 367 | 368 | if( e.detail.state ){ 369 | var event = createVRDisplayEvent( 'vrdisplayactivate', this, 'HMD activated' ); 370 | window.dispatchEvent(event); 371 | } else { 372 | var event = createVRDisplayEvent( 'vrdisplaydeactivate', this, 'HMD deactivated' ); 373 | window.dispatchEvent(event); 374 | } 375 | 376 | }.bind( this ) ); 377 | 378 | } 379 | 380 | VRDisplay.prototype.requestAnimationFrame = function( c ) { 381 | 382 | return requestAnimationFrame( c ); 383 | 384 | } 385 | 386 | VRDisplay.prototype.cancelAnimationFrame = function(handle) { 387 | 388 | cancelAnimationFrame(handle); 389 | 390 | } 391 | 392 | VRDisplay.prototype.getEyeParameters = function( id ) { 393 | 394 | if( id === 'left' ) return this.leftEyeParameters; 395 | return this.rightEyeParameters; 396 | 397 | } 398 | 399 | VRDisplay.prototype.getPose = function() { 400 | 401 | this.pose.timestamp = startDate + ( performance.now() - startPerfNow ); 402 | 403 | return this.pose; 404 | 405 | } 406 | 407 | VRDisplay.prototype.getFrameData = function( frameData ){ 408 | 409 | return frameDataFromPose( frameData, this.getPose(), this ); 410 | 411 | } 412 | 413 | VRDisplay.prototype.requestPresent = function(layers) { 414 | 415 | return new Promise( function( resolve, reject ) { 416 | 417 | this.isPresenting = true; 418 | 419 | this.layers = []; 420 | layers.forEach( function( l ) { 421 | var layer = new VRLayer(); 422 | layer.source = l.source; 423 | if( l.leftBounds ) layer.leftBounds = l.leftBounds; 424 | if( l.rightBounds ) layer.rightBounds = l.rightBounds; 425 | this.layers.push( layer ); 426 | }.bind(this)); 427 | 428 | var event = createVRDisplayEvent( 'vrdisplaypresentchange', this, 'Presenting requested' ); 429 | window.dispatchEvent(event); 430 | 431 | resolve(); 432 | 433 | }.bind( this ) ); 434 | 435 | } 436 | 437 | VRDisplay.prototype.exitPresent = function() { 438 | 439 | return new Promise( function( resolve, reject ) { 440 | 441 | this.isPresenting = false; 442 | 443 | this.layers = []; 444 | 445 | var event = createVRDisplayEvent( 'vrdisplaypresentchange', this, 'Presenting exited' ); 446 | window.dispatchEvent(event); 447 | 448 | resolve(); 449 | 450 | }.bind( this ) ); 451 | 452 | } 453 | 454 | VRDisplay.prototype.submitFrame = function( pose ) { 455 | } 456 | 457 | VRDisplay.prototype.resetPose = function() { 458 | 459 | var event = new Event( 'webvr-resetpose' ); 460 | window.dispatchEvent( event ); 461 | 462 | } 463 | 464 | VRDisplay.prototype.getLayers = function() { 465 | 466 | return this.layers; 467 | 468 | } 469 | 470 | function assignToWindow() { 471 | 472 | window.VRDisplay = VRDisplay; 473 | window.VRFrameData = VRFrameData; 474 | window.VRPose = VRPose; 475 | 476 | } 477 | 478 | assignToWindow(); 479 | 480 | ( function() { 481 | 482 | var vrD = new VRDisplay( ViveData ) 483 | 484 | navigator.getVRDisplays = function() { 485 | 486 | return new Promise( function( resolve, reject ) { 487 | 488 | resolve( [ vrD ] ); 489 | 490 | } ); 491 | 492 | } 493 | 494 | } )(); 495 | 496 | // LEGACY 497 | 498 | function HMDVRDevice( model ) { 499 | 500 | this.deviceName = model.name; 501 | 502 | this.leftEyeParameters = new VREyeParameters(); 503 | this.leftEyeParameters.fieldOfView.upDegrees = model.leftEye.up; 504 | this.leftEyeParameters.fieldOfView.downDegrees = model.leftEye.down; 505 | this.leftEyeParameters.fieldOfView.leftDegrees = model.leftEye.left; 506 | this.leftEyeParameters.fieldOfView.rightDegrees = model.leftEye.right; 507 | this.leftEyeParameters.renderWidth = model.resolution.width; 508 | this.leftEyeParameters.renderHeight = model.resolution.height; 509 | this.leftEyeParameters.offset[ 0 ] = model.leftEye.offset; 510 | 511 | this.rightEyeParameters = new VREyeParameters(); 512 | this.rightEyeParameters.fieldOfView.upDegrees = model.rightEye.up; 513 | this.rightEyeParameters.fieldOfView.downDegrees = model.rightEye.down; 514 | this.rightEyeParameters.fieldOfView.leftDegrees = model.rightEye.left; 515 | this.rightEyeParameters.fieldOfView.rightDegrees = model.rightEye.right; 516 | this.rightEyeParameters.renderWidth = model.resolution.width; 517 | this.rightEyeParameters.renderHeight = model.resolution.height; 518 | this.rightEyeParameters.offset[ 0 ] = model.rightEye.offset; 519 | 520 | this.leftRecommendedFOV = new VREyeParameters(); 521 | this.leftRecommendedFOV.fieldOfView.upDegrees = model.leftEye.up; 522 | this.leftRecommendedFOV.fieldOfView.downDegrees = model.leftEye.down; 523 | this.leftRecommendedFOV.fieldOfView.leftDegrees = model.leftEye.left; 524 | this.leftRecommendedFOV.fieldOfView.rightDegrees = model.leftEye.right; 525 | this.leftRecommendedFOV.renderWidth = model.resolution.width; 526 | this.leftRecommendedFOV.renderHeight = model.resolution.height; 527 | this.leftRecommendedFOV.offset[ 0 ] = model.leftEye.offset; 528 | 529 | this.rightRecommendedFOV = new VREyeParameters(); 530 | this.rightRecommendedFOV.fieldOfView.upDegrees = model.rightEye.up; 531 | this.rightRecommendedFOV.fieldOfView.downDegrees = model.rightEye.down; 532 | this.rightRecommendedFOV.fieldOfView.leftDegrees = model.rightEye.left; 533 | this.rightRecommendedFOV.fieldOfView.rightDegrees = model.rightEye.right; 534 | this.rightRecommendedFOV.renderWidth = model.resolution.width; 535 | this.rightRecommendedFOV.renderHeight = model.resolution.height; 536 | this.rightRecommendedFOV.offset[ 0 ] = model.rightEye.offset; 537 | 538 | } 539 | 540 | HMDVRDevice.prototype.getEyeTranslation = function( eye ) { 541 | 542 | return 3; 543 | 544 | } 545 | 546 | HMDVRDevice.prototype.getRecommendedEyeFieldOfView = function( eye ) { 547 | 548 | if( eye === 'left' ) return this.leftRecommendedFOV; 549 | return this.rightRecommendedFOV; 550 | 551 | } 552 | 553 | function VRFieldOfView() { 554 | 555 | this.upDegrees = 0; 556 | this.downDegrees = 0; 557 | this.leftDegrees = 0; 558 | this.rightDegrees = 0; 559 | 560 | } 561 | 562 | function VREyeParameters() { 563 | 564 | this.eyeTranslation = 0; 565 | this.recommendedFieldOfView = new VRFieldOfView(); 566 | this.offset = new Float32Array( [ 0, 0, 0 ] ); 567 | this.fieldOfView = new VRFieldOfView(); 568 | this.renderWidth = 0; 569 | this.renderHeight = 0; 570 | 571 | } 572 | 573 | HMDVRDevice.prototype.getEyeParameters = function( eye ) { 574 | 575 | if( eye === 'left' ) return this.leftEyeParameters; 576 | return this.rightEyeParameters; 577 | 578 | } 579 | 580 | function PositionSensorVRDevice( model ) { 581 | 582 | this.deviceName = model.name; 583 | 584 | this.state = new VRPositionState( model ); 585 | 586 | window.addEventListener( 'webvr-pose', function( e ) { 587 | 588 | this.state.linearVelocity.x = e.detail.position.x - this.state.position.x; 589 | this.state.linearVelocity.y = e.detail.position.y - this.state.position.y; 590 | this.state.linearVelocity.z = e.detail.position.z - this.state.position.z; 591 | 592 | this.state.position.x = e.detail.position.x; 593 | this.state.position.y = e.detail.position.y; 594 | this.state.position.z = e.detail.position.z; 595 | 596 | this.state.orientation.x = e.detail.rotation.x; 597 | this.state.orientation.y = e.detail.rotation.y; 598 | this.state.orientation.z = e.detail.rotation.z; 599 | this.state.orientation.w = e.detail.rotation.w; 600 | 601 | }.bind( this ) ); 602 | 603 | } 604 | 605 | function VRPositionState( model ) { 606 | 607 | this.angularAcceleration = new DOMPoint(); 608 | this.angularVelocity = new DOMPoint(); 609 | this.linearAcceleration = new DOMPoint(); 610 | this.linearVelocity = new DOMPoint(); 611 | this.orientation = new DOMPoint(); 612 | this.position = new DOMPoint(); 613 | this.timestamp = null; 614 | 615 | this.hasPosition = model.features.hasPosition; 616 | this.hasOrientation = model.features.hasOrientation; 617 | 618 | } 619 | 620 | function DOMPoint() { 621 | this.x = 0; 622 | this.y = 0; 623 | this.z = 0; 624 | this.w = 0; 625 | } 626 | 627 | PositionSensorVRDevice.prototype.getState = function() { 628 | 629 | this.state.timestamp = startDate + ( performance.now() - startPerfNow ); 630 | 631 | return this.state; 632 | 633 | } 634 | 635 | window.HMDVRDevice = HMDVRDevice; 636 | window.PositionSensorVRDevice = PositionSensorVRDevice; 637 | 638 | ( function() { 639 | 640 | var vrD = new HMDVRDevice( ViveData ); 641 | var vrP = new PositionSensorVRDevice( ViveData ); 642 | 643 | navigator.getVRDevices = function() { 644 | 645 | return new Promise( function( resolve, reject ) { 646 | 647 | resolve( [ vrD, vrP ] ); 648 | 649 | } ); 650 | 651 | } 652 | 653 | } )(); 654 | 655 | var event = new Event( 'webvr-ready' ); 656 | window.dispatchEvent( event ); 657 | 658 | } 659 | -------------------------------------------------------------------------------- /OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | this.materials = null; 10 | 11 | this.regexp = { 12 | // v float float float 13 | vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 14 | // vn float float float 15 | normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 16 | // vt float float 17 | uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 18 | // f vertex vertex vertex 19 | face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/, 20 | // f vertex/uv vertex/uv vertex/uv 21 | face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/, 22 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 23 | face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, 24 | // f vertex//normal vertex//normal vertex//normal 25 | face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/, 26 | // o object_name | g group_name 27 | object_pattern : /^[og]\s*(.+)?/, 28 | // s boolean 29 | smoothing_pattern : /^s\s+(\d+|on|off)/, 30 | // mtllib file_reference 31 | material_library_pattern : /^mtllib /, 32 | // usemtl material_name 33 | material_use_pattern : /^usemtl / 34 | }; 35 | 36 | }; 37 | 38 | THREE.OBJLoader.prototype = { 39 | 40 | constructor: THREE.OBJLoader, 41 | 42 | load: function ( url, onLoad, onProgress, onError ) { 43 | 44 | var scope = this; 45 | 46 | var loader = new THREE.FileLoader( scope.manager ); 47 | loader.setPath( this.path ); 48 | loader.load( url, function ( text ) { 49 | 50 | onLoad( scope.parse( text ) ); 51 | 52 | }, onProgress, onError ); 53 | 54 | }, 55 | 56 | setPath: function ( value ) { 57 | 58 | this.path = value; 59 | 60 | }, 61 | 62 | setMaterials: function ( materials ) { 63 | 64 | this.materials = materials; 65 | 66 | }, 67 | 68 | _createParserState : function () { 69 | 70 | var state = { 71 | objects : [], 72 | object : {}, 73 | 74 | vertices : [], 75 | normals : [], 76 | uvs : [], 77 | 78 | materialLibraries : [], 79 | 80 | startObject: function ( name, fromDeclaration ) { 81 | 82 | // If the current object (initial from reset) is not from a g/o declaration in the parsed 83 | // file. We need to use it for the first parsed g/o to keep things in sync. 84 | if ( this.object && this.object.fromDeclaration === false ) { 85 | 86 | this.object.name = name; 87 | this.object.fromDeclaration = ( fromDeclaration !== false ); 88 | return; 89 | 90 | } 91 | 92 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 93 | 94 | if ( this.object && typeof this.object._finalize === 'function' ) { 95 | 96 | this.object._finalize( true ); 97 | 98 | } 99 | 100 | this.object = { 101 | name : name || '', 102 | fromDeclaration : ( fromDeclaration !== false ), 103 | 104 | geometry : { 105 | vertices : [], 106 | normals : [], 107 | uvs : [] 108 | }, 109 | materials : [], 110 | smooth : true, 111 | 112 | startMaterial : function( name, libraries ) { 113 | 114 | var previous = this._finalize( false ); 115 | 116 | // New usemtl declaration overwrites an inherited material, except if faces were declared 117 | // after the material, then it must be preserved for proper MultiMaterial continuation. 118 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { 119 | 120 | this.materials.splice( previous.index, 1 ); 121 | 122 | } 123 | 124 | var material = { 125 | index : this.materials.length, 126 | name : name || '', 127 | mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), 128 | smooth : ( previous !== undefined ? previous.smooth : this.smooth ), 129 | groupStart : ( previous !== undefined ? previous.groupEnd : 0 ), 130 | groupEnd : -1, 131 | groupCount : -1, 132 | inherited : false, 133 | 134 | clone : function( index ) { 135 | var cloned = { 136 | index : ( typeof index === 'number' ? index : this.index ), 137 | name : this.name, 138 | mtllib : this.mtllib, 139 | smooth : this.smooth, 140 | groupStart : 0, 141 | groupEnd : -1, 142 | groupCount : -1, 143 | inherited : false 144 | }; 145 | cloned.clone = this.clone.bind(cloned); 146 | return cloned; 147 | } 148 | }; 149 | 150 | this.materials.push( material ); 151 | 152 | return material; 153 | 154 | }, 155 | 156 | currentMaterial : function() { 157 | 158 | if ( this.materials.length > 0 ) { 159 | return this.materials[ this.materials.length - 1 ]; 160 | } 161 | 162 | return undefined; 163 | 164 | }, 165 | 166 | _finalize : function( end ) { 167 | 168 | var lastMultiMaterial = this.currentMaterial(); 169 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) { 170 | 171 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 172 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 173 | lastMultiMaterial.inherited = false; 174 | 175 | } 176 | 177 | // Ignore objects tail materials if no face declarations followed them before a new o/g started. 178 | if ( end && this.materials.length > 1 ) { 179 | 180 | for ( var mi = this.materials.length - 1; mi >= 0; mi-- ) { 181 | if ( this.materials[mi].groupCount <= 0 ) { 182 | this.materials.splice( mi, 1 ); 183 | } 184 | } 185 | 186 | } 187 | 188 | // Guarantee at least one empty material, this makes the creation later more straight forward. 189 | if ( end && this.materials.length === 0 ) { 190 | 191 | this.materials.push({ 192 | name : '', 193 | smooth : this.smooth 194 | }); 195 | 196 | } 197 | 198 | return lastMultiMaterial; 199 | 200 | } 201 | }; 202 | 203 | // Inherit previous objects material. 204 | // Spec tells us that a declared material must be set to all objects until a new material is declared. 205 | // If a usemtl declaration is encountered while this new object is being parsed, it will 206 | // overwrite the inherited material. Exception being that there was already face declarations 207 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation. 208 | 209 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) { 210 | 211 | var declared = previousMaterial.clone( 0 ); 212 | declared.inherited = true; 213 | this.object.materials.push( declared ); 214 | 215 | } 216 | 217 | this.objects.push( this.object ); 218 | 219 | }, 220 | 221 | finalize : function() { 222 | 223 | if ( this.object && typeof this.object._finalize === 'function' ) { 224 | 225 | this.object._finalize( true ); 226 | 227 | } 228 | 229 | }, 230 | 231 | parseVertexIndex: function ( value, len ) { 232 | 233 | var index = parseInt( value, 10 ); 234 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 235 | 236 | }, 237 | 238 | parseNormalIndex: function ( value, len ) { 239 | 240 | var index = parseInt( value, 10 ); 241 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 242 | 243 | }, 244 | 245 | parseUVIndex: function ( value, len ) { 246 | 247 | var index = parseInt( value, 10 ); 248 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; 249 | 250 | }, 251 | 252 | addVertex: function ( a, b, c ) { 253 | 254 | var src = this.vertices; 255 | var dst = this.object.geometry.vertices; 256 | 257 | dst.push( src[ a + 0 ] ); 258 | dst.push( src[ a + 1 ] ); 259 | dst.push( src[ a + 2 ] ); 260 | dst.push( src[ b + 0 ] ); 261 | dst.push( src[ b + 1 ] ); 262 | dst.push( src[ b + 2 ] ); 263 | dst.push( src[ c + 0 ] ); 264 | dst.push( src[ c + 1 ] ); 265 | dst.push( src[ c + 2 ] ); 266 | 267 | }, 268 | 269 | addVertexLine: function ( a ) { 270 | 271 | var src = this.vertices; 272 | var dst = this.object.geometry.vertices; 273 | 274 | dst.push( src[ a + 0 ] ); 275 | dst.push( src[ a + 1 ] ); 276 | dst.push( src[ a + 2 ] ); 277 | 278 | }, 279 | 280 | addNormal : function ( a, b, c ) { 281 | 282 | var src = this.normals; 283 | var dst = this.object.geometry.normals; 284 | 285 | dst.push( src[ a + 0 ] ); 286 | dst.push( src[ a + 1 ] ); 287 | dst.push( src[ a + 2 ] ); 288 | dst.push( src[ b + 0 ] ); 289 | dst.push( src[ b + 1 ] ); 290 | dst.push( src[ b + 2 ] ); 291 | dst.push( src[ c + 0 ] ); 292 | dst.push( src[ c + 1 ] ); 293 | dst.push( src[ c + 2 ] ); 294 | 295 | }, 296 | 297 | addUV: function ( a, b, c ) { 298 | 299 | var src = this.uvs; 300 | var dst = this.object.geometry.uvs; 301 | 302 | dst.push( src[ a + 0 ] ); 303 | dst.push( src[ a + 1 ] ); 304 | dst.push( src[ b + 0 ] ); 305 | dst.push( src[ b + 1 ] ); 306 | dst.push( src[ c + 0 ] ); 307 | dst.push( src[ c + 1 ] ); 308 | 309 | }, 310 | 311 | addUVLine: function ( a ) { 312 | 313 | var src = this.uvs; 314 | var dst = this.object.geometry.uvs; 315 | 316 | dst.push( src[ a + 0 ] ); 317 | dst.push( src[ a + 1 ] ); 318 | 319 | }, 320 | 321 | addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 322 | 323 | var vLen = this.vertices.length; 324 | 325 | var ia = this.parseVertexIndex( a, vLen ); 326 | var ib = this.parseVertexIndex( b, vLen ); 327 | var ic = this.parseVertexIndex( c, vLen ); 328 | var id; 329 | 330 | if ( d === undefined ) { 331 | 332 | this.addVertex( ia, ib, ic ); 333 | 334 | } else { 335 | 336 | id = this.parseVertexIndex( d, vLen ); 337 | 338 | this.addVertex( ia, ib, id ); 339 | this.addVertex( ib, ic, id ); 340 | 341 | } 342 | 343 | if ( ua !== undefined ) { 344 | 345 | var uvLen = this.uvs.length; 346 | 347 | ia = this.parseUVIndex( ua, uvLen ); 348 | ib = this.parseUVIndex( ub, uvLen ); 349 | ic = this.parseUVIndex( uc, uvLen ); 350 | 351 | if ( d === undefined ) { 352 | 353 | this.addUV( ia, ib, ic ); 354 | 355 | } else { 356 | 357 | id = this.parseUVIndex( ud, uvLen ); 358 | 359 | this.addUV( ia, ib, id ); 360 | this.addUV( ib, ic, id ); 361 | 362 | } 363 | 364 | } 365 | 366 | if ( na !== undefined ) { 367 | 368 | // Normals are many times the same. If so, skip function call and parseInt. 369 | var nLen = this.normals.length; 370 | ia = this.parseNormalIndex( na, nLen ); 371 | 372 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 373 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 374 | 375 | if ( d === undefined ) { 376 | 377 | this.addNormal( ia, ib, ic ); 378 | 379 | } else { 380 | 381 | id = this.parseNormalIndex( nd, nLen ); 382 | 383 | this.addNormal( ia, ib, id ); 384 | this.addNormal( ib, ic, id ); 385 | 386 | } 387 | 388 | } 389 | 390 | }, 391 | 392 | addLineGeometry: function ( vertices, uvs ) { 393 | 394 | this.object.geometry.type = 'Line'; 395 | 396 | var vLen = this.vertices.length; 397 | var uvLen = this.uvs.length; 398 | 399 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 400 | 401 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); 402 | 403 | } 404 | 405 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { 406 | 407 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); 408 | 409 | } 410 | 411 | } 412 | 413 | }; 414 | 415 | state.startObject( '', false ); 416 | 417 | return state; 418 | 419 | }, 420 | 421 | parse: function ( text ) { 422 | 423 | console.time( 'OBJLoader' ); 424 | 425 | var state = this._createParserState(); 426 | 427 | if ( text.indexOf( '\r\n' ) !== - 1 ) { 428 | 429 | // This is faster than String.split with regex that splits on both 430 | text = text.replace( /\r\n/g, '\n' ); 431 | 432 | } 433 | 434 | if ( text.indexOf( '\\\n' ) !== - 1) { 435 | 436 | // join lines separated by a line continuation character (\) 437 | text = text.replace( /\\\n/g, '' ); 438 | 439 | } 440 | 441 | var lines = text.split( '\n' ); 442 | var line = '', lineFirstChar = '', lineSecondChar = ''; 443 | var lineLength = 0; 444 | var result = []; 445 | 446 | // Faster to just trim left side of the line. Use if available. 447 | var trimLeft = ( typeof ''.trimLeft === 'function' ); 448 | 449 | for ( var i = 0, l = lines.length; i < l; i ++ ) { 450 | 451 | line = lines[ i ]; 452 | 453 | line = trimLeft ? line.trimLeft() : line.trim(); 454 | 455 | lineLength = line.length; 456 | 457 | if ( lineLength === 0 ) continue; 458 | 459 | lineFirstChar = line.charAt( 0 ); 460 | 461 | // @todo invoke passed in handler if any 462 | if ( lineFirstChar === '#' ) continue; 463 | 464 | if ( lineFirstChar === 'v' ) { 465 | 466 | lineSecondChar = line.charAt( 1 ); 467 | 468 | if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) { 469 | 470 | // 0 1 2 3 471 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 472 | 473 | state.vertices.push( 474 | parseFloat( result[ 1 ] ), 475 | parseFloat( result[ 2 ] ), 476 | parseFloat( result[ 3 ] ) 477 | ); 478 | 479 | } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) { 480 | 481 | // 0 1 2 3 482 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 483 | 484 | state.normals.push( 485 | parseFloat( result[ 1 ] ), 486 | parseFloat( result[ 2 ] ), 487 | parseFloat( result[ 3 ] ) 488 | ); 489 | 490 | } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) { 491 | 492 | // 0 1 2 493 | // ["vt 0.1 0.2", "0.1", "0.2"] 494 | 495 | state.uvs.push( 496 | parseFloat( result[ 1 ] ), 497 | parseFloat( result[ 2 ] ) 498 | ); 499 | 500 | } else { 501 | 502 | throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" ); 503 | 504 | } 505 | 506 | } else if ( lineFirstChar === "f" ) { 507 | 508 | if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) { 509 | 510 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 511 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 512 | // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined] 513 | 514 | state.addFace( 515 | result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ], 516 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 517 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 518 | ); 519 | 520 | } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) { 521 | 522 | // f vertex/uv vertex/uv vertex/uv 523 | // 0 1 2 3 4 5 6 7 8 524 | // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined] 525 | 526 | state.addFace( 527 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 528 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 529 | ); 530 | 531 | } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) { 532 | 533 | // f vertex//normal vertex//normal vertex//normal 534 | // 0 1 2 3 4 5 6 7 8 535 | // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined] 536 | 537 | state.addFace( 538 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 539 | undefined, undefined, undefined, undefined, 540 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 541 | ); 542 | 543 | } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) { 544 | 545 | // f vertex vertex vertex 546 | // 0 1 2 3 4 547 | // ["f 1 2 3", "1", "2", "3", undefined] 548 | 549 | state.addFace( 550 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 551 | ); 552 | 553 | } else { 554 | 555 | throw new Error( "Unexpected face line: '" + line + "'" ); 556 | 557 | } 558 | 559 | } else if ( lineFirstChar === "l" ) { 560 | 561 | var lineParts = line.substring( 1 ).trim().split( " " ); 562 | var lineVertices = [], lineUVs = []; 563 | 564 | if ( line.indexOf( "/" ) === - 1 ) { 565 | 566 | lineVertices = lineParts; 567 | 568 | } else { 569 | 570 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 571 | 572 | var parts = lineParts[ li ].split( "/" ); 573 | 574 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 575 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 576 | 577 | } 578 | 579 | } 580 | state.addLineGeometry( lineVertices, lineUVs ); 581 | 582 | } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) { 583 | 584 | // o object_name 585 | // or 586 | // g group_name 587 | 588 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 589 | // var name = result[ 0 ].substr( 1 ).trim(); 590 | var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); 591 | 592 | state.startObject( name ); 593 | 594 | } else if ( this.regexp.material_use_pattern.test( line ) ) { 595 | 596 | // material 597 | 598 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 599 | 600 | } else if ( this.regexp.material_library_pattern.test( line ) ) { 601 | 602 | // mtl file 603 | 604 | state.materialLibraries.push( line.substring( 7 ).trim() ); 605 | 606 | } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) { 607 | 608 | // smooth shading 609 | 610 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 611 | // but does not define a usemtl for each face set. 612 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 613 | // This requires some care to not create extra material on each smooth value for "normal" obj files. 614 | // where explicit usemtl defines geometry groups. 615 | // Example asset: examples/models/obj/cerberus/Cerberus.obj 616 | 617 | var value = result[ 1 ].trim().toLowerCase(); 618 | state.object.smooth = ( value === '1' || value === 'on' ); 619 | 620 | var material = state.object.currentMaterial(); 621 | if ( material ) { 622 | 623 | material.smooth = state.object.smooth; 624 | 625 | } 626 | 627 | } else { 628 | 629 | // Handle null terminated files without exception 630 | if ( line === '\0' ) continue; 631 | 632 | throw new Error( "Unexpected line: '" + line + "'" ); 633 | 634 | } 635 | 636 | } 637 | 638 | state.finalize(); 639 | 640 | var container = new THREE.Group(); 641 | container.materialLibraries = [].concat( state.materialLibraries ); 642 | 643 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 644 | 645 | var object = state.objects[ i ]; 646 | var geometry = object.geometry; 647 | var materials = object.materials; 648 | var isLine = ( geometry.type === 'Line' ); 649 | 650 | // Skip o/g line declarations that did not follow with any faces 651 | if ( geometry.vertices.length === 0 ) continue; 652 | 653 | var buffergeometry = new THREE.BufferGeometry(); 654 | 655 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 656 | 657 | if ( geometry.normals.length > 0 ) { 658 | 659 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 660 | 661 | } else { 662 | 663 | buffergeometry.computeVertexNormals(); 664 | 665 | } 666 | 667 | if ( geometry.uvs.length > 0 ) { 668 | 669 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 670 | 671 | } 672 | 673 | // Create materials 674 | 675 | var createdMaterials = []; 676 | 677 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 678 | 679 | var sourceMaterial = materials[mi]; 680 | var material = undefined; 681 | 682 | if ( this.materials !== null ) { 683 | 684 | material = this.materials.create( sourceMaterial.name ); 685 | 686 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 687 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { 688 | 689 | var materialLine = new THREE.LineBasicMaterial(); 690 | materialLine.copy( material ); 691 | material = materialLine; 692 | 693 | } 694 | 695 | } 696 | 697 | if ( ! material ) { 698 | 699 | material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); 700 | material.name = sourceMaterial.name; 701 | 702 | } 703 | 704 | material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading; 705 | 706 | createdMaterials.push(material); 707 | 708 | } 709 | 710 | // Create mesh 711 | 712 | var mesh; 713 | 714 | if ( createdMaterials.length > 1 ) { 715 | 716 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 717 | 718 | var sourceMaterial = materials[mi]; 719 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 720 | 721 | } 722 | 723 | var multiMaterial = new THREE.MultiMaterial( createdMaterials ); 724 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.LineSegments( buffergeometry, multiMaterial ) ); 725 | 726 | } else { 727 | 728 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) ); 729 | } 730 | 731 | mesh.name = object.name; 732 | 733 | container.add( mesh ); 734 | 735 | } 736 | 737 | console.timeEnd( 'OBJLoader' ); 738 | 739 | return container; 740 | 741 | } 742 | 743 | }; 744 | -------------------------------------------------------------------------------- /OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finger swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | // 84 | // public methods 85 | // 86 | 87 | this.getPolarAngle = function () { 88 | 89 | return spherical.phi; 90 | 91 | }; 92 | 93 | this.getAzimuthalAngle = function () { 94 | 95 | return spherical.theta; 96 | 97 | }; 98 | 99 | this.reset = function () { 100 | 101 | scope.target.copy( scope.target0 ); 102 | scope.object.position.copy( scope.position0 ); 103 | scope.object.zoom = scope.zoom0; 104 | 105 | scope.object.updateProjectionMatrix(); 106 | scope.dispatchEvent( changeEvent ); 107 | 108 | scope.update(); 109 | 110 | state = STATE.NONE; 111 | 112 | }; 113 | 114 | // this method is exposed, but perhaps it would be better if we can make it private... 115 | this.update = function () { 116 | 117 | var offset = new THREE.Vector3(); 118 | 119 | // so camera.up is the orbit axis 120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 121 | var quatInverse = quat.clone().inverse(); 122 | 123 | var lastPosition = new THREE.Vector3(); 124 | var lastQuaternion = new THREE.Quaternion(); 125 | 126 | return function update() { 127 | 128 | var position = scope.object.position; 129 | 130 | offset.copy( position ).sub( scope.target ); 131 | 132 | // rotate offset to "y-axis-is-up" space 133 | offset.applyQuaternion( quat ); 134 | 135 | // angle from z-axis around y-axis 136 | spherical.setFromVector3( offset ); 137 | 138 | if ( scope.autoRotate && state === STATE.NONE ) { 139 | 140 | rotateLeft( getAutoRotationAngle() ); 141 | 142 | } 143 | 144 | spherical.theta += sphericalDelta.theta; 145 | spherical.phi += sphericalDelta.phi; 146 | 147 | // restrict theta to be between desired limits 148 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 149 | 150 | // restrict phi to be between desired limits 151 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 152 | 153 | spherical.makeSafe(); 154 | 155 | 156 | spherical.radius *= scale; 157 | 158 | // restrict radius to be between desired limits 159 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 160 | 161 | // move target to panned location 162 | scope.target.add( panOffset ); 163 | 164 | offset.setFromSpherical( spherical ); 165 | 166 | // rotate offset back to "camera-up-vector-is-up" space 167 | offset.applyQuaternion( quatInverse ); 168 | 169 | position.copy( scope.target ).add( offset ); 170 | 171 | scope.object.lookAt( scope.target ); 172 | 173 | if ( scope.enableDamping === true ) { 174 | 175 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 176 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 177 | 178 | } else { 179 | 180 | sphericalDelta.set( 0, 0, 0 ); 181 | 182 | } 183 | 184 | scale = 1; 185 | panOffset.set( 0, 0, 0 ); 186 | 187 | // update condition is: 188 | // min(camera displacement, camera rotation in radians)^2 > EPS 189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 190 | 191 | if ( zoomChanged || 192 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 194 | 195 | scope.dispatchEvent( changeEvent ); 196 | 197 | lastPosition.copy( scope.object.position ); 198 | lastQuaternion.copy( scope.object.quaternion ); 199 | zoomChanged = false; 200 | 201 | return true; 202 | 203 | } 204 | 205 | return false; 206 | 207 | }; 208 | 209 | }(); 210 | 211 | this.dispose = function () { 212 | 213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 215 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 216 | 217 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 218 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 219 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 220 | 221 | document.removeEventListener( 'mousemove', onMouseMove, false ); 222 | document.removeEventListener( 'mouseup', onMouseUp, false ); 223 | 224 | window.removeEventListener( 'keydown', onKeyDown, false ); 225 | 226 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 227 | 228 | }; 229 | 230 | // 231 | // internals 232 | // 233 | 234 | var scope = this; 235 | 236 | var changeEvent = { type: 'change' }; 237 | var startEvent = { type: 'start' }; 238 | var endEvent = { type: 'end' }; 239 | 240 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; 241 | 242 | var state = STATE.NONE; 243 | 244 | var EPS = 0.000001; 245 | 246 | // current position in spherical coordinates 247 | var spherical = new THREE.Spherical(); 248 | var sphericalDelta = new THREE.Spherical(); 249 | 250 | var scale = 1; 251 | var panOffset = new THREE.Vector3(); 252 | var zoomChanged = false; 253 | 254 | var rotateStart = new THREE.Vector2(); 255 | var rotateEnd = new THREE.Vector2(); 256 | var rotateDelta = new THREE.Vector2(); 257 | 258 | var panStart = new THREE.Vector2(); 259 | var panEnd = new THREE.Vector2(); 260 | var panDelta = new THREE.Vector2(); 261 | 262 | var dollyStart = new THREE.Vector2(); 263 | var dollyEnd = new THREE.Vector2(); 264 | var dollyDelta = new THREE.Vector2(); 265 | 266 | function getAutoRotationAngle() { 267 | 268 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 269 | 270 | } 271 | 272 | function getZoomScale() { 273 | 274 | return Math.pow( 0.95, scope.zoomSpeed ); 275 | 276 | } 277 | 278 | function rotateLeft( angle ) { 279 | 280 | sphericalDelta.theta -= angle; 281 | 282 | } 283 | 284 | function rotateUp( angle ) { 285 | 286 | sphericalDelta.phi -= angle; 287 | 288 | } 289 | 290 | var panLeft = function () { 291 | 292 | var v = new THREE.Vector3(); 293 | 294 | return function panLeft( distance, objectMatrix ) { 295 | 296 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 297 | v.multiplyScalar( - distance ); 298 | 299 | panOffset.add( v ); 300 | 301 | }; 302 | 303 | }(); 304 | 305 | var panUp = function () { 306 | 307 | var v = new THREE.Vector3(); 308 | 309 | return function panUp( distance, objectMatrix ) { 310 | 311 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 312 | v.multiplyScalar( distance ); 313 | 314 | panOffset.add( v ); 315 | 316 | }; 317 | 318 | }(); 319 | 320 | // deltaX and deltaY are in pixels; right and down are positive 321 | var pan = function () { 322 | 323 | var offset = new THREE.Vector3(); 324 | 325 | return function pan( deltaX, deltaY ) { 326 | 327 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 328 | 329 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 330 | 331 | // perspective 332 | var position = scope.object.position; 333 | offset.copy( position ).sub( scope.target ); 334 | var targetDistance = offset.length(); 335 | 336 | // half of the fov is center to top of screen 337 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 338 | 339 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 340 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 341 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 342 | 343 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 344 | 345 | // orthographic 346 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 347 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 348 | 349 | } else { 350 | 351 | // camera neither orthographic nor perspective 352 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 353 | scope.enablePan = false; 354 | 355 | } 356 | 357 | }; 358 | 359 | }(); 360 | 361 | function dollyIn( dollyScale ) { 362 | 363 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 364 | 365 | scale /= dollyScale; 366 | 367 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 368 | 369 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 370 | scope.object.updateProjectionMatrix(); 371 | zoomChanged = true; 372 | 373 | } else { 374 | 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 376 | scope.enableZoom = false; 377 | 378 | } 379 | 380 | } 381 | 382 | function dollyOut( dollyScale ) { 383 | 384 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 385 | 386 | scale *= dollyScale; 387 | 388 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 389 | 390 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 391 | scope.object.updateProjectionMatrix(); 392 | zoomChanged = true; 393 | 394 | } else { 395 | 396 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 397 | scope.enableZoom = false; 398 | 399 | } 400 | 401 | } 402 | 403 | // 404 | // event callbacks - update the object state 405 | // 406 | 407 | function handleMouseDownRotate( event ) { 408 | 409 | //console.log( 'handleMouseDownRotate' ); 410 | 411 | rotateStart.set( event.clientX, event.clientY ); 412 | 413 | } 414 | 415 | function handleMouseDownDolly( event ) { 416 | 417 | //console.log( 'handleMouseDownDolly' ); 418 | 419 | dollyStart.set( event.clientX, event.clientY ); 420 | 421 | } 422 | 423 | function handleMouseDownPan( event ) { 424 | 425 | //console.log( 'handleMouseDownPan' ); 426 | 427 | panStart.set( event.clientX, event.clientY ); 428 | 429 | } 430 | 431 | function handleMouseMoveRotate( event ) { 432 | 433 | //console.log( 'handleMouseMoveRotate' ); 434 | 435 | rotateEnd.set( event.clientX, event.clientY ); 436 | rotateDelta.subVectors( rotateEnd, rotateStart ); 437 | 438 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 439 | 440 | // rotating across whole screen goes 360 degrees around 441 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 442 | 443 | // rotating up and down along whole screen attempts to go 360, but limited to 180 444 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 445 | 446 | rotateStart.copy( rotateEnd ); 447 | 448 | scope.update(); 449 | 450 | } 451 | 452 | function handleMouseMoveDolly( event ) { 453 | 454 | //console.log( 'handleMouseMoveDolly' ); 455 | 456 | dollyEnd.set( event.clientX, event.clientY ); 457 | 458 | dollyDelta.subVectors( dollyEnd, dollyStart ); 459 | 460 | if ( dollyDelta.y > 0 ) { 461 | 462 | dollyIn( getZoomScale() ); 463 | 464 | } else if ( dollyDelta.y < 0 ) { 465 | 466 | dollyOut( getZoomScale() ); 467 | 468 | } 469 | 470 | dollyStart.copy( dollyEnd ); 471 | 472 | scope.update(); 473 | 474 | } 475 | 476 | function handleMouseMovePan( event ) { 477 | 478 | //console.log( 'handleMouseMovePan' ); 479 | 480 | panEnd.set( event.clientX, event.clientY ); 481 | 482 | panDelta.subVectors( panEnd, panStart ); 483 | 484 | pan( panDelta.x, panDelta.y ); 485 | 486 | panStart.copy( panEnd ); 487 | 488 | scope.update(); 489 | 490 | } 491 | 492 | function handleMouseUp( event ) { 493 | 494 | // console.log( 'handleMouseUp' ); 495 | 496 | } 497 | 498 | function handleMouseWheel( event ) { 499 | 500 | // console.log( 'handleMouseWheel' ); 501 | 502 | if ( event.deltaY < 0 ) { 503 | 504 | dollyOut( getZoomScale() ); 505 | 506 | } else if ( event.deltaY > 0 ) { 507 | 508 | dollyIn( getZoomScale() ); 509 | 510 | } 511 | 512 | scope.update(); 513 | 514 | } 515 | 516 | function handleKeyDown( event ) { 517 | 518 | //console.log( 'handleKeyDown' ); 519 | 520 | switch ( event.keyCode ) { 521 | 522 | case scope.keys.UP: 523 | pan( 0, scope.keyPanSpeed ); 524 | scope.update(); 525 | break; 526 | 527 | case scope.keys.BOTTOM: 528 | pan( 0, - scope.keyPanSpeed ); 529 | scope.update(); 530 | break; 531 | 532 | case scope.keys.LEFT: 533 | pan( scope.keyPanSpeed, 0 ); 534 | scope.update(); 535 | break; 536 | 537 | case scope.keys.RIGHT: 538 | pan( - scope.keyPanSpeed, 0 ); 539 | scope.update(); 540 | break; 541 | 542 | } 543 | 544 | } 545 | 546 | function handleTouchStartRotate( event ) { 547 | 548 | //console.log( 'handleTouchStartRotate' ); 549 | 550 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 551 | 552 | } 553 | 554 | function handleTouchStartDolly( event ) { 555 | 556 | //console.log( 'handleTouchStartDolly' ); 557 | 558 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 559 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 560 | 561 | var distance = Math.sqrt( dx * dx + dy * dy ); 562 | 563 | dollyStart.set( 0, distance ); 564 | 565 | } 566 | 567 | function handleTouchStartPan( event ) { 568 | 569 | //console.log( 'handleTouchStartPan' ); 570 | 571 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 572 | 573 | } 574 | 575 | function handleTouchMoveRotate( event ) { 576 | 577 | //console.log( 'handleTouchMoveRotate' ); 578 | 579 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 580 | rotateDelta.subVectors( rotateEnd, rotateStart ); 581 | 582 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 583 | 584 | // rotating across whole screen goes 360 degrees around 585 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 586 | 587 | // rotating up and down along whole screen attempts to go 360, but limited to 180 588 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 589 | 590 | rotateStart.copy( rotateEnd ); 591 | 592 | scope.update(); 593 | 594 | } 595 | 596 | function handleTouchMoveDolly( event ) { 597 | 598 | //console.log( 'handleTouchMoveDolly' ); 599 | 600 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 601 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 602 | 603 | var distance = Math.sqrt( dx * dx + dy * dy ); 604 | 605 | dollyEnd.set( 0, distance ); 606 | 607 | dollyDelta.subVectors( dollyEnd, dollyStart ); 608 | 609 | if ( dollyDelta.y > 0 ) { 610 | 611 | dollyOut( getZoomScale() ); 612 | 613 | } else if ( dollyDelta.y < 0 ) { 614 | 615 | dollyIn( getZoomScale() ); 616 | 617 | } 618 | 619 | dollyStart.copy( dollyEnd ); 620 | 621 | scope.update(); 622 | 623 | } 624 | 625 | function handleTouchMovePan( event ) { 626 | 627 | //console.log( 'handleTouchMovePan' ); 628 | 629 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 630 | 631 | panDelta.subVectors( panEnd, panStart ); 632 | 633 | pan( panDelta.x, panDelta.y ); 634 | 635 | panStart.copy( panEnd ); 636 | 637 | scope.update(); 638 | 639 | } 640 | 641 | function handleTouchEnd( event ) { 642 | 643 | //console.log( 'handleTouchEnd' ); 644 | 645 | } 646 | 647 | // 648 | // event handlers - FSM: listen for events and reset state 649 | // 650 | 651 | function onMouseDown( event ) { 652 | 653 | if ( scope.enabled === false ) return; 654 | 655 | event.preventDefault(); 656 | 657 | if ( event.button === scope.mouseButtons.ORBIT ) { 658 | 659 | if ( scope.enableRotate === false ) return; 660 | 661 | handleMouseDownRotate( event ); 662 | 663 | state = STATE.ROTATE; 664 | 665 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 666 | 667 | if ( scope.enableZoom === false ) return; 668 | 669 | handleMouseDownDolly( event ); 670 | 671 | state = STATE.DOLLY; 672 | 673 | } else if ( event.button === scope.mouseButtons.PAN ) { 674 | 675 | if ( scope.enablePan === false ) return; 676 | 677 | handleMouseDownPan( event ); 678 | 679 | state = STATE.PAN; 680 | 681 | } 682 | 683 | if ( state !== STATE.NONE ) { 684 | 685 | document.addEventListener( 'mousemove', onMouseMove, false ); 686 | document.addEventListener( 'mouseup', onMouseUp, false ); 687 | 688 | scope.dispatchEvent( startEvent ); 689 | 690 | } 691 | 692 | } 693 | 694 | function onMouseMove( event ) { 695 | 696 | if ( scope.enabled === false ) return; 697 | 698 | event.preventDefault(); 699 | 700 | if ( state === STATE.ROTATE ) { 701 | 702 | if ( scope.enableRotate === false ) return; 703 | 704 | handleMouseMoveRotate( event ); 705 | 706 | } else if ( state === STATE.DOLLY ) { 707 | 708 | if ( scope.enableZoom === false ) return; 709 | 710 | handleMouseMoveDolly( event ); 711 | 712 | } else if ( state === STATE.PAN ) { 713 | 714 | if ( scope.enablePan === false ) return; 715 | 716 | handleMouseMovePan( event ); 717 | 718 | } 719 | 720 | } 721 | 722 | function onMouseUp( event ) { 723 | 724 | if ( scope.enabled === false ) return; 725 | 726 | handleMouseUp( event ); 727 | 728 | document.removeEventListener( 'mousemove', onMouseMove, false ); 729 | document.removeEventListener( 'mouseup', onMouseUp, false ); 730 | 731 | scope.dispatchEvent( endEvent ); 732 | 733 | state = STATE.NONE; 734 | 735 | } 736 | 737 | function onMouseWheel( event ) { 738 | 739 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 740 | 741 | event.preventDefault(); 742 | event.stopPropagation(); 743 | 744 | handleMouseWheel( event ); 745 | 746 | scope.dispatchEvent( startEvent ); // not sure why these are here... 747 | scope.dispatchEvent( endEvent ); 748 | 749 | } 750 | 751 | function onKeyDown( event ) { 752 | 753 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 754 | 755 | handleKeyDown( event ); 756 | 757 | } 758 | 759 | function onTouchStart( event ) { 760 | 761 | if ( scope.enabled === false ) return; 762 | 763 | switch ( event.touches.length ) { 764 | 765 | case 1: // one-fingered touch: rotate 766 | 767 | if ( scope.enableRotate === false ) return; 768 | 769 | handleTouchStartRotate( event ); 770 | 771 | state = STATE.TOUCH_ROTATE; 772 | 773 | break; 774 | 775 | case 2: // two-fingered touch: dolly 776 | 777 | if ( scope.enableZoom === false ) return; 778 | 779 | handleTouchStartDolly( event ); 780 | 781 | state = STATE.TOUCH_DOLLY; 782 | 783 | break; 784 | 785 | case 3: // three-fingered touch: pan 786 | 787 | if ( scope.enablePan === false ) return; 788 | 789 | handleTouchStartPan( event ); 790 | 791 | state = STATE.TOUCH_PAN; 792 | 793 | break; 794 | 795 | default: 796 | 797 | state = STATE.NONE; 798 | 799 | } 800 | 801 | if ( state !== STATE.NONE ) { 802 | 803 | scope.dispatchEvent( startEvent ); 804 | 805 | } 806 | 807 | } 808 | 809 | function onTouchMove( event ) { 810 | 811 | if ( scope.enabled === false ) return; 812 | 813 | event.preventDefault(); 814 | event.stopPropagation(); 815 | 816 | switch ( event.touches.length ) { 817 | 818 | case 1: // one-fingered touch: rotate 819 | 820 | if ( scope.enableRotate === false ) return; 821 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 822 | 823 | handleTouchMoveRotate( event ); 824 | 825 | break; 826 | 827 | case 2: // two-fingered touch: dolly 828 | 829 | if ( scope.enableZoom === false ) return; 830 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 831 | 832 | handleTouchMoveDolly( event ); 833 | 834 | break; 835 | 836 | case 3: // three-fingered touch: pan 837 | 838 | if ( scope.enablePan === false ) return; 839 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 840 | 841 | handleTouchMovePan( event ); 842 | 843 | break; 844 | 845 | default: 846 | 847 | state = STATE.NONE; 848 | 849 | } 850 | 851 | } 852 | 853 | function onTouchEnd( event ) { 854 | 855 | if ( scope.enabled === false ) return; 856 | 857 | handleTouchEnd( event ); 858 | 859 | scope.dispatchEvent( endEvent ); 860 | 861 | state = STATE.NONE; 862 | 863 | } 864 | 865 | function onContextMenu( event ) { 866 | 867 | event.preventDefault(); 868 | 869 | } 870 | 871 | // 872 | 873 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 874 | 875 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 876 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 877 | 878 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 879 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 880 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 881 | 882 | window.addEventListener( 'keydown', onKeyDown, false ); 883 | 884 | // force an update at start 885 | 886 | this.update(); 887 | 888 | }; 889 | 890 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 891 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 892 | 893 | Object.defineProperties( THREE.OrbitControls.prototype, { 894 | 895 | center: { 896 | 897 | get: function () { 898 | 899 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 900 | return this.target; 901 | 902 | } 903 | 904 | }, 905 | 906 | // backward compatibility 907 | 908 | noZoom: { 909 | 910 | get: function () { 911 | 912 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 913 | return ! this.enableZoom; 914 | 915 | }, 916 | 917 | set: function ( value ) { 918 | 919 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 920 | this.enableZoom = ! value; 921 | 922 | } 923 | 924 | }, 925 | 926 | noRotate: { 927 | 928 | get: function () { 929 | 930 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 931 | return ! this.enableRotate; 932 | 933 | }, 934 | 935 | set: function ( value ) { 936 | 937 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 938 | this.enableRotate = ! value; 939 | 940 | } 941 | 942 | }, 943 | 944 | noPan: { 945 | 946 | get: function () { 947 | 948 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 949 | return ! this.enablePan; 950 | 951 | }, 952 | 953 | set: function ( value ) { 954 | 955 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 956 | this.enablePan = ! value; 957 | 958 | } 959 | 960 | }, 961 | 962 | noKeys: { 963 | 964 | get: function () { 965 | 966 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 967 | return ! this.enableKeys; 968 | 969 | }, 970 | 971 | set: function ( value ) { 972 | 973 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 974 | this.enableKeys = ! value; 975 | 976 | } 977 | 978 | }, 979 | 980 | staticMoving: { 981 | 982 | get: function () { 983 | 984 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 985 | return ! this.enableDamping; 986 | 987 | }, 988 | 989 | set: function ( value ) { 990 | 991 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 992 | this.enableDamping = ! value; 993 | 994 | } 995 | 996 | }, 997 | 998 | dynamicDampingFactor: { 999 | 1000 | get: function () { 1001 | 1002 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1003 | return this.dampingFactor; 1004 | 1005 | }, 1006 | 1007 | set: function ( value ) { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1010 | this.dampingFactor = value; 1011 | 1012 | } 1013 | 1014 | } 1015 | 1016 | } ); 1017 | -------------------------------------------------------------------------------- /TransformControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author arodic / https://github.com/arodic 3 | */ 4 | 5 | ( function () { 6 | 7 | 'use strict'; 8 | 9 | var GizmoMaterial = function ( parameters ) { 10 | 11 | THREE.MeshBasicMaterial.call( this ); 12 | 13 | this.depthTest = false; 14 | this.depthWrite = false; 15 | this.side = THREE.FrontSide; 16 | this.transparent = true; 17 | 18 | this.setValues( parameters ); 19 | 20 | this.oldColor = this.color.clone(); 21 | this.oldOpacity = this.opacity; 22 | 23 | this.highlight = function( highlighted ) { 24 | 25 | if ( highlighted ) { 26 | 27 | this.color.setRGB( 1, 1, 0 ); 28 | this.opacity = 1; 29 | 30 | } else { 31 | 32 | this.color.copy( this.oldColor ); 33 | this.opacity = this.oldOpacity; 34 | 35 | } 36 | 37 | }; 38 | 39 | }; 40 | 41 | GizmoMaterial.prototype = Object.create( THREE.MeshBasicMaterial.prototype ); 42 | GizmoMaterial.prototype.constructor = GizmoMaterial; 43 | 44 | 45 | var GizmoLineMaterial = function ( parameters ) { 46 | 47 | THREE.LineBasicMaterial.call( this ); 48 | 49 | this.depthTest = false; 50 | this.depthWrite = false; 51 | this.transparent = true; 52 | this.linewidth = 1; 53 | 54 | this.setValues( parameters ); 55 | 56 | this.oldColor = this.color.clone(); 57 | this.oldOpacity = this.opacity; 58 | 59 | this.highlight = function( highlighted ) { 60 | 61 | if ( highlighted ) { 62 | 63 | this.color.setRGB( 1, 1, 0 ); 64 | this.opacity = 1; 65 | 66 | } else { 67 | 68 | this.color.copy( this.oldColor ); 69 | this.opacity = this.oldOpacity; 70 | 71 | } 72 | 73 | }; 74 | 75 | }; 76 | 77 | GizmoLineMaterial.prototype = Object.create( THREE.LineBasicMaterial.prototype ); 78 | GizmoLineMaterial.prototype.constructor = GizmoLineMaterial; 79 | 80 | 81 | var pickerMaterial = new GizmoMaterial( { visible: false, transparent: false } ); 82 | 83 | 84 | THREE.TransformGizmo = function () { 85 | 86 | var scope = this; 87 | 88 | this.init = function () { 89 | 90 | THREE.Object3D.call( this ); 91 | 92 | this.handles = new THREE.Object3D(); 93 | this.pickers = new THREE.Object3D(); 94 | this.planes = new THREE.Object3D(); 95 | 96 | this.add( this.handles ); 97 | this.add( this.pickers ); 98 | this.add( this.planes ); 99 | 100 | //// PLANES 101 | 102 | var planeGeometry = new THREE.PlaneBufferGeometry( 50, 50, 2, 2 ); 103 | var planeMaterial = new THREE.MeshBasicMaterial( { visible: false, side: THREE.DoubleSide } ); 104 | 105 | var planes = { 106 | "XY": new THREE.Mesh( planeGeometry, planeMaterial ), 107 | "YZ": new THREE.Mesh( planeGeometry, planeMaterial ), 108 | "XZ": new THREE.Mesh( planeGeometry, planeMaterial ), 109 | "XYZE": new THREE.Mesh( planeGeometry, planeMaterial ) 110 | }; 111 | 112 | this.activePlane = planes[ "XYZE" ]; 113 | 114 | planes[ "YZ" ].rotation.set( 0, Math.PI / 2, 0 ); 115 | planes[ "XZ" ].rotation.set( - Math.PI / 2, 0, 0 ); 116 | 117 | for ( var i in planes ) { 118 | 119 | planes[ i ].name = i; 120 | this.planes.add( planes[ i ] ); 121 | this.planes[ i ] = planes[ i ]; 122 | 123 | } 124 | 125 | //// HANDLES AND PICKERS 126 | 127 | var setupGizmos = function( gizmoMap, parent ) { 128 | 129 | for ( var name in gizmoMap ) { 130 | 131 | for ( i = gizmoMap[ name ].length; i --; ) { 132 | 133 | var object = gizmoMap[ name ][ i ][ 0 ]; 134 | var position = gizmoMap[ name ][ i ][ 1 ]; 135 | var rotation = gizmoMap[ name ][ i ][ 2 ]; 136 | 137 | object.name = name; 138 | 139 | if ( position ) object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] ); 140 | if ( rotation ) object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] ); 141 | 142 | parent.add( object ); 143 | 144 | } 145 | 146 | } 147 | 148 | }; 149 | 150 | setupGizmos( this.handleGizmos, this.handles ); 151 | setupGizmos( this.pickerGizmos, this.pickers ); 152 | 153 | // reset Transformations 154 | 155 | this.traverse( function ( child ) { 156 | 157 | if ( child instanceof THREE.Mesh ) { 158 | 159 | child.updateMatrix(); 160 | 161 | var tempGeometry = child.geometry.clone(); 162 | tempGeometry.applyMatrix( child.matrix ); 163 | child.geometry = tempGeometry; 164 | 165 | child.position.set( 0, 0, 0 ); 166 | child.rotation.set( 0, 0, 0 ); 167 | child.scale.set( 1, 1, 1 ); 168 | 169 | } 170 | 171 | } ); 172 | 173 | }; 174 | 175 | this.highlight = function ( axis ) { 176 | 177 | this.traverse( function( child ) { 178 | 179 | if ( child.material && child.material.highlight ) { 180 | 181 | if ( child.name === axis ) { 182 | 183 | child.material.highlight( true ); 184 | 185 | } else { 186 | 187 | child.material.highlight( false ); 188 | 189 | } 190 | 191 | } 192 | 193 | } ); 194 | 195 | }; 196 | 197 | }; 198 | 199 | THREE.TransformGizmo.prototype = Object.create( THREE.Object3D.prototype ); 200 | THREE.TransformGizmo.prototype.constructor = THREE.TransformGizmo; 201 | 202 | THREE.TransformGizmo.prototype.update = function ( rotation, eye ) { 203 | 204 | var vec1 = new THREE.Vector3( 0, 0, 0 ); 205 | var vec2 = new THREE.Vector3( 0, 1, 0 ); 206 | var lookAtMatrix = new THREE.Matrix4(); 207 | 208 | this.traverse( function( child ) { 209 | 210 | if ( child.name.search( "E" ) !== - 1 ) { 211 | 212 | child.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( eye, vec1, vec2 ) ); 213 | 214 | } else if ( child.name.search( "X" ) !== - 1 || child.name.search( "Y" ) !== - 1 || child.name.search( "Z" ) !== - 1 ) { 215 | 216 | child.quaternion.setFromEuler( rotation ); 217 | 218 | } 219 | 220 | } ); 221 | 222 | }; 223 | 224 | THREE.TransformGizmoTranslate = function () { 225 | 226 | THREE.TransformGizmo.call( this ); 227 | 228 | var arrowGeometry = new THREE.Geometry(); 229 | var mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0, 0.05, 0.2, 12, 1, false ) ); 230 | mesh.position.y = 0.5; 231 | mesh.updateMatrix(); 232 | 233 | arrowGeometry.merge( mesh.geometry, mesh.matrix ); 234 | 235 | var lineXGeometry = new THREE.BufferGeometry(); 236 | lineXGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) ); 237 | 238 | var lineYGeometry = new THREE.BufferGeometry(); 239 | lineYGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); 240 | 241 | var lineZGeometry = new THREE.BufferGeometry(); 242 | lineZGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); 243 | 244 | this.handleGizmos = { 245 | 246 | X: [ 247 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ], 248 | [ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ] 249 | ], 250 | 251 | Y: [ 252 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ], 253 | [ new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] 254 | ], 255 | 256 | Z: [ 257 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ], 258 | [ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ] 259 | ], 260 | 261 | XYZ: [ 262 | [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, 0, 0 ] ] 263 | ], 264 | 265 | XY: [ 266 | [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ), [ 0.15, 0.15, 0 ] ] 267 | ], 268 | 269 | YZ: [ 270 | [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0x00ffff, opacity: 0.25 } ) ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ] ] 271 | ], 272 | 273 | XZ: [ 274 | [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xff00ff, opacity: 0.25 } ) ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ] ] 275 | ] 276 | 277 | }; 278 | 279 | this.pickerGizmos = { 280 | 281 | X: [ 282 | [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ] 283 | ], 284 | 285 | Y: [ 286 | [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0.6, 0 ] ] 287 | ], 288 | 289 | Z: [ 290 | [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ] 291 | ], 292 | 293 | XYZ: [ 294 | [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), pickerMaterial ) ] 295 | ], 296 | 297 | XY: [ 298 | [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0.2, 0.2, 0 ] ] 299 | ], 300 | 301 | YZ: [ 302 | [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ] ] 303 | ], 304 | 305 | XZ: [ 306 | [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0.2, 0, 0.2 ], [ - Math.PI / 2, 0, 0 ] ] 307 | ] 308 | 309 | }; 310 | 311 | this.setActivePlane = function ( axis, eye ) { 312 | 313 | var tempMatrix = new THREE.Matrix4(); 314 | eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) ); 315 | 316 | if ( axis === "X" ) { 317 | 318 | this.activePlane = this.planes[ "XY" ]; 319 | 320 | if ( Math.abs( eye.y ) > Math.abs( eye.z ) ) this.activePlane = this.planes[ "XZ" ]; 321 | 322 | } 323 | 324 | if ( axis === "Y" ) { 325 | 326 | this.activePlane = this.planes[ "XY" ]; 327 | 328 | if ( Math.abs( eye.x ) > Math.abs( eye.z ) ) this.activePlane = this.planes[ "YZ" ]; 329 | 330 | } 331 | 332 | if ( axis === "Z" ) { 333 | 334 | this.activePlane = this.planes[ "XZ" ]; 335 | 336 | if ( Math.abs( eye.x ) > Math.abs( eye.y ) ) this.activePlane = this.planes[ "YZ" ]; 337 | 338 | } 339 | 340 | if ( axis === "XYZ" ) this.activePlane = this.planes[ "XYZE" ]; 341 | 342 | if ( axis === "XY" ) this.activePlane = this.planes[ "XY" ]; 343 | 344 | if ( axis === "YZ" ) this.activePlane = this.planes[ "YZ" ]; 345 | 346 | if ( axis === "XZ" ) this.activePlane = this.planes[ "XZ" ]; 347 | 348 | }; 349 | 350 | this.init(); 351 | 352 | }; 353 | 354 | THREE.TransformGizmoTranslate.prototype = Object.create( THREE.TransformGizmo.prototype ); 355 | THREE.TransformGizmoTranslate.prototype.constructor = THREE.TransformGizmoTranslate; 356 | 357 | THREE.TransformGizmoRotate = function () { 358 | 359 | THREE.TransformGizmo.call( this ); 360 | 361 | var CircleGeometry = function ( radius, facing, arc ) { 362 | 363 | var geometry = new THREE.BufferGeometry(); 364 | var vertices = []; 365 | arc = arc ? arc : 1; 366 | 367 | for ( var i = 0; i <= 64 * arc; ++ i ) { 368 | 369 | if ( facing === 'x' ) vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius ); 370 | if ( facing === 'y' ) vertices.push( Math.cos( i / 32 * Math.PI ) * radius, 0, Math.sin( i / 32 * Math.PI ) * radius ); 371 | if ( facing === 'z' ) vertices.push( Math.sin( i / 32 * Math.PI ) * radius, Math.cos( i / 32 * Math.PI ) * radius, 0 ); 372 | 373 | } 374 | 375 | geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); 376 | return geometry; 377 | 378 | }; 379 | 380 | this.handleGizmos = { 381 | 382 | X: [ 383 | [ new THREE.Line( new CircleGeometry( 1, 'x', 0.5 ), new GizmoLineMaterial( { color: 0xff0000 } ) ) ] 384 | ], 385 | 386 | Y: [ 387 | [ new THREE.Line( new CircleGeometry( 1, 'y', 0.5 ), new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] 388 | ], 389 | 390 | Z: [ 391 | [ new THREE.Line( new CircleGeometry( 1, 'z', 0.5 ), new GizmoLineMaterial( { color: 0x0000ff } ) ) ] 392 | ], 393 | 394 | E: [ 395 | [ new THREE.Line( new CircleGeometry( 1.25, 'z', 1 ), new GizmoLineMaterial( { color: 0xcccc00 } ) ) ] 396 | ], 397 | 398 | XYZE: [ 399 | [ new THREE.Line( new CircleGeometry( 1, 'z', 1 ), new GizmoLineMaterial( { color: 0x787878 } ) ) ] 400 | ] 401 | 402 | }; 403 | 404 | this.pickerGizmos = { 405 | 406 | X: [ 407 | [ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ] ] 408 | ], 409 | 410 | Y: [ 411 | [ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ] ] 412 | ], 413 | 414 | Z: [ 415 | [ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ] 416 | ], 417 | 418 | E: [ 419 | [ new THREE.Mesh( new THREE.TorusBufferGeometry( 1.25, 0.12, 2, 24 ), pickerMaterial ) ] 420 | ], 421 | 422 | XYZE: [ 423 | [ new THREE.Mesh() ]// TODO 424 | ] 425 | 426 | }; 427 | 428 | this.setActivePlane = function ( axis ) { 429 | 430 | if ( axis === "E" ) this.activePlane = this.planes[ "XYZE" ]; 431 | 432 | if ( axis === "X" ) this.activePlane = this.planes[ "YZ" ]; 433 | 434 | if ( axis === "Y" ) this.activePlane = this.planes[ "XZ" ]; 435 | 436 | if ( axis === "Z" ) this.activePlane = this.planes[ "XY" ]; 437 | 438 | }; 439 | 440 | this.update = function ( rotation, eye2 ) { 441 | 442 | THREE.TransformGizmo.prototype.update.apply( this, arguments ); 443 | 444 | var group = { 445 | 446 | handles: this[ "handles" ], 447 | pickers: this[ "pickers" ] 448 | 449 | }; 450 | 451 | var tempMatrix = new THREE.Matrix4(); 452 | var worldRotation = new THREE.Euler( 0, 0, 1 ); 453 | var tempQuaternion = new THREE.Quaternion(); 454 | var unitX = new THREE.Vector3( 1, 0, 0 ); 455 | var unitY = new THREE.Vector3( 0, 1, 0 ); 456 | var unitZ = new THREE.Vector3( 0, 0, 1 ); 457 | var quaternionX = new THREE.Quaternion(); 458 | var quaternionY = new THREE.Quaternion(); 459 | var quaternionZ = new THREE.Quaternion(); 460 | var eye = eye2.clone(); 461 | 462 | worldRotation.copy( this.planes[ "XY" ].rotation ); 463 | tempQuaternion.setFromEuler( worldRotation ); 464 | 465 | tempMatrix.makeRotationFromQuaternion( tempQuaternion ).getInverse( tempMatrix ); 466 | eye.applyMatrix4( tempMatrix ); 467 | 468 | this.traverse( function( child ) { 469 | 470 | tempQuaternion.setFromEuler( worldRotation ); 471 | 472 | if ( child.name === "X" ) { 473 | 474 | quaternionX.setFromAxisAngle( unitX, Math.atan2( - eye.y, eye.z ) ); 475 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); 476 | child.quaternion.copy( tempQuaternion ); 477 | 478 | } 479 | 480 | if ( child.name === "Y" ) { 481 | 482 | quaternionY.setFromAxisAngle( unitY, Math.atan2( eye.x, eye.z ) ); 483 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY ); 484 | child.quaternion.copy( tempQuaternion ); 485 | 486 | } 487 | 488 | if ( child.name === "Z" ) { 489 | 490 | quaternionZ.setFromAxisAngle( unitZ, Math.atan2( eye.y, eye.x ) ); 491 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ ); 492 | child.quaternion.copy( tempQuaternion ); 493 | 494 | } 495 | 496 | } ); 497 | 498 | }; 499 | 500 | this.init(); 501 | 502 | }; 503 | 504 | THREE.TransformGizmoRotate.prototype = Object.create( THREE.TransformGizmo.prototype ); 505 | THREE.TransformGizmoRotate.prototype.constructor = THREE.TransformGizmoRotate; 506 | 507 | THREE.TransformGizmoScale = function () { 508 | 509 | THREE.TransformGizmo.call( this ); 510 | 511 | var arrowGeometry = new THREE.Geometry(); 512 | var mesh = new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ) ); 513 | mesh.position.y = 0.5; 514 | mesh.updateMatrix(); 515 | 516 | arrowGeometry.merge( mesh.geometry, mesh.matrix ); 517 | 518 | var lineXGeometry = new THREE.BufferGeometry(); 519 | lineXGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) ); 520 | 521 | var lineYGeometry = new THREE.BufferGeometry(); 522 | lineYGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); 523 | 524 | var lineZGeometry = new THREE.BufferGeometry(); 525 | lineZGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); 526 | 527 | this.handleGizmos = { 528 | 529 | X: [ 530 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ], 531 | [ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ] 532 | ], 533 | 534 | Y: [ 535 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ], 536 | [ new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] 537 | ], 538 | 539 | Z: [ 540 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ], 541 | [ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ] 542 | ], 543 | 544 | XYZ: [ 545 | [ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ] 546 | ] 547 | 548 | }; 549 | 550 | this.pickerGizmos = { 551 | 552 | X: [ 553 | [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ] 554 | ], 555 | 556 | Y: [ 557 | [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0.6, 0 ] ] 558 | ], 559 | 560 | Z: [ 561 | [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ] 562 | ], 563 | 564 | XYZ: [ 565 | [ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.4, 0.4, 0.4 ), pickerMaterial ) ] 566 | ] 567 | 568 | }; 569 | 570 | this.setActivePlane = function ( axis, eye ) { 571 | 572 | var tempMatrix = new THREE.Matrix4(); 573 | eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) ); 574 | 575 | if ( axis === "X" ) { 576 | 577 | this.activePlane = this.planes[ "XY" ]; 578 | if ( Math.abs( eye.y ) > Math.abs( eye.z ) ) this.activePlane = this.planes[ "XZ" ]; 579 | 580 | } 581 | 582 | if ( axis === "Y" ) { 583 | 584 | this.activePlane = this.planes[ "XY" ]; 585 | if ( Math.abs( eye.x ) > Math.abs( eye.z ) ) this.activePlane = this.planes[ "YZ" ]; 586 | 587 | } 588 | 589 | if ( axis === "Z" ) { 590 | 591 | this.activePlane = this.planes[ "XZ" ]; 592 | if ( Math.abs( eye.x ) > Math.abs( eye.y ) ) this.activePlane = this.planes[ "YZ" ]; 593 | 594 | } 595 | 596 | if ( axis === "XYZ" ) this.activePlane = this.planes[ "XYZE" ]; 597 | 598 | }; 599 | 600 | this.init(); 601 | 602 | }; 603 | 604 | THREE.TransformGizmoScale.prototype = Object.create( THREE.TransformGizmo.prototype ); 605 | THREE.TransformGizmoScale.prototype.constructor = THREE.TransformGizmoScale; 606 | 607 | THREE.TransformControls = function ( camera, domElement ) { 608 | 609 | // TODO: Make non-uniform scale and rotate play nice in hierarchies 610 | // TODO: ADD RXYZ contol 611 | 612 | THREE.Object3D.call( this ); 613 | 614 | domElement = ( domElement !== undefined ) ? domElement : document; 615 | 616 | this.object = undefined; 617 | this.visible = false; 618 | this.translationSnap = null; 619 | this.rotationSnap = null; 620 | this.space = "world"; 621 | this.size = 1; 622 | this.axis = null; 623 | 624 | var scope = this; 625 | 626 | var _mode = "translate"; 627 | var _dragging = false; 628 | var _plane = "XY"; 629 | var _gizmo = { 630 | 631 | "translate": new THREE.TransformGizmoTranslate(), 632 | "rotate": new THREE.TransformGizmoRotate(), 633 | "scale": new THREE.TransformGizmoScale() 634 | }; 635 | 636 | for ( var type in _gizmo ) { 637 | 638 | var gizmoObj = _gizmo[ type ]; 639 | 640 | gizmoObj.visible = ( type === _mode ); 641 | this.add( gizmoObj ); 642 | 643 | } 644 | 645 | var changeEvent = { type: "change" }; 646 | var mouseDownEvent = { type: "mouseDown" }; 647 | var mouseUpEvent = { type: "mouseUp", mode: _mode }; 648 | var objectChangeEvent = { type: "objectChange" }; 649 | 650 | var ray = new THREE.Raycaster(); 651 | var pointerVector = new THREE.Vector2(); 652 | 653 | var point = new THREE.Vector3(); 654 | var offset = new THREE.Vector3(); 655 | 656 | var rotation = new THREE.Vector3(); 657 | var offsetRotation = new THREE.Vector3(); 658 | var scale = 1; 659 | 660 | var lookAtMatrix = new THREE.Matrix4(); 661 | var eye = new THREE.Vector3(); 662 | 663 | var tempMatrix = new THREE.Matrix4(); 664 | var tempVector = new THREE.Vector3(); 665 | var tempQuaternion = new THREE.Quaternion(); 666 | var unitX = new THREE.Vector3( 1, 0, 0 ); 667 | var unitY = new THREE.Vector3( 0, 1, 0 ); 668 | var unitZ = new THREE.Vector3( 0, 0, 1 ); 669 | 670 | var quaternionXYZ = new THREE.Quaternion(); 671 | var quaternionX = new THREE.Quaternion(); 672 | var quaternionY = new THREE.Quaternion(); 673 | var quaternionZ = new THREE.Quaternion(); 674 | var quaternionE = new THREE.Quaternion(); 675 | 676 | var oldPosition = new THREE.Vector3(); 677 | var oldScale = new THREE.Vector3(); 678 | var oldRotationMatrix = new THREE.Matrix4(); 679 | 680 | var parentRotationMatrix = new THREE.Matrix4(); 681 | var parentScale = new THREE.Vector3(); 682 | 683 | var worldPosition = new THREE.Vector3(); 684 | var worldRotation = new THREE.Euler(); 685 | var worldRotationMatrix = new THREE.Matrix4(); 686 | var camPosition = new THREE.Vector3(); 687 | var camRotation = new THREE.Euler(); 688 | 689 | domElement.addEventListener( "mousedown", onPointerDown, false ); 690 | domElement.addEventListener( "touchstart", onPointerDown, false ); 691 | 692 | domElement.addEventListener( "mousemove", onPointerHover, false ); 693 | domElement.addEventListener( "touchmove", onPointerHover, false ); 694 | 695 | domElement.addEventListener( "mousemove", onPointerMove, false ); 696 | domElement.addEventListener( "touchmove", onPointerMove, false ); 697 | 698 | domElement.addEventListener( "mouseup", onPointerUp, false ); 699 | domElement.addEventListener( "mouseout", onPointerUp, false ); 700 | domElement.addEventListener( "touchend", onPointerUp, false ); 701 | domElement.addEventListener( "touchcancel", onPointerUp, false ); 702 | domElement.addEventListener( "touchleave", onPointerUp, false ); 703 | 704 | this.dispose = function () { 705 | 706 | domElement.removeEventListener( "mousedown", onPointerDown ); 707 | domElement.removeEventListener( "touchstart", onPointerDown ); 708 | 709 | domElement.removeEventListener( "mousemove", onPointerHover ); 710 | domElement.removeEventListener( "touchmove", onPointerHover ); 711 | 712 | domElement.removeEventListener( "mousemove", onPointerMove ); 713 | domElement.removeEventListener( "touchmove", onPointerMove ); 714 | 715 | domElement.removeEventListener( "mouseup", onPointerUp ); 716 | domElement.removeEventListener( "mouseout", onPointerUp ); 717 | domElement.removeEventListener( "touchend", onPointerUp ); 718 | domElement.removeEventListener( "touchcancel", onPointerUp ); 719 | domElement.removeEventListener( "touchleave", onPointerUp ); 720 | 721 | }; 722 | 723 | this.attach = function ( object ) { 724 | 725 | this.object = object; 726 | this.visible = true; 727 | this.update(); 728 | 729 | }; 730 | 731 | this.detach = function () { 732 | 733 | this.object = undefined; 734 | this.visible = false; 735 | this.axis = null; 736 | 737 | }; 738 | 739 | this.getMode = function () { 740 | 741 | return _mode; 742 | 743 | }; 744 | 745 | this.setMode = function ( mode ) { 746 | 747 | _mode = mode ? mode : _mode; 748 | 749 | if ( _mode === "scale" ) scope.space = "local"; 750 | 751 | for ( var type in _gizmo ) _gizmo[ type ].visible = ( type === _mode ); 752 | 753 | this.update(); 754 | scope.dispatchEvent( changeEvent ); 755 | 756 | }; 757 | 758 | this.setTranslationSnap = function ( translationSnap ) { 759 | 760 | scope.translationSnap = translationSnap; 761 | 762 | }; 763 | 764 | this.setRotationSnap = function ( rotationSnap ) { 765 | 766 | scope.rotationSnap = rotationSnap; 767 | 768 | }; 769 | 770 | this.setSize = function ( size ) { 771 | 772 | scope.size = size; 773 | this.update(); 774 | scope.dispatchEvent( changeEvent ); 775 | 776 | }; 777 | 778 | this.setSpace = function ( space ) { 779 | 780 | scope.space = space; 781 | this.update(); 782 | scope.dispatchEvent( changeEvent ); 783 | 784 | }; 785 | 786 | this.update = function () { 787 | 788 | if ( scope.object === undefined ) return; 789 | 790 | scope.object.updateMatrixWorld(); 791 | worldPosition.setFromMatrixPosition( scope.object.matrixWorld ); 792 | worldRotation.setFromRotationMatrix( tempMatrix.extractRotation( scope.object.matrixWorld ) ); 793 | 794 | camera.updateMatrixWorld(); 795 | camPosition.setFromMatrixPosition( camera.matrixWorld ); 796 | camRotation.setFromRotationMatrix( tempMatrix.extractRotation( camera.matrixWorld ) ); 797 | 798 | scale = worldPosition.distanceTo( camPosition ) / 6 * scope.size; 799 | this.position.copy( worldPosition ); 800 | this.scale.set( scale, scale, scale ); 801 | 802 | if ( camera instanceof THREE.PerspectiveCamera ) { 803 | 804 | eye.copy( camPosition ).sub( worldPosition ).normalize(); 805 | 806 | } else if ( camera instanceof THREE.OrthographicCamera ) { 807 | 808 | eye.copy( camPosition ).normalize(); 809 | 810 | } 811 | 812 | if ( scope.space === "local" ) { 813 | 814 | _gizmo[ _mode ].update( worldRotation, eye ); 815 | 816 | } else if ( scope.space === "world" ) { 817 | 818 | _gizmo[ _mode ].update( new THREE.Euler(), eye ); 819 | 820 | } 821 | 822 | _gizmo[ _mode ].highlight( scope.axis ); 823 | 824 | }; 825 | 826 | function onPointerHover( event ) { 827 | 828 | if ( scope.object === undefined || _dragging === true || ( event.button !== undefined && event.button !== 0 ) ) return; 829 | 830 | var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; 831 | 832 | var intersect = intersectObjects( pointer, _gizmo[ _mode ].pickers.children ); 833 | 834 | var axis = null; 835 | 836 | if ( intersect ) { 837 | 838 | axis = intersect.object.name; 839 | 840 | event.preventDefault(); 841 | 842 | } 843 | 844 | if ( scope.axis !== axis ) { 845 | 846 | scope.axis = axis; 847 | scope.update(); 848 | scope.dispatchEvent( changeEvent ); 849 | 850 | } 851 | 852 | } 853 | 854 | function onPointerDown( event ) { 855 | 856 | if ( scope.object === undefined || _dragging === true || ( event.button !== undefined && event.button !== 0 ) ) return; 857 | 858 | var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; 859 | 860 | if ( pointer.button === 0 || pointer.button === undefined ) { 861 | 862 | var intersect = intersectObjects( pointer, _gizmo[ _mode ].pickers.children ); 863 | 864 | if ( intersect ) { 865 | 866 | event.preventDefault(); 867 | event.stopPropagation(); 868 | 869 | scope.dispatchEvent( mouseDownEvent ); 870 | 871 | scope.axis = intersect.object.name; 872 | 873 | scope.update(); 874 | 875 | eye.copy( camPosition ).sub( worldPosition ).normalize(); 876 | 877 | _gizmo[ _mode ].setActivePlane( scope.axis, eye ); 878 | 879 | var planeIntersect = intersectObjects( pointer, [ _gizmo[ _mode ].activePlane ] ); 880 | 881 | if ( planeIntersect ) { 882 | 883 | oldPosition.copy( scope.object.position ); 884 | oldScale.copy( scope.object.scale ); 885 | 886 | oldRotationMatrix.extractRotation( scope.object.matrix ); 887 | worldRotationMatrix.extractRotation( scope.object.matrixWorld ); 888 | 889 | parentRotationMatrix.extractRotation( scope.object.parent.matrixWorld ); 890 | parentScale.setFromMatrixScale( tempMatrix.getInverse( scope.object.parent.matrixWorld ) ); 891 | 892 | offset.copy( planeIntersect.point ); 893 | 894 | } 895 | 896 | } 897 | 898 | } 899 | 900 | _dragging = true; 901 | 902 | } 903 | 904 | function onPointerMove( event ) { 905 | 906 | if ( scope.object === undefined || scope.axis === null || _dragging === false || ( event.button !== undefined && event.button !== 0 ) ) return; 907 | 908 | var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; 909 | 910 | var planeIntersect = intersectObjects( pointer, [ _gizmo[ _mode ].activePlane ] ); 911 | 912 | if ( planeIntersect === false ) return; 913 | 914 | event.preventDefault(); 915 | event.stopPropagation(); 916 | 917 | point.copy( planeIntersect.point ); 918 | 919 | if ( _mode === "translate" ) { 920 | 921 | point.sub( offset ); 922 | point.multiply( parentScale ); 923 | 924 | if ( scope.space === "local" ) { 925 | 926 | point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 927 | 928 | if ( scope.axis.search( "X" ) === - 1 ) point.x = 0; 929 | if ( scope.axis.search( "Y" ) === - 1 ) point.y = 0; 930 | if ( scope.axis.search( "Z" ) === - 1 ) point.z = 0; 931 | 932 | point.applyMatrix4( oldRotationMatrix ); 933 | 934 | scope.object.position.copy( oldPosition ); 935 | scope.object.position.add( point ); 936 | 937 | } 938 | 939 | if ( scope.space === "world" || scope.axis.search( "XYZ" ) !== - 1 ) { 940 | 941 | if ( scope.axis.search( "X" ) === - 1 ) point.x = 0; 942 | if ( scope.axis.search( "Y" ) === - 1 ) point.y = 0; 943 | if ( scope.axis.search( "Z" ) === - 1 ) point.z = 0; 944 | 945 | point.applyMatrix4( tempMatrix.getInverse( parentRotationMatrix ) ); 946 | 947 | scope.object.position.copy( oldPosition ); 948 | scope.object.position.add( point ); 949 | 950 | } 951 | 952 | if ( scope.translationSnap !== null ) { 953 | 954 | if ( scope.space === "local" ) { 955 | 956 | scope.object.position.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 957 | 958 | } 959 | 960 | if ( scope.axis.search( "X" ) !== - 1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.translationSnap ) * scope.translationSnap; 961 | if ( scope.axis.search( "Y" ) !== - 1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.translationSnap ) * scope.translationSnap; 962 | if ( scope.axis.search( "Z" ) !== - 1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.translationSnap ) * scope.translationSnap; 963 | 964 | if ( scope.space === "local" ) { 965 | 966 | scope.object.position.applyMatrix4( worldRotationMatrix ); 967 | 968 | } 969 | 970 | } 971 | 972 | } else if ( _mode === "scale" ) { 973 | 974 | point.sub( offset ); 975 | point.multiply( parentScale ); 976 | 977 | if ( scope.space === "local" ) { 978 | 979 | if ( scope.axis === "XYZ" ) { 980 | 981 | scale = 1 + ( ( point.y ) / Math.max( oldScale.x, oldScale.y, oldScale.z ) ); 982 | 983 | scope.object.scale.x = oldScale.x * scale; 984 | scope.object.scale.y = oldScale.y * scale; 985 | scope.object.scale.z = oldScale.z * scale; 986 | 987 | } else { 988 | 989 | point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 990 | 991 | if ( scope.axis === "X" ) scope.object.scale.x = oldScale.x * ( 1 + point.x / oldScale.x ); 992 | if ( scope.axis === "Y" ) scope.object.scale.y = oldScale.y * ( 1 + point.y / oldScale.y ); 993 | if ( scope.axis === "Z" ) scope.object.scale.z = oldScale.z * ( 1 + point.z / oldScale.z ); 994 | 995 | } 996 | 997 | } 998 | 999 | } else if ( _mode === "rotate" ) { 1000 | 1001 | point.sub( worldPosition ); 1002 | point.multiply( parentScale ); 1003 | tempVector.copy( offset ).sub( worldPosition ); 1004 | tempVector.multiply( parentScale ); 1005 | 1006 | if ( scope.axis === "E" ) { 1007 | 1008 | point.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) ); 1009 | tempVector.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) ); 1010 | 1011 | rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); 1012 | offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); 1013 | 1014 | tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); 1015 | 1016 | quaternionE.setFromAxisAngle( eye, rotation.z - offsetRotation.z ); 1017 | quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); 1018 | 1019 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionE ); 1020 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); 1021 | 1022 | scope.object.quaternion.copy( tempQuaternion ); 1023 | 1024 | } else if ( scope.axis === "XYZE" ) { 1025 | 1026 | quaternionE.setFromEuler( point.clone().cross( tempVector ).normalize() ); // rotation axis 1027 | 1028 | tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); 1029 | quaternionX.setFromAxisAngle( quaternionE, - point.clone().angleTo( tempVector ) ); 1030 | quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); 1031 | 1032 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); 1033 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); 1034 | 1035 | scope.object.quaternion.copy( tempQuaternion ); 1036 | 1037 | } else if ( scope.space === "local" ) { 1038 | 1039 | point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 1040 | 1041 | tempVector.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 1042 | 1043 | rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); 1044 | offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); 1045 | 1046 | quaternionXYZ.setFromRotationMatrix( oldRotationMatrix ); 1047 | 1048 | if ( scope.rotationSnap !== null ) { 1049 | 1050 | quaternionX.setFromAxisAngle( unitX, Math.round( ( rotation.x - offsetRotation.x ) / scope.rotationSnap ) * scope.rotationSnap ); 1051 | quaternionY.setFromAxisAngle( unitY, Math.round( ( rotation.y - offsetRotation.y ) / scope.rotationSnap ) * scope.rotationSnap ); 1052 | quaternionZ.setFromAxisAngle( unitZ, Math.round( ( rotation.z - offsetRotation.z ) / scope.rotationSnap ) * scope.rotationSnap ); 1053 | 1054 | } else { 1055 | 1056 | quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x ); 1057 | quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y ); 1058 | quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z ); 1059 | 1060 | } 1061 | 1062 | if ( scope.axis === "X" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionX ); 1063 | if ( scope.axis === "Y" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionY ); 1064 | if ( scope.axis === "Z" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionZ ); 1065 | 1066 | scope.object.quaternion.copy( quaternionXYZ ); 1067 | 1068 | } else if ( scope.space === "world" ) { 1069 | 1070 | rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); 1071 | offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); 1072 | 1073 | tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); 1074 | 1075 | if ( scope.rotationSnap !== null ) { 1076 | 1077 | quaternionX.setFromAxisAngle( unitX, Math.round( ( rotation.x - offsetRotation.x ) / scope.rotationSnap ) * scope.rotationSnap ); 1078 | quaternionY.setFromAxisAngle( unitY, Math.round( ( rotation.y - offsetRotation.y ) / scope.rotationSnap ) * scope.rotationSnap ); 1079 | quaternionZ.setFromAxisAngle( unitZ, Math.round( ( rotation.z - offsetRotation.z ) / scope.rotationSnap ) * scope.rotationSnap ); 1080 | 1081 | } else { 1082 | 1083 | quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x ); 1084 | quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y ); 1085 | quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z ); 1086 | 1087 | } 1088 | 1089 | quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); 1090 | 1091 | if ( scope.axis === "X" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); 1092 | if ( scope.axis === "Y" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY ); 1093 | if ( scope.axis === "Z" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ ); 1094 | 1095 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); 1096 | 1097 | scope.object.quaternion.copy( tempQuaternion ); 1098 | 1099 | } 1100 | 1101 | } 1102 | 1103 | scope.update(); 1104 | scope.dispatchEvent( changeEvent ); 1105 | scope.dispatchEvent( objectChangeEvent ); 1106 | 1107 | } 1108 | 1109 | function onPointerUp( event ) { 1110 | 1111 | event.preventDefault(); // Prevent MouseEvent on mobile 1112 | 1113 | if ( event.button !== undefined && event.button !== 0 ) return; 1114 | 1115 | if ( _dragging && ( scope.axis !== null ) ) { 1116 | 1117 | mouseUpEvent.mode = _mode; 1118 | scope.dispatchEvent( mouseUpEvent ); 1119 | 1120 | } 1121 | 1122 | _dragging = false; 1123 | 1124 | if ( 'TouchEvent' in window && event instanceof TouchEvent ) { 1125 | 1126 | // Force "rollover" 1127 | 1128 | scope.axis = null; 1129 | scope.update(); 1130 | scope.dispatchEvent( changeEvent ); 1131 | 1132 | } else { 1133 | 1134 | onPointerHover( event ); 1135 | 1136 | } 1137 | 1138 | } 1139 | 1140 | function intersectObjects( pointer, objects ) { 1141 | 1142 | var rect = domElement.getBoundingClientRect(); 1143 | var x = ( pointer.clientX - rect.left ) / rect.width; 1144 | var y = ( pointer.clientY - rect.top ) / rect.height; 1145 | 1146 | pointerVector.set( ( x * 2 ) - 1, - ( y * 2 ) + 1 ); 1147 | ray.setFromCamera( pointerVector, camera ); 1148 | 1149 | var intersections = ray.intersectObjects( objects, true ); 1150 | return intersections[ 0 ] ? intersections[ 0 ] : false; 1151 | 1152 | } 1153 | 1154 | }; 1155 | 1156 | THREE.TransformControls.prototype = Object.create( THREE.Object3D.prototype ); 1157 | THREE.TransformControls.prototype.constructor = THREE.TransformControls; 1158 | 1159 | }() ); 1160 | -------------------------------------------------------------------------------- /assets/headset.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.73 (sub 0) OBJ File: 'headset (1).blend' 2 | # www.blender.org 3 | mtllib headset (1).mtl 4 | o Cube 5 | v -0.432918 -0.095950 -0.245231 6 | v -0.432918 0.095950 -0.245231 7 | v -0.432918 -0.095950 -0.437132 8 | v -0.432918 0.095950 -0.437132 9 | v -0.378281 0.117514 -0.458696 10 | v -0.445198 0.000000 -0.458696 11 | v -0.378281 -0.117514 -0.458696 12 | v -0.445198 0.117514 -0.341182 13 | v -0.445198 0.000000 -0.223667 14 | v -0.445198 -0.117514 -0.341182 15 | v -0.378281 0.117514 -0.223667 16 | v -0.378281 -0.117514 -0.223667 17 | v -0.378281 0.166190 -0.341182 18 | v -0.378281 -0.166190 -0.341182 19 | v -0.378281 0.000000 -0.174991 20 | v -0.472915 0.000000 -0.341182 21 | v -0.378281 0.000000 -0.507372 22 | v -0.408817 0.110808 -0.451989 23 | v -0.444048 -0.049389 -0.451363 24 | v -0.444048 0.110181 -0.291793 25 | v -0.444048 -0.049389 -0.231000 26 | v -0.444048 -0.110181 -0.390570 27 | v -0.408817 -0.110808 -0.230374 28 | v -0.444048 0.049389 -0.451363 29 | v -0.408817 -0.110808 -0.451989 30 | v -0.444048 0.110181 -0.390570 31 | v -0.444048 0.049389 -0.231000 32 | v -0.444048 -0.110181 -0.291793 33 | v -0.408817 0.110808 -0.230374 34 | v -0.417629 0.149948 -0.341182 35 | v -0.378281 0.153540 -0.277583 36 | v -0.378281 0.153540 -0.404780 37 | v -0.417629 -0.149948 -0.341182 38 | v -0.378281 -0.153540 -0.277583 39 | v -0.378281 -0.153540 -0.404780 40 | v -0.378281 -0.063598 -0.187642 41 | v -0.378281 0.063598 -0.187642 42 | v -0.417629 0.000000 -0.191233 43 | v -0.468360 -0.060952 -0.341182 44 | v -0.468360 0.060952 -0.341182 45 | v -0.468360 0.000000 -0.402133 46 | v -0.468360 0.000000 -0.280230 47 | v -0.378281 -0.063598 -0.494721 48 | v -0.378281 0.063598 -0.494721 49 | v -0.417629 0.000000 -0.491130 50 | v -0.418059 0.058863 -0.480263 51 | v -0.463896 0.057046 -0.283553 52 | v -0.463896 0.057046 -0.398810 53 | v -0.463896 -0.057046 -0.398810 54 | v -0.418059 0.058863 -0.202100 55 | v -0.418059 -0.058863 -0.202100 56 | v -0.416546 -0.138985 -0.281521 57 | v -0.416546 0.138985 -0.400842 58 | v -0.416546 0.138985 -0.281521 59 | v -0.416546 -0.138985 -0.400842 60 | v -0.463896 -0.057046 -0.283553 61 | v -0.418059 -0.058863 -0.480263 62 | v 0.432918 -0.095950 -0.245231 63 | v 0.432918 0.095950 -0.245231 64 | v 0.432918 -0.095950 -0.437132 65 | v 0.432918 0.095950 -0.437132 66 | v 0.378281 0.117514 -0.458696 67 | v 0.445198 0.000000 -0.458696 68 | v 0.378281 -0.117514 -0.458696 69 | v 0.445198 0.117514 -0.341182 70 | v 0.445198 0.000000 -0.223667 71 | v 0.445198 -0.117514 -0.341182 72 | v 0.378281 0.117514 -0.223667 73 | v 0.378281 -0.117514 -0.223667 74 | v 0.378281 0.166190 -0.341182 75 | v 0.378281 -0.166190 -0.341182 76 | v 0.378281 0.000000 -0.174991 77 | v 0.472915 0.000000 -0.341182 78 | v 0.378281 0.000000 -0.507372 79 | v 0.408817 0.110808 -0.451989 80 | v 0.444048 -0.049389 -0.451363 81 | v 0.444048 0.110181 -0.291793 82 | v 0.444048 -0.049389 -0.231000 83 | v 0.444048 -0.110181 -0.390570 84 | v 0.408817 -0.110808 -0.230374 85 | v 0.444048 0.049389 -0.451363 86 | v 0.408817 -0.110808 -0.451989 87 | v 0.444048 0.110181 -0.390570 88 | v 0.444048 0.049389 -0.231000 89 | v 0.444048 -0.110181 -0.291793 90 | v 0.408817 0.110808 -0.230374 91 | v 0.417629 0.149948 -0.341182 92 | v 0.378281 0.153540 -0.277583 93 | v 0.378281 0.153540 -0.404780 94 | v 0.417629 -0.149948 -0.341182 95 | v 0.378281 -0.153540 -0.277583 96 | v 0.378281 -0.153540 -0.404780 97 | v 0.378281 -0.063598 -0.187642 98 | v 0.378281 0.063598 -0.187642 99 | v 0.417629 0.000000 -0.191233 100 | v 0.468360 -0.060952 -0.341182 101 | v 0.468360 0.060952 -0.341182 102 | v 0.468360 0.000000 -0.402133 103 | v 0.468360 0.000000 -0.280230 104 | v 0.378281 -0.063598 -0.494721 105 | v 0.378281 0.063598 -0.494721 106 | v 0.417629 0.000000 -0.491130 107 | v 0.418059 0.058863 -0.480263 108 | v 0.463896 0.057046 -0.283553 109 | v 0.463896 0.057046 -0.398810 110 | v 0.463896 -0.057046 -0.398810 111 | v 0.418059 0.058863 -0.202100 112 | v 0.418059 -0.058863 -0.202100 113 | v 0.416546 -0.138985 -0.281521 114 | v 0.416546 0.138985 -0.400842 115 | v 0.416546 0.138985 -0.281521 116 | v 0.416546 -0.138985 -0.400842 117 | v 0.463896 -0.057046 -0.283553 118 | v 0.418059 -0.058863 -0.480263 119 | v -0.361902 -0.030520 -0.621708 120 | v -0.328987 0.053826 -0.573702 121 | v -0.356207 0.070101 -0.594097 122 | v 0.345226 -0.250474 0.103491 123 | v 0.345226 0.250474 0.103491 124 | v 0.342958 0.253431 -0.083064 125 | v -0.331489 -0.034626 -0.598385 126 | v -0.195368 0.122583 -0.766118 127 | v -0.167985 -0.032685 -0.745037 128 | v -0.182222 -0.026597 -0.790694 129 | v 0.000000 -0.263700 -0.171040 130 | v 0.366931 0.278336 0.000000 131 | v 0.389080 -0.000000 0.139468 132 | v 0.366931 -0.278336 -0.000000 133 | v 0.000000 0.255614 0.167144 134 | v 0.000000 -0.255614 0.167144 135 | v 0.000000 0.000000 0.259818 136 | v 0.456746 -0.000000 0.000000 137 | v -0.000000 0.000000 -0.180381 138 | v -0.182298 0.098455 -0.725585 139 | v -0.302647 -0.030180 -0.722889 140 | v -0.304736 0.095950 -0.703150 141 | v -0.279924 0.075550 -0.680466 142 | v -0.275092 -0.035328 -0.697199 143 | v 0.152418 0.282189 -0.186901 144 | v -0.371445 0.044167 -0.470783 145 | v 0.361775 0.271684 0.065971 146 | v 0.377117 -0.131193 0.130242 147 | v 0.342958 -0.253431 -0.083064 148 | v -0.343899 0.031471 -0.466802 149 | v 0.180781 -0.251337 0.150842 150 | v -0.382251 -0.034331 -0.485298 151 | v -0.351359 -0.037534 -0.480536 152 | v 0.152418 -0.259423 -0.209370 153 | v 0.377117 0.131193 0.130242 154 | v 0.361775 -0.271684 0.065971 155 | v 0.180781 0.251337 0.150842 156 | v -0.079013 0.148698 -0.780999 157 | v -0.077686 0.120994 -0.723957 158 | v 0.198704 0.329613 0.000000 159 | v -0.000000 0.345367 -0.086362 160 | v 0.198704 -0.329613 0.000000 161 | v -0.160695 0.389688 -0.224471 162 | v -0.000000 -0.317736 -0.086362 163 | v 0.000000 -0.137817 0.234631 164 | v 0.208660 0.000000 0.230367 165 | v -0.152418 0.324445 -0.285887 166 | v 0.433230 -0.150542 0.000000 167 | v 0.433230 0.150542 0.000000 168 | v 0.413209 -0.000000 -0.098745 169 | v 0.437339 0.000000 0.087566 170 | v 0.000000 -0.137817 -0.168077 171 | v 0.000000 0.137817 -0.168077 172 | v 0.175924 0.000000 -0.223199 173 | v 0.000000 0.324568 -0.279800 174 | v -0.126202 0.326610 -0.550199 175 | v -0.128358 0.391159 -0.571334 176 | v 0.171309 0.134604 -0.212084 177 | v -0.054811 -0.022591 -0.807329 178 | v 0.417401 0.145307 0.083380 179 | v 0.397259 0.138250 -0.095155 180 | v 0.397259 -0.138250 -0.095155 181 | v -0.053891 -0.029582 -0.743266 182 | v 0.203186 0.134604 0.207614 183 | v 0.203186 -0.134604 0.207614 184 | v -0.102650 0.295273 -0.713857 185 | v 0.193458 -0.305944 0.096446 186 | v 0.165448 0.333575 -0.081571 187 | v 0.193458 0.305944 0.096446 188 | v 0.000000 0.291003 -0.717042 189 | v -0.100925 0.247340 -0.663672 190 | v 0.193458 -0.305944 -0.081571 191 | v 0.417401 -0.145307 0.083380 192 | v 0.171309 -0.134604 -0.212084 193 | v -0.155022 0.433681 -0.395049 194 | v 0.180781 0.251337 -0.246101 195 | v 0.324142 0.235177 -0.222968 196 | v 0.377117 -0.131193 -0.236037 197 | v 0.324142 -0.235177 -0.222968 198 | v -0.152418 0.351898 -0.404044 199 | v -0.203186 -0.134604 -0.273836 200 | v 0.377117 0.131193 -0.236037 201 | v 0.389080 0.000000 -0.240544 202 | v -0.203186 0.134604 -0.273836 203 | v -0.208660 0.000000 -0.284951 204 | v -0.180781 -0.251337 -0.246101 205 | v 0.180781 -0.251337 -0.246101 206 | v 0.208660 0.000000 -0.284951 207 | v 0.203186 0.134604 -0.273836 208 | v -0.389080 0.000000 -0.240544 209 | v -0.377117 0.131193 -0.236037 210 | v 0.203186 -0.134604 -0.273836 211 | v -0.324142 -0.235177 -0.222968 212 | v -0.000000 0.350699 -0.400515 213 | v 0.152418 0.351898 -0.404044 214 | v -0.000000 0.432702 -0.400631 215 | v 0.155022 0.433681 -0.395049 216 | v -0.377117 -0.131193 -0.236037 217 | v -0.324142 0.235177 -0.222968 218 | v -0.000000 0.249109 -0.660404 219 | v 0.100925 0.247340 -0.663672 220 | v 0.102650 0.295273 -0.713857 221 | v -0.180781 0.251337 -0.246101 222 | v -0.171309 -0.134604 -0.212084 223 | v -0.000000 -0.028842 -0.740853 224 | v 0.053891 -0.029582 -0.743266 225 | v -0.000000 -0.023935 -0.808766 226 | v 0.054811 -0.022591 -0.807329 227 | v -0.417401 -0.145307 0.083380 228 | v -0.193458 -0.305944 -0.081571 229 | v -0.000000 0.326922 -0.546803 230 | v -0.000000 0.388504 -0.575696 231 | v -0.193458 0.305944 0.096446 232 | v 0.128358 0.391159 -0.571334 233 | v 0.126202 0.326610 -0.550199 234 | v -0.165448 0.333575 -0.081571 235 | v -0.193458 -0.305944 0.096446 236 | v -0.000000 0.395658 -0.229623 237 | v 0.152418 0.324445 -0.285887 238 | v 0.160695 0.389688 -0.224471 239 | v -0.203186 -0.134604 0.207614 240 | v -0.203186 0.134604 0.207614 241 | v 0.077686 0.120994 -0.723957 242 | v 0.079013 0.148698 -0.780999 243 | v -0.000000 0.122255 -0.721111 244 | v -0.000000 0.145874 -0.783321 245 | v 0.351359 -0.037534 -0.480536 246 | v 0.382251 -0.034331 -0.485298 247 | v 0.343899 0.031471 -0.466802 248 | v 0.371445 0.044167 -0.470783 249 | v 0.275092 -0.035328 -0.697199 250 | v 0.279924 0.075550 -0.680466 251 | v 0.304736 0.095950 -0.703150 252 | v 0.302647 -0.030180 -0.722889 253 | v 0.182298 0.098455 -0.725585 254 | v 0.182222 -0.026597 -0.790694 255 | v 0.167985 -0.032685 -0.745037 256 | v 0.195368 0.122583 -0.766118 257 | v 0.331489 -0.034626 -0.598385 258 | v 0.356207 0.070101 -0.594097 259 | v 0.328987 0.053826 -0.573702 260 | v 0.361902 -0.030520 -0.621708 261 | v -0.397259 -0.138250 -0.095155 262 | v -0.397259 0.138250 -0.095155 263 | v -0.417401 0.145307 0.083380 264 | v -0.171309 0.134604 -0.212084 265 | v -0.175924 0.000000 -0.223199 266 | v -0.437339 0.000000 0.087566 267 | v -0.413209 -0.000000 -0.098745 268 | v -0.433230 0.150542 0.000000 269 | v -0.433230 -0.150542 0.000000 270 | v -0.208660 0.000000 0.230367 271 | v 0.000000 0.137817 0.234631 272 | v 0.000000 -0.317736 0.103952 273 | v -0.198704 -0.329613 0.000000 274 | v 0.000000 0.317736 0.103952 275 | v -0.198704 0.329613 0.000000 276 | v -0.180781 0.251337 0.150842 277 | v -0.361775 -0.271684 0.065971 278 | v -0.377117 0.131193 0.130242 279 | v -0.152418 -0.259423 -0.209370 280 | v -0.180781 -0.251337 0.150842 281 | v -0.342958 -0.253431 -0.083064 282 | v -0.377117 -0.131193 0.130242 283 | v -0.361775 0.271684 0.065971 284 | v -0.152418 0.282189 -0.186901 285 | v -0.456746 -0.000000 0.000000 286 | v 0.000000 -0.345694 0.000000 287 | v 0.000000 0.345694 0.000000 288 | v -0.366931 -0.278336 -0.000000 289 | v -0.389080 -0.000000 0.139468 290 | v -0.366931 0.278336 0.000000 291 | v 0.000000 0.283419 -0.178673 292 | v -0.342958 0.253431 -0.083064 293 | v -0.345226 0.250474 0.103491 294 | v -0.345226 -0.250474 0.103491 295 | vt 0.158999 0.224286 296 | vt 0.158999 0.255714 297 | vt 0.146972 0.284749 298 | vt 0.124749 0.306972 299 | vt 0.095714 0.318999 300 | vt 0.064286 0.318999 301 | vt 0.035251 0.306972 302 | vt 0.013028 0.284749 303 | vt 0.001001 0.255714 304 | vt 0.001001 0.224286 305 | vt 0.013028 0.195251 306 | vt 0.035251 0.173028 307 | vt 0.064286 0.161001 308 | vt 0.095714 0.161001 309 | vt 0.124749 0.173028 310 | vt 0.146972 0.195251 311 | vt 0.998999 0.561001 312 | vt 0.961001 0.561001 313 | vt 0.961001 0.598999 314 | vt 0.998999 0.598999 315 | vt 0.598999 0.961001 316 | vt 0.561001 0.961001 317 | vt 0.561001 0.998999 318 | vt 0.598999 0.998999 319 | vt 0.478999 0.441001 320 | vt 0.441001 0.441001 321 | vt 0.441001 0.478999 322 | vt 0.478999 0.478999 323 | vt 0.438999 0.441001 324 | vt 0.401001 0.441001 325 | vt 0.401001 0.478999 326 | vt 0.438999 0.478999 327 | vt 0.478999 0.401001 328 | vt 0.441001 0.401001 329 | vt 0.441001 0.438999 330 | vt 0.478999 0.438999 331 | vt 0.438999 0.401001 332 | vt 0.401001 0.401001 333 | vt 0.401001 0.438999 334 | vt 0.438999 0.438999 335 | vt 0.238999 0.281001 336 | vt 0.201001 0.281001 337 | vt 0.201001 0.318999 338 | vt 0.238999 0.318999 339 | vt 0.198999 0.281001 340 | vt 0.161001 0.281001 341 | vt 0.161001 0.318999 342 | vt 0.198999 0.318999 343 | vt 0.238999 0.241001 344 | vt 0.201001 0.241001 345 | vt 0.201001 0.278999 346 | vt 0.238999 0.278999 347 | vt 0.198999 0.241001 348 | vt 0.161001 0.241001 349 | vt 0.161001 0.278999 350 | vt 0.198999 0.278999 351 | vt 0.798999 0.681001 352 | vt 0.761001 0.681001 353 | vt 0.761001 0.718999 354 | vt 0.798999 0.718999 355 | vt 0.758999 0.681001 356 | vt 0.721001 0.681001 357 | vt 0.721001 0.718999 358 | vt 0.758999 0.718999 359 | vt 0.798999 0.641001 360 | vt 0.761001 0.641001 361 | vt 0.761001 0.678999 362 | vt 0.798999 0.678999 363 | vt 0.758999 0.641001 364 | vt 0.721001 0.641001 365 | vt 0.721001 0.678999 366 | vt 0.758999 0.678999 367 | vt 0.638999 0.761001 368 | vt 0.601001 0.761001 369 | vt 0.601001 0.798999 370 | vt 0.638999 0.798999 371 | vt 0.598999 0.761001 372 | vt 0.561001 0.761001 373 | vt 0.561001 0.798999 374 | vt 0.598999 0.798999 375 | vt 0.638999 0.721001 376 | vt 0.601001 0.721001 377 | vt 0.601001 0.758999 378 | vt 0.638999 0.758999 379 | vt 0.598999 0.721001 380 | vt 0.561001 0.721001 381 | vt 0.561001 0.758999 382 | vt 0.598999 0.758999 383 | vt 0.798999 0.601001 384 | vt 0.761001 0.601001 385 | vt 0.761001 0.638999 386 | vt 0.798999 0.638999 387 | vt 0.758999 0.601001 388 | vt 0.721001 0.601001 389 | vt 0.721001 0.638999 390 | vt 0.758999 0.638999 391 | vt 0.798999 0.561001 392 | vt 0.761001 0.561001 393 | vt 0.761001 0.598999 394 | vt 0.798999 0.598999 395 | vt 0.758999 0.561001 396 | vt 0.721001 0.561001 397 | vt 0.721001 0.598999 398 | vt 0.758999 0.598999 399 | vt 0.798999 0.521001 400 | vt 0.761001 0.521001 401 | vt 0.761001 0.558999 402 | vt 0.798999 0.558999 403 | vt 0.758999 0.521001 404 | vt 0.721001 0.521001 405 | vt 0.721001 0.558999 406 | vt 0.758999 0.558999 407 | vt 0.798999 0.481001 408 | vt 0.761001 0.481001 409 | vt 0.761001 0.518999 410 | vt 0.798999 0.518999 411 | vt 0.758999 0.481001 412 | vt 0.721001 0.481001 413 | vt 0.721001 0.518999 414 | vt 0.758999 0.518999 415 | vt 0.558999 0.761001 416 | vt 0.521001 0.761001 417 | vt 0.521001 0.798999 418 | vt 0.558999 0.798999 419 | vt 0.518999 0.761001 420 | vt 0.481001 0.761001 421 | vt 0.481001 0.798999 422 | vt 0.518999 0.798999 423 | vt 0.558999 0.721001 424 | vt 0.521001 0.721001 425 | vt 0.521001 0.758999 426 | vt 0.558999 0.758999 427 | vt 0.518999 0.721001 428 | vt 0.481001 0.721001 429 | vt 0.481001 0.758999 430 | vt 0.518999 0.758999 431 | vt 0.478999 0.761001 432 | vt 0.441001 0.761001 433 | vt 0.441001 0.798999 434 | vt 0.478999 0.798999 435 | vt 0.438999 0.761001 436 | vt 0.401001 0.761001 437 | vt 0.401001 0.798999 438 | vt 0.438999 0.798999 439 | vt 0.478999 0.721001 440 | vt 0.441001 0.721001 441 | vt 0.441001 0.758999 442 | vt 0.478999 0.758999 443 | vt 0.438999 0.721001 444 | vt 0.401001 0.721001 445 | vt 0.401001 0.758999 446 | vt 0.438999 0.758999 447 | vt 0.798999 0.441001 448 | vt 0.761001 0.441001 449 | vt 0.761001 0.478999 450 | vt 0.798999 0.478999 451 | vt 0.758999 0.441001 452 | vt 0.721001 0.441001 453 | vt 0.721001 0.478999 454 | vt 0.758999 0.478999 455 | vt 0.798999 0.401001 456 | vt 0.761001 0.401001 457 | vt 0.761001 0.438999 458 | vt 0.798999 0.438999 459 | vt 0.758999 0.401001 460 | vt 0.721001 0.401001 461 | vt 0.721001 0.438999 462 | vt 0.758999 0.438999 463 | vt 0.798999 0.361001 464 | vt 0.761001 0.361001 465 | vt 0.761001 0.398999 466 | vt 0.798999 0.398999 467 | vt 0.758999 0.361001 468 | vt 0.721001 0.361001 469 | vt 0.721001 0.398999 470 | vt 0.758999 0.398999 471 | vt 0.798999 0.321001 472 | vt 0.761001 0.321001 473 | vt 0.761001 0.358999 474 | vt 0.798999 0.358999 475 | vt 0.758999 0.321001 476 | vt 0.721001 0.321001 477 | vt 0.721001 0.358999 478 | vt 0.758999 0.358999 479 | vt 0.398999 0.761001 480 | vt 0.361001 0.761001 481 | vt 0.361001 0.798999 482 | vt 0.398999 0.798999 483 | vt 0.358999 0.761001 484 | vt 0.321001 0.761001 485 | vt 0.321001 0.798999 486 | vt 0.358999 0.798999 487 | vt 0.398999 0.721001 488 | vt 0.361001 0.721001 489 | vt 0.361001 0.758999 490 | vt 0.398999 0.758999 491 | vt 0.358999 0.721001 492 | vt 0.321001 0.721001 493 | vt 0.321001 0.758999 494 | vt 0.358999 0.758999 495 | vt 0.318999 0.761001 496 | vt 0.281001 0.761001 497 | vt 0.281001 0.798999 498 | vt 0.318999 0.798999 499 | vt 0.278999 0.761001 500 | vt 0.241001 0.761001 501 | vt 0.241001 0.798999 502 | vt 0.278999 0.798999 503 | vt 0.064286 0.158999 504 | vt 0.035251 0.146972 505 | vt 0.013028 0.124749 506 | vt 0.001001 0.095714 507 | vt 0.001001 0.064286 508 | vt 0.013028 0.035251 509 | vt 0.035251 0.013028 510 | vt 0.064286 0.001001 511 | vt 0.095714 0.001001 512 | vt 0.124749 0.013028 513 | vt 0.146972 0.035251 514 | vt 0.158999 0.064286 515 | vt 0.158999 0.095714 516 | vt 0.146972 0.124749 517 | vt 0.124749 0.146972 518 | vt 0.095714 0.158999 519 | vt 0.318999 0.721001 520 | vt 0.281001 0.721001 521 | vt 0.281001 0.758999 522 | vt 0.318999 0.758999 523 | vt 0.278999 0.721001 524 | vt 0.241001 0.721001 525 | vt 0.241001 0.758999 526 | vt 0.278999 0.758999 527 | vt 0.798999 0.281001 528 | vt 0.761001 0.281001 529 | vt 0.761001 0.318999 530 | vt 0.798999 0.318999 531 | vt 0.758999 0.281001 532 | vt 0.721001 0.281001 533 | vt 0.721001 0.318999 534 | vt 0.758999 0.318999 535 | vt 0.798999 0.241001 536 | vt 0.761001 0.241001 537 | vt 0.761001 0.278999 538 | vt 0.798999 0.278999 539 | vt 0.758999 0.241001 540 | vt 0.721001 0.241001 541 | vt 0.721001 0.278999 542 | vt 0.758999 0.278999 543 | vt 0.238999 0.761001 544 | vt 0.201001 0.761001 545 | vt 0.201001 0.798999 546 | vt 0.238999 0.798999 547 | vt 0.198999 0.761001 548 | vt 0.161001 0.761001 549 | vt 0.161001 0.798999 550 | vt 0.198999 0.798999 551 | vt 0.238999 0.721001 552 | vt 0.201001 0.721001 553 | vt 0.201001 0.758999 554 | vt 0.238999 0.758999 555 | vt 0.198999 0.721001 556 | vt 0.161001 0.721001 557 | vt 0.161001 0.758999 558 | vt 0.198999 0.758999 559 | vt 0.798999 0.201001 560 | vt 0.761001 0.201001 561 | vt 0.761001 0.238999 562 | vt 0.798999 0.238999 563 | vt 0.758999 0.201001 564 | vt 0.721001 0.201001 565 | vt 0.721001 0.238999 566 | vt 0.758999 0.238999 567 | vt 0.798999 0.161001 568 | vt 0.761001 0.161001 569 | vt 0.761001 0.198999 570 | vt 0.798999 0.198999 571 | vt 0.758999 0.161001 572 | vt 0.721001 0.161001 573 | vt 0.721001 0.198999 574 | vt 0.758999 0.198999 575 | vt 0.158999 0.761001 576 | vt 0.121001 0.761001 577 | vt 0.121001 0.798999 578 | vt 0.158999 0.798999 579 | vt 0.118999 0.761001 580 | vt 0.081001 0.761001 581 | vt 0.081001 0.798999 582 | vt 0.118999 0.798999 583 | vt 0.158999 0.721001 584 | vt 0.121001 0.721001 585 | vt 0.121001 0.758999 586 | vt 0.158999 0.758999 587 | vt 0.118999 0.721001 588 | vt 0.081001 0.721001 589 | vt 0.081001 0.758999 590 | vt 0.118999 0.758999 591 | vt 0.798999 0.121001 592 | vt 0.761001 0.121001 593 | vt 0.761001 0.158999 594 | vt 0.798999 0.158999 595 | vt 0.758999 0.121001 596 | vt 0.721001 0.121001 597 | vt 0.721001 0.158999 598 | vt 0.758999 0.158999 599 | vt 0.798999 0.081001 600 | vt 0.761001 0.081001 601 | vt 0.761001 0.118999 602 | vt 0.798999 0.118999 603 | vt 0.758999 0.081001 604 | vt 0.721001 0.081001 605 | vt 0.721001 0.118999 606 | vt 0.758999 0.118999 607 | vt 0.078999 0.761001 608 | vt 0.041001 0.761001 609 | vt 0.041001 0.798999 610 | vt 0.078999 0.798999 611 | vt 0.038999 0.761001 612 | vt 0.001001 0.761001 613 | vt 0.001001 0.798999 614 | vt 0.038999 0.798999 615 | vt 0.078999 0.721001 616 | vt 0.041001 0.721001 617 | vt 0.041001 0.758999 618 | vt 0.078999 0.758999 619 | vt 0.038999 0.721001 620 | vt 0.001001 0.721001 621 | vt 0.001001 0.758999 622 | vt 0.038999 0.758999 623 | vt 0.798999 0.041001 624 | vt 0.761001 0.041001 625 | vt 0.761001 0.078999 626 | vt 0.798999 0.078999 627 | vt 0.758999 0.041001 628 | vt 0.721001 0.041001 629 | vt 0.721001 0.078999 630 | vt 0.758999 0.078999 631 | vt 0.798999 0.001001 632 | vt 0.761001 0.001001 633 | vt 0.761001 0.038999 634 | vt 0.798999 0.038999 635 | vt 0.758999 0.001001 636 | vt 0.721001 0.001001 637 | vt 0.721001 0.038999 638 | vt 0.758999 0.038999 639 | vt 0.718999 0.681001 640 | vt 0.681001 0.681001 641 | vt 0.681001 0.718999 642 | vt 0.718999 0.718999 643 | vt 0.678999 0.681001 644 | vt 0.641001 0.681001 645 | vt 0.641001 0.718999 646 | vt 0.678999 0.718999 647 | vt 0.718999 0.641001 648 | vt 0.681001 0.641001 649 | vt 0.681001 0.678999 650 | vt 0.718999 0.678999 651 | vt 0.678999 0.641001 652 | vt 0.641001 0.641001 653 | vt 0.641001 0.678999 654 | vt 0.678999 0.678999 655 | vt 0.638999 0.681001 656 | vt 0.601001 0.681001 657 | vt 0.601001 0.718999 658 | vt 0.638999 0.718999 659 | vt 0.598999 0.681001 660 | vt 0.561001 0.681001 661 | vt 0.561001 0.718999 662 | vt 0.598999 0.718999 663 | vt 0.638999 0.641001 664 | vt 0.601001 0.641001 665 | vt 0.601001 0.678999 666 | vt 0.638999 0.678999 667 | vt 0.598999 0.641001 668 | vt 0.561001 0.641001 669 | vt 0.561001 0.678999 670 | vt 0.598999 0.678999 671 | vt 0.718999 0.601001 672 | vt 0.681001 0.601001 673 | vt 0.681001 0.638999 674 | vt 0.718999 0.638999 675 | vt 0.678999 0.601001 676 | vt 0.641001 0.601001 677 | vt 0.641001 0.638999 678 | vt 0.678999 0.638999 679 | vt 0.718999 0.561001 680 | vt 0.681001 0.561001 681 | vt 0.681001 0.598999 682 | vt 0.718999 0.598999 683 | vt 0.678999 0.561001 684 | vt 0.641001 0.561001 685 | vt 0.641001 0.598999 686 | vt 0.678999 0.598999 687 | vt 0.718999 0.521001 688 | vt 0.681001 0.521001 689 | vt 0.681001 0.558999 690 | vt 0.718999 0.558999 691 | vt 0.678999 0.521001 692 | vt 0.641001 0.521001 693 | vt 0.641001 0.558999 694 | vt 0.678999 0.558999 695 | vt 0.718999 0.481001 696 | vt 0.681001 0.481001 697 | vt 0.681001 0.518999 698 | vt 0.718999 0.518999 699 | vt 0.678999 0.481001 700 | vt 0.641001 0.481001 701 | vt 0.641001 0.518999 702 | vt 0.678999 0.518999 703 | vt 0.558999 0.681001 704 | vt 0.521001 0.681001 705 | vt 0.521001 0.718999 706 | vt 0.558999 0.718999 707 | vt 0.518999 0.681001 708 | vt 0.481001 0.681001 709 | vt 0.481001 0.718999 710 | vt 0.518999 0.718999 711 | vt 0.838999 0.398999 712 | vt 0.838999 0.321001 713 | vt 0.801001 0.321001 714 | vt 0.801001 0.398999 715 | vt 0.478999 0.838999 716 | vt 0.478999 0.801001 717 | vt 0.401001 0.801001 718 | vt 0.401001 0.838999 719 | vt 0.838999 0.478999 720 | vt 0.838999 0.401001 721 | vt 0.801001 0.401001 722 | vt 0.801001 0.478999 723 | vt 0.478999 0.718999 724 | vt 0.402002 0.718999 725 | vt 0.478999 0.642002 726 | vt 0.398999 0.401001 727 | vt 0.321001 0.401001 728 | vt 0.321001 0.478999 729 | vt 0.398999 0.478999 730 | vt 0.481001 0.838999 731 | vt 0.558999 0.838999 732 | vt 0.558999 0.801001 733 | vt 0.481001 0.801001 734 | vt 0.838999 0.481001 735 | vt 0.801001 0.481001 736 | vt 0.801001 0.558999 737 | vt 0.838999 0.558999 738 | vt 0.561001 0.838999 739 | vt 0.638999 0.838999 740 | vt 0.638999 0.801001 741 | vt 0.561001 0.801001 742 | vt 0.838999 0.561001 743 | vt 0.801001 0.561001 744 | vt 0.801001 0.638999 745 | vt 0.838999 0.638999 746 | vt 0.641001 0.838999 747 | vt 0.718999 0.838999 748 | vt 0.718999 0.801001 749 | vt 0.641001 0.801001 750 | vt 0.838999 0.718999 751 | vt 0.838999 0.641001 752 | vt 0.801001 0.641001 753 | vt 0.801001 0.718999 754 | vt 0.158999 0.321001 755 | vt 0.081001 0.321001 756 | vt 0.081001 0.398999 757 | vt 0.158999 0.398999 758 | vt 0.398999 0.081001 759 | vt 0.321001 0.081001 760 | vt 0.321001 0.158999 761 | vt 0.398999 0.158999 762 | vt 0.318999 0.321001 763 | vt 0.241001 0.321001 764 | vt 0.241001 0.398999 765 | vt 0.318999 0.398999 766 | vt 0.798999 0.838999 767 | vt 0.798999 0.801001 768 | vt 0.721001 0.801001 769 | vt 0.721001 0.838999 770 | vt 0.838999 0.721001 771 | vt 0.801001 0.721001 772 | vt 0.801001 0.798999 773 | vt 0.838999 0.798999 774 | vt 0.078999 0.878999 775 | vt 0.078999 0.841001 776 | vt 0.001001 0.841001 777 | vt 0.001001 0.878999 778 | vt 0.078999 0.481001 779 | vt 0.001001 0.481001 780 | vt 0.001001 0.558999 781 | vt 0.078999 0.558999 782 | vt 0.558999 0.081001 783 | vt 0.481001 0.081001 784 | vt 0.481001 0.158999 785 | vt 0.558999 0.158999 786 | vt 0.318999 0.641001 787 | vt 0.241001 0.641001 788 | vt 0.241001 0.718999 789 | vt 0.318999 0.718999 790 | vt 0.878999 0.078999 791 | vt 0.878999 0.001001 792 | vt 0.841001 0.001001 793 | vt 0.841001 0.078999 794 | vt 0.081001 0.878999 795 | vt 0.158999 0.878999 796 | vt 0.158999 0.841001 797 | vt 0.081001 0.841001 798 | vt 0.878999 0.158999 799 | vt 0.878999 0.081001 800 | vt 0.841001 0.081001 801 | vt 0.841001 0.158999 802 | vt 0.161001 0.878999 803 | vt 0.238999 0.878999 804 | vt 0.238999 0.841001 805 | vt 0.161001 0.841001 806 | vt 0.878999 0.238999 807 | vt 0.878999 0.161001 808 | vt 0.841001 0.161001 809 | vt 0.841001 0.238999 810 | vt 0.241001 0.878999 811 | vt 0.318999 0.878999 812 | vt 0.318999 0.841001 813 | vt 0.241001 0.841001 814 | vt 0.878999 0.318999 815 | vt 0.878999 0.241001 816 | vt 0.841001 0.241001 817 | vt 0.841001 0.318999 818 | vt 0.321001 0.878999 819 | vt 0.398999 0.878999 820 | vt 0.398999 0.841001 821 | vt 0.321001 0.841001 822 | vt 0.878999 0.321001 823 | vt 0.841001 0.321001 824 | vt 0.841001 0.398999 825 | vt 0.878999 0.398999 826 | vt 0.401001 0.878999 827 | vt 0.478999 0.878999 828 | vt 0.478999 0.841001 829 | vt 0.401001 0.841001 830 | vt 0.718999 0.721001 831 | vt 0.641001 0.721001 832 | vt 0.641001 0.798999 833 | vt 0.718999 0.798999 834 | vt 0.238999 0.081001 835 | vt 0.161001 0.081001 836 | vt 0.161001 0.158999 837 | vt 0.238999 0.158999 838 | vt 0.238999 0.161001 839 | vt 0.161001 0.161001 840 | vt 0.161001 0.238999 841 | vt 0.238999 0.238999 842 | vt 0.318999 0.001001 843 | vt 0.241001 0.001001 844 | vt 0.241001 0.078999 845 | vt 0.318999 0.078999 846 | vt 0.318999 0.081001 847 | vt 0.241001 0.081001 848 | vt 0.241001 0.158999 849 | vt 0.318999 0.158999 850 | vt 0.318999 0.161001 851 | vt 0.241001 0.161001 852 | vt 0.241001 0.238999 853 | vt 0.318999 0.238999 854 | vt 0.878999 0.478999 855 | vt 0.878999 0.401001 856 | vt 0.841001 0.401001 857 | vt 0.841001 0.478999 858 | vt 0.398999 0.001001 859 | vt 0.321001 0.001001 860 | vt 0.321001 0.078999 861 | vt 0.398999 0.078999 862 | vt 0.481001 0.878999 863 | vt 0.558999 0.878999 864 | vt 0.558999 0.841001 865 | vt 0.481001 0.841001 866 | vt 0.878999 0.481001 867 | vt 0.841001 0.481001 868 | vt 0.841001 0.558999 869 | vt 0.878999 0.558999 870 | vt 0.638999 0.878999 871 | vt 0.638999 0.841001 872 | vt 0.561001 0.841001 873 | vt 0.561001 0.878999 874 | vt 0.878999 0.561001 875 | vt 0.841001 0.561001 876 | vt 0.841001 0.638999 877 | vt 0.878999 0.638999 878 | vt 0.718999 0.878999 879 | vt 0.718999 0.841001 880 | vt 0.641001 0.841001 881 | vt 0.641001 0.878999 882 | vt 0.878999 0.641001 883 | vt 0.841001 0.641001 884 | vt 0.841001 0.718999 885 | vt 0.878999 0.718999 886 | vt 0.721001 0.878999 887 | vt 0.798999 0.878999 888 | vt 0.798999 0.841001 889 | vt 0.721001 0.841001 890 | vt 0.878999 0.798999 891 | vt 0.878999 0.721001 892 | vt 0.841001 0.721001 893 | vt 0.841001 0.798999 894 | vt 0.558999 0.641001 895 | vt 0.521001 0.641001 896 | vt 0.521001 0.678999 897 | vt 0.558999 0.678999 898 | vt 0.558999 0.241001 899 | vt 0.481001 0.241001 900 | vt 0.481001 0.318999 901 | vt 0.558999 0.318999 902 | vt 0.318999 0.481001 903 | vt 0.241001 0.481001 904 | vt 0.241001 0.558999 905 | vt 0.318999 0.558999 906 | vt 0.398999 0.481001 907 | vt 0.321001 0.481001 908 | vt 0.321001 0.558999 909 | vt 0.398999 0.558999 910 | vt 0.478999 0.561001 911 | vt 0.401001 0.561001 912 | vt 0.401001 0.638999 913 | vt 0.478999 0.638999 914 | vt 0.638999 0.481001 915 | vt 0.561001 0.481001 916 | vt 0.561001 0.558999 917 | vt 0.638999 0.558999 918 | vt 0.718999 0.001001 919 | vt 0.641001 0.001001 920 | vt 0.641001 0.078999 921 | vt 0.718999 0.078999 922 | vt 0.801001 0.838999 923 | vt 0.878999 0.838999 924 | vt 0.878999 0.801001 925 | vt 0.801001 0.801001 926 | vt 0.718999 0.241001 927 | vt 0.641001 0.241001 928 | vt 0.641001 0.318999 929 | vt 0.718999 0.318999 930 | vt 0.718999 0.321001 931 | vt 0.641001 0.321001 932 | vt 0.641001 0.398999 933 | vt 0.718999 0.398999 934 | vt 0.918999 0.078999 935 | vt 0.918999 0.001001 936 | vt 0.881001 0.001001 937 | vt 0.881001 0.078999 938 | vt 0.798999 0.721001 939 | vt 0.721001 0.721001 940 | vt 0.721001 0.798999 941 | vt 0.798999 0.798999 942 | vt 0.801001 0.878999 943 | vt 0.878999 0.878999 944 | vt 0.878999 0.841001 945 | vt 0.801001 0.841001 946 | vt 0.918999 0.081001 947 | vt 0.881001 0.081001 948 | vt 0.881001 0.158999 949 | vt 0.918999 0.158999 950 | vt 0.078999 0.918999 951 | vt 0.078999 0.881001 952 | vt 0.001001 0.881001 953 | vt 0.001001 0.918999 954 | vt 0.918999 0.161001 955 | vt 0.881001 0.161001 956 | vt 0.881001 0.238999 957 | vt 0.918999 0.238999 958 | vt 0.158999 0.918999 959 | vt 0.158999 0.881001 960 | vt 0.081001 0.881001 961 | vt 0.081001 0.918999 962 | vt 0.918999 0.241001 963 | vt 0.881001 0.241001 964 | vt 0.881001 0.318999 965 | vt 0.918999 0.318999 966 | vt 0.718999 0.402002 967 | vt 0.642002 0.478999 968 | vt 0.718999 0.478999 969 | vt 0.238999 0.918999 970 | vt 0.238999 0.881001 971 | vt 0.161001 0.881001 972 | vt 0.161001 0.918999 973 | vt 0.918999 0.321001 974 | vt 0.881001 0.321001 975 | vt 0.881001 0.398999 976 | vt 0.918999 0.398999 977 | vt 0.318999 0.918999 978 | vt 0.318999 0.881001 979 | vt 0.241001 0.881001 980 | vt 0.241001 0.918999 981 | vt 0.398999 0.241001 982 | vt 0.321001 0.241001 983 | vt 0.321001 0.318999 984 | vt 0.398999 0.318999 985 | vt 0.918999 0.401001 986 | vt 0.881001 0.401001 987 | vt 0.881001 0.478999 988 | vt 0.918999 0.478999 989 | vt 0.398999 0.918999 990 | vt 0.398999 0.881001 991 | vt 0.321001 0.881001 992 | vt 0.321001 0.918999 993 | vt 0.078999 0.401001 994 | vt 0.001001 0.401001 995 | vt 0.001001 0.478999 996 | vt 0.078999 0.478999 997 | vt 0.478999 0.081001 998 | vt 0.401001 0.081001 999 | vt 0.401001 0.158999 1000 | vt 0.478999 0.158999 1001 | vt 0.918999 0.481001 1002 | vt 0.881001 0.481001 1003 | vt 0.881001 0.558999 1004 | vt 0.918999 0.558999 1005 | vt 0.478999 0.918999 1006 | vt 0.478999 0.881001 1007 | vt 0.401001 0.881001 1008 | vt 0.401001 0.918999 1009 | vt 0.918999 0.561001 1010 | vt 0.881001 0.561001 1011 | vt 0.881001 0.638999 1012 | vt 0.918999 0.638999 1013 | vt 0.158999 0.481001 1014 | vt 0.081001 0.481001 1015 | vt 0.081001 0.558999 1016 | vt 0.158999 0.558999 1017 | vt 0.558999 0.918999 1018 | vt 0.558999 0.881001 1019 | vt 0.481001 0.881001 1020 | vt 0.481001 0.918999 1021 | vt 0.558999 0.161001 1022 | vt 0.481001 0.161001 1023 | vt 0.481001 0.238999 1024 | vt 0.558999 0.238999 1025 | vt 0.238999 0.481001 1026 | vt 0.161001 0.481001 1027 | vt 0.161001 0.558999 1028 | vt 0.238999 0.558999 1029 | vt 0.918999 0.641001 1030 | vt 0.881001 0.641001 1031 | vt 0.881001 0.718999 1032 | vt 0.918999 0.718999 1033 | vt 0.638999 0.918999 1034 | vt 0.638999 0.881001 1035 | vt 0.561001 0.881001 1036 | vt 0.561001 0.918999 1037 | vt 0.918999 0.721001 1038 | vt 0.881001 0.721001 1039 | vt 0.881001 0.798999 1040 | vt 0.918999 0.798999 1041 | vt 0.558999 0.321001 1042 | vt 0.481001 0.321001 1043 | vt 0.481001 0.398999 1044 | vt 0.558999 0.398999 1045 | vt 0.558999 0.401001 1046 | vt 0.481001 0.401001 1047 | vt 0.481001 0.478999 1048 | vt 0.558999 0.478999 1049 | vt 0.478999 0.481001 1050 | vt 0.401001 0.481001 1051 | vt 0.401001 0.558999 1052 | vt 0.478999 0.558999 1053 | vt 0.558999 0.481001 1054 | vt 0.481001 0.481001 1055 | vt 0.481001 0.558999 1056 | vt 0.558999 0.558999 1057 | vt 0.078999 0.561001 1058 | vt 0.001001 0.561001 1059 | vt 0.001001 0.638999 1060 | vt 0.078999 0.638999 1061 | vt 0.238999 0.561001 1062 | vt 0.161001 0.561001 1063 | vt 0.161001 0.638999 1064 | vt 0.238999 0.638999 1065 | vt 0.318999 0.561001 1066 | vt 0.241001 0.561001 1067 | vt 0.241001 0.638999 1068 | vt 0.318999 0.638999 1069 | vt 0.638999 0.241001 1070 | vt 0.561001 0.241001 1071 | vt 0.561001 0.318999 1072 | vt 0.638999 0.318999 1073 | vt 0.398999 0.561001 1074 | vt 0.321001 0.561001 1075 | vt 0.321001 0.638999 1076 | vt 0.398999 0.638999 1077 | vt 0.641001 0.918999 1078 | vt 0.718999 0.918999 1079 | vt 0.718999 0.881001 1080 | vt 0.641001 0.881001 1081 | vt 0.918999 0.878999 1082 | vt 0.918999 0.801001 1083 | vt 0.881001 0.801001 1084 | vt 0.881001 0.878999 1085 | vt 0.721001 0.918999 1086 | vt 0.798999 0.918999 1087 | vt 0.798999 0.881001 1088 | vt 0.721001 0.881001 1089 | vt 0.158999 0.641001 1090 | vt 0.081001 0.641001 1091 | vt 0.081001 0.718999 1092 | vt 0.158999 0.718999 1093 | vt 0.718999 0.081001 1094 | vt 0.641001 0.081001 1095 | vt 0.641001 0.158999 1096 | vt 0.718999 0.158999 1097 | vt 0.958999 0.078999 1098 | vt 0.958999 0.001001 1099 | vt 0.921001 0.001001 1100 | vt 0.921001 0.078999 1101 | vt 0.801001 0.918999 1102 | vt 0.878999 0.918999 1103 | vt 0.878999 0.881001 1104 | vt 0.801001 0.881001 1105 | vt 0.958999 0.158999 1106 | vt 0.958999 0.081001 1107 | vt 0.921001 0.081001 1108 | vt 0.921001 0.158999 1109 | vt 0.398999 0.641001 1110 | vt 0.321001 0.641001 1111 | vt 0.321001 0.718999 1112 | vt 0.398999 0.718999 1113 | vt 0.001001 0.958999 1114 | vt 0.078999 0.958999 1115 | vt 0.078999 0.921001 1116 | vt 0.001001 0.921001 1117 | vt 0.958999 0.238999 1118 | vt 0.958999 0.161001 1119 | vt 0.921001 0.161001 1120 | vt 0.921001 0.238999 1121 | vt 0.238999 0.001001 1122 | vt 0.161001 0.001001 1123 | vt 0.161001 0.078999 1124 | vt 0.238999 0.078999 1125 | vt 0.081001 0.958999 1126 | vt 0.158999 0.958999 1127 | vt 0.158999 0.921001 1128 | vt 0.081001 0.921001 1129 | vt 0.958999 0.318999 1130 | vt 0.958999 0.241001 1131 | vt 0.921001 0.241001 1132 | vt 0.921001 0.318999 1133 | vt 0.161001 0.958999 1134 | vt 0.238999 0.958999 1135 | vt 0.238999 0.921001 1136 | vt 0.161001 0.921001 1137 | vt 0.958999 0.398999 1138 | vt 0.958999 0.321001 1139 | vt 0.921001 0.321001 1140 | vt 0.921001 0.398999 1141 | vt 0.641001 0.477998 1142 | vt 0.641001 0.401001 1143 | vt 0.717998 0.401001 1144 | vt 0.241001 0.958999 1145 | vt 0.318999 0.958999 1146 | vt 0.318999 0.921001 1147 | vt 0.241001 0.921001 1148 | vt 0.958999 0.478999 1149 | vt 0.958999 0.401001 1150 | vt 0.921001 0.401001 1151 | vt 0.921001 0.478999 1152 | vt 0.318999 0.241001 1153 | vt 0.241001 0.241001 1154 | vt 0.241001 0.318999 1155 | vt 0.318999 0.318999 1156 | vt 0.398999 0.958999 1157 | vt 0.398999 0.921001 1158 | vt 0.321001 0.921001 1159 | vt 0.321001 0.958999 1160 | vt 0.078999 0.321001 1161 | vt 0.001001 0.321001 1162 | vt 0.001001 0.398999 1163 | vt 0.078999 0.398999 1164 | vt 0.958999 0.481001 1165 | vt 0.921001 0.481001 1166 | vt 0.921001 0.558999 1167 | vt 0.958999 0.558999 1168 | vt 0.401001 0.958999 1169 | vt 0.478999 0.958999 1170 | vt 0.478999 0.921001 1171 | vt 0.401001 0.921001 1172 | vt 0.398999 0.161001 1173 | vt 0.321001 0.161001 1174 | vt 0.321001 0.238999 1175 | vt 0.398999 0.238999 1176 | vt 0.238999 0.321001 1177 | vt 0.161001 0.321001 1178 | vt 0.161001 0.398999 1179 | vt 0.238999 0.398999 1180 | vt 0.958999 0.638999 1181 | vt 0.958999 0.561001 1182 | vt 0.921001 0.561001 1183 | vt 0.921001 0.638999 1184 | vt 0.558999 0.958999 1185 | vt 0.558999 0.921001 1186 | vt 0.481001 0.921001 1187 | vt 0.481001 0.958999 1188 | vt 0.518999 0.641001 1189 | vt 0.481001 0.641001 1190 | vt 0.481001 0.678999 1191 | vt 0.518999 0.678999 1192 | vt 0.958999 0.718999 1193 | vt 0.958999 0.641001 1194 | vt 0.921001 0.641001 1195 | vt 0.921001 0.718999 1196 | vt 0.398999 0.321001 1197 | vt 0.321001 0.321001 1198 | vt 0.321001 0.398999 1199 | vt 0.398999 0.398999 1200 | vt 0.478999 0.001001 1201 | vt 0.401001 0.001001 1202 | vt 0.401001 0.078999 1203 | vt 0.478999 0.078999 1204 | vt 0.398999 0.838999 1205 | vt 0.398999 0.801001 1206 | vt 0.321001 0.801001 1207 | vt 0.321001 0.838999 1208 | vt 0.958999 0.721001 1209 | vt 0.921001 0.721001 1210 | vt 0.921001 0.798999 1211 | vt 0.958999 0.798999 1212 | vt 0.638999 0.958999 1213 | vt 0.638999 0.921001 1214 | vt 0.561001 0.921001 1215 | vt 0.561001 0.958999 1216 | vt 0.158999 0.401001 1217 | vt 0.081001 0.401001 1218 | vt 0.081001 0.478999 1219 | vt 0.158999 0.478999 1220 | vt 0.478999 0.161001 1221 | vt 0.401001 0.161001 1222 | vt 0.401001 0.238999 1223 | vt 0.478999 0.238999 1224 | vt 0.238999 0.401001 1225 | vt 0.161001 0.401001 1226 | vt 0.161001 0.478999 1227 | vt 0.238999 0.478999 1228 | vt 0.478999 0.241001 1229 | vt 0.401001 0.241001 1230 | vt 0.401001 0.318999 1231 | vt 0.478999 0.318999 1232 | vt 0.318999 0.401001 1233 | vt 0.241001 0.401001 1234 | vt 0.241001 0.478999 1235 | vt 0.318999 0.478999 1236 | vt 0.478999 0.321001 1237 | vt 0.401001 0.321001 1238 | vt 0.401001 0.398999 1239 | vt 0.478999 0.398999 1240 | vt 0.958999 0.801001 1241 | vt 0.921001 0.801001 1242 | vt 0.921001 0.878999 1243 | vt 0.958999 0.878999 1244 | vt 0.641001 0.958999 1245 | vt 0.718999 0.958999 1246 | vt 0.718999 0.921001 1247 | vt 0.641001 0.921001 1248 | vt 0.558999 0.001001 1249 | vt 0.481001 0.001001 1250 | vt 0.481001 0.078999 1251 | vt 0.558999 0.078999 1252 | vt 0.918999 0.958999 1253 | vt 0.918999 0.881001 1254 | vt 0.881001 0.881001 1255 | vt 0.881001 0.958999 1256 | vt 0.721001 0.958999 1257 | vt 0.798999 0.958999 1258 | vt 0.798999 0.921001 1259 | vt 0.721001 0.921001 1260 | vt 0.958999 0.881001 1261 | vt 0.921001 0.881001 1262 | vt 0.921001 0.958999 1263 | vt 0.958999 0.958999 1264 | vt 0.878999 0.958999 1265 | vt 0.878999 0.921001 1266 | vt 0.801001 0.921001 1267 | vt 0.801001 0.958999 1268 | vt 0.998999 0.078999 1269 | vt 0.998999 0.001001 1270 | vt 0.961001 0.001001 1271 | vt 0.961001 0.078999 1272 | vt 0.001001 0.998999 1273 | vt 0.078999 0.998999 1274 | vt 0.078999 0.961001 1275 | vt 0.001001 0.961001 1276 | vt 0.998999 0.158999 1277 | vt 0.998999 0.081001 1278 | vt 0.961001 0.081001 1279 | vt 0.961001 0.158999 1280 | vt 0.158999 0.998999 1281 | vt 0.158999 0.961001 1282 | vt 0.081001 0.961001 1283 | vt 0.081001 0.998999 1284 | vt 0.998999 0.161001 1285 | vt 0.961001 0.161001 1286 | vt 0.961001 0.238999 1287 | vt 0.998999 0.238999 1288 | vt 0.238999 0.998999 1289 | vt 0.238999 0.961001 1290 | vt 0.161001 0.961001 1291 | vt 0.161001 0.998999 1292 | vt 0.998999 0.241001 1293 | vt 0.961001 0.241001 1294 | vt 0.961001 0.318999 1295 | vt 0.998999 0.318999 1296 | vt 0.318999 0.998999 1297 | vt 0.318999 0.961001 1298 | vt 0.241001 0.961001 1299 | vt 0.241001 0.998999 1300 | vt 0.998999 0.321001 1301 | vt 0.961001 0.321001 1302 | vt 0.961001 0.398999 1303 | vt 0.998999 0.398999 1304 | vt 0.638999 0.001001 1305 | vt 0.561001 0.001001 1306 | vt 0.561001 0.078999 1307 | vt 0.638999 0.078999 1308 | vt 0.638999 0.081001 1309 | vt 0.561001 0.081001 1310 | vt 0.561001 0.158999 1311 | vt 0.638999 0.158999 1312 | vt 0.158999 0.561001 1313 | vt 0.081001 0.561001 1314 | vt 0.081001 0.638999 1315 | vt 0.158999 0.638999 1316 | vt 0.638999 0.161001 1317 | vt 0.561001 0.161001 1318 | vt 0.561001 0.238999 1319 | vt 0.638999 0.238999 1320 | vt 0.398999 0.998999 1321 | vt 0.398999 0.961001 1322 | vt 0.321001 0.961001 1323 | vt 0.321001 0.998999 1324 | vt 0.998999 0.401001 1325 | vt 0.961001 0.401001 1326 | vt 0.961001 0.478999 1327 | vt 0.998999 0.478999 1328 | vt 0.478999 0.998999 1329 | vt 0.478999 0.961001 1330 | vt 0.401001 0.961001 1331 | vt 0.401001 0.998999 1332 | vt 0.998999 0.481001 1333 | vt 0.961001 0.481001 1334 | vt 0.961001 0.558999 1335 | vt 0.998999 0.558999 1336 | vt 0.638999 0.321001 1337 | vt 0.561001 0.321001 1338 | vt 0.561001 0.398999 1339 | vt 0.638999 0.398999 1340 | vt 0.558999 0.998999 1341 | vt 0.558999 0.961001 1342 | vt 0.481001 0.961001 1343 | vt 0.481001 0.998999 1344 | vt 0.638999 0.401001 1345 | vt 0.561001 0.401001 1346 | vt 0.561001 0.478999 1347 | vt 0.638999 0.478999 1348 | vt 0.838999 0.001001 1349 | vt 0.801001 0.001001 1350 | vt 0.801001 0.078999 1351 | vt 0.838999 0.078999 1352 | vt 0.558999 0.561001 1353 | vt 0.481001 0.561001 1354 | vt 0.481001 0.638999 1355 | vt 0.558999 0.638999 1356 | vt 0.638999 0.561001 1357 | vt 0.561001 0.561001 1358 | vt 0.561001 0.638999 1359 | vt 0.638999 0.638999 1360 | vt 0.078999 0.641001 1361 | vt 0.001001 0.641001 1362 | vt 0.001001 0.718999 1363 | vt 0.078999 0.718999 1364 | vt 0.001001 0.838999 1365 | vt 0.078999 0.838999 1366 | vt 0.078999 0.801001 1367 | vt 0.001001 0.801001 1368 | vt 0.838999 0.158999 1369 | vt 0.838999 0.081001 1370 | vt 0.801001 0.081001 1371 | vt 0.801001 0.158999 1372 | vt 0.081001 0.838999 1373 | vt 0.158999 0.838999 1374 | vt 0.158999 0.801001 1375 | vt 0.081001 0.801001 1376 | vt 0.718999 0.161001 1377 | vt 0.641001 0.161001 1378 | vt 0.641001 0.238999 1379 | vt 0.718999 0.238999 1380 | vt 0.238999 0.641001 1381 | vt 0.161001 0.641001 1382 | vt 0.161001 0.718999 1383 | vt 0.238999 0.718999 1384 | vt 0.838999 0.238999 1385 | vt 0.838999 0.161001 1386 | vt 0.801001 0.161001 1387 | vt 0.801001 0.238999 1388 | vt 0.161001 0.838999 1389 | vt 0.238999 0.838999 1390 | vt 0.238999 0.801001 1391 | vt 0.161001 0.801001 1392 | vt 0.401001 0.641001 1393 | vt 0.401001 0.717998 1394 | vt 0.477998 0.641001 1395 | vt 0.838999 0.318999 1396 | vt 0.838999 0.241001 1397 | vt 0.801001 0.241001 1398 | vt 0.801001 0.318999 1399 | vt 0.318999 0.838999 1400 | vt 0.318999 0.801001 1401 | vt 0.241001 0.801001 1402 | vt 0.241001 0.838999 1403 | usemtl Material 1404 | s 1 1405 | f 11/1 37/2 15/3 36/4 12/5 34/6 14/7 35/8 7/9 43/10 17/11 44/12 5/13 32/14 13/15 31/16 1406 | f 57/17 25/18 3/19 19/20 1407 | f 56/21 28/22 1/23 21/24 1408 | f 55/25 22/26 3/27 25/28 1409 | f 54/29 31/30 13/31 30/32 1410 | f 53/33 32/34 5/35 18/36 1411 | f 52/37 28/38 10/39 33/40 1412 | f 51/41 23/42 12/43 36/44 1413 | f 50/45 38/46 15/47 37/48 1414 | f 49/49 22/50 10/51 39/52 1415 | f 48/53 41/54 16/55 40/56 1416 | f 47/57 42/58 9/59 27/60 1417 | f 46/61 45/62 6/63 24/64 1418 | f 44/65 17/66 45/67 46/68 1419 | f 5/69 44/70 46/71 18/72 1420 | f 18/73 46/74 24/75 4/76 1421 | f 40/77 16/78 42/79 47/80 1422 | f 8/81 40/82 47/83 20/84 1423 | f 20/85 47/86 27/87 2/88 1424 | f 24/89 6/90 41/91 48/92 1425 | f 4/93 24/94 48/95 26/96 1426 | f 26/97 48/98 40/99 8/100 1427 | f 19/101 3/102 22/103 49/104 1428 | f 6/105 19/106 49/107 41/108 1429 | f 41/109 49/110 39/111 16/112 1430 | f 27/113 9/114 38/115 50/116 1431 | f 2/117 27/118 50/119 29/120 1432 | f 29/121 50/122 37/123 11/124 1433 | f 21/125 1/126 23/127 51/128 1434 | f 9/129 21/130 51/131 38/132 1435 | f 38/133 51/134 36/135 15/136 1436 | f 23/137 1/138 28/139 52/140 1437 | f 12/141 23/142 52/143 34/144 1438 | f 34/145 52/146 33/147 14/148 1439 | f 30/149 13/150 32/151 53/152 1440 | f 8/153 30/154 53/155 26/156 1441 | f 26/157 53/158 18/159 4/160 1442 | f 29/161 11/162 31/163 54/164 1443 | f 2/165 29/166 54/167 20/168 1444 | f 20/169 54/170 30/171 8/172 1445 | f 33/173 10/174 22/175 55/176 1446 | f 14/177 33/178 55/179 35/180 1447 | f 35/181 55/182 25/183 7/184 1448 | f 39/185 10/186 28/187 56/188 1449 | f 16/189 39/190 56/191 42/192 1450 | f 42/193 56/194 21/195 9/196 1451 | f 43/197 7/198 25/199 57/200 1452 | f 17/201 43/202 57/203 45/204 1453 | f 45/205 57/206 19/207 6/208 1454 | f 68/209 88/210 70/211 89/212 62/213 101/214 74/215 100/216 64/217 92/218 71/219 91/220 69/221 93/222 72/223 94/224 1455 | f 114/225 76/226 60/227 82/228 1456 | f 113/229 78/230 58/231 85/232 1457 | f 112/233 82/234 60/235 79/236 1458 | f 111/237 87/238 70/239 88/240 1459 | f 110/241 75/242 62/243 89/244 1460 | f 109/245 90/246 67/247 85/248 1461 | f 108/249 93/250 69/251 80/252 1462 | f 107/253 94/254 72/255 95/256 1463 | f 106/257 96/258 67/259 79/260 1464 | f 105/261 97/262 73/263 98/264 1465 | f 104/265 84/266 66/267 99/268 1466 | f 103/269 81/270 63/271 102/272 1467 | f 101/273 103/274 102/275 74/276 1468 | f 62/277 75/278 103/279 101/280 1469 | f 75/281 61/282 81/283 103/284 1470 | f 97/285 104/286 99/287 73/288 1471 | f 65/289 77/290 104/291 97/292 1472 | f 77/293 59/294 84/295 104/296 1473 | f 81/297 105/298 98/299 63/300 1474 | f 61/301 83/302 105/303 81/304 1475 | f 83/305 65/306 97/307 105/308 1476 | f 76/309 106/310 79/311 60/312 1477 | f 63/313 98/314 106/315 76/316 1478 | f 98/317 73/318 96/319 106/320 1479 | f 84/321 107/322 95/323 66/324 1480 | f 59/325 86/326 107/327 84/328 1481 | f 86/329 68/330 94/331 107/332 1482 | f 78/333 108/334 80/335 58/336 1483 | f 66/337 95/338 108/339 78/340 1484 | f 95/341 72/342 93/343 108/344 1485 | f 80/345 109/346 85/347 58/348 1486 | f 69/349 91/350 109/351 80/352 1487 | f 91/353 71/354 90/355 109/356 1488 | f 87/357 110/358 89/359 70/360 1489 | f 65/361 83/362 110/363 87/364 1490 | f 83/365 61/366 75/367 110/368 1491 | f 86/369 111/370 88/371 68/372 1492 | f 59/373 77/374 111/375 86/376 1493 | f 77/377 65/378 87/379 111/380 1494 | f 90/381 112/382 79/383 67/384 1495 | f 71/385 92/386 112/387 90/388 1496 | f 92/389 64/390 82/391 112/392 1497 | f 96/393 113/394 85/395 67/396 1498 | f 73/397 99/398 113/399 96/400 1499 | f 99/401 66/402 78/403 113/404 1500 | f 100/405 114/406 82/407 64/408 1501 | f 74/409 102/410 114/411 100/412 1502 | f 102/413 63/414 76/415 114/416 1503 | f 148/417 188/418 206/419 201/420 1504 | f 187/421 142/422 118/423 150/424 1505 | f 137/425 116/426 121/427 138/428 1506 | f 186/429 148/430 143/431 1507 | f 135/432 115/433 117/434 136/435 1508 | f 138/436 121/437 115/438 135/439 1509 | f 183/440 154/441 283/442 270/443 1510 | f 136/444 117/445 116/446 137/447 1511 | f 181/448 156/449 128/450 150/451 1512 | f 152/452 122/453 134/454 153/455 1513 | f 177/456 123/457 124/458 173/459 1514 | f 179/460 159/461 130/462 145/463 1515 | f 178/464 267/465 131/466 160/467 1516 | f 173/468 124/469 122/470 152/471 1517 | f 176/472 162/473 128/474 143/475 1518 | f 175/476 163/477 132/478 164/479 1519 | f 174/480 149/481 127/482 165/483 1520 | f 153/484 134/485 123/486 177/487 1521 | f 134/488 137/489 138/490 123/491 1522 | f 124/492 135/493 136/494 122/495 1523 | f 123/496 138/497 135/498 124/499 1524 | f 122/500 136/501 137/502 134/503 1525 | f 117/504 140/505 144/506 116/507 1526 | f 121/508 147/509 146/510 115/511 1527 | f 115/512 146/513 140/514 117/515 1528 | f 180/516 152/517 153/518 185/519 1529 | f 184/520 240/521 152/522 180/523 1530 | f 185/524 153/525 239/526 214/527 1531 | f 280/528 161/529 169/530 287/531 1532 | f 230/532 157/533 161/534 280/535 1533 | f 167/536 172/537 168/538 133/539 1534 | f 287/540 139/541 172/542 167/543 1535 | f 155/544 232/545 157/546 230/547 1536 | f 120/548 175/549 196/550 191/551 1537 | f 210/552 226/553 171/554 189/555 1538 | f 194/556 170/557 225/558 208/559 1539 | f 172/560 139/561 190/562 203/563 1540 | f 176/564 143/565 193/566 192/567 1541 | f 189/568 171/569 170/570 194/571 1542 | f 163/572 174/573 165/574 132/575 1543 | f 126/576 141/577 174/578 163/579 1544 | f 141/580 119/581 149/582 174/583 1545 | f 120/584 126/585 163/586 175/587 1546 | f 164/588 132/589 162/590 176/591 1547 | f 153/592 177/593 219/594 239/595 1548 | f 240/596 221/597 173/598 152/599 1549 | f 173/600 221/601 219/602 177/603 1550 | f 149/604 178/605 160/606 127/607 1551 | f 119/608 151/609 178/610 149/611 1552 | f 151/612 129/613 267/614 178/615 1553 | f 142/616 179/617 145/618 118/619 1554 | f 127/620 160/621 179/622 142/623 1555 | f 160/624 131/625 159/626 179/627 1556 | f 171/628 180/629 185/630 170/631 1557 | f 170/632 185/633 214/634 225/635 1558 | f 226/636 184/637 180/638 171/639 1559 | f 116/640 144/641 147/642 121/643 1560 | f 232/644 210/645 189/646 157/647 1561 | f 157/648 189/649 194/650 161/651 1562 | f 145/652 181/653 150/654 118/655 1563 | f 130/656 268/657 181/658 145/659 1564 | f 268/660 282/661 156/662 181/663 1565 | f 154/664 182/665 155/666 283/667 1566 | f 126/668 120/669 182/670 154/671 1567 | f 120/672 139/673 182/674 1568 | f 151/675 183/676 270/677 129/678 1569 | f 119/679 141/680 183/681 151/682 1570 | f 141/683 126/684 154/685 183/686 1571 | f 161/687 194/688 208/689 169/690 1572 | f 261/691 199/692 198/693 260/694 1573 | f 280/695 217/696 213/697 288/698 1574 | f 258/699 205/700 204/701 263/702 1575 | f 263/703 204/704 212/705 257/706 1576 | f 218/707 195/708 199/709 261/710 1577 | f 156/711 186/712 143/713 128/714 1578 | f 282/715 158/716 186/717 156/718 1579 | f 158/719 125/720 148/721 186/722 1580 | f 277/723 207/724 200/725 275/726 1581 | f 199/727 195/728 212/729 204/730 1582 | f 217/731 198/732 205/733 213/734 1583 | f 162/735 187/736 150/737 128/738 1584 | f 132/739 165/740 187/741 162/742 1585 | f 165/743 127/744 142/745 187/746 1586 | f 166/747 188/748 148/749 125/750 1587 | f 133/751 168/752 188/753 166/754 1588 | f 198/755 199/756 204/757 205/758 1589 | f 195/759 200/760 207/761 212/762 1590 | f 133/763 166/764 218/765 261/766 1591 | f 206/767 192/768 193/769 201/770 1592 | f 203/771 196/772 197/773 202/774 1593 | f 190/775 191/776 196/777 203/778 1594 | f 166/779 125/780 275/781 218/782 1595 | f 262/783 223/784 278/785 285/786 1596 | f 281/787 265/788 223/789 262/790 1597 | f 265/791 284/792 273/793 223/794 1598 | f 202/795 197/796 192/797 206/798 1599 | f 158/799 224/800 275/801 125/802 1600 | f 143/803 148/804 201/805 193/806 1601 | f 188/807 168/808 202/809 206/810 1602 | f 282/811 269/812 224/813 158/814 1603 | f 164/815 176/816 192/817 197/818 1604 | f 269/819 284/820 277/821 224/822 1605 | f 279/823 227/824 271/825 286/826 1606 | f 175/827 164/828 197/829 196/830 1607 | f 289/831 272/832 227/833 279/834 1608 | f 139/835 120/836 191/837 190/838 1609 | f 168/839 172/840 203/841 202/842 1610 | f 272/843 129/844 270/845 227/846 1611 | f 288/847 230/848 280/849 1612 | f 286/850 271/851 230/852 288/853 1613 | f 271/854 283/855 155/856 230/857 1614 | f 233/858 169/859 208/860 209/861 1615 | f 234/862 233/863 209/864 211/865 1616 | f 232/866 234/867 211/868 210/869 1617 | f 255/870 253/871 241/872 243/873 1618 | f 268/874 231/875 269/876 282/877 1619 | f 226/878 228/879 216/880 184/881 1620 | f 229/882 225/883 214/884 215/885 1621 | f 130/886 276/887 231/888 268/889 1622 | f 228/890 229/891 215/892 216/893 1623 | f 222/894 220/895 219/896 221/897 1624 | f 276/898 290/899 273/900 231/901 1625 | f 266/902 235/903 159/904 131/905 1626 | f 285/906 278/907 235/908 266/909 1627 | f 240/910 238/911 222/912 221/913 1628 | f 237/914 239/915 219/916 220/917 1629 | f 211/918 209/919 229/920 228/921 1630 | f 278/922 290/923 276/924 235/925 1631 | f 209/926 208/927 225/928 229/929 1632 | f 210/930 211/931 228/932 226/933 1633 | f 272/934 236/935 267/936 129/937 1634 | f 289/938 274/939 236/940 272/941 1635 | f 155/942 182/943 234/944 232/945 1636 | f 182/946 139/947 233/948 234/949 1637 | f 139/950 287/951 169/952 233/953 1638 | f 274/954 285/955 266/956 236/957 1639 | f 263/958 257/959 265/960 281/961 1640 | f 288/962 258/963 264/964 286/965 1641 | f 215/966 214/967 239/968 237/969 1642 | f 184/970 216/971 238/972 240/973 1643 | f 279/974 259/975 274/976 289/977 1644 | f 286/978 264/979 259/980 279/981 1645 | f 264/982 281/983 262/984 259/985 1646 | f 216/986 215/987 237/988 238/989 1647 | f 256/990 254/991 244/992 242/993 1648 | f 253/994 256/995 242/996 241/997 1649 | f 254/998 255/999 243/1000 244/1001 1650 | f 252/1002 249/1003 246/1004 247/1005 1651 | f 251/1006 250/1007 248/1008 245/1009 1652 | f 250/1010 252/1011 247/1012 248/1013 1653 | f 249/1014 251/1015 245/1016 246/1017 1654 | f 237/1018 220/1019 251/1020 249/1021 1655 | f 222/1022 238/1023 252/1024 250/1025 1656 | f 220/1026 222/1027 250/1028 251/1029 1657 | f 238/1030 237/1031 249/1032 252/1033 1658 | f 247/1034 246/1035 255/1036 254/1037 1659 | f 245/1038 248/1039 256/1040 253/1041 1660 | f 248/1042 247/1043 254/1044 256/1045 1661 | f 246/1046 245/1047 253/1048 255/1049 1662 | f 257/1050 212/1051 207/1052 277/1053 1663 | f 260/1054 198/1055 217/1056 280/1057 1664 | f 288/1058 213/1059 205/1060 258/1061 1665 | f 287/1062 167/1063 260/1064 280/1065 1666 | f 167/1066 133/1067 261/1068 260/1069 1667 | f 259/1070 262/1071 285/1072 274/1073 1668 | f 258/1074 263/1075 281/1076 264/1077 1669 | f 257/1078 277/1079 284/1080 265/1081 1670 | f 236/1082 266/1083 131/1084 267/1085 1671 | f 235/1086 276/1087 130/1088 159/1089 1672 | f 231/1090 273/1091 284/1092 269/1093 1673 | f 227/1094 270/1095 283/1096 271/1097 1674 | f 224/1098 277/1099 275/1100 1675 | f 223/1101 273/1102 290/1103 278/1104 1676 | f 275/1105 200/1106 195/1107 218/1108 1677 | --------------------------------------------------------------------------------