PageSourceSearch

https://www.artlebedev.com/competencies/js/sheenmain.js

js artlebedev.com collected 2026-10-02 02:26:16 UTC 31,079 bytes, 935 lines download raw bytes

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.