├── LICENSE ├── README.md ├── index.html ├── js ├── MarchingCubes.js ├── ViveController.js ├── WebVR.js ├── controls │ └── VRControls.js ├── effects │ └── VREffect.js ├── loaders │ └── OBJLoader.js └── three.js └── models ├── onepointfive_spec.png ├── onepointfive_texture.png └── vr_controller_vive_1_5.obj /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Mr.doob 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webvr-sculpt -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | three.js webvr - htc vive - sculpt 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 332 | 333 | 334 | -------------------------------------------------------------------------------- /js/MarchingCubes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mrdoob / http://mrdoob.com 4 | * Port of http://webglsamples.org/blob/blob.html 5 | */ 6 | 7 | THREE.MarchingCubes = function ( resolution, material, enableUvs, enableColors ) { 8 | 9 | THREE.ImmediateRenderObject.call( this, material ); 10 | 11 | var scope = this; 12 | 13 | // temp buffers used in polygonize 14 | 15 | var vlist = new Float32Array( 12 * 3 ); 16 | var nlist = new Float32Array( 12 * 3 ); 17 | 18 | this.enableUvs = enableUvs !== undefined ? enableUvs : false; 19 | this.enableColors = enableColors !== undefined ? enableColors : false; 20 | 21 | // functions have to be object properties 22 | // prototype functions kill performance 23 | // (tested and it was 4x slower !!!) 24 | 25 | this.init = function ( resolution ) { 26 | 27 | this.resolution = resolution; 28 | 29 | // parameters 30 | 31 | this.isolation = 80.0; 32 | 33 | // size of field, 32 is pushing it in Javascript :) 34 | 35 | this.size = resolution; 36 | this.size2 = this.size * this.size; 37 | this.size3 = this.size2 * this.size; 38 | this.halfsize = this.size / 2.0; 39 | 40 | // deltas 41 | 42 | this.delta = 2.0 / this.size; 43 | this.yd = this.size; 44 | this.zd = this.size2; 45 | 46 | this.field = new Float32Array( this.size3 ); 47 | this.normal_cache = new Float32Array( this.size3 * 3 ); 48 | 49 | // immediate render mode simulator 50 | 51 | this.maxCount = 4096; // TODO: find the fastest size for this buffer 52 | this.count = 0; 53 | 54 | this.hasPositions = false; 55 | this.hasNormals = false; 56 | this.hasColors = false; 57 | this.hasUvs = false; 58 | 59 | this.positionArray = new Float32Array( this.maxCount * 3 ); 60 | this.normalArray = new Float32Array( this.maxCount * 3 ); 61 | 62 | if ( this.enableUvs ) { 63 | 64 | this.uvArray = new Float32Array( this.maxCount * 2 ); 65 | 66 | } 67 | 68 | if ( this.enableColors ) { 69 | 70 | this.colorArray = new Float32Array( this.maxCount * 3 ); 71 | 72 | } 73 | 74 | }; 75 | 76 | /////////////////////// 77 | // Polygonization 78 | /////////////////////// 79 | 80 | function lerp( a, b, t ) { 81 | 82 | return a + ( b - a ) * t; 83 | 84 | } 85 | 86 | function VIntX( q, offset, isol, x, y, z, valp1, valp2 ) { 87 | 88 | var mu = ( isol - valp1 ) / ( valp2 - valp1 ), 89 | nc = scope.normal_cache; 90 | 91 | vlist[ offset + 0 ] = x + mu * scope.delta; 92 | vlist[ offset + 1 ] = y; 93 | vlist[ offset + 2 ] = z; 94 | 95 | nlist[ offset + 0 ] = lerp( nc[ q + 0 ], nc[ q + 3 ], mu ); 96 | nlist[ offset + 1 ] = lerp( nc[ q + 1 ], nc[ q + 4 ], mu ); 97 | nlist[ offset + 2 ] = lerp( nc[ q + 2 ], nc[ q + 5 ], mu ); 98 | 99 | } 100 | 101 | function VIntY( q, offset, isol, x, y, z, valp1, valp2 ) { 102 | 103 | var mu = ( isol - valp1 ) / ( valp2 - valp1 ), 104 | nc = scope.normal_cache; 105 | 106 | vlist[ offset + 0 ] = x; 107 | vlist[ offset + 1 ] = y + mu * scope.delta; 108 | vlist[ offset + 2 ] = z; 109 | 110 | var q2 = q + scope.yd * 3; 111 | 112 | nlist[ offset + 0 ] = lerp( nc[ q + 0 ], nc[ q2 + 0 ], mu ); 113 | nlist[ offset + 1 ] = lerp( nc[ q + 1 ], nc[ q2 + 1 ], mu ); 114 | nlist[ offset + 2 ] = lerp( nc[ q + 2 ], nc[ q2 + 2 ], mu ); 115 | 116 | } 117 | 118 | function VIntZ( q, offset, isol, x, y, z, valp1, valp2 ) { 119 | 120 | var mu = ( isol - valp1 ) / ( valp2 - valp1 ), 121 | nc = scope.normal_cache; 122 | 123 | vlist[ offset + 0 ] = x; 124 | vlist[ offset + 1 ] = y; 125 | vlist[ offset + 2 ] = z + mu * scope.delta; 126 | 127 | var q2 = q + scope.zd * 3; 128 | 129 | nlist[ offset + 0 ] = lerp( nc[ q + 0 ], nc[ q2 + 0 ], mu ); 130 | nlist[ offset + 1 ] = lerp( nc[ q + 1 ], nc[ q2 + 1 ], mu ); 131 | nlist[ offset + 2 ] = lerp( nc[ q + 2 ], nc[ q2 + 2 ], mu ); 132 | 133 | } 134 | 135 | function compNorm( q ) { 136 | 137 | var q3 = q * 3; 138 | 139 | if ( scope.normal_cache[ q3 ] === 0.0 ) { 140 | 141 | scope.normal_cache[ q3 + 0 ] = scope.field[ q - 1 ] - scope.field[ q + 1 ]; 142 | scope.normal_cache[ q3 + 1 ] = scope.field[ q - scope.yd ] - scope.field[ q + scope.yd ]; 143 | scope.normal_cache[ q3 + 2 ] = scope.field[ q - scope.zd ] - scope.field[ q + scope.zd ]; 144 | 145 | } 146 | 147 | } 148 | 149 | // Returns total number of triangles. Fills triangles. 150 | // (this is where most of time is spent - it's inner work of O(n3) loop ) 151 | 152 | function polygonize( fx, fy, fz, q, isol, renderCallback ) { 153 | 154 | // cache indices 155 | var q1 = q + 1, 156 | qy = q + scope.yd, 157 | qz = q + scope.zd, 158 | q1y = q1 + scope.yd, 159 | q1z = q1 + scope.zd, 160 | qyz = q + scope.yd + scope.zd, 161 | q1yz = q1 + scope.yd + scope.zd; 162 | 163 | var cubeindex = 0, 164 | field0 = scope.field[ q ], 165 | field1 = scope.field[ q1 ], 166 | field2 = scope.field[ qy ], 167 | field3 = scope.field[ q1y ], 168 | field4 = scope.field[ qz ], 169 | field5 = scope.field[ q1z ], 170 | field6 = scope.field[ qyz ], 171 | field7 = scope.field[ q1yz ]; 172 | 173 | if ( field0 < isol ) cubeindex |= 1; 174 | if ( field1 < isol ) cubeindex |= 2; 175 | if ( field2 < isol ) cubeindex |= 8; 176 | if ( field3 < isol ) cubeindex |= 4; 177 | if ( field4 < isol ) cubeindex |= 16; 178 | if ( field5 < isol ) cubeindex |= 32; 179 | if ( field6 < isol ) cubeindex |= 128; 180 | if ( field7 < isol ) cubeindex |= 64; 181 | 182 | // if cube is entirely in/out of the surface - bail, nothing to draw 183 | 184 | var bits = THREE.edgeTable[ cubeindex ]; 185 | if ( bits === 0 ) return 0; 186 | 187 | var d = scope.delta, 188 | fx2 = fx + d, 189 | fy2 = fy + d, 190 | fz2 = fz + d; 191 | 192 | // top of the cube 193 | 194 | if ( bits & 1 ) { 195 | 196 | compNorm( q ); 197 | compNorm( q1 ); 198 | VIntX( q * 3, 0, isol, fx, fy, fz, field0, field1 ); 199 | 200 | } 201 | 202 | if ( bits & 2 ) { 203 | 204 | compNorm( q1 ); 205 | compNorm( q1y ); 206 | VIntY( q1 * 3, 3, isol, fx2, fy, fz, field1, field3 ); 207 | 208 | } 209 | 210 | if ( bits & 4 ) { 211 | 212 | compNorm( qy ); 213 | compNorm( q1y ); 214 | VIntX( qy * 3, 6, isol, fx, fy2, fz, field2, field3 ); 215 | 216 | } 217 | 218 | if ( bits & 8 ) { 219 | 220 | compNorm( q ); 221 | compNorm( qy ); 222 | VIntY( q * 3, 9, isol, fx, fy, fz, field0, field2 ); 223 | 224 | } 225 | 226 | // bottom of the cube 227 | 228 | if ( bits & 16 ) { 229 | 230 | compNorm( qz ); 231 | compNorm( q1z ); 232 | VIntX( qz * 3, 12, isol, fx, fy, fz2, field4, field5 ); 233 | 234 | } 235 | 236 | if ( bits & 32 ) { 237 | 238 | compNorm( q1z ); 239 | compNorm( q1yz ); 240 | VIntY( q1z * 3, 15, isol, fx2, fy, fz2, field5, field7 ); 241 | 242 | } 243 | 244 | if ( bits & 64 ) { 245 | 246 | compNorm( qyz ); 247 | compNorm( q1yz ); 248 | VIntX( qyz * 3, 18, isol, fx, fy2, fz2, field6, field7 ); 249 | 250 | } 251 | 252 | if ( bits & 128 ) { 253 | 254 | compNorm( qz ); 255 | compNorm( qyz ); 256 | VIntY( qz * 3, 21, isol, fx, fy, fz2, field4, field6 ); 257 | 258 | } 259 | 260 | // vertical lines of the cube 261 | 262 | if ( bits & 256 ) { 263 | 264 | compNorm( q ); 265 | compNorm( qz ); 266 | VIntZ( q * 3, 24, isol, fx, fy, fz, field0, field4 ); 267 | 268 | } 269 | 270 | if ( bits & 512 ) { 271 | 272 | compNorm( q1 ); 273 | compNorm( q1z ); 274 | VIntZ( q1 * 3, 27, isol, fx2, fy, fz, field1, field5 ); 275 | 276 | } 277 | 278 | if ( bits & 1024 ) { 279 | 280 | compNorm( q1y ); 281 | compNorm( q1yz ); 282 | VIntZ( q1y * 3, 30, isol, fx2, fy2, fz, field3, field7 ); 283 | 284 | } 285 | 286 | if ( bits & 2048 ) { 287 | 288 | compNorm( qy ); 289 | compNorm( qyz ); 290 | VIntZ( qy * 3, 33, isol, fx, fy2, fz, field2, field6 ); 291 | 292 | } 293 | 294 | cubeindex <<= 4; // re-purpose cubeindex into an offset into triTable 295 | 296 | var o1, o2, o3, numtris = 0, i = 0; 297 | 298 | // here is where triangles are created 299 | 300 | while ( THREE.triTable[ cubeindex + i ] != - 1 ) { 301 | 302 | o1 = cubeindex + i; 303 | o2 = o1 + 1; 304 | o3 = o1 + 2; 305 | 306 | posnormtriv( vlist, nlist, 307 | 3 * THREE.triTable[ o1 ], 308 | 3 * THREE.triTable[ o2 ], 309 | 3 * THREE.triTable[ o3 ], 310 | renderCallback ); 311 | 312 | i += 3; 313 | numtris ++; 314 | 315 | } 316 | 317 | return numtris; 318 | 319 | } 320 | 321 | ///////////////////////////////////// 322 | // Immediate render mode simulator 323 | ///////////////////////////////////// 324 | 325 | function posnormtriv( pos, norm, o1, o2, o3, renderCallback ) { 326 | 327 | var c = scope.count * 3; 328 | 329 | // positions 330 | 331 | scope.positionArray[ c + 0 ] = pos[ o1 ]; 332 | scope.positionArray[ c + 1 ] = pos[ o1 + 1 ]; 333 | scope.positionArray[ c + 2 ] = pos[ o1 + 2 ]; 334 | 335 | scope.positionArray[ c + 3 ] = pos[ o2 ]; 336 | scope.positionArray[ c + 4 ] = pos[ o2 + 1 ]; 337 | scope.positionArray[ c + 5 ] = pos[ o2 + 2 ]; 338 | 339 | scope.positionArray[ c + 6 ] = pos[ o3 ]; 340 | scope.positionArray[ c + 7 ] = pos[ o3 + 1 ]; 341 | scope.positionArray[ c + 8 ] = pos[ o3 + 2 ]; 342 | 343 | // normals 344 | 345 | scope.normalArray[ c + 0 ] = norm[ o1 ]; 346 | scope.normalArray[ c + 1 ] = norm[ o1 + 1 ]; 347 | scope.normalArray[ c + 2 ] = norm[ o1 + 2 ]; 348 | 349 | scope.normalArray[ c + 3 ] = norm[ o2 ]; 350 | scope.normalArray[ c + 4 ] = norm[ o2 + 1 ]; 351 | scope.normalArray[ c + 5 ] = norm[ o2 + 2 ]; 352 | 353 | scope.normalArray[ c + 6 ] = norm[ o3 ]; 354 | scope.normalArray[ c + 7 ] = norm[ o3 + 1 ]; 355 | scope.normalArray[ c + 8 ] = norm[ o3 + 2 ]; 356 | 357 | // uvs 358 | 359 | if ( scope.enableUvs ) { 360 | 361 | var d = scope.count * 2; 362 | 363 | scope.uvArray[ d + 0 ] = pos[ o1 ]; 364 | scope.uvArray[ d + 1 ] = pos[ o1 + 2 ]; 365 | 366 | scope.uvArray[ d + 2 ] = pos[ o2 ]; 367 | scope.uvArray[ d + 3 ] = pos[ o2 + 2 ]; 368 | 369 | scope.uvArray[ d + 4 ] = pos[ o3 ]; 370 | scope.uvArray[ d + 5 ] = pos[ o3 + 2 ]; 371 | 372 | } 373 | 374 | // colors 375 | 376 | if ( scope.enableColors ) { 377 | 378 | scope.colorArray[ c + 0 ] = pos[ o1 ]; 379 | scope.colorArray[ c + 1 ] = pos[ o1 + 1 ]; 380 | scope.colorArray[ c + 2 ] = pos[ o1 + 2 ]; 381 | 382 | scope.colorArray[ c + 3 ] = pos[ o2 ]; 383 | scope.colorArray[ c + 4 ] = pos[ o2 + 1 ]; 384 | scope.colorArray[ c + 5 ] = pos[ o2 + 2 ]; 385 | 386 | scope.colorArray[ c + 6 ] = pos[ o3 ]; 387 | scope.colorArray[ c + 7 ] = pos[ o3 + 1 ]; 388 | scope.colorArray[ c + 8 ] = pos[ o3 + 2 ]; 389 | 390 | } 391 | 392 | scope.count += 3; 393 | 394 | if ( scope.count >= scope.maxCount - 3 ) { 395 | 396 | scope.hasPositions = true; 397 | scope.hasNormals = true; 398 | 399 | if ( scope.enableUvs ) { 400 | 401 | scope.hasUvs = true; 402 | 403 | } 404 | 405 | if ( scope.enableColors ) { 406 | 407 | scope.hasColors = true; 408 | 409 | } 410 | 411 | renderCallback( scope ); 412 | 413 | } 414 | 415 | }; 416 | 417 | this.begin = function () { 418 | 419 | this.count = 0; 420 | 421 | this.hasPositions = false; 422 | this.hasNormals = false; 423 | this.hasUvs = false; 424 | this.hasColors = false; 425 | 426 | }; 427 | 428 | this.end = function ( renderCallback ) { 429 | 430 | if ( this.count === 0 ) return; 431 | 432 | for ( var i = this.count * 3; i < this.positionArray.length; i ++ ) { 433 | 434 | this.positionArray[ i ] = 0.0; 435 | 436 | } 437 | 438 | this.hasPositions = true; 439 | this.hasNormals = true; 440 | 441 | if ( this.enableUvs ) { 442 | 443 | this.hasUvs = true; 444 | 445 | } 446 | 447 | if ( this.enableColors ) { 448 | 449 | this.hasColors = true; 450 | 451 | } 452 | 453 | renderCallback( this ); 454 | 455 | }; 456 | 457 | ///////////////////////////////////// 458 | // Metaballs 459 | ///////////////////////////////////// 460 | 461 | // Adds a reciprocal ball (nice and blobby) that, to be fast, fades to zero after 462 | // a fixed distance, determined by strength and subtract. 463 | 464 | this.addBall = function ( ballx, bally, ballz, strength, subtract ) { 465 | 466 | var sign = Math.sign( strength ); 467 | strength = Math.abs( strength ); 468 | 469 | // Let's solve the equation to find the radius: 470 | // 1.0 / (0.000001 + radius^2) * strength - subtract = 0 471 | // strength / (radius^2) = subtract 472 | // strength = subtract * radius^2 473 | // radius^2 = strength / subtract 474 | // radius = sqrt(strength / subtract) 475 | 476 | var radius = this.size * Math.sqrt( strength / subtract ), 477 | zs = ballz * this.size, 478 | ys = bally * this.size, 479 | xs = ballx * this.size; 480 | 481 | var min_z = Math.floor( zs - radius ); if ( min_z < 1 ) min_z = 1; 482 | var max_z = Math.floor( zs + radius ); if ( max_z > this.size - 1 ) max_z = this.size - 1; 483 | var min_y = Math.floor( ys - radius ); if ( min_y < 1 ) min_y = 1; 484 | var max_y = Math.floor( ys + radius ); if ( max_y > this.size - 1 ) max_y = this.size - 1; 485 | var min_x = Math.floor( xs - radius ); if ( min_x < 1 ) min_x = 1; 486 | var max_x = Math.floor( xs + radius ); if ( max_x > this.size - 1 ) max_x = this.size - 1; 487 | 488 | 489 | // Don't polygonize in the outer layer because normals aren't 490 | // well-defined there. 491 | 492 | var x, y, z, y_offset, z_offset, fx, fy, fz, fz2, fy2, val; 493 | 494 | for ( z = min_z; z < max_z; z ++ ) { 495 | 496 | z_offset = this.size2 * z, 497 | fz = z / this.size - ballz, 498 | fz2 = fz * fz; 499 | 500 | for ( y = min_y; y < max_y; y ++ ) { 501 | 502 | y_offset = z_offset + this.size * y; 503 | fy = y / this.size - bally; 504 | fy2 = fy * fy; 505 | 506 | for ( x = min_x; x < max_x; x ++ ) { 507 | 508 | fx = x / this.size - ballx; 509 | val = strength / ( 0.000001 + fx * fx + fy2 + fz2 ) - subtract; 510 | if ( val > 0.0 ) this.field[ y_offset + x ] += val * sign; 511 | 512 | } 513 | 514 | } 515 | 516 | } 517 | 518 | }; 519 | 520 | this.addPlaneX = function( strength, subtract ) { 521 | 522 | var x, y, z, xx, val, xdiv, cxy, 523 | 524 | // cache attribute lookups 525 | size = this.size, 526 | yd = this.yd, 527 | zd = this.zd, 528 | field = this.field, 529 | 530 | dist = size * Math.sqrt( strength / subtract ); 531 | 532 | if ( dist > size ) dist = size; 533 | 534 | for ( x = 0; x < dist; x ++ ) { 535 | 536 | xdiv = x / size; 537 | xx = xdiv * xdiv; 538 | val = strength / ( 0.0001 + xx ) - subtract; 539 | 540 | if ( val > 0.0 ) { 541 | 542 | for ( y = 0; y < size; y ++ ) { 543 | 544 | cxy = x + y * yd; 545 | 546 | for ( z = 0; z < size; z ++ ) { 547 | 548 | field[ zd * z + cxy ] += val; 549 | 550 | } 551 | 552 | } 553 | 554 | } 555 | 556 | } 557 | 558 | }; 559 | 560 | this.addPlaneY = function( strength, subtract ) { 561 | 562 | var x, y, z, yy, val, ydiv, cy, cxy, 563 | 564 | // cache attribute lookups 565 | size = this.size, 566 | yd = this.yd, 567 | zd = this.zd, 568 | field = this.field, 569 | 570 | dist = size * Math.sqrt( strength / subtract ); 571 | 572 | if ( dist > size ) dist = size; 573 | 574 | for ( y = 0; y < dist; y ++ ) { 575 | 576 | ydiv = y / size; 577 | yy = ydiv * ydiv; 578 | val = strength / ( 0.0001 + yy ) - subtract; 579 | 580 | if ( val > 0.0 ) { 581 | 582 | cy = y * yd; 583 | 584 | for ( x = 0; x < size; x ++ ) { 585 | 586 | cxy = cy + x; 587 | 588 | for ( z = 0; z < size; z ++ ) 589 | field[ zd * z + cxy ] += val; 590 | 591 | } 592 | 593 | } 594 | 595 | } 596 | 597 | }; 598 | 599 | this.addPlaneZ = function( strength, subtract ) { 600 | 601 | var x, y, z, zz, val, zdiv, cz, cyz, 602 | 603 | // cache attribute lookups 604 | size = this.size, 605 | yd = this.yd, 606 | zd = this.zd, 607 | field = this.field, 608 | 609 | dist = size * Math.sqrt( strength / subtract ); 610 | 611 | if ( dist > size ) dist = size; 612 | 613 | for ( z = 0; z < dist; z ++ ) { 614 | 615 | zdiv = z / size; 616 | zz = zdiv * zdiv; 617 | val = strength / ( 0.0001 + zz ) - subtract; 618 | if ( val > 0.0 ) { 619 | 620 | cz = zd * z; 621 | 622 | for ( y = 0; y < size; y ++ ) { 623 | 624 | cyz = cz + y * yd; 625 | 626 | for ( x = 0; x < size; x ++ ) 627 | field[ cyz + x ] += val; 628 | 629 | } 630 | 631 | } 632 | 633 | } 634 | 635 | }; 636 | 637 | ///////////////////////////////////// 638 | // Updates 639 | ///////////////////////////////////// 640 | 641 | this.reset = function () { 642 | 643 | var i; 644 | 645 | // wipe the normal cache 646 | 647 | for ( i = 0; i < this.size3; i ++ ) { 648 | 649 | this.normal_cache[ i * 3 ] = 0.0; 650 | this.field[ i ] = 0.0; 651 | 652 | } 653 | 654 | }; 655 | 656 | this.render = function ( renderCallback ) { 657 | 658 | this.begin(); 659 | 660 | // Triangulate. Yeah, this is slow. 661 | 662 | var smin2 = this.size - 2; 663 | 664 | for ( var z = 1; z < smin2; z ++ ) { 665 | 666 | var z_offset = this.size2 * z; 667 | var fz = ( z - this.halfsize ) / this.halfsize; //+ 1 668 | 669 | for ( var y = 1; y < smin2; y ++ ) { 670 | 671 | var y_offset = z_offset + this.size * y; 672 | var fy = ( y - this.halfsize ) / this.halfsize; //+ 1 673 | 674 | for ( var x = 1; x < smin2; x ++ ) { 675 | 676 | var fx = ( x - this.halfsize ) / this.halfsize; //+ 1 677 | var q = y_offset + x; 678 | 679 | polygonize( fx, fy, fz, q, this.isolation, renderCallback ); 680 | 681 | } 682 | 683 | } 684 | 685 | } 686 | 687 | this.end( renderCallback ); 688 | 689 | }; 690 | 691 | this.generateGeometry = function() { 692 | 693 | var start = 0, geo = new THREE.Geometry(); 694 | var normals = []; 695 | 696 | var geo_callback = function( object ) { 697 | 698 | for ( var i = 0; i < object.count; i ++ ) { 699 | 700 | var vertex = new THREE.Vector3().fromArray( object.positionArray, i * 3 ); 701 | var normal = new THREE.Vector3().fromArray( object.normalArray, i * 3 ); 702 | 703 | geo.vertices.push( vertex ); 704 | normals.push( normal ); 705 | 706 | } 707 | 708 | var nfaces = object.count / 3; 709 | 710 | for ( i = 0; i < nfaces; i ++ ) { 711 | 712 | var a = ( start + i ) * 3; 713 | var b = a + 1; 714 | var c = a + 2; 715 | 716 | var na = normals[ a ]; 717 | var nb = normals[ b ]; 718 | var nc = normals[ c ]; 719 | 720 | var face = new THREE.Face3( a, b, c, [ na, nb, nc ] ); 721 | geo.faces.push( face ); 722 | 723 | } 724 | 725 | start += nfaces; 726 | object.count = 0; 727 | 728 | }; 729 | 730 | this.render( geo_callback ); 731 | 732 | // console.log( "generated " + geo.faces.length + " triangles" ); 733 | 734 | return geo; 735 | 736 | }; 737 | 738 | this.init( resolution ); 739 | 740 | }; 741 | 742 | THREE.MarchingCubes.prototype = Object.create( THREE.ImmediateRenderObject.prototype ); 743 | THREE.MarchingCubes.prototype.constructor = THREE.MarchingCubes; 744 | 745 | 746 | ///////////////////////////////////// 747 | // Marching cubes lookup tables 748 | ///////////////////////////////////// 749 | 750 | // These tables are straight from Paul Bourke's page: 751 | // http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/ 752 | // who in turn got them from Cory Gene Bloyd. 753 | 754 | THREE.edgeTable = new Int32Array( [ 755 | 0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 756 | 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 757 | 0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 758 | 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 759 | 0x230, 0x339, 0x33, 0x13a, 0x636, 0x73f, 0x435, 0x53c, 760 | 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 761 | 0x3a0, 0x2a9, 0x1a3, 0xaa, 0x7a6, 0x6af, 0x5a5, 0x4ac, 762 | 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 763 | 0x460, 0x569, 0x663, 0x76a, 0x66, 0x16f, 0x265, 0x36c, 764 | 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 765 | 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff, 0x3f5, 0x2fc, 766 | 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 767 | 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55, 0x15c, 768 | 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 769 | 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc, 770 | 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 771 | 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 772 | 0xcc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 773 | 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 774 | 0x15c, 0x55, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 775 | 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 776 | 0x2fc, 0x3f5, 0xff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 777 | 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 778 | 0x36c, 0x265, 0x16f, 0x66, 0x76a, 0x663, 0x569, 0x460, 779 | 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 780 | 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa, 0x1a3, 0x2a9, 0x3a0, 781 | 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 782 | 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33, 0x339, 0x230, 783 | 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 784 | 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99, 0x190, 785 | 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 786 | 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ] ); 787 | 788 | THREE.triTable = new Int32Array( [ 789 | - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 790 | 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 791 | 0, 1, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 792 | 1, 8, 3, 9, 8, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 793 | 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 794 | 0, 8, 3, 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 795 | 9, 2, 10, 0, 2, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 796 | 2, 8, 3, 2, 10, 8, 10, 9, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 797 | 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 798 | 0, 11, 2, 8, 11, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 799 | 1, 9, 0, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 800 | 1, 11, 2, 1, 9, 11, 9, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 801 | 3, 10, 1, 11, 10, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 802 | 0, 10, 1, 0, 8, 10, 8, 11, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 803 | 3, 9, 0, 3, 11, 9, 11, 10, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 804 | 9, 8, 10, 10, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 805 | 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 806 | 4, 3, 0, 7, 3, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 807 | 0, 1, 9, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 808 | 4, 1, 9, 4, 7, 1, 7, 3, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 809 | 1, 2, 10, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 810 | 3, 4, 7, 3, 0, 4, 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 811 | 9, 2, 10, 9, 0, 2, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 812 | 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, - 1, - 1, - 1, - 1, 813 | 8, 4, 7, 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 814 | 11, 4, 7, 11, 2, 4, 2, 0, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 815 | 9, 0, 1, 8, 4, 7, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 816 | 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, - 1, - 1, - 1, - 1, 817 | 3, 10, 1, 3, 11, 10, 7, 8, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 818 | 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, - 1, - 1, - 1, - 1, 819 | 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, - 1, - 1, - 1, - 1, 820 | 4, 7, 11, 4, 11, 9, 9, 11, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 821 | 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 822 | 9, 5, 4, 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 823 | 0, 5, 4, 1, 5, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 824 | 8, 5, 4, 8, 3, 5, 3, 1, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 825 | 1, 2, 10, 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 826 | 3, 0, 8, 1, 2, 10, 4, 9, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 827 | 5, 2, 10, 5, 4, 2, 4, 0, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 828 | 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, - 1, - 1, - 1, - 1, 829 | 9, 5, 4, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 830 | 0, 11, 2, 0, 8, 11, 4, 9, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 831 | 0, 5, 4, 0, 1, 5, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 832 | 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, - 1, - 1, - 1, - 1, 833 | 10, 3, 11, 10, 1, 3, 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 834 | 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, - 1, - 1, - 1, - 1, 835 | 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, - 1, - 1, - 1, - 1, 836 | 5, 4, 8, 5, 8, 10, 10, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 837 | 9, 7, 8, 5, 7, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 838 | 9, 3, 0, 9, 5, 3, 5, 7, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 839 | 0, 7, 8, 0, 1, 7, 1, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 840 | 1, 5, 3, 3, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 841 | 9, 7, 8, 9, 5, 7, 10, 1, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 842 | 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, - 1, - 1, - 1, - 1, 843 | 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, - 1, - 1, - 1, - 1, 844 | 2, 10, 5, 2, 5, 3, 3, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 845 | 7, 9, 5, 7, 8, 9, 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 846 | 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, - 1, - 1, - 1, - 1, 847 | 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, - 1, - 1, - 1, - 1, 848 | 11, 2, 1, 11, 1, 7, 7, 1, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 849 | 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, - 1, - 1, - 1, - 1, 850 | 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, - 1, 851 | 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, - 1, 852 | 11, 10, 5, 7, 11, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 853 | 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 854 | 0, 8, 3, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 855 | 9, 0, 1, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 856 | 1, 8, 3, 1, 9, 8, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 857 | 1, 6, 5, 2, 6, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 858 | 1, 6, 5, 1, 2, 6, 3, 0, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 859 | 9, 6, 5, 9, 0, 6, 0, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 860 | 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, - 1, - 1, - 1, - 1, 861 | 2, 3, 11, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 862 | 11, 0, 8, 11, 2, 0, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 863 | 0, 1, 9, 2, 3, 11, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 864 | 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, - 1, - 1, - 1, - 1, 865 | 6, 3, 11, 6, 5, 3, 5, 1, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 866 | 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, - 1, - 1, - 1, - 1, 867 | 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, - 1, - 1, - 1, - 1, 868 | 6, 5, 9, 6, 9, 11, 11, 9, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 869 | 5, 10, 6, 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 870 | 4, 3, 0, 4, 7, 3, 6, 5, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 871 | 1, 9, 0, 5, 10, 6, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 872 | 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, - 1, - 1, - 1, - 1, 873 | 6, 1, 2, 6, 5, 1, 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 874 | 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, - 1, - 1, - 1, - 1, 875 | 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, - 1, - 1, - 1, - 1, 876 | 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, - 1, 877 | 3, 11, 2, 7, 8, 4, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 878 | 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, - 1, - 1, - 1, - 1, 879 | 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, - 1, - 1, - 1, - 1, 880 | 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, - 1, 881 | 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, - 1, - 1, - 1, - 1, 882 | 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, - 1, 883 | 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, - 1, 884 | 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, - 1, - 1, - 1, - 1, 885 | 10, 4, 9, 6, 4, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 886 | 4, 10, 6, 4, 9, 10, 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 887 | 10, 0, 1, 10, 6, 0, 6, 4, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 888 | 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, - 1, - 1, - 1, - 1, 889 | 1, 4, 9, 1, 2, 4, 2, 6, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 890 | 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, - 1, - 1, - 1, - 1, 891 | 0, 2, 4, 4, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 892 | 8, 3, 2, 8, 2, 4, 4, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 893 | 10, 4, 9, 10, 6, 4, 11, 2, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 894 | 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, - 1, - 1, - 1, - 1, 895 | 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, - 1, - 1, - 1, - 1, 896 | 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, - 1, 897 | 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, - 1, - 1, - 1, - 1, 898 | 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, - 1, 899 | 3, 11, 6, 3, 6, 0, 0, 6, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 900 | 6, 4, 8, 11, 6, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 901 | 7, 10, 6, 7, 8, 10, 8, 9, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 902 | 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, - 1, - 1, - 1, - 1, 903 | 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, - 1, - 1, - 1, - 1, 904 | 10, 6, 7, 10, 7, 1, 1, 7, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 905 | 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, - 1, - 1, - 1, - 1, 906 | 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, - 1, 907 | 7, 8, 0, 7, 0, 6, 6, 0, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 908 | 7, 3, 2, 6, 7, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 909 | 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, - 1, - 1, - 1, - 1, 910 | 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, - 1, 911 | 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, - 1, 912 | 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, - 1, - 1, - 1, - 1, 913 | 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, - 1, 914 | 0, 9, 1, 11, 6, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 915 | 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, - 1, - 1, - 1, - 1, 916 | 7, 11, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 917 | 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 918 | 3, 0, 8, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 919 | 0, 1, 9, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 920 | 8, 1, 9, 8, 3, 1, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 921 | 10, 1, 2, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 922 | 1, 2, 10, 3, 0, 8, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 923 | 2, 9, 0, 2, 10, 9, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 924 | 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, - 1, - 1, - 1, - 1, 925 | 7, 2, 3, 6, 2, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 926 | 7, 0, 8, 7, 6, 0, 6, 2, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 927 | 2, 7, 6, 2, 3, 7, 0, 1, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 928 | 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, - 1, - 1, - 1, - 1, 929 | 10, 7, 6, 10, 1, 7, 1, 3, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 930 | 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, - 1, - 1, - 1, - 1, 931 | 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, - 1, - 1, - 1, - 1, 932 | 7, 6, 10, 7, 10, 8, 8, 10, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 933 | 6, 8, 4, 11, 8, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 934 | 3, 6, 11, 3, 0, 6, 0, 4, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 935 | 8, 6, 11, 8, 4, 6, 9, 0, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 936 | 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, - 1, - 1, - 1, - 1, 937 | 6, 8, 4, 6, 11, 8, 2, 10, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 938 | 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, - 1, - 1, - 1, - 1, 939 | 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, - 1, - 1, - 1, - 1, 940 | 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, - 1, 941 | 8, 2, 3, 8, 4, 2, 4, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 942 | 0, 4, 2, 4, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 943 | 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, - 1, - 1, - 1, - 1, 944 | 1, 9, 4, 1, 4, 2, 2, 4, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 945 | 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, - 1, - 1, - 1, - 1, 946 | 10, 1, 0, 10, 0, 6, 6, 0, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 947 | 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, - 1, 948 | 10, 9, 4, 6, 10, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 949 | 4, 9, 5, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 950 | 0, 8, 3, 4, 9, 5, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 951 | 5, 0, 1, 5, 4, 0, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 952 | 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, - 1, - 1, - 1, - 1, 953 | 9, 5, 4, 10, 1, 2, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 954 | 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, - 1, - 1, - 1, - 1, 955 | 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, - 1, - 1, - 1, - 1, 956 | 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, - 1, 957 | 7, 2, 3, 7, 6, 2, 5, 4, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 958 | 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, - 1, - 1, - 1, - 1, 959 | 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, - 1, - 1, - 1, - 1, 960 | 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, - 1, 961 | 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, - 1, - 1, - 1, - 1, 962 | 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, - 1, 963 | 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, - 1, 964 | 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, - 1, - 1, - 1, - 1, 965 | 6, 9, 5, 6, 11, 9, 11, 8, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 966 | 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, - 1, - 1, - 1, - 1, 967 | 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, - 1, - 1, - 1, - 1, 968 | 6, 11, 3, 6, 3, 5, 5, 3, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 969 | 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, - 1, - 1, - 1, - 1, 970 | 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, - 1, 971 | 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, - 1, 972 | 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, - 1, - 1, - 1, - 1, 973 | 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, - 1, - 1, - 1, - 1, 974 | 9, 5, 6, 9, 6, 0, 0, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 975 | 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, - 1, 976 | 1, 5, 6, 2, 1, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 977 | 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, - 1, 978 | 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, - 1, - 1, - 1, - 1, 979 | 0, 3, 8, 5, 6, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 980 | 10, 5, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 981 | 11, 5, 10, 7, 5, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 982 | 11, 5, 10, 11, 7, 5, 8, 3, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 983 | 5, 11, 7, 5, 10, 11, 1, 9, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 984 | 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, - 1, - 1, - 1, - 1, 985 | 11, 1, 2, 11, 7, 1, 7, 5, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 986 | 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, - 1, - 1, - 1, - 1, 987 | 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, - 1, - 1, - 1, - 1, 988 | 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, - 1, 989 | 2, 5, 10, 2, 3, 5, 3, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 990 | 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, - 1, - 1, - 1, - 1, 991 | 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, - 1, - 1, - 1, - 1, 992 | 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, - 1, 993 | 1, 3, 5, 3, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 994 | 0, 8, 7, 0, 7, 1, 1, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 995 | 9, 0, 3, 9, 3, 5, 5, 3, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 996 | 9, 8, 7, 5, 9, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 997 | 5, 8, 4, 5, 10, 8, 10, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 998 | 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, - 1, - 1, - 1, - 1, 999 | 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, - 1, - 1, - 1, - 1, 1000 | 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, - 1, 1001 | 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, - 1, - 1, - 1, - 1, 1002 | 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, - 1, 1003 | 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, - 1, 1004 | 9, 4, 5, 2, 11, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1005 | 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, - 1, - 1, - 1, - 1, 1006 | 5, 10, 2, 5, 2, 4, 4, 2, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1007 | 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, - 1, 1008 | 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, - 1, - 1, - 1, - 1, 1009 | 8, 4, 5, 8, 5, 3, 3, 5, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1010 | 0, 4, 5, 1, 0, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1011 | 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, - 1, - 1, - 1, - 1, 1012 | 9, 4, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1013 | 4, 11, 7, 4, 9, 11, 9, 10, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1014 | 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, - 1, - 1, - 1, - 1, 1015 | 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, - 1, - 1, - 1, - 1, 1016 | 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, - 1, 1017 | 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, - 1, - 1, - 1, - 1, 1018 | 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, - 1, 1019 | 11, 7, 4, 11, 4, 2, 2, 4, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1020 | 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, - 1, - 1, - 1, - 1, 1021 | 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, - 1, - 1, - 1, - 1, 1022 | 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, - 1, 1023 | 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, - 1, 1024 | 1, 10, 2, 8, 7, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1025 | 4, 9, 1, 4, 1, 7, 7, 1, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1026 | 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, - 1, - 1, - 1, - 1, 1027 | 4, 0, 3, 7, 4, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1028 | 4, 8, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1029 | 9, 10, 8, 10, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1030 | 3, 0, 9, 3, 9, 11, 11, 9, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1031 | 0, 1, 10, 0, 10, 8, 8, 10, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1032 | 3, 1, 10, 11, 3, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1033 | 1, 2, 11, 1, 11, 9, 9, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1034 | 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, - 1, - 1, - 1, - 1, 1035 | 0, 2, 11, 8, 0, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1036 | 3, 2, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1037 | 2, 3, 8, 2, 8, 10, 10, 8, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1038 | 9, 10, 2, 0, 9, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1039 | 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, - 1, - 1, - 1, - 1, 1040 | 1, 10, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1041 | 1, 3, 8, 9, 1, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1042 | 0, 9, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1043 | 0, 3, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1044 | - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 ] ); 1045 | -------------------------------------------------------------------------------- /js/ViveController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com 3 | * @author stewdio / http://stewd.io 4 | */ 5 | 6 | THREE.ViveController = function ( id ) { 7 | 8 | THREE.Object3D.call( this ); 9 | 10 | var scope = this; 11 | var gamepad; 12 | 13 | var axes = [ 0, 0 ]; 14 | var thumbpadIsPressed = false; 15 | var triggerIsPressed = false; 16 | var gripsArePressed = false; 17 | var menuIsPressed = false; 18 | 19 | function findGamepad( id ) { 20 | 21 | // Iterate across gamepads as Vive Controllers may not be 22 | // in position 0 and 1. 23 | 24 | var gamepads = navigator.getGamepads(); 25 | 26 | for ( var i = 0, j = 0; i < 4; i ++ ) { 27 | 28 | var gamepad = gamepads[ i ]; 29 | 30 | if ( gamepad && gamepad.id === 'OpenVR Gamepad' ) { 31 | 32 | if ( j === id ) return gamepad; 33 | 34 | j ++; 35 | 36 | } 37 | 38 | } 39 | 40 | } 41 | 42 | this.matrixAutoUpdate = false; 43 | this.standingMatrix = new THREE.Matrix4(); 44 | 45 | this.getGamepad = function () { 46 | 47 | return gamepad; 48 | 49 | }; 50 | 51 | this.getButtonState = function ( button ) { 52 | 53 | if ( button === 'thumbpad' ) return thumbpadIsPressed; 54 | if ( button === 'trigger' ) return triggerIsPressed; 55 | if ( button === 'grips' ) return gripsArePressed; 56 | if ( button === 'menu' ) return menuIsPressed; 57 | 58 | }; 59 | 60 | this.update = function () { 61 | 62 | gamepad = findGamepad( id ); 63 | 64 | if ( gamepad !== undefined && gamepad.pose !== null ) { 65 | 66 | // Position and orientation. 67 | 68 | var pose = gamepad.pose; 69 | 70 | scope.position.fromArray( pose.position ); 71 | scope.quaternion.fromArray( pose.orientation ); 72 | scope.matrix.compose( scope.position, scope.quaternion, scope.scale ); 73 | scope.matrix.multiplyMatrices( scope.standingMatrix, scope.matrix ); 74 | scope.matrixWorldNeedsUpdate = true; 75 | scope.visible = true; 76 | 77 | // Thumbpad and Buttons. 78 | 79 | if ( axes[ 0 ] !== gamepad.axes[ 0 ] || axes[ 1 ] !== gamepad.axes[ 1 ] ) { 80 | 81 | axes[ 0 ] = gamepad.axes[ 0 ]; // X axis: -1 = Left, +1 = Right. 82 | axes[ 1 ] = gamepad.axes[ 1 ]; // Y axis: -1 = Bottom, +1 = Top. 83 | scope.dispatchEvent( { type: 'axischanged', axes: axes } ); 84 | 85 | } 86 | 87 | if ( thumbpadIsPressed !== gamepad.buttons[ 0 ].pressed ) { 88 | 89 | thumbpadIsPressed = gamepad.buttons[ 0 ].pressed; 90 | scope.dispatchEvent( { type: thumbpadIsPressed ? 'thumbpaddown' : 'thumbpadup' } ); 91 | 92 | } 93 | 94 | if ( triggerIsPressed !== gamepad.buttons[ 1 ].pressed ) { 95 | 96 | triggerIsPressed = gamepad.buttons[ 1 ].pressed; 97 | scope.dispatchEvent( { type: triggerIsPressed ? 'triggerdown' : 'triggerup' } ); 98 | 99 | } 100 | 101 | if ( gripsArePressed !== gamepad.buttons[ 2 ].pressed ) { 102 | 103 | gripsArePressed = gamepad.buttons[ 2 ].pressed; 104 | scope.dispatchEvent( { type: gripsArePressed ? 'gripsdown' : 'gripsup' } ); 105 | 106 | } 107 | 108 | if ( menuIsPressed !== gamepad.buttons[ 3 ].pressed ) { 109 | 110 | menuIsPressed = gamepad.buttons[ 3 ].pressed; 111 | scope.dispatchEvent( { type: menuIsPressed ? 'menudown' : 'menuup' } ); 112 | 113 | } 114 | 115 | } else { 116 | 117 | scope.visible = false; 118 | 119 | } 120 | 121 | }; 122 | 123 | }; 124 | 125 | THREE.ViveController.prototype = Object.create( THREE.Object3D.prototype ); 126 | THREE.ViveController.prototype.constructor = THREE.ViveController; 127 | -------------------------------------------------------------------------------- /js/WebVR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com 3 | * Based on @tojiro's vr-samples-utils.js 4 | */ 5 | 6 | var WEBVR = { 7 | 8 | isLatestAvailable: function () { 9 | 10 | return navigator.getVRDisplays !== undefined; 11 | 12 | }, 13 | 14 | isAvailable: function () { 15 | 16 | return navigator.getVRDisplays !== undefined || navigator.getVRDevices !== undefined; 17 | 18 | }, 19 | 20 | getMessage: function () { 21 | 22 | var message; 23 | 24 | if ( navigator.getVRDisplays ) { 25 | 26 | navigator.getVRDisplays().then( function ( displays ) { 27 | 28 | if ( displays.length === 0 ) message = 'WebVR supported, but no VRDisplays found.'; 29 | 30 | } ); 31 | 32 | } else if ( navigator.getVRDevices ) { 33 | 34 | message = 'Your browser supports WebVR but not the latest version. See webvr.info for more info.'; 35 | 36 | } else { 37 | 38 | message = 'Your browser does not support WebVR. See webvr.info for assistance.'; 39 | 40 | } 41 | 42 | if ( message !== undefined ) { 43 | 44 | var container = document.createElement( 'div' ); 45 | container.style.position = 'absolute'; 46 | container.style.left = '0'; 47 | container.style.top = '0'; 48 | container.style.right = '0'; 49 | container.style.zIndex = '999'; 50 | container.align = 'center'; 51 | 52 | var error = document.createElement( 'div' ); 53 | error.style.fontFamily = 'sans-serif'; 54 | error.style.fontSize = '16px'; 55 | error.style.fontStyle = 'normal'; 56 | error.style.lineHeight = '26px'; 57 | error.style.backgroundColor = '#fff'; 58 | error.style.color = '#000'; 59 | error.style.padding = '10px 20px'; 60 | error.style.margin = '50px'; 61 | error.style.display = 'inline-block'; 62 | error.innerHTML = message; 63 | container.appendChild( error ); 64 | 65 | return container; 66 | 67 | } 68 | 69 | }, 70 | 71 | getButton: function ( effect ) { 72 | 73 | var button = document.createElement( 'button' ); 74 | button.style.position = 'absolute'; 75 | button.style.left = 'calc(50% - 50px)'; 76 | button.style.bottom = '20px'; 77 | button.style.width = '100px'; 78 | button.style.border = '0'; 79 | button.style.padding = '8px'; 80 | button.style.cursor = 'pointer'; 81 | button.style.backgroundColor = '#000'; 82 | button.style.color = '#fff'; 83 | button.style.fontFamily = 'sans-serif'; 84 | button.style.fontSize = '13px'; 85 | button.style.fontStyle = 'normal'; 86 | button.style.textAlign = 'center'; 87 | button.style.zIndex = '999'; 88 | button.textContent = 'ENTER VR'; 89 | button.onclick = function() { 90 | 91 | effect.isPresenting ? effect.exitPresent() : effect.requestPresent(); 92 | 93 | }; 94 | 95 | window.addEventListener( 'vrdisplaypresentchange', function ( event ) { 96 | 97 | button.textContent = effect.isPresenting ? 'EXIT VR' : 'ENTER VR'; 98 | 99 | }, false ); 100 | 101 | return button; 102 | 103 | } 104 | 105 | }; 106 | -------------------------------------------------------------------------------- /js/controls/VRControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | */ 5 | 6 | THREE.VRControls = function ( object, onError ) { 7 | 8 | var scope = this; 9 | 10 | var vrDisplay, vrDisplays; 11 | 12 | var standingMatrix = new THREE.Matrix4(); 13 | 14 | function gotVRDisplays( displays ) { 15 | 16 | vrDisplays = displays; 17 | 18 | for ( var i = 0; i < displays.length; i ++ ) { 19 | 20 | if ( ( 'VRDisplay' in window && displays[ i ] instanceof VRDisplay ) || 21 | ( 'PositionSensorVRDevice' in window && displays[ i ] instanceof PositionSensorVRDevice ) ) { 22 | 23 | vrDisplay = displays[ i ]; 24 | break; // We keep the first we encounter 25 | 26 | } 27 | 28 | } 29 | 30 | if ( vrDisplay === undefined ) { 31 | 32 | if ( onError ) onError( 'VR input not available.' ); 33 | 34 | } 35 | 36 | } 37 | 38 | if ( navigator.getVRDisplays ) { 39 | 40 | navigator.getVRDisplays().then( gotVRDisplays ); 41 | 42 | } else if ( navigator.getVRDevices ) { 43 | 44 | // Deprecated API. 45 | navigator.getVRDevices().then( gotVRDisplays ); 46 | 47 | } 48 | 49 | // the Rift SDK returns the position in meters 50 | // this scale factor allows the user to define how meters 51 | // are converted to scene units. 52 | 53 | this.scale = 1; 54 | 55 | // If true will use "standing space" coordinate system where y=0 is the 56 | // floor and x=0, z=0 is the center of the room. 57 | this.standing = false; 58 | 59 | // Distance from the users eyes to the floor in meters. Used when 60 | // standing=true but the VRDisplay doesn't provide stageParameters. 61 | this.userHeight = 1.6; 62 | 63 | this.getVRDisplay = function () { 64 | 65 | return vrDisplay; 66 | 67 | }; 68 | 69 | this.getVRDisplays = function () { 70 | 71 | return vrDisplays; 72 | 73 | }; 74 | 75 | this.getStandingMatrix = function () { 76 | 77 | return standingMatrix; 78 | 79 | }; 80 | 81 | this.update = function () { 82 | 83 | if ( vrDisplay ) { 84 | 85 | if ( vrDisplay.getPose ) { 86 | 87 | var pose = vrDisplay.getPose(); 88 | 89 | if ( pose.orientation !== null ) { 90 | 91 | object.quaternion.fromArray( pose.orientation ); 92 | 93 | } 94 | 95 | if ( pose.position !== null ) { 96 | 97 | object.position.fromArray( pose.position ); 98 | 99 | } else { 100 | 101 | object.position.set( 0, 0, 0 ); 102 | 103 | } 104 | 105 | } else { 106 | 107 | // Deprecated API. 108 | var state = vrDisplay.getState(); 109 | 110 | if ( state.orientation !== null ) { 111 | 112 | object.quaternion.copy( state.orientation ); 113 | 114 | } 115 | 116 | if ( state.position !== null ) { 117 | 118 | object.position.copy( state.position ); 119 | 120 | } else { 121 | 122 | object.position.set( 0, 0, 0 ); 123 | 124 | } 125 | 126 | } 127 | 128 | if ( this.standing ) { 129 | 130 | if ( vrDisplay.stageParameters ) { 131 | 132 | object.updateMatrix(); 133 | 134 | standingMatrix.fromArray( vrDisplay.stageParameters.sittingToStandingTransform ); 135 | object.applyMatrix( standingMatrix ); 136 | 137 | } else { 138 | 139 | object.position.setY( object.position.y + this.userHeight ); 140 | 141 | } 142 | 143 | } 144 | 145 | object.position.multiplyScalar( scope.scale ); 146 | 147 | } 148 | 149 | }; 150 | 151 | this.resetPose = function () { 152 | 153 | if ( vrDisplay ) { 154 | 155 | if ( vrDisplay.resetPose !== undefined ) { 156 | 157 | vrDisplay.resetPose(); 158 | 159 | } else if ( vrDisplay.resetSensor !== undefined ) { 160 | 161 | // Deprecated API. 162 | vrDisplay.resetSensor(); 163 | 164 | } else if ( vrDisplay.zeroSensor !== undefined ) { 165 | 166 | // Really deprecated API. 167 | vrDisplay.zeroSensor(); 168 | 169 | } 170 | 171 | } 172 | 173 | }; 174 | 175 | this.resetSensor = function () { 176 | 177 | console.warn( 'THREE.VRControls: .resetSensor() is now .resetPose().' ); 178 | this.resetPose(); 179 | 180 | }; 181 | 182 | this.zeroSensor = function () { 183 | 184 | console.warn( 'THREE.VRControls: .zeroSensor() is now .resetPose().' ); 185 | this.resetPose(); 186 | 187 | }; 188 | 189 | this.dispose = function () { 190 | 191 | vrDisplay = null; 192 | 193 | }; 194 | 195 | }; 196 | -------------------------------------------------------------------------------- /js/effects/VREffect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | * 5 | * WebVR Spec: http://mozvr.github.io/webvr-spec/webvr.html 6 | * 7 | * Firefox: http://mozvr.com/downloads/ 8 | * Chromium: https://drive.google.com/folderview?id=0BzudLt22BqGRbW9WTHMtOWMzNjQ&usp=sharing#list 9 | * 10 | */ 11 | 12 | THREE.VREffect = function ( renderer, onError ) { 13 | 14 | var isWebVR1 = true; 15 | 16 | var vrDisplay, vrDisplays; 17 | var eyeTranslationL = new THREE.Vector3(); 18 | var eyeTranslationR = new THREE.Vector3(); 19 | var renderRectL, renderRectR; 20 | var eyeFOVL, eyeFOVR; 21 | 22 | function gotVRDisplays( displays ) { 23 | 24 | vrDisplays = displays; 25 | 26 | for ( var i = 0; i < displays.length; i ++ ) { 27 | 28 | if ( 'VRDisplay' in window && displays[ i ] instanceof VRDisplay ) { 29 | 30 | vrDisplay = displays[ i ]; 31 | isWebVR1 = true; 32 | break; // We keep the first we encounter 33 | 34 | } else if ( 'HMDVRDevice' in window && displays[ i ] instanceof HMDVRDevice ) { 35 | 36 | vrDisplay = displays[ i ]; 37 | isWebVR1 = false; 38 | break; // We keep the first we encounter 39 | 40 | } 41 | 42 | } 43 | 44 | if ( vrDisplay === undefined ) { 45 | 46 | if ( onError ) onError( 'HMD not available' ); 47 | 48 | } 49 | 50 | } 51 | 52 | if ( navigator.getVRDisplays ) { 53 | 54 | navigator.getVRDisplays().then( gotVRDisplays ); 55 | 56 | } else if ( navigator.getVRDevices ) { 57 | 58 | // Deprecated API. 59 | navigator.getVRDevices().then( gotVRDisplays ); 60 | 61 | } 62 | 63 | // 64 | 65 | this.isPresenting = false; 66 | this.scale = 1; 67 | 68 | var scope = this; 69 | 70 | var rendererSize = renderer.getSize(); 71 | var rendererPixelRatio = renderer.getPixelRatio(); 72 | 73 | this.getVRDisplay = function () { 74 | 75 | return vrDisplay; 76 | 77 | }; 78 | 79 | this.getVRDisplays = function () { 80 | 81 | return vrDisplays; 82 | 83 | }; 84 | 85 | this.setSize = function ( width, height ) { 86 | 87 | rendererSize = { width: width, height: height }; 88 | 89 | if ( scope.isPresenting ) { 90 | 91 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); 92 | renderer.setPixelRatio( 1 ); 93 | 94 | if ( isWebVR1 ) { 95 | 96 | renderer.setSize( eyeParamsL.renderWidth * 2, eyeParamsL.renderHeight, false ); 97 | 98 | } else { 99 | 100 | renderer.setSize( eyeParamsL.renderRect.width * 2, eyeParamsL.renderRect.height, false ); 101 | 102 | } 103 | 104 | } else { 105 | 106 | renderer.setPixelRatio( rendererPixelRatio ); 107 | renderer.setSize( width, height ); 108 | 109 | } 110 | 111 | }; 112 | 113 | // fullscreen 114 | 115 | var canvas = renderer.domElement; 116 | var requestFullscreen; 117 | var exitFullscreen; 118 | var fullscreenElement; 119 | var leftBounds = [ 0.0, 0.0, 0.5, 1.0 ]; 120 | var rightBounds = [ 0.5, 0.0, 0.5, 1.0 ]; 121 | 122 | function onFullscreenChange() { 123 | 124 | var wasPresenting = scope.isPresenting; 125 | scope.isPresenting = vrDisplay !== undefined && ( vrDisplay.isPresenting || ( ! isWebVR1 && document[ fullscreenElement ] instanceof window.HTMLElement ) ); 126 | 127 | if ( scope.isPresenting ) { 128 | 129 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); 130 | var eyeWidth, eyeHeight; 131 | 132 | if ( isWebVR1 ) { 133 | 134 | eyeWidth = eyeParamsL.renderWidth; 135 | eyeHeight = eyeParamsL.renderHeight; 136 | 137 | if ( vrDisplay.getLayers ) { 138 | 139 | var layers = vrDisplay.getLayers(); 140 | if ( layers.length ) { 141 | 142 | leftBounds = layers[0].leftBounds || [ 0.0, 0.0, 0.5, 1.0 ]; 143 | rightBounds = layers[0].rightBounds || [ 0.5, 0.0, 0.5, 1.0 ]; 144 | 145 | } 146 | 147 | } 148 | 149 | } else { 150 | 151 | eyeWidth = eyeParamsL.renderRect.width; 152 | eyeHeight = eyeParamsL.renderRect.height; 153 | 154 | } 155 | 156 | if ( !wasPresenting ) { 157 | 158 | rendererPixelRatio = renderer.getPixelRatio(); 159 | rendererSize = renderer.getSize(); 160 | 161 | renderer.setPixelRatio( 1 ); 162 | renderer.setSize( eyeWidth * 2, eyeHeight, false ); 163 | 164 | } 165 | 166 | } else if ( wasPresenting ) { 167 | 168 | renderer.setPixelRatio( rendererPixelRatio ); 169 | renderer.setSize( rendererSize.width, rendererSize.height ); 170 | 171 | } 172 | 173 | } 174 | 175 | if ( canvas.requestFullscreen ) { 176 | 177 | requestFullscreen = 'requestFullscreen'; 178 | fullscreenElement = 'fullscreenElement'; 179 | exitFullscreen = 'exitFullscreen'; 180 | document.addEventListener( 'fullscreenchange', onFullscreenChange, false ); 181 | 182 | } else if ( canvas.mozRequestFullScreen ) { 183 | 184 | requestFullscreen = 'mozRequestFullScreen'; 185 | fullscreenElement = 'mozFullScreenElement'; 186 | exitFullscreen = 'mozCancelFullScreen'; 187 | document.addEventListener( 'mozfullscreenchange', onFullscreenChange, false ); 188 | 189 | } else { 190 | 191 | requestFullscreen = 'webkitRequestFullscreen'; 192 | fullscreenElement = 'webkitFullscreenElement'; 193 | exitFullscreen = 'webkitExitFullscreen'; 194 | document.addEventListener( 'webkitfullscreenchange', onFullscreenChange, false ); 195 | 196 | } 197 | 198 | window.addEventListener( 'vrdisplaypresentchange', onFullscreenChange, false ); 199 | 200 | this.setFullScreen = function ( boolean ) { 201 | 202 | return new Promise( function ( resolve, reject ) { 203 | 204 | if ( vrDisplay === undefined ) { 205 | 206 | reject( new Error( 'No VR hardware found.' ) ); 207 | return; 208 | 209 | } 210 | 211 | if ( scope.isPresenting === boolean ) { 212 | 213 | resolve(); 214 | return; 215 | 216 | } 217 | 218 | if ( isWebVR1 ) { 219 | 220 | if ( boolean ) { 221 | 222 | resolve( vrDisplay.requestPresent( [ { source: canvas } ] ) ); 223 | 224 | } else { 225 | 226 | resolve( vrDisplay.exitPresent() ); 227 | 228 | } 229 | 230 | } else { 231 | 232 | if ( canvas[ requestFullscreen ] ) { 233 | 234 | canvas[ boolean ? requestFullscreen : exitFullscreen ]( { vrDisplay: vrDisplay } ); 235 | resolve(); 236 | 237 | } else { 238 | 239 | console.error( 'No compatible requestFullscreen method found.' ); 240 | reject( new Error( 'No compatible requestFullscreen method found.' ) ); 241 | 242 | } 243 | 244 | } 245 | 246 | } ); 247 | 248 | }; 249 | 250 | this.requestPresent = function () { 251 | 252 | return this.setFullScreen( true ); 253 | 254 | }; 255 | 256 | this.exitPresent = function () { 257 | 258 | return this.setFullScreen( false ); 259 | 260 | }; 261 | 262 | this.requestAnimationFrame = function ( f ) { 263 | 264 | if ( isWebVR1 && vrDisplay !== undefined ) { 265 | 266 | return vrDisplay.requestAnimationFrame( f ); 267 | 268 | } else { 269 | 270 | return window.requestAnimationFrame( f ); 271 | 272 | } 273 | 274 | }; 275 | 276 | this.cancelAnimationFrame = function ( h ) { 277 | 278 | if ( isWebVR1 && vrDisplay !== undefined ) { 279 | 280 | vrDisplay.cancelAnimationFrame( h ); 281 | 282 | } else { 283 | 284 | window.cancelAnimationFrame( h ); 285 | 286 | } 287 | 288 | }; 289 | 290 | this.submitFrame = function () { 291 | 292 | if ( isWebVR1 && vrDisplay !== undefined && scope.isPresenting ) { 293 | 294 | vrDisplay.submitFrame(); 295 | 296 | } 297 | 298 | }; 299 | 300 | this.autoSubmitFrame = true; 301 | 302 | // render 303 | 304 | var cameraL = new THREE.PerspectiveCamera(); 305 | cameraL.layers.enable( 1 ); 306 | 307 | var cameraR = new THREE.PerspectiveCamera(); 308 | cameraR.layers.enable( 2 ); 309 | 310 | this.render = function ( scene, camera, renderTarget, forceClear ) { 311 | 312 | if ( vrDisplay && scope.isPresenting ) { 313 | 314 | var autoUpdate = scene.autoUpdate; 315 | 316 | if ( autoUpdate ) { 317 | 318 | scene.updateMatrixWorld(); 319 | scene.autoUpdate = false; 320 | 321 | } 322 | 323 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' ); 324 | var eyeParamsR = vrDisplay.getEyeParameters( 'right' ); 325 | 326 | if ( isWebVR1 ) { 327 | 328 | eyeTranslationL.fromArray( eyeParamsL.offset ); 329 | eyeTranslationR.fromArray( eyeParamsR.offset ); 330 | eyeFOVL = eyeParamsL.fieldOfView; 331 | eyeFOVR = eyeParamsR.fieldOfView; 332 | 333 | } else { 334 | 335 | eyeTranslationL.copy( eyeParamsL.eyeTranslation ); 336 | eyeTranslationR.copy( eyeParamsR.eyeTranslation ); 337 | eyeFOVL = eyeParamsL.recommendedFieldOfView; 338 | eyeFOVR = eyeParamsR.recommendedFieldOfView; 339 | 340 | } 341 | 342 | if ( Array.isArray( scene ) ) { 343 | 344 | console.warn( 'THREE.VREffect.render() no longer supports arrays. Use object.layers instead.' ); 345 | scene = scene[ 0 ]; 346 | 347 | } 348 | 349 | // When rendering we don't care what the recommended size is, only what the actual size 350 | // of the backbuffer is. 351 | var size = renderer.getSize(); 352 | renderRectL = { 353 | x: Math.round( size.width * leftBounds[ 0 ] ), 354 | y: Math.round( size.height * leftBounds[ 1 ] ), 355 | width: Math.round( size.width * leftBounds[ 2 ] ), 356 | height: Math.round(size.height * leftBounds[ 3 ] ) 357 | }; 358 | renderRectR = { 359 | x: Math.round( size.width * rightBounds[ 0 ] ), 360 | y: Math.round( size.height * rightBounds[ 1 ] ), 361 | width: Math.round( size.width * rightBounds[ 2 ] ), 362 | height: Math.round(size.height * rightBounds[ 3 ] ) 363 | }; 364 | 365 | if ( renderTarget ) { 366 | 367 | renderer.setRenderTarget( renderTarget ); 368 | renderTarget.scissorTest = true; 369 | 370 | } else { 371 | 372 | renderer.setScissorTest( true ); 373 | 374 | } 375 | 376 | if ( renderer.autoClear || forceClear ) renderer.clear(); 377 | 378 | if ( camera.parent === null ) camera.updateMatrixWorld(); 379 | 380 | cameraL.projectionMatrix = fovToProjection( eyeFOVL, true, camera.near, camera.far ); 381 | cameraR.projectionMatrix = fovToProjection( eyeFOVR, true, camera.near, camera.far ); 382 | 383 | camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale ); 384 | camera.matrixWorld.decompose( cameraR.position, cameraR.quaternion, cameraR.scale ); 385 | 386 | var scale = this.scale; 387 | cameraL.translateOnAxis( eyeTranslationL, scale ); 388 | cameraR.translateOnAxis( eyeTranslationR, scale ); 389 | 390 | 391 | // render left eye 392 | if ( renderTarget ) { 393 | 394 | renderTarget.viewport.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); 395 | renderTarget.scissor.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); 396 | 397 | } else { 398 | 399 | renderer.setViewport( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); 400 | renderer.setScissor( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height ); 401 | 402 | } 403 | renderer.render( scene, cameraL, renderTarget, forceClear ); 404 | 405 | // render right eye 406 | if ( renderTarget ) { 407 | 408 | renderTarget.viewport.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); 409 | renderTarget.scissor.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); 410 | 411 | } else { 412 | 413 | renderer.setViewport( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); 414 | renderer.setScissor( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height ); 415 | 416 | } 417 | renderer.render( scene, cameraR, renderTarget, forceClear ); 418 | 419 | if ( renderTarget ) { 420 | 421 | renderTarget.viewport.set( 0, 0, size.width, size.height ); 422 | renderTarget.scissor.set( 0, 0, size.width, size.height ); 423 | renderTarget.scissorTest = false; 424 | renderer.setRenderTarget( null ); 425 | 426 | } else { 427 | 428 | renderer.setScissorTest( false ); 429 | 430 | } 431 | 432 | if ( autoUpdate ) { 433 | 434 | scene.autoUpdate = true; 435 | 436 | } 437 | 438 | if ( scope.autoSubmitFrame ) { 439 | 440 | scope.submitFrame(); 441 | 442 | } 443 | 444 | return; 445 | 446 | } 447 | 448 | // Regular render mode if not HMD 449 | 450 | renderer.render( scene, camera, renderTarget, forceClear ); 451 | 452 | }; 453 | 454 | // 455 | 456 | function fovToNDCScaleOffset( fov ) { 457 | 458 | var pxscale = 2.0 / ( fov.leftTan + fov.rightTan ); 459 | var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5; 460 | var pyscale = 2.0 / ( fov.upTan + fov.downTan ); 461 | var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5; 462 | return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] }; 463 | 464 | } 465 | 466 | function fovPortToProjection( fov, rightHanded, zNear, zFar ) { 467 | 468 | rightHanded = rightHanded === undefined ? true : rightHanded; 469 | zNear = zNear === undefined ? 0.01 : zNear; 470 | zFar = zFar === undefined ? 10000.0 : zFar; 471 | 472 | var handednessScale = rightHanded ? - 1.0 : 1.0; 473 | 474 | // start with an identity matrix 475 | var mobj = new THREE.Matrix4(); 476 | var m = mobj.elements; 477 | 478 | // and with scale/offset info for normalized device coords 479 | var scaleAndOffset = fovToNDCScaleOffset( fov ); 480 | 481 | // X result, map clip edges to [-w,+w] 482 | m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ]; 483 | m[ 0 * 4 + 1 ] = 0.0; 484 | m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale; 485 | m[ 0 * 4 + 3 ] = 0.0; 486 | 487 | // Y result, map clip edges to [-w,+w] 488 | // Y offset is negated because this proj matrix transforms from world coords with Y=up, 489 | // but the NDC scaling has Y=down (thanks D3D?) 490 | m[ 1 * 4 + 0 ] = 0.0; 491 | m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ]; 492 | m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale; 493 | m[ 1 * 4 + 3 ] = 0.0; 494 | 495 | // Z result (up to the app) 496 | m[ 2 * 4 + 0 ] = 0.0; 497 | m[ 2 * 4 + 1 ] = 0.0; 498 | m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale; 499 | m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar ); 500 | 501 | // W result (= Z in) 502 | m[ 3 * 4 + 0 ] = 0.0; 503 | m[ 3 * 4 + 1 ] = 0.0; 504 | m[ 3 * 4 + 2 ] = handednessScale; 505 | m[ 3 * 4 + 3 ] = 0.0; 506 | 507 | mobj.transpose(); 508 | 509 | return mobj; 510 | 511 | } 512 | 513 | function fovToProjection( fov, rightHanded, zNear, zFar ) { 514 | 515 | var DEG2RAD = Math.PI / 180.0; 516 | 517 | var fovPort = { 518 | upTan: Math.tan( fov.upDegrees * DEG2RAD ), 519 | downTan: Math.tan( fov.downDegrees * DEG2RAD ), 520 | leftTan: Math.tan( fov.leftDegrees * DEG2RAD ), 521 | rightTan: Math.tan( fov.rightDegrees * DEG2RAD ) 522 | }; 523 | 524 | return fovPortToProjection( fovPort, rightHanded, zNear, zFar ); 525 | 526 | } 527 | 528 | }; 529 | -------------------------------------------------------------------------------- /js/loaders/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | this.materials = null; 10 | 11 | this.regexp = { 12 | // v float float float 13 | vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 14 | // vn float float float 15 | normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 16 | // vt float float 17 | uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 18 | // f vertex vertex vertex 19 | face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/, 20 | // f vertex/uv vertex/uv vertex/uv 21 | face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/, 22 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 23 | face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, 24 | // f vertex//normal vertex//normal vertex//normal 25 | face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/, 26 | // o object_name | g group_name 27 | object_pattern : /^[og]\s*(.+)?/, 28 | // s boolean 29 | smoothing_pattern : /^s\s+(\d+|on|off)/, 30 | // mtllib file_reference 31 | material_library_pattern : /^mtllib /, 32 | // usemtl material_name 33 | material_use_pattern : /^usemtl / 34 | }; 35 | 36 | }; 37 | 38 | THREE.OBJLoader.prototype = { 39 | 40 | constructor: THREE.OBJLoader, 41 | 42 | load: function ( url, onLoad, onProgress, onError ) { 43 | 44 | var scope = this; 45 | 46 | var loader = new THREE.XHRLoader( scope.manager ); 47 | loader.setPath( this.path ); 48 | loader.load( url, function ( text ) { 49 | 50 | onLoad( scope.parse( text ) ); 51 | 52 | }, onProgress, onError ); 53 | 54 | }, 55 | 56 | setPath: function ( value ) { 57 | 58 | this.path = value; 59 | 60 | }, 61 | 62 | setMaterials: function ( materials ) { 63 | 64 | this.materials = materials; 65 | 66 | }, 67 | 68 | _createParserState : function () { 69 | 70 | var state = { 71 | objects : [], 72 | object : {}, 73 | 74 | vertices : [], 75 | normals : [], 76 | uvs : [], 77 | 78 | materialLibraries : [], 79 | 80 | startObject: function ( name, fromDeclaration ) { 81 | 82 | // If the current object (initial from reset) is not from a g/o declaration in the parsed 83 | // file. We need to use it for the first parsed g/o to keep things in sync. 84 | if ( this.object && this.object.fromDeclaration === false ) { 85 | 86 | this.object.name = name; 87 | this.object.fromDeclaration = ( fromDeclaration !== false ); 88 | return; 89 | 90 | } 91 | 92 | if ( this.object && typeof this.object._finalize === 'function' ) { 93 | 94 | this.object._finalize(); 95 | 96 | } 97 | 98 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 99 | 100 | this.object = { 101 | name : name || '', 102 | fromDeclaration : ( fromDeclaration !== false ), 103 | 104 | geometry : { 105 | vertices : [], 106 | normals : [], 107 | uvs : [] 108 | }, 109 | materials : [], 110 | smooth : true, 111 | 112 | startMaterial : function( name, libraries ) { 113 | 114 | var previous = this._finalize( false ); 115 | 116 | // New usemtl declaration overwrites an inherited material, except if faces were declared 117 | // after the material, then it must be preserved for proper MultiMaterial continuation. 118 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { 119 | 120 | this.materials.splice( previous.index, 1 ); 121 | 122 | } 123 | 124 | var material = { 125 | index : this.materials.length, 126 | name : name || '', 127 | mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), 128 | smooth : ( previous !== undefined ? previous.smooth : this.smooth ), 129 | groupStart : ( previous !== undefined ? previous.groupEnd : 0 ), 130 | groupEnd : -1, 131 | groupCount : -1, 132 | inherited : false, 133 | 134 | clone : function( index ) { 135 | return { 136 | index : ( typeof index === 'number' ? index : this.index ), 137 | name : this.name, 138 | mtllib : this.mtllib, 139 | smooth : this.smooth, 140 | groupStart : this.groupEnd, 141 | groupEnd : -1, 142 | groupCount : -1, 143 | inherited : false 144 | }; 145 | } 146 | }; 147 | 148 | this.materials.push( material ); 149 | 150 | return material; 151 | 152 | }, 153 | 154 | currentMaterial : function() { 155 | 156 | if ( this.materials.length > 0 ) { 157 | return this.materials[ this.materials.length - 1 ]; 158 | } 159 | 160 | return undefined; 161 | 162 | }, 163 | 164 | _finalize : function( end ) { 165 | 166 | var lastMultiMaterial = this.currentMaterial(); 167 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) { 168 | 169 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 170 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 171 | lastMultiMaterial.inherited = false; 172 | 173 | } 174 | 175 | // Guarantee at least one empty material, this makes the creation later more straight forward. 176 | if ( end !== false && this.materials.length === 0 ) { 177 | this.materials.push({ 178 | name : '', 179 | smooth : this.smooth 180 | }); 181 | } 182 | 183 | return lastMultiMaterial; 184 | 185 | } 186 | }; 187 | 188 | // Inherit previous objects material. 189 | // Spec tells us that a declared material must be set to all objects until a new material is declared. 190 | // If a usemtl declaration is encountered while this new object is being parsed, it will 191 | // overwrite the inherited material. Exception being that there was already face declarations 192 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation. 193 | 194 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) { 195 | 196 | var declared = previousMaterial.clone( 0 ); 197 | declared.inherited = true; 198 | this.object.materials.push( declared ); 199 | 200 | } 201 | 202 | this.objects.push( this.object ); 203 | 204 | }, 205 | 206 | finalize : function() { 207 | 208 | if ( this.object && typeof this.object._finalize === 'function' ) { 209 | 210 | this.object._finalize(); 211 | 212 | } 213 | 214 | }, 215 | 216 | parseVertexIndex: function ( value, len ) { 217 | 218 | var index = parseInt( value, 10 ); 219 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 220 | 221 | }, 222 | 223 | parseNormalIndex: function ( value, len ) { 224 | 225 | var index = parseInt( value, 10 ); 226 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 227 | 228 | }, 229 | 230 | parseUVIndex: function ( value, len ) { 231 | 232 | var index = parseInt( value, 10 ); 233 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; 234 | 235 | }, 236 | 237 | addVertex: function ( a, b, c ) { 238 | 239 | var src = this.vertices; 240 | var dst = this.object.geometry.vertices; 241 | 242 | dst.push( src[ a + 0 ] ); 243 | dst.push( src[ a + 1 ] ); 244 | dst.push( src[ a + 2 ] ); 245 | dst.push( src[ b + 0 ] ); 246 | dst.push( src[ b + 1 ] ); 247 | dst.push( src[ b + 2 ] ); 248 | dst.push( src[ c + 0 ] ); 249 | dst.push( src[ c + 1 ] ); 250 | dst.push( src[ c + 2 ] ); 251 | 252 | }, 253 | 254 | addVertexLine: function ( a ) { 255 | 256 | var src = this.vertices; 257 | var dst = this.object.geometry.vertices; 258 | 259 | dst.push( src[ a + 0 ] ); 260 | dst.push( src[ a + 1 ] ); 261 | dst.push( src[ a + 2 ] ); 262 | 263 | }, 264 | 265 | addNormal : function ( a, b, c ) { 266 | 267 | var src = this.normals; 268 | var dst = this.object.geometry.normals; 269 | 270 | dst.push( src[ a + 0 ] ); 271 | dst.push( src[ a + 1 ] ); 272 | dst.push( src[ a + 2 ] ); 273 | dst.push( src[ b + 0 ] ); 274 | dst.push( src[ b + 1 ] ); 275 | dst.push( src[ b + 2 ] ); 276 | dst.push( src[ c + 0 ] ); 277 | dst.push( src[ c + 1 ] ); 278 | dst.push( src[ c + 2 ] ); 279 | 280 | }, 281 | 282 | addUV: function ( a, b, c ) { 283 | 284 | var src = this.uvs; 285 | var dst = this.object.geometry.uvs; 286 | 287 | dst.push( src[ a + 0 ] ); 288 | dst.push( src[ a + 1 ] ); 289 | dst.push( src[ b + 0 ] ); 290 | dst.push( src[ b + 1 ] ); 291 | dst.push( src[ c + 0 ] ); 292 | dst.push( src[ c + 1 ] ); 293 | 294 | }, 295 | 296 | addUVLine: function ( a ) { 297 | 298 | var src = this.uvs; 299 | var dst = this.object.geometry.uvs; 300 | 301 | dst.push( src[ a + 0 ] ); 302 | dst.push( src[ a + 1 ] ); 303 | 304 | }, 305 | 306 | addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 307 | 308 | var vLen = this.vertices.length; 309 | 310 | var ia = this.parseVertexIndex( a, vLen ); 311 | var ib = this.parseVertexIndex( b, vLen ); 312 | var ic = this.parseVertexIndex( c, vLen ); 313 | var id; 314 | 315 | if ( d === undefined ) { 316 | 317 | this.addVertex( ia, ib, ic ); 318 | 319 | } else { 320 | 321 | id = this.parseVertexIndex( d, vLen ); 322 | 323 | this.addVertex( ia, ib, id ); 324 | this.addVertex( ib, ic, id ); 325 | 326 | } 327 | 328 | if ( ua !== undefined ) { 329 | 330 | var uvLen = this.uvs.length; 331 | 332 | ia = this.parseUVIndex( ua, uvLen ); 333 | ib = this.parseUVIndex( ub, uvLen ); 334 | ic = this.parseUVIndex( uc, uvLen ); 335 | 336 | if ( d === undefined ) { 337 | 338 | this.addUV( ia, ib, ic ); 339 | 340 | } else { 341 | 342 | id = this.parseUVIndex( ud, uvLen ); 343 | 344 | this.addUV( ia, ib, id ); 345 | this.addUV( ib, ic, id ); 346 | 347 | } 348 | 349 | } 350 | 351 | if ( na !== undefined ) { 352 | 353 | // Normals are many times the same. If so, skip function call and parseInt. 354 | var nLen = this.normals.length; 355 | ia = this.parseNormalIndex( na, nLen ); 356 | 357 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 358 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 359 | 360 | if ( d === undefined ) { 361 | 362 | this.addNormal( ia, ib, ic ); 363 | 364 | } else { 365 | 366 | id = this.parseNormalIndex( nd, nLen ); 367 | 368 | this.addNormal( ia, ib, id ); 369 | this.addNormal( ib, ic, id ); 370 | 371 | } 372 | 373 | } 374 | 375 | }, 376 | 377 | addLineGeometry: function ( vertices, uvs ) { 378 | 379 | this.object.geometry.type = 'Line'; 380 | 381 | var vLen = this.vertices.length; 382 | var uvLen = this.uvs.length; 383 | 384 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 385 | 386 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); 387 | 388 | } 389 | 390 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { 391 | 392 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); 393 | 394 | } 395 | 396 | } 397 | 398 | }; 399 | 400 | state.startObject( '', false ); 401 | 402 | return state; 403 | 404 | }, 405 | 406 | parse: function ( text ) { 407 | 408 | console.time( 'OBJLoader' ); 409 | 410 | var state = this._createParserState(); 411 | 412 | if ( text.indexOf( '\r\n' ) !== - 1 ) { 413 | 414 | // This is faster than String.split with regex that splits on both 415 | text = text.replace( '\r\n', '\n' ); 416 | 417 | } 418 | 419 | var lines = text.split( '\n' ); 420 | var line = '', lineFirstChar = '', lineSecondChar = ''; 421 | var lineLength = 0; 422 | var result = []; 423 | 424 | // Faster to just trim left side of the line. Use if available. 425 | var trimLeft = ( typeof ''.trimLeft === 'function' ); 426 | 427 | for ( var i = 0, l = lines.length; i < l; i ++ ) { 428 | 429 | line = lines[ i ]; 430 | 431 | line = trimLeft ? line.trimLeft() : line.trim(); 432 | 433 | lineLength = line.length; 434 | 435 | if ( lineLength === 0 ) continue; 436 | 437 | lineFirstChar = line.charAt( 0 ); 438 | 439 | // @todo invoke passed in handler if any 440 | if ( lineFirstChar === '#' ) continue; 441 | 442 | if ( lineFirstChar === 'v' ) { 443 | 444 | lineSecondChar = line.charAt( 1 ); 445 | 446 | if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) { 447 | 448 | // 0 1 2 3 449 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 450 | 451 | state.vertices.push( 452 | parseFloat( result[ 1 ] ), 453 | parseFloat( result[ 2 ] ), 454 | parseFloat( result[ 3 ] ) 455 | ); 456 | 457 | } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) { 458 | 459 | // 0 1 2 3 460 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 461 | 462 | state.normals.push( 463 | parseFloat( result[ 1 ] ), 464 | parseFloat( result[ 2 ] ), 465 | parseFloat( result[ 3 ] ) 466 | ); 467 | 468 | } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) { 469 | 470 | // 0 1 2 471 | // ["vt 0.1 0.2", "0.1", "0.2"] 472 | 473 | state.uvs.push( 474 | parseFloat( result[ 1 ] ), 475 | parseFloat( result[ 2 ] ) 476 | ); 477 | 478 | } else { 479 | 480 | throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" ); 481 | 482 | } 483 | 484 | } else if ( lineFirstChar === "f" ) { 485 | 486 | if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) { 487 | 488 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 489 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 490 | // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined] 491 | 492 | state.addFace( 493 | result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ], 494 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 495 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 496 | ); 497 | 498 | } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) { 499 | 500 | // f vertex/uv vertex/uv vertex/uv 501 | // 0 1 2 3 4 5 6 7 8 502 | // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined] 503 | 504 | state.addFace( 505 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 506 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 507 | ); 508 | 509 | } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) { 510 | 511 | // f vertex//normal vertex//normal vertex//normal 512 | // 0 1 2 3 4 5 6 7 8 513 | // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined] 514 | 515 | state.addFace( 516 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 517 | undefined, undefined, undefined, undefined, 518 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 519 | ); 520 | 521 | } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) { 522 | 523 | // f vertex vertex vertex 524 | // 0 1 2 3 4 525 | // ["f 1 2 3", "1", "2", "3", undefined] 526 | 527 | state.addFace( 528 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 529 | ); 530 | 531 | } else { 532 | 533 | throw new Error( "Unexpected face line: '" + line + "'" ); 534 | 535 | } 536 | 537 | } else if ( lineFirstChar === "l" ) { 538 | 539 | var lineParts = line.substring( 1 ).trim().split( " " ); 540 | var lineVertices = [], lineUVs = []; 541 | 542 | if ( line.indexOf( "/" ) === - 1 ) { 543 | 544 | lineVertices = lineParts; 545 | 546 | } else { 547 | 548 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 549 | 550 | var parts = lineParts[ li ].split( "/" ); 551 | 552 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 553 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 554 | 555 | } 556 | 557 | } 558 | state.addLineGeometry( lineVertices, lineUVs ); 559 | 560 | } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) { 561 | 562 | // o object_name 563 | // or 564 | // g group_name 565 | 566 | var name = result[ 0 ].substr( 1 ).trim(); 567 | state.startObject( name ); 568 | 569 | } else if ( this.regexp.material_use_pattern.test( line ) ) { 570 | 571 | // material 572 | 573 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 574 | 575 | } else if ( this.regexp.material_library_pattern.test( line ) ) { 576 | 577 | // mtl file 578 | 579 | state.materialLibraries.push( line.substring( 7 ).trim() ); 580 | 581 | } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) { 582 | 583 | // smooth shading 584 | 585 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 586 | // but does not define a usemtl for each face set. 587 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 588 | // This requires some care to not create extra material on each smooth value for "normal" obj files. 589 | // where explicit usemtl defines geometry groups. 590 | // Example asset: examples/models/obj/cerberus/Cerberus.obj 591 | 592 | var value = result[ 1 ].trim().toLowerCase(); 593 | state.object.smooth = ( value === '1' || value === 'on' ); 594 | 595 | var material = state.object.currentMaterial(); 596 | if ( material ) { 597 | 598 | material.smooth = state.object.smooth; 599 | 600 | } 601 | 602 | } else { 603 | 604 | // Handle null terminated files without exception 605 | if ( line === '\0' ) continue; 606 | 607 | throw new Error( "Unexpected line: '" + line + "'" ); 608 | 609 | } 610 | 611 | } 612 | 613 | state.finalize(); 614 | 615 | var container = new THREE.Group(); 616 | container.materialLibraries = [].concat( state.materialLibraries ); 617 | 618 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 619 | 620 | var object = state.objects[ i ]; 621 | var geometry = object.geometry; 622 | var materials = object.materials; 623 | var isLine = ( geometry.type === 'Line' ); 624 | 625 | // Skip o/g line declarations that did not follow with any faces 626 | if ( geometry.vertices.length === 0 ) continue; 627 | 628 | var buffergeometry = new THREE.BufferGeometry(); 629 | 630 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 631 | 632 | if ( geometry.normals.length > 0 ) { 633 | 634 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 635 | 636 | } else { 637 | 638 | buffergeometry.computeVertexNormals(); 639 | 640 | } 641 | 642 | if ( geometry.uvs.length > 0 ) { 643 | 644 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 645 | 646 | } 647 | 648 | // Create materials 649 | 650 | var createdMaterials = []; 651 | 652 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 653 | 654 | var sourceMaterial = materials[mi]; 655 | var material = undefined; 656 | 657 | if ( this.materials !== null ) { 658 | 659 | material = this.materials.create( sourceMaterial.name ); 660 | 661 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 662 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { 663 | 664 | var materialLine = new THREE.LineBasicMaterial(); 665 | materialLine.copy( material ); 666 | material = materialLine; 667 | 668 | } 669 | 670 | } 671 | 672 | if ( ! material ) { 673 | 674 | material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); 675 | material.name = sourceMaterial.name; 676 | 677 | } 678 | 679 | material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading; 680 | 681 | createdMaterials.push(material); 682 | 683 | } 684 | 685 | // Create mesh 686 | 687 | var mesh; 688 | 689 | if ( createdMaterials.length > 1 ) { 690 | 691 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 692 | 693 | var sourceMaterial = materials[mi]; 694 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 695 | 696 | } 697 | 698 | var multiMaterial = new THREE.MultiMaterial( createdMaterials ); 699 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.LineSegments( buffergeometry, multiMaterial ) ); 700 | 701 | } else { 702 | 703 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) ); 704 | } 705 | 706 | mesh.name = object.name; 707 | 708 | container.add( mesh ); 709 | 710 | } 711 | 712 | console.timeEnd( 'OBJLoader' ); 713 | 714 | return container; 715 | 716 | } 717 | 718 | }; 719 | -------------------------------------------------------------------------------- /models/onepointfive_spec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdoob/webvr-sculpt/67da3beb7069d361ca04a0450437f543b8aa48e9/models/onepointfive_spec.png -------------------------------------------------------------------------------- /models/onepointfive_texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdoob/webvr-sculpt/67da3beb7069d361ca04a0450437f543b8aa48e9/models/onepointfive_texture.png --------------------------------------------------------------------------------