├── .gitignore ├── README.md ├── assets ├── buddha.obj └── teapot.obj ├── libs ├── OBJLoader.js ├── OrbitControls.js ├── bvhtree.js └── three.js ├── main.html ├── pres └── pres PRT.pdf └── src ├── main.js └── sh.js /.gitignore: -------------------------------------------------------------------------------- 1 | prt_precomputed.json 2 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PRT 2 | Precomputed Radiance Transfert (PRT) 3 | 4 | Demo for the computer graphics paper discussion group at McGill University. 5 | 6 | Based on: 7 | Precomputed Radiance Transfer for Real-Time Rendering in Dynamic, Low-Frequency Lighting Environments 8 | http://www.ppsloan.org/publications/shillum_final23.pdf 9 | 10 | ## Interface 11 | ![alt text](https://preview.ibb.co/dK2dXk/ui.png) 12 | 13 | ## Usage 14 | Run using: 15 | 16 | ### Windows 17 | `chrome --allow-file-access-from-files` 18 | 19 | ### MacOS 20 | `/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --allow-file-access-from-files` 21 | -------------------------------------------------------------------------------- /libs/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | this.materials = null; 10 | 11 | this.regexp = { 12 | // v float float float 13 | vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 14 | // vn float float float 15 | normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 16 | // vt float float 17 | uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 18 | // f vertex vertex vertex 19 | face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/, 20 | // f vertex/uv vertex/uv vertex/uv 21 | face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/, 22 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 23 | face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, 24 | // f vertex//normal vertex//normal vertex//normal 25 | face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/, 26 | // o object_name | g group_name 27 | object_pattern : /^[og]\s*(.+)?/, 28 | // s boolean 29 | smoothing_pattern : /^s\s+(\d+|on|off)/, 30 | // mtllib file_reference 31 | material_library_pattern : /^mtllib /, 32 | // usemtl material_name 33 | material_use_pattern : /^usemtl / 34 | }; 35 | 36 | }; 37 | 38 | THREE.OBJLoader.prototype = { 39 | 40 | constructor: THREE.OBJLoader, 41 | 42 | load: function ( url, onLoad, onProgress, onError ) { 43 | 44 | var scope = this; 45 | 46 | var loader = new THREE.FileLoader( scope.manager ); 47 | loader.setPath( this.path ); 48 | loader.load( url, function ( text ) { 49 | 50 | onLoad( scope.parse( text ) ); 51 | 52 | }, onProgress, onError ); 53 | 54 | }, 55 | 56 | setPath: function ( value ) { 57 | 58 | this.path = value; 59 | 60 | }, 61 | 62 | setMaterials: function ( materials ) { 63 | 64 | this.materials = materials; 65 | 66 | }, 67 | 68 | _createParserState : function () { 69 | 70 | var state = { 71 | objects : [], 72 | object : {}, 73 | 74 | vertices : [], 75 | normals : [], 76 | uvs : [], 77 | 78 | materialLibraries : [], 79 | 80 | startObject: function ( name, fromDeclaration ) { 81 | 82 | // If the current object (initial from reset) is not from a g/o declaration in the parsed 83 | // file. We need to use it for the first parsed g/o to keep things in sync. 84 | if ( this.object && this.object.fromDeclaration === false ) { 85 | 86 | this.object.name = name; 87 | this.object.fromDeclaration = ( fromDeclaration !== false ); 88 | return; 89 | 90 | } 91 | 92 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 93 | 94 | if ( this.object && typeof this.object._finalize === 'function' ) { 95 | 96 | this.object._finalize( true ); 97 | 98 | } 99 | 100 | this.object = { 101 | name : name || '', 102 | fromDeclaration : ( fromDeclaration !== false ), 103 | 104 | geometry : { 105 | vertices : [], 106 | normals : [], 107 | uvs : [] 108 | }, 109 | materials : [], 110 | smooth : true, 111 | 112 | startMaterial : function( name, libraries ) { 113 | 114 | var previous = this._finalize( false ); 115 | 116 | // New usemtl declaration overwrites an inherited material, except if faces were declared 117 | // after the material, then it must be preserved for proper MultiMaterial continuation. 118 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { 119 | 120 | this.materials.splice( previous.index, 1 ); 121 | 122 | } 123 | 124 | var material = { 125 | index : this.materials.length, 126 | name : name || '', 127 | mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), 128 | smooth : ( previous !== undefined ? previous.smooth : this.smooth ), 129 | groupStart : ( previous !== undefined ? previous.groupEnd : 0 ), 130 | groupEnd : -1, 131 | groupCount : -1, 132 | inherited : false, 133 | 134 | clone : function( index ) { 135 | var cloned = { 136 | index : ( typeof index === 'number' ? index : this.index ), 137 | name : this.name, 138 | mtllib : this.mtllib, 139 | smooth : this.smooth, 140 | groupStart : 0, 141 | groupEnd : -1, 142 | groupCount : -1, 143 | inherited : false 144 | }; 145 | cloned.clone = this.clone.bind(cloned); 146 | return cloned; 147 | } 148 | }; 149 | 150 | this.materials.push( material ); 151 | 152 | return material; 153 | 154 | }, 155 | 156 | currentMaterial : function() { 157 | 158 | if ( this.materials.length > 0 ) { 159 | return this.materials[ this.materials.length - 1 ]; 160 | } 161 | 162 | return undefined; 163 | 164 | }, 165 | 166 | _finalize : function( end ) { 167 | 168 | var lastMultiMaterial = this.currentMaterial(); 169 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) { 170 | 171 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 172 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 173 | lastMultiMaterial.inherited = false; 174 | 175 | } 176 | 177 | // Ignore objects tail materials if no face declarations followed them before a new o/g started. 178 | if ( end && this.materials.length > 1 ) { 179 | 180 | for ( var mi = this.materials.length - 1; mi >= 0; mi-- ) { 181 | if ( this.materials[mi].groupCount <= 0 ) { 182 | this.materials.splice( mi, 1 ); 183 | } 184 | } 185 | 186 | } 187 | 188 | // Guarantee at least one empty material, this makes the creation later more straight forward. 189 | if ( end && this.materials.length === 0 ) { 190 | 191 | this.materials.push({ 192 | name : '', 193 | smooth : this.smooth 194 | }); 195 | 196 | } 197 | 198 | return lastMultiMaterial; 199 | 200 | } 201 | }; 202 | 203 | // Inherit previous objects material. 204 | // Spec tells us that a declared material must be set to all objects until a new material is declared. 205 | // If a usemtl declaration is encountered while this new object is being parsed, it will 206 | // overwrite the inherited material. Exception being that there was already face declarations 207 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation. 208 | 209 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) { 210 | 211 | var declared = previousMaterial.clone( 0 ); 212 | declared.inherited = true; 213 | this.object.materials.push( declared ); 214 | 215 | } 216 | 217 | this.objects.push( this.object ); 218 | 219 | }, 220 | 221 | finalize : function() { 222 | 223 | if ( this.object && typeof this.object._finalize === 'function' ) { 224 | 225 | this.object._finalize( true ); 226 | 227 | } 228 | 229 | }, 230 | 231 | parseVertexIndex: function ( value, len ) { 232 | 233 | var index = parseInt( value, 10 ); 234 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 235 | 236 | }, 237 | 238 | parseNormalIndex: function ( value, len ) { 239 | 240 | var index = parseInt( value, 10 ); 241 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 242 | 243 | }, 244 | 245 | parseUVIndex: function ( value, len ) { 246 | 247 | var index = parseInt( value, 10 ); 248 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; 249 | 250 | }, 251 | 252 | addVertex: function ( a, b, c ) { 253 | 254 | var src = this.vertices; 255 | var dst = this.object.geometry.vertices; 256 | 257 | dst.push( src[ a + 0 ] ); 258 | dst.push( src[ a + 1 ] ); 259 | dst.push( src[ a + 2 ] ); 260 | dst.push( src[ b + 0 ] ); 261 | dst.push( src[ b + 1 ] ); 262 | dst.push( src[ b + 2 ] ); 263 | dst.push( src[ c + 0 ] ); 264 | dst.push( src[ c + 1 ] ); 265 | dst.push( src[ c + 2 ] ); 266 | 267 | }, 268 | 269 | addVertexLine: function ( a ) { 270 | 271 | var src = this.vertices; 272 | var dst = this.object.geometry.vertices; 273 | 274 | dst.push( src[ a + 0 ] ); 275 | dst.push( src[ a + 1 ] ); 276 | dst.push( src[ a + 2 ] ); 277 | 278 | }, 279 | 280 | addNormal : function ( a, b, c ) { 281 | 282 | var src = this.normals; 283 | var dst = this.object.geometry.normals; 284 | 285 | dst.push( src[ a + 0 ] ); 286 | dst.push( src[ a + 1 ] ); 287 | dst.push( src[ a + 2 ] ); 288 | dst.push( src[ b + 0 ] ); 289 | dst.push( src[ b + 1 ] ); 290 | dst.push( src[ b + 2 ] ); 291 | dst.push( src[ c + 0 ] ); 292 | dst.push( src[ c + 1 ] ); 293 | dst.push( src[ c + 2 ] ); 294 | 295 | }, 296 | 297 | addUV: function ( a, b, c ) { 298 | 299 | var src = this.uvs; 300 | var dst = this.object.geometry.uvs; 301 | 302 | dst.push( src[ a + 0 ] ); 303 | dst.push( src[ a + 1 ] ); 304 | dst.push( src[ b + 0 ] ); 305 | dst.push( src[ b + 1 ] ); 306 | dst.push( src[ c + 0 ] ); 307 | dst.push( src[ c + 1 ] ); 308 | 309 | }, 310 | 311 | addUVLine: function ( a ) { 312 | 313 | var src = this.uvs; 314 | var dst = this.object.geometry.uvs; 315 | 316 | dst.push( src[ a + 0 ] ); 317 | dst.push( src[ a + 1 ] ); 318 | 319 | }, 320 | 321 | addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 322 | 323 | var vLen = this.vertices.length; 324 | 325 | var ia = this.parseVertexIndex( a, vLen ); 326 | var ib = this.parseVertexIndex( b, vLen ); 327 | var ic = this.parseVertexIndex( c, vLen ); 328 | var id; 329 | 330 | if ( d === undefined ) { 331 | 332 | this.addVertex( ia, ib, ic ); 333 | 334 | } else { 335 | 336 | id = this.parseVertexIndex( d, vLen ); 337 | 338 | this.addVertex( ia, ib, id ); 339 | this.addVertex( ib, ic, id ); 340 | 341 | } 342 | 343 | if ( ua !== undefined ) { 344 | 345 | var uvLen = this.uvs.length; 346 | 347 | ia = this.parseUVIndex( ua, uvLen ); 348 | ib = this.parseUVIndex( ub, uvLen ); 349 | ic = this.parseUVIndex( uc, uvLen ); 350 | 351 | if ( d === undefined ) { 352 | 353 | this.addUV( ia, ib, ic ); 354 | 355 | } else { 356 | 357 | id = this.parseUVIndex( ud, uvLen ); 358 | 359 | this.addUV( ia, ib, id ); 360 | this.addUV( ib, ic, id ); 361 | 362 | } 363 | 364 | } 365 | 366 | if ( na !== undefined ) { 367 | 368 | // Normals are many times the same. If so, skip function call and parseInt. 369 | var nLen = this.normals.length; 370 | ia = this.parseNormalIndex( na, nLen ); 371 | 372 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 373 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 374 | 375 | if ( d === undefined ) { 376 | 377 | this.addNormal( ia, ib, ic ); 378 | 379 | } else { 380 | 381 | id = this.parseNormalIndex( nd, nLen ); 382 | 383 | this.addNormal( ia, ib, id ); 384 | this.addNormal( ib, ic, id ); 385 | 386 | } 387 | 388 | } 389 | 390 | }, 391 | 392 | addLineGeometry: function ( vertices, uvs ) { 393 | 394 | this.object.geometry.type = 'Line'; 395 | 396 | var vLen = this.vertices.length; 397 | var uvLen = this.uvs.length; 398 | 399 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 400 | 401 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); 402 | 403 | } 404 | 405 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { 406 | 407 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); 408 | 409 | } 410 | 411 | } 412 | 413 | }; 414 | 415 | state.startObject( '', false ); 416 | 417 | return state; 418 | 419 | }, 420 | 421 | parse: function ( text ) { 422 | 423 | console.time( 'OBJLoader' ); 424 | 425 | var state = this._createParserState(); 426 | 427 | if ( text.indexOf( '\r\n' ) !== - 1 ) { 428 | 429 | // This is faster than String.split with regex that splits on both 430 | text = text.replace( /\r\n/g, '\n' ); 431 | 432 | } 433 | 434 | if ( text.indexOf( '\\\n' ) !== - 1) { 435 | 436 | // join lines separated by a line continuation character (\) 437 | text = text.replace( /\\\n/g, '' ); 438 | 439 | } 440 | 441 | var lines = text.split( '\n' ); 442 | var line = '', lineFirstChar = '', lineSecondChar = ''; 443 | var lineLength = 0; 444 | var result = []; 445 | 446 | // Faster to just trim left side of the line. Use if available. 447 | var trimLeft = ( typeof ''.trimLeft === 'function' ); 448 | 449 | for ( var i = 0, l = lines.length; i < l; i ++ ) { 450 | 451 | line = lines[ i ]; 452 | 453 | line = trimLeft ? line.trimLeft() : line.trim(); 454 | 455 | lineLength = line.length; 456 | 457 | if ( lineLength === 0 ) continue; 458 | 459 | lineFirstChar = line.charAt( 0 ); 460 | 461 | // @todo invoke passed in handler if any 462 | if ( lineFirstChar === '#' ) continue; 463 | 464 | if ( lineFirstChar === 'v' ) { 465 | 466 | lineSecondChar = line.charAt( 1 ); 467 | 468 | if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) { 469 | 470 | // 0 1 2 3 471 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 472 | 473 | state.vertices.push( 474 | parseFloat( result[ 1 ] ), 475 | parseFloat( result[ 2 ] ), 476 | parseFloat( result[ 3 ] ) 477 | ); 478 | 479 | } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) { 480 | 481 | // 0 1 2 3 482 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 483 | 484 | state.normals.push( 485 | parseFloat( result[ 1 ] ), 486 | parseFloat( result[ 2 ] ), 487 | parseFloat( result[ 3 ] ) 488 | ); 489 | 490 | } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) { 491 | 492 | // 0 1 2 493 | // ["vt 0.1 0.2", "0.1", "0.2"] 494 | 495 | state.uvs.push( 496 | parseFloat( result[ 1 ] ), 497 | parseFloat( result[ 2 ] ) 498 | ); 499 | 500 | } else { 501 | 502 | throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" ); 503 | 504 | } 505 | 506 | } else if ( lineFirstChar === "f" ) { 507 | 508 | if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) { 509 | 510 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 511 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 512 | // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined] 513 | 514 | state.addFace( 515 | result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ], 516 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 517 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 518 | ); 519 | 520 | } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) { 521 | 522 | // f vertex/uv vertex/uv vertex/uv 523 | // 0 1 2 3 4 5 6 7 8 524 | // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined] 525 | 526 | state.addFace( 527 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 528 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 529 | ); 530 | 531 | } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) { 532 | 533 | // f vertex//normal vertex//normal vertex//normal 534 | // 0 1 2 3 4 5 6 7 8 535 | // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined] 536 | 537 | state.addFace( 538 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 539 | undefined, undefined, undefined, undefined, 540 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 541 | ); 542 | 543 | } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) { 544 | 545 | // f vertex vertex vertex 546 | // 0 1 2 3 4 547 | // ["f 1 2 3", "1", "2", "3", undefined] 548 | 549 | state.addFace( 550 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 551 | ); 552 | 553 | } else { 554 | 555 | throw new Error( "Unexpected face line: '" + line + "'" ); 556 | 557 | } 558 | 559 | } else if ( lineFirstChar === "l" ) { 560 | 561 | var lineParts = line.substring( 1 ).trim().split( " " ); 562 | var lineVertices = [], lineUVs = []; 563 | 564 | if ( line.indexOf( "/" ) === - 1 ) { 565 | 566 | lineVertices = lineParts; 567 | 568 | } else { 569 | 570 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 571 | 572 | var parts = lineParts[ li ].split( "/" ); 573 | 574 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 575 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 576 | 577 | } 578 | 579 | } 580 | state.addLineGeometry( lineVertices, lineUVs ); 581 | 582 | } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) { 583 | 584 | // o object_name 585 | // or 586 | // g group_name 587 | 588 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 589 | // var name = result[ 0 ].substr( 1 ).trim(); 590 | var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); 591 | 592 | state.startObject( name ); 593 | 594 | } else if ( this.regexp.material_use_pattern.test( line ) ) { 595 | 596 | // material 597 | 598 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 599 | 600 | } else if ( this.regexp.material_library_pattern.test( line ) ) { 601 | 602 | // mtl file 603 | 604 | state.materialLibraries.push( line.substring( 7 ).trim() ); 605 | 606 | } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) { 607 | 608 | // smooth shading 609 | 610 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 611 | // but does not define a usemtl for each face set. 612 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 613 | // This requires some care to not create extra material on each smooth value for "normal" obj files. 614 | // where explicit usemtl defines geometry groups. 615 | // Example asset: examples/models/obj/cerberus/Cerberus.obj 616 | 617 | var value = result[ 1 ].trim().toLowerCase(); 618 | state.object.smooth = ( value === '1' || value === 'on' ); 619 | 620 | var material = state.object.currentMaterial(); 621 | if ( material ) { 622 | 623 | material.smooth = state.object.smooth; 624 | 625 | } 626 | 627 | } else { 628 | 629 | // Handle null terminated files without exception 630 | if ( line === '\0' ) continue; 631 | 632 | throw new Error( "Unexpected line: '" + line + "'" ); 633 | 634 | } 635 | 636 | } 637 | 638 | state.finalize(); 639 | 640 | var container = new THREE.Group(); 641 | container.materialLibraries = [].concat( state.materialLibraries ); 642 | 643 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 644 | 645 | var object = state.objects[ i ]; 646 | var geometry = object.geometry; 647 | var materials = object.materials; 648 | var isLine = ( geometry.type === 'Line' ); 649 | 650 | // Skip o/g line declarations that did not follow with any faces 651 | if ( geometry.vertices.length === 0 ) continue; 652 | 653 | var buffergeometry = new THREE.BufferGeometry(); 654 | 655 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 656 | 657 | if ( geometry.normals.length > 0 ) { 658 | 659 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 660 | 661 | } else { 662 | 663 | buffergeometry.computeVertexNormals(); 664 | 665 | } 666 | 667 | if ( geometry.uvs.length > 0 ) { 668 | 669 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 670 | 671 | } 672 | 673 | // Create materials 674 | 675 | var createdMaterials = []; 676 | 677 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 678 | 679 | var sourceMaterial = materials[mi]; 680 | var material = undefined; 681 | 682 | if ( this.materials !== null ) { 683 | 684 | material = this.materials.create( sourceMaterial.name ); 685 | 686 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 687 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { 688 | 689 | var materialLine = new THREE.LineBasicMaterial(); 690 | materialLine.copy( material ); 691 | material = materialLine; 692 | 693 | } 694 | 695 | } 696 | 697 | if ( ! material ) { 698 | 699 | material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); 700 | material.name = sourceMaterial.name; 701 | 702 | } 703 | 704 | material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading; 705 | 706 | createdMaterials.push(material); 707 | 708 | } 709 | 710 | // Create mesh 711 | 712 | var mesh; 713 | 714 | if ( createdMaterials.length > 1 ) { 715 | 716 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 717 | 718 | var sourceMaterial = materials[mi]; 719 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 720 | 721 | } 722 | 723 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials ) : new THREE.LineSegments( buffergeometry, createdMaterials ) ); 724 | 725 | } else { 726 | 727 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) ); 728 | } 729 | 730 | mesh.name = object.name; 731 | 732 | container.add( mesh ); 733 | 734 | } 735 | 736 | console.timeEnd( 'OBJLoader' ); 737 | 738 | return container; 739 | 740 | } 741 | 742 | }; 743 | -------------------------------------------------------------------------------- /libs/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finger swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | // 84 | // public methods 85 | // 86 | 87 | this.getPolarAngle = function () { 88 | 89 | return spherical.phi; 90 | 91 | }; 92 | 93 | this.getAzimuthalAngle = function () { 94 | 95 | return spherical.theta; 96 | 97 | }; 98 | 99 | this.saveState = function () { 100 | 101 | scope.target0.copy( scope.target ); 102 | scope.position0.copy( scope.object.position ); 103 | scope.zoom0 = scope.object.zoom; 104 | 105 | }; 106 | 107 | this.reset = function () { 108 | 109 | scope.target.copy( scope.target0 ); 110 | scope.object.position.copy( scope.position0 ); 111 | scope.object.zoom = scope.zoom0; 112 | 113 | scope.object.updateProjectionMatrix(); 114 | scope.dispatchEvent( changeEvent ); 115 | 116 | scope.update(); 117 | 118 | state = STATE.NONE; 119 | 120 | }; 121 | 122 | // this method is exposed, but perhaps it would be better if we can make it private... 123 | this.update = function () { 124 | 125 | var offset = new THREE.Vector3(); 126 | 127 | // so camera.up is the orbit axis 128 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 129 | var quatInverse = quat.clone().inverse(); 130 | 131 | var lastPosition = new THREE.Vector3(); 132 | var lastQuaternion = new THREE.Quaternion(); 133 | 134 | return function update() { 135 | 136 | var position = scope.object.position; 137 | 138 | offset.copy( position ).sub( scope.target ); 139 | 140 | // rotate offset to "y-axis-is-up" space 141 | offset.applyQuaternion( quat ); 142 | 143 | // angle from z-axis around y-axis 144 | spherical.setFromVector3( offset ); 145 | 146 | if ( scope.autoRotate && state === STATE.NONE ) { 147 | 148 | rotateLeft( getAutoRotationAngle() ); 149 | 150 | } 151 | 152 | spherical.theta += sphericalDelta.theta; 153 | spherical.phi += sphericalDelta.phi; 154 | 155 | // restrict theta to be between desired limits 156 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 157 | 158 | // restrict phi to be between desired limits 159 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 160 | 161 | spherical.makeSafe(); 162 | 163 | 164 | spherical.radius *= scale; 165 | 166 | // restrict radius to be between desired limits 167 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 168 | 169 | // move target to panned location 170 | scope.target.add( panOffset ); 171 | 172 | offset.setFromSpherical( spherical ); 173 | 174 | // rotate offset back to "camera-up-vector-is-up" space 175 | offset.applyQuaternion( quatInverse ); 176 | 177 | position.copy( scope.target ).add( offset ); 178 | 179 | scope.object.lookAt( scope.target ); 180 | 181 | if ( scope.enableDamping === true ) { 182 | 183 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 184 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 185 | 186 | } else { 187 | 188 | sphericalDelta.set( 0, 0, 0 ); 189 | 190 | } 191 | 192 | scale = 1; 193 | panOffset.set( 0, 0, 0 ); 194 | 195 | // update condition is: 196 | // min(camera displacement, camera rotation in radians)^2 > EPS 197 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 198 | 199 | if ( zoomChanged || 200 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 201 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 202 | 203 | scope.dispatchEvent( changeEvent ); 204 | 205 | lastPosition.copy( scope.object.position ); 206 | lastQuaternion.copy( scope.object.quaternion ); 207 | zoomChanged = false; 208 | 209 | return true; 210 | 211 | } 212 | 213 | return false; 214 | 215 | }; 216 | 217 | }(); 218 | 219 | this.dispose = function () { 220 | 221 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 222 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 223 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 224 | 225 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 226 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 227 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 228 | 229 | document.removeEventListener( 'mousemove', onMouseMove, false ); 230 | document.removeEventListener( 'mouseup', onMouseUp, false ); 231 | 232 | window.removeEventListener( 'keydown', onKeyDown, false ); 233 | 234 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 235 | 236 | }; 237 | 238 | // 239 | // internals 240 | // 241 | 242 | var scope = this; 243 | 244 | var changeEvent = { type: 'change' }; 245 | var startEvent = { type: 'start' }; 246 | var endEvent = { type: 'end' }; 247 | 248 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; 249 | 250 | var state = STATE.NONE; 251 | 252 | var EPS = 0.000001; 253 | 254 | // current position in spherical coordinates 255 | var spherical = new THREE.Spherical(); 256 | var sphericalDelta = new THREE.Spherical(); 257 | 258 | var scale = 1; 259 | var panOffset = new THREE.Vector3(); 260 | var zoomChanged = false; 261 | 262 | var rotateStart = new THREE.Vector2(); 263 | var rotateEnd = new THREE.Vector2(); 264 | var rotateDelta = new THREE.Vector2(); 265 | 266 | var panStart = new THREE.Vector2(); 267 | var panEnd = new THREE.Vector2(); 268 | var panDelta = new THREE.Vector2(); 269 | 270 | var dollyStart = new THREE.Vector2(); 271 | var dollyEnd = new THREE.Vector2(); 272 | var dollyDelta = new THREE.Vector2(); 273 | 274 | function getAutoRotationAngle() { 275 | 276 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 277 | 278 | } 279 | 280 | function getZoomScale() { 281 | 282 | return Math.pow( 0.95, scope.zoomSpeed ); 283 | 284 | } 285 | 286 | function rotateLeft( angle ) { 287 | 288 | sphericalDelta.theta -= angle; 289 | 290 | } 291 | 292 | function rotateUp( angle ) { 293 | 294 | sphericalDelta.phi -= angle; 295 | 296 | } 297 | 298 | var panLeft = function () { 299 | 300 | var v = new THREE.Vector3(); 301 | 302 | return function panLeft( distance, objectMatrix ) { 303 | 304 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 305 | v.multiplyScalar( - distance ); 306 | 307 | panOffset.add( v ); 308 | 309 | }; 310 | 311 | }(); 312 | 313 | var panUp = function () { 314 | 315 | var v = new THREE.Vector3(); 316 | 317 | return function panUp( distance, objectMatrix ) { 318 | 319 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 320 | v.multiplyScalar( distance ); 321 | 322 | panOffset.add( v ); 323 | 324 | }; 325 | 326 | }(); 327 | 328 | // deltaX and deltaY are in pixels; right and down are positive 329 | var pan = function () { 330 | 331 | var offset = new THREE.Vector3(); 332 | 333 | return function pan( deltaX, deltaY ) { 334 | 335 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 336 | 337 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 338 | 339 | // perspective 340 | var position = scope.object.position; 341 | offset.copy( position ).sub( scope.target ); 342 | var targetDistance = offset.length(); 343 | 344 | // half of the fov is center to top of screen 345 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 346 | 347 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 348 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 349 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 350 | 351 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 352 | 353 | // orthographic 354 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 355 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 356 | 357 | } else { 358 | 359 | // camera neither orthographic nor perspective 360 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 361 | scope.enablePan = false; 362 | 363 | } 364 | 365 | }; 366 | 367 | }(); 368 | 369 | function dollyIn( dollyScale ) { 370 | 371 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 372 | 373 | scale /= dollyScale; 374 | 375 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 376 | 377 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 378 | scope.object.updateProjectionMatrix(); 379 | zoomChanged = true; 380 | 381 | } else { 382 | 383 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 384 | scope.enableZoom = false; 385 | 386 | } 387 | 388 | } 389 | 390 | function dollyOut( dollyScale ) { 391 | 392 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 393 | 394 | scale *= dollyScale; 395 | 396 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 397 | 398 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 399 | scope.object.updateProjectionMatrix(); 400 | zoomChanged = true; 401 | 402 | } else { 403 | 404 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 405 | scope.enableZoom = false; 406 | 407 | } 408 | 409 | } 410 | 411 | // 412 | // event callbacks - update the object state 413 | // 414 | 415 | function handleMouseDownRotate( event ) { 416 | 417 | //console.log( 'handleMouseDownRotate' ); 418 | 419 | rotateStart.set( event.clientX, event.clientY ); 420 | 421 | } 422 | 423 | function handleMouseDownDolly( event ) { 424 | 425 | //console.log( 'handleMouseDownDolly' ); 426 | 427 | dollyStart.set( event.clientX, event.clientY ); 428 | 429 | } 430 | 431 | function handleMouseDownPan( event ) { 432 | 433 | //console.log( 'handleMouseDownPan' ); 434 | 435 | panStart.set( event.clientX, event.clientY ); 436 | 437 | } 438 | 439 | function handleMouseMoveRotate( event ) { 440 | 441 | //console.log( 'handleMouseMoveRotate' ); 442 | 443 | rotateEnd.set( event.clientX, event.clientY ); 444 | rotateDelta.subVectors( rotateEnd, rotateStart ); 445 | 446 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 447 | 448 | // rotating across whole screen goes 360 degrees around 449 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 450 | 451 | // rotating up and down along whole screen attempts to go 360, but limited to 180 452 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 453 | 454 | rotateStart.copy( rotateEnd ); 455 | 456 | scope.update(); 457 | 458 | } 459 | 460 | function handleMouseMoveDolly( event ) { 461 | 462 | //console.log( 'handleMouseMoveDolly' ); 463 | 464 | dollyEnd.set( event.clientX, event.clientY ); 465 | 466 | dollyDelta.subVectors( dollyEnd, dollyStart ); 467 | 468 | if ( dollyDelta.y > 0 ) { 469 | 470 | dollyIn( getZoomScale() ); 471 | 472 | } else if ( dollyDelta.y < 0 ) { 473 | 474 | dollyOut( getZoomScale() ); 475 | 476 | } 477 | 478 | dollyStart.copy( dollyEnd ); 479 | 480 | scope.update(); 481 | 482 | } 483 | 484 | function handleMouseMovePan( event ) { 485 | 486 | //console.log( 'handleMouseMovePan' ); 487 | 488 | panEnd.set( event.clientX, event.clientY ); 489 | 490 | panDelta.subVectors( panEnd, panStart ); 491 | 492 | pan( panDelta.x, panDelta.y ); 493 | 494 | panStart.copy( panEnd ); 495 | 496 | scope.update(); 497 | 498 | } 499 | 500 | function handleMouseUp( event ) { 501 | 502 | // console.log( 'handleMouseUp' ); 503 | 504 | } 505 | 506 | function handleMouseWheel( event ) { 507 | 508 | // console.log( 'handleMouseWheel' ); 509 | 510 | if ( event.deltaY < 0 ) { 511 | 512 | dollyOut( getZoomScale() ); 513 | 514 | } else if ( event.deltaY > 0 ) { 515 | 516 | dollyIn( getZoomScale() ); 517 | 518 | } 519 | 520 | scope.update(); 521 | 522 | } 523 | 524 | function handleKeyDown( event ) { 525 | 526 | //console.log( 'handleKeyDown' ); 527 | 528 | switch ( event.keyCode ) { 529 | 530 | case scope.keys.UP: 531 | pan( 0, scope.keyPanSpeed ); 532 | scope.update(); 533 | break; 534 | 535 | case scope.keys.BOTTOM: 536 | pan( 0, - scope.keyPanSpeed ); 537 | scope.update(); 538 | break; 539 | 540 | case scope.keys.LEFT: 541 | pan( scope.keyPanSpeed, 0 ); 542 | scope.update(); 543 | break; 544 | 545 | case scope.keys.RIGHT: 546 | pan( - scope.keyPanSpeed, 0 ); 547 | scope.update(); 548 | break; 549 | 550 | } 551 | 552 | } 553 | 554 | function handleTouchStartRotate( event ) { 555 | 556 | //console.log( 'handleTouchStartRotate' ); 557 | 558 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 559 | 560 | } 561 | 562 | function handleTouchStartDolly( event ) { 563 | 564 | //console.log( 'handleTouchStartDolly' ); 565 | 566 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 567 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 568 | 569 | var distance = Math.sqrt( dx * dx + dy * dy ); 570 | 571 | dollyStart.set( 0, distance ); 572 | 573 | } 574 | 575 | function handleTouchStartPan( event ) { 576 | 577 | //console.log( 'handleTouchStartPan' ); 578 | 579 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 580 | 581 | } 582 | 583 | function handleTouchMoveRotate( event ) { 584 | 585 | //console.log( 'handleTouchMoveRotate' ); 586 | 587 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 588 | rotateDelta.subVectors( rotateEnd, rotateStart ); 589 | 590 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 591 | 592 | // rotating across whole screen goes 360 degrees around 593 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 594 | 595 | // rotating up and down along whole screen attempts to go 360, but limited to 180 596 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 597 | 598 | rotateStart.copy( rotateEnd ); 599 | 600 | scope.update(); 601 | 602 | } 603 | 604 | function handleTouchMoveDolly( event ) { 605 | 606 | //console.log( 'handleTouchMoveDolly' ); 607 | 608 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 609 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 610 | 611 | var distance = Math.sqrt( dx * dx + dy * dy ); 612 | 613 | dollyEnd.set( 0, distance ); 614 | 615 | dollyDelta.subVectors( dollyEnd, dollyStart ); 616 | 617 | if ( dollyDelta.y > 0 ) { 618 | 619 | dollyOut( getZoomScale() ); 620 | 621 | } else if ( dollyDelta.y < 0 ) { 622 | 623 | dollyIn( getZoomScale() ); 624 | 625 | } 626 | 627 | dollyStart.copy( dollyEnd ); 628 | 629 | scope.update(); 630 | 631 | } 632 | 633 | function handleTouchMovePan( event ) { 634 | 635 | //console.log( 'handleTouchMovePan' ); 636 | 637 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 638 | 639 | panDelta.subVectors( panEnd, panStart ); 640 | 641 | pan( panDelta.x, panDelta.y ); 642 | 643 | panStart.copy( panEnd ); 644 | 645 | scope.update(); 646 | 647 | } 648 | 649 | function handleTouchEnd( event ) { 650 | 651 | //console.log( 'handleTouchEnd' ); 652 | 653 | } 654 | 655 | // 656 | // event handlers - FSM: listen for events and reset state 657 | // 658 | 659 | function onMouseDown( event ) { 660 | 661 | if ( scope.enabled === false ) return; 662 | 663 | event.preventDefault(); 664 | 665 | switch ( event.button ) { 666 | 667 | case scope.mouseButtons.ORBIT: 668 | 669 | if ( scope.enableRotate === false ) return; 670 | 671 | handleMouseDownRotate( event ); 672 | 673 | state = STATE.ROTATE; 674 | 675 | break; 676 | 677 | case scope.mouseButtons.ZOOM: 678 | 679 | if ( scope.enableZoom === false ) return; 680 | 681 | handleMouseDownDolly( event ); 682 | 683 | state = STATE.DOLLY; 684 | 685 | break; 686 | 687 | case scope.mouseButtons.PAN: 688 | 689 | if ( scope.enablePan === false ) return; 690 | 691 | handleMouseDownPan( event ); 692 | 693 | state = STATE.PAN; 694 | 695 | break; 696 | 697 | } 698 | 699 | if ( state !== STATE.NONE ) { 700 | 701 | document.addEventListener( 'mousemove', onMouseMove, false ); 702 | document.addEventListener( 'mouseup', onMouseUp, false ); 703 | 704 | scope.dispatchEvent( startEvent ); 705 | 706 | } 707 | 708 | } 709 | 710 | function onMouseMove( event ) { 711 | 712 | if ( scope.enabled === false ) return; 713 | 714 | event.preventDefault(); 715 | 716 | switch ( state ) { 717 | 718 | case STATE.ROTATE: 719 | 720 | if ( scope.enableRotate === false ) return; 721 | 722 | handleMouseMoveRotate( event ); 723 | 724 | break; 725 | 726 | case STATE.DOLLY: 727 | 728 | if ( scope.enableZoom === false ) return; 729 | 730 | handleMouseMoveDolly( event ); 731 | 732 | break; 733 | 734 | case STATE.PAN: 735 | 736 | if ( scope.enablePan === false ) return; 737 | 738 | handleMouseMovePan( event ); 739 | 740 | break; 741 | 742 | } 743 | 744 | } 745 | 746 | function onMouseUp( event ) { 747 | 748 | if ( scope.enabled === false ) return; 749 | 750 | handleMouseUp( event ); 751 | 752 | document.removeEventListener( 'mousemove', onMouseMove, false ); 753 | document.removeEventListener( 'mouseup', onMouseUp, false ); 754 | 755 | scope.dispatchEvent( endEvent ); 756 | 757 | state = STATE.NONE; 758 | 759 | } 760 | 761 | function onMouseWheel( event ) { 762 | 763 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 764 | 765 | event.preventDefault(); 766 | event.stopPropagation(); 767 | 768 | handleMouseWheel( event ); 769 | 770 | scope.dispatchEvent( startEvent ); // not sure why these are here... 771 | scope.dispatchEvent( endEvent ); 772 | 773 | } 774 | 775 | function onKeyDown( event ) { 776 | 777 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 778 | 779 | handleKeyDown( event ); 780 | 781 | } 782 | 783 | function onTouchStart( event ) { 784 | 785 | if ( scope.enabled === false ) return; 786 | 787 | switch ( event.touches.length ) { 788 | 789 | case 1: // one-fingered touch: rotate 790 | 791 | if ( scope.enableRotate === false ) return; 792 | 793 | handleTouchStartRotate( event ); 794 | 795 | state = STATE.TOUCH_ROTATE; 796 | 797 | break; 798 | 799 | case 2: // two-fingered touch: dolly 800 | 801 | if ( scope.enableZoom === false ) return; 802 | 803 | handleTouchStartDolly( event ); 804 | 805 | state = STATE.TOUCH_DOLLY; 806 | 807 | break; 808 | 809 | case 3: // three-fingered touch: pan 810 | 811 | if ( scope.enablePan === false ) return; 812 | 813 | handleTouchStartPan( event ); 814 | 815 | state = STATE.TOUCH_PAN; 816 | 817 | break; 818 | 819 | default: 820 | 821 | state = STATE.NONE; 822 | 823 | } 824 | 825 | if ( state !== STATE.NONE ) { 826 | 827 | scope.dispatchEvent( startEvent ); 828 | 829 | } 830 | 831 | } 832 | 833 | function onTouchMove( event ) { 834 | 835 | if ( scope.enabled === false ) return; 836 | 837 | event.preventDefault(); 838 | event.stopPropagation(); 839 | 840 | switch ( event.touches.length ) { 841 | 842 | case 1: // one-fingered touch: rotate 843 | 844 | if ( scope.enableRotate === false ) return; 845 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 846 | 847 | handleTouchMoveRotate( event ); 848 | 849 | break; 850 | 851 | case 2: // two-fingered touch: dolly 852 | 853 | if ( scope.enableZoom === false ) return; 854 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 855 | 856 | handleTouchMoveDolly( event ); 857 | 858 | break; 859 | 860 | case 3: // three-fingered touch: pan 861 | 862 | if ( scope.enablePan === false ) return; 863 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 864 | 865 | handleTouchMovePan( event ); 866 | 867 | break; 868 | 869 | default: 870 | 871 | state = STATE.NONE; 872 | 873 | } 874 | 875 | } 876 | 877 | function onTouchEnd( event ) { 878 | 879 | if ( scope.enabled === false ) return; 880 | 881 | handleTouchEnd( event ); 882 | 883 | scope.dispatchEvent( endEvent ); 884 | 885 | state = STATE.NONE; 886 | 887 | } 888 | 889 | function onContextMenu( event ) { 890 | 891 | event.preventDefault(); 892 | 893 | } 894 | 895 | // 896 | 897 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 898 | 899 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 900 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 901 | 902 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 903 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 904 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 905 | 906 | window.addEventListener( 'keydown', onKeyDown, false ); 907 | 908 | // force an update at start 909 | 910 | this.update(); 911 | 912 | }; 913 | 914 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 915 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 916 | 917 | Object.defineProperties( THREE.OrbitControls.prototype, { 918 | 919 | center: { 920 | 921 | get: function () { 922 | 923 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 924 | return this.target; 925 | 926 | } 927 | 928 | }, 929 | 930 | // backward compatibility 931 | 932 | noZoom: { 933 | 934 | get: function () { 935 | 936 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 937 | return ! this.enableZoom; 938 | 939 | }, 940 | 941 | set: function ( value ) { 942 | 943 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 944 | this.enableZoom = ! value; 945 | 946 | } 947 | 948 | }, 949 | 950 | noRotate: { 951 | 952 | get: function () { 953 | 954 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 955 | return ! this.enableRotate; 956 | 957 | }, 958 | 959 | set: function ( value ) { 960 | 961 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 962 | this.enableRotate = ! value; 963 | 964 | } 965 | 966 | }, 967 | 968 | noPan: { 969 | 970 | get: function () { 971 | 972 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 973 | return ! this.enablePan; 974 | 975 | }, 976 | 977 | set: function ( value ) { 978 | 979 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 980 | this.enablePan = ! value; 981 | 982 | } 983 | 984 | }, 985 | 986 | noKeys: { 987 | 988 | get: function () { 989 | 990 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 991 | return ! this.enableKeys; 992 | 993 | }, 994 | 995 | set: function ( value ) { 996 | 997 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 998 | this.enableKeys = ! value; 999 | 1000 | } 1001 | 1002 | }, 1003 | 1004 | staticMoving: { 1005 | 1006 | get: function () { 1007 | 1008 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1009 | return ! this.enableDamping; 1010 | 1011 | }, 1012 | 1013 | set: function ( value ) { 1014 | 1015 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1016 | this.enableDamping = ! value; 1017 | 1018 | } 1019 | 1020 | }, 1021 | 1022 | dynamicDampingFactor: { 1023 | 1024 | get: function () { 1025 | 1026 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1027 | return this.dampingFactor; 1028 | 1029 | }, 1030 | 1031 | set: function ( value ) { 1032 | 1033 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1034 | this.dampingFactor = value; 1035 | 1036 | } 1037 | 1038 | } 1039 | 1040 | } ); 1041 | -------------------------------------------------------------------------------- /libs/bvhtree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bvh-tree 3 | * A Bounding Volume Hierarchy data structure implementation. 4 | * https://github.com/benraziel/bvh-tree 5 | * 6 | * @author Ben Raziel 7 | */ 8 | var bvhtree = bvhtree || {}; 9 | bvhtree.EPSILON = 1e-6; 10 | 11 | /** 12 | * A 3D Vector class. Based on three.js Vector3 13 | */ 14 | bvhtree.BVHVector3 = function ( x, y, z ) { 15 | this.x = x || 0; 16 | this.y = y || 0; 17 | this.z = z || 0; 18 | 19 | }; 20 | 21 | bvhtree.BVHVector3.prototype = { 22 | 23 | constructor: bvhtree.BVHVector3, 24 | 25 | copy: function ( v ) { 26 | 27 | this.x = v.x; 28 | this.y = v.y; 29 | this.z = v.z; 30 | 31 | return this; 32 | 33 | }, 34 | 35 | set: function ( x, y, z ) { 36 | 37 | this.x = x; 38 | this.y = y; 39 | this.z = z; 40 | 41 | return this; 42 | }, 43 | 44 | setFromArray: function(array, firstElementPos) { 45 | this.x = array[firstElementPos]; 46 | this.y = array[firstElementPos+1]; 47 | this.z = array[firstElementPos+2]; 48 | }, 49 | 50 | add: function ( v ) { 51 | 52 | this.x += v.x; 53 | this.y += v.y; 54 | this.z += v.z; 55 | 56 | return this; 57 | }, 58 | 59 | multiplyScalar: function ( scalar ) { 60 | 61 | this.x *= scalar; 62 | this.y *= scalar; 63 | this.z *= scalar; 64 | 65 | return this; 66 | 67 | }, 68 | 69 | subVectors: function ( a, b ) { 70 | 71 | this.x = a.x - b.x; 72 | this.y = a.y - b.y; 73 | this.z = a.z - b.z; 74 | 75 | return this; 76 | 77 | }, 78 | 79 | dot: function ( v ) { 80 | 81 | return this.x * v.x + this.y * v.y + this.z * v.z; 82 | 83 | }, 84 | 85 | cross: function ( v ) { 86 | var x = this.x, y = this.y, z = this.z; 87 | 88 | this.x = y * v.z - z * v.y; 89 | this.y = z * v.x - x * v.z; 90 | this.z = x * v.y - y * v.x; 91 | 92 | return this; 93 | }, 94 | 95 | crossVectors: function ( a, b ) { 96 | 97 | var ax = a.x, ay = a.y, az = a.z; 98 | var bx = b.x, by = b.y, bz = b.z; 99 | 100 | this.x = ay * bz - az * by; 101 | this.y = az * bx - ax * bz; 102 | this.z = ax * by - ay * bx; 103 | 104 | return this; 105 | }, 106 | 107 | clone: function () { 108 | return new bvhtree.BVHVector3( this.x, this.y, this.z ); 109 | } 110 | }; 111 | 112 | /** 113 | * @typedef {Object} Point A Point in 3D space 114 | * @property {number} x x coordinate of the point 115 | * @property {number} y y coordinate of the point 116 | * @property {number} z z coordinate of the point 117 | * 118 | * @typedef Point[3] Triangle A triangle in 3D space 119 | */ 120 | 121 | /** 122 | * Constructs a bounding volume heirarchy from a list of triangles 123 | * @class 124 | * @param {Triangle[]} triangles an array of triangles to index. Each triangle is represented as an array of 3 xyz coordinates: [{x: X0, y: Y0, z: Z0}, {x: X1, y: Y1, z: Z1}, {x: X2, y: Y2, z: Z2}] 125 | * @param {number} [maxTrianglesPerNode=10] the maximum number of triangles in each node of the BVH tree. Once this value is reached, that node is split into two child nodes. 126 | */ 127 | bvhtree.BVH = function(triangles, maxTrianglesPerNode) { 128 | var trianglesArray = []; 129 | trianglesArray.length = triangles.length * 9; 130 | 131 | for (var i = 0; i < triangles.length; i++) { 132 | var p0 = triangles[i][0]; 133 | var p1 = triangles[i][1]; 134 | var p2 = triangles[i][2]; 135 | 136 | trianglesArray[i*9] = p0.x; 137 | trianglesArray[i*9+1] = p0.y; 138 | trianglesArray[i*9+2] = p0.z; 139 | 140 | trianglesArray[i*9+3] = p1.x; 141 | trianglesArray[i*9+4] = p1.y; 142 | trianglesArray[i*9+5] = p1.z; 143 | 144 | trianglesArray[i*9+6] = p2.x; 145 | trianglesArray[i*9+7] = p2.y; 146 | trianglesArray[i*9+8] = p2.z; 147 | } 148 | 149 | 150 | this._trianglesArray = trianglesArray; 151 | this._maxTrianglesPerNode = maxTrianglesPerNode || 10; 152 | this._bboxArray = this.calcBoundingBoxes(trianglesArray); 153 | 154 | // clone a helper array 155 | this._bboxHelper = new Float32Array(this._bboxArray.length); 156 | this._bboxHelper.set(this._bboxArray); 157 | 158 | // create the root node, add all the triangles to it 159 | var triangleCount = trianglesArray.length / 9; 160 | var extents = this.calcExtents(0, triangleCount, bvhtree.EPSILON); 161 | this._rootNode = new bvhtree.BVHNode(extents[0], extents[1], 0, triangleCount, 0); 162 | 163 | this._nodesToSplit = [this._rootNode]; 164 | 165 | while (this._nodesToSplit.length > 0) { 166 | var node = this._nodesToSplit.pop(); 167 | this.splitNode(node); 168 | } 169 | }; 170 | 171 | /** 172 | * returns a list of all the triangles in the BVH which interected a specific node. 173 | * We use the BVH node structure to first cull out nodes which do not intereset the ray. 174 | * For rays that did intersect, we test intersection of the ray with each triangle 175 | * @param {Point} rayOrigin the origin position of the ray. 176 | * @param {Point} rayDirection the direction vector of the ray. 177 | * @param {Boolean} backfaceCulling if 'true', only intersections with front-faces of the mesh will be performed. 178 | * @return IntersectionResult[] an array of intersection result, one for each triangle which intersected the BVH 179 | * 180 | * @typedef {Object} IntersectionResult 181 | * @property Array[] triangle the triangle which the ray intersected 182 | * @property number triangleIndex the position of the interescting triangle in the input triangle array provided to the BVH constructor. 183 | * @property {Point} intersectionPoint the interesection point of the ray on the triangle. 184 | */ 185 | bvhtree.BVH.prototype.intersectRay = function(rayOrigin, rayDirection, backfaceCulling) { 186 | var nodesToIntersect = [this._rootNode]; 187 | var trianglesInIntersectingNodes = []; // a list of nodes that intersect the ray (according to their bounding box) 188 | var intersectingTriangles = []; 189 | var i; 190 | 191 | var invRayDirection = new bvhtree.BVHVector3( 192 | 1.0 / rayDirection.x, 193 | 1.0 / rayDirection.y, 194 | 1.0 / rayDirection.z 195 | ); 196 | 197 | // go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the ray. 198 | // note: these triangles may not intersect the ray themselves 199 | while (nodesToIntersect.length > 0) { 200 | var node = nodesToIntersect.pop(); 201 | 202 | if (bvhtree.BVH.intersectNodeBox(rayOrigin, invRayDirection, node)) { 203 | if (node._node0) { 204 | nodesToIntersect.push(node._node0); 205 | } 206 | 207 | if (node._node1) { 208 | nodesToIntersect.push(node._node1); 209 | } 210 | 211 | for (i = node._startIndex; i < node._endIndex; i++) { 212 | trianglesInIntersectingNodes.push(this._bboxArray[i*7]); 213 | } 214 | } 215 | } 216 | 217 | // go over the list of candidate triangles, and check each of them using ray triangle intersection 218 | var a = new bvhtree.BVHVector3(); 219 | var b = new bvhtree.BVHVector3(); 220 | var c = new bvhtree.BVHVector3(); 221 | var rayOriginVec3 = new bvhtree.BVHVector3(rayOrigin.x, rayOrigin.y, rayOrigin.z); 222 | var rayDirectionVec3 = new bvhtree.BVHVector3(rayDirection.x, rayDirection.y, rayDirection.z); 223 | 224 | for (i = 0; i < trianglesInIntersectingNodes.length; i++) { 225 | var triIndex = trianglesInIntersectingNodes[i]; 226 | 227 | a.setFromArray(this._trianglesArray, triIndex*9); 228 | b.setFromArray(this._trianglesArray, triIndex*9+3); 229 | c.setFromArray(this._trianglesArray, triIndex*9+6); 230 | 231 | var intersectionPoint = bvhtree.BVH.intersectRayTriangle(a, b, c, rayOriginVec3, rayDirectionVec3, backfaceCulling); 232 | 233 | if (intersectionPoint) { 234 | intersectingTriangles.push({ 235 | triangle: [a.clone(), b.clone(), c.clone()], 236 | triangleIndex: triIndex, 237 | intersectionPoint: intersectionPoint 238 | }); 239 | } 240 | } 241 | 242 | return intersectingTriangles; 243 | }; 244 | 245 | /** 246 | * Gets an array of triangle, and calculates the bounding box for each of them, and adds an index to the triangle's position in the array 247 | * Each bbox is saved as 7 values in a Float32Array: (position, minX, minY, minZ, maxX, maxY, maxZ) 248 | */ 249 | bvhtree.BVH.prototype.calcBoundingBoxes = function(trianglesArray) { 250 | var p0x, p0y, p0z; 251 | var p1x, p1y, p1z; 252 | var p2x, p2y, p2z; 253 | var minX, minY, minZ; 254 | var maxX, maxY, maxZ; 255 | 256 | var triangleCount = trianglesArray.length / 9; 257 | var bboxArray = new Float32Array(triangleCount * 7); 258 | 259 | for (var i = 0; i < triangleCount; i++) { 260 | p0x = trianglesArray[i*9]; 261 | p0y = trianglesArray[i*9+1]; 262 | p0z = trianglesArray[i*9+2]; 263 | p1x = trianglesArray[i*9+3]; 264 | p1y = trianglesArray[i*9+4]; 265 | p1z = trianglesArray[i*9+5]; 266 | p2x = trianglesArray[i*9+6]; 267 | p2y = trianglesArray[i*9+7]; 268 | p2z = trianglesArray[i*9+8]; 269 | 270 | minX = Math.min(Math.min(p0x, p1x), p2x); 271 | minY = Math.min(Math.min(p0y, p1y), p2y); 272 | minZ = Math.min(Math.min(p0z, p1z), p2z); 273 | maxX = Math.max(Math.max(p0x, p1x), p2x); 274 | maxY = Math.max(Math.max(p0y, p1y), p2y); 275 | maxZ = Math.max(Math.max(p0z, p1z), p2z); 276 | 277 | bvhtree.BVH.setBox(bboxArray, i, i, minX, minY, minZ, maxX, maxY, maxZ); 278 | } 279 | 280 | return bboxArray; 281 | }; 282 | 283 | /** 284 | * Calculates the extents (i.e the min and max coordinates) of a list of bounding boxes in the bboxArray 285 | * @param startIndex the index of the first triangle that we want to calc extents for 286 | * @param endIndex the index of the last triangle that we want to calc extents for 287 | * @param expandBy a small epsilon to expand the bbox by, for safety during ray-box intersections 288 | */ 289 | bvhtree.BVH.prototype.calcExtents = function(startIndex, endIndex, expandBy) { 290 | expandBy = expandBy || 0.0; 291 | 292 | if (startIndex >= endIndex) { 293 | return [{'x': 0, 'y': 0, 'z': 0}, {'x': 0, 'y': 0, 'z': 0}]; 294 | } 295 | 296 | var minX = Number.MAX_VALUE; 297 | var minY = Number.MAX_VALUE; 298 | var minZ = Number.MAX_VALUE; 299 | var maxX = -Number.MAX_VALUE; 300 | var maxY = -Number.MAX_VALUE; 301 | var maxZ = -Number.MAX_VALUE; 302 | 303 | for (var i = startIndex; i < endIndex; i++) { 304 | minX = Math.min(this._bboxArray[i*7+1], minX); 305 | minY = Math.min(this._bboxArray[i*7+2], minY); 306 | minZ = Math.min(this._bboxArray[i*7+3], minZ); 307 | maxX = Math.max(this._bboxArray[i*7+4], maxX); 308 | maxY = Math.max(this._bboxArray[i*7+5], maxY); 309 | maxZ = Math.max(this._bboxArray[i*7+6], maxZ); 310 | } 311 | 312 | return [ 313 | {'x': minX - expandBy, 'y': minY - expandBy, 'z': minZ - expandBy}, 314 | {'x': maxX + expandBy, 'y': maxY + expandBy, 'z': maxZ + expandBy} 315 | ]; 316 | }; 317 | 318 | bvhtree.BVH.prototype.splitNode = function(node) { 319 | if ((node.elementCount() <= this._maxTrianglesPerNode) || (node.elementCount() === 0)) { 320 | return; 321 | } 322 | 323 | var startIndex = node._startIndex; 324 | var endIndex = node._endIndex; 325 | 326 | var leftNode = [ [],[],[] ]; 327 | var rightNode = [ [],[],[] ]; 328 | var extentCenters = [node.centerX(), node.centerY(), node.centerZ()]; 329 | 330 | var extentsLength = [ 331 | node._extentsMax.x - node._extentsMin.x, 332 | node._extentsMax.y - node._extentsMin.y, 333 | node._extentsMax.z - node._extentsMin.z 334 | ]; 335 | 336 | var objectCenter = []; 337 | objectCenter.length = 3; 338 | 339 | for (var i = startIndex; i < endIndex; i++) { 340 | objectCenter[0] = (this._bboxArray[i * 7 + 1] + this._bboxArray[i * 7 + 4]) * 0.5; // center = (min + max) / 2 341 | objectCenter[1] = (this._bboxArray[i * 7 + 2] + this._bboxArray[i * 7 + 5]) * 0.5; // center = (min + max) / 2 342 | objectCenter[2] = (this._bboxArray[i * 7 + 3] + this._bboxArray[i * 7 + 6]) * 0.5; // center = (min + max) / 2 343 | 344 | for (var j = 0; j < 3; j++) { 345 | if (objectCenter[j] < extentCenters[j]) { 346 | leftNode[j].push(i); 347 | } 348 | else { 349 | rightNode[j].push(i); 350 | } 351 | } 352 | } 353 | 354 | // check if we couldn't split the node by any of the axes (x, y or z). halt here, dont try to split any more (cause it will always fail, and we'll enter an infinite loop 355 | var splitFailed = []; 356 | splitFailed.length = 3; 357 | 358 | splitFailed[0] = (leftNode[0].length === 0) || (rightNode[0].length === 0); 359 | splitFailed[1] = (leftNode[1].length === 0) || (rightNode[1].length === 0); 360 | splitFailed[2] = (leftNode[2].length === 0) || (rightNode[2].length === 0); 361 | 362 | if (splitFailed[0] && splitFailed[1] && splitFailed[2]) { 363 | return; 364 | } 365 | 366 | // choose the longest split axis. if we can't split by it, choose next best one. 367 | var splitOrder = [0, 1, 2]; 368 | 369 | splitOrder.sort(function(axis0, axis1) { 370 | return (extentsLength[axis1] - extentsLength[axis0]) 371 | }); 372 | 373 | var leftElements; 374 | var rightElements; 375 | 376 | for (j = 0; j < 3; j++) { 377 | var candidateIndex = splitOrder[j]; 378 | 379 | if (!splitFailed[candidateIndex]) { 380 | leftElements = leftNode[candidateIndex]; 381 | rightElements = rightNode[candidateIndex]; 382 | 383 | break; 384 | } 385 | } 386 | 387 | // sort the elements in range (startIndex, endIndex) according to which node they should be at 388 | var node0Start = startIndex; 389 | var node0End = node0Start + leftElements.length; 390 | var node1Start = node0End; 391 | var node1End = endIndex; 392 | var currElement; 393 | 394 | var helperPos = node._startIndex; 395 | var concatenatedElements = leftElements.concat(rightElements); 396 | 397 | for (i = 0; i < concatenatedElements.length; i++) { 398 | currElement = concatenatedElements[i]; 399 | bvhtree.BVH.copyBox(this._bboxArray, currElement, this._bboxHelper, helperPos); 400 | helperPos++; 401 | } 402 | 403 | // copy results back to main array 404 | var subArr = this._bboxHelper.subarray(node._startIndex * 7, node._endIndex * 7); 405 | this._bboxArray.set(subArr, node._startIndex * 7); 406 | 407 | // create 2 new nodes for the node we just split, and add links to them from the parent node 408 | var node0Extents = this.calcExtents(node0Start, node0End, bvhtree.EPSILON); 409 | var node1Extents = this.calcExtents(node1Start, node1End, bvhtree.EPSILON); 410 | 411 | var node0 = new bvhtree.BVHNode(node0Extents[0], node0Extents[1], node0Start, node0End, node._level + 1); 412 | var node1 = new bvhtree.BVHNode(node1Extents[0], node1Extents[1], node1Start, node1End, node._level + 1); 413 | 414 | node._node0 = node0; 415 | node._node1 = node1; 416 | node.clearShapes(); 417 | 418 | // add new nodes to the split queue 419 | this._nodesToSplit.push(node0); 420 | this._nodesToSplit.push(node1); 421 | }; 422 | 423 | bvhtree.BVH._calcTValues = function(minVal, maxVal, rayOriginCoord, invdir) { 424 | var res = {min: 0, max: 0}; 425 | 426 | if ( invdir >= 0 ) { 427 | res.min = ( minVal - rayOriginCoord ) * invdir; 428 | res.max = ( maxVal - rayOriginCoord ) * invdir; 429 | 430 | } else { 431 | res.min = ( maxVal - rayOriginCoord ) * invdir; 432 | res.max = ( minVal - rayOriginCoord ) * invdir; 433 | } 434 | 435 | return res; 436 | }; 437 | 438 | bvhtree.BVH.intersectNodeBox = function(rayOrigin, invRayDirection, node) { 439 | var t = bvhtree.BVH._calcTValues(node._extentsMin.x, node._extentsMax.x, rayOrigin.x, invRayDirection.x); 440 | var ty = bvhtree.BVH._calcTValues(node._extentsMin.y, node._extentsMax.y, rayOrigin.y, invRayDirection.y); 441 | 442 | if ( ( t.min > ty.max ) || ( ty.min > t.max ) ) { 443 | return false; 444 | } 445 | 446 | // These lines also handle the case where tmin or tmax is NaN 447 | // (result of 0 * Infinity). x !== x returns true if x is NaN 448 | if ( ty.min > t.min || t.min !== t.min ) { 449 | t.min = ty.min; 450 | } 451 | 452 | if ( ty.max < t.max || t.max !== t.max ) { 453 | t.max = ty.max; 454 | } 455 | 456 | var tz = bvhtree.BVH._calcTValues(node._extentsMin.z, node._extentsMax.z, rayOrigin.z, invRayDirection.z); 457 | 458 | if ( ( t.min > tz.max ) || ( tz.min > t.max ) ) { 459 | return false; 460 | } 461 | 462 | if ( tz.min > t.min || t.min !== t.min ) { 463 | t.min = tz.min; 464 | } 465 | 466 | if ( tz.max < t.max || t.max !== t.max ) { 467 | t.max = tz.max; 468 | } 469 | 470 | //return point closest to the ray (positive side) 471 | if (t.max < 0 ) { 472 | return false; 473 | } 474 | 475 | return true; 476 | }; 477 | 478 | bvhtree.BVH.intersectRayTriangle = (function () { 479 | // Compute the offset origin, edges, and normal. 480 | var diff = new bvhtree.BVHVector3(); 481 | var edge1 = new bvhtree.BVHVector3(); 482 | var edge2 = new bvhtree.BVHVector3(); 483 | var normal = new bvhtree.BVHVector3(); 484 | 485 | return function (a, b, c, rayOrigin, rayDirection, backfaceCulling) { 486 | 487 | // from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp 488 | 489 | edge1.subVectors(b, a); 490 | edge2.subVectors(c, a); 491 | normal.crossVectors(edge1, edge2); 492 | 493 | // Solve Q + t*D = b1*E1 + bL*E2 (Q = kDiff, D = ray direction, 494 | // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by 495 | // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) 496 | // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) 497 | // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) 498 | var DdN = rayDirection.dot(normal); 499 | var sign; 500 | 501 | if (DdN > 0) { 502 | 503 | if (backfaceCulling) { 504 | return null; 505 | } 506 | 507 | sign = 1; 508 | 509 | } else if (DdN < 0) { 510 | 511 | sign = -1; 512 | DdN = -DdN; 513 | 514 | } else { 515 | 516 | return null; 517 | 518 | } 519 | 520 | diff.subVectors(rayOrigin, a); 521 | var DdQxE2 = sign * rayDirection.dot(edge2.crossVectors(diff, edge2)); 522 | 523 | // b1 < 0, no intersection 524 | if (DdQxE2 < 0) { 525 | return null; 526 | } 527 | 528 | var DdE1xQ = sign * rayDirection.dot(edge1.cross(diff)); 529 | 530 | // b2 < 0, no intersection 531 | if (DdE1xQ < 0) { 532 | return null; 533 | } 534 | 535 | // b1+b2 > 1, no intersection 536 | if (DdQxE2 + DdE1xQ > DdN) { 537 | return null; 538 | } 539 | 540 | // Line intersects triangle, check if ray does. 541 | var QdN = -sign * diff.dot(normal); 542 | 543 | // t < 0, no intersection 544 | if (QdN < 0) { 545 | return null; 546 | } 547 | 548 | // Ray intersects triangle. 549 | var t = QdN / DdN; 550 | var result = new bvhtree.BVHVector3(); 551 | return result.copy( rayDirection ).multiplyScalar( t ).add( rayOrigin ); 552 | }; 553 | }()); 554 | 555 | bvhtree.BVH.setBox = function(bboxArray, pos, triangleId, minX, minY, minZ, maxX, maxY, maxZ) { 556 | bboxArray[pos*7] = triangleId; 557 | bboxArray[pos*7+1] = minX; 558 | bboxArray[pos*7+2] = minY; 559 | bboxArray[pos*7+3] = minZ; 560 | bboxArray[pos*7+4] = maxX; 561 | bboxArray[pos*7+5] = maxY; 562 | bboxArray[pos*7+6] = maxZ; 563 | }; 564 | 565 | bvhtree.BVH.copyBox = function(sourceArray, sourcePos, destArray, destPos) { 566 | destArray[destPos*7] = sourceArray[sourcePos*7]; 567 | destArray[destPos*7+1] = sourceArray[sourcePos*7+1]; 568 | destArray[destPos*7+2] = sourceArray[sourcePos*7+2]; 569 | destArray[destPos*7+3] = sourceArray[sourcePos*7+3]; 570 | destArray[destPos*7+4] = sourceArray[sourcePos*7+4]; 571 | destArray[destPos*7+5] = sourceArray[sourcePos*7+5]; 572 | destArray[destPos*7+6] = sourceArray[sourcePos*7+6]; 573 | }; 574 | 575 | bvhtree.BVH.getBox = function(bboxArray, pos, outputBox) { 576 | outputBox.triangleId = bboxArray[pos*7]; 577 | outputBox.minX = bboxArray[pos*7+1]; 578 | outputBox.minY = bboxArray[pos*7+2]; 579 | outputBox.minZ = bboxArray[pos*7+3]; 580 | outputBox.maxX = bboxArray[pos*7+4]; 581 | outputBox.maxY = bboxArray[pos*7+5]; 582 | outputBox.maxZ = bboxArray[pos*7+6]; 583 | }; 584 | 585 | 586 | /** 587 | * A node in the BVH structure 588 | * @class 589 | * @param {Point} extentsMin the min coords of this node's bounding box ({x,y,z}) 590 | * @param {Point} extentsMax the max coords of this node's bounding box ({x,y,z}) 591 | * @param {number} startIndex an index in the bbox array, where the first element of this node is located 592 | * @param {number} endIndex an index in the bbox array, where the last of this node is located, plus 1 (meaning that its non-inclusive). 593 | * @param {number} the distance of this node from the root for the bvh tree. root node has level=0, its children have level=1 etc. 594 | */ 595 | bvhtree.BVHNode = function(extentsMin, extentsMax, startIndex, endIndex, level) { 596 | this._extentsMin = extentsMin; 597 | this._extentsMax = extentsMax; 598 | this._startIndex = startIndex; 599 | this._endIndex = endIndex; 600 | this._level = level; 601 | this._node0 = null; 602 | this._node1 = null; 603 | }; 604 | 605 | bvhtree.BVHNode.prototype.elementCount = function() { 606 | return this._endIndex - this._startIndex; 607 | }; 608 | 609 | bvhtree.BVHNode.prototype.centerX = function() { 610 | return (this._extentsMin.x + this._extentsMax.x) * 0.5; 611 | }; 612 | 613 | bvhtree.BVHNode.prototype.centerY = function() { 614 | return (this._extentsMin.y + this._extentsMax.y) * 0.5; 615 | }; 616 | 617 | bvhtree.BVHNode.prototype.centerZ = function() { 618 | return (this._extentsMin.z + this._extentsMax.z) * 0.5; 619 | }; 620 | 621 | bvhtree.BVHNode.prototype.clearShapes = function() { 622 | this._startIndex = -1; 623 | this._endIndex = -1; 624 | }; 625 | 626 | bvhtree.BVHNode.calcBoundingSphereRadius = function(extentsMin, extentsMax) { 627 | var centerX = (extentsMin.x + extentsMax.x) * 0.5; 628 | var centerY = (extentsMin.y + extentsMax.y) * 0.5; 629 | var centerZ = (extentsMin.z + extentsMax.z) * 0.5; 630 | 631 | var extentsMinDistSqr = 632 | (centerX - extentsMin.x) * (centerX - extentsMin.x) + 633 | (centerY - extentsMin.y) * (centerY - extentsMin.y) + 634 | (centerZ - extentsMin.z) * (centerZ - extentsMin.z); 635 | 636 | var extentsMaxDistSqr = 637 | (centerX - extentsMax.x) * (centerX - extentsMax.x) + 638 | (centerY - extentsMax.y) * (centerY - extentsMax.y) + 639 | (centerZ - extentsMax.z) * (centerZ - extentsMax.z); 640 | 641 | return Math.sqrt(Math.max(extentsMinDistSqr, extentsMaxDistSqr)); 642 | }; 643 | 644 | // commonjs module definiton 645 | if (typeof module !== 'undefined' && module.exports) { 646 | module.exports.BVH = bvhtree.BVH; 647 | module.exports.intersectRay = bvhtree.intersectRay; 648 | } 649 | -------------------------------------------------------------------------------- /main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Precomputed Radiance Transfert (PRT) 6 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 |
32 |
33 | 34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 |
42 |
43 | 44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 | 67 |
68 |
69 | 70 | 71 |
72 |
73 | 74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /pres/pres PRT.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevenv/prt/2510e97aab0eb0ccf0b2ab1b13343de6f18459f9/pres/pres PRT.pdf -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Precomputed Radiance Transfert (PRT) 3 | 4 | Demo for the computer graphics paper discussion group at McGill University. 5 | Based on: 6 | Precomputed Radiance Transfer for Real-Time Rendering in Dynamic, Low-Frequency Lighting Environments 7 | http://www.ppsloan.org/publications/shillum_final23.pdf 8 | 9 | by Keven Villeneuve 10 | */ 11 | 12 | 'use strict' 13 | 14 | // Constants 15 | var WINDOW_WIDTH = 800; 16 | var WINDOW_HEIGHT = 600; 17 | var ALBEDO = new Array(2); 18 | ALBEDO[0] = new THREE.Vector3(1,0,0); 19 | ALBEDO[1] = new THREE.Vector3(1,1,1); 20 | var N_ORDER = 3; 21 | var N_COEFFS = N_ORDER*N_ORDER; 22 | var N_MONTE_CARLO = 100; 23 | var RAY_OFFSET = 1e-18; 24 | var MODEL_FILE_NAME = "teapot.obj"; 25 | var PRECOMPUTE_FILE_NAME = "prt_precomputed.json"; 26 | 27 | // Globals 28 | var scene = null; 29 | var camera = null; 30 | var renderer = null; 31 | var controls = null; 32 | var bvh = null; 33 | var basicShader = null; 34 | var plane = null; 35 | var loadedModel = null; 36 | var objects = []; 37 | 38 | var L = []; 39 | var PRTCache = []; // list of G 40 | var PRTCacheGood = false; 41 | var PRECOMPUTE_FILE_PATH = ""; 42 | 43 | var L_r = 1.5; 44 | var L_d = 1.7; 45 | var L_INTENSITY = 1.0; 46 | var L_DIR = new THREE.Vector3(0,0,1); 47 | var L_ANGLE = 90.0; 48 | 49 | // Events 50 | document.addEventListener("load", onLoad()); 51 | 52 | function onLoad() { 53 | initControls(); 54 | onInit(); 55 | } 56 | 57 | function onRender() { 58 | requestAnimationFrame(onRender); 59 | onUpdate(); 60 | renderer.render(scene, camera); 61 | } 62 | 63 | function onInit() { 64 | renderer = new THREE.WebGLRenderer(); 65 | renderer.setSize(WINDOW_WIDTH, WINDOW_HEIGHT); 66 | var viewport = document.getElementById("viewport"); 67 | viewport.appendChild(renderer.domElement); 68 | 69 | scene = new THREE.Scene(); 70 | camera = new THREE.PerspectiveCamera(50, WINDOW_WIDTH/WINDOW_HEIGHT, 0.1, 1000); 71 | // camera 72 | camera.up.set(0,0,1); 73 | camera.position.x = 7.54; 74 | camera.position.y = 3.77; 75 | camera.position.z = 7.54; 76 | camera.lookAt(new THREE.Vector3(0,0,0)); 77 | 78 | // controls 79 | controls = new THREE.OrbitControls(camera, renderer.domElement); 80 | 81 | // 3D-axis 82 | var K = 10; 83 | var matX = new THREE.LineBasicMaterial({color:0xff0000}); 84 | var matY = new THREE.LineBasicMaterial({color:0x00ff00}); 85 | var matZ = new THREE.LineBasicMaterial({color:0x0000ff}); 86 | var geometryX = new THREE.Geometry(); 87 | geometryX.vertices.push(new THREE.Vector3(0, 0, 0)); 88 | geometryX.vertices.push(new THREE.Vector3(K, 0, 0)); 89 | var lineX = new THREE.Line(geometryX, matX); 90 | scene.add(lineX); 91 | var geometryY = new THREE.Geometry(); 92 | geometryY.vertices.push(new THREE.Vector3(0, 0, 0)); 93 | geometryY.vertices.push(new THREE.Vector3(0, K, 0)); 94 | var lineY = new THREE.Line(geometryY, matY); 95 | scene.add(lineY); 96 | var geometryZ = new THREE.Geometry(); 97 | geometryZ.vertices.push(new THREE.Vector3(0, 0, 0)); 98 | geometryZ.vertices.push(new THREE.Vector3(0, 0, K)); 99 | var lineZ = new THREE.Line(geometryZ, matZ); 100 | scene.add(lineZ); 101 | 102 | // shader 103 | basicShader = createShader(); 104 | 105 | // plane 106 | var geometry = new THREE.PlaneBufferGeometry(10,10,100,100); 107 | plane = new THREE.Mesh(geometry, basicShader); 108 | createColorAttrib(plane, new THREE.Vector3(0.0,1.0,0.0)); 109 | scene.add(plane); 110 | 111 | onRender(); 112 | } 113 | 114 | function loadModel() { 115 | if(loadedModel) { 116 | scene.remove(loadedModel); 117 | objects = [] 118 | loadedModel = null; 119 | PRTCacheGood = false; 120 | } 121 | 122 | var loader = new THREE.OBJLoader(); 123 | loader.load("assets/" + MODEL_FILE_NAME, function(object) { 124 | var model = object.children[0]; 125 | 126 | // shader 127 | model.material = basicShader; 128 | createColorAttrib(model, new THREE.Vector3(1.0,0.0,0.0)); 129 | 130 | // position + rotation 131 | var rotMat = new THREE.Matrix4(); 132 | rotMat.makeRotationX(Math.PI/2); 133 | 134 | var verts = model.geometry.getAttribute("position"); 135 | var N_VERTS = verts.count; 136 | verts = verts.array; 137 | for(var v = 0; v < N_VERTS; v++) { 138 | var vert = new THREE.Vector3(verts[v*3+0], verts[v*3+1], verts[v*3+2]); 139 | vert.applyMatrix4(rotMat); 140 | verts[v*3+0] = vert.x; 141 | verts[v*3+1] = vert.y; 142 | verts[v*3+2] = vert.z + 1.5; 143 | } 144 | 145 | scene.add(object); 146 | objects.push(plane); 147 | objects.push(model); 148 | 149 | console.log('loaded model'); 150 | 151 | // init 152 | buildBVH(objects); 153 | 154 | loadedModel = object; 155 | }); 156 | } 157 | 158 | function createColorAttrib(mesh, color) { 159 | var verts = mesh.geometry.getAttribute("position"); 160 | var N_VERTS = verts.count; 161 | var colors = new Float32Array(N_VERTS * 3); 162 | for(var i = 0; i < N_VERTS; i++) { 163 | colors[i*3+0] = color.x; 164 | colors[i*3+1] = color.y; 165 | colors[i*3+2] = color.z; 166 | } 167 | mesh.geometry.addAttribute("mycolor", new THREE.BufferAttribute(colors, 3)); 168 | } 169 | 170 | function createShader() { 171 | var vShaderStr = 172 | "attribute vec3 mycolor;" + 173 | "varying vec3 vColor;" + 174 | "void main() {" + 175 | " vColor = mycolor;" + 176 | " gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);" + 177 | "}"; 178 | var fShaderStr = 179 | "varying vec3 vColor;" + 180 | "void main() {" + 181 | " gl_FragColor = vec4(vColor,1.0);" + 182 | "}"; 183 | var shader = new THREE.ShaderMaterial( { 184 | vertexShader : vShaderStr, 185 | fragmentShader : fShaderStr 186 | }); 187 | return shader; 188 | } 189 | 190 | function buildBVH(objects) { 191 | console.log("build bvh..."); 192 | 193 | var triangles = []; 194 | 195 | for(var i = 0; i < objects.length; i++) { 196 | var verts = objects[i].geometry.getAttribute("position"); 197 | var N_VERTS = verts.count; 198 | var verts = verts.array; 199 | for(var k = 0; k < N_VERTS*3; k+=3*3) { 200 | var v0 = new THREE.Vector3(verts[k+0], verts[k+1], verts[k+2]); 201 | var v1 = new THREE.Vector3(verts[k+3], verts[k+4], verts[k+5]); 202 | var v2 = new THREE.Vector3(verts[k+6], verts[k+7], verts[k+8]); 203 | var triangle = [ 204 | {x: v0.x, y: v0.y, z: v0.z}, 205 | {x: v1.x, y: v1.y, z: v1.z}, 206 | {x: v2.x, y: v2.y, z: v2.z}, 207 | ]; 208 | triangles.push(triangle); 209 | } 210 | } 211 | 212 | // the maximum number of triangles that can fit in a node before splitting it. 213 | var maxTrianglesPerNode = 7; 214 | bvh = new bvhtree.BVH(triangles, maxTrianglesPerNode); 215 | 216 | console.log("[done]"); 217 | } 218 | 219 | function precomputeL() { 220 | console.log("compute L..."); 221 | computeL_env_proj(L_r, L_d, L_DIR); 222 | console.log("[done]"); 223 | } 224 | 225 | function precomputeG(useCache) { 226 | PRTCacheGood = false; 227 | 228 | if(useCache) { 229 | readJson(PRECOMPUTE_FILE_PATH, function(data) { 230 | PRTCache = data; 231 | PRTCacheGood = true; 232 | }); 233 | } 234 | else { 235 | // do precomputations 236 | var samples = new Array(N_MONTE_CARLO); 237 | createSamples(N_MONTE_CARLO, samples); 238 | 239 | console.log("compute G..."); 240 | 241 | PRTCache = []; 242 | 243 | for(var j = 0; j < objects.length; j++) { 244 | var obj = objects[j]; 245 | var verts = obj.geometry.getAttribute("position"); 246 | var normals = obj.geometry.getAttribute("normal"); 247 | var N_VERTS = verts.count; 248 | var G = new Array(N_VERTS); 249 | for(var i = 0; i < G.length; i++) { 250 | G[i] = new Array(N_COEFFS); 251 | for(var k = 0; k < N_COEFFS; k++) { 252 | G[i][k] = 0.0; 253 | } 254 | } 255 | 256 | for(var v = 0; v < N_VERTS; v++) { 257 | computeG(G, v, verts.array, normals.array, samples); 258 | } 259 | 260 | PRTCache.push(G); 261 | } 262 | 263 | PRTCacheGood = true; 264 | console.log("[done]"); 265 | } 266 | } 267 | 268 | function computeG(G, v, verts, normals, samples) { 269 | var p = new THREE.Vector3(verts[v*3+0],verts[v*3+1],verts[v*3+2]); 270 | var n = new THREE.Vector3(normals[v*3+0],normals[v*3+1],normals[v*3+2]); 271 | 272 | // offset ray 273 | var n_ = n.clone(); 274 | n_.multiplyScalar(RAY_OFFSET); 275 | p.add(n_); 276 | 277 | for(var i = 0; i < N_MONTE_CARLO; i++) { 278 | //console.log("v= " + v + " MC = " + (i+1)); 279 | 280 | var w = samples[i].clone(); 281 | //w.add(p);//to world space 282 | w.normalize(); 283 | var cosTheta = Math.max(0.0, w.dot(n)); 284 | if(cosTheta == 0.0) continue; 285 | var pWi = 1.0 / (4.0 * Math.PI); 286 | var V = bvh.intersectRay(p, w, true).length == 0; 287 | if(V) { 288 | var yi = SHEval(w.x, w.y, w.z, N_ORDER); 289 | for(var k = 0; k < N_COEFFS; k++) { 290 | G[v][k] += cosTheta * yi[k]; 291 | } 292 | } 293 | } 294 | 295 | for(var k = 0; k < N_COEFFS; k++) { 296 | G[v][k] *= 1.0 / (Math.PI * N_MONTE_CARLO * pWi); 297 | } 298 | } 299 | 300 | function createSamples(N, samples) { 301 | for(var i = 0; i < N; i++) { 302 | var sample = new THREE.Vector2(Math.random(), Math.random()); 303 | samples[i] = squareToUniformSphere(sample); 304 | //console.log(sample.x + "," + sample.y + ": " + samples[i].x + "," + samples[i].y + "," + samples[i].z); 305 | } 306 | } 307 | 308 | function squareToUniformSphere(sample) { 309 | var z = 1.0 - 2.0 * sample.x; 310 | var r = Math.sqrt(Math.max(0.0, 1.0 - z*z)); 311 | var phi = 2.0 * Math.PI * sample.y; 312 | return new THREE.Vector3(r * Math.cos(phi), r * Math.sin(phi), z); 313 | } 314 | 315 | function computeL_env_proj(r, d, dir) { 316 | var L0 = computeL0_env_proj[N_ORDER-3](r,d); 317 | var y = SHEval(dir.x, dir.y, dir.z, N_ORDER); 318 | var Lr = new Array(N_COEFFS); 319 | for(var l = 0; l <= N_ORDER-1; l++) { 320 | for(var m = -l; m <= l; m++) { 321 | var i = l*(l+1) + m; 322 | Lr[i] = Math.sqrt(4*Math.PI / (2*l + 1)) * L0[l*(l+1)] * y[i]; 323 | } 324 | } 325 | L = Lr; 326 | } 327 | 328 | var computeL0_env_proj = [ 329 | computeL0_env_proj_Order3, 330 | computeL0_env_proj_Order4, 331 | computeL0_env_proj_Order5 332 | ]; 333 | 334 | function computeL0_env_proj_Order3(r, d) { 335 | // up = z 336 | // based on sh.nb 337 | var L0 = new Array(N_COEFFS); 338 | 339 | L0[0] = Math.sqrt(Math.PI) * (1 - Math.sqrt(1 - (r*r)/(d*d))); 340 | L0[1] = 0.0; 341 | L0[2] = (Math.sqrt(3*Math.PI) * r*r) / (2*d*d); 342 | L0[3] = 0.0; 343 | L0[4] = 0.0; 344 | L0[5] = 0.0; 345 | L0[6] = (Math.sqrt(5*Math.PI) * r*r * Math.sqrt(1 - (r*r)/(d*d))) / (2*d*d); 346 | L0[7] = 0.0; 347 | L0[8] = 0.0; 348 | 349 | return L0; 350 | } 351 | 352 | function computeL0_env_proj_Order4(r, d) { 353 | // up = z 354 | // based on sh.nb 355 | var L0 = new Array(N_COEFFS); 356 | 357 | L0[0] = Math.sqrt(Math.PI) * (1 - Math.sqrt(1 - (r*r)/(d*d))); 358 | L0[1] = 0.0; 359 | L0[2] = (Math.sqrt(3*Math.PI) * r*r) / (2*d*d); 360 | L0[3] = 0.0; 361 | L0[4] = 0.0; 362 | L0[5] = 0.0; 363 | L0[6] = (Math.sqrt(5*Math.PI) * r*r * Math.sqrt(1 - (r*r)/(d*d))) / (2*d*d); 364 | L0[7] = 0.0; 365 | L0[8] = 0.0; 366 | L0[9] = 0.0; 367 | L0[10] = 0.0; 368 | L0[11] = 0.0; 369 | L0[12] = (Math.sqrt(7*Math.PI) * r*r * (4*d*d - 5*r*r)) / (8*d*d*d*d); 370 | L0[13] = 0.0; 371 | L0[14] = 0.0; 372 | L0[15] = 0.0; 373 | 374 | return L0; 375 | } 376 | 377 | function computeL0_env_proj_Order5(r, d) { 378 | // up = z 379 | // based on sh.nb 380 | var L0 = new Array(N_COEFFS); 381 | 382 | L0[0] = Math.sqrt(Math.PI) * (1 - Math.sqrt(1 - (r*r)/(d*d))); 383 | L0[1] = 0.0; 384 | L0[2] = (Math.sqrt(3*Math.PI) * r*r) / (2*d*d); 385 | L0[3] = 0.0; 386 | L0[4] = 0.0; 387 | L0[5] = 0.0; 388 | L0[6] = (Math.sqrt(5*Math.PI) * r*r * Math.sqrt(1 - (r*r)/(d*d))) / (2*d*d); 389 | L0[7] = 0.0; 390 | L0[8] = 0.0; 391 | L0[9] = 0.0; 392 | L0[10] = 0.0; 393 | L0[11] = 0.0; 394 | L0[12] = (Math.sqrt(7*Math.PI) * r*r * (4*d*d - 5*r*r)) / (8*d*d*d*d); 395 | L0[13] = 0.0; 396 | L0[14] = 0.0; 397 | L0[15] = 0.0; 398 | L0[16] = 0.0; 399 | L0[17] = 0.0; 400 | L0[18] = 0.0; 401 | L0[19] = 0.0; 402 | L0[20] = (3*r*r * (4*d*d - 7*r*r) * Math.sqrt(Math.PI - ((Math.PI*r*r)/(d*d)))) / (8*d*d*d*d); 403 | L0[21] = 0.0; 404 | L0[22] = 0.0; 405 | L0[23] = 0.0; 406 | L0[24] = 0.0; 407 | 408 | return L0; 409 | } 410 | 411 | function onUpdate() { 412 | controls.update(); 413 | 414 | if(!PRTCacheGood) return; 415 | 416 | for(var j = 0; j < objects.length; j++) { 417 | var obj = objects[j]; 418 | var G = PRTCache[j]; 419 | var verts = obj.geometry.getAttribute("mycolor"); 420 | for(var v = 0; v < verts.count; v++) { 421 | verts.array[v*3+0] = 0.0; 422 | verts.array[v*3+1] = 0.0; 423 | verts.array[v*3+2] = 0.0; 424 | for(var i = 0; i < N_COEFFS; i++) { 425 | var k = L_INTENSITY * L[i] * G[v][i]; 426 | k = Math.max(0.0,k); 427 | k = Math.min(1.0,k); 428 | verts.array[v*3+0] += k * ALBEDO[j].x; 429 | verts.array[v*3+1] += k * ALBEDO[j].y; 430 | verts.array[v*3+2] += k * ALBEDO[j].z; 431 | } 432 | } 433 | 434 | verts.needsUpdate = true; 435 | } 436 | } 437 | 438 | // JSON file read/write 439 | function writeJson(object, name, type) { 440 | var text = JSON.stringify(object); 441 | var a = document.createElement("a"); 442 | var file = new Blob([text], {type: type}); 443 | a.href = URL.createObjectURL(file); 444 | a.download = name; 445 | a.click(); 446 | } 447 | 448 | function readJson(file, callback) { 449 | var rawFile = new XMLHttpRequest(); 450 | rawFile.overrideMimeType("application/json"); 451 | rawFile.open("GET", file, true); 452 | rawFile.onreadystatechange = function() { 453 | if (rawFile.readyState === 4) { 454 | var data = JSON.parse(rawFile.responseText); 455 | callback(data); 456 | } 457 | } 458 | rawFile.send(null); 459 | } 460 | 461 | function initControls() { 462 | var text_L_intensity = document.getElementById("text_L_intensity"); 463 | text_L_intensity.value = L_INTENSITY; 464 | 465 | var text_L_direction = document.getElementById("text_L_direction"); 466 | text_L_direction.value = L_ANGLE; 467 | L_DIR = computeLightDir(L_ANGLE); 468 | 469 | var text_L_r = document.getElementById("text_L_r"); 470 | text_L_r.value = L_r; 471 | 472 | var text_L_d = document.getElementById("text_L_d"); 473 | text_L_d.value = L_d; 474 | 475 | var text_montecarlo = document.getElementById("text_montecarlo"); 476 | text_montecarlo.value = N_MONTE_CARLO; 477 | 478 | var text_order = document.getElementById("text_order"); 479 | text_order.value = N_ORDER; 480 | 481 | var text_savePRT = document.getElementById("text_savePRT"); 482 | text_savePRT.value = PRECOMPUTE_FILE_NAME; 483 | 484 | var sliderIntensity = document.getElementById("slider_L_intensity"); 485 | sliderIntensity.defaultValue = L_INTENSITY; 486 | sliderIntensity.min = 0.0; 487 | sliderIntensity.max = 3.0; 488 | sliderIntensity.step = 0.1; 489 | sliderIntensity.addEventListener("input", function() { 490 | L_INTENSITY = parseFloat(sliderIntensity.value); 491 | var text_L_intensity = document.getElementById("text_L_intensity"); 492 | text_L_intensity.value = L_INTENSITY; 493 | }); 494 | 495 | var sliderDirection = document.getElementById("slider_L_direction"); 496 | sliderDirection.defaultValue = L_ANGLE; 497 | sliderDirection.min = 0.0; 498 | sliderDirection.max = 180.0; 499 | sliderDirection.step = 5.0; 500 | sliderDirection.addEventListener("input", function() { 501 | L_ANGLE = parseFloat(sliderDirection.value); 502 | var text_L_direction = document.getElementById("text_L_direction"); 503 | text_L_direction.value = L_ANGLE; 504 | L_DIR = computeLightDir(L_ANGLE); 505 | precomputeL(); 506 | }); 507 | 508 | var sliderL_r = document.getElementById("slider_L_r"); 509 | sliderL_r.defaultValue = L_r; 510 | sliderL_r.min = 0.0; 511 | sliderL_r.max = 3.0; 512 | sliderL_r.step = 0.1; 513 | sliderL_r.addEventListener("input", function() { 514 | L_r = parseFloat(sliderL_r.value); 515 | var text_L_r = document.getElementById("text_L_r"); 516 | text_L_r.value = L_r; 517 | precomputeL(); 518 | }); 519 | 520 | var sliderL_d = document.getElementById("slider_L_d"); 521 | sliderL_d.defaultValue = L_d; 522 | sliderL_d.min = 0.0; 523 | sliderL_d.max = 3.0; 524 | sliderL_d.step = 0.1; 525 | sliderL_d.addEventListener("input", function() { 526 | L_d = parseFloat(sliderL_d.value); 527 | var text_L_d = document.getElementById("text_L_d"); 528 | text_L_d.value = L_d; 529 | precomputeL(); 530 | }); 531 | 532 | var button_loadModel = document.getElementById("button_loadModel"); 533 | button_loadModel.addEventListener("click", function() { 534 | var file_loadModel = document.getElementById("file_loadModel"); 535 | MODEL_FILE_NAME = file_loadModel.value.substring(12,file_loadModel.value.length); 536 | loadModel(); 537 | }); 538 | 539 | var slider_montecarlo = document.getElementById("slider_montecarlo"); 540 | slider_montecarlo.defaultValue = N_MONTE_CARLO; 541 | slider_montecarlo.min = 0; 542 | slider_montecarlo.max = 1000; 543 | slider_montecarlo.step = 100; 544 | slider_montecarlo.addEventListener("input", function() { 545 | N_MONTE_CARLO = parseFloat(slider_montecarlo.value); 546 | var text_montecarlo = document.getElementById("text_montecarlo"); 547 | text_montecarlo.value = N_MONTE_CARLO; 548 | }); 549 | 550 | text_montecarlo.addEventListener("change", function() { 551 | N_MONTE_CARLO = parseFloat(text_montecarlo.value); 552 | slider_montecarlo.value = N_MONTE_CARLO; 553 | }); 554 | 555 | var slider_order = document.getElementById("slider_order"); 556 | slider_order.defaultValue = N_ORDER; 557 | slider_order.min = 3; 558 | slider_order.max = 5; 559 | slider_order.step = 1; 560 | slider_order.addEventListener("input", function() { 561 | N_ORDER = parseFloat(slider_order.value); 562 | N_COEFFS = N_ORDER*N_ORDER; 563 | var text_order = document.getElementById("text_order"); 564 | text_order.value = N_ORDER; 565 | }); 566 | 567 | text_order.addEventListener("change", function() { 568 | N_ORDER = parseFloat(text_order.value); 569 | N_COEFFS = N_ORDER*N_ORDER; 570 | slider_order.value = N_ORDER; 571 | }); 572 | 573 | var button_computePRT = document.getElementById("button_computePRT"); 574 | button_computePRT.addEventListener("click", function() { 575 | precomputeL(); 576 | precomputeG(false); 577 | }); 578 | 579 | var button_savePRT = document.getElementById("button_savePRT"); 580 | button_savePRT.addEventListener("click", function() { 581 | var text_savePRT = document.getElementById("text_savePRT"); 582 | PRECOMPUTE_FILE_NAME = text_savePRT.value; 583 | if(PRTCacheGood) { 584 | writeJson(PRTCache, PRECOMPUTE_FILE_NAME, 'text/plain'); 585 | } 586 | }); 587 | 588 | var button_loadPRT = document.getElementById("button_loadPRT"); 589 | button_loadPRT.addEventListener("click", function() { 590 | var file_loadPRT = document.getElementById("file_loadPRT"); 591 | PRECOMPUTE_FILE_NAME = file_loadPRT.value.substring(12,file_loadPRT.value.length); 592 | 593 | var text_savePRT = document.getElementById("text_savePRT"); 594 | text_savePRT.value = PRECOMPUTE_FILE_NAME; 595 | 596 | var loc = window.location.pathname; 597 | var dir = loc.substring(0, loc.lastIndexOf('/')); 598 | PRECOMPUTE_FILE_PATH = dir + "/" + PRECOMPUTE_FILE_NAME; 599 | precomputeL(); 600 | precomputeG(true); 601 | }); 602 | } 603 | 604 | function computeLightDir(angleDeg) { 605 | var v = new THREE.Vector3(0,1,0); // at 0 deg 606 | var rotMat = new THREE.Matrix4(); 607 | rotMat.makeRotationX(toRadians(angleDeg)); 608 | v.applyMatrix4(rotMat); 609 | return v; 610 | } 611 | 612 | function toRadians(deg) { 613 | return deg * Math.PI / 180; 614 | }; -------------------------------------------------------------------------------- /src/sh.js: -------------------------------------------------------------------------------- 1 | 2 | // order n 3 | function SHEval(fX, fY, fZ, order) 4 | { 5 | return SHEvalFct[order - 3](fX, fY, fZ); 6 | } 7 | 8 | SHEvalFct = [ 9 | SHEval3, 10 | SHEval4, 11 | SHEval5, 12 | SHEval6, 13 | SHEval7, 14 | SHEval8, 15 | SHEval9, 16 | SHEval10 17 | ]; 18 | 19 | // order 3 20 | function SHEval3(fX, fY, fZ) { 21 | var fC0,fC1,fS0,fS1,fTmpA,fTmpB,fTmpC; 22 | var fZ2 = fZ*fZ; 23 | var pSH = new Array(9); 24 | 25 | pSH[0] = 0.2820947917738781; 26 | pSH[2] = 0.4886025119029199*fZ; 27 | pSH[6] = 0.9461746957575601*fZ2 + -0.3153915652525201; 28 | fC0 = fX; 29 | fS0 = fY; 30 | 31 | fTmpA = -0.48860251190292; 32 | pSH[3] = fTmpA*fC0; 33 | pSH[1] = fTmpA*fS0; 34 | fTmpB = -1.092548430592079*fZ; 35 | pSH[7] = fTmpB*fC0; 36 | pSH[5] = fTmpB*fS0; 37 | fC1 = fX*fC0 - fY*fS0; 38 | fS1 = fX*fS0 + fY*fC0; 39 | 40 | fTmpC = 0.5462742152960395; 41 | pSH[8] = fTmpC*fC1; 42 | pSH[4] = fTmpC*fS1; 43 | 44 | return pSH; 45 | } 46 | 47 | // order 4 48 | function SHEval4(fX, fY, fZ) 49 | { 50 | var fC0,fC1,fS0,fS1,fTmpA,fTmpB,fTmpC; 51 | var fZ2 = fZ*fZ; 52 | var pSH = new Array(16); 53 | 54 | pSH[0] = 0.2820947917738781; 55 | pSH[2] = 0.4886025119029199*fZ; 56 | pSH[6] = 0.9461746957575601*fZ2 + -0.3153915652525201; 57 | pSH[12] = fZ*(1.865881662950577*fZ2 + -1.119528997770346); 58 | fC0 = fX; 59 | fS0 = fY; 60 | 61 | fTmpA = -0.48860251190292; 62 | pSH[3] = fTmpA*fC0; 63 | pSH[1] = fTmpA*fS0; 64 | fTmpB = -1.092548430592079*fZ; 65 | pSH[7] = fTmpB*fC0; 66 | pSH[5] = fTmpB*fS0; 67 | fTmpC = -2.285228997322329*fZ2 + 0.4570457994644658; 68 | pSH[13] = fTmpC*fC0; 69 | pSH[11] = fTmpC*fS0; 70 | fC1 = fX*fC0 - fY*fS0; 71 | fS1 = fX*fS0 + fY*fC0; 72 | 73 | fTmpA = 0.5462742152960395; 74 | pSH[8] = fTmpA*fC1; 75 | pSH[4] = fTmpA*fS1; 76 | fTmpB = 1.445305721320277*fZ; 77 | pSH[14] = fTmpB*fC1; 78 | pSH[10] = fTmpB*fS1; 79 | fC0 = fX*fC1 - fY*fS1; 80 | fS0 = fX*fS1 + fY*fC1; 81 | 82 | fTmpC = -0.5900435899266435; 83 | pSH[15] = fTmpC*fC0; 84 | pSH[9] = fTmpC*fS0; 85 | 86 | return pSH; 87 | } 88 | 89 | // order 5 90 | function SHEval5(fX, fY, fZ) 91 | { 92 | var fC0,fC1,fS0,fS1,fTmpA,fTmpB,fTmpC; 93 | var fZ2 = fZ*fZ; 94 | var pSH = new Array(25); 95 | 96 | pSH[0] = 0.2820947917738781; 97 | pSH[2] = 0.4886025119029199*fZ; 98 | pSH[6] = 0.9461746957575601*fZ2 + -0.3153915652525201; 99 | pSH[12] = fZ*(1.865881662950577*fZ2 + -1.119528997770346); 100 | pSH[20] = 1.984313483298443*fZ*pSH[12] + -1.006230589874905*pSH[6]; 101 | fC0 = fX; 102 | fS0 = fY; 103 | 104 | fTmpA = -0.48860251190292; 105 | pSH[3] = fTmpA*fC0; 106 | pSH[1] = fTmpA*fS0; 107 | fTmpB = -1.092548430592079*fZ; 108 | pSH[7] = fTmpB*fC0; 109 | pSH[5] = fTmpB*fS0; 110 | fTmpC = -2.285228997322329*fZ2 + 0.4570457994644658; 111 | pSH[13] = fTmpC*fC0; 112 | pSH[11] = fTmpC*fS0; 113 | fTmpA = fZ*(-4.683325804901025*fZ2 + 2.007139630671868); 114 | pSH[21] = fTmpA*fC0; 115 | pSH[19] = fTmpA*fS0; 116 | fC1 = fX*fC0 - fY*fS0; 117 | fS1 = fX*fS0 + fY*fC0; 118 | 119 | fTmpA = 0.5462742152960395; 120 | pSH[8] = fTmpA*fC1; 121 | pSH[4] = fTmpA*fS1; 122 | fTmpB = 1.445305721320277*fZ; 123 | pSH[14] = fTmpB*fC1; 124 | pSH[10] = fTmpB*fS1; 125 | fTmpC = 3.31161143515146*fZ2 + -0.47308734787878; 126 | pSH[22] = fTmpC*fC1; 127 | pSH[18] = fTmpC*fS1; 128 | fC0 = fX*fC1 - fY*fS1; 129 | fS0 = fX*fS1 + fY*fC1; 130 | 131 | fTmpA = -0.5900435899266435; 132 | pSH[15] = fTmpA*fC0; 133 | pSH[9] = fTmpA*fS0; 134 | fTmpB = -1.770130769779931*fZ; 135 | pSH[23] = fTmpB*fC0; 136 | pSH[17] = fTmpB*fS0; 137 | fC1 = fX*fC0 - fY*fS0; 138 | fS1 = fX*fS0 + fY*fC0; 139 | 140 | fTmpC = 0.6258357354491763; 141 | pSH[24] = fTmpC*fC1; 142 | pSH[16] = fTmpC*fS1; 143 | 144 | return pSH; 145 | } 146 | 147 | // order 6 148 | function SHEval6(fX, fY, fZ) 149 | { 150 | var fC0,fC1,fS0,fS1,fTmpA,fTmpB,fTmpC; 151 | var fZ2 = fZ*fZ; 152 | var pSH = new Array(36); 153 | 154 | pSH[0] = 0.2820947917738781; 155 | pSH[2] = 0.4886025119029199*fZ; 156 | pSH[6] = 0.9461746957575601*fZ2 + -0.3153915652525201; 157 | pSH[12] = fZ*(1.865881662950577*fZ2 + -1.119528997770346); 158 | pSH[20] = 1.984313483298443*fZ*pSH[12] + -1.006230589874905*pSH[6]; 159 | pSH[30] = 1.98997487421324*fZ*pSH[20] + -1.002853072844814*pSH[12]; 160 | fC0 = fX; 161 | fS0 = fY; 162 | 163 | fTmpA = -0.48860251190292; 164 | pSH[3] = fTmpA*fC0; 165 | pSH[1] = fTmpA*fS0; 166 | fTmpB = -1.092548430592079*fZ; 167 | pSH[7] = fTmpB*fC0; 168 | pSH[5] = fTmpB*fS0; 169 | fTmpC = -2.285228997322329*fZ2 + 0.4570457994644658; 170 | pSH[13] = fTmpC*fC0; 171 | pSH[11] = fTmpC*fS0; 172 | fTmpA = fZ*(-4.683325804901025*fZ2 + 2.007139630671868); 173 | pSH[21] = fTmpA*fC0; 174 | pSH[19] = fTmpA*fS0; 175 | fTmpB = 2.03100960115899*fZ*fTmpA + -0.991031208965115*fTmpC; 176 | pSH[31] = fTmpB*fC0; 177 | pSH[29] = fTmpB*fS0; 178 | fC1 = fX*fC0 - fY*fS0; 179 | fS1 = fX*fS0 + fY*fC0; 180 | 181 | fTmpA = 0.5462742152960395; 182 | pSH[8] = fTmpA*fC1; 183 | pSH[4] = fTmpA*fS1; 184 | fTmpB = 1.445305721320277*fZ; 185 | pSH[14] = fTmpB*fC1; 186 | pSH[10] = fTmpB*fS1; 187 | fTmpC = 3.31161143515146*fZ2 + -0.47308734787878; 188 | pSH[22] = fTmpC*fC1; 189 | pSH[18] = fTmpC*fS1; 190 | fTmpA = fZ*(7.190305177459987*fZ2 + -2.396768392486662); 191 | pSH[32] = fTmpA*fC1; 192 | pSH[28] = fTmpA*fS1; 193 | fC0 = fX*fC1 - fY*fS1; 194 | fS0 = fX*fS1 + fY*fC1; 195 | 196 | fTmpA = -0.5900435899266435; 197 | pSH[15] = fTmpA*fC0; 198 | pSH[9] = fTmpA*fS0; 199 | fTmpB = -1.770130769779931*fZ; 200 | pSH[23] = fTmpB*fC0; 201 | pSH[17] = fTmpB*fS0; 202 | fTmpC = -4.403144694917254*fZ2 + 0.4892382994352505; 203 | pSH[33] = fTmpC*fC0; 204 | pSH[27] = fTmpC*fS0; 205 | fC1 = fX*fC0 - fY*fS0; 206 | fS1 = fX*fS0 + fY*fC0; 207 | 208 | fTmpA = 0.6258357354491763; 209 | pSH[24] = fTmpA*fC1; 210 | pSH[16] = fTmpA*fS1; 211 | fTmpB = 2.075662314881041*fZ; 212 | pSH[34] = fTmpB*fC1; 213 | pSH[26] = fTmpB*fS1; 214 | fC0 = fX*fC1 - fY*fS1; 215 | fS0 = fX*fS1 + fY*fC1; 216 | 217 | fTmpC = -0.6563820568401703; 218 | pSH[35] = fTmpC*fC0; 219 | pSH[25] = fTmpC*fS0; 220 | 221 | return pSH; 222 | } 223 | 224 | // order 7 225 | function SHEval7(fX, fY, fZ) 226 | { 227 | var fC0,fC1,fS0,fS1,fTmpA,fTmpB,fTmpC; 228 | var fZ2 = fZ*fZ; 229 | var pSH = new Array(49); 230 | 231 | pSH[0] = 0.2820947917738781; 232 | pSH[2] = 0.4886025119029199*fZ; 233 | pSH[6] = 0.9461746957575601*fZ2 + -0.3153915652525201; 234 | pSH[12] = fZ*(1.865881662950577*fZ2 + -1.119528997770346); 235 | pSH[20] = 1.984313483298443*fZ*pSH[12] + -1.006230589874905*pSH[6]; 236 | pSH[30] = 1.98997487421324*fZ*pSH[20] + -1.002853072844814*pSH[12]; 237 | pSH[42] = 1.993043457183567*fZ*pSH[30] + -1.001542020962219*pSH[20]; 238 | fC0 = fX; 239 | fS0 = fY; 240 | 241 | fTmpA = -0.48860251190292; 242 | pSH[3] = fTmpA*fC0; 243 | pSH[1] = fTmpA*fS0; 244 | fTmpB = -1.092548430592079*fZ; 245 | pSH[7] = fTmpB*fC0; 246 | pSH[5] = fTmpB*fS0; 247 | fTmpC = -2.285228997322329*fZ2 + 0.4570457994644658; 248 | pSH[13] = fTmpC*fC0; 249 | pSH[11] = fTmpC*fS0; 250 | fTmpA = fZ*(-4.683325804901025*fZ2 + 2.007139630671868); 251 | pSH[21] = fTmpA*fC0; 252 | pSH[19] = fTmpA*fS0; 253 | fTmpB = 2.03100960115899*fZ*fTmpA + -0.991031208965115*fTmpC; 254 | pSH[31] = fTmpB*fC0; 255 | pSH[29] = fTmpB*fS0; 256 | fTmpC = 2.021314989237028*fZ*fTmpB + -0.9952267030562385*fTmpA; 257 | pSH[43] = fTmpC*fC0; 258 | pSH[41] = fTmpC*fS0; 259 | fC1 = fX*fC0 - fY*fS0; 260 | fS1 = fX*fS0 + fY*fC0; 261 | 262 | fTmpA = 0.5462742152960395; 263 | pSH[8] = fTmpA*fC1; 264 | pSH[4] = fTmpA*fS1; 265 | fTmpB = 1.445305721320277*fZ; 266 | pSH[14] = fTmpB*fC1; 267 | pSH[10] = fTmpB*fS1; 268 | fTmpC = 3.31161143515146*fZ2 + -0.47308734787878; 269 | pSH[22] = fTmpC*fC1; 270 | pSH[18] = fTmpC*fS1; 271 | fTmpA = fZ*(7.190305177459987*fZ2 + -2.396768392486662); 272 | pSH[32] = fTmpA*fC1; 273 | pSH[28] = fTmpA*fS1; 274 | fTmpB = 2.11394181566097*fZ*fTmpA + -0.9736101204623268*fTmpC; 275 | pSH[44] = fTmpB*fC1; 276 | pSH[40] = fTmpB*fS1; 277 | fC0 = fX*fC1 - fY*fS1; 278 | fS0 = fX*fS1 + fY*fC1; 279 | 280 | fTmpA = -0.5900435899266435; 281 | pSH[15] = fTmpA*fC0; 282 | pSH[9] = fTmpA*fS0; 283 | fTmpB = -1.770130769779931*fZ; 284 | pSH[23] = fTmpB*fC0; 285 | pSH[17] = fTmpB*fS0; 286 | fTmpC = -4.403144694917254*fZ2 + 0.4892382994352505; 287 | pSH[33] = fTmpC*fC0; 288 | pSH[27] = fTmpC*fS0; 289 | fTmpA = fZ*(-10.13325785466416*fZ2 + 2.763615778544771); 290 | pSH[45] = fTmpA*fC0; 291 | pSH[39] = fTmpA*fS0; 292 | fC1 = fX*fC0 - fY*fS0; 293 | fS1 = fX*fS0 + fY*fC0; 294 | 295 | fTmpA = 0.6258357354491763; 296 | pSH[24] = fTmpA*fC1; 297 | pSH[16] = fTmpA*fS1; 298 | fTmpB = 2.075662314881041*fZ; 299 | pSH[34] = fTmpB*fC1; 300 | pSH[26] = fTmpB*fS1; 301 | fTmpC = 5.550213908015966*fZ2 + -0.5045649007287241; 302 | pSH[46] = fTmpC*fC1; 303 | pSH[38] = fTmpC*fS1; 304 | fC0 = fX*fC1 - fY*fS1; 305 | fS0 = fX*fS1 + fY*fC1; 306 | 307 | fTmpA = -0.6563820568401703; 308 | pSH[35] = fTmpA*fC0; 309 | pSH[25] = fTmpA*fS0; 310 | fTmpB = -2.366619162231753*fZ; 311 | pSH[47] = fTmpB*fC0; 312 | pSH[37] = fTmpB*fS0; 313 | fC1 = fX*fC0 - fY*fS0; 314 | fS1 = fX*fS0 + fY*fC0; 315 | 316 | fTmpC = 0.6831841051919144; 317 | pSH[48] = fTmpC*fC1; 318 | pSH[36] = fTmpC*fS1; 319 | 320 | return pSH; 321 | } 322 | 323 | // order 8 324 | function SHEval8(fX, fY, fZ) 325 | { 326 | var fC0,fC1,fS0,fS1,fTmpA,fTmpB,fTmpC; 327 | var fZ2 = fZ*fZ; 328 | var pSH = new Array(64); 329 | 330 | pSH[0] = 0.2820947917738781; 331 | pSH[2] = 0.4886025119029199*fZ; 332 | pSH[6] = 0.9461746957575601*fZ2 + -0.3153915652525201; 333 | pSH[12] = fZ*(1.865881662950577*fZ2 + -1.119528997770346); 334 | pSH[20] = 1.984313483298443*fZ*pSH[12] + -1.006230589874905*pSH[6]; 335 | pSH[30] = 1.98997487421324*fZ*pSH[20] + -1.002853072844814*pSH[12]; 336 | pSH[42] = 1.993043457183567*fZ*pSH[30] + -1.001542020962219*pSH[20]; 337 | pSH[56] = 1.994891434824135*fZ*pSH[42] + -1.000927213921958*pSH[30]; 338 | fC0 = fX; 339 | fS0 = fY; 340 | 341 | fTmpA = -0.48860251190292; 342 | pSH[3] = fTmpA*fC0; 343 | pSH[1] = fTmpA*fS0; 344 | fTmpB = -1.092548430592079*fZ; 345 | pSH[7] = fTmpB*fC0; 346 | pSH[5] = fTmpB*fS0; 347 | fTmpC = -2.285228997322329*fZ2 + 0.4570457994644658; 348 | pSH[13] = fTmpC*fC0; 349 | pSH[11] = fTmpC*fS0; 350 | fTmpA = fZ*(-4.683325804901025*fZ2 + 2.007139630671868); 351 | pSH[21] = fTmpA*fC0; 352 | pSH[19] = fTmpA*fS0; 353 | fTmpB = 2.03100960115899*fZ*fTmpA + -0.991031208965115*fTmpC; 354 | pSH[31] = fTmpB*fC0; 355 | pSH[29] = fTmpB*fS0; 356 | fTmpC = 2.021314989237028*fZ*fTmpB + -0.9952267030562385*fTmpA; 357 | pSH[43] = fTmpC*fC0; 358 | pSH[41] = fTmpC*fS0; 359 | fTmpA = 2.015564437074638*fZ*fTmpC + -0.9971550440218319*fTmpB; 360 | pSH[57] = fTmpA*fC0; 361 | pSH[55] = fTmpA*fS0; 362 | fC1 = fX*fC0 - fY*fS0; 363 | fS1 = fX*fS0 + fY*fC0; 364 | 365 | fTmpA = 0.5462742152960395; 366 | pSH[8] = fTmpA*fC1; 367 | pSH[4] = fTmpA*fS1; 368 | fTmpB = 1.445305721320277*fZ; 369 | pSH[14] = fTmpB*fC1; 370 | pSH[10] = fTmpB*fS1; 371 | fTmpC = 3.31161143515146*fZ2 + -0.47308734787878; 372 | pSH[22] = fTmpC*fC1; 373 | pSH[18] = fTmpC*fS1; 374 | fTmpA = fZ*(7.190305177459987*fZ2 + -2.396768392486662); 375 | pSH[32] = fTmpA*fC1; 376 | pSH[28] = fTmpA*fS1; 377 | fTmpB = 2.11394181566097*fZ*fTmpA + -0.9736101204623268*fTmpC; 378 | pSH[44] = fTmpB*fC1; 379 | pSH[40] = fTmpB*fS1; 380 | fTmpC = 2.081665999466133*fZ*fTmpB + -0.9847319278346618*fTmpA; 381 | pSH[58] = fTmpC*fC1; 382 | pSH[54] = fTmpC*fS1; 383 | fC0 = fX*fC1 - fY*fS1; 384 | fS0 = fX*fS1 + fY*fC1; 385 | 386 | fTmpA = -0.5900435899266435; 387 | pSH[15] = fTmpA*fC0; 388 | pSH[9] = fTmpA*fS0; 389 | fTmpB = -1.770130769779931*fZ; 390 | pSH[23] = fTmpB*fC0; 391 | pSH[17] = fTmpB*fS0; 392 | fTmpC = -4.403144694917254*fZ2 + 0.4892382994352505; 393 | pSH[33] = fTmpC*fC0; 394 | pSH[27] = fTmpC*fS0; 395 | fTmpA = fZ*(-10.13325785466416*fZ2 + 2.763615778544771); 396 | pSH[45] = fTmpA*fC0; 397 | pSH[39] = fTmpA*fS0; 398 | fTmpB = 2.207940216581962*fZ*fTmpA + -0.959403223600247*fTmpC; 399 | pSH[59] = fTmpB*fC0; 400 | pSH[53] = fTmpB*fS0; 401 | fC1 = fX*fC0 - fY*fS0; 402 | fS1 = fX*fS0 + fY*fC0; 403 | 404 | fTmpA = 0.6258357354491763; 405 | pSH[24] = fTmpA*fC1; 406 | pSH[16] = fTmpA*fS1; 407 | fTmpB = 2.075662314881041*fZ; 408 | pSH[34] = fTmpB*fC1; 409 | pSH[26] = fTmpB*fS1; 410 | fTmpC = 5.550213908015966*fZ2 + -0.5045649007287241; 411 | pSH[46] = fTmpC*fC1; 412 | pSH[38] = fTmpC*fS1; 413 | fTmpA = fZ*(13.49180504672677*fZ2 + -3.113493472321562); 414 | pSH[60] = fTmpA*fC1; 415 | pSH[52] = fTmpA*fS1; 416 | fC0 = fX*fC1 - fY*fS1; 417 | fS0 = fX*fS1 + fY*fC1; 418 | 419 | fTmpA = -0.6563820568401703; 420 | pSH[35] = fTmpA*fC0; 421 | pSH[25] = fTmpA*fS0; 422 | fTmpB = -2.366619162231753*fZ; 423 | pSH[47] = fTmpB*fC0; 424 | pSH[37] = fTmpB*fS0; 425 | fTmpC = -6.745902523363385*fZ2 + 0.5189155787202604; 426 | pSH[61] = fTmpC*fC0; 427 | pSH[51] = fTmpC*fS0; 428 | fC1 = fX*fC0 - fY*fS0; 429 | fS1 = fX*fS0 + fY*fC0; 430 | 431 | fTmpA = 0.6831841051919144; 432 | pSH[48] = fTmpA*fC1; 433 | pSH[36] = fTmpA*fS1; 434 | fTmpB = 2.645960661801901*fZ; 435 | pSH[62] = fTmpB*fC1; 436 | pSH[50] = fTmpB*fS1; 437 | fC0 = fX*fC1 - fY*fS1; 438 | fS0 = fX*fS1 + fY*fC1; 439 | 440 | fTmpC = -0.7071627325245963; 441 | pSH[63] = fTmpC*fC0; 442 | pSH[49] = fTmpC*fS0; 443 | 444 | return pSH; 445 | } 446 | 447 | // order 9 448 | function SHEval9(fX, fY, fZ) 449 | { 450 | var fC0,fC1,fS0,fS1,fTmpA,fTmpB,fTmpC; 451 | var fZ2 = fZ*fZ; 452 | var pSH = new Array(81); 453 | 454 | pSH[0] = 0.2820947917738781; 455 | pSH[2] = 0.4886025119029199*fZ; 456 | pSH[6] = 0.9461746957575601*fZ2 + -0.3153915652525201; 457 | pSH[12] = fZ*(1.865881662950577*fZ2 + -1.119528997770346); 458 | pSH[20] = 1.984313483298443*fZ*pSH[12] + -1.006230589874905*pSH[6]; 459 | pSH[30] = 1.98997487421324*fZ*pSH[20] + -1.002853072844814*pSH[12]; 460 | pSH[42] = 1.993043457183567*fZ*pSH[30] + -1.001542020962219*pSH[20]; 461 | pSH[56] = 1.994891434824135*fZ*pSH[42] + -1.000927213921958*pSH[30]; 462 | pSH[72] = 1.996089927833914*fZ*pSH[56] + -1.000600781069515*pSH[42]; 463 | fC0 = fX; 464 | fS0 = fY; 465 | 466 | fTmpA = -0.48860251190292; 467 | pSH[3] = fTmpA*fC0; 468 | pSH[1] = fTmpA*fS0; 469 | fTmpB = -1.092548430592079*fZ; 470 | pSH[7] = fTmpB*fC0; 471 | pSH[5] = fTmpB*fS0; 472 | fTmpC = -2.285228997322329*fZ2 + 0.4570457994644658; 473 | pSH[13] = fTmpC*fC0; 474 | pSH[11] = fTmpC*fS0; 475 | fTmpA = fZ*(-4.683325804901025*fZ2 + 2.007139630671868); 476 | pSH[21] = fTmpA*fC0; 477 | pSH[19] = fTmpA*fS0; 478 | fTmpB = 2.03100960115899*fZ*fTmpA + -0.991031208965115*fTmpC; 479 | pSH[31] = fTmpB*fC0; 480 | pSH[29] = fTmpB*fS0; 481 | fTmpC = 2.021314989237028*fZ*fTmpB + -0.9952267030562385*fTmpA; 482 | pSH[43] = fTmpC*fC0; 483 | pSH[41] = fTmpC*fS0; 484 | fTmpA = 2.015564437074638*fZ*fTmpC + -0.9971550440218319*fTmpB; 485 | pSH[57] = fTmpA*fC0; 486 | pSH[55] = fTmpA*fS0; 487 | fTmpB = 2.011869540407391*fZ*fTmpA + -0.9981668178901745*fTmpC; 488 | pSH[73] = fTmpB*fC0; 489 | pSH[71] = fTmpB*fS0; 490 | fC1 = fX*fC0 - fY*fS0; 491 | fS1 = fX*fS0 + fY*fC0; 492 | 493 | fTmpA = 0.5462742152960395; 494 | pSH[8] = fTmpA*fC1; 495 | pSH[4] = fTmpA*fS1; 496 | fTmpB = 1.445305721320277*fZ; 497 | pSH[14] = fTmpB*fC1; 498 | pSH[10] = fTmpB*fS1; 499 | fTmpC = 3.31161143515146*fZ2 + -0.47308734787878; 500 | pSH[22] = fTmpC*fC1; 501 | pSH[18] = fTmpC*fS1; 502 | fTmpA = fZ*(7.190305177459987*fZ2 + -2.396768392486662); 503 | pSH[32] = fTmpA*fC1; 504 | pSH[28] = fTmpA*fS1; 505 | fTmpB = 2.11394181566097*fZ*fTmpA + -0.9736101204623268*fTmpC; 506 | pSH[44] = fTmpB*fC1; 507 | pSH[40] = fTmpB*fS1; 508 | fTmpC = 2.081665999466133*fZ*fTmpB + -0.9847319278346618*fTmpA; 509 | pSH[58] = fTmpC*fC1; 510 | pSH[54] = fTmpC*fS1; 511 | fTmpA = 2.06155281280883*fZ*fTmpC + -0.9903379376602873*fTmpB; 512 | pSH[74] = fTmpA*fC1; 513 | pSH[70] = fTmpA*fS1; 514 | fC0 = fX*fC1 - fY*fS1; 515 | fS0 = fX*fS1 + fY*fC1; 516 | 517 | fTmpA = -0.5900435899266435; 518 | pSH[15] = fTmpA*fC0; 519 | pSH[9] = fTmpA*fS0; 520 | fTmpB = -1.770130769779931*fZ; 521 | pSH[23] = fTmpB*fC0; 522 | pSH[17] = fTmpB*fS0; 523 | fTmpC = -4.403144694917254*fZ2 + 0.4892382994352505; 524 | pSH[33] = fTmpC*fC0; 525 | pSH[27] = fTmpC*fS0; 526 | fTmpA = fZ*(-10.13325785466416*fZ2 + 2.763615778544771); 527 | pSH[45] = fTmpA*fC0; 528 | pSH[39] = fTmpA*fS0; 529 | fTmpB = 2.207940216581962*fZ*fTmpA + -0.959403223600247*fTmpC; 530 | pSH[59] = fTmpB*fC0; 531 | pSH[53] = fTmpB*fS0; 532 | fTmpC = 2.15322168769582*fZ*fTmpB + -0.9752173865600178*fTmpA; 533 | pSH[75] = fTmpC*fC0; 534 | pSH[69] = fTmpC*fS0; 535 | fC1 = fX*fC0 - fY*fS0; 536 | fS1 = fX*fS0 + fY*fC0; 537 | 538 | fTmpA = 0.6258357354491763; 539 | pSH[24] = fTmpA*fC1; 540 | pSH[16] = fTmpA*fS1; 541 | fTmpB = 2.075662314881041*fZ; 542 | pSH[34] = fTmpB*fC1; 543 | pSH[26] = fTmpB*fS1; 544 | fTmpC = 5.550213908015966*fZ2 + -0.5045649007287241; 545 | pSH[46] = fTmpC*fC1; 546 | pSH[38] = fTmpC*fS1; 547 | fTmpA = fZ*(13.49180504672677*fZ2 + -3.113493472321562); 548 | pSH[60] = fTmpA*fC1; 549 | pSH[52] = fTmpA*fS1; 550 | fTmpB = 2.304886114323221*fZ*fTmpA + -0.9481763873554654*fTmpC; 551 | pSH[76] = fTmpB*fC1; 552 | pSH[68] = fTmpB*fS1; 553 | fC0 = fX*fC1 - fY*fS1; 554 | fS0 = fX*fS1 + fY*fC1; 555 | 556 | fTmpA = -0.6563820568401703; 557 | pSH[35] = fTmpA*fC0; 558 | pSH[25] = fTmpA*fS0; 559 | fTmpB = -2.366619162231753*fZ; 560 | pSH[47] = fTmpB*fC0; 561 | pSH[37] = fTmpB*fS0; 562 | fTmpC = -6.745902523363385*fZ2 + 0.5189155787202604; 563 | pSH[61] = fTmpC*fC0; 564 | pSH[51] = fTmpC*fS0; 565 | fTmpA = fZ*(-17.24955311049054*fZ2 + 3.449910622098108); 566 | pSH[77] = fTmpA*fC0; 567 | pSH[67] = fTmpA*fS0; 568 | fC1 = fX*fC0 - fY*fS0; 569 | fS1 = fX*fS0 + fY*fC0; 570 | 571 | fTmpA = 0.6831841051919144; 572 | pSH[48] = fTmpA*fC1; 573 | pSH[36] = fTmpA*fS1; 574 | fTmpB = 2.645960661801901*fZ; 575 | pSH[62] = fTmpB*fC1; 576 | pSH[50] = fTmpB*fS1; 577 | fTmpC = 7.984991490893139*fZ2 + -0.5323327660595426; 578 | pSH[78] = fTmpC*fC1; 579 | pSH[66] = fTmpC*fS1; 580 | fC0 = fX*fC1 - fY*fS1; 581 | fS0 = fX*fS1 + fY*fC1; 582 | 583 | fTmpA = -0.7071627325245963; 584 | pSH[63] = fTmpA*fC0; 585 | pSH[49] = fTmpA*fS0; 586 | fTmpB = -2.91570664069932*fZ; 587 | pSH[79] = fTmpB*fC0; 588 | pSH[65] = fTmpB*fS0; 589 | fC1 = fX*fC0 - fY*fS0; 590 | fS1 = fX*fS0 + fY*fC0; 591 | 592 | fTmpC = 0.72892666017483; 593 | pSH[80] = fTmpC*fC1; 594 | pSH[64] = fTmpC*fS1; 595 | 596 | return pSH; 597 | } 598 | 599 | // order 10 600 | function SHEval10(fX, fY, fZ) 601 | { 602 | var fC0,fC1,fS0,fS1,fTmpA,fTmpB,fTmpC; 603 | var fZ2 = fZ*fZ; 604 | var pSH = new Array(100); 605 | 606 | pSH[0] = 0.2820947917738781; 607 | pSH[2] = 0.4886025119029199*fZ; 608 | pSH[6] = 0.9461746957575601*fZ2 + -0.3153915652525201; 609 | pSH[12] = fZ*(1.865881662950577*fZ2 + -1.119528997770346); 610 | pSH[20] = 1.984313483298443*fZ*pSH[12] + -1.006230589874905*pSH[6]; 611 | pSH[30] = 1.98997487421324*fZ*pSH[20] + -1.002853072844814*pSH[12]; 612 | pSH[42] = 1.993043457183567*fZ*pSH[30] + -1.001542020962219*pSH[20]; 613 | pSH[56] = 1.994891434824135*fZ*pSH[42] + -1.000927213921958*pSH[30]; 614 | pSH[72] = 1.996089927833914*fZ*pSH[56] + -1.000600781069515*pSH[42]; 615 | pSH[90] = 1.996911195067937*fZ*pSH[72] + -1.000411437993134*pSH[56]; 616 | fC0 = fX; 617 | fS0 = fY; 618 | 619 | fTmpA = -0.48860251190292; 620 | pSH[3] = fTmpA*fC0; 621 | pSH[1] = fTmpA*fS0; 622 | fTmpB = -1.092548430592079*fZ; 623 | pSH[7] = fTmpB*fC0; 624 | pSH[5] = fTmpB*fS0; 625 | fTmpC = -2.285228997322329*fZ2 + 0.4570457994644658; 626 | pSH[13] = fTmpC*fC0; 627 | pSH[11] = fTmpC*fS0; 628 | fTmpA = fZ*(-4.683325804901025*fZ2 + 2.007139630671868); 629 | pSH[21] = fTmpA*fC0; 630 | pSH[19] = fTmpA*fS0; 631 | fTmpB = 2.03100960115899*fZ*fTmpA + -0.991031208965115*fTmpC; 632 | pSH[31] = fTmpB*fC0; 633 | pSH[29] = fTmpB*fS0; 634 | fTmpC = 2.021314989237028*fZ*fTmpB + -0.9952267030562385*fTmpA; 635 | pSH[43] = fTmpC*fC0; 636 | pSH[41] = fTmpC*fS0; 637 | fTmpA = 2.015564437074638*fZ*fTmpC + -0.9971550440218319*fTmpB; 638 | pSH[57] = fTmpA*fC0; 639 | pSH[55] = fTmpA*fS0; 640 | fTmpB = 2.011869540407391*fZ*fTmpA + -0.9981668178901745*fTmpC; 641 | pSH[73] = fTmpB*fC0; 642 | pSH[71] = fTmpB*fS0; 643 | fTmpC = 2.009353129741012*fZ*fTmpB + -0.9987492177719088*fTmpA; 644 | pSH[91] = fTmpC*fC0; 645 | pSH[89] = fTmpC*fS0; 646 | fC1 = fX*fC0 - fY*fS0; 647 | fS1 = fX*fS0 + fY*fC0; 648 | 649 | fTmpA = 0.5462742152960395; 650 | pSH[8] = fTmpA*fC1; 651 | pSH[4] = fTmpA*fS1; 652 | fTmpB = 1.445305721320277*fZ; 653 | pSH[14] = fTmpB*fC1; 654 | pSH[10] = fTmpB*fS1; 655 | fTmpC = 3.31161143515146*fZ2 + -0.47308734787878; 656 | pSH[22] = fTmpC*fC1; 657 | pSH[18] = fTmpC*fS1; 658 | fTmpA = fZ*(7.190305177459987*fZ2 + -2.396768392486662); 659 | pSH[32] = fTmpA*fC1; 660 | pSH[28] = fTmpA*fS1; 661 | fTmpB = 2.11394181566097*fZ*fTmpA + -0.9736101204623268*fTmpC; 662 | pSH[44] = fTmpB*fC1; 663 | pSH[40] = fTmpB*fS1; 664 | fTmpC = 2.081665999466133*fZ*fTmpB + -0.9847319278346618*fTmpA; 665 | pSH[58] = fTmpC*fC1; 666 | pSH[54] = fTmpC*fS1; 667 | fTmpA = 2.06155281280883*fZ*fTmpC + -0.9903379376602873*fTmpB; 668 | pSH[74] = fTmpA*fC1; 669 | pSH[70] = fTmpA*fS1; 670 | fTmpB = 2.048122358357819*fZ*fTmpA + -0.9934852726704042*fTmpC; 671 | pSH[92] = fTmpB*fC1; 672 | pSH[88] = fTmpB*fS1; 673 | fC0 = fX*fC1 - fY*fS1; 674 | fS0 = fX*fS1 + fY*fC1; 675 | 676 | fTmpA = -0.5900435899266435; 677 | pSH[15] = fTmpA*fC0; 678 | pSH[9] = fTmpA*fS0; 679 | fTmpB = -1.770130769779931*fZ; 680 | pSH[23] = fTmpB*fC0; 681 | pSH[17] = fTmpB*fS0; 682 | fTmpC = -4.403144694917254*fZ2 + 0.4892382994352505; 683 | pSH[33] = fTmpC*fC0; 684 | pSH[27] = fTmpC*fS0; 685 | fTmpA = fZ*(-10.13325785466416*fZ2 + 2.763615778544771); 686 | pSH[45] = fTmpA*fC0; 687 | pSH[39] = fTmpA*fS0; 688 | fTmpB = 2.207940216581962*fZ*fTmpA + -0.959403223600247*fTmpC; 689 | pSH[59] = fTmpB*fC0; 690 | pSH[53] = fTmpB*fS0; 691 | fTmpC = 2.15322168769582*fZ*fTmpB + -0.9752173865600178*fTmpA; 692 | pSH[75] = fTmpC*fC0; 693 | pSH[69] = fTmpC*fS0; 694 | fTmpA = 2.118044171189805*fZ*fTmpC + -0.9836628449792094*fTmpB; 695 | pSH[93] = fTmpA*fC0; 696 | pSH[87] = fTmpA*fS0; 697 | fC1 = fX*fC0 - fY*fS0; 698 | fS1 = fX*fS0 + fY*fC0; 699 | 700 | fTmpA = 0.6258357354491763; 701 | pSH[24] = fTmpA*fC1; 702 | pSH[16] = fTmpA*fS1; 703 | fTmpB = 2.075662314881041*fZ; 704 | pSH[34] = fTmpB*fC1; 705 | pSH[26] = fTmpB*fS1; 706 | fTmpC = 5.550213908015966*fZ2 + -0.5045649007287241; 707 | pSH[46] = fTmpC*fC1; 708 | pSH[38] = fTmpC*fS1; 709 | fTmpA = fZ*(13.49180504672677*fZ2 + -3.113493472321562); 710 | pSH[60] = fTmpA*fC1; 711 | pSH[52] = fTmpA*fS1; 712 | fTmpB = 2.304886114323221*fZ*fTmpA + -0.9481763873554654*fTmpC; 713 | pSH[76] = fTmpB*fC1; 714 | pSH[68] = fTmpB*fS1; 715 | fTmpC = 2.229177150706235*fZ*fTmpB + -0.9671528397231821*fTmpA; 716 | pSH[94] = fTmpC*fC1; 717 | pSH[86] = fTmpC*fS1; 718 | fC0 = fX*fC1 - fY*fS1; 719 | fS0 = fX*fS1 + fY*fC1; 720 | 721 | fTmpA = -0.6563820568401703; 722 | pSH[35] = fTmpA*fC0; 723 | pSH[25] = fTmpA*fS0; 724 | fTmpB = -2.366619162231753*fZ; 725 | pSH[47] = fTmpB*fC0; 726 | pSH[37] = fTmpB*fS0; 727 | fTmpC = -6.745902523363385*fZ2 + 0.5189155787202604; 728 | pSH[61] = fTmpC*fC0; 729 | pSH[51] = fTmpC*fS0; 730 | fTmpA = fZ*(-17.24955311049054*fZ2 + 3.449910622098108); 731 | pSH[77] = fTmpA*fC0; 732 | pSH[67] = fTmpA*fS0; 733 | fTmpB = 2.401636346922062*fZ*fTmpA + -0.9392246042043708*fTmpC; 734 | pSH[95] = fTmpB*fC0; 735 | pSH[85] = fTmpB*fS0; 736 | fC1 = fX*fC0 - fY*fS0; 737 | fS1 = fX*fS0 + fY*fC0; 738 | 739 | fTmpA = 0.6831841051919144; 740 | pSH[48] = fTmpA*fC1; 741 | pSH[36] = fTmpA*fS1; 742 | fTmpB = 2.645960661801901*fZ; 743 | pSH[62] = fTmpB*fC1; 744 | pSH[50] = fTmpB*fS1; 745 | fTmpC = 7.984991490893139*fZ2 + -0.5323327660595426; 746 | pSH[78] = fTmpC*fC1; 747 | pSH[66] = fTmpC*fS1; 748 | fTmpA = fZ*(21.39289019090864*fZ2 + -3.775215916042701); 749 | pSH[96] = fTmpA*fC1; 750 | pSH[84] = fTmpA*fS1; 751 | fC0 = fX*fC1 - fY*fS1; 752 | fS0 = fX*fS1 + fY*fC1; 753 | 754 | fTmpA = -0.7071627325245963; 755 | pSH[63] = fTmpA*fC0; 756 | pSH[49] = fTmpA*fS0; 757 | fTmpB = -2.91570664069932*fZ; 758 | pSH[79] = fTmpB*fC0; 759 | pSH[65] = fTmpB*fS0; 760 | fTmpC = -9.263393182848905*fZ2 + 0.5449054813440533; 761 | pSH[97] = fTmpC*fC0; 762 | pSH[83] = fTmpC*fS0; 763 | fC1 = fX*fC0 - fY*fS0; 764 | fS1 = fX*fS0 + fY*fC0; 765 | 766 | fTmpA = 0.72892666017483; 767 | pSH[80] = fTmpA*fC1; 768 | pSH[64] = fTmpA*fS1; 769 | fTmpB = 3.177317648954698*fZ; 770 | pSH[98] = fTmpB*fC1; 771 | pSH[82] = fTmpB*fS1; 772 | fC0 = fX*fC1 - fY*fS1; 773 | fS0 = fX*fS1 + fY*fC1; 774 | 775 | fTmpC = -0.7489009518531884; 776 | pSH[99] = fTmpC*fC0; 777 | pSH[81] = fTmpC*fS0; 778 | 779 | return pSH; 780 | } 781 | --------------------------------------------------------------------------------