1"use strict"; 2 3import * as THREE from '../build/three.module.js'; 4import { WEBGL } from './helpers/WebGL.js'; 5import { GLTFLoader } from './loaders/GLTFLoader.js'; 6import { RGBELoader } from './loaders/RGBELoader.js'; 7import { DRACOLoader } from './loaders/DRACOLoader.js'; 8import { MeshoptDecoder } from './loaders/meshopt/meshopt_decoder.module.js'; 9import { TrackballControls } from './controls/TrackballControls.js'; 10import { OrbitControls } from './controls/OrbitControls.js'; 11//import Stats from './helpers/stats.module.js'; 12 13 14 15///////////////////////////////////////////////////////////////////////// 16// Threejs main function 17async function main() { 18 const canvas = document.createElement('canvas'); 19 const renderer = new THREE.WebGLRenderer({canvas, alpha: true, antialias: true}); 20 21 // DEVICE SUPPORT CHECK 22 // Check if WebGL 2 is supported, otherwise return 23 if ( WEBGL.isWebGLAvailable() ) { 24 console.log('%cDevice supports WebGl 2.0. Begin main script.', "color: green"); 25 } else { 26 console.warn('%cDevice does not support WebGl 2.0. Loading a replacement video or picture.', "color: red"); 27 28 try { 29 progressBar.forEach((elem) => { elem.style.display = 'none';}); 30 displayThumbnails(); 31 return 32 } 33 34 finally { 35 canvas.remove(); 36 renderer.dispose() 37 } 38 } 39 40 // Check device supported extensions 41 const gl = renderer.getContext(); 42 const extensions = gl.getSupportedExtensions(); 43 console.log('Device supports followed WebGl extensions:', extensions); 44 45 // Handling WebGl context lost error. If render crashes, return 46 gl.canvas.addEventListener("webglcontextlost", function(event) { 47 // Hide progress bars and blurred thumbnail, then exit function 48 try { 49 progressBar.forEach((elem) => { elem.style.display = 'none';}); 50 displayThumbnails(); 51 return 52 } 53 // Remove renderer, canvas and put videos in container 54 finally { 55 canvas.remove(); 56 renderer.dispose() 57 } 58 }, false); 59 60 // Check anisotropic filter extension support 61 const anisotropicSupported = (() => { 62 try { 63 if ((extensions.includes('EXT_texture_filter_anisotropic')) || 64 (extensions.includes('MOZ_EXT_texture_filter_anisotropic')) || 65 (extensions.includes('WEBKIT_EXT_texture_filter_anisotropic'))){ 66 console.log("%cDevice supports anisotropic texture filter, setting maximum texture quality.", "color: green") 67 return true; 68 } 69 } catch (e) { 70 console.warn("Can't check anisotropy support on this device") 71 } 72 console.log("%cDevice does not support anisotropic texture filter, setting texture quality with avaliable fixes.", "color: orange") 73 return false; 74 })(); 75 76 // Checking Web Assembly support 77 const wasmSupported = (() => { 78 try { 79 if (typeof WebAssembly === "object" 80 && typeof WebAssembly.instantiate === "function") { 81 const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); 82 if (module instanceof WebAssembly.Module) 83 console.log("%cDevice supports WebAssembly, loading optimized models.", "color: green") 84 return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; 85 } 86 } catch (e) { 87 console.warn("Can't check WebAssembly support on this device") 88 } 89 console.log("%cDevice does not support WebAssembly, loading raw models.", "color: orange") 90 return false; 91 })(); 92 93 94 // Preloading scenes based on DOM elements. Creating global scene dictionary 95 var SCENES = {}; 96 var names = [], promises = []; 97 98 try { 99 document.querySelectorAll('[data-scene]').forEach((elem) => { 100 var gltf; 101 if (elem.dataset.fallback) { 102 gltf = (wasmSupported) ? gltfLoader(elem.dataset.scene) : gltfLoader(elem.dataset.fallback); 103 } else { 104 gltf = gltfLoader(elem.dataset.scene); 105 } 106 107 names.push(getFileName("models/", elem.dataset.scene)); 108 promises.push(gltf); 109 }); 110 111 await Promise.all(promises).then(array => { 112 array.forEach((scene, i) => { 113 SCENES[names[i]] = scene; 114 }) 115 }, reason => { 116 console.log(reason); 117 }); 118 119 } catch (e) { 120 console.error('Error loading scenes'); 121 progressBar.forEach((elem) => { elem.style.display = 'none';}); 122 displayThumbnails(); 123 return 124 } 125 126 console.log('%cCreated scenes dictionary.', "color: green"); 127 console.log('Dictionary data contains:', SCENES); 128 129 // Preloading scene HDRI
130 var LIGHTS = {}; 131 names = []; promises = []; 132 133 try { 134 document.querySelectorAll('[data-lights]').forEach((elem) => { 135 var lightname = getFileName("hdri/", elem.dataset.lights); 136 if (!names.includes(lightname)) { 137 names.push(getFileName("hdri/", elem.dataset.lights)); 138 promises.push(setEnvironmentMap(renderer, elem.dataset.lights)) 139 } 140 }); 141 142 await Promise.all(promises).then(array => { 143 array.forEach((light, i) => { 144 LIGHTS[names[i]] = light; 145 }) 146 }, reason => { 147 console.log(reason); 148 }); 149 150 } catch (e) { 151 console.error('Error loading scenes'); 152 progressBar.forEach((elem) => { elem.style.display = 'none';}); 153 displayThumbnails(); 154 return 155 } 156 157 console.log('%cCreated HDRI dictionary.', "color: green"); 158 console.log('Dictionary data contains:', LIGHTS); 159 160 // Set renderer settings 161 renderer.gammaFactor = 2.2; 162 renderer.outputEncoding = THREE.sRGBEncoding; 163 renderer.toneMapping = THREE.ACESFilmicToneMapping; 164 renderer.toneMappingExposure = 1; 165 renderer.stencil = false; 166 renderer.premultipliedAlpha = true; 167 renderer.depth = false; 168 renderer.sortObjects = false; 169 renderer.powerPreference = "high-performance"; 170 renderer.physicallyCorrectLights = true; 171 renderer.preserveDrawingBuffer = false; 172 renderer.setScissorTest(true); 173 174 // Store scene elements, create scenes and corresponding canvases to each custom id 175 const sceneElements = []; 176 function addScene(elem, fn) { 177 const ctx = document.createElement('canvas').getContext('2d'); 178 elem.appendChild(ctx.canvas); 179 sceneElements.push({elem, ctx, fn}); 180 } 181 182 // Default scene, camera and light presets 183 // Making scene, with default camera and default light 184 function makeEmptyScene(elem) { 185 const scene = new THREE.Scene(); 186 const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 2500); 187 return {scene, camera}; 188 } 189 190 function makeDefaultScene(elem, light) { 191 const scene = new THREE.Scene(); 192 const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 2500); 193 194 // Scene lights 195 { 196 const light = new THREE.DirectionalLight(0xFFFFFF, 1); 197 light.position.set(0, 10, 0); 198 scene.add(light); 199 200 const ambient = new THREE.AmbientLight(0xFFFFFF, 0.5); 201 scene.add(ambient); 202 203 const hemisphere = new THREE.HemisphereLight(0xffffff, 0xff0000, 0.1); 204 hemisphere.position.set(0, 10, 0).normalize(); 205 scene.add(hemisphere); 206 } 207 208 scene.environment = light; 209 return {scene, camera}; 210 } 211 212 // Interactive 213 // Raycaster 214 function makeRaycaster(scene) { 215 const raycaster = new THREE.Raycaster(); 216 const pointer = new THREE.Vector2(); 217 218 const geometry = new THREE.BufferGeometry(); 219 geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( 4 * 3 ), 3 ) ); 220 const material = new THREE.LineBasicMaterial( { color: 0xffffff, transparent: true } ); 221 const line = new THREE.Line( geometry, material ); 222 scene.add( line ); 223 224 document.addEventListener( 'pointermove', onPointerMove ); 225 } 226 227 function onPointerMove( event ) { 228 pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1; 229 pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1; 230 } 231 232 // Scene presets 233 // Default scene initialization function 234 function sceneInit (elem, gltf, light, animation = true, rotation = [0, 0.05, 0]) { 235 var gltfScene = gltf; 236 var hdriLights = light; 237 238 var {scene, camera} = (light) ? makeDefaultScene(elem, hdriLights) : makeEmptyScene(elem); 239 240 // Scene camera from gltf 241 // If exists get the first one, if no use already premade 242 if (gltfScene.cameras.length > 0) { 243 camera = gltfScene.cameras[0]; 244 } else { 245 const box = new THREE.Box3().setFromObject(gltfScene.scene); 246 const size = box.getSize(new THREE.Vector3()).length(); 247 const center = box.getCenter(new THREE.Vector3()); 248 249 camera.position.set(center.x, center.y, size); 250 camera.lookAt(center) 251 } 252 253 const model = gltfScene.scene; // Scene objects from gltf 254 scene.add(model); 255 256 const mixer = new THREE.AnimationMixer(model); // Init scene animations from gltf 257 const clips = gltfScene.animations; 258 259 clips.forEach (function (clip) { 260 if (animation) mixer.clipAction(clip).play(); // Playing each animation action in scene 261 }); 262 263 // Materials and textures adjustment 264 { 265 const maxAnisotropy = renderer.capabilities.getMaxAnisotropy(); 266 scene.traverse(function (child) { 267 if (child.isMesh) { 268 child.frustumCulled = false;
269 270 // Texture quality 271 if (child.material.map != null) { 272 anisotropicSupported ? child.material.map.anisotropy = maxAnisotropy : child.material.map.minFilter = THREE.LinearFilter; 273 } 274 if (child.material.metalnessMap != null) { 275 child.material.metalnessMap.anisotropy = maxAnisotropy; 276 } 277 if (child.material.normalMap != null) { 278 child.material.normalMap.anisotropy = maxAnisotropy; 279 } 280 if (child.material.roughnessMap != null) { 281 child.material.roughnessMap.anisotropy = maxAnisotropy; 282 } 283 if (child.material.clearcoatNormalMap != null) { 284 child.material.clearcoatNormalMap.anisotropy = maxAnisotropy; 285 } 286 287 // Dithering 288 if (child.material != null) { 289 child.material.dithering = true; 290 } 291 292 // Transparent material override 293 if (child.material.transparent) { 294 child.material.side = THREE.FrontSide; 295 child.material.depthWrite = true; // Fix for complex alpha materials 296 child.material.depthTest = true; 297 } 298 299 } 300 }); 301 } 302 303 // Animation function. Updating each frame 304 return (time, rect, delta) => { 305 if (animation) scene.rotation.x = time * rotation[0]; 306 if (animation) scene.rotation.y = time * rotation[1]; 307 if (animation) scene.rotation.z = time * rotation[2]; 308 if ( mixer ) { 309 mixer.update( delta ); 310 } 311 camera.aspect = rect.width / rect.height; 312 camera.updateProjectionMatrix(); 313 renderer.render(scene, camera); 314 }; 315 } 316 317 // Trackball controls preset 318 function sceneInitTrackballs (elem, gltf, light, animation = true, target) { 319 var gltfScene = gltf; 320 var hdriLights = light; 321 322 var {scene, camera} = (light) ? makeDefaultScene(elem, hdriLights) : makeEmptyScene(elem); 323 324 //Trackballs camera 325 //Blender Y -> -Z; Blender Z -> Y 326 if (gltfScene.cameras.length > 0) { 327 if (gltfScene.cameras[0].name.includes("Orientation")) { 328 camera.position.copy(gltfScene.cameras[0].parent.position); 329 } else { 330 camera.position.copy(gltfScene.cameras[0].position); 331 } 332 camera.fov = gltfScene.cameras[0].fov 333 } else { 334 const box = new THREE.Box3().setFromObject(gltfScene.scene); 335 const size = box.getSize(new THREE.Vector3()).length(); 336 const center = box.getCenter(new THREE.Vector3()); 337 338 camera.position.set(center.x, center.y, size); 339 camera.lookAt(center) 340 } 341 342 const controls = new TrackballControls(camera, elem); 343 controls.noZoom = true; 344 controls.noPan = true; 345 controls.dynamicDampingFactor = 0.05; 346 347 var cameraTarget 348 try { 349 cameraTarget = gltfScene.scene.getObjectByName(target); 350 controls.target = cameraTarget.position 351 } catch (e) { 352 const box = new THREE.Box3().setFromObject(gltfScene.scene); 353 const size = box.getSize(new THREE.Vector3()).length(); 354 const center = box.getCenter(new THREE.Vector3()); 355 356 controls.target = center; 357 } 358 359 // Scene settings 360 const model = gltfScene.scene; // Scene objects from gltf 361 scene.add(model); 362 363 const mixer = new THREE.AnimationMixer(model); // Init scene animations from gltf 364 const clips = gltfScene.animations; 365 366 clips.forEach (function (clip) { 367 mixer.clipAction(clip).play(); // Playing each animation action in scene 368 }); 369 370 // Materials and textures adjustment 371 { 372 const maxAnisotropy = renderer.capabilities.getMaxAnisotropy(); 373 scene.traverse(function (child) { 374 if (child.isMesh) { 375 child.frustumCulled = false; 376 377 // Texture quality 378 if (child.material.map != null) { 379 anisotropicSupported ? child.material.map.anisotropy = maxAnisotropy : child.material.map.minFilter = THREE.LinearFilter; 380 } 381 if (child.material.metalnessMap != null) { 382 child.material.metalnessMap.anisotropy = maxAnisotropy; 383 } 384 if (child.material.normalMap != null) { 385 child.material.normalMap.anisotropy = maxAnisotropy; 386 } 387 if (child.material.roughnessMap != null) { 388 child.material.roughnessMap.anisotropy = maxAnisotropy; 389 } 390 if (child.material.clearcoatNormalMap != null) { 391 child.material.clearcoatNormalMap.anisotropy = maxAnisotropy; 392 } 393 394 // Dithering 395 if (child.material != null) { 396 child.material.dithering = true; 397 } 398 399 // Transparent material override 400 if (child.material.transparent) { 401 child.material.side = THREE.FrontSide; 402 child.material.depthWrite = true; // Fix for complex alpha materials 403 child.material.depthTest = true; 404 } 405 406 } 407 }); 408 } 409 410 // Animation function. Updating each frame 411 return (time, rect, delta) => { 412 if (animation) scene.rotation.y = time * 0.05; 413 if ( mixer ) { 414 mixer.update( delta ); 415 } 416 camera.aspect = rect.width / rect.height; 417 camera.updateProjectionMatrix(); 418 controls.handleResize(); 419 controls.update(); 420 renderer.render(scene, camera); 421 }; 422 } 423 424 // Orbit controls preset 425 function sceneInitOrbit (elem, gltf, light, animation = true, target) { 426 var gltfScene = gltf; 427 var hdriLights = light; 428 429 var {scene, camera} = (light) ? makeDefaultScene(elem, hdriLights) : makeEmptyScene(elem); 430 431 // Calculate bounding box of the scene 432 const box = new THREE.Box3().setFromObject(gltfScene.scene); 433 const size = box.getSize(new THREE.Vector3()).length(); 434 const center = box.getCenter(new THREE.Vector3()); 435 436 //Orbit camera 437 if (gltfScene.cameras.length > 0) { 438 if (gltfScene.cameras[0].name.includes("Orientation")) { 439 camera.position.copy(gltfScene.cameras[0].parent.position); 440 } else { 441 camera.position.copy(gltfScene.cameras[0].position); 442 } 443 camera.fov = gltfScene.cameras[0].fov 444 445 } else { 446 camera.position.set(center.x, center.y, size); 447 camera.lookAt(center) 448 } 449 450 // Init Orbit Controls 451 const controls = new OrbitControls(camera, elem); 452 controls.enableDamping = true; 453 controls.dampingFactor = 0.05; 454 controls.screenSpacePanning = false;
455 controls.enableZoom = false; 456 457 var cameraTarget 458 try { 459 cameraTarget = gltfScene.scene.getObjectByName(target); 460 controls.target = cameraTarget.position 461 } catch (e) { 462 const box = new THREE.Box3().setFromObject(gltfScene.scene); 463 const size = box.getSize(new THREE.Vector3()).length(); 464 const center = box.getCenter(new THREE.Vector3()); 465 466 controls.target = center; 467 } 468 469 // Set camera polar angle as camera have right now 470 var cameraAngle 471 try { 472 cameraAngle = (cameraTarget)? camera.position.angleTo(cameraTarget.position) : camera.position.angleTo(center); 473 console.log('%cCalculated camera angle for orbit controls scene.', "color: green"); 474 console.log('Angle: ' + cameraAngle, elem.dataset.scene); 475 } catch (e) { 476 cameraAngle = Math.PI / 2; 477 console.log('%cSet default camera angle for orbit controls scene', "color: green"); 478 console.log(cameraAngle, elem.dataset.scene); 479 } 480 controls.maxPolarAngle = cameraAngle; 481 controls.minPolarAngle = cameraAngle; 482 483 // Scene settings 484 const model = gltfScene.scene; // Scene objects from gltf 485 scene.add(model); 486 487 const mixer = new THREE.AnimationMixer(model); // Init scene animations from gltf 488 const clips = gltfScene.animations; 489 490 clips.forEach (function (clip) { 491 mixer.clipAction(clip).play(); // Playing each animation action in scene 492 }); 493 494 // Materials and textures adjustment 495 { 496 const maxAnisotropy = renderer.capabilities.getMaxAnisotropy(); 497 scene.traverse(function (child) { 498 if (child.isMesh) { 499 child.frustumCulled = false; 500 501 // Texture quality 502 if (child.material.map != null) { 503 anisotropicSupported ? child.material.map.anisotropy = maxAnisotropy : child.material.map.minFilter = THREE.LinearFilter; 504 } 505 if (child.material.metalnessMap != null) { 506 child.material.metalnessMap.anisotropy = maxAnisotropy; 507 } 508 if (child.material.normalMap != null) { 509 child.material.normalMap.anisotropy = maxAnisotropy; 510 } 511 if (child.material.roughnessMap != null) { 512 child.material.roughnessMap.anisotropy = maxAnisotropy; 513 } 514 if (child.material.clearcoatNormalMap != null) { 515 child.material.clearcoatNormalMap.anisotropy = maxAnisotropy; 516 } 517 518 // Dithering 519 if (child.material != null) { 520 child.material.dithering = true; 521 } 522 523 // Transparent material override 524 if (child.material.transparent) { 525 child.material.side = THREE.FrontSide; 526 child.material.depthWrite = true; // Fix for complex alpha materials 527 child.material.depthTest = true; 528 } 529 530 } 531 }); 532 } 533 534 // Animation function. Updating each frame 535 return (time, rect, delta) => { 536 if (animation) scene.rotation.y = time * 0.05; 537 if ( mixer ) { 538 mixer.update( delta ); 539 } 540 camera.aspect = rect.width / rect.height; 541 camera.updateProjectionMatrix(); 542 controls.update(); 543 renderer.render(scene, camera); 544 }; 545 } 546 547 548 // Select all custom id's in HTML and init corresponding scene 549 document.querySelectorAll('[data-scene]').forEach((elem) => { 550 var sceneName = getFileName("models/", elem.dataset.scene); 551 var sceneInitFunction 552 var sceneRenderFunction 553 554 // Get HDRI path from dataset 555 var hdri 556 try { 557 var lightname = getFileName("hdri/", elem.dataset.lights); 558 hdri = LIGHTS[lightname]; 559 } catch (e) { 560 hdri = null; 561 } 562 563 // Check custom data extensions 564 var controlsArray 565 try { 566 controlsArray = JSON.parse(elem.dataset.controls); 567 } catch (e) { 568 controlsArray = null; 569 } 570 571 // Check scene rotation speed 572 var lightsRotArray 573 try { 574 lightsRotArray = JSON.parse(elem.dataset.lightsrot); 575 } catch (e) { 576 } 577 578 // Check if animation is playing 579 var sceneAnim 580 try { 581 sceneAnim = (elem.dataset.anim == 'false') ? false : true 582 } catch (e) { 583 sceneAnim = true; 584 } 585
586 // Scene initialization 587 // Check if scene have any custom marker, if not run usual scene 588 try { 589 if (controlsArray.includes('trackball')) { 590 sceneInitFunction = sceneInitTrackballs 591 elem.classList.add("sheen-grabbing"); 592 var target 593 try { 594 target = controlsArray[1]; 595 } catch (e) { 596 target = null 597 } 598 sceneRenderFunction = sceneInitFunction(elem, SCENES[sceneName], hdri, sceneAnim, target); 599 } 600 601 else if (controlsArray.includes('orbit')) { 602 sceneInitFunction = sceneInitOrbit 603 elem.classList.add("sheen-grabbing"); 604 var target 605 try { 606 target = controlsArray[1]; 607 } catch (e) { 608 target = null 609 } 610 sceneRenderFunction = sceneInitFunction(elem, SCENES[sceneName], hdri, sceneAnim, target); 611 } 612 613 else { 614 sceneInitFunction = sceneInit; 615 sceneRenderFunction = sceneInitFunction(elem, SCENES[sceneName], hdri, sceneAnim, lightsRotArray) 616 } 617 618 } catch (e) { 619 sceneInitFunction = sceneInit; 620 sceneRenderFunction = sceneInitFunction(elem, SCENES[sceneName], hdri, sceneAnim, lightsRotArray) 621 } 622 623 addScene(elem, sceneRenderFunction); 624 }); 625 626 627 // Hide blurry thumbnail after everyting is setted up 628 fadeScreen(); 629 630 // Scene stats. 1: FPS, 2: MS, 3: MB and custom 631 /*var stats = new Stats(); 632 stats.showPanel( 0 ); 633 document.body.appendChild( stats.dom );*/ 634 635 // Main animation function 636 const clock = new THREE.Clock(); 637 638 // Main render function 639 function render(time) { 640 // Begin collecting perfomance stats 641 //stats.begin(); 642 643 time *= 0.001; 644 var delta = clock.getDelta(); // Framerate independent animation 645 646 for (const {elem, fn, ctx} of sceneElements) { 647 // Get the viewport relative position of this element 648 const rect = elem.getBoundingClientRect(); 649 const {left, right, top, bottom, width, height} = rect; 650 const rendererCanvas = renderer.domElement; 651 652 // Check canvas visibility 653 const isOffscreen = 654 bottom < 0 || 655 top > window.innerHeight || 656 right < 0 || 657 left > window.innerWidth; 658 659 if (!isOffscreen) { 660 // Make sure the renderer's canvas is big enough 661 if (rendererCanvas.width < width || rendererCanvas.height < height) { 662 renderer.setSize(width, height, false); 663 } 664 665 // Make sure the canvas for this area is the same size as the area 666 if (ctx.canvas.width !== width || ctx.canvas.height !== height) { 667 ctx.canvas.width = width; 668 ctx.canvas.height = height; 669 } 670 671 renderer.setScissor(0, 0, width, height); 672 renderer.setViewport(0, 0, width, height); 673 674 fn(time, rect, delta); 675 676 // Copy the rendered scene to this element's canvas 677 ctx.globalCompositeOperation = 'copy'; 678 ctx.drawImage( 679 rendererCanvas, 680 0, rendererCanvas.height - height, width, height, // src rect 681 0, 0, width, height); // dst rect 682 } 683 } 684 // End collecting perfomance stats 685 //stats.end(); 686 687 requestAnimationFrame(render); 688 } 689 requestAnimationFrame(render); 690 } 691 692 // Array of all progress bars on the page. Each one shoud have it's own container 693 const progressBar = document.querySelectorAll(".sheen-progressBar"); 694 695 // Loading manager. Handles multiple file loading with callbacks 696 const manager = new THREE.LoadingManager(); 697 manager.onStart = function ( url, itemsLoaded, itemsTotal ) { 698 console.log( 'Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); 699 progressBar.forEach((elem) => { elem.style.display = 'block';}); 700 }; 701 manager.onLoad = function ( ) { 702 console.log( '%cLoading complete!', 'color: green'); 703 progressBar.forEach((elem) => { elem.style.display = 'none';}); // Hide all Progress bar on load 704 }; 705 manager.onProgress = function ( url, itemsLoaded, itemsTotal ) { 706 console.log( 'Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); 707 const progress = itemsLoaded / itemsTotal; 708 progressBar.forEach((elem) => { elem.style.transform = `scaleX(${progress})`;}); // Change Progress bar size while loading 709 }; 710 manager.onError = function ( url ) { 711 console.log( '%cThere was an error loading ' + url, 'color: red'); 712 }; 713 714 // File loader function, accepts glb or gltf files 715 // Add -meshopt or -draco to filename if files are compressed 716 // Usual file: file.glb; Meshoptimizer: file-meshopt.glb; Draco: file-draco.glb 717 function gltfLoader( path ) { 718 return new Promise((resolve, reject) => { 719 const loader = new GLTFLoader( manager ); 720 721 // Intializing Draco loader 722 const dracoLoader = new DRACOLoader(); 723 dracoLoader.setDecoderPath( './js/loaders/draco/' ); 724 dracoLoader.setDecoderConfig( { type: 'js' } ); 725 726 // Checking file format and compression tag: -meshopt or -draco 727 var format = path.split('.').pop(); 728 var type = path.split('-').pop().slice(0, -format.length - 1); 729 // console.log('Loading ' + type + ' compressed file.'); 730 731 if (type == 'draco') loader.setDRACOLoader( dracoLoader ); 732 if (type == 'meshopt') loader.setMeshoptDecoder( MeshoptDecoder ); 733 734 loader.load( 735 path, 736 data => resolve(data), 737 null, 738 reject => console.warn('This device may not support ' + type + ' compressor')); 739 }); 740 } 741 742 // Loading and building environmental map texture. Supports only hdr at the moment 743 function setEnvironmentMap(renderer, filename) { 744 return new Promise((resolve, reject) => { 745 const pmremGenerator = new THREE.PMREMGenerator( renderer ); 746 pmremGenerator.compileEquirectangularShader(); 747 748 let type = filename.split('.').pop(); 749 console.log('Loading ' + type + ' environmental map.'); 750 751 if (type == 'hdr') { 752 new RGBELoader( manager ) 753 .setDataType(THREE.UnsignedByteType) 754 //.setPath(path) 755 .load(filename, function (texture) { 756 757 try { 758 const environment_map = pmremGenerator.fromEquirectangular(texture).texture; 759 resolve(environment_map) 760 console.log(filename + ' loaded') 761 } 762 763 finally { 764 texture.dispose(); 765 pmremGenerator.dispose(); 766 console.log(filename + ' texture and generator are disposed'); 767 } 768 }); 769 } 770 771 else if (type == 'exr') { 772 console.error('Failed to load ' + filename + '; Exr file format is not supported, due to library bug on mobile devices') 773 } 774 775 else { 776 console.error('Failed to load ' + filename + '; Only hdr environmental maps are supported') 777 } 778 }); 779 } 780 781 // Retrieve scene name from filepath 782 function getFileName(folder, path) { 783 var filename = path.split(folder).pop(); 784 785 var format; 786 if (filename.includes("-")) { 787 format = filename.split('-').pop(); 788 } else { 789 format = filename.split('.').pop(); 790 } 791 792 var name = filename.slice(0, -format.length - 1); 793 794 return name; 795 } 796 797 // Run main script 798 main().catch(error => { 799 console.warn(error); 800 }); 801 802 803 804 // Start Screen fading 805 function fadeScreen() { 806 document.querySelectorAll('.sheen-loading-screen').forEach((elem) => { 807 elem.classList.add( 'fade-out' ); 808 elem.addEventListener( 'transitionend', onTransitionEnd ); 809 }); 810 } 811 812 // Removes fade screen from html 813 function onTransitionEnd( event ) { 814 const element = event.target; 815 //element.remove(); 816 element.style.display = 'none'; 817 } 818 819 // Shows all hidden thumbnails 820 function displayThumbnails() { 821 document.querySelectorAll(".sheen-loading-screen").forEach(function(elem) { 822 elem.style.display = 'block'; 823 elem.classList.remove('fade-out'); 824 elem.style.opacity = '1'; 825 }); 826 } 827
828 ///////////////////////////////////////////////////////////////////////// 829 // Safari compatibility monkey-patch for drawImage() 830 (()=> { 831 832 if( !needPoly() ) { return; } 833 834 const proto = CanvasRenderingContext2D.prototype; 835 const original = proto.drawImage; 836 if( !original ) { 837 console.error( "This script requires a basic implementation of drawImage" ); 838 return; 839 } 840 841 proto.drawImage = function drawImage( source, x, y ) { // length: 3 842 843 const will_crop = arguments.length === 9; 844 if( !will_crop ) { 845 return original.apply( this, [...arguments] ); 846 } 847 848 const safe_rect = getSafeRect( ...arguments ); 849 if( isEmptyRect( safe_rect ) ) { 850 return; 851 } 852 return original.apply( this, safe_rect ); 853 } 854 855 function needPoly() { 856 const ctx = document.createElement( "canvas" ).getContext( "2d" ); 857 ctx.fillRect( 0, 0, 40, 40 ); 858 ctx.drawImage( ctx.canvas, -40, -40, 80, 80, 50, 50, 20, 20 ); 859 860 const img = ctx.getImageData( 50, 50, 30, 30 ); // 10px around expected square 861 const data = new Uint32Array( img.data.buffer ); 862 const colorAt = (x, y) => data[ y * img.width + x ]; 863 864 const transparents = [ [ 9, 9 ], [ 20, 9 ], [ 9, 20 ], [ 20, 20 ] ]; 865 const blacks = [ [ 10, 10 ], [ 19, 10 ], [ 10, 19 ], [ 19, 19 ] ]; 866 return transparents.some( ([ x, y ]) => colorAt( x, y ) !== 0x00000000 ) || 867 blacks.some( ([ x, y ]) => colorAt( x, y ) === 0x00000000 ) 868 } 869 870 function getSafeRect( image, sx, sy, sw, sh, dx, dy, dw, dh ) { 871 872 const { width, height } = getSourceDimensions( image ); 873 874 if( sw < 0 ) { 875 sx += sw; 876 sw = Math.abs( sw ); 877 } 878 if( sh < 0 ) { 879 sy += sh; 880 sh = Math.abs( sh ); 881 } 882 if( dw < 0 ) { 883 dx += dw; 884 dw = Math.abs( dw ); 885 } 886 if( dh < 0 ) { 887 dy += dh; 888 dh = Math.abs( dh ); 889 } 890 const x1 = Math.max( sx, 0 ); 891 const x2 = Math.min( sx + sw, width ); 892 const y1 = Math.max( sy, 0 ); 893 const y2 = Math.min( sy + sh, height ); 894 const w_ratio = dw / sw; 895 const h_ratio = dh / sh; 896 897 return [ 898 image, 899 x1, 900 y1, 901 x2 - x1, 902 y2 - y1, 903 sx < 0 ? dx - (sx * w_ratio) : dx, 904 sy < 0 ? dy - (sy * h_ratio) : dy, 905 (x2 - x1) * w_ratio, 906 (y2 - y1) * h_ratio 907 ]; 908 909 } 910 911 function isEmptyRect( args ) { 912 // sw, sh, dw, dh 913 return [ 3, 4, 7, 8 ].some( (index) => !args[ index ] ); 914 } 915 916 function getSourceDimensions( source ) { 917 const sourceIs = ( type ) => { 918 const constructor = globalThis[ type ] || global[ type ]; 919 return constructor && (source instanceof constructor); 920 }; 921 if( sourceIs( "HTMLImageElement" ) ) { 922 return { width: source.naturalWidth, height: source.naturalHeight }; 923 } 924 else if( sourceIs( "HTMLVideoElement" ) ) { 925 return { width: source.videoWidth, height: source.videoHeight }; 926 } 927 else if( sourceIs( "SVGImageElement" ) ) { 928 throw new TypeError( "SVGImageElement isn't yet supported as source image.", "UnsupportedError" ); 929 } 930 else if( sourceIs( "HTMLCanvasElement" ) || sourceIs( "ImageBitmap" ) ) { 931 return source; 932 } 933 } 934 935 })();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.