├── 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
--------------------------------------------------------------------------------