├── trex └── .gitkeep ├── renovate.json ├── screenshot.gif ├── plugin └── trex.vim ├── index.html ├── package.json ├── README.md ├── download.cmd ├── download_mac.cmd ├── main.js ├── index.js └── three.js ├── OBJLoader.js ├── OrbitControls.js └── GLTFLoader.js /trex/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattn/vim-trex/HEAD/screenshot.gif -------------------------------------------------------------------------------- /plugin/trex.vim: -------------------------------------------------------------------------------- 1 | let s:base = expand(':h:h') 2 | 3 | function! s:trex() 4 | let cmd = has('win32') ? 'electron.cmd' : 'electron' 5 | let s:job = job_start([cmd, s:base], { 6 | \ 'callback': {x->[execute('echomsg x', 1)]}, 7 | \}) 8 | call foreground() 9 | endfunction 10 | 11 | command! TRex call s:trex() 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | T-Rex 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trex", 3 | "version": "1.0.0", 4 | "description": "Running T-Rex", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron ." 8 | }, 9 | "repository": "https://github.com/mattn/vim-trex", 10 | "keywords": [ 11 | "Electron", 12 | "trex" 13 | ], 14 | "author": "GitHub", 15 | "license": "CC0-1.0", 16 | "devDependencies": { 17 | "electron": "15.5.5" 18 | }, 19 | "dependencies": {} 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-trex 2 | 3 | Running T-Rex on Vim 4 | 5 | ![TREx](https://raw.githubusercontent.com/mattn/vim-trex/master/screenshot.gif) 6 | 7 | ## Usage 8 | 9 | ``` 10 | :TRex 11 | ``` 12 | 13 | ## Requirements 14 | 15 | * Electron 16 | 17 | ## Installation 18 | 19 | To install using [Vim-Plug](https://github.com/junegunn/vim-plug): 20 | 21 | ``` 22 | " add this line to your .vimrc file 23 | Plug 'mattn/vim-trex' 24 | ``` 25 | 26 | Before using this, you need to run below once. 27 | 28 | ``` 29 | $ npm install 30 | ``` 31 | 32 | And download T-Rex glTF model. 33 | 34 | For Windows 35 | ``` 36 | download.cmd 37 | ``` 38 | 39 | For UNIX: 40 | ``` 41 | source download.cmd 42 | ``` 43 | 44 | 45 | ## License 46 | 47 | MIT 48 | 49 | ## Author 50 | 51 | Yasuhiro Matsumoto (a.k.a. mattn) 52 | -------------------------------------------------------------------------------- /download.cmd: -------------------------------------------------------------------------------- 1 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%%202.0/T-Rex/mtl_trex_baseColor.jpg" 2 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%%202.0/T-Rex/mtl_trex_emissive.jpg" 3 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%%202.0/T-Rex/mtl_trex_metallicRoughness.jpg" 4 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%%202.0/T-Rex/trex_nml.png" 5 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%%202.0/T-Rex/trex_running.bin" 6 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%%202.0/T-Rex/trex_running.gltf" 7 | -------------------------------------------------------------------------------- /download_mac.cmd: -------------------------------------------------------------------------------- 1 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%202.0/T-Rex/mtl_trex_baseColor.jpg" 2 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%202.0/T-Rex/mtl_trex_emissive.jpg" 3 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%202.0/T-Rex/mtl_trex_metallicRoughness.jpg" 4 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%202.0/T-Rex/trex_nml.png" 5 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%202.0/T-Rex/trex_running.bin" 6 | wget -nc -P trex "https://raw.githubusercontent.com/BabylonJS/Exporters/master/Maya/Samples/glTF%202.0/T-Rex/trex_running.gltf" 7 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'), 2 | app = electron.app, 3 | BrowserWindow = electron.BrowserWindow, 4 | path = require('path'), 5 | url = require('url') 6 | 7 | let mainWindow 8 | 9 | function createWindow() { 10 | let fullscreen = true 11 | if (process.platform === 'darwin') { 12 | fullscreen = false 13 | } 14 | 15 | const size = electron.screen.getPrimaryDisplay().size 16 | mainWindow = new BrowserWindow({ 17 | left: 0, 18 | top: 0, 19 | width: size.width, 20 | height: size.height, 21 | frame: false, 22 | show: true, 23 | kiosk: true, 24 | alwaysOnTop: true, 25 | ignoreMouseEvents: true, 26 | maximize: true, 27 | fullscreen: fullscreen, 28 | focusable: false, 29 | skipTaskbar: true, 30 | transparent: true, 31 | resizable: false 32 | }) 33 | 34 | //mainWindow.setAlwaysOnTop(true, 'screen'); 35 | //mainWindow.maximize() 36 | mainWindow.setIgnoreMouseEvents(true) 37 | mainWindow.loadURL(url.format({ 38 | pathname: path.join(__dirname, 'index.html'), 39 | protocol: 'file:', 40 | slashes: true 41 | })) 42 | mainWindow.on('closed', () => { mainWindow = null }) 43 | } 44 | 45 | app.on('ready', createWindow) 46 | app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit() }) 47 | app.on('activate', () => { if (mainWindow === null) createWindow() }) 48 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const width = window.innerWidth, 4 | height = window.innerHeight 5 | 6 | const renderer = new THREE.WebGLRenderer({alpha: true, antialias: true}) 7 | renderer.setSize(width, height) 8 | renderer.setClearColor(new THREE.Color(0xffffff), 0.0) 9 | document.body.appendChild(renderer.domElement) 10 | 11 | const scene = new THREE.Scene(), 12 | camera = new THREE.PerspectiveCamera(50, width/height, 0.1, 3000) 13 | camera.position.set(0, 0, -2500) 14 | 15 | 16 | var light1 = new THREE.DirectionalLight(0xffffff, 0.5) 17 | light1.position.set(0, 0, 50) 18 | scene.add(light1) 19 | 20 | var light2 = new THREE.DirectionalLight(0xffffff, 0.5) 21 | light2.position.set(0, 0, -50) 22 | scene.add(light2) 23 | 24 | var light3 = new THREE.DirectionalLight(0xffffff, 0.2) 25 | light3.position.set(50, 0, 0) 26 | scene.add(light3) 27 | 28 | const light = new THREE.AmbientLight(0xffffff, 1) 29 | scene.add(light) 30 | 31 | const controls = new THREE.OrbitControls(camera, renderer.domElement) 32 | 33 | const windmillClock = new THREE.Clock() 34 | let mixer, mixerWindmill 35 | 36 | const loader = new THREE.GLTFLoader() 37 | loader.load('./trex/trex_running.gltf', (data) => { 38 | const object = data.scene 39 | const animations = data.animations 40 | if (animations && animations.length) { 41 | mixerWindmill = new THREE.AnimationMixer(object) 42 | for (let i = 0; i < animations.length; i++) 43 | mixerWindmill.clipAction(animations[i]).play() 44 | } 45 | object.rotation.y = -60 46 | object.position.set(0, -200, 0) 47 | scene.add(object) 48 | }) 49 | 50 | const clock = new THREE.Clock() 51 | 52 | const animation = () => { 53 | renderer.render(scene, camera) 54 | controls.update() 55 | if(mixer) mixer.update(clock.getDelta()) 56 | if(mixerWindmill) mixerWindmill.update(windmillClock.getDelta()) 57 | requestAnimationFrame(animation) 58 | } 59 | 60 | animation() 61 | -------------------------------------------------------------------------------- /three.js/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = ( function () { 6 | 7 | // o object_name | g group_name 8 | var object_pattern = /^[og]\s*(.+)?/; 9 | // mtllib file_reference 10 | var material_library_pattern = /^mtllib /; 11 | // usemtl material_name 12 | var material_use_pattern = /^usemtl /; 13 | 14 | function ParserState() { 15 | 16 | var state = { 17 | objects: [], 18 | object: {}, 19 | 20 | vertices: [], 21 | normals: [], 22 | colors: [], 23 | uvs: [], 24 | 25 | materialLibraries: [], 26 | 27 | startObject: function ( name, fromDeclaration ) { 28 | 29 | // If the current object (initial from reset) is not from a g/o declaration in the parsed 30 | // file. We need to use it for the first parsed g/o to keep things in sync. 31 | if ( this.object && this.object.fromDeclaration === false ) { 32 | 33 | this.object.name = name; 34 | this.object.fromDeclaration = ( fromDeclaration !== false ); 35 | return; 36 | 37 | } 38 | 39 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 40 | 41 | if ( this.object && typeof this.object._finalize === 'function' ) { 42 | 43 | this.object._finalize( true ); 44 | 45 | } 46 | 47 | this.object = { 48 | name: name || '', 49 | fromDeclaration: ( fromDeclaration !== false ), 50 | 51 | geometry: { 52 | vertices: [], 53 | normals: [], 54 | colors: [], 55 | uvs: [] 56 | }, 57 | materials: [], 58 | smooth: true, 59 | 60 | startMaterial: function ( name, libraries ) { 61 | 62 | var previous = this._finalize( false ); 63 | 64 | // New usemtl declaration overwrites an inherited material, except if faces were declared 65 | // after the material, then it must be preserved for proper MultiMaterial continuation. 66 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { 67 | 68 | this.materials.splice( previous.index, 1 ); 69 | 70 | } 71 | 72 | var material = { 73 | index: this.materials.length, 74 | name: name || '', 75 | mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), 76 | smooth: ( previous !== undefined ? previous.smooth : this.smooth ), 77 | groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), 78 | groupEnd: - 1, 79 | groupCount: - 1, 80 | inherited: false, 81 | 82 | clone: function ( index ) { 83 | 84 | var cloned = { 85 | index: ( typeof index === 'number' ? index : this.index ), 86 | name: this.name, 87 | mtllib: this.mtllib, 88 | smooth: this.smooth, 89 | groupStart: 0, 90 | groupEnd: - 1, 91 | groupCount: - 1, 92 | inherited: false 93 | }; 94 | cloned.clone = this.clone.bind( cloned ); 95 | return cloned; 96 | 97 | } 98 | }; 99 | 100 | this.materials.push( material ); 101 | 102 | return material; 103 | 104 | }, 105 | 106 | currentMaterial: function () { 107 | 108 | if ( this.materials.length > 0 ) { 109 | 110 | return this.materials[ this.materials.length - 1 ]; 111 | 112 | } 113 | 114 | return undefined; 115 | 116 | }, 117 | 118 | _finalize: function ( end ) { 119 | 120 | var lastMultiMaterial = this.currentMaterial(); 121 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { 122 | 123 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 124 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 125 | lastMultiMaterial.inherited = false; 126 | 127 | } 128 | 129 | // Ignore objects tail materials if no face declarations followed them before a new o/g started. 130 | if ( end && this.materials.length > 1 ) { 131 | 132 | for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { 133 | 134 | if ( this.materials[ mi ].groupCount <= 0 ) { 135 | 136 | this.materials.splice( mi, 1 ); 137 | 138 | } 139 | 140 | } 141 | 142 | } 143 | 144 | // Guarantee at least one empty material, this makes the creation later more straight forward. 145 | if ( end && this.materials.length === 0 ) { 146 | 147 | this.materials.push( { 148 | name: '', 149 | smooth: this.smooth 150 | } ); 151 | 152 | } 153 | 154 | return lastMultiMaterial; 155 | 156 | } 157 | }; 158 | 159 | // Inherit previous objects material. 160 | // Spec tells us that a declared material must be set to all objects until a new material is declared. 161 | // If a usemtl declaration is encountered while this new object is being parsed, it will 162 | // overwrite the inherited material. Exception being that there was already face declarations 163 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation. 164 | 165 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { 166 | 167 | var declared = previousMaterial.clone( 0 ); 168 | declared.inherited = true; 169 | this.object.materials.push( declared ); 170 | 171 | } 172 | 173 | this.objects.push( this.object ); 174 | 175 | }, 176 | 177 | finalize: function () { 178 | 179 | if ( this.object && typeof this.object._finalize === 'function' ) { 180 | 181 | this.object._finalize( true ); 182 | 183 | } 184 | 185 | }, 186 | 187 | parseVertexIndex: function ( value, len ) { 188 | 189 | var index = parseInt( value, 10 ); 190 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 191 | 192 | }, 193 | 194 | parseNormalIndex: function ( value, len ) { 195 | 196 | var index = parseInt( value, 10 ); 197 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 198 | 199 | }, 200 | 201 | parseUVIndex: function ( value, len ) { 202 | 203 | var index = parseInt( value, 10 ); 204 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; 205 | 206 | }, 207 | 208 | addVertex: function ( a, b, c ) { 209 | 210 | var src = this.vertices; 211 | var dst = this.object.geometry.vertices; 212 | 213 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 214 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); 215 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); 216 | 217 | }, 218 | 219 | addVertexPoint: function ( a ) { 220 | 221 | var src = this.vertices; 222 | var dst = this.object.geometry.vertices; 223 | 224 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 225 | 226 | }, 227 | 228 | addVertexLine: function ( a ) { 229 | 230 | var src = this.vertices; 231 | var dst = this.object.geometry.vertices; 232 | 233 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 234 | 235 | }, 236 | 237 | addNormal: function ( a, b, c ) { 238 | 239 | var src = this.normals; 240 | var dst = this.object.geometry.normals; 241 | 242 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 243 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); 244 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); 245 | 246 | }, 247 | 248 | addColor: function ( a, b, c ) { 249 | 250 | var src = this.colors; 251 | var dst = this.object.geometry.colors; 252 | 253 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 254 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); 255 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); 256 | 257 | }, 258 | 259 | addUV: function ( a, b, c ) { 260 | 261 | var src = this.uvs; 262 | var dst = this.object.geometry.uvs; 263 | 264 | dst.push( src[ a + 0 ], src[ a + 1 ] ); 265 | dst.push( src[ b + 0 ], src[ b + 1 ] ); 266 | dst.push( src[ c + 0 ], src[ c + 1 ] ); 267 | 268 | }, 269 | 270 | addUVLine: function ( a ) { 271 | 272 | var src = this.uvs; 273 | var dst = this.object.geometry.uvs; 274 | 275 | dst.push( src[ a + 0 ], src[ a + 1 ] ); 276 | 277 | }, 278 | 279 | addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { 280 | 281 | var vLen = this.vertices.length; 282 | 283 | var ia = this.parseVertexIndex( a, vLen ); 284 | var ib = this.parseVertexIndex( b, vLen ); 285 | var ic = this.parseVertexIndex( c, vLen ); 286 | 287 | this.addVertex( ia, ib, ic ); 288 | 289 | if ( ua !== undefined && ua !== '' ) { 290 | 291 | var uvLen = this.uvs.length; 292 | ia = this.parseUVIndex( ua, uvLen ); 293 | ib = this.parseUVIndex( ub, uvLen ); 294 | ic = this.parseUVIndex( uc, uvLen ); 295 | this.addUV( ia, ib, ic ); 296 | 297 | } 298 | 299 | if ( na !== undefined && na !== '' ) { 300 | 301 | // Normals are many times the same. If so, skip function call and parseInt. 302 | var nLen = this.normals.length; 303 | ia = this.parseNormalIndex( na, nLen ); 304 | 305 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 306 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 307 | 308 | this.addNormal( ia, ib, ic ); 309 | 310 | } 311 | 312 | if ( this.colors.length > 0 ) { 313 | 314 | this.addColor( ia, ib, ic ); 315 | 316 | } 317 | 318 | }, 319 | 320 | addPointGeometry: function ( vertices ) { 321 | 322 | this.object.geometry.type = 'Points'; 323 | 324 | var vLen = this.vertices.length; 325 | 326 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 327 | 328 | this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) ); 329 | 330 | } 331 | 332 | }, 333 | 334 | addLineGeometry: function ( vertices, uvs ) { 335 | 336 | this.object.geometry.type = 'Line'; 337 | 338 | var vLen = this.vertices.length; 339 | var uvLen = this.uvs.length; 340 | 341 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 342 | 343 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); 344 | 345 | } 346 | 347 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { 348 | 349 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); 350 | 351 | } 352 | 353 | } 354 | 355 | }; 356 | 357 | state.startObject( '', false ); 358 | 359 | return state; 360 | 361 | } 362 | 363 | // 364 | 365 | function OBJLoader( manager ) { 366 | 367 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 368 | 369 | this.materials = null; 370 | 371 | } 372 | 373 | OBJLoader.prototype = { 374 | 375 | constructor: OBJLoader, 376 | 377 | load: function ( url, onLoad, onProgress, onError ) { 378 | 379 | var scope = this; 380 | 381 | var loader = new THREE.FileLoader( scope.manager ); 382 | loader.setPath( this.path ); 383 | loader.load( url, function ( text ) { 384 | 385 | onLoad( scope.parse( text ) ); 386 | 387 | }, onProgress, onError ); 388 | 389 | }, 390 | 391 | setPath: function ( value ) { 392 | 393 | this.path = value; 394 | 395 | return this; 396 | 397 | }, 398 | 399 | setMaterials: function ( materials ) { 400 | 401 | this.materials = materials; 402 | 403 | return this; 404 | 405 | }, 406 | 407 | parse: function ( text ) { 408 | 409 | console.time( 'OBJLoader' ); 410 | 411 | var state = new ParserState(); 412 | 413 | if ( text.indexOf( '\r\n' ) !== - 1 ) { 414 | 415 | // This is faster than String.split with regex that splits on both 416 | text = text.replace( /\r\n/g, '\n' ); 417 | 418 | } 419 | 420 | if ( text.indexOf( '\\\n' ) !== - 1 ) { 421 | 422 | // join lines separated by a line continuation character (\) 423 | text = text.replace( /\\\n/g, '' ); 424 | 425 | } 426 | 427 | var lines = text.split( '\n' ); 428 | var line = '', lineFirstChar = ''; 429 | var lineLength = 0; 430 | var result = []; 431 | 432 | // Faster to just trim left side of the line. Use if available. 433 | var trimLeft = ( typeof ''.trimLeft === 'function' ); 434 | 435 | for ( var i = 0, l = lines.length; i < l; i ++ ) { 436 | 437 | line = lines[ i ]; 438 | 439 | line = trimLeft ? line.trimLeft() : line.trim(); 440 | 441 | lineLength = line.length; 442 | 443 | if ( lineLength === 0 ) continue; 444 | 445 | lineFirstChar = line.charAt( 0 ); 446 | 447 | // @todo invoke passed in handler if any 448 | if ( lineFirstChar === '#' ) continue; 449 | 450 | if ( lineFirstChar === 'v' ) { 451 | 452 | var data = line.split( /\s+/ ); 453 | 454 | switch ( data[ 0 ] ) { 455 | 456 | case 'v': 457 | state.vertices.push( 458 | parseFloat( data[ 1 ] ), 459 | parseFloat( data[ 2 ] ), 460 | parseFloat( data[ 3 ] ) 461 | ); 462 | if ( data.length === 8 ) { 463 | 464 | state.colors.push( 465 | parseFloat( data[ 4 ] ), 466 | parseFloat( data[ 5 ] ), 467 | parseFloat( data[ 6 ] ) 468 | 469 | ); 470 | 471 | } 472 | break; 473 | case 'vn': 474 | state.normals.push( 475 | parseFloat( data[ 1 ] ), 476 | parseFloat( data[ 2 ] ), 477 | parseFloat( data[ 3 ] ) 478 | ); 479 | break; 480 | case 'vt': 481 | state.uvs.push( 482 | parseFloat( data[ 1 ] ), 483 | parseFloat( data[ 2 ] ) 484 | ); 485 | break; 486 | 487 | } 488 | 489 | } else if ( lineFirstChar === 'f' ) { 490 | 491 | var lineData = line.substr( 1 ).trim(); 492 | var vertexData = lineData.split( /\s+/ ); 493 | var faceVertices = []; 494 | 495 | // Parse the face vertex data into an easy to work with format 496 | 497 | for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { 498 | 499 | var vertex = vertexData[ j ]; 500 | 501 | if ( vertex.length > 0 ) { 502 | 503 | var vertexParts = vertex.split( '/' ); 504 | faceVertices.push( vertexParts ); 505 | 506 | } 507 | 508 | } 509 | 510 | // Draw an edge between the first vertex and all subsequent vertices to form an n-gon 511 | 512 | var v1 = faceVertices[ 0 ]; 513 | 514 | for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { 515 | 516 | var v2 = faceVertices[ j ]; 517 | var v3 = faceVertices[ j + 1 ]; 518 | 519 | state.addFace( 520 | v1[ 0 ], v2[ 0 ], v3[ 0 ], 521 | v1[ 1 ], v2[ 1 ], v3[ 1 ], 522 | v1[ 2 ], v2[ 2 ], v3[ 2 ] 523 | ); 524 | 525 | } 526 | 527 | } else if ( lineFirstChar === 'l' ) { 528 | 529 | var lineParts = line.substring( 1 ).trim().split( " " ); 530 | var lineVertices = [], lineUVs = []; 531 | 532 | if ( line.indexOf( "/" ) === - 1 ) { 533 | 534 | lineVertices = lineParts; 535 | 536 | } else { 537 | 538 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 539 | 540 | var parts = lineParts[ li ].split( "/" ); 541 | 542 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 543 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 544 | 545 | } 546 | 547 | } 548 | state.addLineGeometry( lineVertices, lineUVs ); 549 | 550 | } else if ( lineFirstChar === 'p' ) { 551 | 552 | var lineData = line.substr( 1 ).trim(); 553 | var pointData = lineData.split( " " ); 554 | 555 | state.addPointGeometry( pointData ); 556 | 557 | } else if ( ( result = object_pattern.exec( line ) ) !== null ) { 558 | 559 | // o object_name 560 | // or 561 | // g group_name 562 | 563 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 564 | // var name = result[ 0 ].substr( 1 ).trim(); 565 | var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); 566 | 567 | state.startObject( name ); 568 | 569 | } else if ( material_use_pattern.test( line ) ) { 570 | 571 | // material 572 | 573 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 574 | 575 | } else if ( material_library_pattern.test( line ) ) { 576 | 577 | // mtl file 578 | 579 | state.materialLibraries.push( line.substring( 7 ).trim() ); 580 | 581 | } else if ( lineFirstChar === 's' ) { 582 | 583 | result = line.split( ' ' ); 584 | 585 | // smooth shading 586 | 587 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 588 | // but does not define a usemtl for each face set. 589 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 590 | // This requires some care to not create extra material on each smooth value for "normal" obj files. 591 | // where explicit usemtl defines geometry groups. 592 | // Example asset: examples/models/obj/cerberus/Cerberus.obj 593 | 594 | /* 595 | * http://paulbourke.net/dataformats/obj/ 596 | * or 597 | * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf 598 | * 599 | * From chapter "Grouping" Syntax explanation "s group_number": 600 | * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. 601 | * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form 602 | * surfaces, smoothing groups are either turned on or off; there is no difference between values greater 603 | * than 0." 604 | */ 605 | if ( result.length > 1 ) { 606 | 607 | var value = result[ 1 ].trim().toLowerCase(); 608 | state.object.smooth = ( value !== '0' && value !== 'off' ); 609 | 610 | } else { 611 | 612 | // ZBrush can produce "s" lines #11707 613 | state.object.smooth = true; 614 | 615 | } 616 | var material = state.object.currentMaterial(); 617 | if ( material ) material.smooth = state.object.smooth; 618 | 619 | } else { 620 | 621 | // Handle null terminated files without exception 622 | if ( line === '\0' ) continue; 623 | 624 | throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); 625 | 626 | } 627 | 628 | } 629 | 630 | state.finalize(); 631 | 632 | var container = new THREE.Group(); 633 | container.materialLibraries = [].concat( state.materialLibraries ); 634 | 635 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 636 | 637 | var object = state.objects[ i ]; 638 | var geometry = object.geometry; 639 | var materials = object.materials; 640 | var isLine = ( geometry.type === 'Line' ); 641 | var isPoints = ( geometry.type === 'Points' ); 642 | var hasVertexColors = false; 643 | 644 | // Skip o/g line declarations that did not follow with any faces 645 | if ( geometry.vertices.length === 0 ) continue; 646 | 647 | var buffergeometry = new THREE.BufferGeometry(); 648 | 649 | buffergeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); 650 | 651 | if ( geometry.normals.length > 0 ) { 652 | 653 | buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); 654 | 655 | } else { 656 | 657 | buffergeometry.computeVertexNormals(); 658 | 659 | } 660 | 661 | if ( geometry.colors.length > 0 ) { 662 | 663 | hasVertexColors = true; 664 | buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) ); 665 | 666 | } 667 | 668 | if ( geometry.uvs.length > 0 ) { 669 | 670 | buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) ); 671 | 672 | } 673 | 674 | // Create materials 675 | 676 | var createdMaterials = []; 677 | 678 | for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { 679 | 680 | var sourceMaterial = materials[ mi ]; 681 | var material = undefined; 682 | 683 | if ( this.materials !== null ) { 684 | 685 | material = this.materials.create( sourceMaterial.name ); 686 | 687 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 688 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { 689 | 690 | var materialLine = new THREE.LineBasicMaterial(); 691 | materialLine.copy( material ); 692 | materialLine.lights = false; // TOFIX 693 | material = materialLine; 694 | 695 | } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) { 696 | 697 | var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } ); 698 | materialLine.copy( material ); 699 | material = materialPoints; 700 | 701 | } 702 | 703 | } 704 | 705 | if ( ! material ) { 706 | 707 | if ( isLine ) { 708 | 709 | material = new THREE.LineBasicMaterial(); 710 | 711 | } else if ( isPoints ) { 712 | 713 | material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } ); 714 | 715 | } else { 716 | 717 | material = new THREE.MeshPhongMaterial(); 718 | 719 | } 720 | 721 | material.name = sourceMaterial.name; 722 | 723 | } 724 | 725 | material.flatShading = sourceMaterial.smooth ? false : true; 726 | material.vertexColors = hasVertexColors ? THREE.VertexColors : THREE.NoColors; 727 | 728 | createdMaterials.push( material ); 729 | 730 | } 731 | 732 | // Create mesh 733 | 734 | var mesh; 735 | 736 | if ( createdMaterials.length > 1 ) { 737 | 738 | for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { 739 | 740 | var sourceMaterial = materials[ mi ]; 741 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 742 | 743 | } 744 | 745 | if ( isLine ) { 746 | 747 | mesh = new THREE.LineSegments( buffergeometry, createdMaterials ); 748 | 749 | } else if ( isPoints ) { 750 | 751 | mesh = new THREE.Points( buffergeometry, createdMaterials ); 752 | 753 | } else { 754 | 755 | mesh = new THREE.Mesh( buffergeometry, createdMaterials ); 756 | 757 | } 758 | 759 | } else { 760 | 761 | if ( isLine ) { 762 | 763 | mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ); 764 | 765 | } else if ( isPoints ) { 766 | 767 | mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] ); 768 | 769 | } else { 770 | 771 | mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ); 772 | 773 | } 774 | 775 | } 776 | 777 | mesh.name = object.name; 778 | 779 | container.add( mesh ); 780 | 781 | } 782 | 783 | console.timeEnd( 'OBJLoader' ); 784 | 785 | return container; 786 | 787 | } 788 | 789 | }; 790 | 791 | return OBJLoader; 792 | 793 | } )(); 794 | -------------------------------------------------------------------------------- /three.js/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: two-finger move 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.panSpeed = 1.0; 63 | this.screenSpacePanning = false; // if true, pan in screen-space 64 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 65 | 66 | // Set to true to automatically rotate around the target 67 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 68 | this.autoRotate = false; 69 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 70 | 71 | // Set to false to disable use of the keys 72 | this.enableKeys = true; 73 | 74 | // The four arrow keys 75 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 76 | 77 | // Mouse buttons 78 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 79 | 80 | // for reset 81 | this.target0 = this.target.clone(); 82 | this.position0 = this.object.position.clone(); 83 | this.zoom0 = this.object.zoom; 84 | 85 | // 86 | // public methods 87 | // 88 | 89 | this.getPolarAngle = function () { 90 | 91 | return spherical.phi; 92 | 93 | }; 94 | 95 | this.getAzimuthalAngle = function () { 96 | 97 | return spherical.theta; 98 | 99 | }; 100 | 101 | this.saveState = function () { 102 | 103 | scope.target0.copy( scope.target ); 104 | scope.position0.copy( scope.object.position ); 105 | scope.zoom0 = scope.object.zoom; 106 | 107 | }; 108 | 109 | this.reset = function () { 110 | 111 | scope.target.copy( scope.target0 ); 112 | scope.object.position.copy( scope.position0 ); 113 | scope.object.zoom = scope.zoom0; 114 | 115 | scope.object.updateProjectionMatrix(); 116 | scope.dispatchEvent( changeEvent ); 117 | 118 | scope.update(); 119 | 120 | state = STATE.NONE; 121 | 122 | }; 123 | 124 | // this method is exposed, but perhaps it would be better if we can make it private... 125 | this.update = function () { 126 | 127 | var offset = new THREE.Vector3(); 128 | 129 | // so camera.up is the orbit axis 130 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 131 | var quatInverse = quat.clone().inverse(); 132 | 133 | var lastPosition = new THREE.Vector3(); 134 | var lastQuaternion = new THREE.Quaternion(); 135 | 136 | return function update() { 137 | 138 | var position = scope.object.position; 139 | 140 | offset.copy( position ).sub( scope.target ); 141 | 142 | // rotate offset to "y-axis-is-up" space 143 | offset.applyQuaternion( quat ); 144 | 145 | // angle from z-axis around y-axis 146 | spherical.setFromVector3( offset ); 147 | 148 | if ( scope.autoRotate && state === STATE.NONE ) { 149 | 150 | rotateLeft( getAutoRotationAngle() ); 151 | 152 | } 153 | 154 | spherical.theta += sphericalDelta.theta; 155 | spherical.phi += sphericalDelta.phi; 156 | 157 | // restrict theta to be between desired limits 158 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 159 | 160 | // restrict phi to be between desired limits 161 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 162 | 163 | spherical.makeSafe(); 164 | 165 | 166 | spherical.radius *= scale; 167 | 168 | // restrict radius to be between desired limits 169 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 170 | 171 | // move target to panned location 172 | scope.target.add( panOffset ); 173 | 174 | offset.setFromSpherical( spherical ); 175 | 176 | // rotate offset back to "camera-up-vector-is-up" space 177 | offset.applyQuaternion( quatInverse ); 178 | 179 | position.copy( scope.target ).add( offset ); 180 | 181 | scope.object.lookAt( scope.target ); 182 | 183 | if ( scope.enableDamping === true ) { 184 | 185 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 186 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 187 | 188 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 189 | 190 | } else { 191 | 192 | sphericalDelta.set( 0, 0, 0 ); 193 | 194 | panOffset.set( 0, 0, 0 ); 195 | 196 | } 197 | 198 | scale = 1; 199 | 200 | // update condition is: 201 | // min(camera displacement, camera rotation in radians)^2 > EPS 202 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 203 | 204 | if ( zoomChanged || 205 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 206 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 207 | 208 | scope.dispatchEvent( changeEvent ); 209 | 210 | lastPosition.copy( scope.object.position ); 211 | lastQuaternion.copy( scope.object.quaternion ); 212 | zoomChanged = false; 213 | 214 | return true; 215 | 216 | } 217 | 218 | return false; 219 | 220 | }; 221 | 222 | }(); 223 | 224 | this.dispose = function () { 225 | 226 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 227 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 228 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 229 | 230 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 231 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 232 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 233 | 234 | document.removeEventListener( 'mousemove', onMouseMove, false ); 235 | document.removeEventListener( 'mouseup', onMouseUp, false ); 236 | 237 | window.removeEventListener( 'keydown', onKeyDown, false ); 238 | 239 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 240 | 241 | }; 242 | 243 | // 244 | // internals 245 | // 246 | 247 | var scope = this; 248 | 249 | var changeEvent = { type: 'change' }; 250 | var startEvent = { type: 'start' }; 251 | var endEvent = { type: 'end' }; 252 | 253 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 }; 254 | 255 | var state = STATE.NONE; 256 | 257 | var EPS = 0.000001; 258 | 259 | // current position in spherical coordinates 260 | var spherical = new THREE.Spherical(); 261 | var sphericalDelta = new THREE.Spherical(); 262 | 263 | var scale = 1; 264 | var panOffset = new THREE.Vector3(); 265 | var zoomChanged = false; 266 | 267 | var rotateStart = new THREE.Vector2(); 268 | var rotateEnd = new THREE.Vector2(); 269 | var rotateDelta = new THREE.Vector2(); 270 | 271 | var panStart = new THREE.Vector2(); 272 | var panEnd = new THREE.Vector2(); 273 | var panDelta = new THREE.Vector2(); 274 | 275 | var dollyStart = new THREE.Vector2(); 276 | var dollyEnd = new THREE.Vector2(); 277 | var dollyDelta = new THREE.Vector2(); 278 | 279 | function getAutoRotationAngle() { 280 | 281 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 282 | 283 | } 284 | 285 | function getZoomScale() { 286 | 287 | return Math.pow( 0.95, scope.zoomSpeed ); 288 | 289 | } 290 | 291 | function rotateLeft( angle ) { 292 | 293 | sphericalDelta.theta -= angle; 294 | 295 | } 296 | 297 | function rotateUp( angle ) { 298 | 299 | sphericalDelta.phi -= angle; 300 | 301 | } 302 | 303 | var panLeft = function () { 304 | 305 | var v = new THREE.Vector3(); 306 | 307 | return function panLeft( distance, objectMatrix ) { 308 | 309 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 310 | v.multiplyScalar( - distance ); 311 | 312 | panOffset.add( v ); 313 | 314 | }; 315 | 316 | }(); 317 | 318 | var panUp = function () { 319 | 320 | var v = new THREE.Vector3(); 321 | 322 | return function panUp( distance, objectMatrix ) { 323 | 324 | if ( scope.screenSpacePanning === true ) { 325 | 326 | v.setFromMatrixColumn( objectMatrix, 1 ); 327 | 328 | } else { 329 | 330 | v.setFromMatrixColumn( objectMatrix, 0 ); 331 | v.crossVectors( scope.object.up, v ); 332 | 333 | } 334 | 335 | v.multiplyScalar( distance ); 336 | 337 | panOffset.add( v ); 338 | 339 | }; 340 | 341 | }(); 342 | 343 | // deltaX and deltaY are in pixels; right and down are positive 344 | var pan = function () { 345 | 346 | var offset = new THREE.Vector3(); 347 | 348 | return function pan( deltaX, deltaY ) { 349 | 350 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 351 | 352 | if ( scope.object.isPerspectiveCamera ) { 353 | 354 | // perspective 355 | var position = scope.object.position; 356 | offset.copy( position ).sub( scope.target ); 357 | var targetDistance = offset.length(); 358 | 359 | // half of the fov is center to top of screen 360 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 361 | 362 | // we use only clientHeight here so aspect ratio does not distort speed 363 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 364 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 365 | 366 | } else if ( scope.object.isOrthographicCamera ) { 367 | 368 | // orthographic 369 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 370 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 371 | 372 | } else { 373 | 374 | // camera neither orthographic nor perspective 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 376 | scope.enablePan = false; 377 | 378 | } 379 | 380 | }; 381 | 382 | }(); 383 | 384 | function dollyIn( dollyScale ) { 385 | 386 | if ( scope.object.isPerspectiveCamera ) { 387 | 388 | scale /= dollyScale; 389 | 390 | } else if ( scope.object.isOrthographicCamera ) { 391 | 392 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 393 | scope.object.updateProjectionMatrix(); 394 | zoomChanged = true; 395 | 396 | } else { 397 | 398 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 399 | scope.enableZoom = false; 400 | 401 | } 402 | 403 | } 404 | 405 | function dollyOut( dollyScale ) { 406 | 407 | if ( scope.object.isPerspectiveCamera ) { 408 | 409 | scale *= dollyScale; 410 | 411 | } else if ( scope.object.isOrthographicCamera ) { 412 | 413 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 414 | scope.object.updateProjectionMatrix(); 415 | zoomChanged = true; 416 | 417 | } else { 418 | 419 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 420 | scope.enableZoom = false; 421 | 422 | } 423 | 424 | } 425 | 426 | // 427 | // event callbacks - update the object state 428 | // 429 | 430 | function handleMouseDownRotate( event ) { 431 | 432 | //console.log( 'handleMouseDownRotate' ); 433 | 434 | rotateStart.set( event.clientX, event.clientY ); 435 | 436 | } 437 | 438 | function handleMouseDownDolly( event ) { 439 | 440 | //console.log( 'handleMouseDownDolly' ); 441 | 442 | dollyStart.set( event.clientX, event.clientY ); 443 | 444 | } 445 | 446 | function handleMouseDownPan( event ) { 447 | 448 | //console.log( 'handleMouseDownPan' ); 449 | 450 | panStart.set( event.clientX, event.clientY ); 451 | 452 | } 453 | 454 | function handleMouseMoveRotate( event ) { 455 | 456 | //console.log( 'handleMouseMoveRotate' ); 457 | 458 | rotateEnd.set( event.clientX, event.clientY ); 459 | 460 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 461 | 462 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 463 | 464 | // rotating across whole screen goes 360 degrees around 465 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth ); 466 | 467 | // rotating up and down along whole screen attempts to go 360, but limited to 180 468 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 469 | 470 | rotateStart.copy( rotateEnd ); 471 | 472 | scope.update(); 473 | 474 | } 475 | 476 | function handleMouseMoveDolly( event ) { 477 | 478 | //console.log( 'handleMouseMoveDolly' ); 479 | 480 | dollyEnd.set( event.clientX, event.clientY ); 481 | 482 | dollyDelta.subVectors( dollyEnd, dollyStart ); 483 | 484 | if ( dollyDelta.y > 0 ) { 485 | 486 | dollyIn( getZoomScale() ); 487 | 488 | } else if ( dollyDelta.y < 0 ) { 489 | 490 | dollyOut( getZoomScale() ); 491 | 492 | } 493 | 494 | dollyStart.copy( dollyEnd ); 495 | 496 | scope.update(); 497 | 498 | } 499 | 500 | function handleMouseMovePan( event ) { 501 | 502 | //console.log( 'handleMouseMovePan' ); 503 | 504 | panEnd.set( event.clientX, event.clientY ); 505 | 506 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 507 | 508 | pan( panDelta.x, panDelta.y ); 509 | 510 | panStart.copy( panEnd ); 511 | 512 | scope.update(); 513 | 514 | } 515 | 516 | function handleMouseUp( event ) { 517 | 518 | // console.log( 'handleMouseUp' ); 519 | 520 | } 521 | 522 | function handleMouseWheel( event ) { 523 | 524 | // console.log( 'handleMouseWheel' ); 525 | 526 | if ( event.deltaY < 0 ) { 527 | 528 | dollyOut( getZoomScale() ); 529 | 530 | } else if ( event.deltaY > 0 ) { 531 | 532 | dollyIn( getZoomScale() ); 533 | 534 | } 535 | 536 | scope.update(); 537 | 538 | } 539 | 540 | function handleKeyDown( event ) { 541 | 542 | //console.log( 'handleKeyDown' ); 543 | 544 | switch ( event.keyCode ) { 545 | 546 | case scope.keys.UP: 547 | pan( 0, scope.keyPanSpeed ); 548 | scope.update(); 549 | break; 550 | 551 | case scope.keys.BOTTOM: 552 | pan( 0, - scope.keyPanSpeed ); 553 | scope.update(); 554 | break; 555 | 556 | case scope.keys.LEFT: 557 | pan( scope.keyPanSpeed, 0 ); 558 | scope.update(); 559 | break; 560 | 561 | case scope.keys.RIGHT: 562 | pan( - scope.keyPanSpeed, 0 ); 563 | scope.update(); 564 | break; 565 | 566 | } 567 | 568 | } 569 | 570 | function handleTouchStartRotate( event ) { 571 | 572 | //console.log( 'handleTouchStartRotate' ); 573 | 574 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 575 | 576 | } 577 | 578 | function handleTouchStartDollyPan( event ) { 579 | 580 | //console.log( 'handleTouchStartDollyPan' ); 581 | 582 | if ( scope.enableZoom ) { 583 | 584 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 585 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 586 | 587 | var distance = Math.sqrt( dx * dx + dy * dy ); 588 | 589 | dollyStart.set( 0, distance ); 590 | 591 | } 592 | 593 | if ( scope.enablePan ) { 594 | 595 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 596 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 597 | 598 | panStart.set( x, y ); 599 | 600 | } 601 | 602 | } 603 | 604 | function handleTouchMoveRotate( event ) { 605 | 606 | //console.log( 'handleTouchMoveRotate' ); 607 | 608 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 609 | 610 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 611 | 612 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 613 | 614 | // rotating across whole screen goes 360 degrees around 615 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth ); 616 | 617 | // rotating up and down along whole screen attempts to go 360, but limited to 180 618 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 619 | 620 | rotateStart.copy( rotateEnd ); 621 | 622 | scope.update(); 623 | 624 | } 625 | 626 | function handleTouchMoveDollyPan( event ) { 627 | 628 | //console.log( 'handleTouchMoveDollyPan' ); 629 | 630 | if ( scope.enableZoom ) { 631 | 632 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 633 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 634 | 635 | var distance = Math.sqrt( dx * dx + dy * dy ); 636 | 637 | dollyEnd.set( 0, distance ); 638 | 639 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 640 | 641 | dollyIn( dollyDelta.y ); 642 | 643 | dollyStart.copy( dollyEnd ); 644 | 645 | } 646 | 647 | if ( scope.enablePan ) { 648 | 649 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 650 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 651 | 652 | panEnd.set( x, y ); 653 | 654 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 655 | 656 | pan( panDelta.x, panDelta.y ); 657 | 658 | panStart.copy( panEnd ); 659 | 660 | } 661 | 662 | scope.update(); 663 | 664 | } 665 | 666 | function handleTouchEnd( event ) { 667 | 668 | //console.log( 'handleTouchEnd' ); 669 | 670 | } 671 | 672 | // 673 | // event handlers - FSM: listen for events and reset state 674 | // 675 | 676 | function onMouseDown( event ) { 677 | 678 | if ( scope.enabled === false ) return; 679 | 680 | event.preventDefault(); 681 | 682 | switch ( event.button ) { 683 | 684 | case scope.mouseButtons.ORBIT: 685 | 686 | if ( scope.enableRotate === false ) return; 687 | 688 | handleMouseDownRotate( event ); 689 | 690 | state = STATE.ROTATE; 691 | 692 | break; 693 | 694 | case scope.mouseButtons.ZOOM: 695 | 696 | if ( scope.enableZoom === false ) return; 697 | 698 | handleMouseDownDolly( event ); 699 | 700 | state = STATE.DOLLY; 701 | 702 | break; 703 | 704 | case scope.mouseButtons.PAN: 705 | 706 | if ( scope.enablePan === false ) return; 707 | 708 | handleMouseDownPan( event ); 709 | 710 | state = STATE.PAN; 711 | 712 | break; 713 | 714 | } 715 | 716 | if ( state !== STATE.NONE ) { 717 | 718 | document.addEventListener( 'mousemove', onMouseMove, false ); 719 | document.addEventListener( 'mouseup', onMouseUp, false ); 720 | 721 | scope.dispatchEvent( startEvent ); 722 | 723 | } 724 | 725 | } 726 | 727 | function onMouseMove( event ) { 728 | 729 | if ( scope.enabled === false ) return; 730 | 731 | event.preventDefault(); 732 | 733 | switch ( state ) { 734 | 735 | case STATE.ROTATE: 736 | 737 | if ( scope.enableRotate === false ) return; 738 | 739 | handleMouseMoveRotate( event ); 740 | 741 | break; 742 | 743 | case STATE.DOLLY: 744 | 745 | if ( scope.enableZoom === false ) return; 746 | 747 | handleMouseMoveDolly( event ); 748 | 749 | break; 750 | 751 | case STATE.PAN: 752 | 753 | if ( scope.enablePan === false ) return; 754 | 755 | handleMouseMovePan( event ); 756 | 757 | break; 758 | 759 | } 760 | 761 | } 762 | 763 | function onMouseUp( event ) { 764 | 765 | if ( scope.enabled === false ) return; 766 | 767 | handleMouseUp( event ); 768 | 769 | document.removeEventListener( 'mousemove', onMouseMove, false ); 770 | document.removeEventListener( 'mouseup', onMouseUp, false ); 771 | 772 | scope.dispatchEvent( endEvent ); 773 | 774 | state = STATE.NONE; 775 | 776 | } 777 | 778 | function onMouseWheel( event ) { 779 | 780 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 781 | 782 | event.preventDefault(); 783 | event.stopPropagation(); 784 | 785 | scope.dispatchEvent( startEvent ); 786 | 787 | handleMouseWheel( event ); 788 | 789 | scope.dispatchEvent( endEvent ); 790 | 791 | } 792 | 793 | function onKeyDown( event ) { 794 | 795 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 796 | 797 | handleKeyDown( event ); 798 | 799 | } 800 | 801 | function onTouchStart( event ) { 802 | 803 | if ( scope.enabled === false ) return; 804 | 805 | event.preventDefault(); 806 | 807 | switch ( event.touches.length ) { 808 | 809 | case 1: // one-fingered touch: rotate 810 | 811 | if ( scope.enableRotate === false ) return; 812 | 813 | handleTouchStartRotate( event ); 814 | 815 | state = STATE.TOUCH_ROTATE; 816 | 817 | break; 818 | 819 | case 2: // two-fingered touch: dolly-pan 820 | 821 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 822 | 823 | handleTouchStartDollyPan( event ); 824 | 825 | state = STATE.TOUCH_DOLLY_PAN; 826 | 827 | break; 828 | 829 | default: 830 | 831 | state = STATE.NONE; 832 | 833 | } 834 | 835 | if ( state !== STATE.NONE ) { 836 | 837 | scope.dispatchEvent( startEvent ); 838 | 839 | } 840 | 841 | } 842 | 843 | function onTouchMove( event ) { 844 | 845 | if ( scope.enabled === false ) return; 846 | 847 | event.preventDefault(); 848 | event.stopPropagation(); 849 | 850 | switch ( event.touches.length ) { 851 | 852 | case 1: // one-fingered touch: rotate 853 | 854 | if ( scope.enableRotate === false ) return; 855 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed? 856 | 857 | handleTouchMoveRotate( event ); 858 | 859 | break; 860 | 861 | case 2: // two-fingered touch: dolly-pan 862 | 863 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 864 | if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed? 865 | 866 | handleTouchMoveDollyPan( event ); 867 | 868 | break; 869 | 870 | default: 871 | 872 | state = STATE.NONE; 873 | 874 | } 875 | 876 | } 877 | 878 | function onTouchEnd( event ) { 879 | 880 | if ( scope.enabled === false ) return; 881 | 882 | handleTouchEnd( event ); 883 | 884 | scope.dispatchEvent( endEvent ); 885 | 886 | state = STATE.NONE; 887 | 888 | } 889 | 890 | function onContextMenu( event ) { 891 | 892 | if ( scope.enabled === false ) return; 893 | 894 | event.preventDefault(); 895 | 896 | } 897 | 898 | // 899 | 900 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 901 | 902 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 903 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 904 | 905 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 906 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 907 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 908 | 909 | window.addEventListener( 'keydown', onKeyDown, false ); 910 | 911 | // force an update at start 912 | 913 | this.update(); 914 | 915 | }; 916 | 917 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 918 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 919 | 920 | Object.defineProperties( THREE.OrbitControls.prototype, { 921 | 922 | center: { 923 | 924 | get: function () { 925 | 926 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 927 | return this.target; 928 | 929 | } 930 | 931 | }, 932 | 933 | // backward compatibility 934 | 935 | noZoom: { 936 | 937 | get: function () { 938 | 939 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 940 | return ! this.enableZoom; 941 | 942 | }, 943 | 944 | set: function ( value ) { 945 | 946 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 947 | this.enableZoom = ! value; 948 | 949 | } 950 | 951 | }, 952 | 953 | noRotate: { 954 | 955 | get: function () { 956 | 957 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 958 | return ! this.enableRotate; 959 | 960 | }, 961 | 962 | set: function ( value ) { 963 | 964 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 965 | this.enableRotate = ! value; 966 | 967 | } 968 | 969 | }, 970 | 971 | noPan: { 972 | 973 | get: function () { 974 | 975 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 976 | return ! this.enablePan; 977 | 978 | }, 979 | 980 | set: function ( value ) { 981 | 982 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 983 | this.enablePan = ! value; 984 | 985 | } 986 | 987 | }, 988 | 989 | noKeys: { 990 | 991 | get: function () { 992 | 993 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 994 | return ! this.enableKeys; 995 | 996 | }, 997 | 998 | set: function ( value ) { 999 | 1000 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1001 | this.enableKeys = ! value; 1002 | 1003 | } 1004 | 1005 | }, 1006 | 1007 | staticMoving: { 1008 | 1009 | get: function () { 1010 | 1011 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1012 | return ! this.enableDamping; 1013 | 1014 | }, 1015 | 1016 | set: function ( value ) { 1017 | 1018 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1019 | this.enableDamping = ! value; 1020 | 1021 | } 1022 | 1023 | }, 1024 | 1025 | dynamicDampingFactor: { 1026 | 1027 | get: function () { 1028 | 1029 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1030 | return this.dampingFactor; 1031 | 1032 | }, 1033 | 1034 | set: function ( value ) { 1035 | 1036 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1037 | this.dampingFactor = value; 1038 | 1039 | } 1040 | 1041 | } 1042 | 1043 | } ); 1044 | -------------------------------------------------------------------------------- /three.js/GLTFLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Rich Tibbett / https://github.com/richtr 3 | * @author mrdoob / http://mrdoob.com/ 4 | * @author Tony Parisi / http://www.tonyparisi.com/ 5 | * @author Takahiro / https://github.com/takahirox 6 | * @author Don McCurdy / https://www.donmccurdy.com 7 | */ 8 | 9 | THREE.GLTFLoader = ( function () { 10 | 11 | function GLTFLoader( manager ) { 12 | 13 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 14 | this.dracoLoader = null; 15 | 16 | } 17 | 18 | GLTFLoader.prototype = { 19 | 20 | constructor: GLTFLoader, 21 | 22 | crossOrigin: 'Anonymous', 23 | 24 | load: function ( url, onLoad, onProgress, onError ) { 25 | 26 | var scope = this; 27 | 28 | var path = this.path !== undefined ? this.path : THREE.LoaderUtils.extractUrlBase( url ); 29 | 30 | var loader = new THREE.FileLoader( scope.manager ); 31 | 32 | loader.setResponseType( 'arraybuffer' ); 33 | 34 | loader.load( url, function ( data ) { 35 | 36 | try { 37 | 38 | scope.parse( data, path, onLoad, onError ); 39 | 40 | } catch ( e ) { 41 | 42 | if ( onError !== undefined ) { 43 | 44 | onError( e ); 45 | 46 | } else { 47 | 48 | throw e; 49 | 50 | } 51 | 52 | } 53 | 54 | }, onProgress, onError ); 55 | 56 | }, 57 | 58 | setCrossOrigin: function ( value ) { 59 | 60 | this.crossOrigin = value; 61 | return this; 62 | 63 | }, 64 | 65 | setPath: function ( value ) { 66 | 67 | this.path = value; 68 | return this; 69 | 70 | }, 71 | 72 | setDRACOLoader: function ( dracoLoader ) { 73 | 74 | this.dracoLoader = dracoLoader; 75 | return this; 76 | 77 | }, 78 | 79 | parse: function ( data, path, onLoad, onError ) { 80 | 81 | var content; 82 | var extensions = {}; 83 | 84 | if ( typeof data === 'string' ) { 85 | 86 | content = data; 87 | 88 | } else { 89 | 90 | var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); 91 | 92 | if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { 93 | 94 | try { 95 | 96 | extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); 97 | 98 | } catch ( error ) { 99 | 100 | if ( onError ) onError( error ); 101 | return; 102 | 103 | } 104 | 105 | content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; 106 | 107 | } else { 108 | 109 | content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) ); 110 | 111 | } 112 | 113 | } 114 | 115 | var json = JSON.parse( content ); 116 | 117 | if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { 118 | 119 | if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.' ) ); 120 | return; 121 | 122 | } 123 | 124 | if ( json.extensionsUsed ) { 125 | 126 | if ( json.extensionsUsed.indexOf( EXTENSIONS.KHR_LIGHTS ) >= 0 ) { 127 | 128 | extensions[ EXTENSIONS.KHR_LIGHTS ] = new GLTFLightsExtension( json ); 129 | 130 | } 131 | 132 | if ( json.extensionsUsed.indexOf( EXTENSIONS.KHR_MATERIALS_UNLIT ) >= 0 ) { 133 | 134 | extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] = new GLTFMaterialsUnlitExtension( json ); 135 | 136 | } 137 | 138 | if ( json.extensionsUsed.indexOf( EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ) >= 0 ) { 139 | 140 | extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); 141 | 142 | } 143 | 144 | if ( json.extensionsUsed.indexOf( EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ) >= 0 ) { 145 | 146 | extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] = new GLTFDracoMeshCompressionExtension( this.dracoLoader ); 147 | 148 | } 149 | 150 | if ( json.extensionsUsed.indexOf( EXTENSIONS.MSFT_TEXTURE_DDS ) >= 0 ) { 151 | 152 | extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] = new GLTFTextureDDSExtension(); 153 | 154 | } 155 | 156 | } 157 | 158 | var parser = new GLTFParser( json, extensions, { 159 | 160 | path: path || this.path || '', 161 | crossOrigin: this.crossOrigin, 162 | manager: this.manager 163 | 164 | } ); 165 | 166 | parser.parse( function ( scene, scenes, cameras, animations, asset ) { 167 | 168 | var glTF = { 169 | scene: scene, 170 | scenes: scenes, 171 | cameras: cameras, 172 | animations: animations, 173 | asset: asset 174 | }; 175 | 176 | onLoad( glTF ); 177 | 178 | }, onError ); 179 | 180 | } 181 | 182 | }; 183 | 184 | /* GLTFREGISTRY */ 185 | 186 | function GLTFRegistry() { 187 | 188 | var objects = {}; 189 | 190 | return { 191 | 192 | get: function ( key ) { 193 | 194 | return objects[ key ]; 195 | 196 | }, 197 | 198 | add: function ( key, object ) { 199 | 200 | objects[ key ] = object; 201 | 202 | }, 203 | 204 | remove: function ( key ) { 205 | 206 | delete objects[ key ]; 207 | 208 | }, 209 | 210 | removeAll: function () { 211 | 212 | objects = {}; 213 | 214 | } 215 | 216 | }; 217 | 218 | } 219 | 220 | /*********************************/ 221 | /********** EXTENSIONS ***********/ 222 | /*********************************/ 223 | 224 | var EXTENSIONS = { 225 | KHR_BINARY_GLTF: 'KHR_binary_glTF', 226 | KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', 227 | KHR_LIGHTS: 'KHR_lights', 228 | KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', 229 | KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', 230 | MSFT_TEXTURE_DDS: 'MSFT_texture_dds' 231 | }; 232 | 233 | /** 234 | * DDS Texture Extension 235 | * 236 | * Specification: 237 | * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds 238 | * 239 | */ 240 | function GLTFTextureDDSExtension() { 241 | 242 | if ( ! THREE.DDSLoader ) { 243 | 244 | throw new Error( 'THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader' ); 245 | 246 | } 247 | 248 | this.name = EXTENSIONS.MSFT_TEXTURE_DDS; 249 | this.ddsLoader = new THREE.DDSLoader(); 250 | 251 | } 252 | 253 | /** 254 | * Lights Extension 255 | * 256 | * Specification: PENDING 257 | */ 258 | function GLTFLightsExtension( json ) { 259 | 260 | this.name = EXTENSIONS.KHR_LIGHTS; 261 | 262 | this.lights = {}; 263 | 264 | var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS ] ) || {}; 265 | var lights = extension.lights || {}; 266 | 267 | for ( var lightId in lights ) { 268 | 269 | var light = lights[ lightId ]; 270 | var lightNode; 271 | 272 | var color = new THREE.Color().fromArray( light.color ); 273 | 274 | switch ( light.type ) { 275 | 276 | case 'directional': 277 | lightNode = new THREE.DirectionalLight( color ); 278 | lightNode.target.position.set( 0, 0, 1 ); 279 | lightNode.add( lightNode.target ); 280 | break; 281 | 282 | case 'point': 283 | lightNode = new THREE.PointLight( color ); 284 | break; 285 | 286 | case 'spot': 287 | lightNode = new THREE.SpotLight( color ); 288 | // Handle spotlight properties. 289 | light.spot = light.spot || {}; 290 | light.spot.innerConeAngle = light.spot.innerConeAngle !== undefined ? light.spot.innerConeAngle : 0; 291 | light.spot.outerConeAngle = light.spot.outerConeAngle !== undefined ? light.spot.outerConeAngle : Math.PI / 4.0; 292 | lightNode.angle = light.spot.outerConeAngle; 293 | lightNode.penumbra = 1.0 - light.spot.innerConeAngle / light.spot.outerConeAngle; 294 | lightNode.target.position.set( 0, 0, 1 ); 295 | lightNode.add( lightNode.target ); 296 | break; 297 | 298 | case 'ambient': 299 | lightNode = new THREE.AmbientLight( color ); 300 | break; 301 | 302 | } 303 | 304 | if ( lightNode ) { 305 | 306 | lightNode.decay = 2; 307 | 308 | if ( light.intensity !== undefined ) { 309 | 310 | lightNode.intensity = light.intensity; 311 | 312 | } 313 | 314 | lightNode.name = light.name || ( 'light_' + lightId ); 315 | this.lights[ lightId ] = lightNode; 316 | 317 | } 318 | 319 | } 320 | 321 | } 322 | 323 | /** 324 | * Unlit Materials Extension (pending) 325 | * 326 | * PR: https://github.com/KhronosGroup/glTF/pull/1163 327 | */ 328 | function GLTFMaterialsUnlitExtension( json ) { 329 | 330 | this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; 331 | 332 | } 333 | 334 | GLTFMaterialsUnlitExtension.prototype.getMaterialType = function ( material ) { 335 | 336 | return THREE.MeshBasicMaterial; 337 | 338 | }; 339 | 340 | GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, material, parser ) { 341 | 342 | var pending = []; 343 | 344 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); 345 | materialParams.opacity = 1.0; 346 | 347 | var metallicRoughness = material.pbrMetallicRoughness; 348 | 349 | if ( metallicRoughness ) { 350 | 351 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { 352 | 353 | var array = metallicRoughness.baseColorFactor; 354 | 355 | materialParams.color.fromArray( array ); 356 | materialParams.opacity = array[ 3 ]; 357 | 358 | } 359 | 360 | if ( metallicRoughness.baseColorTexture !== undefined ) { 361 | 362 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture.index ) ); 363 | 364 | } 365 | 366 | } 367 | 368 | return Promise.all( pending ); 369 | 370 | }; 371 | 372 | /* BINARY EXTENSION */ 373 | 374 | var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF'; 375 | var BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; 376 | var BINARY_EXTENSION_HEADER_LENGTH = 12; 377 | var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; 378 | 379 | function GLTFBinaryExtension( data ) { 380 | 381 | this.name = EXTENSIONS.KHR_BINARY_GLTF; 382 | this.content = null; 383 | this.body = null; 384 | 385 | var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); 386 | 387 | this.header = { 388 | magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ), 389 | version: headerView.getUint32( 4, true ), 390 | length: headerView.getUint32( 8, true ) 391 | }; 392 | 393 | if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { 394 | 395 | throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); 396 | 397 | } else if ( this.header.version < 2.0 ) { 398 | 399 | throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.' ); 400 | 401 | } 402 | 403 | var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); 404 | var chunkIndex = 0; 405 | 406 | while ( chunkIndex < chunkView.byteLength ) { 407 | 408 | var chunkLength = chunkView.getUint32( chunkIndex, true ); 409 | chunkIndex += 4; 410 | 411 | var chunkType = chunkView.getUint32( chunkIndex, true ); 412 | chunkIndex += 4; 413 | 414 | if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { 415 | 416 | var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); 417 | this.content = THREE.LoaderUtils.decodeText( contentArray ); 418 | 419 | } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { 420 | 421 | var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; 422 | this.body = data.slice( byteOffset, byteOffset + chunkLength ); 423 | 424 | } 425 | 426 | // Clients must ignore chunks with unknown types. 427 | 428 | chunkIndex += chunkLength; 429 | 430 | } 431 | 432 | if ( this.content === null ) { 433 | 434 | throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); 435 | 436 | } 437 | 438 | } 439 | 440 | /** 441 | * DRACO Mesh Compression Extension 442 | * 443 | * Specification: https://github.com/KhronosGroup/glTF/pull/874 444 | */ 445 | function GLTFDracoMeshCompressionExtension ( dracoLoader ) { 446 | 447 | if ( ! dracoLoader ) { 448 | 449 | throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); 450 | 451 | } 452 | 453 | this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; 454 | this.dracoLoader = dracoLoader; 455 | 456 | } 457 | 458 | GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { 459 | 460 | var dracoLoader = this.dracoLoader; 461 | var bufferViewIndex = primitive.extensions[ this.name ].bufferView; 462 | var gltfAttributeMap = primitive.extensions[ this.name ].attributes; 463 | var threeAttributeMap = {}; 464 | 465 | for ( var attributeName in gltfAttributeMap ) { 466 | 467 | if ( !( attributeName in ATTRIBUTES ) ) continue; 468 | 469 | threeAttributeMap[ ATTRIBUTES[ attributeName ] ] = gltfAttributeMap[ attributeName ]; 470 | 471 | } 472 | 473 | return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { 474 | 475 | return new Promise( function ( resolve ) { 476 | 477 | dracoLoader.decodeDracoFile( bufferView, resolve, threeAttributeMap ); 478 | 479 | } ); 480 | 481 | } ); 482 | 483 | }; 484 | 485 | /** 486 | * Specular-Glossiness Extension 487 | * 488 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness 489 | */ 490 | function GLTFMaterialsPbrSpecularGlossinessExtension() { 491 | 492 | return { 493 | 494 | name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, 495 | 496 | specularGlossinessParams: [ 497 | 'color', 498 | 'map', 499 | 'lightMap', 500 | 'lightMapIntensity', 501 | 'aoMap', 502 | 'aoMapIntensity', 503 | 'emissive', 504 | 'emissiveIntensity', 505 | 'emissiveMap', 506 | 'bumpMap', 507 | 'bumpScale', 508 | 'normalMap', 509 | 'displacementMap', 510 | 'displacementScale', 511 | 'displacementBias', 512 | 'specularMap', 513 | 'specular', 514 | 'glossinessMap', 515 | 'glossiness', 516 | 'alphaMap', 517 | 'envMap', 518 | 'envMapIntensity', 519 | 'refractionRatio', 520 | ], 521 | 522 | getMaterialType: function () { 523 | 524 | return THREE.ShaderMaterial; 525 | 526 | }, 527 | 528 | extendParams: function ( params, material, parser ) { 529 | 530 | var pbrSpecularGlossiness = material.extensions[ this.name ]; 531 | 532 | var shader = THREE.ShaderLib[ 'standard' ]; 533 | 534 | var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 535 | 536 | var specularMapParsFragmentChunk = [ 537 | '#ifdef USE_SPECULARMAP', 538 | ' uniform sampler2D specularMap;', 539 | '#endif' 540 | ].join( '\n' ); 541 | 542 | var glossinessMapParsFragmentChunk = [ 543 | '#ifdef USE_GLOSSINESSMAP', 544 | ' uniform sampler2D glossinessMap;', 545 | '#endif' 546 | ].join( '\n' ); 547 | 548 | var specularMapFragmentChunk = [ 549 | 'vec3 specularFactor = specular;', 550 | '#ifdef USE_SPECULARMAP', 551 | ' vec4 texelSpecular = texture2D( specularMap, vUv );', 552 | ' texelSpecular = sRGBToLinear( texelSpecular );', 553 | ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', 554 | ' specularFactor *= texelSpecular.rgb;', 555 | '#endif' 556 | ].join( '\n' ); 557 | 558 | var glossinessMapFragmentChunk = [ 559 | 'float glossinessFactor = glossiness;', 560 | '#ifdef USE_GLOSSINESSMAP', 561 | ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', 562 | ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', 563 | ' glossinessFactor *= texelGlossiness.a;', 564 | '#endif' 565 | ].join( '\n' ); 566 | 567 | var lightPhysicalFragmentChunk = [ 568 | 'PhysicalMaterial material;', 569 | 'material.diffuseColor = diffuseColor.rgb;', 570 | 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', 571 | 'material.specularColor = specularFactor.rgb;', 572 | ].join( '\n' ); 573 | 574 | var fragmentShader = shader.fragmentShader 575 | .replace( '#include ', '' ) 576 | .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) 577 | .replace( 'uniform float metalness;', 'uniform float glossiness;' ) 578 | .replace( '#include ', specularMapParsFragmentChunk ) 579 | .replace( '#include ', glossinessMapParsFragmentChunk ) 580 | .replace( '#include ', specularMapFragmentChunk ) 581 | .replace( '#include ', glossinessMapFragmentChunk ) 582 | .replace( '#include ', lightPhysicalFragmentChunk ); 583 | 584 | delete uniforms.roughness; 585 | delete uniforms.metalness; 586 | delete uniforms.roughnessMap; 587 | delete uniforms.metalnessMap; 588 | 589 | uniforms.specular = { value: new THREE.Color().setHex( 0x111111 ) }; 590 | uniforms.glossiness = { value: 0.5 }; 591 | uniforms.specularMap = { value: null }; 592 | uniforms.glossinessMap = { value: null }; 593 | 594 | params.vertexShader = shader.vertexShader; 595 | params.fragmentShader = fragmentShader; 596 | params.uniforms = uniforms; 597 | params.defines = { 'STANDARD': '' }; 598 | 599 | params.color = new THREE.Color( 1.0, 1.0, 1.0 ); 600 | params.opacity = 1.0; 601 | 602 | var pending = []; 603 | 604 | if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { 605 | 606 | var array = pbrSpecularGlossiness.diffuseFactor; 607 | 608 | params.color.fromArray( array ); 609 | params.opacity = array[ 3 ]; 610 | 611 | } 612 | 613 | if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { 614 | 615 | pending.push( parser.assignTexture( params, 'map', pbrSpecularGlossiness.diffuseTexture.index ) ); 616 | 617 | } 618 | 619 | params.emissive = new THREE.Color( 0.0, 0.0, 0.0 ); 620 | params.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; 621 | params.specular = new THREE.Color( 1.0, 1.0, 1.0 ); 622 | 623 | if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { 624 | 625 | params.specular.fromArray( pbrSpecularGlossiness.specularFactor ); 626 | 627 | } 628 | 629 | if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { 630 | 631 | var specGlossIndex = pbrSpecularGlossiness.specularGlossinessTexture.index; 632 | pending.push( parser.assignTexture( params, 'glossinessMap', specGlossIndex ) ); 633 | pending.push( parser.assignTexture( params, 'specularMap', specGlossIndex ) ); 634 | 635 | } 636 | 637 | return Promise.all( pending ); 638 | 639 | }, 640 | 641 | createMaterial: function ( params ) { 642 | 643 | // setup material properties based on MeshStandardMaterial for Specular-Glossiness 644 | 645 | var material = new THREE.ShaderMaterial( { 646 | defines: params.defines, 647 | vertexShader: params.vertexShader, 648 | fragmentShader: params.fragmentShader, 649 | uniforms: params.uniforms, 650 | fog: true, 651 | lights: true, 652 | opacity: params.opacity, 653 | transparent: params.transparent 654 | } ); 655 | 656 | material.isGLTFSpecularGlossinessMaterial = true; 657 | 658 | material.color = params.color; 659 | 660 | material.map = params.map === undefined ? null : params.map; 661 | 662 | material.lightMap = null; 663 | material.lightMapIntensity = 1.0; 664 | 665 | material.aoMap = params.aoMap === undefined ? null : params.aoMap; 666 | material.aoMapIntensity = 1.0; 667 | 668 | material.emissive = params.emissive; 669 | material.emissiveIntensity = 1.0; 670 | material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap; 671 | 672 | material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap; 673 | material.bumpScale = 1; 674 | 675 | material.normalMap = params.normalMap === undefined ? null : params.normalMap; 676 | if ( params.normalScale ) material.normalScale = params.normalScale; 677 | 678 | material.displacementMap = null; 679 | material.displacementScale = 1; 680 | material.displacementBias = 0; 681 | 682 | material.specularMap = params.specularMap === undefined ? null : params.specularMap; 683 | material.specular = params.specular; 684 | 685 | material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap; 686 | material.glossiness = params.glossiness; 687 | 688 | material.alphaMap = null; 689 | 690 | material.envMap = params.envMap === undefined ? null : params.envMap; 691 | material.envMapIntensity = 1.0; 692 | 693 | material.refractionRatio = 0.98; 694 | 695 | material.extensions.derivatives = true; 696 | 697 | return material; 698 | 699 | }, 700 | 701 | /** 702 | * Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can 703 | * copy only properties it knows about or inherits, and misses many properties that would 704 | * normally be defined by MeshStandardMaterial. 705 | * 706 | * This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of 707 | * loading a glTF model, but cloning later (e.g. by the user) would require these changes 708 | * AND also updating `.onBeforeRender` on the parent mesh. 709 | * 710 | * @param {THREE.ShaderMaterial} source 711 | * @return {THREE.ShaderMaterial} 712 | */ 713 | cloneMaterial: function ( source ) { 714 | 715 | var target = source.clone(); 716 | 717 | target.isGLTFSpecularGlossinessMaterial = true; 718 | 719 | var params = this.specularGlossinessParams; 720 | 721 | for ( var i = 0, il = params.length; i < il; i ++ ) { 722 | 723 | target[ params[ i ] ] = source[ params[ i ] ]; 724 | 725 | } 726 | 727 | return target; 728 | 729 | }, 730 | 731 | // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. 732 | refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) { 733 | 734 | if ( material.isGLTFSpecularGlossinessMaterial !== true ) { 735 | 736 | return; 737 | 738 | } 739 | 740 | var uniforms = material.uniforms; 741 | var defines = material.defines; 742 | 743 | uniforms.opacity.value = material.opacity; 744 | 745 | uniforms.diffuse.value.copy( material.color ); 746 | uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); 747 | 748 | uniforms.map.value = material.map; 749 | uniforms.specularMap.value = material.specularMap; 750 | uniforms.alphaMap.value = material.alphaMap; 751 | 752 | uniforms.lightMap.value = material.lightMap; 753 | uniforms.lightMapIntensity.value = material.lightMapIntensity; 754 | 755 | uniforms.aoMap.value = material.aoMap; 756 | uniforms.aoMapIntensity.value = material.aoMapIntensity; 757 | 758 | // uv repeat and offset setting priorities 759 | // 1. color map 760 | // 2. specular map 761 | // 3. normal map 762 | // 4. bump map 763 | // 5. alpha map 764 | // 6. emissive map 765 | 766 | var uvScaleMap; 767 | 768 | if ( material.map ) { 769 | 770 | uvScaleMap = material.map; 771 | 772 | } else if ( material.specularMap ) { 773 | 774 | uvScaleMap = material.specularMap; 775 | 776 | } else if ( material.displacementMap ) { 777 | 778 | uvScaleMap = material.displacementMap; 779 | 780 | } else if ( material.normalMap ) { 781 | 782 | uvScaleMap = material.normalMap; 783 | 784 | } else if ( material.bumpMap ) { 785 | 786 | uvScaleMap = material.bumpMap; 787 | 788 | } else if ( material.glossinessMap ) { 789 | 790 | uvScaleMap = material.glossinessMap; 791 | 792 | } else if ( material.alphaMap ) { 793 | 794 | uvScaleMap = material.alphaMap; 795 | 796 | } else if ( material.emissiveMap ) { 797 | 798 | uvScaleMap = material.emissiveMap; 799 | 800 | } 801 | 802 | if ( uvScaleMap !== undefined ) { 803 | 804 | // backwards compatibility 805 | if ( uvScaleMap.isWebGLRenderTarget ) { 806 | 807 | uvScaleMap = uvScaleMap.texture; 808 | 809 | } 810 | 811 | var offset; 812 | var repeat; 813 | 814 | if ( uvScaleMap.matrix !== undefined ) { 815 | 816 | // > r88. 817 | 818 | if ( uvScaleMap.matrixAutoUpdate === true ) { 819 | 820 | offset = uvScaleMap.offset; 821 | repeat = uvScaleMap.repeat; 822 | var rotation = uvScaleMap.rotation; 823 | var center = uvScaleMap.center; 824 | 825 | uvScaleMap.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y ); 826 | 827 | } 828 | 829 | uniforms.uvTransform.value.copy( uvScaleMap.matrix ); 830 | 831 | } else { 832 | 833 | // <= r87. Remove when reasonable. 834 | 835 | offset = uvScaleMap.offset; 836 | repeat = uvScaleMap.repeat; 837 | 838 | uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); 839 | 840 | } 841 | 842 | } 843 | 844 | uniforms.envMap.value = material.envMap; 845 | uniforms.envMapIntensity.value = material.envMapIntensity; 846 | uniforms.flipEnvMap.value = ( material.envMap && material.envMap.isCubeTexture ) ? - 1 : 1; 847 | 848 | uniforms.refractionRatio.value = material.refractionRatio; 849 | 850 | uniforms.specular.value.copy( material.specular ); 851 | uniforms.glossiness.value = material.glossiness; 852 | 853 | uniforms.glossinessMap.value = material.glossinessMap; 854 | 855 | uniforms.emissiveMap.value = material.emissiveMap; 856 | uniforms.bumpMap.value = material.bumpMap; 857 | uniforms.normalMap.value = material.normalMap; 858 | 859 | uniforms.displacementMap.value = material.displacementMap; 860 | uniforms.displacementScale.value = material.displacementScale; 861 | uniforms.displacementBias.value = material.displacementBias; 862 | 863 | if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { 864 | 865 | defines.USE_GLOSSINESSMAP = ''; 866 | // set USE_ROUGHNESSMAP to enable vUv 867 | defines.USE_ROUGHNESSMAP = ''; 868 | 869 | } 870 | 871 | if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { 872 | 873 | delete defines.USE_GLOSSINESSMAP; 874 | delete defines.USE_ROUGHNESSMAP; 875 | 876 | } 877 | 878 | } 879 | 880 | }; 881 | 882 | } 883 | 884 | /*********************************/ 885 | /********** INTERPOLATION ********/ 886 | /*********************************/ 887 | 888 | // Spline Interpolation 889 | // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation 890 | function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { 891 | 892 | THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); 893 | 894 | }; 895 | 896 | GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype ); 897 | GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant; 898 | 899 | GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { 900 | 901 | var result = this.resultBuffer; 902 | var values = this.sampleValues; 903 | var stride = this.valueSize; 904 | 905 | var stride2 = stride * 2; 906 | var stride3 = stride * 3; 907 | 908 | var td = t1 - t0; 909 | 910 | var p = ( t - t0 ) / td; 911 | var pp = p * p; 912 | var ppp = pp * p; 913 | 914 | var offset1 = i1 * stride3; 915 | var offset0 = offset1 - stride3; 916 | 917 | var s0 = 2 * ppp - 3 * pp + 1; 918 | var s1 = ppp - 2 * pp + p; 919 | var s2 = - 2 * ppp + 3 * pp; 920 | var s3 = ppp - pp; 921 | 922 | // Layout of keyframe output values for CUBICSPLINE animations: 923 | // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] 924 | for ( var i = 0; i !== stride; i ++ ) { 925 | 926 | var p0 = values[ offset0 + i + stride ]; // splineVertex_k 927 | var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) 928 | var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 929 | var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) 930 | 931 | result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; 932 | 933 | } 934 | 935 | return result; 936 | 937 | }; 938 | 939 | /*********************************/ 940 | /********** INTERNALS ************/ 941 | /*********************************/ 942 | 943 | /* CONSTANTS */ 944 | 945 | var WEBGL_CONSTANTS = { 946 | FLOAT: 5126, 947 | //FLOAT_MAT2: 35674, 948 | FLOAT_MAT3: 35675, 949 | FLOAT_MAT4: 35676, 950 | FLOAT_VEC2: 35664, 951 | FLOAT_VEC3: 35665, 952 | FLOAT_VEC4: 35666, 953 | LINEAR: 9729, 954 | REPEAT: 10497, 955 | SAMPLER_2D: 35678, 956 | POINTS: 0, 957 | LINES: 1, 958 | LINE_LOOP: 2, 959 | LINE_STRIP: 3, 960 | TRIANGLES: 4, 961 | TRIANGLE_STRIP: 5, 962 | TRIANGLE_FAN: 6, 963 | UNSIGNED_BYTE: 5121, 964 | UNSIGNED_SHORT: 5123 965 | }; 966 | 967 | var WEBGL_TYPE = { 968 | 5126: Number, 969 | //35674: THREE.Matrix2, 970 | 35675: THREE.Matrix3, 971 | 35676: THREE.Matrix4, 972 | 35664: THREE.Vector2, 973 | 35665: THREE.Vector3, 974 | 35666: THREE.Vector4, 975 | 35678: THREE.Texture 976 | }; 977 | 978 | var WEBGL_COMPONENT_TYPES = { 979 | 5120: Int8Array, 980 | 5121: Uint8Array, 981 | 5122: Int16Array, 982 | 5123: Uint16Array, 983 | 5125: Uint32Array, 984 | 5126: Float32Array 985 | }; 986 | 987 | var WEBGL_FILTERS = { 988 | 9728: THREE.NearestFilter, 989 | 9729: THREE.LinearFilter, 990 | 9984: THREE.NearestMipMapNearestFilter, 991 | 9985: THREE.LinearMipMapNearestFilter, 992 | 9986: THREE.NearestMipMapLinearFilter, 993 | 9987: THREE.LinearMipMapLinearFilter 994 | }; 995 | 996 | var WEBGL_WRAPPINGS = { 997 | 33071: THREE.ClampToEdgeWrapping, 998 | 33648: THREE.MirroredRepeatWrapping, 999 | 10497: THREE.RepeatWrapping 1000 | }; 1001 | 1002 | var WEBGL_TEXTURE_FORMATS = { 1003 | 6406: THREE.AlphaFormat, 1004 | 6407: THREE.RGBFormat, 1005 | 6408: THREE.RGBAFormat, 1006 | 6409: THREE.LuminanceFormat, 1007 | 6410: THREE.LuminanceAlphaFormat 1008 | }; 1009 | 1010 | var WEBGL_TEXTURE_DATATYPES = { 1011 | 5121: THREE.UnsignedByteType, 1012 | 32819: THREE.UnsignedShort4444Type, 1013 | 32820: THREE.UnsignedShort5551Type, 1014 | 33635: THREE.UnsignedShort565Type 1015 | }; 1016 | 1017 | var WEBGL_SIDES = { 1018 | 1028: THREE.BackSide, // Culling front 1019 | 1029: THREE.FrontSide // Culling back 1020 | //1032: THREE.NoSide // Culling front and back, what to do? 1021 | }; 1022 | 1023 | var WEBGL_DEPTH_FUNCS = { 1024 | 512: THREE.NeverDepth, 1025 | 513: THREE.LessDepth, 1026 | 514: THREE.EqualDepth, 1027 | 515: THREE.LessEqualDepth, 1028 | 516: THREE.GreaterEqualDepth, 1029 | 517: THREE.NotEqualDepth, 1030 | 518: THREE.GreaterEqualDepth, 1031 | 519: THREE.AlwaysDepth 1032 | }; 1033 | 1034 | var WEBGL_BLEND_EQUATIONS = { 1035 | 32774: THREE.AddEquation, 1036 | 32778: THREE.SubtractEquation, 1037 | 32779: THREE.ReverseSubtractEquation 1038 | }; 1039 | 1040 | var WEBGL_BLEND_FUNCS = { 1041 | 0: THREE.ZeroFactor, 1042 | 1: THREE.OneFactor, 1043 | 768: THREE.SrcColorFactor, 1044 | 769: THREE.OneMinusSrcColorFactor, 1045 | 770: THREE.SrcAlphaFactor, 1046 | 771: THREE.OneMinusSrcAlphaFactor, 1047 | 772: THREE.DstAlphaFactor, 1048 | 773: THREE.OneMinusDstAlphaFactor, 1049 | 774: THREE.DstColorFactor, 1050 | 775: THREE.OneMinusDstColorFactor, 1051 | 776: THREE.SrcAlphaSaturateFactor 1052 | // The followings are not supported by Three.js yet 1053 | //32769: CONSTANT_COLOR, 1054 | //32770: ONE_MINUS_CONSTANT_COLOR, 1055 | //32771: CONSTANT_ALPHA, 1056 | //32772: ONE_MINUS_CONSTANT_COLOR 1057 | }; 1058 | 1059 | var WEBGL_TYPE_SIZES = { 1060 | 'SCALAR': 1, 1061 | 'VEC2': 2, 1062 | 'VEC3': 3, 1063 | 'VEC4': 4, 1064 | 'MAT2': 4, 1065 | 'MAT3': 9, 1066 | 'MAT4': 16 1067 | }; 1068 | 1069 | var ATTRIBUTES = { 1070 | POSITION: 'position', 1071 | NORMAL: 'normal', 1072 | TEXCOORD_0: 'uv', 1073 | TEXCOORD0: 'uv', // deprecated 1074 | TEXCOORD: 'uv', // deprecated 1075 | TEXCOORD_1: 'uv2', 1076 | COLOR_0: 'color', 1077 | COLOR0: 'color', // deprecated 1078 | COLOR: 'color', // deprecated 1079 | WEIGHTS_0: 'skinWeight', 1080 | WEIGHT: 'skinWeight', // deprecated 1081 | JOINTS_0: 'skinIndex', 1082 | JOINT: 'skinIndex' // deprecated 1083 | } 1084 | 1085 | var PATH_PROPERTIES = { 1086 | scale: 'scale', 1087 | translation: 'position', 1088 | rotation: 'quaternion', 1089 | weights: 'morphTargetInfluences' 1090 | }; 1091 | 1092 | var INTERPOLATION = { 1093 | CUBICSPLINE: THREE.InterpolateSmooth, // We use custom interpolation GLTFCubicSplineInterpolation for CUBICSPLINE. 1094 | // KeyframeTrack.optimize() can't handle glTF Cubic Spline output values layout, 1095 | // using THREE.InterpolateSmooth for KeyframeTrack instantiation to prevent optimization. 1096 | // See KeyframeTrack.optimize() for the detail. 1097 | LINEAR: THREE.InterpolateLinear, 1098 | STEP: THREE.InterpolateDiscrete 1099 | }; 1100 | 1101 | var STATES_ENABLES = { 1102 | 2884: 'CULL_FACE', 1103 | 2929: 'DEPTH_TEST', 1104 | 3042: 'BLEND', 1105 | 3089: 'SCISSOR_TEST', 1106 | 32823: 'POLYGON_OFFSET_FILL', 1107 | 32926: 'SAMPLE_ALPHA_TO_COVERAGE' 1108 | }; 1109 | 1110 | var ALPHA_MODES = { 1111 | OPAQUE: 'OPAQUE', 1112 | MASK: 'MASK', 1113 | BLEND: 'BLEND' 1114 | }; 1115 | 1116 | /* UTILITY FUNCTIONS */ 1117 | 1118 | function resolveURL( url, path ) { 1119 | 1120 | // Invalid URL 1121 | if ( typeof url !== 'string' || url === '' ) return ''; 1122 | 1123 | // Absolute URL http://,https://,// 1124 | if ( /^(https?:)?\/\//i.test( url ) ) return url; 1125 | 1126 | // Data URI 1127 | if ( /^data:.*,.*$/i.test( url ) ) return url; 1128 | 1129 | // Blob URL 1130 | if ( /^blob:.*$/i.test( url ) ) return url; 1131 | 1132 | // Relative URL 1133 | return path + url; 1134 | 1135 | } 1136 | 1137 | /** 1138 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material 1139 | */ 1140 | function createDefaultMaterial() { 1141 | 1142 | return new THREE.MeshStandardMaterial( { 1143 | color: 0xFFFFFF, 1144 | emissive: 0x000000, 1145 | metalness: 1, 1146 | roughness: 1, 1147 | transparent: false, 1148 | depthTest: true, 1149 | side: THREE.FrontSide 1150 | } ); 1151 | 1152 | } 1153 | 1154 | /** 1155 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets 1156 | * 1157 | * @param {THREE.Mesh} mesh 1158 | * @param {GLTF.Mesh} meshDef 1159 | * @param {GLTF.Primitive} primitiveDef 1160 | * @param {Array} accessors 1161 | */ 1162 | function addMorphTargets( mesh, meshDef, primitiveDef, accessors ) { 1163 | 1164 | var geometry = mesh.geometry; 1165 | var targets = primitiveDef.targets; 1166 | 1167 | var hasMorphPosition = false; 1168 | var hasMorphNormal = false; 1169 | 1170 | for ( var i = 0, il = targets.length; i < il; i ++ ) { 1171 | 1172 | var target = targets[ i ]; 1173 | 1174 | if ( target.POSITION !== undefined ) hasMorphPosition = true; 1175 | if ( target.NORMAL !== undefined ) hasMorphNormal = true; 1176 | 1177 | if ( hasMorphPosition && hasMorphNormal ) break; 1178 | 1179 | } 1180 | 1181 | if ( ! hasMorphPosition && ! hasMorphNormal ) return; 1182 | 1183 | var morphPositions = []; 1184 | var morphNormals = []; 1185 | 1186 | for ( var i = 0, il = targets.length; i < il; i ++ ) { 1187 | 1188 | var target = targets[ i ]; 1189 | var attributeName = 'morphTarget' + i; 1190 | 1191 | if ( hasMorphPosition ) { 1192 | 1193 | // Three.js morph position is absolute value. The formula is 1194 | // basePosition 1195 | // + weight0 * ( morphPosition0 - basePosition ) 1196 | // + weight1 * ( morphPosition1 - basePosition ) 1197 | // ... 1198 | // while the glTF one is relative 1199 | // basePosition 1200 | // + weight0 * glTFmorphPosition0 1201 | // + weight1 * glTFmorphPosition1 1202 | // ... 1203 | // then we need to convert from relative to absolute here. 1204 | 1205 | if ( target.POSITION !== undefined ) { 1206 | 1207 | // Cloning not to pollute original accessor 1208 | var positionAttribute = cloneBufferAttribute( accessors[ target.POSITION ] ); 1209 | positionAttribute.name = attributeName; 1210 | 1211 | var position = geometry.attributes.position; 1212 | 1213 | for ( var j = 0, jl = positionAttribute.count; j < jl; j ++ ) { 1214 | 1215 | positionAttribute.setXYZ( 1216 | j, 1217 | positionAttribute.getX( j ) + position.getX( j ), 1218 | positionAttribute.getY( j ) + position.getY( j ), 1219 | positionAttribute.getZ( j ) + position.getZ( j ) 1220 | ); 1221 | 1222 | } 1223 | 1224 | } else { 1225 | 1226 | positionAttribute = geometry.attributes.position; 1227 | 1228 | } 1229 | 1230 | morphPositions.push( positionAttribute ); 1231 | 1232 | } 1233 | 1234 | if ( hasMorphNormal ) { 1235 | 1236 | // see target.POSITION's comment 1237 | 1238 | var normalAttribute; 1239 | 1240 | if ( target.NORMAL !== undefined ) { 1241 | 1242 | var normalAttribute = cloneBufferAttribute( accessors[ target.NORMAL ] ); 1243 | normalAttribute.name = attributeName; 1244 | 1245 | var normal = geometry.attributes.normal; 1246 | 1247 | for ( var j = 0, jl = normalAttribute.count; j < jl; j ++ ) { 1248 | 1249 | normalAttribute.setXYZ( 1250 | j, 1251 | normalAttribute.getX( j ) + normal.getX( j ), 1252 | normalAttribute.getY( j ) + normal.getY( j ), 1253 | normalAttribute.getZ( j ) + normal.getZ( j ) 1254 | ); 1255 | 1256 | } 1257 | 1258 | } else { 1259 | 1260 | normalAttribute = geometry.attributes.normal; 1261 | 1262 | } 1263 | 1264 | morphNormals.push( normalAttribute ); 1265 | 1266 | } 1267 | 1268 | } 1269 | 1270 | if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; 1271 | if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; 1272 | 1273 | mesh.updateMorphTargets(); 1274 | 1275 | if ( meshDef.weights !== undefined ) { 1276 | 1277 | for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { 1278 | 1279 | mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; 1280 | 1281 | } 1282 | 1283 | } 1284 | 1285 | // .extras has user-defined data, so check that .extras.targetNames is an array. 1286 | if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { 1287 | 1288 | var targetNames = meshDef.extras.targetNames; 1289 | 1290 | if ( mesh.morphTargetInfluences.length === targetNames.length ) { 1291 | 1292 | mesh.morphTargetDictionary = {}; 1293 | 1294 | for ( var i = 0, il = targetNames.length; i < il; i ++ ) { 1295 | 1296 | mesh.morphTargetDictionary[ targetNames[ i ] ] = i; 1297 | 1298 | } 1299 | 1300 | } else { 1301 | 1302 | console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); 1303 | 1304 | } 1305 | 1306 | } 1307 | 1308 | } 1309 | 1310 | function isPrimitiveEqual( a, b ) { 1311 | 1312 | if ( a.indices !== b.indices ) { 1313 | 1314 | return false; 1315 | 1316 | } 1317 | 1318 | var attribA = a.attributes || {}; 1319 | var attribB = b.attributes || {}; 1320 | var keysA = Object.keys( attribA ); 1321 | var keysB = Object.keys( attribB ); 1322 | 1323 | if ( keysA.length !== keysB.length ) { 1324 | 1325 | return false; 1326 | 1327 | } 1328 | 1329 | for ( var i = 0, il = keysA.length; i < il; i ++ ) { 1330 | 1331 | var key = keysA[ i ]; 1332 | 1333 | if ( attribA[ key ] !== attribB[ key ] ) { 1334 | 1335 | return false; 1336 | 1337 | } 1338 | 1339 | } 1340 | 1341 | return true; 1342 | 1343 | } 1344 | 1345 | function getCachedGeometry( cache, newPrimitive ) { 1346 | 1347 | for ( var i = 0, il = cache.length; i < il; i ++ ) { 1348 | 1349 | var cached = cache[ i ]; 1350 | 1351 | if ( isPrimitiveEqual( cached.primitive, newPrimitive ) ) { 1352 | 1353 | return cached.promise; 1354 | 1355 | } 1356 | 1357 | } 1358 | 1359 | return null; 1360 | 1361 | } 1362 | 1363 | function cloneBufferAttribute( attribute ) { 1364 | 1365 | if ( attribute.isInterleavedBufferAttribute ) { 1366 | 1367 | var count = attribute.count; 1368 | var itemSize = attribute.itemSize; 1369 | var array = attribute.array.slice( 0, count * itemSize ); 1370 | 1371 | for ( var i = 0; i < count; ++ i ) { 1372 | 1373 | array[ i ] = attribute.getX( i ); 1374 | if ( itemSize >= 2 ) array[ i + 1 ] = attribute.getY( i ); 1375 | if ( itemSize >= 3 ) array[ i + 2 ] = attribute.getZ( i ); 1376 | if ( itemSize >= 4 ) array[ i + 3 ] = attribute.getW( i ); 1377 | 1378 | } 1379 | 1380 | return new THREE.BufferAttribute( array, itemSize, attribute.normalized ); 1381 | 1382 | } 1383 | 1384 | return attribute.clone(); 1385 | 1386 | } 1387 | 1388 | /* GLTF PARSER */ 1389 | 1390 | function GLTFParser( json, extensions, options ) { 1391 | 1392 | this.json = json || {}; 1393 | this.extensions = extensions || {}; 1394 | this.options = options || {}; 1395 | 1396 | // loader object cache 1397 | this.cache = new GLTFRegistry(); 1398 | 1399 | // BufferGeometry caching 1400 | this.primitiveCache = []; 1401 | 1402 | this.textureLoader = new THREE.TextureLoader( this.options.manager ); 1403 | this.textureLoader.setCrossOrigin( this.options.crossOrigin ); 1404 | 1405 | this.fileLoader = new THREE.FileLoader( this.options.manager ); 1406 | this.fileLoader.setResponseType( 'arraybuffer' ); 1407 | 1408 | } 1409 | 1410 | GLTFParser.prototype.parse = function ( onLoad, onError ) { 1411 | 1412 | var json = this.json; 1413 | 1414 | // Clear the loader cache 1415 | this.cache.removeAll(); 1416 | 1417 | // Mark the special nodes/meshes in json for efficient parse 1418 | this.markDefs(); 1419 | 1420 | // Fire the callback on complete 1421 | this.getMultiDependencies( [ 1422 | 1423 | 'scene', 1424 | 'animation', 1425 | 'camera' 1426 | 1427 | ] ).then( function ( dependencies ) { 1428 | 1429 | var scenes = dependencies.scenes || []; 1430 | var scene = scenes[ json.scene || 0 ]; 1431 | var animations = dependencies.animations || []; 1432 | var asset = json.asset; 1433 | var cameras = dependencies.cameras || []; 1434 | 1435 | onLoad( scene, scenes, cameras, animations, asset ); 1436 | 1437 | } ).catch( onError ); 1438 | 1439 | }; 1440 | 1441 | /** 1442 | * Marks the special nodes/meshes in json for efficient parse. 1443 | */ 1444 | GLTFParser.prototype.markDefs = function () { 1445 | 1446 | var nodeDefs = this.json.nodes || []; 1447 | var skinDefs = this.json.skins || []; 1448 | var meshDefs = this.json.meshes || []; 1449 | 1450 | var meshReferences = {}; 1451 | var meshUses = {}; 1452 | 1453 | // Nothing in the node definition indicates whether it is a Bone or an 1454 | // Object3D. Use the skins' joint references to mark bones. 1455 | for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { 1456 | 1457 | var joints = skinDefs[ skinIndex ].joints; 1458 | 1459 | for ( var i = 0, il = joints.length; i < il; i ++ ) { 1460 | 1461 | nodeDefs[ joints[ i ] ].isBone = true; 1462 | 1463 | } 1464 | 1465 | } 1466 | 1467 | // Meshes can (and should) be reused by multiple nodes in a glTF asset. To 1468 | // avoid having more than one THREE.Mesh with the same name, count 1469 | // references and rename instances below. 1470 | // 1471 | // Example: CesiumMilkTruck sample model reuses "Wheel" meshes. 1472 | for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { 1473 | 1474 | var nodeDef = nodeDefs[ nodeIndex ]; 1475 | 1476 | if ( nodeDef.mesh !== undefined ) { 1477 | 1478 | if ( meshReferences[ nodeDef.mesh ] === undefined ) { 1479 | 1480 | meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0; 1481 | 1482 | } 1483 | 1484 | meshReferences[ nodeDef.mesh ] ++; 1485 | 1486 | // Nothing in the mesh definition indicates whether it is 1487 | // a SkinnedMesh or Mesh. Use the node's mesh reference 1488 | // to mark SkinnedMesh if node has skin. 1489 | if ( nodeDef.skin !== undefined ) { 1490 | 1491 | meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; 1492 | 1493 | } 1494 | 1495 | } 1496 | 1497 | } 1498 | 1499 | this.json.meshReferences = meshReferences; 1500 | this.json.meshUses = meshUses; 1501 | 1502 | }; 1503 | 1504 | /** 1505 | * Requests the specified dependency asynchronously, with caching. 1506 | * @param {string} type 1507 | * @param {number} index 1508 | * @return {Promise} 1509 | */ 1510 | GLTFParser.prototype.getDependency = function ( type, index ) { 1511 | 1512 | var cacheKey = type + ':' + index; 1513 | var dependency = this.cache.get( cacheKey ); 1514 | 1515 | if ( ! dependency ) { 1516 | 1517 | switch ( type ) { 1518 | 1519 | case 'scene': 1520 | dependency = this.loadScene( index ); 1521 | break; 1522 | 1523 | case 'node': 1524 | dependency = this.loadNode( index ); 1525 | break; 1526 | 1527 | case 'mesh': 1528 | dependency = this.loadMesh( index ); 1529 | break; 1530 | 1531 | case 'accessor': 1532 | dependency = this.loadAccessor( index ); 1533 | break; 1534 | 1535 | case 'bufferView': 1536 | dependency = this.loadBufferView( index ); 1537 | break; 1538 | 1539 | case 'buffer': 1540 | dependency = this.loadBuffer( index ); 1541 | break; 1542 | 1543 | case 'material': 1544 | dependency = this.loadMaterial( index ); 1545 | break; 1546 | 1547 | case 'texture': 1548 | dependency = this.loadTexture( index ); 1549 | break; 1550 | 1551 | case 'skin': 1552 | dependency = this.loadSkin( index ); 1553 | break; 1554 | 1555 | case 'animation': 1556 | dependency = this.loadAnimation( index ); 1557 | break; 1558 | 1559 | case 'camera': 1560 | dependency = this.loadCamera( index ); 1561 | break; 1562 | 1563 | default: 1564 | throw new Error( 'Unknown type: ' + type ); 1565 | 1566 | } 1567 | 1568 | this.cache.add( cacheKey, dependency ); 1569 | 1570 | } 1571 | 1572 | return dependency; 1573 | 1574 | }; 1575 | 1576 | /** 1577 | * Requests all dependencies of the specified type asynchronously, with caching. 1578 | * @param {string} type 1579 | * @return {Promise>} 1580 | */ 1581 | GLTFParser.prototype.getDependencies = function ( type ) { 1582 | 1583 | var dependencies = this.cache.get( type ); 1584 | 1585 | if ( ! dependencies ) { 1586 | 1587 | var parser = this; 1588 | var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; 1589 | 1590 | dependencies = Promise.all( defs.map( function ( def, index ) { 1591 | 1592 | return parser.getDependency( type, index ); 1593 | 1594 | } ) ); 1595 | 1596 | this.cache.add( type, dependencies ); 1597 | 1598 | } 1599 | 1600 | return dependencies; 1601 | 1602 | }; 1603 | 1604 | /** 1605 | * Requests all multiple dependencies of the specified types asynchronously, with caching. 1606 | * @param {Array} types 1607 | * @return {Promise>>} 1608 | */ 1609 | GLTFParser.prototype.getMultiDependencies = function ( types ) { 1610 | 1611 | var results = {}; 1612 | var pendings = []; 1613 | 1614 | for ( var i = 0, il = types.length; i < il; i ++ ) { 1615 | 1616 | var type = types[ i ]; 1617 | var value = this.getDependencies( type ); 1618 | 1619 | value = value.then( function ( key, value ) { 1620 | 1621 | results[ key ] = value; 1622 | 1623 | }.bind( this, type + ( type === 'mesh' ? 'es' : 's' ) ) ); 1624 | 1625 | pendings.push( value ); 1626 | 1627 | } 1628 | 1629 | return Promise.all( pendings ).then( function () { 1630 | 1631 | return results; 1632 | 1633 | } ); 1634 | 1635 | }; 1636 | 1637 | /** 1638 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views 1639 | * @param {number} bufferIndex 1640 | * @return {Promise} 1641 | */ 1642 | GLTFParser.prototype.loadBuffer = function ( bufferIndex ) { 1643 | 1644 | var bufferDef = this.json.buffers[ bufferIndex ]; 1645 | var loader = this.fileLoader; 1646 | 1647 | if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { 1648 | 1649 | throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); 1650 | 1651 | } 1652 | 1653 | // If present, GLB container is required to be the first buffer. 1654 | if ( bufferDef.uri === undefined && bufferIndex === 0 ) { 1655 | 1656 | return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); 1657 | 1658 | } 1659 | 1660 | var options = this.options; 1661 | 1662 | return new Promise( function ( resolve, reject ) { 1663 | 1664 | loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { 1665 | 1666 | reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); 1667 | 1668 | } ); 1669 | 1670 | } ); 1671 | 1672 | }; 1673 | 1674 | /** 1675 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views 1676 | * @param {number} bufferViewIndex 1677 | * @return {Promise} 1678 | */ 1679 | GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) { 1680 | 1681 | var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; 1682 | 1683 | return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { 1684 | 1685 | var byteLength = bufferViewDef.byteLength || 0; 1686 | var byteOffset = bufferViewDef.byteOffset || 0; 1687 | return buffer.slice( byteOffset, byteOffset + byteLength ); 1688 | 1689 | } ); 1690 | 1691 | }; 1692 | 1693 | /** 1694 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors 1695 | * @param {number} accessorIndex 1696 | * @return {Promise} 1697 | */ 1698 | GLTFParser.prototype.loadAccessor = function ( accessorIndex ) { 1699 | 1700 | var parser = this; 1701 | var json = this.json; 1702 | 1703 | var accessorDef = this.json.accessors[ accessorIndex ]; 1704 | 1705 | if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { 1706 | 1707 | // Ignore empty accessors, which may be used to declare runtime 1708 | // information about attributes coming from another source (e.g. Draco 1709 | // compression extension). 1710 | return null; 1711 | 1712 | } 1713 | 1714 | var pendingBufferViews = []; 1715 | 1716 | if ( accessorDef.bufferView !== undefined ) { 1717 | 1718 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); 1719 | 1720 | } else { 1721 | 1722 | pendingBufferViews.push( null ); 1723 | 1724 | } 1725 | 1726 | if ( accessorDef.sparse !== undefined ) { 1727 | 1728 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); 1729 | pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); 1730 | 1731 | } 1732 | 1733 | return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { 1734 | 1735 | var bufferView = bufferViews[ 0 ]; 1736 | 1737 | var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; 1738 | var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; 1739 | 1740 | // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. 1741 | var elementBytes = TypedArray.BYTES_PER_ELEMENT; 1742 | var itemBytes = elementBytes * itemSize; 1743 | var byteOffset = accessorDef.byteOffset || 0; 1744 | var byteStride = json.bufferViews[ accessorDef.bufferView ].byteStride; 1745 | var normalized = accessorDef.normalized === true; 1746 | var array, bufferAttribute; 1747 | 1748 | // The buffer is not interleaved if the stride is the item size in bytes. 1749 | if ( byteStride && byteStride !== itemBytes ) { 1750 | 1751 | var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType; 1752 | var ib = parser.cache.get( ibCacheKey ); 1753 | 1754 | if ( ! ib ) { 1755 | 1756 | // Use the full buffer if it's interleaved. 1757 | array = new TypedArray( bufferView ); 1758 | 1759 | // Integer parameters to IB/IBA are in array elements, not bytes. 1760 | ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes ); 1761 | 1762 | parser.cache.add( ibCacheKey, ib ); 1763 | 1764 | } 1765 | 1766 | bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, byteOffset / elementBytes, normalized ); 1767 | 1768 | } else { 1769 | 1770 | if ( bufferView === null ) { 1771 | 1772 | array = new TypedArray( accessorDef.count * itemSize ); 1773 | 1774 | } else { 1775 | 1776 | array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); 1777 | 1778 | } 1779 | 1780 | bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized ); 1781 | 1782 | } 1783 | 1784 | // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors 1785 | if ( accessorDef.sparse !== undefined ) { 1786 | 1787 | var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; 1788 | var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; 1789 | 1790 | var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; 1791 | var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; 1792 | 1793 | var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); 1794 | var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); 1795 | 1796 | if ( bufferView !== null ) { 1797 | 1798 | // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. 1799 | bufferAttribute.setArray( bufferAttribute.array.slice() ); 1800 | 1801 | } 1802 | 1803 | for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) { 1804 | 1805 | var index = sparseIndices[ i ]; 1806 | 1807 | bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); 1808 | if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); 1809 | if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); 1810 | if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); 1811 | if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); 1812 | 1813 | } 1814 | 1815 | } 1816 | 1817 | return bufferAttribute; 1818 | 1819 | } ); 1820 | 1821 | }; 1822 | 1823 | /** 1824 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures 1825 | * @param {number} textureIndex 1826 | * @return {Promise} 1827 | */ 1828 | GLTFParser.prototype.loadTexture = function ( textureIndex ) { 1829 | 1830 | var parser = this; 1831 | var json = this.json; 1832 | var options = this.options; 1833 | var textureLoader = this.textureLoader; 1834 | 1835 | var URL = window.URL || window.webkitURL; 1836 | 1837 | var textureDef = json.textures[ textureIndex ]; 1838 | 1839 | var textureExtensions = textureDef.extensions || {}; 1840 | 1841 | var source; 1842 | 1843 | if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) { 1844 | 1845 | source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ]; 1846 | 1847 | } else { 1848 | 1849 | source = json.images[ textureDef.source ]; 1850 | 1851 | } 1852 | 1853 | var sourceURI = source.uri; 1854 | var isObjectURL = false; 1855 | 1856 | if ( source.bufferView !== undefined ) { 1857 | 1858 | // Load binary image data from bufferView, if provided. 1859 | 1860 | sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) { 1861 | 1862 | isObjectURL = true; 1863 | var blob = new Blob( [ bufferView ], { type: source.mimeType } ); 1864 | sourceURI = URL.createObjectURL( blob ); 1865 | return sourceURI; 1866 | 1867 | } ); 1868 | 1869 | } 1870 | 1871 | return Promise.resolve( sourceURI ).then( function ( sourceURI ) { 1872 | 1873 | // Load Texture resource. 1874 | 1875 | var loader = THREE.Loader.Handlers.get( sourceURI ); 1876 | 1877 | if ( ! loader ) { 1878 | 1879 | loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] 1880 | ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader 1881 | : textureLoader; 1882 | 1883 | } 1884 | 1885 | return new Promise( function ( resolve, reject ) { 1886 | 1887 | loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); 1888 | 1889 | } ); 1890 | 1891 | } ).then( function ( texture ) { 1892 | 1893 | // Clean up resources and configure Texture. 1894 | 1895 | if ( isObjectURL === true ) { 1896 | 1897 | URL.revokeObjectURL( sourceURI ); 1898 | 1899 | } 1900 | 1901 | texture.flipY = false; 1902 | 1903 | if ( textureDef.name !== undefined ) texture.name = textureDef.name; 1904 | 1905 | // .format of dds texture is set in DDSLoader 1906 | if ( ! textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) { 1907 | 1908 | texture.format = textureDef.format !== undefined ? WEBGL_TEXTURE_FORMATS[ textureDef.format ] : THREE.RGBAFormat; 1909 | 1910 | } 1911 | 1912 | if ( textureDef.internalFormat !== undefined && texture.format !== WEBGL_TEXTURE_FORMATS[ textureDef.internalFormat ] ) { 1913 | 1914 | console.warn( 'THREE.GLTFLoader: Three.js does not support texture internalFormat which is different from texture format. ' + 1915 | 'internalFormat will be forced to be the same value as format.' ); 1916 | 1917 | } 1918 | 1919 | texture.type = textureDef.type !== undefined ? WEBGL_TEXTURE_DATATYPES[ textureDef.type ] : THREE.UnsignedByteType; 1920 | 1921 | var samplers = json.samplers || {}; 1922 | var sampler = samplers[ textureDef.sampler ] || {}; 1923 | 1924 | texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter; 1925 | texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipMapLinearFilter; 1926 | texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping; 1927 | texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping; 1928 | 1929 | return texture; 1930 | 1931 | } ); 1932 | 1933 | }; 1934 | 1935 | /** 1936 | * Asynchronously assigns a texture to the given material parameters. 1937 | * @param {Object} materialParams 1938 | * @param {string} textureName 1939 | * @param {number} textureIndex 1940 | * @return {Promise} 1941 | */ 1942 | GLTFParser.prototype.assignTexture = function ( materialParams, textureName, textureIndex ) { 1943 | 1944 | return this.getDependency( 'texture', textureIndex ).then( function ( texture ) { 1945 | 1946 | materialParams[ textureName ] = texture; 1947 | 1948 | } ); 1949 | 1950 | }; 1951 | 1952 | /** 1953 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials 1954 | * @param {number} materialIndex 1955 | * @return {Promise} 1956 | */ 1957 | GLTFParser.prototype.loadMaterial = function ( materialIndex ) { 1958 | 1959 | var parser = this; 1960 | var json = this.json; 1961 | var extensions = this.extensions; 1962 | var materialDef = this.json.materials[ materialIndex ]; 1963 | 1964 | var materialType; 1965 | var materialParams = {}; 1966 | var materialExtensions = materialDef.extensions || {}; 1967 | 1968 | var pending = []; 1969 | 1970 | if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { 1971 | 1972 | var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; 1973 | materialType = sgExtension.getMaterialType( materialDef ); 1974 | pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); 1975 | 1976 | } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { 1977 | 1978 | var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; 1979 | materialType = kmuExtension.getMaterialType( materialDef ); 1980 | pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); 1981 | 1982 | } else { 1983 | 1984 | // Specification: 1985 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material 1986 | 1987 | materialType = THREE.MeshStandardMaterial; 1988 | 1989 | var metallicRoughness = materialDef.pbrMetallicRoughness || {}; 1990 | 1991 | materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); 1992 | materialParams.opacity = 1.0; 1993 | 1994 | if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { 1995 | 1996 | var array = metallicRoughness.baseColorFactor; 1997 | 1998 | materialParams.color.fromArray( array ); 1999 | materialParams.opacity = array[ 3 ]; 2000 | 2001 | } 2002 | 2003 | if ( metallicRoughness.baseColorTexture !== undefined ) { 2004 | 2005 | pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture.index ) ); 2006 | 2007 | } 2008 | 2009 | materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; 2010 | materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; 2011 | 2012 | if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { 2013 | 2014 | var textureIndex = metallicRoughness.metallicRoughnessTexture.index; 2015 | pending.push( parser.assignTexture( materialParams, 'metalnessMap', textureIndex ) ); 2016 | pending.push( parser.assignTexture( materialParams, 'roughnessMap', textureIndex ) ); 2017 | 2018 | } 2019 | 2020 | } 2021 | 2022 | if ( materialDef.doubleSided === true ) { 2023 | 2024 | materialParams.side = THREE.DoubleSide; 2025 | 2026 | } 2027 | 2028 | var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; 2029 | 2030 | if ( alphaMode === ALPHA_MODES.BLEND ) { 2031 | 2032 | materialParams.transparent = true; 2033 | 2034 | } else { 2035 | 2036 | materialParams.transparent = false; 2037 | 2038 | if ( alphaMode === ALPHA_MODES.MASK ) { 2039 | 2040 | materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; 2041 | 2042 | } 2043 | 2044 | } 2045 | 2046 | if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial) { 2047 | 2048 | pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture.index ) ); 2049 | 2050 | materialParams.normalScale = new THREE.Vector2( 1, 1 ); 2051 | 2052 | if ( materialDef.normalTexture.scale !== undefined ) { 2053 | 2054 | materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale ); 2055 | 2056 | } 2057 | 2058 | } 2059 | 2060 | if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial) { 2061 | 2062 | pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture.index ) ); 2063 | 2064 | if ( materialDef.occlusionTexture.strength !== undefined ) { 2065 | 2066 | materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; 2067 | 2068 | } 2069 | 2070 | } 2071 | 2072 | if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial) { 2073 | 2074 | materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor ); 2075 | 2076 | } 2077 | 2078 | if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial) { 2079 | 2080 | pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture.index ) ); 2081 | 2082 | } 2083 | 2084 | return Promise.all( pending ).then( function () { 2085 | 2086 | var material; 2087 | 2088 | if ( materialType === THREE.ShaderMaterial ) { 2089 | 2090 | material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); 2091 | 2092 | } else { 2093 | 2094 | material = new materialType( materialParams ); 2095 | 2096 | } 2097 | 2098 | if ( materialDef.name !== undefined ) material.name = materialDef.name; 2099 | 2100 | // Normal map textures use OpenGL conventions: 2101 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materialnormaltexture 2102 | if ( material.normalScale ) { 2103 | 2104 | material.normalScale.y = - material.normalScale.y; 2105 | 2106 | } 2107 | 2108 | // emissiveTexture and baseColorTexture use sRGB encoding. 2109 | if ( material.map ) material.map.encoding = THREE.sRGBEncoding; 2110 | if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding; 2111 | 2112 | if ( materialDef.extras ) material.userData = materialDef.extras; 2113 | 2114 | return material; 2115 | 2116 | } ); 2117 | 2118 | }; 2119 | 2120 | /** 2121 | * @param {THREE.BufferGeometry} geometry 2122 | * @param {GLTF.Primitive} primitiveDef 2123 | * @param {Array} accessors 2124 | */ 2125 | function addPrimitiveAttributes ( geometry, primitiveDef, accessors ) { 2126 | 2127 | var attributes = primitiveDef.attributes; 2128 | 2129 | for ( var gltfAttributeName in attributes ) { 2130 | 2131 | var threeAttributeName = ATTRIBUTES[ gltfAttributeName ]; 2132 | var bufferAttribute = accessors[ attributes[ gltfAttributeName ] ]; 2133 | 2134 | // Skip attributes already provided by e.g. Draco extension. 2135 | if ( !threeAttributeName ) continue; 2136 | if ( threeAttributeName in geometry.attributes ) continue; 2137 | 2138 | geometry.addAttribute( threeAttributeName, bufferAttribute ); 2139 | 2140 | } 2141 | 2142 | if ( primitiveDef.indices !== undefined && !geometry.index ) { 2143 | 2144 | geometry.setIndex( accessors[ primitiveDef.indices ] ); 2145 | 2146 | } 2147 | 2148 | } 2149 | 2150 | /** 2151 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry 2152 | * @param {Array} primitives 2153 | * @return {Promise>} 2154 | */ 2155 | GLTFParser.prototype.loadGeometries = function ( primitives ) { 2156 | 2157 | var parser = this; 2158 | var extensions = this.extensions; 2159 | var cache = this.primitiveCache; 2160 | 2161 | return this.getDependencies( 'accessor' ).then( function ( accessors ) { 2162 | 2163 | var pending = []; 2164 | 2165 | for ( var i = 0, il = primitives.length; i < il; i ++ ) { 2166 | 2167 | var primitive = primitives[ i ]; 2168 | 2169 | // See if we've already created this geometry 2170 | var cached = getCachedGeometry( cache, primitive ); 2171 | 2172 | if ( cached ) { 2173 | 2174 | // Use the cached geometry if it exists 2175 | pending.push( cached ); 2176 | 2177 | } else if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { 2178 | 2179 | // Use DRACO geometry if available 2180 | var geometryPromise = extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] 2181 | .decodePrimitive( primitive, parser ) 2182 | .then( function ( geometry ) { 2183 | 2184 | addPrimitiveAttributes( geometry, primitive, accessors ); 2185 | 2186 | return geometry; 2187 | 2188 | } ); 2189 | 2190 | cache.push( { primitive: primitive, promise: geometryPromise } ); 2191 | 2192 | pending.push( geometryPromise ); 2193 | 2194 | } else { 2195 | 2196 | // Otherwise create a new geometry 2197 | var geometry = new THREE.BufferGeometry(); 2198 | 2199 | addPrimitiveAttributes( geometry, primitive, accessors ); 2200 | 2201 | var geometryPromise = Promise.resolve( geometry ); 2202 | 2203 | // Cache this geometry 2204 | cache.push( { 2205 | 2206 | primitive: primitive, 2207 | promise: geometryPromise 2208 | 2209 | } ); 2210 | 2211 | pending.push( geometryPromise ); 2212 | 2213 | } 2214 | 2215 | } 2216 | 2217 | return Promise.all( pending ); 2218 | 2219 | } ); 2220 | 2221 | }; 2222 | 2223 | /** 2224 | * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes 2225 | * @param {number} meshIndex 2226 | * @return {Promise} 2227 | */ 2228 | GLTFParser.prototype.loadMesh = function ( meshIndex ) { 2229 | 2230 | var scope = this; 2231 | var json = this.json; 2232 | var extensions = this.extensions; 2233 | 2234 | var meshDef = this.json.meshes[ meshIndex ]; 2235 | 2236 | return this.getMultiDependencies( [ 2237 | 2238 | 'accessor', 2239 | 'material' 2240 | 2241 | ] ).then( function ( dependencies ) { 2242 | 2243 | var group = new THREE.Group(); 2244 | 2245 | var primitives = meshDef.primitives; 2246 | 2247 | return scope.loadGeometries( primitives ).then( function ( geometries ) { 2248 | 2249 | for ( var i = 0, il = primitives.length; i < il; i ++ ) { 2250 | 2251 | var primitive = primitives[ i ]; 2252 | var geometry = geometries[ i ]; 2253 | 2254 | var material = primitive.material === undefined 2255 | ? createDefaultMaterial() 2256 | : dependencies.materials[ primitive.material ]; 2257 | 2258 | if ( material.aoMap 2259 | && geometry.attributes.uv2 === undefined 2260 | && geometry.attributes.uv !== undefined ) { 2261 | 2262 | console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' ); 2263 | geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); 2264 | 2265 | } 2266 | 2267 | // If the material will be modified later on, clone it now. 2268 | var useVertexColors = geometry.attributes.color !== undefined; 2269 | var useFlatShading = geometry.attributes.normal === undefined; 2270 | var useSkinning = meshDef.isSkinnedMesh === true; 2271 | var useMorphTargets = primitive.targets !== undefined; 2272 | 2273 | if ( useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { 2274 | 2275 | if ( material.isGLTFSpecularGlossinessMaterial ) { 2276 | 2277 | var specGlossExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; 2278 | material = specGlossExtension.cloneMaterial( material ); 2279 | 2280 | } else { 2281 | 2282 | material = material.clone(); 2283 | 2284 | } 2285 | 2286 | } 2287 | 2288 | if ( useVertexColors ) { 2289 | 2290 | material.vertexColors = THREE.VertexColors; 2291 | material.needsUpdate = true; 2292 | 2293 | } 2294 | 2295 | if ( useFlatShading ) { 2296 | 2297 | material.flatShading = true; 2298 | 2299 | } 2300 | 2301 | var mesh; 2302 | 2303 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || 2304 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || 2305 | primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || 2306 | primitive.mode === undefined ) { 2307 | 2308 | if ( useSkinning ) { 2309 | 2310 | mesh = new THREE.SkinnedMesh( geometry, material ); 2311 | material.skinning = true; 2312 | 2313 | } else { 2314 | 2315 | mesh = new THREE.Mesh( geometry, material ); 2316 | 2317 | } 2318 | 2319 | if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { 2320 | 2321 | mesh.drawMode = THREE.TriangleStripDrawMode; 2322 | 2323 | } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { 2324 | 2325 | mesh.drawMode = THREE.TriangleFanDrawMode; 2326 | 2327 | } 2328 | 2329 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINES || 2330 | primitive.mode === WEBGL_CONSTANTS.LINE_STRIP || 2331 | primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { 2332 | 2333 | var cacheKey = 'LineBasicMaterial:' + material.uuid; 2334 | 2335 | var lineMaterial = scope.cache.get( cacheKey ); 2336 | 2337 | if ( ! lineMaterial ) { 2338 | 2339 | lineMaterial = new THREE.LineBasicMaterial(); 2340 | THREE.Material.prototype.copy.call( lineMaterial, material ); 2341 | lineMaterial.color.copy( material.color ); 2342 | lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet 2343 | 2344 | scope.cache.add( cacheKey, lineMaterial ); 2345 | 2346 | } 2347 | 2348 | material = lineMaterial; 2349 | 2350 | if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { 2351 | 2352 | mesh = new THREE.LineSegments( geometry, material ); 2353 | 2354 | } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { 2355 | 2356 | mesh = new THREE.Line( geometry, material ); 2357 | 2358 | } else { 2359 | 2360 | mesh = new THREE.LineLoop( geometry, material ); 2361 | 2362 | } 2363 | 2364 | } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { 2365 | 2366 | var cacheKey = 'PointsMaterial:' + material.uuid; 2367 | 2368 | var pointsMaterial = scope.cache.get( cacheKey ); 2369 | 2370 | if ( ! pointsMaterial ) { 2371 | 2372 | pointsMaterial = new THREE.PointsMaterial(); 2373 | THREE.Material.prototype.copy.call( pointsMaterial, material ); 2374 | pointsMaterial.color.copy( material.color ); 2375 | pointsMaterial.map = material.map; 2376 | pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet 2377 | 2378 | scope.cache.add( cacheKey, pointsMaterial ); 2379 | 2380 | } 2381 | 2382 | material = pointsMaterial; 2383 | 2384 | mesh = new THREE.Points( geometry, material ); 2385 | 2386 | } else { 2387 | 2388 | throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); 2389 | 2390 | } 2391 | 2392 | mesh.name = meshDef.name || ( 'mesh_' + meshIndex ); 2393 | 2394 | if ( useMorphTargets ) { 2395 | 2396 | addMorphTargets( mesh, meshDef, primitive, dependencies.accessors ); 2397 | 2398 | material.morphTargets = true; 2399 | 2400 | if ( mesh.geometry.morphAttributes.normal !== undefined ) material.morphNormals = true; 2401 | 2402 | } 2403 | 2404 | if ( meshDef.extras !== undefined ) mesh.userData = meshDef.extras; 2405 | if ( primitive.extras !== undefined ) mesh.geometry.userData = primitive.extras; 2406 | 2407 | // for Specular-Glossiness. 2408 | if ( material.isGLTFSpecularGlossinessMaterial === true ) { 2409 | 2410 | mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; 2411 | 2412 | } 2413 | 2414 | if ( primitives.length > 1 ) { 2415 | 2416 | mesh.name += '_' + i; 2417 | 2418 | group.add( mesh ); 2419 | 2420 | } else { 2421 | 2422 | return mesh; 2423 | 2424 | } 2425 | 2426 | } 2427 | 2428 | return group; 2429 | 2430 | } ); 2431 | 2432 | } ); 2433 | 2434 | }; 2435 | 2436 | /** 2437 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras 2438 | * @param {number} cameraIndex 2439 | * @return {Promise} 2440 | */ 2441 | GLTFParser.prototype.loadCamera = function ( cameraIndex ) { 2442 | 2443 | var camera; 2444 | var cameraDef = this.json.cameras[ cameraIndex ]; 2445 | var params = cameraDef[ cameraDef.type ]; 2446 | 2447 | if ( ! params ) { 2448 | 2449 | console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); 2450 | return; 2451 | 2452 | } 2453 | 2454 | if ( cameraDef.type === 'perspective' ) { 2455 | 2456 | camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); 2457 | 2458 | } else if ( cameraDef.type === 'orthographic' ) { 2459 | 2460 | camera = new THREE.OrthographicCamera( params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar ); 2461 | 2462 | } 2463 | 2464 | if ( cameraDef.name !== undefined ) camera.name = cameraDef.name; 2465 | if ( cameraDef.extras ) camera.userData = cameraDef.extras; 2466 | 2467 | return Promise.resolve( camera ); 2468 | 2469 | }; 2470 | 2471 | /** 2472 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins 2473 | * @param {number} skinIndex 2474 | * @return {Promise} 2475 | */ 2476 | GLTFParser.prototype.loadSkin = function ( skinIndex ) { 2477 | 2478 | var skinDef = this.json.skins[ skinIndex ]; 2479 | 2480 | var skinEntry = { joints: skinDef.joints }; 2481 | 2482 | if ( skinDef.inverseBindMatrices === undefined ) { 2483 | 2484 | return Promise.resolve( skinEntry ); 2485 | 2486 | } 2487 | 2488 | return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) { 2489 | 2490 | skinEntry.inverseBindMatrices = accessor; 2491 | 2492 | return skinEntry; 2493 | 2494 | } ); 2495 | 2496 | }; 2497 | 2498 | /** 2499 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations 2500 | * @param {number} animationIndex 2501 | * @return {Promise} 2502 | */ 2503 | GLTFParser.prototype.loadAnimation = function ( animationIndex ) { 2504 | 2505 | var json = this.json; 2506 | 2507 | var animationDef = this.json.animations[ animationIndex ]; 2508 | 2509 | return this.getMultiDependencies( [ 2510 | 2511 | 'accessor', 2512 | 'node' 2513 | 2514 | ] ).then( function ( dependencies ) { 2515 | 2516 | var tracks = []; 2517 | 2518 | for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) { 2519 | 2520 | var channel = animationDef.channels[ i ]; 2521 | var sampler = animationDef.samplers[ channel.sampler ]; 2522 | 2523 | if ( sampler ) { 2524 | 2525 | var target = channel.target; 2526 | var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. 2527 | var input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; 2528 | var output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; 2529 | 2530 | var inputAccessor = dependencies.accessors[ input ]; 2531 | var outputAccessor = dependencies.accessors[ output ]; 2532 | 2533 | var node = dependencies.nodes[ name ]; 2534 | 2535 | if ( node ) { 2536 | 2537 | node.updateMatrix(); 2538 | node.matrixAutoUpdate = true; 2539 | 2540 | var TypedKeyframeTrack; 2541 | 2542 | switch ( PATH_PROPERTIES[ target.path ] ) { 2543 | 2544 | case PATH_PROPERTIES.weights: 2545 | 2546 | TypedKeyframeTrack = THREE.NumberKeyframeTrack; 2547 | break; 2548 | 2549 | case PATH_PROPERTIES.rotation: 2550 | 2551 | TypedKeyframeTrack = THREE.QuaternionKeyframeTrack; 2552 | break; 2553 | 2554 | case PATH_PROPERTIES.position: 2555 | case PATH_PROPERTIES.scale: 2556 | default: 2557 | 2558 | TypedKeyframeTrack = THREE.VectorKeyframeTrack; 2559 | break; 2560 | 2561 | } 2562 | 2563 | var targetName = node.name ? node.name : node.uuid; 2564 | 2565 | var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear; 2566 | 2567 | var targetNames = []; 2568 | 2569 | if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { 2570 | 2571 | // node should be THREE.Group here but 2572 | // PATH_PROPERTIES.weights(morphTargetInfluences) should be 2573 | // the property of a mesh object under node. 2574 | // So finding targets here. 2575 | 2576 | node.traverse( function ( object ) { 2577 | 2578 | if ( object.isMesh === true && object.material.morphTargets === true ) { 2579 | 2580 | targetNames.push( object.name ? object.name : object.uuid ); 2581 | 2582 | } 2583 | 2584 | } ); 2585 | 2586 | } else { 2587 | 2588 | targetNames.push( targetName ); 2589 | 2590 | } 2591 | 2592 | // KeyframeTrack.optimize() will modify given 'times' and 'values' 2593 | // buffers before creating a truncated copy to keep. Because buffers may 2594 | // be reused by other tracks, make copies here. 2595 | for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) { 2596 | 2597 | var track = new TypedKeyframeTrack( 2598 | targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], 2599 | THREE.AnimationUtils.arraySlice( inputAccessor.array, 0 ), 2600 | THREE.AnimationUtils.arraySlice( outputAccessor.array, 0 ), 2601 | interpolation 2602 | ); 2603 | 2604 | // Here is the trick to enable custom interpolation. 2605 | // Overrides .createInterpolant in a factory method which creates custom interpolation. 2606 | if ( sampler.interpolation === 'CUBICSPLINE' ) { 2607 | 2608 | track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { 2609 | 2610 | // A CUBICSPLINE keyframe in glTF has three output values for each input value, 2611 | // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() 2612 | // must be divided by three to get the interpolant's sampleSize argument. 2613 | 2614 | return new GLTFCubicSplineInterpolant( this.times, this.values, this.getValueSize() / 3, result ); 2615 | 2616 | }; 2617 | 2618 | // Workaround, provide an alternate way to know if the interpolant type is cubis spline to track. 2619 | // track.getInterpolation() doesn't return valid value for custom interpolant. 2620 | track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; 2621 | 2622 | } 2623 | 2624 | tracks.push( track ); 2625 | 2626 | } 2627 | 2628 | } 2629 | 2630 | } 2631 | 2632 | } 2633 | 2634 | var name = animationDef.name !== undefined ? animationDef.name : 'animation_' + animationIndex; 2635 | 2636 | return new THREE.AnimationClip( name, undefined, tracks ); 2637 | 2638 | } ); 2639 | 2640 | }; 2641 | 2642 | /** 2643 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy 2644 | * @param {number} nodeIndex 2645 | * @return {Promise} 2646 | */ 2647 | GLTFParser.prototype.loadNode = function ( nodeIndex ) { 2648 | 2649 | var json = this.json; 2650 | var extensions = this.extensions; 2651 | 2652 | var meshReferences = this.json.meshReferences; 2653 | var meshUses = this.json.meshUses; 2654 | 2655 | var nodeDef = this.json.nodes[ nodeIndex ]; 2656 | 2657 | return this.getMultiDependencies( [ 2658 | 2659 | 'mesh', 2660 | 'skin', 2661 | 'camera', 2662 | 'light' 2663 | 2664 | ] ).then( function ( dependencies ) { 2665 | 2666 | var node; 2667 | 2668 | if ( nodeDef.isBone === true ) { 2669 | 2670 | node = new THREE.Bone(); 2671 | 2672 | } else if ( nodeDef.mesh !== undefined ) { 2673 | 2674 | var mesh = dependencies.meshes[ nodeDef.mesh ]; 2675 | 2676 | node = mesh.clone(); 2677 | 2678 | // for Specular-Glossiness 2679 | if ( mesh.isGroup === true ) { 2680 | 2681 | for ( var i = 0, il = mesh.children.length; i < il; i ++ ) { 2682 | 2683 | var child = mesh.children[ i ]; 2684 | 2685 | if ( child.material && child.material.isGLTFSpecularGlossinessMaterial === true ) { 2686 | 2687 | node.children[ i ].onBeforeRender = child.onBeforeRender; 2688 | 2689 | } 2690 | 2691 | } 2692 | 2693 | } else { 2694 | 2695 | if ( mesh.material && mesh.material.isGLTFSpecularGlossinessMaterial === true ) { 2696 | 2697 | node.onBeforeRender = mesh.onBeforeRender; 2698 | 2699 | } 2700 | 2701 | } 2702 | 2703 | if ( meshReferences[ nodeDef.mesh ] > 1 ) { 2704 | 2705 | node.name += '_instance_' + meshUses[ nodeDef.mesh ] ++; 2706 | 2707 | } 2708 | 2709 | } else if ( nodeDef.camera !== undefined ) { 2710 | 2711 | node = dependencies.cameras[ nodeDef.camera ]; 2712 | 2713 | } else if ( nodeDef.extensions 2714 | && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS ] 2715 | && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS ].light !== undefined ) { 2716 | 2717 | var lights = extensions[ EXTENSIONS.KHR_LIGHTS ].lights; 2718 | node = lights[ nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS ].light ]; 2719 | 2720 | } else { 2721 | 2722 | node = new THREE.Object3D(); 2723 | 2724 | } 2725 | 2726 | if ( nodeDef.name !== undefined ) { 2727 | 2728 | node.name = THREE.PropertyBinding.sanitizeNodeName( nodeDef.name ); 2729 | 2730 | } 2731 | 2732 | if ( nodeDef.extras ) node.userData = nodeDef.extras; 2733 | 2734 | if ( nodeDef.matrix !== undefined ) { 2735 | 2736 | var matrix = new THREE.Matrix4(); 2737 | matrix.fromArray( nodeDef.matrix ); 2738 | node.applyMatrix( matrix ); 2739 | 2740 | } else { 2741 | 2742 | if ( nodeDef.translation !== undefined ) { 2743 | 2744 | node.position.fromArray( nodeDef.translation ); 2745 | 2746 | } 2747 | 2748 | if ( nodeDef.rotation !== undefined ) { 2749 | 2750 | node.quaternion.fromArray( nodeDef.rotation ); 2751 | 2752 | } 2753 | 2754 | if ( nodeDef.scale !== undefined ) { 2755 | 2756 | node.scale.fromArray( nodeDef.scale ); 2757 | 2758 | } 2759 | 2760 | } 2761 | 2762 | return node; 2763 | 2764 | } ); 2765 | 2766 | }; 2767 | 2768 | /** 2769 | * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes 2770 | * @param {number} sceneIndex 2771 | * @return {Promise} 2772 | */ 2773 | GLTFParser.prototype.loadScene = function () { 2774 | 2775 | // scene node hierachy builder 2776 | 2777 | function buildNodeHierachy( nodeId, parentObject, json, allNodes, skins ) { 2778 | 2779 | var node = allNodes[ nodeId ]; 2780 | var nodeDef = json.nodes[ nodeId ]; 2781 | 2782 | // build skeleton here as well 2783 | 2784 | if ( nodeDef.skin !== undefined ) { 2785 | 2786 | var meshes = node.isGroup === true ? node.children : [ node ]; 2787 | 2788 | for ( var i = 0, il = meshes.length; i < il; i ++ ) { 2789 | 2790 | var mesh = meshes[ i ]; 2791 | var skinEntry = skins[ nodeDef.skin ]; 2792 | 2793 | var bones = []; 2794 | var boneInverses = []; 2795 | 2796 | for ( var j = 0, jl = skinEntry.joints.length; j < jl; j ++ ) { 2797 | 2798 | var jointId = skinEntry.joints[ j ]; 2799 | var jointNode = allNodes[ jointId ]; 2800 | 2801 | if ( jointNode ) { 2802 | 2803 | bones.push( jointNode ); 2804 | 2805 | var mat = new THREE.Matrix4(); 2806 | 2807 | if ( skinEntry.inverseBindMatrices !== undefined ) { 2808 | 2809 | mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); 2810 | 2811 | } 2812 | 2813 | boneInverses.push( mat ); 2814 | 2815 | } else { 2816 | 2817 | console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', jointId ); 2818 | 2819 | } 2820 | 2821 | } 2822 | 2823 | mesh.bind( new THREE.Skeleton( bones, boneInverses ), mesh.matrixWorld ); 2824 | 2825 | } 2826 | 2827 | } 2828 | 2829 | // build node hierachy 2830 | 2831 | parentObject.add( node ); 2832 | 2833 | if ( nodeDef.children ) { 2834 | 2835 | var children = nodeDef.children; 2836 | 2837 | for ( var i = 0, il = children.length; i < il; i ++ ) { 2838 | 2839 | var child = children[ i ]; 2840 | buildNodeHierachy( child, node, json, allNodes, skins ); 2841 | 2842 | } 2843 | 2844 | } 2845 | 2846 | } 2847 | 2848 | return function loadScene( sceneIndex ) { 2849 | 2850 | var json = this.json; 2851 | var extensions = this.extensions; 2852 | var sceneDef = this.json.scenes[ sceneIndex ]; 2853 | 2854 | return this.getMultiDependencies( [ 2855 | 2856 | 'node', 2857 | 'skin' 2858 | 2859 | ] ).then( function ( dependencies ) { 2860 | 2861 | var scene = new THREE.Scene(); 2862 | if ( sceneDef.name !== undefined ) scene.name = sceneDef.name; 2863 | 2864 | if ( sceneDef.extras ) scene.userData = sceneDef.extras; 2865 | 2866 | var nodeIds = sceneDef.nodes || []; 2867 | 2868 | for ( var i = 0, il = nodeIds.length; i < il; i ++ ) { 2869 | 2870 | buildNodeHierachy( nodeIds[ i ], scene, json, dependencies.nodes, dependencies.skins ); 2871 | 2872 | } 2873 | 2874 | // Ambient lighting, if present, is always attached to the scene root. 2875 | if ( sceneDef.extensions 2876 | && sceneDef.extensions[ EXTENSIONS.KHR_LIGHTS ] 2877 | && sceneDef.extensions[ EXTENSIONS.KHR_LIGHTS ].light !== undefined ) { 2878 | 2879 | var lights = extensions[ EXTENSIONS.KHR_LIGHTS ].lights; 2880 | scene.add( lights[ sceneDef.extensions[ EXTENSIONS.KHR_LIGHTS ].light ] ); 2881 | 2882 | } 2883 | 2884 | return scene; 2885 | 2886 | } ); 2887 | 2888 | }; 2889 | 2890 | }(); 2891 | 2892 | return GLTFLoader; 2893 | 2894 | } )(); 2895 | --------------------------------------------------------------------------------