KoeYe commited on
Commit
c77f6d9
·
1 Parent(s): e4cab33

Render geometry before progressive textures and self-host Three.js

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +8 -0
  2. assets/buildingbench.css +2 -0
  3. assets/progressive-gltf.js +87 -0
  4. assets/scene-assets.js +1 -1
  5. assets/scene-cards.js +20 -29
  6. assets/vendor/three-r160/LICENSE +21 -0
  7. assets/vendor/three-r160/addons/controls/OrbitControls.js +1417 -0
  8. assets/vendor/three-r160/addons/libs/meshopt_decoder.module.js +178 -0
  9. assets/vendor/three-r160/addons/loaders/GLTFLoader.js +0 -0
  10. assets/vendor/three-r160/addons/objects/Sky.js +219 -0
  11. assets/vendor/three-r160/addons/utils/BufferGeometryUtils.js +1375 -0
  12. assets/vendor/three-r160/build/three.module.js +0 -0
  13. cases.html +1 -1
  14. data/progressive-scenes.json +90 -0
  15. index.html +2 -2
  16. models/progressive-v1/geo-054a9fa272d6216f02828f09.bin +3 -0
  17. models/progressive-v1/geo-2cb0699fcdfbb0077b14a03a.bin +3 -0
  18. models/progressive-v1/geo-6917456ac7ecb3f644ad6e37.bin +3 -0
  19. models/progressive-v1/geo-8d8ac2389fb2144d0ada046a.bin +3 -0
  20. models/progressive-v1/geo-9e85a029420d9485d131d029.bin +3 -0
  21. models/progressive-v1/geo-ab194913839cc37985edc5c8.bin +3 -0
  22. models/progressive-v1/geo-b0e37abcf8f8538d5c7c567d.bin +3 -0
  23. models/progressive-v1/geo-cfc8f9530bbdf82e9ad5803e.bin +3 -0
  24. models/progressive-v1/geo-f52df859622b51e5a8e79b7f.bin +3 -0
  25. models/progressive-v1/geo-fbdfbee814c5ec5bbbd7b049.bin +3 -0
  26. models/progressive-v1/geo-fc7f349eacd0387d378791ba.bin +3 -0
  27. models/progressive-v1/tex-012c8e3c3ec399a5e3814072.bin +3 -0
  28. models/progressive-v1/tex-145b1eebf364866f45d8e439.bin +3 -0
  29. models/progressive-v1/tex-1c281547b397376efd0199f4.bin +3 -0
  30. models/progressive-v1/tex-1f1e882fba2f5973b698e4eb.bin +3 -0
  31. models/progressive-v1/tex-1fedf9beb0efec481eb12bf4.bin +3 -0
  32. models/progressive-v1/tex-2452f0e9439a921b6ddef41f.bin +3 -0
  33. models/progressive-v1/tex-2a7f522dfd3141f76017fb18.bin +3 -0
  34. models/progressive-v1/tex-2d0d7e1fa9f1d976efb71cee.bin +3 -0
  35. models/progressive-v1/tex-466f0b10c87df10345ad2f25.bin +3 -0
  36. models/progressive-v1/tex-4d8b036a2ee06405bef3e76c.bin +3 -0
  37. models/progressive-v1/tex-4f27ae3f9204f54fd17a9d19.bin +3 -0
  38. models/progressive-v1/tex-60a1d5975059cf1464d7e63d.bin +3 -0
  39. models/progressive-v1/tex-62e57d9806cfa50c8907036d.bin +3 -0
  40. models/progressive-v1/tex-7617ace0a61438d8778a6ca1.bin +3 -0
  41. models/progressive-v1/tex-787aa264f2ab67e4714dd87d.bin +3 -0
  42. models/progressive-v1/tex-7e84a3d9e41d34dc95c56b50.bin +3 -0
  43. models/progressive-v1/tex-80312b3d120467ae6a6d0c5c.bin +3 -0
  44. models/progressive-v1/tex-87528eb6654a28944db234fa.bin +3 -0
  45. models/progressive-v1/tex-89b9c55ddabdd596bb39e1b0.bin +3 -0
  46. models/progressive-v1/tex-8d83f93625bdd185c8322e5a.bin +3 -0
  47. models/progressive-v1/tex-8eb5e2b95996e3395d479ec7.bin +3 -0
  48. models/progressive-v1/tex-8ed4918e0b472598b2b0fefc.bin +3 -0
  49. models/progressive-v1/tex-918b22ae7e82d5e17e5166bb.bin +3 -0
  50. models/progressive-v1/tex-919c52e07425864831344ed8.bin +3 -0
README.md CHANGED
@@ -37,3 +37,11 @@ Task Format switches the entire construction/editing example, including instruct
37
  Homepage model work uses a shared two-job download/decode queue. Hidden/offscreen views release their request; stale results cannot replace the selected state. Card textures are limited to 512px and render resolution is bounded, while original model files and the full case explorer stay intact. Idle decoded assets are evicted; the 128 MiB cache target is not a hard limit on currently visible geometry. Downloads show progress, abort after 20 seconds without data or 90 seconds total, retry once, then expose an explicit Retry button. Context loss pauses drawing and restoration rebuilds the environment; failures expose recovery instead of an indefinite loading overlay.
38
 
39
  Queue tests: `node --test tests/scene-assets.test.mjs`. Browser QA includes public-model cold loads, quick scrolling, task/repair switching, desktop/mobile resize, forced HTTP failures, stalled downloads, retry and WebGL context loss.
 
 
 
 
 
 
 
 
 
37
  Homepage model work uses a shared two-job download/decode queue. Hidden/offscreen views release their request; stale results cannot replace the selected state. Card textures are limited to 512px and render resolution is bounded, while original model files and the full case explorer stay intact. Idle decoded assets are evicted; the 128 MiB cache target is not a hard limit on currently visible geometry. Downloads show progress, abort after 20 seconds without data or 90 seconds total, retry once, then expose an explicit Retry button. Context loss pauses drawing and restoration rebuilds the environment; failures expose recovery instead of an indefinite loading overlay.
38
 
39
  Queue tests: `node --test tests/scene-assets.test.mjs`. Browser QA includes public-model cold loads, quick scrolling, task/repair switching, desktop/mobile resize, forced HTTP failures, stalled downloads, retry and WebGL context loss.
40
+
41
+ ## Geometry-first scene cards
42
+
43
+ The homepage uses `data/progressive-scenes.json` to choose immutable packed derivatives for its 11 public scene/state variants. Geometry and meshopt bytes are unchanged; embedded images are moved into small packed batches. The original packed models remain available to the full case explorer. `assets/progressive-gltf.js` returns the geometry with neutral texture placeholders, then the visible card starts material work after its first rendered frame. Image updates preserve glTF samplers, color spaces, transforms and material parameters. At most two texture jobs run across the page; offscreen cards pause them, and a texture failure keeps the geometry visible with a separate retry control.
44
+
45
+ Three.js r160, GLTFLoader, MeshoptDecoder, Sky and OrbitControls are served from `assets/vendor/three-r160/`, retaining the official MIT license. Both page import maps use this pinned same-origin version. Original sources: https://github.com/mrdoob/three.js/tree/r160 .
46
+
47
+ The 11 original files total 73,341,132 bytes; their initial geometry payloads total 53,799,776 bytes (26.6% less before the first geometry frame). This is a payload comparison, not a promise of an equivalent wall-clock speedup. Card image batches add 19,821,674 bytes across variants before deduplication. Rebuild with Pillow and `python scripts/build_progressive.py`; generated texture batches keep images at a maximum of 512px for the cards. Original model/media/data files and the full-resolution case explorer are unchanged.
assets/buildingbench.css CHANGED
@@ -39,3 +39,5 @@ section,section.alt,section.dark{padding:34px 0;background:transparent;color:var
39
 
40
  /* The homepage lead uses the existing teaser, with no 3D download on first paint. */
41
  .featured.featured-video{height:auto;align-self:center;scroll-margin-top:88px}.featured-video video{display:block;width:100%;height:auto;aspect-ratio:16/9;object-fit:contain;background:#171816}.featured-video-caption{padding:12px 16px;display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:5px 12px}.featured-video-caption strong{font-size:13px;color:var(--ink)}.featured-video-caption span{font-size:11px;color:var(--muted)}
 
 
 
39
 
40
  /* The homepage lead uses the existing teaser, with no 3D download on first paint. */
41
  .featured.featured-video{height:auto;align-self:center;scroll-margin-top:88px}.featured-video video{display:block;width:100%;height:auto;aspect-ratio:16/9;object-fit:contain;background:#171816}.featured-video-caption{padding:12px 16px;display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:5px 12px}.featured-video-caption strong{font-size:13px;color:var(--ink)}.featured-video-caption span{font-size:11px;color:var(--muted)}
42
+
43
+ .scene-texture-status{position:absolute;top:10px;right:10px;z-index:2;background:#fffefbed;color:#53655b;border-radius:5px;padding:5px 8px;font:10px/1.4 Inter,sans-serif;pointer-events:none}.scene-texture-status button{display:block;pointer-events:auto;margin-top:4px;border:1px solid #b4c1b9;border-radius:4px;background:white;padding:4px 6px;font:inherit;color:#35687e;cursor:pointer}
assets/progressive-gltf.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {Texture,Source} from 'three';
2
+ import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
3
+ import {MeshoptDecoder} from 'three/addons/libs/meshopt_decoder.module.js';
4
+ import {fetchSceneBytes} from './scene-assets.js?v=20261002a';
5
+
6
+ const KEY=Uint8Array.from('5c2f9e71d3a84b06e1f7c29a38d05b64a7193ce8f2046db95e81c73a2df6904b'.match(/../g),h=>parseInt(h,16));
7
+ export function unpack(bytes){for(let i=0;i<bytes.length;i++)bytes[i]^=KEY[i&31]^(((i>>>5)*131)&255);return bytes;}
8
+ let manifestPromise;
9
+ function manifest(){return manifestPromise??=fetch(new URL('../data/progressive-scenes.json?v=1',import.meta.url)).then(r=>r.ok?r.json():{}).catch(()=>({}));}
10
+
11
+ // Two texture jobs across the entire page, separate from the geometry queue.
12
+ let running=0;const waiting=[];
13
+ function textureJob(work,signal){return new Promise((resolve,reject)=>{waiting.push({work,signal,resolve,reject});pump();});}
14
+ function pump(){while(running<2&&waiting.length){const job=waiting.shift();if(job.signal.aborted){job.reject(new DOMException('Aborted','AbortError'));continue;}running++;Promise.resolve().then(job.work).then(job.resolve,job.reject).finally(()=>{running--;pump();});}}
15
+
16
+ class Materials {
17
+ constructor(parser,maxSize){this.parser=parser;this.maxSize=maxSize;this.sources=new Map();this.bundles=new Map();this.failedBundles=new Set();this.active=false;this.disposed=false;this.epoch=0;this.onUpdate=()=>{};}
18
+ placeholder(index){
19
+ let entry=this.sources.get(index);
20
+ if(!entry){const canvas=document.createElement('canvas');canvas.width=canvas.height=1;const ctx=canvas.getContext('2d');ctx.fillStyle='#ffffff';ctx.fillRect(0,0,1,1);const texture=new Texture(canvas);texture.needsUpdate=true;entry={index,texture,state:'pending',refs:new Set([texture])};this.sources.set(index,entry);}
21
+ return Promise.resolve(entry.texture.clone());
22
+ }
23
+ bind(root){
24
+ root.traverse(node=>{for(const material of [].concat(node.material||[])){for(const [name,texture] of Object.entries(material)){if(!texture?.isTexture)continue;const entry=[...this.sources.values()].find(e=>e.texture.source===texture.source);if(!entry)continue;entry.refs.add(texture);const colors={map:'#b0b0b0',normalMap:'#8080ff',clearcoatNormalMap:'#8080ff',emissiveMap:'#000000',roughnessMap:'#ffff00',metalnessMap:'#ffff00',transmissionMap:'#000000'};if(colors[name]){const ctx=texture.image.getContext('2d');ctx.fillStyle=colors[name];ctx.fillRect(0,0,1,1);texture.needsUpdate=true;}}}});
25
+ }
26
+ get status(){const entries=[...this.sources.values()];return {loaded:entries.filter(e=>e.state==='loaded').length,failed:entries.filter(e=>e.state==='failed').length,total:entries.length,active:this.active};}
27
+ async imageBytes(entry,signal){
28
+ const image=this.parser.json.images[entry.index],part=image.extras?.c4sTexture;
29
+ if(part){
30
+ if(!this.bundles.has(part.bundle)){const promise=(async()=>{for(let attempt=0;attempt<2;attempt++){try{return unpack(await fetchSceneBytes(new URL(part.bundle,this.parser.options.path),signal,()=>{}));}catch(error){if(signal.aborted||attempt===1)throw error;}}})().catch(error=>{if(signal.aborted)this.bundles.delete(part.bundle);else this.failedBundles.add(part.bundle);throw error;});this.bundles.set(part.bundle,promise);}
31
+ const bytes=await this.bundles.get(part.bundle);return new Blob([bytes.subarray(part.offset,part.offset+part.length)],{type:image.mimeType});
32
+ }
33
+ if(image.bufferView!==undefined)return new Blob([await this.parser.getDependency('bufferView',image.bufferView)],{type:image.mimeType});
34
+ const bytes=await fetchSceneBytes(new URL(image.uri,this.parser.options.path),signal,()=>{});return new Blob([bytes],{type:image.mimeType});
35
+ }
36
+ async load(entry,signal){
37
+ let bitmap;
38
+ for(let attempt=0;attempt<2;attempt++){
39
+ try{const blob=await this.imageBytes(entry,signal);signal.throwIfAborted();bitmap=await createImageBitmap(blob,{colorSpaceConversion:'none',premultiplyAlpha:'none'});break;}
40
+ catch(error){if(signal.aborted||attempt===1)throw error;}
41
+ }
42
+ if(signal.aborted){bitmap.close();signal.throwIfAborted();}
43
+ if(this.maxSize&&Math.max(bitmap.width,bitmap.height)>this.maxSize){const scale=this.maxSize/Math.max(bitmap.width,bitmap.height);const small=await createImageBitmap(bitmap,{resizeWidth:Math.max(1,Math.round(bitmap.width*scale)),resizeHeight:Math.max(1,Math.round(bitmap.height*scale)),resizeQuality:'high',colorSpaceConversion:'none',premultiplyAlpha:'none'});bitmap.close();bitmap=small;}
44
+ if(signal.aborted||this.disposed){bitmap.close();throw new DOMException('Aborted','AbortError');}
45
+ // WebGL2 texture storage cannot grow from a 1px placeholder in place.
46
+ // Retain each glTF texture object's sampler/transform, but allocate fresh storage.
47
+ const source=new Source(bitmap);for(const texture of entry.refs){texture.dispose();texture.source=source;texture.needsUpdate=true;}entry.state='loaded';this.onUpdate(this.status);
48
+ // Yield between uploads so orbiting and the first geometry frame remain responsive.
49
+ await new Promise(resolve=>setTimeout(resolve,0));
50
+ }
51
+ start(onUpdate){
52
+ this.onUpdate=onUpdate||this.onUpdate;
53
+ if(this.active||this.disposed||![...this.sources.values()].some(e=>e.state==='pending'))return;
54
+ this.active=true;const epoch=++this.epoch;this.controller=new AbortController();const signal=this.controller.signal;
55
+ const worker=async()=>{while(this.active&&!signal.aborted){const entry=[...this.sources.values()].find(e=>e.state==='pending');if(!entry)break;entry.state='loading';try{await textureJob(()=>this.load(entry,signal),signal);}catch(error){entry.state=signal.aborted?'pending':'failed';if(!signal.aborted){entry.error=error;this.onUpdate(this.status);}}}};
56
+ Promise.all([worker(),worker()]).finally(()=>{if(this.epoch===epoch){this.active=false;this.onUpdate(this.status);}});
57
+ }
58
+ pause(){this.active=false;this.epoch++;this.controller?.abort();}
59
+ retry(){for(const name of this.failedBundles)this.bundles.delete(name);this.failedBundles.clear();for(const e of this.sources.values())if(e.state==='failed')e.state='pending';this.start();}
60
+ dispose(){this.disposed=true;this.pause();this.bundles.clear();this.sources.clear();this.parser=null;}
61
+ }
62
+
63
+ export async function loadProgressive(url,{signal,onProgress=()=>{},derivative=true,maxTextureSize=512}={}){
64
+ const original=new URL(url,document.baseURI),name=original.pathname.split('/').pop();
65
+ const entry=derivative?(await manifest())[name]:null;
66
+ const geometry=entry?new URL('../'+entry.geometry,import.meta.url):original;
67
+ onProgress('downloading',{received:0,total:0});
68
+ let bytes;
69
+ try{bytes=await fetchSceneBytes(geometry.href,signal,p=>onProgress('downloading',p));}
70
+ catch(error){if(!entry||signal.aborted)throw error;bytes=await fetchSceneBytes(original.href,signal,p=>onProgress('downloading',p));}
71
+ unpack(bytes);signal.throwIfAborted();onProgress('decoding');let materials;
72
+ const loader=new GLTFLoader().setMeshoptDecoder(MeshoptDecoder);
73
+ loader.register(parser=>{materials=new Materials(parser,maxTextureSize);parser.loadImageSource=index=>materials.placeholder(index);return {name:'C4S_progressive_materials'};});
74
+ const gltf=await new Promise((resolve,reject)=>{
75
+ let settled=false;
76
+ const fail=error=>{if(settled)return;settled=true;clearTimeout(timer);signal.removeEventListener('abort',abort);materials?.dispose();reject(error);};
77
+ const abort=()=>fail(new DOMException('Aborted','AbortError'));
78
+ const timer=setTimeout(()=>fail(new Error('Scene geometry decoding timed out.')),30000);
79
+ signal.addEventListener('abort',abort,{once:true});
80
+ loader.parseAsync(bytes.buffer,new URL('.',geometry).href).then(result=>{
81
+ if(settled||signal.aborted){result.scene.traverse(n=>{n.geometry?.dispose();for(const m of [].concat(n.material||[]))m.dispose();});materials?.dispose();return;}
82
+ settled=true;clearTimeout(timer);signal.removeEventListener('abort',abort);resolve(result);
83
+ },fail);
84
+ });
85
+ materials.bind(gltf.scene);gltf.materials=materials;
86
+ return gltf;
87
+ }
assets/scene-assets.js CHANGED
@@ -8,7 +8,7 @@ export class AssetQueue {
8
  }
9
  const user={listener,priority};e.users.add(user);e.last=Date.now();queueMicrotask(()=>{if(e.users.has(user))listener(e);this.pump();});this.changed();
10
  let released=false;
11
- return {entry:e,release:()=>{if(released)return;released=true;e.users.delete(user);e.last=Date.now();if(!e.users.size&&!e.asset){e.state='cancelled';e.controller.abort();if(this.entries.get(key)===e)this.entries.delete(key);}this.evict();this.pump();this.changed();}};
12
  }
13
  notify(e,state,detail={}){if(e.controller.signal.aborted)return;Object.assign(e,detail,{state});for(const u of e.users)u.listener(e);this.changed();}
14
  pump(){
 
8
  }
9
  const user={listener,priority};e.users.add(user);e.last=Date.now();queueMicrotask(()=>{if(e.users.has(user))listener(e);this.pump();});this.changed();
10
  let released=false;
11
+ return {entry:e,release:()=>{if(released)return;released=true;e.users.delete(user);e.last=Date.now();if(!e.users.size&&e.asset)e.asset.materials?.pause();if(!e.users.size&&!e.asset){e.state='cancelled';e.controller.abort();if(this.entries.get(key)===e)this.entries.delete(key);}this.evict();this.pump();this.changed();}};
12
  }
13
  notify(e,state,detail={}){if(e.controller.signal.aborted)return;Object.assign(e,detail,{state});for(const u of e.users)u.listener(e);this.changed();}
14
  pump(){
assets/scene-cards.js CHANGED
@@ -1,7 +1,6 @@
1
  import * as THREE from "three";
2
- import {AssetQueue,fetchSceneBytes} from "./scene-assets.js?v=20261001c";
3
- import {GLTFLoader} from "three/addons/loaders/GLTFLoader.js";
4
- import {MeshoptDecoder} from "three/addons/libs/meshopt_decoder.module.js";
5
  import {Sky} from "three/addons/objects/Sky.js";
6
 
7
  "use strict";
@@ -10,9 +9,8 @@ import {Sky} from "three/addons/objects/Sky.js";
10
  // procedural viewer had (rotationX / rotationY / zoom / paused / group /
11
  // draw(dt) / destroy()), so the page code above it did not have to change.
12
  //
13
- // Existing public Code4Scene packed GLBs. The decoder preserves their original geometry and textures.
14
  (function () {
15
- const ADD = {GLTFLoader};
16
  const activeViewers = new Set();
17
  window.activeViewers = activeViewers;
18
  let lastFrame = performance.now();
@@ -87,9 +85,8 @@ import {Sky} from "three/addons/objects/Sky.js";
87
  asset.scene.add(RIG_GROUP);
88
  }
89
 
90
- const PACK_KEY=Uint8Array.from("5c2f9e71d3a84b06e1f7c29a38d05b64a7193ce8f2046db95e81c73a2df6904b".match(/../g),h=>parseInt(h,16));
91
  function disposeAsset(asset){
92
- if(!asset)return;asset.scene.remove(RIG_GROUP);const geometries=new Set(),materials=new Set(),textures=new Set();
93
  asset.scene.traverse(n=>{if(n.geometry)geometries.add(n.geometry);for(const m of [].concat(n.material||[])){materials.add(m);for(const v of Object.values(m))if(v?.isTexture)textures.add(v);}});
94
  geometries.forEach(g=>g.dispose());textures.forEach(t=>{t.dispose();t.source?.data?.close?.();});materials.forEach(m=>m.dispose());
95
  }
@@ -99,30 +96,12 @@ import {Sky} from "three/addons/objects/Sky.js";
99
  return {geometry,textures,maxTexture,bytes:geometry+textures};
100
  }
101
 
102
- // Cards never need full-resolution scene textures. Bound their decoded/GPU footprint,
103
- // while the original public files and full case inspector retain their original texture resolution.
104
- async function cardTextures(root,signal){
105
- const images=new Map(),textures=new Set();root.traverse(n=>{for(const m of [].concat(n.material||[]))for(const t of Object.values(m))if(t?.isTexture)textures.add(t);});
106
- for(const t of textures){const image=t.source?.data;if(!image||Math.max(image.width||0,image.height||0)<=512)continue;
107
- if(signal.aborted)throw new DOMException('Scene no longer visible','AbortError');
108
- if(!images.has(image)){const scale=512/Math.max(image.width,image.height);images.set(image,await createImageBitmap(image,{resizeWidth:Math.max(1,Math.round(image.width*scale)),resizeHeight:Math.max(1,Math.round(image.height*scale)),resizeQuality:'high',colorSpaceConversion:'none',premultiplyAlpha:'none'}));}
109
- t.image=images.get(image);t.needsUpdate=true;
110
- }
111
- for(const original of images.keys())original.close?.();
112
- }
113
  const assets=new AssetQueue({
114
  maxConcurrent:2,maxIdle:1,maxBytes:128*1024*1024,dispose:disposeAsset,
115
  onChange:s=>{document.documentElement.dataset.sceneJobs=String(s.running);document.documentElement.dataset.scenePeakJobs=String(s.peak);document.documentElement.dataset.sceneCached=String(s.cached);document.documentElement.dataset.sceneBytes=String(Math.round(s.bytes));},
116
  load:async(key,signal,progress)=>{
117
- progress('downloading',{received:0,total:0});const bytes=await fetchSceneBytes(key,signal,p=>progress('downloading',p));
118
- for(let i=0;i<bytes.length;i++)bytes[i]^=PACK_KEY[i&31]^(((i>>>5)*131)&255);
119
- if(new DataView(bytes.buffer).getUint32(0,true)!==0x46546c67)throw new Error('The scene file is invalid.');
120
- if(signal.aborted)throw new DOMException('Scene no longer visible','AbortError');
121
- progress('decoding');const gltf=await new Promise((resolve,reject)=>{
122
- let done=false;const timer=setTimeout(()=>{done=true;reject(new Error('The scene could not be decoded in time.'));},30000);
123
- new GLTFLoader().setMeshoptDecoder(MeshoptDecoder).parse(bytes.buffer,'',g=>{clearTimeout(timer);if(done||signal.aborted){const fake={scene:g.scene,root:g.scene};disposeAsset(fake);if(!done)reject(new DOMException('Scene no longer visible','AbortError'));return;}done=true;resolve(g);},error=>{clearTimeout(timer);if(!done){done=true;reject(error);}});
124
- });
125
- try{await cardTextures(gltf.scene,signal);}catch(error){disposeAsset({scene:gltf.scene,root:gltf.scene});throw error;}
126
  const root = gltf.scene;
127
  root.traverse(node => { if (node.isMesh) { node.castShadow = true; node.receiveShadow = true; } });
128
  const box = new THREE.Box3(), pts=[];
@@ -139,7 +118,7 @@ import {Sky} from "three/addons/objects/Sky.js";
139
  scene.background = new THREE.Color(0xe6edf0); scene.add(root);
140
  const task=window.SITE.cases.find(c=>c.track==='i2s'&&(c.input_glb===key||c.gt_glb===key||c.agents.some(a=>a.glb===key)));
141
  if(task){root.updateMatrixWorld(true);root.traverse(o=>{const duplicate=(task.duplicates||[]).includes(o.name);if(!duplicate&&!(task.targets||[]).includes(o.name))return;const b=new THREE.Box3().setFromObject(o);if(b.isEmpty())return;const h=new THREE.Box3Helper(b.expandByScalar(.08),duplicate?0xff6b5e:0x5ee08a);h.material.depthTest=false;h.material.transparent=true;h.renderOrder=999;scene.add(h);});}
142
- return {key,root,scene,box,centre,corners,radius:Math.max(size.length()/2,1),floor:box.min.y,...assetProfile(root)};
143
  }
144
  });
145
 
@@ -244,6 +223,10 @@ import {Sky} from "three/addons/objects/Sky.js";
244
  if (rect.width < 2 || rect.height < 2 || document.hidden) return;
245
  if (rect.bottom < 0 || rect.top > window.innerHeight) return;
246
  this.resize();
 
 
 
 
247
  if(this.hasDrawn&&!this.dirty&&(this.paused||reduced()))return;
248
  if (this.group) { this.rotationX = this.group.rotationX; this.rotationY = this.group.rotationY; this.zoom = this.group.zoom; }
249
  if (!this.group && !this.paused && !this.dragging && !reduced()) this.rotationY += this.speed * dt;
@@ -261,7 +244,15 @@ import {Sky} from "three/addons/objects/Sky.js";
261
  g.render(this.asset.scene, this.camera);
262
  ctx.clearRect(0, 0, W, H);
263
  ctx.drawImage(g.domElement, 0, 0, W, H, 0, 0, W, H);
264
- this.hasDrawn = true; this.dirty=false;this.element.dataset.statePhase="ready"; this.canvas.dataset.rendered="true"; this.canvas.parentElement.querySelector(".scene-loading")?.remove();
 
 
 
 
 
 
 
 
265
  }
266
  destroy() {
267
  this.destroyed = true; activeViewers.delete(this);
 
1
  import * as THREE from "three";
2
+ import {AssetQueue,fetchSceneBytes} from "./scene-assets.js?v=20261002a";
3
+ import {loadProgressive} from "./progressive-gltf.js?v=20261002a";
 
4
  import {Sky} from "three/addons/objects/Sky.js";
5
 
6
  "use strict";
 
9
  // procedural viewer had (rotationX / rotationY / zoom / paused / group /
10
  // draw(dt) / destroy()), so the page code above it did not have to change.
11
  //
12
+ // Existing public Code4Scene geometry with progressively loaded card textures.
13
  (function () {
 
14
  const activeViewers = new Set();
15
  window.activeViewers = activeViewers;
16
  let lastFrame = performance.now();
 
85
  asset.scene.add(RIG_GROUP);
86
  }
87
 
 
88
  function disposeAsset(asset){
89
+ if(!asset)return;asset.materials?.dispose();asset.scene.remove(RIG_GROUP);const geometries=new Set(),materials=new Set(),textures=new Set();
90
  asset.scene.traverse(n=>{if(n.geometry)geometries.add(n.geometry);for(const m of [].concat(n.material||[])){materials.add(m);for(const v of Object.values(m))if(v?.isTexture)textures.add(v);}});
91
  geometries.forEach(g=>g.dispose());textures.forEach(t=>{t.dispose();t.source?.data?.close?.();});materials.forEach(m=>m.dispose());
92
  }
 
96
  return {geometry,textures,maxTexture,bytes:geometry+textures};
97
  }
98
 
 
 
 
 
 
 
 
 
 
 
 
99
  const assets=new AssetQueue({
100
  maxConcurrent:2,maxIdle:1,maxBytes:128*1024*1024,dispose:disposeAsset,
101
  onChange:s=>{document.documentElement.dataset.sceneJobs=String(s.running);document.documentElement.dataset.scenePeakJobs=String(s.peak);document.documentElement.dataset.sceneCached=String(s.cached);document.documentElement.dataset.sceneBytes=String(Math.round(s.bytes));},
102
  load:async(key,signal,progress)=>{
103
+ const gltf=await loadProgressive(key,{signal,onProgress:progress});
104
+ if(signal.aborted){disposeAsset({scene:gltf.scene,materials:gltf.materials});signal.throwIfAborted();}
 
 
 
 
 
 
 
105
  const root = gltf.scene;
106
  root.traverse(node => { if (node.isMesh) { node.castShadow = true; node.receiveShadow = true; } });
107
  const box = new THREE.Box3(), pts=[];
 
118
  scene.background = new THREE.Color(0xe6edf0); scene.add(root);
119
  const task=window.SITE.cases.find(c=>c.track==='i2s'&&(c.input_glb===key||c.gt_glb===key||c.agents.some(a=>a.glb===key)));
120
  if(task){root.updateMatrixWorld(true);root.traverse(o=>{const duplicate=(task.duplicates||[]).includes(o.name);if(!duplicate&&!(task.targets||[]).includes(o.name))return;const b=new THREE.Box3().setFromObject(o);if(b.isEmpty())return;const h=new THREE.Box3Helper(b.expandByScalar(.08),duplicate?0xff6b5e:0x5ee08a);h.material.depthTest=false;h.material.transparent=true;h.renderOrder=999;scene.add(h);});}
121
+ return {key,root,scene,materials:gltf.materials,box,centre,corners,radius:Math.max(size.length()/2,1),floor:box.min.y,...assetProfile(root)};
122
  }
123
  });
124
 
 
223
  if (rect.width < 2 || rect.height < 2 || document.hidden) return;
224
  if (rect.bottom < 0 || rect.top > window.innerHeight) return;
225
  this.resize();
226
+ if(this.asset&&this.hasDrawn){const asset=this.asset;asset.materials?.start(state=>{
227
+ for(const v of activeViewers)if(v.asset===asset){v.dirty=true;v.textureStatus();}
228
+ if(state.loaded===state.total||state.loaded%32===0){const e=assets.entries.get(asset.key);if(e){Object.assign(asset,assetProfile(asset.root));e.bytes=asset.bytes;assets.changed();for(const v of activeViewers)if(v.asset===asset){v.element.dataset.sceneMb=(asset.bytes/1048576).toFixed(1);v.element.dataset.sceneTextureMb=(asset.textures/1048576).toFixed(1);v.element.dataset.sceneMaxTexture=String(asset.maxTexture);}}}
229
+ });}
230
  if(this.hasDrawn&&!this.dirty&&(this.paused||reduced()))return;
231
  if (this.group) { this.rotationX = this.group.rotationX; this.rotationY = this.group.rotationY; this.zoom = this.group.zoom; }
232
  if (!this.group && !this.paused && !this.dragging && !reduced()) this.rotationY += this.speed * dt;
 
244
  g.render(this.asset.scene, this.camera);
245
  ctx.clearRect(0, 0, W, H);
246
  ctx.drawImage(g.domElement, 0, 0, W, H, 0, 0, W, H);
247
+ this.hasDrawn = true; this.dirty=false;this.element.dataset.statePhase="ready"; this.canvas.dataset.rendered="true"; this.canvas.parentElement.querySelector(".scene-loading")?.remove();this.textureStatus();
248
+ }
249
+ textureStatus(){
250
+ if(!this.asset?.materials)return;const s=this.asset.materials.status;this.element.dataset.texturesLoaded=String(s.loaded);this.element.dataset.texturesTotal=String(s.total);
251
+ let badge=this.element.querySelector('.scene-texture-status');
252
+ if(s.loaded===s.total){badge?.remove();return;}
253
+ if(!badge){badge=document.createElement('div');badge.className='scene-texture-status';badge.setAttribute('role','status');this.element.append(badge);}
254
+ const signature=s.loaded+'/'+s.total+'/'+s.failed;if(badge.dataset.signature===signature)return;badge.dataset.signature=signature;badge.replaceChildren(document.createTextNode('Textures '+s.loaded+' / '+s.total));
255
+ if(s.failed){const retry=document.createElement('button');retry.textContent='Retry textures';retry.onclick=()=>{this.asset?.materials.retry();this.dirty=true;};badge.append(retry);}
256
  }
257
  destroy() {
258
  this.destroyed = true; activeViewers.delete(this);
assets/vendor/three-r160/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License
2
+
3
+ Copyright © 2010-2023 three.js authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
assets/vendor/three-r160/addons/controls/OrbitControls.js ADDED
@@ -0,0 +1,1417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ EventDispatcher,
3
+ MOUSE,
4
+ Quaternion,
5
+ Spherical,
6
+ TOUCH,
7
+ Vector2,
8
+ Vector3,
9
+ Plane,
10
+ Ray,
11
+ MathUtils
12
+ } from 'three';
13
+
14
+ // OrbitControls performs orbiting, dollying (zooming), and panning.
15
+ // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
16
+ //
17
+ // Orbit - left mouse / touch: one-finger move
18
+ // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
19
+ // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
20
+
21
+ const _changeEvent = { type: 'change' };
22
+ const _startEvent = { type: 'start' };
23
+ const _endEvent = { type: 'end' };
24
+ const _ray = new Ray();
25
+ const _plane = new Plane();
26
+ const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
27
+
28
+ class OrbitControls extends EventDispatcher {
29
+
30
+ constructor( object, domElement ) {
31
+
32
+ super();
33
+
34
+ this.object = object;
35
+ this.domElement = domElement;
36
+ this.domElement.style.touchAction = 'none'; // disable touch scroll
37
+
38
+ // Set to false to disable this control
39
+ this.enabled = true;
40
+
41
+ // "target" sets the location of focus, where the object orbits around
42
+ this.target = new Vector3();
43
+
44
+ // Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect
45
+ this.cursor = new Vector3();
46
+
47
+ // How far you can dolly in and out ( PerspectiveCamera only )
48
+ this.minDistance = 0;
49
+ this.maxDistance = Infinity;
50
+
51
+ // How far you can zoom in and out ( OrthographicCamera only )
52
+ this.minZoom = 0;
53
+ this.maxZoom = Infinity;
54
+
55
+ // Limit camera target within a spherical area around the cursor
56
+ this.minTargetRadius = 0;
57
+ this.maxTargetRadius = Infinity;
58
+
59
+ // How far you can orbit vertically, upper and lower limits.
60
+ // Range is 0 to Math.PI radians.
61
+ this.minPolarAngle = 0; // radians
62
+ this.maxPolarAngle = Math.PI; // radians
63
+
64
+ // How far you can orbit horizontally, upper and lower limits.
65
+ // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
66
+ this.minAzimuthAngle = - Infinity; // radians
67
+ this.maxAzimuthAngle = Infinity; // radians
68
+
69
+ // Set to true to enable damping (inertia)
70
+ // If damping is enabled, you must call controls.update() in your animation loop
71
+ this.enableDamping = false;
72
+ this.dampingFactor = 0.05;
73
+
74
+ // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
75
+ // Set to false to disable zooming
76
+ this.enableZoom = true;
77
+ this.zoomSpeed = 1.0;
78
+
79
+ // Set to false to disable rotating
80
+ this.enableRotate = true;
81
+ this.rotateSpeed = 1.0;
82
+
83
+ // Set to false to disable panning
84
+ this.enablePan = true;
85
+ this.panSpeed = 1.0;
86
+ this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
87
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
88
+ this.zoomToCursor = false;
89
+
90
+ // Set to true to automatically rotate around the target
91
+ // If auto-rotate is enabled, you must call controls.update() in your animation loop
92
+ this.autoRotate = false;
93
+ this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
94
+
95
+ // The four arrow keys
96
+ this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
97
+
98
+ // Mouse buttons
99
+ this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
100
+
101
+ // Touch fingers
102
+ this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
103
+
104
+ // for reset
105
+ this.target0 = this.target.clone();
106
+ this.position0 = this.object.position.clone();
107
+ this.zoom0 = this.object.zoom;
108
+
109
+ // the target DOM element for key events
110
+ this._domElementKeyEvents = null;
111
+
112
+ //
113
+ // public methods
114
+ //
115
+
116
+ this.getPolarAngle = function () {
117
+
118
+ return spherical.phi;
119
+
120
+ };
121
+
122
+ this.getAzimuthalAngle = function () {
123
+
124
+ return spherical.theta;
125
+
126
+ };
127
+
128
+ this.getDistance = function () {
129
+
130
+ return this.object.position.distanceTo( this.target );
131
+
132
+ };
133
+
134
+ this.listenToKeyEvents = function ( domElement ) {
135
+
136
+ domElement.addEventListener( 'keydown', onKeyDown );
137
+ this._domElementKeyEvents = domElement;
138
+
139
+ };
140
+
141
+ this.stopListenToKeyEvents = function () {
142
+
143
+ this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
144
+ this._domElementKeyEvents = null;
145
+
146
+ };
147
+
148
+ this.saveState = function () {
149
+
150
+ scope.target0.copy( scope.target );
151
+ scope.position0.copy( scope.object.position );
152
+ scope.zoom0 = scope.object.zoom;
153
+
154
+ };
155
+
156
+ this.reset = function () {
157
+
158
+ scope.target.copy( scope.target0 );
159
+ scope.object.position.copy( scope.position0 );
160
+ scope.object.zoom = scope.zoom0;
161
+
162
+ scope.object.updateProjectionMatrix();
163
+ scope.dispatchEvent( _changeEvent );
164
+
165
+ scope.update();
166
+
167
+ state = STATE.NONE;
168
+
169
+ };
170
+
171
+ // this method is exposed, but perhaps it would be better if we can make it private...
172
+ this.update = function () {
173
+
174
+ const offset = new Vector3();
175
+
176
+ // so camera.up is the orbit axis
177
+ const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
178
+ const quatInverse = quat.clone().invert();
179
+
180
+ const lastPosition = new Vector3();
181
+ const lastQuaternion = new Quaternion();
182
+ const lastTargetPosition = new Vector3();
183
+
184
+ const twoPI = 2 * Math.PI;
185
+
186
+ return function update( deltaTime = null ) {
187
+
188
+ const position = scope.object.position;
189
+
190
+ offset.copy( position ).sub( scope.target );
191
+
192
+ // rotate offset to "y-axis-is-up" space
193
+ offset.applyQuaternion( quat );
194
+
195
+ // angle from z-axis around y-axis
196
+ spherical.setFromVector3( offset );
197
+
198
+ if ( scope.autoRotate && state === STATE.NONE ) {
199
+
200
+ rotateLeft( getAutoRotationAngle( deltaTime ) );
201
+
202
+ }
203
+
204
+ if ( scope.enableDamping ) {
205
+
206
+ spherical.theta += sphericalDelta.theta * scope.dampingFactor;
207
+ spherical.phi += sphericalDelta.phi * scope.dampingFactor;
208
+
209
+ } else {
210
+
211
+ spherical.theta += sphericalDelta.theta;
212
+ spherical.phi += sphericalDelta.phi;
213
+
214
+ }
215
+
216
+ // restrict theta to be between desired limits
217
+
218
+ let min = scope.minAzimuthAngle;
219
+ let max = scope.maxAzimuthAngle;
220
+
221
+ if ( isFinite( min ) && isFinite( max ) ) {
222
+
223
+ if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
224
+
225
+ if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
226
+
227
+ if ( min <= max ) {
228
+
229
+ spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
230
+
231
+ } else {
232
+
233
+ spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ?
234
+ Math.max( min, spherical.theta ) :
235
+ Math.min( max, spherical.theta );
236
+
237
+ }
238
+
239
+ }
240
+
241
+ // restrict phi to be between desired limits
242
+ spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
243
+
244
+ spherical.makeSafe();
245
+
246
+
247
+ // move target to panned location
248
+
249
+ if ( scope.enableDamping === true ) {
250
+
251
+ scope.target.addScaledVector( panOffset, scope.dampingFactor );
252
+
253
+ } else {
254
+
255
+ scope.target.add( panOffset );
256
+
257
+ }
258
+
259
+ // Limit the target distance from the cursor to create a sphere around the center of interest
260
+ scope.target.sub( scope.cursor );
261
+ scope.target.clampLength( scope.minTargetRadius, scope.maxTargetRadius );
262
+ scope.target.add( scope.cursor );
263
+
264
+ // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
265
+ // we adjust zoom later in these cases
266
+ if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) {
267
+
268
+ spherical.radius = clampDistance( spherical.radius );
269
+
270
+ } else {
271
+
272
+ spherical.radius = clampDistance( spherical.radius * scale );
273
+
274
+ }
275
+
276
+ offset.setFromSpherical( spherical );
277
+
278
+ // rotate offset back to "camera-up-vector-is-up" space
279
+ offset.applyQuaternion( quatInverse );
280
+
281
+ position.copy( scope.target ).add( offset );
282
+
283
+ scope.object.lookAt( scope.target );
284
+
285
+ if ( scope.enableDamping === true ) {
286
+
287
+ sphericalDelta.theta *= ( 1 - scope.dampingFactor );
288
+ sphericalDelta.phi *= ( 1 - scope.dampingFactor );
289
+
290
+ panOffset.multiplyScalar( 1 - scope.dampingFactor );
291
+
292
+ } else {
293
+
294
+ sphericalDelta.set( 0, 0, 0 );
295
+
296
+ panOffset.set( 0, 0, 0 );
297
+
298
+ }
299
+
300
+ // adjust camera position
301
+ let zoomChanged = false;
302
+ if ( scope.zoomToCursor && performCursorZoom ) {
303
+
304
+ let newRadius = null;
305
+ if ( scope.object.isPerspectiveCamera ) {
306
+
307
+ // move the camera down the pointer ray
308
+ // this method avoids floating point error
309
+ const prevRadius = offset.length();
310
+ newRadius = clampDistance( prevRadius * scale );
311
+
312
+ const radiusDelta = prevRadius - newRadius;
313
+ scope.object.position.addScaledVector( dollyDirection, radiusDelta );
314
+ scope.object.updateMatrixWorld();
315
+
316
+ } else if ( scope.object.isOrthographicCamera ) {
317
+
318
+ // adjust the ortho camera position based on zoom changes
319
+ const mouseBefore = new Vector3( mouse.x, mouse.y, 0 );
320
+ mouseBefore.unproject( scope.object );
321
+
322
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
323
+ scope.object.updateProjectionMatrix();
324
+ zoomChanged = true;
325
+
326
+ const mouseAfter = new Vector3( mouse.x, mouse.y, 0 );
327
+ mouseAfter.unproject( scope.object );
328
+
329
+ scope.object.position.sub( mouseAfter ).add( mouseBefore );
330
+ scope.object.updateMatrixWorld();
331
+
332
+ newRadius = offset.length();
333
+
334
+ } else {
335
+
336
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
337
+ scope.zoomToCursor = false;
338
+
339
+ }
340
+
341
+ // handle the placement of the target
342
+ if ( newRadius !== null ) {
343
+
344
+ if ( this.screenSpacePanning ) {
345
+
346
+ // position the orbit target in front of the new camera position
347
+ scope.target.set( 0, 0, - 1 )
348
+ .transformDirection( scope.object.matrix )
349
+ .multiplyScalar( newRadius )
350
+ .add( scope.object.position );
351
+
352
+ } else {
353
+
354
+ // get the ray and translation plane to compute target
355
+ _ray.origin.copy( scope.object.position );
356
+ _ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix );
357
+
358
+ // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
359
+ // extremely large values
360
+ if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) {
361
+
362
+ object.lookAt( scope.target );
363
+
364
+ } else {
365
+
366
+ _plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target );
367
+ _ray.intersectPlane( _plane, scope.target );
368
+
369
+ }
370
+
371
+ }
372
+
373
+ }
374
+
375
+ } else if ( scope.object.isOrthographicCamera ) {
376
+
377
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
378
+ scope.object.updateProjectionMatrix();
379
+ zoomChanged = true;
380
+
381
+ }
382
+
383
+ scale = 1;
384
+ performCursorZoom = false;
385
+
386
+ // update condition is:
387
+ // min(camera displacement, camera rotation in radians)^2 > EPS
388
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
389
+
390
+ if ( zoomChanged ||
391
+ lastPosition.distanceToSquared( scope.object.position ) > EPS ||
392
+ 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ||
393
+ lastTargetPosition.distanceToSquared( scope.target ) > 0 ) {
394
+
395
+ scope.dispatchEvent( _changeEvent );
396
+
397
+ lastPosition.copy( scope.object.position );
398
+ lastQuaternion.copy( scope.object.quaternion );
399
+ lastTargetPosition.copy( scope.target );
400
+
401
+ return true;
402
+
403
+ }
404
+
405
+ return false;
406
+
407
+ };
408
+
409
+ }();
410
+
411
+ this.dispose = function () {
412
+
413
+ scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
414
+
415
+ scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
416
+ scope.domElement.removeEventListener( 'pointercancel', onPointerUp );
417
+ scope.domElement.removeEventListener( 'wheel', onMouseWheel );
418
+
419
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
420
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
421
+
422
+
423
+ if ( scope._domElementKeyEvents !== null ) {
424
+
425
+ scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
426
+ scope._domElementKeyEvents = null;
427
+
428
+ }
429
+
430
+ //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
431
+
432
+ };
433
+
434
+ //
435
+ // internals
436
+ //
437
+
438
+ const scope = this;
439
+
440
+ const STATE = {
441
+ NONE: - 1,
442
+ ROTATE: 0,
443
+ DOLLY: 1,
444
+ PAN: 2,
445
+ TOUCH_ROTATE: 3,
446
+ TOUCH_PAN: 4,
447
+ TOUCH_DOLLY_PAN: 5,
448
+ TOUCH_DOLLY_ROTATE: 6
449
+ };
450
+
451
+ let state = STATE.NONE;
452
+
453
+ const EPS = 0.000001;
454
+
455
+ // current position in spherical coordinates
456
+ const spherical = new Spherical();
457
+ const sphericalDelta = new Spherical();
458
+
459
+ let scale = 1;
460
+ const panOffset = new Vector3();
461
+
462
+ const rotateStart = new Vector2();
463
+ const rotateEnd = new Vector2();
464
+ const rotateDelta = new Vector2();
465
+
466
+ const panStart = new Vector2();
467
+ const panEnd = new Vector2();
468
+ const panDelta = new Vector2();
469
+
470
+ const dollyStart = new Vector2();
471
+ const dollyEnd = new Vector2();
472
+ const dollyDelta = new Vector2();
473
+
474
+ const dollyDirection = new Vector3();
475
+ const mouse = new Vector2();
476
+ let performCursorZoom = false;
477
+
478
+ const pointers = [];
479
+ const pointerPositions = {};
480
+
481
+ function getAutoRotationAngle( deltaTime ) {
482
+
483
+ if ( deltaTime !== null ) {
484
+
485
+ return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime;
486
+
487
+ } else {
488
+
489
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
490
+
491
+ }
492
+
493
+ }
494
+
495
+ function getZoomScale( delta ) {
496
+
497
+ const normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) );
498
+ return Math.pow( 0.95, scope.zoomSpeed * normalized_delta );
499
+
500
+ }
501
+
502
+ function rotateLeft( angle ) {
503
+
504
+ sphericalDelta.theta -= angle;
505
+
506
+ }
507
+
508
+ function rotateUp( angle ) {
509
+
510
+ sphericalDelta.phi -= angle;
511
+
512
+ }
513
+
514
+ const panLeft = function () {
515
+
516
+ const v = new Vector3();
517
+
518
+ return function panLeft( distance, objectMatrix ) {
519
+
520
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
521
+ v.multiplyScalar( - distance );
522
+
523
+ panOffset.add( v );
524
+
525
+ };
526
+
527
+ }();
528
+
529
+ const panUp = function () {
530
+
531
+ const v = new Vector3();
532
+
533
+ return function panUp( distance, objectMatrix ) {
534
+
535
+ if ( scope.screenSpacePanning === true ) {
536
+
537
+ v.setFromMatrixColumn( objectMatrix, 1 );
538
+
539
+ } else {
540
+
541
+ v.setFromMatrixColumn( objectMatrix, 0 );
542
+ v.crossVectors( scope.object.up, v );
543
+
544
+ }
545
+
546
+ v.multiplyScalar( distance );
547
+
548
+ panOffset.add( v );
549
+
550
+ };
551
+
552
+ }();
553
+
554
+ // deltaX and deltaY are in pixels; right and down are positive
555
+ const pan = function () {
556
+
557
+ const offset = new Vector3();
558
+
559
+ return function pan( deltaX, deltaY ) {
560
+
561
+ const element = scope.domElement;
562
+
563
+ if ( scope.object.isPerspectiveCamera ) {
564
+
565
+ // perspective
566
+ const position = scope.object.position;
567
+ offset.copy( position ).sub( scope.target );
568
+ let targetDistance = offset.length();
569
+
570
+ // half of the fov is center to top of screen
571
+ targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
572
+
573
+ // we use only clientHeight here so aspect ratio does not distort speed
574
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
575
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
576
+
577
+ } else if ( scope.object.isOrthographicCamera ) {
578
+
579
+ // orthographic
580
+ panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
581
+ panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
582
+
583
+ } else {
584
+
585
+ // camera neither orthographic nor perspective
586
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
587
+ scope.enablePan = false;
588
+
589
+ }
590
+
591
+ };
592
+
593
+ }();
594
+
595
+ function dollyOut( dollyScale ) {
596
+
597
+ if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
598
+
599
+ scale /= dollyScale;
600
+
601
+ } else {
602
+
603
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
604
+ scope.enableZoom = false;
605
+
606
+ }
607
+
608
+ }
609
+
610
+ function dollyIn( dollyScale ) {
611
+
612
+ if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
613
+
614
+ scale *= dollyScale;
615
+
616
+ } else {
617
+
618
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
619
+ scope.enableZoom = false;
620
+
621
+ }
622
+
623
+ }
624
+
625
+ function updateZoomParameters( x, y ) {
626
+
627
+ if ( ! scope.zoomToCursor ) {
628
+
629
+ return;
630
+
631
+ }
632
+
633
+ performCursorZoom = true;
634
+
635
+ const rect = scope.domElement.getBoundingClientRect();
636
+ const dx = x - rect.left;
637
+ const dy = y - rect.top;
638
+ const w = rect.width;
639
+ const h = rect.height;
640
+
641
+ mouse.x = ( dx / w ) * 2 - 1;
642
+ mouse.y = - ( dy / h ) * 2 + 1;
643
+
644
+ dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( scope.object ).sub( scope.object.position ).normalize();
645
+
646
+ }
647
+
648
+ function clampDistance( dist ) {
649
+
650
+ return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) );
651
+
652
+ }
653
+
654
+ //
655
+ // event callbacks - update the object state
656
+ //
657
+
658
+ function handleMouseDownRotate( event ) {
659
+
660
+ rotateStart.set( event.clientX, event.clientY );
661
+
662
+ }
663
+
664
+ function handleMouseDownDolly( event ) {
665
+
666
+ updateZoomParameters( event.clientX, event.clientX );
667
+ dollyStart.set( event.clientX, event.clientY );
668
+
669
+ }
670
+
671
+ function handleMouseDownPan( event ) {
672
+
673
+ panStart.set( event.clientX, event.clientY );
674
+
675
+ }
676
+
677
+ function handleMouseMoveRotate( event ) {
678
+
679
+ rotateEnd.set( event.clientX, event.clientY );
680
+
681
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
682
+
683
+ const element = scope.domElement;
684
+
685
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
686
+
687
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
688
+
689
+ rotateStart.copy( rotateEnd );
690
+
691
+ scope.update();
692
+
693
+ }
694
+
695
+ function handleMouseMoveDolly( event ) {
696
+
697
+ dollyEnd.set( event.clientX, event.clientY );
698
+
699
+ dollyDelta.subVectors( dollyEnd, dollyStart );
700
+
701
+ if ( dollyDelta.y > 0 ) {
702
+
703
+ dollyOut( getZoomScale( dollyDelta.y ) );
704
+
705
+ } else if ( dollyDelta.y < 0 ) {
706
+
707
+ dollyIn( getZoomScale( dollyDelta.y ) );
708
+
709
+ }
710
+
711
+ dollyStart.copy( dollyEnd );
712
+
713
+ scope.update();
714
+
715
+ }
716
+
717
+ function handleMouseMovePan( event ) {
718
+
719
+ panEnd.set( event.clientX, event.clientY );
720
+
721
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
722
+
723
+ pan( panDelta.x, panDelta.y );
724
+
725
+ panStart.copy( panEnd );
726
+
727
+ scope.update();
728
+
729
+ }
730
+
731
+ function handleMouseWheel( event ) {
732
+
733
+ updateZoomParameters( event.clientX, event.clientY );
734
+
735
+ if ( event.deltaY < 0 ) {
736
+
737
+ dollyIn( getZoomScale( event.deltaY ) );
738
+
739
+ } else if ( event.deltaY > 0 ) {
740
+
741
+ dollyOut( getZoomScale( event.deltaY ) );
742
+
743
+ }
744
+
745
+ scope.update();
746
+
747
+ }
748
+
749
+ function handleKeyDown( event ) {
750
+
751
+ let needsUpdate = false;
752
+
753
+ switch ( event.code ) {
754
+
755
+ case scope.keys.UP:
756
+
757
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
758
+
759
+ rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
760
+
761
+ } else {
762
+
763
+ pan( 0, scope.keyPanSpeed );
764
+
765
+ }
766
+
767
+ needsUpdate = true;
768
+ break;
769
+
770
+ case scope.keys.BOTTOM:
771
+
772
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
773
+
774
+ rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
775
+
776
+ } else {
777
+
778
+ pan( 0, - scope.keyPanSpeed );
779
+
780
+ }
781
+
782
+ needsUpdate = true;
783
+ break;
784
+
785
+ case scope.keys.LEFT:
786
+
787
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
788
+
789
+ rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
790
+
791
+ } else {
792
+
793
+ pan( scope.keyPanSpeed, 0 );
794
+
795
+ }
796
+
797
+ needsUpdate = true;
798
+ break;
799
+
800
+ case scope.keys.RIGHT:
801
+
802
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
803
+
804
+ rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
805
+
806
+ } else {
807
+
808
+ pan( - scope.keyPanSpeed, 0 );
809
+
810
+ }
811
+
812
+ needsUpdate = true;
813
+ break;
814
+
815
+ }
816
+
817
+ if ( needsUpdate ) {
818
+
819
+ // prevent the browser from scrolling on cursor keys
820
+ event.preventDefault();
821
+
822
+ scope.update();
823
+
824
+ }
825
+
826
+
827
+ }
828
+
829
+ function handleTouchStartRotate( event ) {
830
+
831
+ if ( pointers.length === 1 ) {
832
+
833
+ rotateStart.set( event.pageX, event.pageY );
834
+
835
+ } else {
836
+
837
+ const position = getSecondPointerPosition( event );
838
+
839
+ const x = 0.5 * ( event.pageX + position.x );
840
+ const y = 0.5 * ( event.pageY + position.y );
841
+
842
+ rotateStart.set( x, y );
843
+
844
+ }
845
+
846
+ }
847
+
848
+ function handleTouchStartPan( event ) {
849
+
850
+ if ( pointers.length === 1 ) {
851
+
852
+ panStart.set( event.pageX, event.pageY );
853
+
854
+ } else {
855
+
856
+ const position = getSecondPointerPosition( event );
857
+
858
+ const x = 0.5 * ( event.pageX + position.x );
859
+ const y = 0.5 * ( event.pageY + position.y );
860
+
861
+ panStart.set( x, y );
862
+
863
+ }
864
+
865
+ }
866
+
867
+ function handleTouchStartDolly( event ) {
868
+
869
+ const position = getSecondPointerPosition( event );
870
+
871
+ const dx = event.pageX - position.x;
872
+ const dy = event.pageY - position.y;
873
+
874
+ const distance = Math.sqrt( dx * dx + dy * dy );
875
+
876
+ dollyStart.set( 0, distance );
877
+
878
+ }
879
+
880
+ function handleTouchStartDollyPan( event ) {
881
+
882
+ if ( scope.enableZoom ) handleTouchStartDolly( event );
883
+
884
+ if ( scope.enablePan ) handleTouchStartPan( event );
885
+
886
+ }
887
+
888
+ function handleTouchStartDollyRotate( event ) {
889
+
890
+ if ( scope.enableZoom ) handleTouchStartDolly( event );
891
+
892
+ if ( scope.enableRotate ) handleTouchStartRotate( event );
893
+
894
+ }
895
+
896
+ function handleTouchMoveRotate( event ) {
897
+
898
+ if ( pointers.length == 1 ) {
899
+
900
+ rotateEnd.set( event.pageX, event.pageY );
901
+
902
+ } else {
903
+
904
+ const position = getSecondPointerPosition( event );
905
+
906
+ const x = 0.5 * ( event.pageX + position.x );
907
+ const y = 0.5 * ( event.pageY + position.y );
908
+
909
+ rotateEnd.set( x, y );
910
+
911
+ }
912
+
913
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
914
+
915
+ const element = scope.domElement;
916
+
917
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
918
+
919
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
920
+
921
+ rotateStart.copy( rotateEnd );
922
+
923
+ }
924
+
925
+ function handleTouchMovePan( event ) {
926
+
927
+ if ( pointers.length === 1 ) {
928
+
929
+ panEnd.set( event.pageX, event.pageY );
930
+
931
+ } else {
932
+
933
+ const position = getSecondPointerPosition( event );
934
+
935
+ const x = 0.5 * ( event.pageX + position.x );
936
+ const y = 0.5 * ( event.pageY + position.y );
937
+
938
+ panEnd.set( x, y );
939
+
940
+ }
941
+
942
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
943
+
944
+ pan( panDelta.x, panDelta.y );
945
+
946
+ panStart.copy( panEnd );
947
+
948
+ }
949
+
950
+ function handleTouchMoveDolly( event ) {
951
+
952
+ const position = getSecondPointerPosition( event );
953
+
954
+ const dx = event.pageX - position.x;
955
+ const dy = event.pageY - position.y;
956
+
957
+ const distance = Math.sqrt( dx * dx + dy * dy );
958
+
959
+ dollyEnd.set( 0, distance );
960
+
961
+ dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
962
+
963
+ dollyOut( dollyDelta.y );
964
+
965
+ dollyStart.copy( dollyEnd );
966
+
967
+ const centerX = ( event.pageX + position.x ) * 0.5;
968
+ const centerY = ( event.pageY + position.y ) * 0.5;
969
+
970
+ updateZoomParameters( centerX, centerY );
971
+
972
+ }
973
+
974
+ function handleTouchMoveDollyPan( event ) {
975
+
976
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
977
+
978
+ if ( scope.enablePan ) handleTouchMovePan( event );
979
+
980
+ }
981
+
982
+ function handleTouchMoveDollyRotate( event ) {
983
+
984
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
985
+
986
+ if ( scope.enableRotate ) handleTouchMoveRotate( event );
987
+
988
+ }
989
+
990
+ //
991
+ // event handlers - FSM: listen for events and reset state
992
+ //
993
+
994
+ function onPointerDown( event ) {
995
+
996
+ if ( scope.enabled === false ) return;
997
+
998
+ if ( pointers.length === 0 ) {
999
+
1000
+ scope.domElement.setPointerCapture( event.pointerId );
1001
+
1002
+ scope.domElement.addEventListener( 'pointermove', onPointerMove );
1003
+ scope.domElement.addEventListener( 'pointerup', onPointerUp );
1004
+
1005
+ }
1006
+
1007
+ //
1008
+
1009
+ addPointer( event );
1010
+
1011
+ if ( event.pointerType === 'touch' ) {
1012
+
1013
+ onTouchStart( event );
1014
+
1015
+ } else {
1016
+
1017
+ onMouseDown( event );
1018
+
1019
+ }
1020
+
1021
+ }
1022
+
1023
+ function onPointerMove( event ) {
1024
+
1025
+ if ( scope.enabled === false ) return;
1026
+
1027
+ if ( event.pointerType === 'touch' ) {
1028
+
1029
+ onTouchMove( event );
1030
+
1031
+ } else {
1032
+
1033
+ onMouseMove( event );
1034
+
1035
+ }
1036
+
1037
+ }
1038
+
1039
+ function onPointerUp( event ) {
1040
+
1041
+ removePointer( event );
1042
+
1043
+ if ( pointers.length === 0 ) {
1044
+
1045
+ scope.domElement.releasePointerCapture( event.pointerId );
1046
+
1047
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
1048
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
1049
+
1050
+ }
1051
+
1052
+ scope.dispatchEvent( _endEvent );
1053
+
1054
+ state = STATE.NONE;
1055
+
1056
+ }
1057
+
1058
+ function onMouseDown( event ) {
1059
+
1060
+ let mouseAction;
1061
+
1062
+ switch ( event.button ) {
1063
+
1064
+ case 0:
1065
+
1066
+ mouseAction = scope.mouseButtons.LEFT;
1067
+ break;
1068
+
1069
+ case 1:
1070
+
1071
+ mouseAction = scope.mouseButtons.MIDDLE;
1072
+ break;
1073
+
1074
+ case 2:
1075
+
1076
+ mouseAction = scope.mouseButtons.RIGHT;
1077
+ break;
1078
+
1079
+ default:
1080
+
1081
+ mouseAction = - 1;
1082
+
1083
+ }
1084
+
1085
+ switch ( mouseAction ) {
1086
+
1087
+ case MOUSE.DOLLY:
1088
+
1089
+ if ( scope.enableZoom === false ) return;
1090
+
1091
+ handleMouseDownDolly( event );
1092
+
1093
+ state = STATE.DOLLY;
1094
+
1095
+ break;
1096
+
1097
+ case MOUSE.ROTATE:
1098
+
1099
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
1100
+
1101
+ if ( scope.enablePan === false ) return;
1102
+
1103
+ handleMouseDownPan( event );
1104
+
1105
+ state = STATE.PAN;
1106
+
1107
+ } else {
1108
+
1109
+ if ( scope.enableRotate === false ) return;
1110
+
1111
+ handleMouseDownRotate( event );
1112
+
1113
+ state = STATE.ROTATE;
1114
+
1115
+ }
1116
+
1117
+ break;
1118
+
1119
+ case MOUSE.PAN:
1120
+
1121
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
1122
+
1123
+ if ( scope.enableRotate === false ) return;
1124
+
1125
+ handleMouseDownRotate( event );
1126
+
1127
+ state = STATE.ROTATE;
1128
+
1129
+ } else {
1130
+
1131
+ if ( scope.enablePan === false ) return;
1132
+
1133
+ handleMouseDownPan( event );
1134
+
1135
+ state = STATE.PAN;
1136
+
1137
+ }
1138
+
1139
+ break;
1140
+
1141
+ default:
1142
+
1143
+ state = STATE.NONE;
1144
+
1145
+ }
1146
+
1147
+ if ( state !== STATE.NONE ) {
1148
+
1149
+ scope.dispatchEvent( _startEvent );
1150
+
1151
+ }
1152
+
1153
+ }
1154
+
1155
+ function onMouseMove( event ) {
1156
+
1157
+ switch ( state ) {
1158
+
1159
+ case STATE.ROTATE:
1160
+
1161
+ if ( scope.enableRotate === false ) return;
1162
+
1163
+ handleMouseMoveRotate( event );
1164
+
1165
+ break;
1166
+
1167
+ case STATE.DOLLY:
1168
+
1169
+ if ( scope.enableZoom === false ) return;
1170
+
1171
+ handleMouseMoveDolly( event );
1172
+
1173
+ break;
1174
+
1175
+ case STATE.PAN:
1176
+
1177
+ if ( scope.enablePan === false ) return;
1178
+
1179
+ handleMouseMovePan( event );
1180
+
1181
+ break;
1182
+
1183
+ }
1184
+
1185
+ }
1186
+
1187
+ function onMouseWheel( event ) {
1188
+
1189
+ if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
1190
+
1191
+ event.preventDefault();
1192
+
1193
+ scope.dispatchEvent( _startEvent );
1194
+
1195
+ handleMouseWheel( event );
1196
+
1197
+ scope.dispatchEvent( _endEvent );
1198
+
1199
+ }
1200
+
1201
+ function onKeyDown( event ) {
1202
+
1203
+ if ( scope.enabled === false || scope.enablePan === false ) return;
1204
+
1205
+ handleKeyDown( event );
1206
+
1207
+ }
1208
+
1209
+ function onTouchStart( event ) {
1210
+
1211
+ trackPointer( event );
1212
+
1213
+ switch ( pointers.length ) {
1214
+
1215
+ case 1:
1216
+
1217
+ switch ( scope.touches.ONE ) {
1218
+
1219
+ case TOUCH.ROTATE:
1220
+
1221
+ if ( scope.enableRotate === false ) return;
1222
+
1223
+ handleTouchStartRotate( event );
1224
+
1225
+ state = STATE.TOUCH_ROTATE;
1226
+
1227
+ break;
1228
+
1229
+ case TOUCH.PAN:
1230
+
1231
+ if ( scope.enablePan === false ) return;
1232
+
1233
+ handleTouchStartPan( event );
1234
+
1235
+ state = STATE.TOUCH_PAN;
1236
+
1237
+ break;
1238
+
1239
+ default:
1240
+
1241
+ state = STATE.NONE;
1242
+
1243
+ }
1244
+
1245
+ break;
1246
+
1247
+ case 2:
1248
+
1249
+ switch ( scope.touches.TWO ) {
1250
+
1251
+ case TOUCH.DOLLY_PAN:
1252
+
1253
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
1254
+
1255
+ handleTouchStartDollyPan( event );
1256
+
1257
+ state = STATE.TOUCH_DOLLY_PAN;
1258
+
1259
+ break;
1260
+
1261
+ case TOUCH.DOLLY_ROTATE:
1262
+
1263
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1264
+
1265
+ handleTouchStartDollyRotate( event );
1266
+
1267
+ state = STATE.TOUCH_DOLLY_ROTATE;
1268
+
1269
+ break;
1270
+
1271
+ default:
1272
+
1273
+ state = STATE.NONE;
1274
+
1275
+ }
1276
+
1277
+ break;
1278
+
1279
+ default:
1280
+
1281
+ state = STATE.NONE;
1282
+
1283
+ }
1284
+
1285
+ if ( state !== STATE.NONE ) {
1286
+
1287
+ scope.dispatchEvent( _startEvent );
1288
+
1289
+ }
1290
+
1291
+ }
1292
+
1293
+ function onTouchMove( event ) {
1294
+
1295
+ trackPointer( event );
1296
+
1297
+ switch ( state ) {
1298
+
1299
+ case STATE.TOUCH_ROTATE:
1300
+
1301
+ if ( scope.enableRotate === false ) return;
1302
+
1303
+ handleTouchMoveRotate( event );
1304
+
1305
+ scope.update();
1306
+
1307
+ break;
1308
+
1309
+ case STATE.TOUCH_PAN:
1310
+
1311
+ if ( scope.enablePan === false ) return;
1312
+
1313
+ handleTouchMovePan( event );
1314
+
1315
+ scope.update();
1316
+
1317
+ break;
1318
+
1319
+ case STATE.TOUCH_DOLLY_PAN:
1320
+
1321
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
1322
+
1323
+ handleTouchMoveDollyPan( event );
1324
+
1325
+ scope.update();
1326
+
1327
+ break;
1328
+
1329
+ case STATE.TOUCH_DOLLY_ROTATE:
1330
+
1331
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1332
+
1333
+ handleTouchMoveDollyRotate( event );
1334
+
1335
+ scope.update();
1336
+
1337
+ break;
1338
+
1339
+ default:
1340
+
1341
+ state = STATE.NONE;
1342
+
1343
+ }
1344
+
1345
+ }
1346
+
1347
+ function onContextMenu( event ) {
1348
+
1349
+ if ( scope.enabled === false ) return;
1350
+
1351
+ event.preventDefault();
1352
+
1353
+ }
1354
+
1355
+ function addPointer( event ) {
1356
+
1357
+ pointers.push( event.pointerId );
1358
+
1359
+ }
1360
+
1361
+ function removePointer( event ) {
1362
+
1363
+ delete pointerPositions[ event.pointerId ];
1364
+
1365
+ for ( let i = 0; i < pointers.length; i ++ ) {
1366
+
1367
+ if ( pointers[ i ] == event.pointerId ) {
1368
+
1369
+ pointers.splice( i, 1 );
1370
+ return;
1371
+
1372
+ }
1373
+
1374
+ }
1375
+
1376
+ }
1377
+
1378
+ function trackPointer( event ) {
1379
+
1380
+ let position = pointerPositions[ event.pointerId ];
1381
+
1382
+ if ( position === undefined ) {
1383
+
1384
+ position = new Vector2();
1385
+ pointerPositions[ event.pointerId ] = position;
1386
+
1387
+ }
1388
+
1389
+ position.set( event.pageX, event.pageY );
1390
+
1391
+ }
1392
+
1393
+ function getSecondPointerPosition( event ) {
1394
+
1395
+ const pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ];
1396
+
1397
+ return pointerPositions[ pointerId ];
1398
+
1399
+ }
1400
+
1401
+ //
1402
+
1403
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu );
1404
+
1405
+ scope.domElement.addEventListener( 'pointerdown', onPointerDown );
1406
+ scope.domElement.addEventListener( 'pointercancel', onPointerUp );
1407
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
1408
+
1409
+ // force an update at start
1410
+
1411
+ this.update();
1412
+
1413
+ }
1414
+
1415
+ }
1416
+
1417
+ export { OrbitControls };
assets/vendor/three-r160/addons/libs/meshopt_decoder.module.js ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This file is part of meshoptimizer library and is distributed under the terms of MIT License.
2
+ // Copyright (C) 2016-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
3
+ var MeshoptDecoder = (function() {
4
+ "use strict";
5
+
6
+ // Built with clang version 14.0.4
7
+ // Built from meshoptimizer 0.18
8
+ var wasm_base = "b9H79Tebbbe8Fv9Gbb9Gvuuuuueu9Giuuub9Geueu9Giuuueuikqbeeedddillviebeoweuec:q;iekr;leDo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbeY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVbdE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbiL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtblK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949Wbol79IV9Rbrq:P8Yqdbk;3sezu8Jjjjjbcj;eb9Rgv8Kjjjjbc9:hodnadcefal0mbcuhoaiRbbc:Ge9hmbavaialfgrad9Radz1jjjbhwcj;abad9UhoaicefhldnadTmbaoc;WFbGgocjdaocjd6EhDcbhqinaqae9pmeaDaeaq9RaqaDfae6Egkcsfgocl4cifcd4hxdndndndnaoc9WGgmTmbcbhPcehsawcjdfhzalhHinaraH9Rax6midnaraHaxfgl9RcK6mbczhoinawcj;cbfaogifgoc9WfhOdndndndndnaHaic9WfgAco4fRbbaAci4coG4ciGPlbedibkaO9cb83ibaOcwf9cb83ibxikaOalRblalRbbgAco4gCaCciSgCE86bbaocGfalclfaCfgORbbaAcl4ciGgCaCciSgCE86bbaocVfaOaCfgORbbaAcd4ciGgCaCciSgCE86bbaoc7faOaCfgORbbaAciGgAaAciSgAE86bbaoctfaOaAfgARbbalRbegOco4gCaCciSgCE86bbaoc91faAaCfgARbbaOcl4ciGgCaCciSgCE86bbaoc4faAaCfgARbbaOcd4ciGgCaCciSgCE86bbaoc93faAaCfgARbbaOciGgOaOciSgOE86bbaoc94faAaOfgARbbalRbdgOco4gCaCciSgCE86bbaoc95faAaCfgARbbaOcl4ciGgCaCciSgCE86bbaoc96faAaCfgARbbaOcd4ciGgCaCciSgCE86bbaoc97faAaCfgARbbaOciGgOaOciSgOE86bbaoc98faAaOfgORbbalRbiglco4gAaAciSgAE86bbaoc99faOaAfgORbbalcl4ciGgAaAciSgAE86bbaoc9:faOaAfgORbbalcd4ciGgAaAciSgAE86bbaocufaOaAfgoRbbalciGglalciSglE86bbaoalfhlxdkaOalRbwalRbbgAcl4gCaCcsSgCE86bbaocGfalcwfaCfgORbbaAcsGgAaAcsSgAE86bbaocVfaOaAfgORbbalRbegAcl4gCaCcsSgCE86bbaoc7faOaCfgORbbaAcsGgAaAcsSgAE86bbaoctfaOaAfgORbbalRbdgAcl4gCaCcsSgCE86bbaoc91faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc4faOaAfgORbbalRbigAcl4gCaCcsSgCE86bbaoc93faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc94faOaAfgORbbalRblgAcl4gCaCcsSgCE86bbaoc95faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc96faOaAfgORbbalRbvgAcl4gCaCcsSgCE86bbaoc97faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc98faOaAfgORbbalRbogAcl4gCaCcsSgCE86bbaoc99faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc9:faOaAfgORbbalRbrglcl4gAaAcsSgAE86bbaocufaOaAfgoRbbalcsGglalcsSglE86bbaoalfhlxekaOal8Pbb83bbaOcwfalcwf8Pbb83bbalczfhlkdnaiam9pmbaiczfhoaral9RcL0mekkaiam6mialTmidnakTmbawaPfRbbhOcbhoazhiinaiawcj;cbfaofRbbgAce4cbaAceG9R7aOfgO86bbaiadfhiaocefgoak9hmbkkazcefhzaPcefgPad6hsalhHaPad9hmexvkkcbhlasceGmdxikalaxad2fhCdnakTmbcbhHcehsawcjdfhminaral9Rax6mialTmdalaxfhlawaHfRbbhOcbhoamhiinaiawcj;cbfaofRbbgAce4cbaAceG9R7aOfgO86bbaiadfhiaocefgoak9hmbkamcefhmaHcefgHad6hsaHad9hmbkaChlxikcbhocehsinaral9Rax6mdalTmealaxfhlaocefgoad6hsadao9hmbkaChlxdkcbhlasceGTmekc9:hoxikabaqad2fawcjdfakad2z1jjjb8Aawawcjdfakcufad2fadz1jjjb8Aakaqfhqalmbkc9:hoxekcbc99aral9Radcaadca0ESEhokavcj;ebf8Kjjjjbaok;yzeHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecjez:jjjjb8AavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhodnaeTmbcmcsaDceSEhkcbhxcbhmcbhDcbhicbhlindnaoaq9nmbc9:hoxikdndnawRbbgrc;Ve0mbavc;abfalarcl4cu7fcsGcitfgPydlhsaPydbhzdnarcsGgPak9pmbavaiarcu7fcsGcdtfydbaxaPEhraPThPdndnadcd9hmbabaDcetfgHaz87ebaHcdfas87ebaHclfar87ebxekabaDcdtfgHazBdbaHclfasBdbaHcwfarBdbkaxaPfhxavc;abfalcitfgHarBdbaHasBdlavaicdtfarBdbavc;abfalcefcsGglcitfgHazBdbaHarBdlaiaPfhialcefhlxdkdndnaPcsSmbamaPfaPc987fcefhmxekaocefhrao8SbbgPcFeGhHdndnaPcu9mmbarhoxekaocvfhoaHcFbGhHcrhPdninar8SbbgOcFbGaPtaHVhHaOcu9kmearcefhraPcrfgPc8J9hmbxdkkarcefhokaHce4cbaHceG9R7amfhmkdndnadcd9hmbabaDcetfgraz87ebarcdfas87ebarclfam87ebxekabaDcdtfgrazBdbarclfasBdbarcwfamBdbkavc;abfalcitfgramBdbarasBdlavaicdtfamBdbavc;abfalcefcsGglcitfgrazBdbaramBdlaicefhialcefhlxekdnarcpe0mbaxcefgOavaiaqarcsGfRbbgPcl49RcsGcdtfydbaPcz6gHEhravaiaP9RcsGcdtfydbaOaHfgsaPcsGgOEhPaOThOdndnadcd9hmbabaDcetfgzax87ebazcdfar87ebazclfaP87ebxekabaDcdtfgzaxBdbazclfarBdbazcwfaPBdbkavaicdtfaxBdbavc;abfalcitfgzarBdbazaxBdlavaicefgicsGcdtfarBdbavc;abfalcefcsGcitfgzaPBdbazarBdlavaiaHfcsGgicdtfaPBdbavc;abfalcdfcsGglcitfgraxBdbaraPBdlalcefhlaiaOfhiasaOfhxxekaxcbaoRbbgzEgAarc;:eSgrfhsazcsGhCazcl4hXdndnazcs0mbascefhOxekashOavaiaX9RcsGcdtfydbhskdndnaCmbaOcefhxxekaOhxavaiaz9RcsGcdtfydbhOkdndnarTmbaocefhrxekaocdfhrao8SbegHcFeGhPdnaHcu9kmbaocofhAaPcFbGhPcrhodninar8SbbgHcFbGaotaPVhPaHcu9kmearcefhraocrfgoc8J9hmbkaAhrxekarcefhrkaPce4cbaPceG9R7amfgmhAkdndnaXcsSmbarhPxekarcefhPar8SbbgocFeGhHdnaocu9kmbarcvfhsaHcFbGhHcrhodninaP8SbbgrcFbGaotaHVhHarcu9kmeaPcefhPaocrfgoc8J9hmbkashPxekaPcefhPkaHce4cbaHceG9R7amfgmhskdndnaCcsSmbaPhoxekaPcefhoaP8SbbgrcFeGhHdnarcu9kmbaPcvfhOaHcFbGhHcrhrdninao8SbbgPcFbGartaHVhHaPcu9kmeaocefhoarcrfgrc8J9hmbkaOhoxekaocefhokaHce4cbaHceG9R7amfgmhOkdndnadcd9hmbabaDcetfgraA87ebarcdfas87ebarclfaO87ebxekabaDcdtfgraABdbarclfasBdbarcwfaOBdbkavc;abfalcitfgrasBdbaraABdlavaicdtfaABdbavc;abfalcefcsGcitfgraOBdbarasBdlavaicefgicsGcdtfasBdbavc;abfalcdfcsGcitfgraABdbaraOBdlavaiazcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhialcifhlkawcefhwalcsGhlaicsGhiaDcifgDae6mbkkcbc99aoaqSEhokavc;aef8Kjjjjbaok:llevu8Jjjjjbcz9Rhvc9:hodnaecvfal0mbcuhoaiRbbc;:eGc;qe9hmbav9cb83iwaicefhraialfc98fhwdnaeTmbdnadcdSmbcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcdtfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfglBdbaoalBdbaDcefgDae9hmbxdkkcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcetfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfgl87ebaoalBdbaDcefgDae9hmbkkcbc99arawSEhokaok:Lvoeue99dud99eud99dndnadcl9hmbaeTmeindndnabcdfgd8Sbb:Yab8Sbbgi:Ygl:l:tabcefgv8Sbbgo:Ygr:l:tgwJbb;:9cawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai86bbdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad86bbdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad86bbabclfhbaecufgembxdkkaeTmbindndnabclfgd8Ueb:Yab8Uebgi:Ygl:l:tabcdfgv8Uebgo:Ygr:l:tgwJb;:FSawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai87ebdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad87ebdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad87ebabcwfhbaecufgembkkk;siliui99iue99dnaeTmbcbhiabhlindndnJ;Zl81Zalcof8UebgvciV:Y:vgoal8Ueb:YNgrJb;:FSNJbbbZJbbb:;arJbbbb9GEMgw:lJbbb9p9DTmbaw:OhDxekcjjjj94hDkalclf8Uebhqalcdf8UebhkabavcefciGaiVcetfaD87ebdndnaoak:YNgwJb;:FSNJbbbZJbbb:;awJbbbb9GEMgx:lJbbb9p9DTmbax:Ohkxekcjjjj94hkkabavcdfciGaiVcetfak87ebdndnaoaq:YNgoJb;:FSNJbbbZJbbb:;aoJbbbb9GEMgx:lJbbb9p9DTmbax:Ohqxekcjjjj94hqkabavcufciGaiVcetfaq87ebdndnJbbjZararN:tawawN:taoaoN:tgrJbbbbarJbbbb9GE:rJb;:FSNJbbbZMgr:lJbbb9p9DTmbar:Ohqxekcjjjj94hqkabavciGaiVcetfaq87ebalcwfhlaiclfhiaecufgembkkk9mbdnadcd4ae2geTmbinababydbgdcwtcw91:Yadce91cjjj;8ifcjjj98G::NUdbabclfhbaecufgembkkk9teiucbcbydj1jjbgeabcifc98GfgbBdj1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;LeeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiclfaeclfydbBdbaicwfaecwfydbBdbaicxfaecxfydbBdbaiczfhiaeczfheadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk;aeedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdbaicxfalBdbaicwfalBdbaiclfalBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabkkkebcjwklz9Kbb";
9
+ var wasm_simd = "b9H79TebbbeKl9Gbb9Gvuuuuueu9Giuuub9Geueuikqbbebeedddilve9Weeeviebeoweuec:q;Aekr;leDo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbdY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVblE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtboK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbrL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949Wbwl79IV9RbDq;t9tqlbzik9:evu8Jjjjjbcz9Rhbcbheincbhdcbhiinabcwfadfaicjuaead4ceGglE86bbaialfhiadcefgdcw9hmbkaec:q:yjjbfai86bbaecitc:q1jjbfab8Piw83ibaecefgecjd9hmbkk;h8JlHud97euo978Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnadcefal0mbcuhoaiRbbc:Ge9hmbavaialfgrad9Rad;8qbbcj;abad9UhoaicefhldnadTmbaoc;WFbGgocjdaocjd6EhwcbhDinaDae9pmeawaeaD9RaDawfae6Egqcsfgoc9WGgkci2hxakcethmaocl4cifcd4hPabaDad2fhscbhzdnincehHalhOcbhAdninaraO9RaP6miavcj;cbfaAak2fhCaOaPfhlcbhidnakc;ab6mbaral9Rc;Gb6mbcbhoinaCaofhidndndndndnaOaoco4fRbbgXciGPlbedibkaipxbbbbbbbbbbbbbbbbpklbxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaialpbbbpklbalczfhlkdndndndndnaXcd4ciGPlbedibkaipxbbbbbbbbbbbbbbbbpklzxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklzalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklzalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaialpbbbpklzalczfhlkdndndndndnaXcl4ciGPlbedibkaipxbbbbbbbbbbbbbbbbpklaxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklaalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklaalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaialpbbbpklaalczfhlkdndndndndnaXco4Plbedibkaipxbbbbbbbbbbbbbbbbpkl8WxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgXcitc:q1jjbfpbibaXc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgXcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spkl8WalclfaYpQbfaXc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgXcitc:q1jjbfpbibaXc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgXcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spkl8WalcwfaYpQbfaXc:q:yjjbfRbbfhlxekaialpbbbpkl8Walczfhlkaoc;abfhiaocjefak0meaihoaral9Rc;Fb0mbkkdndnaiak9pmbaici4hoinaral9RcK6mdaCaifhXdndndndndnaOaico4fRbbaocoG4ciGPlbedibkaXpxbbbbbbbbbbbbbbbbpklbxikaXalpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaXalpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaXalpbbbpklbalczfhlkaocdfhoaiczfgiak6mbkkalTmbaAci6hHalhOaAcefgohAaoclSmdxekkcbhlaHceGmdkdnakTmbavcjdfazfhiavazfpbdbhYcbhXinaiavcj;cbfaXfgopblbgLcep9TaLpxeeeeeeeeeeeeeeeegQp9op9Hp9rgLaoakfpblbg8Acep9Ta8AaQp9op9Hp9rg8ApmbzeHdOiAlCvXoQrLgEaoamfpblbg3cep9Ta3aQp9op9Hp9rg3aoaxfpblbg5cep9Ta5aQp9op9Hp9rg5pmbzeHdOiAlCvXoQrLg8EpmbezHdiOAlvCXorQLgQaQpmbedibedibedibediaYp9UgYp9AdbbaiadfgoaYaQaQpmlvorlvorlvorlvorp9UgYp9AdbbaoadfgoaYaQaQpmwDqkwDqkwDqkwDqkp9UgYp9AdbbaoadfgoaYaQaQpmxmPsxmPsxmPsxmPsp9UgYp9AdbbaoadfgoaYaEa8EpmwDKYqk8AExm35Ps8E8FgQaQpmbedibedibedibedip9UgYp9AdbbaoadfgoaYaQaQpmlvorlvorlvorlvorp9UgYp9AdbbaoadfgoaYaQaQpmwDqkwDqkwDqkwDqkp9UgYp9AdbbaoadfgoaYaQaQpmxmPsxmPsxmPsxmPsp9UgYp9AdbbaoadfgoaYaLa8ApmwKDYq8AkEx3m5P8Es8FgLa3a5pmwKDYq8AkEx3m5P8Es8Fg8ApmbezHdiOAlvCXorQLgQaQpmbedibedibedibedip9UgYp9AdbbaoadfgoaYaQaQpmlvorlvorlvorlvorp9UgYp9AdbbaoadfgoaYaQaQpmwDqkwDqkwDqkwDqkp9UgYp9AdbbaoadfgoaYaQaQpmxmPsxmPsxmPsxmPsp9UgYp9AdbbaoadfgoaYaLa8ApmwDKYqk8AExm35Ps8E8FgQaQpmbedibedibedibedip9UgYp9AdbbaoadfgoaYaQaQpmlvorlvorlvorlvorp9UgYp9AdbbaoadfgoaYaQaQpmwDqkwDqkwDqkwDqkp9UgYp9AdbbaoadfgoaYaQaQpmxmPsxmPsxmPsxmPsp9UgYp9AdbbaoadfhiaXczfgXak6mbkkazclfgzad6mbkasavcjdfaqad2;8qbbavavcjdfaqcufad2fad;8qbbaqaDfhDc9:hoalmexikkc9:hoxekcbc99aral9Radcaadca0ESEhokavcj;kbf8Kjjjjbaokwbz:bjjjbk;uzeHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecje;8kbavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhodnaeTmbcmcsaDceSEhkcbhxcbhmcbhDcbhicbhlindnaoaq9nmbc9:hoxikdndnawRbbgrc;Ve0mbavc;abfalarcl4cu7fcsGcitfgPydlhsaPydbhzdnarcsGgPak9pmbavaiarcu7fcsGcdtfydbaxaPEhraPThPdndnadcd9hmbabaDcetfgHaz87ebaHcdfas87ebaHclfar87ebxekabaDcdtfgHazBdbaHclfasBdbaHcwfarBdbkaxaPfhxavc;abfalcitfgHarBdbaHasBdlavaicdtfarBdbavc;abfalcefcsGglcitfgHazBdbaHarBdlaiaPfhialcefhlxdkdndnaPcsSmbamaPfaPc987fcefhmxekaocefhrao8SbbgPcFeGhHdndnaPcu9mmbarhoxekaocvfhoaHcFbGhHcrhPdninar8SbbgOcFbGaPtaHVhHaOcu9kmearcefhraPcrfgPc8J9hmbxdkkarcefhokaHce4cbaHceG9R7amfhmkdndnadcd9hmbabaDcetfgraz87ebarcdfas87ebarclfam87ebxekabaDcdtfgrazBdbarclfasBdbarcwfamBdbkavc;abfalcitfgramBdbarasBdlavaicdtfamBdbavc;abfalcefcsGglcitfgrazBdbaramBdlaicefhialcefhlxekdnarcpe0mbaxcefgOavaiaqarcsGfRbbgPcl49RcsGcdtfydbaPcz6gHEhravaiaP9RcsGcdtfydbaOaHfgsaPcsGgOEhPaOThOdndnadcd9hmbabaDcetfgzax87ebazcdfar87ebazclfaP87ebxekabaDcdtfgzaxBdbazclfarBdbazcwfaPBdbkavaicdtfaxBdbavc;abfalcitfgzarBdbazaxBdlavaicefgicsGcdtfarBdbavc;abfalcefcsGcitfgzaPBdbazarBdlavaiaHfcsGgicdtfaPBdbavc;abfalcdfcsGglcitfgraxBdbaraPBdlalcefhlaiaOfhiasaOfhxxekaxcbaoRbbgzEgAarc;:eSgrfhsazcsGhCazcl4hXdndnazcs0mbascefhOxekashOavaiaX9RcsGcdtfydbhskdndnaCmbaOcefhxxekaOhxavaiaz9RcsGcdtfydbhOkdndnarTmbaocefhrxekaocdfhrao8SbegHcFeGhPdnaHcu9kmbaocofhAaPcFbGhPcrhodninar8SbbgHcFbGaotaPVhPaHcu9kmearcefhraocrfgoc8J9hmbkaAhrxekarcefhrkaPce4cbaPceG9R7amfgmhAkdndnaXcsSmbarhPxekarcefhPar8SbbgocFeGhHdnaocu9kmbarcvfhsaHcFbGhHcrhodninaP8SbbgrcFbGaotaHVhHarcu9kmeaPcefhPaocrfgoc8J9hmbkashPxekaPcefhPkaHce4cbaHceG9R7amfgmhskdndnaCcsSmbaPhoxekaPcefhoaP8SbbgrcFeGhHdnarcu9kmbaPcvfhOaHcFbGhHcrhrdninao8SbbgPcFbGartaHVhHaPcu9kmeaocefhoarcrfgrc8J9hmbkaOhoxekaocefhokaHce4cbaHceG9R7amfgmhOkdndnadcd9hmbabaDcetfgraA87ebarcdfas87ebarclfaO87ebxekabaDcdtfgraABdbarclfasBdbarcwfaOBdbkavc;abfalcitfgrasBdbaraABdlavaicdtfaABdbavc;abfalcefcsGcitfgraOBdbarasBdlavaicefgicsGcdtfasBdbavc;abfalcdfcsGcitfgraABdbaraOBdlavaiazcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhialcifhlkawcefhwalcsGhlaicsGhiaDcifgDae6mbkkcbc99aoaqSEhokavc;aef8Kjjjjbaok:llevu8Jjjjjbcz9Rhvc9:hodnaecvfal0mbcuhoaiRbbc;:eGc;qe9hmbav9cb83iwaicefhraialfc98fhwdnaeTmbdnadcdSmbcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcdtfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfglBdbaoalBdbaDcefgDae9hmbxdkkcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcetfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfgl87ebaoalBdbaDcefgDae9hmbkkcbc99arawSEhokaok:EPliuo97eue978Jjjjjbca9Rhidndnadcl9hmbdnaec98GglTmbcbhvabhdinadadpbbbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDpxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpkbbadczfhdavclfgval6mbkkalae9pmeaiaeciGgvcdtgdVcbczad9R;8kbaiabalcdtfglad;8qbbdnavTmbaiaipblbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDpxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpklbkalaiad;8qbbskdnaec98GgxTmbcbhvabhdinadczfglalpbbbgopxbbbbbbFFbbbbbbFFgkp9oadpbbbgDaopmlvorxmPsCXQL358E8FpxFubbFubbFubbFubbp9op;6eaDaopmbediwDqkzHOAKY8AEgoczp:Sep;6egrp;Geaoczp:Reczp:Sep;6egwp;Gep;Kep;Legopxb;:FSb;:FSb;:FSb;:FSawaopxbbbbbbbbbbbbbbbbp:2egqawpxbbbjbbbjbbbjbbbjgmp9op9rp;Kegwawp;Meaoaop;Mearaqaramp9op9rp;Kegoaop;Mep;Kep;Kep;Jep;Negrp;Mepxbbn0bbn0bbn0bbn0gqp;Keczp:Reawarp;Meaqp;KepxFFbbFFbbFFbbFFbbp9op9qgwaoarp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogopmwDKYqk8AExm35Ps8E8Fp9qpkbbadaDakp9oawaopmbezHdiOAlvCXorQLp9qpkbbadcafhdavclfgvax6mbkkaxae9pmbaiaeciGgvcitgdfcbcaad9R;8kbaiabaxcitfglad;8qbbdnavTmbaiaipblzgopxbbbbbbFFbbbbbbFFgkp9oaipblbgDaopmlvorxmPsCXQL358E8FpxFubbFubbFubbFubbp9op;6eaDaopmbediwDqkzHOAKY8AEgoczp:Sep;6egrp;Geaoczp:Reczp:Sep;6egwp;Gep;Kep;Legopxb;:FSb;:FSb;:FSb;:FSawaopxbbbbbbbbbbbbbbbbp:2egqawpxbbbjbbbjbbbjbbbjgmp9op9rp;Kegwawp;Meaoaop;Mearaqaramp9op9rp;Kegoaop;Mep;Kep;Kep;Jep;Negrp;Mepxbbn0bbn0bbn0bbn0gqp;Keczp:Reawarp;Meaqp;KepxFFbbFFbbFFbbFFbbp9op9qgwaoarp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogopmwDKYqk8AExm35Ps8E8Fp9qpklzaiaDakp9oawaopmbezHdiOAlvCXorQLp9qpklbkalaiad;8qbbkk;4wllue97euv978Jjjjjbc8W9Rhidnaec98GglTmbcbhvabhoinaiaopbbbgraoczfgwpbbbgDpmlvorxmPsCXQL358E8Fgqczp:Segkclp:RepklbaopxbbjZbbjZbbjZbbjZpx;Zl81Z;Zl81Z;Zl81Z;Zl81Zakpxibbbibbbibbbibbbp9qp;6ep;NegkaraDpmbediwDqkzHOAKY8AEgrczp:Reczp:Sep;6ep;MegDaDp;Meakarczp:Sep;6ep;Megxaxp;Meakaqczp:Reczp:Sep;6ep;Megqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jepxb;:FSb;:FSb;:FSb;:FSgkp;Mepxbbn0bbn0bbn0bbn0grp;KepxFFbbFFbbFFbbFFbbgmp9oaxakp;Mearp;Keczp:Rep9qgxaqakp;Mearp;Keczp:ReaDakp;Mearp;Keamp9op9qgkpmbezHdiOAlvCXorQLgrp5baipblbpEb:T:j83ibaocwfarp5eaipblbpEe:T:j83ibawaxakpmwDKYqk8AExm35Ps8E8Fgkp5baipblbpEd:T:j83ibaocKfakp5eaipblbpEi:T:j83ibaocafhoavclfgval6mbkkdnalae9pmbaiaeciGgvcitgofcbcaao9R;8kbaiabalcitfgwao;8qbbdnavTmbaiaipblbgraipblzgDpmlvorxmPsCXQL358E8Fgqczp:Segkclp:RepklaaipxbbjZbbjZbbjZbbjZpx;Zl81Z;Zl81Z;Zl81Z;Zl81Zakpxibbbibbbibbbibbbp9qp;6ep;NegkaraDpmbediwDqkzHOAKY8AEgrczp:Reczp:Sep;6ep;MegDaDp;Meakarczp:Sep;6ep;Megxaxp;Meakaqczp:Reczp:Sep;6ep;Megqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jepxb;:FSb;:FSb;:FSb;:FSgkp;Mepxbbn0bbn0bbn0bbn0grp;KepxFFbbFFbbFFbbFFbbgmp9oaxakp;Mearp;Keczp:Rep9qgxaqakp;Mearp;Keczp:ReaDakp;Mearp;Keamp9op9qgkpmbezHdiOAlvCXorQLgrp5baipblapEb:T:j83ibaiarp5eaipblapEe:T:j83iwaiaxakpmwDKYqk8AExm35Ps8E8Fgkp5baipblapEd:T:j83izaiakp5eaipblapEi:T:j83iKkawaiao;8qbbkk:Pddiue978Jjjjjbc;ab9Rhidnadcd4ae2glc98GgvTmbcbhdabheinaeaepbbbgocwp:Recwp:Sep;6eaocep:SepxbbjZbbjZbbjZbbjZp:UepxbbjFbbjFbbjFbbjFp9op;Mepkbbaeczfheadclfgdav6mbkkdnaval9pmbaialciGgdcdtgeVcbc;abae9R;8kbaiabavcdtfgvae;8qbbdnadTmbaiaipblbgocwp:Recwp:Sep;6eaocep:SepxbbjZbbjZbbjZbbjZp:UepxbbjFbbjFbbjFbbjFp9op;Mepklbkavaiae;8qbbkk9teiucbcbydj1jjbgeabcifc98GfgbBdj1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaikkkebcjwklz9Tbb";
10
+
11
+ var detector = new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,3,2,0,0,5,3,1,0,1,12,1,0,10,22,2,12,0,65,0,65,0,65,0,252,10,0,0,11,7,0,65,0,253,15,26,11]);
12
+ var wasmpack = new Uint8Array([32,0,65,2,1,106,34,33,3,128,11,4,13,64,6,253,10,7,15,116,127,5,8,12,40,16,19,54,20,9,27,255,113,17,42,67,24,23,146,148,18,14,22,45,70,69,56,114,101,21,25,63,75,136,108,28,118,29,73,115]);
13
+
14
+ if (typeof WebAssembly !== 'object') {
15
+ return {
16
+ supported: false,
17
+ };
18
+ }
19
+
20
+ var wasm = WebAssembly.validate(detector) ? wasm_simd : wasm_base;
21
+
22
+ var instance;
23
+
24
+ var ready =
25
+ WebAssembly.instantiate(unpack(wasm), {})
26
+ .then(function(result) {
27
+ instance = result.instance;
28
+ instance.exports.__wasm_call_ctors();
29
+ });
30
+
31
+ function unpack(data) {
32
+ var result = new Uint8Array(data.length);
33
+ for (var i = 0; i < data.length; ++i) {
34
+ var ch = data.charCodeAt(i);
35
+ result[i] = ch > 96 ? ch - 97 : ch > 64 ? ch - 39 : ch + 4;
36
+ }
37
+ var write = 0;
38
+ for (var i = 0; i < data.length; ++i) {
39
+ result[write++] = (result[i] < 60) ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i];
40
+ }
41
+ return result.buffer.slice(0, write);
42
+ }
43
+
44
+ function decode(fun, target, count, size, source, filter) {
45
+ var sbrk = instance.exports.sbrk;
46
+ var count4 = (count + 3) & ~3;
47
+ var tp = sbrk(count4 * size);
48
+ var sp = sbrk(source.length);
49
+ var heap = new Uint8Array(instance.exports.memory.buffer);
50
+ heap.set(source, sp);
51
+ var res = fun(tp, count, size, sp, source.length);
52
+ if (res == 0 && filter) {
53
+ filter(tp, count4, size);
54
+ }
55
+ target.set(heap.subarray(tp, tp + count * size));
56
+ sbrk(tp - sbrk(0));
57
+ if (res != 0) {
58
+ throw new Error("Malformed buffer data: " + res);
59
+ }
60
+ }
61
+
62
+ var filters = {
63
+ NONE: "",
64
+ OCTAHEDRAL: "meshopt_decodeFilterOct",
65
+ QUATERNION: "meshopt_decodeFilterQuat",
66
+ EXPONENTIAL: "meshopt_decodeFilterExp",
67
+ };
68
+
69
+ var decoders = {
70
+ ATTRIBUTES: "meshopt_decodeVertexBuffer",
71
+ TRIANGLES: "meshopt_decodeIndexBuffer",
72
+ INDICES: "meshopt_decodeIndexSequence",
73
+ };
74
+
75
+ var workers = [];
76
+ var requestId = 0;
77
+
78
+ function createWorker(url) {
79
+ var worker = {
80
+ object: new Worker(url),
81
+ pending: 0,
82
+ requests: {}
83
+ };
84
+
85
+ worker.object.onmessage = function(event) {
86
+ var data = event.data;
87
+
88
+ worker.pending -= data.count;
89
+ worker.requests[data.id][data.action](data.value);
90
+
91
+ delete worker.requests[data.id];
92
+ };
93
+
94
+ return worker;
95
+ }
96
+
97
+ function initWorkers(count) {
98
+ var source =
99
+ "var instance; var ready = WebAssembly.instantiate(new Uint8Array([" + new Uint8Array(unpack(wasm)) + "]), {})" +
100
+ ".then(function(result) { instance = result.instance; instance.exports.__wasm_call_ctors(); });" +
101
+ "self.onmessage = workerProcess;" +
102
+ decode.toString() + workerProcess.toString();
103
+
104
+ var blob = new Blob([source], {type: 'text/javascript'});
105
+ var url = URL.createObjectURL(blob);
106
+
107
+ for (var i = 0; i < count; ++i) {
108
+ workers[i] = createWorker(url);
109
+ }
110
+
111
+ URL.revokeObjectURL(url);
112
+ }
113
+
114
+ function decodeWorker(count, size, source, mode, filter) {
115
+ var worker = workers[0];
116
+
117
+ for (var i = 1; i < workers.length; ++i) {
118
+ if (workers[i].pending < worker.pending) {
119
+ worker = workers[i];
120
+ }
121
+ }
122
+
123
+ return new Promise(function (resolve, reject) {
124
+ var data = new Uint8Array(source);
125
+ var id = requestId++;
126
+
127
+ worker.pending += count;
128
+ worker.requests[id] = { resolve: resolve, reject: reject };
129
+ worker.object.postMessage({ id: id, count: count, size: size, source: data, mode: mode, filter: filter }, [ data.buffer ]);
130
+ });
131
+ }
132
+
133
+ function workerProcess(event) {
134
+ ready.then(function() {
135
+ var data = event.data;
136
+ try {
137
+ var target = new Uint8Array(data.count * data.size);
138
+ decode(instance.exports[data.mode], target, data.count, data.size, data.source, instance.exports[data.filter]);
139
+ self.postMessage({ id: data.id, count: data.count, action: "resolve", value: target }, [ target.buffer ]);
140
+ } catch (error) {
141
+ self.postMessage({ id: data.id, count: data.count, action: "reject", value: error });
142
+ }
143
+ });
144
+ }
145
+
146
+ return {
147
+ ready: ready,
148
+ supported: true,
149
+ useWorkers: function(count) {
150
+ initWorkers(count);
151
+ },
152
+ decodeVertexBuffer: function(target, count, size, source, filter) {
153
+ decode(instance.exports.meshopt_decodeVertexBuffer, target, count, size, source, instance.exports[filters[filter]]);
154
+ },
155
+ decodeIndexBuffer: function(target, count, size, source) {
156
+ decode(instance.exports.meshopt_decodeIndexBuffer, target, count, size, source);
157
+ },
158
+ decodeIndexSequence: function(target, count, size, source) {
159
+ decode(instance.exports.meshopt_decodeIndexSequence, target, count, size, source);
160
+ },
161
+ decodeGltfBuffer: function(target, count, size, source, mode, filter) {
162
+ decode(instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]);
163
+ },
164
+ decodeGltfBufferAsync: function(count, size, source, mode, filter) {
165
+ if (workers.length > 0) {
166
+ return decodeWorker(count, size, source, decoders[mode], filters[filter]);
167
+ }
168
+
169
+ return ready.then(function() {
170
+ var target = new Uint8Array(count * size);
171
+ decode(instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]);
172
+ return target;
173
+ });
174
+ }
175
+ };
176
+ })();
177
+
178
+ export { MeshoptDecoder };
assets/vendor/three-r160/addons/loaders/GLTFLoader.js ADDED
The diff for this file is too large to render. See raw diff
 
assets/vendor/three-r160/addons/objects/Sky.js ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ BackSide,
3
+ BoxGeometry,
4
+ Mesh,
5
+ ShaderMaterial,
6
+ UniformsUtils,
7
+ Vector3
8
+ } from 'three';
9
+
10
+ /**
11
+ * Based on "A Practical Analytic Model for Daylight"
12
+ * aka The Preetham Model, the de facto standard analytic skydome model
13
+ * https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight
14
+ *
15
+ * First implemented by Simon Wallner
16
+ * http://simonwallner.at/project/atmospheric-scattering/
17
+ *
18
+ * Improved by Martin Upitis
19
+ * http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR
20
+ *
21
+ * Three.js integration by zz85 http://twitter.com/blurspline
22
+ */
23
+
24
+ class Sky extends Mesh {
25
+
26
+ constructor() {
27
+
28
+ const shader = Sky.SkyShader;
29
+
30
+ const material = new ShaderMaterial( {
31
+ name: shader.name,
32
+ uniforms: UniformsUtils.clone( shader.uniforms ),
33
+ vertexShader: shader.vertexShader,
34
+ fragmentShader: shader.fragmentShader,
35
+ side: BackSide,
36
+ depthWrite: false
37
+ } );
38
+
39
+ super( new BoxGeometry( 1, 1, 1 ), material );
40
+
41
+ this.isSky = true;
42
+
43
+ }
44
+
45
+ }
46
+
47
+ Sky.SkyShader = {
48
+
49
+ name: 'SkyShader',
50
+
51
+ uniforms: {
52
+ 'turbidity': { value: 2 },
53
+ 'rayleigh': { value: 1 },
54
+ 'mieCoefficient': { value: 0.005 },
55
+ 'mieDirectionalG': { value: 0.8 },
56
+ 'sunPosition': { value: new Vector3() },
57
+ 'up': { value: new Vector3( 0, 1, 0 ) }
58
+ },
59
+
60
+ vertexShader: /* glsl */`
61
+ uniform vec3 sunPosition;
62
+ uniform float rayleigh;
63
+ uniform float turbidity;
64
+ uniform float mieCoefficient;
65
+ uniform vec3 up;
66
+
67
+ varying vec3 vWorldPosition;
68
+ varying vec3 vSunDirection;
69
+ varying float vSunfade;
70
+ varying vec3 vBetaR;
71
+ varying vec3 vBetaM;
72
+ varying float vSunE;
73
+
74
+ // constants for atmospheric scattering
75
+ const float e = 2.71828182845904523536028747135266249775724709369995957;
76
+ const float pi = 3.141592653589793238462643383279502884197169;
77
+
78
+ // wavelength of used primaries, according to preetham
79
+ const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 );
80
+ // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function:
81
+ // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn))
82
+ const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 );
83
+
84
+ // mie stuff
85
+ // K coefficient for the primaries
86
+ const float v = 4.0;
87
+ const vec3 K = vec3( 0.686, 0.678, 0.666 );
88
+ // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K
89
+ const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 );
90
+
91
+ // earth shadow hack
92
+ // cutoffAngle = pi / 1.95;
93
+ const float cutoffAngle = 1.6110731556870734;
94
+ const float steepness = 1.5;
95
+ const float EE = 1000.0;
96
+
97
+ float sunIntensity( float zenithAngleCos ) {
98
+ zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 );
99
+ return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) );
100
+ }
101
+
102
+ vec3 totalMie( float T ) {
103
+ float c = ( 0.2 * T ) * 10E-18;
104
+ return 0.434 * c * MieConst;
105
+ }
106
+
107
+ void main() {
108
+
109
+ vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
110
+ vWorldPosition = worldPosition.xyz;
111
+
112
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
113
+ gl_Position.z = gl_Position.w; // set z to camera.far
114
+
115
+ vSunDirection = normalize( sunPosition );
116
+
117
+ vSunE = sunIntensity( dot( vSunDirection, up ) );
118
+
119
+ vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 );
120
+
121
+ float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) );
122
+
123
+ // extinction (absorbtion + out scattering)
124
+ // rayleigh coefficients
125
+ vBetaR = totalRayleigh * rayleighCoefficient;
126
+
127
+ // mie coefficients
128
+ vBetaM = totalMie( turbidity ) * mieCoefficient;
129
+
130
+ }`,
131
+
132
+ fragmentShader: /* glsl */`
133
+ varying vec3 vWorldPosition;
134
+ varying vec3 vSunDirection;
135
+ varying float vSunfade;
136
+ varying vec3 vBetaR;
137
+ varying vec3 vBetaM;
138
+ varying float vSunE;
139
+
140
+ uniform float mieDirectionalG;
141
+ uniform vec3 up;
142
+
143
+ // constants for atmospheric scattering
144
+ const float pi = 3.141592653589793238462643383279502884197169;
145
+
146
+ const float n = 1.0003; // refractive index of air
147
+ const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius)
148
+
149
+ // optical length at zenith for molecules
150
+ const float rayleighZenithLength = 8.4E3;
151
+ const float mieZenithLength = 1.25E3;
152
+ // 66 arc seconds -> degrees, and the cosine of that
153
+ const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324;
154
+
155
+ // 3.0 / ( 16.0 * pi )
156
+ const float THREE_OVER_SIXTEENPI = 0.05968310365946075;
157
+ // 1.0 / ( 4.0 * pi )
158
+ const float ONE_OVER_FOURPI = 0.07957747154594767;
159
+
160
+ float rayleighPhase( float cosTheta ) {
161
+ return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) );
162
+ }
163
+
164
+ float hgPhase( float cosTheta, float g ) {
165
+ float g2 = pow( g, 2.0 );
166
+ float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 );
167
+ return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse );
168
+ }
169
+
170
+ void main() {
171
+
172
+ vec3 direction = normalize( vWorldPosition - cameraPosition );
173
+
174
+ // optical length
175
+ // cutoff angle at 90 to avoid singularity in next formula.
176
+ float zenithAngle = acos( max( 0.0, dot( up, direction ) ) );
177
+ float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) );
178
+ float sR = rayleighZenithLength * inverse;
179
+ float sM = mieZenithLength * inverse;
180
+
181
+ // combined extinction factor
182
+ vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) );
183
+
184
+ // in scattering
185
+ float cosTheta = dot( direction, vSunDirection );
186
+
187
+ float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 );
188
+ vec3 betaRTheta = vBetaR * rPhase;
189
+
190
+ float mPhase = hgPhase( cosTheta, mieDirectionalG );
191
+ vec3 betaMTheta = vBetaM * mPhase;
192
+
193
+ vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) );
194
+ Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) );
195
+
196
+ // nightsky
197
+ float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2]
198
+ float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2]
199
+ vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 );
200
+ vec3 L0 = vec3( 0.1 ) * Fex;
201
+
202
+ // composition + solar disc
203
+ float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta );
204
+ L0 += ( vSunE * 19000.0 * Fex ) * sundisk;
205
+
206
+ vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 );
207
+
208
+ vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) );
209
+
210
+ gl_FragColor = vec4( retColor, 1.0 );
211
+
212
+ #include <tonemapping_fragment>
213
+ #include <colorspace_fragment>
214
+
215
+ }`
216
+
217
+ };
218
+
219
+ export { Sky };
assets/vendor/three-r160/addons/utils/BufferGeometryUtils.js ADDED
@@ -0,0 +1,1375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ BufferAttribute,
3
+ BufferGeometry,
4
+ Float32BufferAttribute,
5
+ InstancedBufferAttribute,
6
+ InterleavedBuffer,
7
+ InterleavedBufferAttribute,
8
+ TriangleFanDrawMode,
9
+ TriangleStripDrawMode,
10
+ TrianglesDrawMode,
11
+ Vector3,
12
+ } from 'three';
13
+
14
+ function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ) {
15
+
16
+ if ( ! MikkTSpace || ! MikkTSpace.isReady ) {
17
+
18
+ throw new Error( 'BufferGeometryUtils: Initialized MikkTSpace library required.' );
19
+
20
+ }
21
+
22
+ if ( ! geometry.hasAttribute( 'position' ) || ! geometry.hasAttribute( 'normal' ) || ! geometry.hasAttribute( 'uv' ) ) {
23
+
24
+ throw new Error( 'BufferGeometryUtils: Tangents require "position", "normal", and "uv" attributes.' );
25
+
26
+ }
27
+
28
+ function getAttributeArray( attribute ) {
29
+
30
+ if ( attribute.normalized || attribute.isInterleavedBufferAttribute ) {
31
+
32
+ const dstArray = new Float32Array( attribute.count * attribute.itemSize );
33
+
34
+ for ( let i = 0, j = 0; i < attribute.count; i ++ ) {
35
+
36
+ dstArray[ j ++ ] = attribute.getX( i );
37
+ dstArray[ j ++ ] = attribute.getY( i );
38
+
39
+ if ( attribute.itemSize > 2 ) {
40
+
41
+ dstArray[ j ++ ] = attribute.getZ( i );
42
+
43
+ }
44
+
45
+ }
46
+
47
+ return dstArray;
48
+
49
+ }
50
+
51
+ if ( attribute.array instanceof Float32Array ) {
52
+
53
+ return attribute.array;
54
+
55
+ }
56
+
57
+ return new Float32Array( attribute.array );
58
+
59
+ }
60
+
61
+ // MikkTSpace algorithm requires non-indexed input.
62
+
63
+ const _geometry = geometry.index ? geometry.toNonIndexed() : geometry;
64
+
65
+ // Compute vertex tangents.
66
+
67
+ const tangents = MikkTSpace.generateTangents(
68
+
69
+ getAttributeArray( _geometry.attributes.position ),
70
+ getAttributeArray( _geometry.attributes.normal ),
71
+ getAttributeArray( _geometry.attributes.uv )
72
+
73
+ );
74
+
75
+ // Texture coordinate convention of glTF differs from the apparent
76
+ // default of the MikkTSpace library; .w component must be flipped.
77
+
78
+ if ( negateSign ) {
79
+
80
+ for ( let i = 3; i < tangents.length; i += 4 ) {
81
+
82
+ tangents[ i ] *= - 1;
83
+
84
+ }
85
+
86
+ }
87
+
88
+ //
89
+
90
+ _geometry.setAttribute( 'tangent', new BufferAttribute( tangents, 4 ) );
91
+
92
+ if ( geometry !== _geometry ) {
93
+
94
+ geometry.copy( _geometry );
95
+
96
+ }
97
+
98
+ return geometry;
99
+
100
+ }
101
+
102
+ /**
103
+ * @param {Array<BufferGeometry>} geometries
104
+ * @param {Boolean} useGroups
105
+ * @return {BufferGeometry}
106
+ */
107
+ function mergeGeometries( geometries, useGroups = false ) {
108
+
109
+ const isIndexed = geometries[ 0 ].index !== null;
110
+
111
+ const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) );
112
+ const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) );
113
+
114
+ const attributes = {};
115
+ const morphAttributes = {};
116
+
117
+ const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative;
118
+
119
+ const mergedGeometry = new BufferGeometry();
120
+
121
+ let offset = 0;
122
+
123
+ for ( let i = 0; i < geometries.length; ++ i ) {
124
+
125
+ const geometry = geometries[ i ];
126
+ let attributesCount = 0;
127
+
128
+ // ensure that all geometries are indexed, or none
129
+
130
+ if ( isIndexed !== ( geometry.index !== null ) ) {
131
+
132
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' );
133
+ return null;
134
+
135
+ }
136
+
137
+ // gather attributes, exit early if they're different
138
+
139
+ for ( const name in geometry.attributes ) {
140
+
141
+ if ( ! attributesUsed.has( name ) ) {
142
+
143
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' );
144
+ return null;
145
+
146
+ }
147
+
148
+ if ( attributes[ name ] === undefined ) attributes[ name ] = [];
149
+
150
+ attributes[ name ].push( geometry.attributes[ name ] );
151
+
152
+ attributesCount ++;
153
+
154
+ }
155
+
156
+ // ensure geometries have the same number of attributes
157
+
158
+ if ( attributesCount !== attributesUsed.size ) {
159
+
160
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' );
161
+ return null;
162
+
163
+ }
164
+
165
+ // gather morph attributes, exit early if they're different
166
+
167
+ if ( morphTargetsRelative !== geometry.morphTargetsRelative ) {
168
+
169
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' );
170
+ return null;
171
+
172
+ }
173
+
174
+ for ( const name in geometry.morphAttributes ) {
175
+
176
+ if ( ! morphAttributesUsed.has( name ) ) {
177
+
178
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphAttributes must be consistent throughout all geometries.' );
179
+ return null;
180
+
181
+ }
182
+
183
+ if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];
184
+
185
+ morphAttributes[ name ].push( geometry.morphAttributes[ name ] );
186
+
187
+ }
188
+
189
+ if ( useGroups ) {
190
+
191
+ let count;
192
+
193
+ if ( isIndexed ) {
194
+
195
+ count = geometry.index.count;
196
+
197
+ } else if ( geometry.attributes.position !== undefined ) {
198
+
199
+ count = geometry.attributes.position.count;
200
+
201
+ } else {
202
+
203
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' );
204
+ return null;
205
+
206
+ }
207
+
208
+ mergedGeometry.addGroup( offset, count, i );
209
+
210
+ offset += count;
211
+
212
+ }
213
+
214
+ }
215
+
216
+ // merge indices
217
+
218
+ if ( isIndexed ) {
219
+
220
+ let indexOffset = 0;
221
+ const mergedIndex = [];
222
+
223
+ for ( let i = 0; i < geometries.length; ++ i ) {
224
+
225
+ const index = geometries[ i ].index;
226
+
227
+ for ( let j = 0; j < index.count; ++ j ) {
228
+
229
+ mergedIndex.push( index.getX( j ) + indexOffset );
230
+
231
+ }
232
+
233
+ indexOffset += geometries[ i ].attributes.position.count;
234
+
235
+ }
236
+
237
+ mergedGeometry.setIndex( mergedIndex );
238
+
239
+ }
240
+
241
+ // merge attributes
242
+
243
+ for ( const name in attributes ) {
244
+
245
+ const mergedAttribute = mergeAttributes( attributes[ name ] );
246
+
247
+ if ( ! mergedAttribute ) {
248
+
249
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' attribute.' );
250
+ return null;
251
+
252
+ }
253
+
254
+ mergedGeometry.setAttribute( name, mergedAttribute );
255
+
256
+ }
257
+
258
+ // merge morph attributes
259
+
260
+ for ( const name in morphAttributes ) {
261
+
262
+ const numMorphTargets = morphAttributes[ name ][ 0 ].length;
263
+
264
+ if ( numMorphTargets === 0 ) break;
265
+
266
+ mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
267
+ mergedGeometry.morphAttributes[ name ] = [];
268
+
269
+ for ( let i = 0; i < numMorphTargets; ++ i ) {
270
+
271
+ const morphAttributesToMerge = [];
272
+
273
+ for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) {
274
+
275
+ morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );
276
+
277
+ }
278
+
279
+ const mergedMorphAttribute = mergeAttributes( morphAttributesToMerge );
280
+
281
+ if ( ! mergedMorphAttribute ) {
282
+
283
+ console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' morphAttribute.' );
284
+ return null;
285
+
286
+ }
287
+
288
+ mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );
289
+
290
+ }
291
+
292
+ }
293
+
294
+ return mergedGeometry;
295
+
296
+ }
297
+
298
+ /**
299
+ * @param {Array<BufferAttribute>} attributes
300
+ * @return {BufferAttribute}
301
+ */
302
+ function mergeAttributes( attributes ) {
303
+
304
+ let TypedArray;
305
+ let itemSize;
306
+ let normalized;
307
+ let gpuType = - 1;
308
+ let arrayLength = 0;
309
+
310
+ for ( let i = 0; i < attributes.length; ++ i ) {
311
+
312
+ const attribute = attributes[ i ];
313
+
314
+ if ( attribute.isInterleavedBufferAttribute ) {
315
+
316
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. InterleavedBufferAttributes are not supported.' );
317
+ return null;
318
+
319
+ }
320
+
321
+ if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
322
+ if ( TypedArray !== attribute.array.constructor ) {
323
+
324
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.' );
325
+ return null;
326
+
327
+ }
328
+
329
+ if ( itemSize === undefined ) itemSize = attribute.itemSize;
330
+ if ( itemSize !== attribute.itemSize ) {
331
+
332
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.' );
333
+ return null;
334
+
335
+ }
336
+
337
+ if ( normalized === undefined ) normalized = attribute.normalized;
338
+ if ( normalized !== attribute.normalized ) {
339
+
340
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.' );
341
+ return null;
342
+
343
+ }
344
+
345
+ if ( gpuType === - 1 ) gpuType = attribute.gpuType;
346
+ if ( gpuType !== attribute.gpuType ) {
347
+
348
+ console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes.' );
349
+ return null;
350
+
351
+ }
352
+
353
+ arrayLength += attribute.array.length;
354
+
355
+ }
356
+
357
+ const array = new TypedArray( arrayLength );
358
+ let offset = 0;
359
+
360
+ for ( let i = 0; i < attributes.length; ++ i ) {
361
+
362
+ array.set( attributes[ i ].array, offset );
363
+
364
+ offset += attributes[ i ].array.length;
365
+
366
+ }
367
+
368
+ const result = new BufferAttribute( array, itemSize, normalized );
369
+ if ( gpuType !== undefined ) {
370
+
371
+ result.gpuType = gpuType;
372
+
373
+ }
374
+
375
+ return result;
376
+
377
+ }
378
+
379
+ /**
380
+ * @param {BufferAttribute}
381
+ * @return {BufferAttribute}
382
+ */
383
+ export function deepCloneAttribute( attribute ) {
384
+
385
+ if ( attribute.isInstancedInterleavedBufferAttribute || attribute.isInterleavedBufferAttribute ) {
386
+
387
+ return deinterleaveAttribute( attribute );
388
+
389
+ }
390
+
391
+ if ( attribute.isInstancedBufferAttribute ) {
392
+
393
+ return new InstancedBufferAttribute().copy( attribute );
394
+
395
+ }
396
+
397
+ return new BufferAttribute().copy( attribute );
398
+
399
+ }
400
+
401
+ /**
402
+ * @param {Array<BufferAttribute>} attributes
403
+ * @return {Array<InterleavedBufferAttribute>}
404
+ */
405
+ function interleaveAttributes( attributes ) {
406
+
407
+ // Interleaves the provided attributes into an InterleavedBuffer and returns
408
+ // a set of InterleavedBufferAttributes for each attribute
409
+ let TypedArray;
410
+ let arrayLength = 0;
411
+ let stride = 0;
412
+
413
+ // calculate the length and type of the interleavedBuffer
414
+ for ( let i = 0, l = attributes.length; i < l; ++ i ) {
415
+
416
+ const attribute = attributes[ i ];
417
+
418
+ if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
419
+ if ( TypedArray !== attribute.array.constructor ) {
420
+
421
+ console.error( 'AttributeBuffers of different types cannot be interleaved' );
422
+ return null;
423
+
424
+ }
425
+
426
+ arrayLength += attribute.array.length;
427
+ stride += attribute.itemSize;
428
+
429
+ }
430
+
431
+ // Create the set of buffer attributes
432
+ const interleavedBuffer = new InterleavedBuffer( new TypedArray( arrayLength ), stride );
433
+ let offset = 0;
434
+ const res = [];
435
+ const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
436
+ const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
437
+
438
+ for ( let j = 0, l = attributes.length; j < l; j ++ ) {
439
+
440
+ const attribute = attributes[ j ];
441
+ const itemSize = attribute.itemSize;
442
+ const count = attribute.count;
443
+ const iba = new InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized );
444
+ res.push( iba );
445
+
446
+ offset += itemSize;
447
+
448
+ // Move the data for each attribute into the new interleavedBuffer
449
+ // at the appropriate offset
450
+ for ( let c = 0; c < count; c ++ ) {
451
+
452
+ for ( let k = 0; k < itemSize; k ++ ) {
453
+
454
+ iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) );
455
+
456
+ }
457
+
458
+ }
459
+
460
+ }
461
+
462
+ return res;
463
+
464
+ }
465
+
466
+ // returns a new, non-interleaved version of the provided attribute
467
+ export function deinterleaveAttribute( attribute ) {
468
+
469
+ const cons = attribute.data.array.constructor;
470
+ const count = attribute.count;
471
+ const itemSize = attribute.itemSize;
472
+ const normalized = attribute.normalized;
473
+
474
+ const array = new cons( count * itemSize );
475
+ let newAttribute;
476
+ if ( attribute.isInstancedInterleavedBufferAttribute ) {
477
+
478
+ newAttribute = new InstancedBufferAttribute( array, itemSize, normalized, attribute.meshPerAttribute );
479
+
480
+ } else {
481
+
482
+ newAttribute = new BufferAttribute( array, itemSize, normalized );
483
+
484
+ }
485
+
486
+ for ( let i = 0; i < count; i ++ ) {
487
+
488
+ newAttribute.setX( i, attribute.getX( i ) );
489
+
490
+ if ( itemSize >= 2 ) {
491
+
492
+ newAttribute.setY( i, attribute.getY( i ) );
493
+
494
+ }
495
+
496
+ if ( itemSize >= 3 ) {
497
+
498
+ newAttribute.setZ( i, attribute.getZ( i ) );
499
+
500
+ }
501
+
502
+ if ( itemSize >= 4 ) {
503
+
504
+ newAttribute.setW( i, attribute.getW( i ) );
505
+
506
+ }
507
+
508
+ }
509
+
510
+ return newAttribute;
511
+
512
+ }
513
+
514
+ // deinterleaves all attributes on the geometry
515
+ export function deinterleaveGeometry( geometry ) {
516
+
517
+ const attributes = geometry.attributes;
518
+ const morphTargets = geometry.morphTargets;
519
+ const attrMap = new Map();
520
+
521
+ for ( const key in attributes ) {
522
+
523
+ const attr = attributes[ key ];
524
+ if ( attr.isInterleavedBufferAttribute ) {
525
+
526
+ if ( ! attrMap.has( attr ) ) {
527
+
528
+ attrMap.set( attr, deinterleaveAttribute( attr ) );
529
+
530
+ }
531
+
532
+ attributes[ key ] = attrMap.get( attr );
533
+
534
+ }
535
+
536
+ }
537
+
538
+ for ( const key in morphTargets ) {
539
+
540
+ const attr = morphTargets[ key ];
541
+ if ( attr.isInterleavedBufferAttribute ) {
542
+
543
+ if ( ! attrMap.has( attr ) ) {
544
+
545
+ attrMap.set( attr, deinterleaveAttribute( attr ) );
546
+
547
+ }
548
+
549
+ morphTargets[ key ] = attrMap.get( attr );
550
+
551
+ }
552
+
553
+ }
554
+
555
+ }
556
+
557
+ /**
558
+ * @param {BufferGeometry} geometry
559
+ * @return {number}
560
+ */
561
+ function estimateBytesUsed( geometry ) {
562
+
563
+ // Return the estimated memory used by this geometry in bytes
564
+ // Calculate using itemSize, count, and BYTES_PER_ELEMENT to account
565
+ // for InterleavedBufferAttributes.
566
+ let mem = 0;
567
+ for ( const name in geometry.attributes ) {
568
+
569
+ const attr = geometry.getAttribute( name );
570
+ mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT;
571
+
572
+ }
573
+
574
+ const indices = geometry.getIndex();
575
+ mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0;
576
+ return mem;
577
+
578
+ }
579
+
580
+ /**
581
+ * @param {BufferGeometry} geometry
582
+ * @param {number} tolerance
583
+ * @return {BufferGeometry}
584
+ */
585
+ function mergeVertices( geometry, tolerance = 1e-4 ) {
586
+
587
+ tolerance = Math.max( tolerance, Number.EPSILON );
588
+
589
+ // Generate an index buffer if the geometry doesn't have one, or optimize it
590
+ // if it's already available.
591
+ const hashToIndex = {};
592
+ const indices = geometry.getIndex();
593
+ const positions = geometry.getAttribute( 'position' );
594
+ const vertexCount = indices ? indices.count : positions.count;
595
+
596
+ // next value for triangle indices
597
+ let nextIndex = 0;
598
+
599
+ // attributes and new attribute arrays
600
+ const attributeNames = Object.keys( geometry.attributes );
601
+ const tmpAttributes = {};
602
+ const tmpMorphAttributes = {};
603
+ const newIndices = [];
604
+ const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
605
+ const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
606
+
607
+ // Initialize the arrays, allocating space conservatively. Extra
608
+ // space will be trimmed in the last step.
609
+ for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
610
+
611
+ const name = attributeNames[ i ];
612
+ const attr = geometry.attributes[ name ];
613
+
614
+ tmpAttributes[ name ] = new BufferAttribute(
615
+ new attr.array.constructor( attr.count * attr.itemSize ),
616
+ attr.itemSize,
617
+ attr.normalized
618
+ );
619
+
620
+ const morphAttr = geometry.morphAttributes[ name ];
621
+ if ( morphAttr ) {
622
+
623
+ tmpMorphAttributes[ name ] = new BufferAttribute(
624
+ new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize ),
625
+ morphAttr.itemSize,
626
+ morphAttr.normalized
627
+ );
628
+
629
+ }
630
+
631
+ }
632
+
633
+ // convert the error tolerance to an amount of decimal places to truncate to
634
+ const halfTolerance = tolerance * 0.5;
635
+ const exponent = Math.log10( 1 / tolerance );
636
+ const hashMultiplier = Math.pow( 10, exponent );
637
+ const hashAdditive = halfTolerance * hashMultiplier;
638
+ for ( let i = 0; i < vertexCount; i ++ ) {
639
+
640
+ const index = indices ? indices.getX( i ) : i;
641
+
642
+ // Generate a hash for the vertex attributes at the current index 'i'
643
+ let hash = '';
644
+ for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
645
+
646
+ const name = attributeNames[ j ];
647
+ const attribute = geometry.getAttribute( name );
648
+ const itemSize = attribute.itemSize;
649
+
650
+ for ( let k = 0; k < itemSize; k ++ ) {
651
+
652
+ // double tilde truncates the decimal value
653
+ hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * hashMultiplier + hashAdditive ) },`;
654
+
655
+ }
656
+
657
+ }
658
+
659
+ // Add another reference to the vertex if it's already
660
+ // used by another index
661
+ if ( hash in hashToIndex ) {
662
+
663
+ newIndices.push( hashToIndex[ hash ] );
664
+
665
+ } else {
666
+
667
+ // copy data to the new index in the temporary attributes
668
+ for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
669
+
670
+ const name = attributeNames[ j ];
671
+ const attribute = geometry.getAttribute( name );
672
+ const morphAttr = geometry.morphAttributes[ name ];
673
+ const itemSize = attribute.itemSize;
674
+ const newarray = tmpAttributes[ name ];
675
+ const newMorphArrays = tmpMorphAttributes[ name ];
676
+
677
+ for ( let k = 0; k < itemSize; k ++ ) {
678
+
679
+ const getterFunc = getters[ k ];
680
+ const setterFunc = setters[ k ];
681
+ newarray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) );
682
+
683
+ if ( morphAttr ) {
684
+
685
+ for ( let m = 0, ml = morphAttr.length; m < ml; m ++ ) {
686
+
687
+ newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttr[ m ][ getterFunc ]( index ) );
688
+
689
+ }
690
+
691
+ }
692
+
693
+ }
694
+
695
+ }
696
+
697
+ hashToIndex[ hash ] = nextIndex;
698
+ newIndices.push( nextIndex );
699
+ nextIndex ++;
700
+
701
+ }
702
+
703
+ }
704
+
705
+ // generate result BufferGeometry
706
+ const result = geometry.clone();
707
+ for ( const name in geometry.attributes ) {
708
+
709
+ const tmpAttribute = tmpAttributes[ name ];
710
+
711
+ result.setAttribute( name, new BufferAttribute(
712
+ tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ),
713
+ tmpAttribute.itemSize,
714
+ tmpAttribute.normalized,
715
+ ) );
716
+
717
+ if ( ! ( name in tmpMorphAttributes ) ) continue;
718
+
719
+ for ( let j = 0; j < tmpMorphAttributes[ name ].length; j ++ ) {
720
+
721
+ const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ];
722
+
723
+ result.morphAttributes[ name ][ j ] = new BufferAttribute(
724
+ tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ),
725
+ tmpMorphAttribute.itemSize,
726
+ tmpMorphAttribute.normalized,
727
+ );
728
+
729
+ }
730
+
731
+ }
732
+
733
+ // indices
734
+
735
+ result.setIndex( newIndices );
736
+
737
+ return result;
738
+
739
+ }
740
+
741
+ /**
742
+ * @param {BufferGeometry} geometry
743
+ * @param {number} drawMode
744
+ * @return {BufferGeometry}
745
+ */
746
+ function toTrianglesDrawMode( geometry, drawMode ) {
747
+
748
+ if ( drawMode === TrianglesDrawMode ) {
749
+
750
+ console.warn( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.' );
751
+ return geometry;
752
+
753
+ }
754
+
755
+ if ( drawMode === TriangleFanDrawMode || drawMode === TriangleStripDrawMode ) {
756
+
757
+ let index = geometry.getIndex();
758
+
759
+ // generate index if not present
760
+
761
+ if ( index === null ) {
762
+
763
+ const indices = [];
764
+
765
+ const position = geometry.getAttribute( 'position' );
766
+
767
+ if ( position !== undefined ) {
768
+
769
+ for ( let i = 0; i < position.count; i ++ ) {
770
+
771
+ indices.push( i );
772
+
773
+ }
774
+
775
+ geometry.setIndex( indices );
776
+ index = geometry.getIndex();
777
+
778
+ } else {
779
+
780
+ console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
781
+ return geometry;
782
+
783
+ }
784
+
785
+ }
786
+
787
+ //
788
+
789
+ const numberOfTriangles = index.count - 2;
790
+ const newIndices = [];
791
+
792
+ if ( drawMode === TriangleFanDrawMode ) {
793
+
794
+ // gl.TRIANGLE_FAN
795
+
796
+ for ( let i = 1; i <= numberOfTriangles; i ++ ) {
797
+
798
+ newIndices.push( index.getX( 0 ) );
799
+ newIndices.push( index.getX( i ) );
800
+ newIndices.push( index.getX( i + 1 ) );
801
+
802
+ }
803
+
804
+ } else {
805
+
806
+ // gl.TRIANGLE_STRIP
807
+
808
+ for ( let i = 0; i < numberOfTriangles; i ++ ) {
809
+
810
+ if ( i % 2 === 0 ) {
811
+
812
+ newIndices.push( index.getX( i ) );
813
+ newIndices.push( index.getX( i + 1 ) );
814
+ newIndices.push( index.getX( i + 2 ) );
815
+
816
+ } else {
817
+
818
+ newIndices.push( index.getX( i + 2 ) );
819
+ newIndices.push( index.getX( i + 1 ) );
820
+ newIndices.push( index.getX( i ) );
821
+
822
+ }
823
+
824
+ }
825
+
826
+ }
827
+
828
+ if ( ( newIndices.length / 3 ) !== numberOfTriangles ) {
829
+
830
+ console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
831
+
832
+ }
833
+
834
+ // build final geometry
835
+
836
+ const newGeometry = geometry.clone();
837
+ newGeometry.setIndex( newIndices );
838
+ newGeometry.clearGroups();
839
+
840
+ return newGeometry;
841
+
842
+ } else {
843
+
844
+ console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unknown draw mode:', drawMode );
845
+ return geometry;
846
+
847
+ }
848
+
849
+ }
850
+
851
+ /**
852
+ * Calculates the morphed attributes of a morphed/skinned BufferGeometry.
853
+ * Helpful for Raytracing or Decals.
854
+ * @param {Mesh | Line | Points} object An instance of Mesh, Line or Points.
855
+ * @return {Object} An Object with original position/normal attributes and morphed ones.
856
+ */
857
+ function computeMorphedAttributes( object ) {
858
+
859
+ const _vA = new Vector3();
860
+ const _vB = new Vector3();
861
+ const _vC = new Vector3();
862
+
863
+ const _tempA = new Vector3();
864
+ const _tempB = new Vector3();
865
+ const _tempC = new Vector3();
866
+
867
+ const _morphA = new Vector3();
868
+ const _morphB = new Vector3();
869
+ const _morphC = new Vector3();
870
+
871
+ function _calculateMorphedAttributeData(
872
+ object,
873
+ attribute,
874
+ morphAttribute,
875
+ morphTargetsRelative,
876
+ a,
877
+ b,
878
+ c,
879
+ modifiedAttributeArray
880
+ ) {
881
+
882
+ _vA.fromBufferAttribute( attribute, a );
883
+ _vB.fromBufferAttribute( attribute, b );
884
+ _vC.fromBufferAttribute( attribute, c );
885
+
886
+ const morphInfluences = object.morphTargetInfluences;
887
+
888
+ if ( morphAttribute && morphInfluences ) {
889
+
890
+ _morphA.set( 0, 0, 0 );
891
+ _morphB.set( 0, 0, 0 );
892
+ _morphC.set( 0, 0, 0 );
893
+
894
+ for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) {
895
+
896
+ const influence = morphInfluences[ i ];
897
+ const morph = morphAttribute[ i ];
898
+
899
+ if ( influence === 0 ) continue;
900
+
901
+ _tempA.fromBufferAttribute( morph, a );
902
+ _tempB.fromBufferAttribute( morph, b );
903
+ _tempC.fromBufferAttribute( morph, c );
904
+
905
+ if ( morphTargetsRelative ) {
906
+
907
+ _morphA.addScaledVector( _tempA, influence );
908
+ _morphB.addScaledVector( _tempB, influence );
909
+ _morphC.addScaledVector( _tempC, influence );
910
+
911
+ } else {
912
+
913
+ _morphA.addScaledVector( _tempA.sub( _vA ), influence );
914
+ _morphB.addScaledVector( _tempB.sub( _vB ), influence );
915
+ _morphC.addScaledVector( _tempC.sub( _vC ), influence );
916
+
917
+ }
918
+
919
+ }
920
+
921
+ _vA.add( _morphA );
922
+ _vB.add( _morphB );
923
+ _vC.add( _morphC );
924
+
925
+ }
926
+
927
+ if ( object.isSkinnedMesh ) {
928
+
929
+ object.applyBoneTransform( a, _vA );
930
+ object.applyBoneTransform( b, _vB );
931
+ object.applyBoneTransform( c, _vC );
932
+
933
+ }
934
+
935
+ modifiedAttributeArray[ a * 3 + 0 ] = _vA.x;
936
+ modifiedAttributeArray[ a * 3 + 1 ] = _vA.y;
937
+ modifiedAttributeArray[ a * 3 + 2 ] = _vA.z;
938
+ modifiedAttributeArray[ b * 3 + 0 ] = _vB.x;
939
+ modifiedAttributeArray[ b * 3 + 1 ] = _vB.y;
940
+ modifiedAttributeArray[ b * 3 + 2 ] = _vB.z;
941
+ modifiedAttributeArray[ c * 3 + 0 ] = _vC.x;
942
+ modifiedAttributeArray[ c * 3 + 1 ] = _vC.y;
943
+ modifiedAttributeArray[ c * 3 + 2 ] = _vC.z;
944
+
945
+ }
946
+
947
+ const geometry = object.geometry;
948
+ const material = object.material;
949
+
950
+ let a, b, c;
951
+ const index = geometry.index;
952
+ const positionAttribute = geometry.attributes.position;
953
+ const morphPosition = geometry.morphAttributes.position;
954
+ const morphTargetsRelative = geometry.morphTargetsRelative;
955
+ const normalAttribute = geometry.attributes.normal;
956
+ const morphNormal = geometry.morphAttributes.position;
957
+
958
+ const groups = geometry.groups;
959
+ const drawRange = geometry.drawRange;
960
+ let i, j, il, jl;
961
+ let group;
962
+ let start, end;
963
+
964
+ const modifiedPosition = new Float32Array( positionAttribute.count * positionAttribute.itemSize );
965
+ const modifiedNormal = new Float32Array( normalAttribute.count * normalAttribute.itemSize );
966
+
967
+ if ( index !== null ) {
968
+
969
+ // indexed buffer geometry
970
+
971
+ if ( Array.isArray( material ) ) {
972
+
973
+ for ( i = 0, il = groups.length; i < il; i ++ ) {
974
+
975
+ group = groups[ i ];
976
+
977
+ start = Math.max( group.start, drawRange.start );
978
+ end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
979
+
980
+ for ( j = start, jl = end; j < jl; j += 3 ) {
981
+
982
+ a = index.getX( j );
983
+ b = index.getX( j + 1 );
984
+ c = index.getX( j + 2 );
985
+
986
+ _calculateMorphedAttributeData(
987
+ object,
988
+ positionAttribute,
989
+ morphPosition,
990
+ morphTargetsRelative,
991
+ a, b, c,
992
+ modifiedPosition
993
+ );
994
+
995
+ _calculateMorphedAttributeData(
996
+ object,
997
+ normalAttribute,
998
+ morphNormal,
999
+ morphTargetsRelative,
1000
+ a, b, c,
1001
+ modifiedNormal
1002
+ );
1003
+
1004
+ }
1005
+
1006
+ }
1007
+
1008
+ } else {
1009
+
1010
+ start = Math.max( 0, drawRange.start );
1011
+ end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
1012
+
1013
+ for ( i = start, il = end; i < il; i += 3 ) {
1014
+
1015
+ a = index.getX( i );
1016
+ b = index.getX( i + 1 );
1017
+ c = index.getX( i + 2 );
1018
+
1019
+ _calculateMorphedAttributeData(
1020
+ object,
1021
+ positionAttribute,
1022
+ morphPosition,
1023
+ morphTargetsRelative,
1024
+ a, b, c,
1025
+ modifiedPosition
1026
+ );
1027
+
1028
+ _calculateMorphedAttributeData(
1029
+ object,
1030
+ normalAttribute,
1031
+ morphNormal,
1032
+ morphTargetsRelative,
1033
+ a, b, c,
1034
+ modifiedNormal
1035
+ );
1036
+
1037
+ }
1038
+
1039
+ }
1040
+
1041
+ } else {
1042
+
1043
+ // non-indexed buffer geometry
1044
+
1045
+ if ( Array.isArray( material ) ) {
1046
+
1047
+ for ( i = 0, il = groups.length; i < il; i ++ ) {
1048
+
1049
+ group = groups[ i ];
1050
+
1051
+ start = Math.max( group.start, drawRange.start );
1052
+ end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
1053
+
1054
+ for ( j = start, jl = end; j < jl; j += 3 ) {
1055
+
1056
+ a = j;
1057
+ b = j + 1;
1058
+ c = j + 2;
1059
+
1060
+ _calculateMorphedAttributeData(
1061
+ object,
1062
+ positionAttribute,
1063
+ morphPosition,
1064
+ morphTargetsRelative,
1065
+ a, b, c,
1066
+ modifiedPosition
1067
+ );
1068
+
1069
+ _calculateMorphedAttributeData(
1070
+ object,
1071
+ normalAttribute,
1072
+ morphNormal,
1073
+ morphTargetsRelative,
1074
+ a, b, c,
1075
+ modifiedNormal
1076
+ );
1077
+
1078
+ }
1079
+
1080
+ }
1081
+
1082
+ } else {
1083
+
1084
+ start = Math.max( 0, drawRange.start );
1085
+ end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
1086
+
1087
+ for ( i = start, il = end; i < il; i += 3 ) {
1088
+
1089
+ a = i;
1090
+ b = i + 1;
1091
+ c = i + 2;
1092
+
1093
+ _calculateMorphedAttributeData(
1094
+ object,
1095
+ positionAttribute,
1096
+ morphPosition,
1097
+ morphTargetsRelative,
1098
+ a, b, c,
1099
+ modifiedPosition
1100
+ );
1101
+
1102
+ _calculateMorphedAttributeData(
1103
+ object,
1104
+ normalAttribute,
1105
+ morphNormal,
1106
+ morphTargetsRelative,
1107
+ a, b, c,
1108
+ modifiedNormal
1109
+ );
1110
+
1111
+ }
1112
+
1113
+ }
1114
+
1115
+ }
1116
+
1117
+ const morphedPositionAttribute = new Float32BufferAttribute( modifiedPosition, 3 );
1118
+ const morphedNormalAttribute = new Float32BufferAttribute( modifiedNormal, 3 );
1119
+
1120
+ return {
1121
+
1122
+ positionAttribute: positionAttribute,
1123
+ normalAttribute: normalAttribute,
1124
+ morphedPositionAttribute: morphedPositionAttribute,
1125
+ morphedNormalAttribute: morphedNormalAttribute
1126
+
1127
+ };
1128
+
1129
+ }
1130
+
1131
+ function mergeGroups( geometry ) {
1132
+
1133
+ if ( geometry.groups.length === 0 ) {
1134
+
1135
+ console.warn( 'THREE.BufferGeometryUtils.mergeGroups(): No groups are defined. Nothing to merge.' );
1136
+ return geometry;
1137
+
1138
+ }
1139
+
1140
+ let groups = geometry.groups;
1141
+
1142
+ // sort groups by material index
1143
+
1144
+ groups = groups.sort( ( a, b ) => {
1145
+
1146
+ if ( a.materialIndex !== b.materialIndex ) return a.materialIndex - b.materialIndex;
1147
+
1148
+ return a.start - b.start;
1149
+
1150
+ } );
1151
+
1152
+ // create index for non-indexed geometries
1153
+
1154
+ if ( geometry.getIndex() === null ) {
1155
+
1156
+ const positionAttribute = geometry.getAttribute( 'position' );
1157
+ const indices = [];
1158
+
1159
+ for ( let i = 0; i < positionAttribute.count; i += 3 ) {
1160
+
1161
+ indices.push( i, i + 1, i + 2 );
1162
+
1163
+ }
1164
+
1165
+ geometry.setIndex( indices );
1166
+
1167
+ }
1168
+
1169
+ // sort index
1170
+
1171
+ const index = geometry.getIndex();
1172
+
1173
+ const newIndices = [];
1174
+
1175
+ for ( let i = 0; i < groups.length; i ++ ) {
1176
+
1177
+ const group = groups[ i ];
1178
+
1179
+ const groupStart = group.start;
1180
+ const groupLength = groupStart + group.count;
1181
+
1182
+ for ( let j = groupStart; j < groupLength; j ++ ) {
1183
+
1184
+ newIndices.push( index.getX( j ) );
1185
+
1186
+ }
1187
+
1188
+ }
1189
+
1190
+ geometry.dispose(); // Required to force buffer recreation
1191
+ geometry.setIndex( newIndices );
1192
+
1193
+ // update groups indices
1194
+
1195
+ let start = 0;
1196
+
1197
+ for ( let i = 0; i < groups.length; i ++ ) {
1198
+
1199
+ const group = groups[ i ];
1200
+
1201
+ group.start = start;
1202
+ start += group.count;
1203
+
1204
+ }
1205
+
1206
+ // merge groups
1207
+
1208
+ let currentGroup = groups[ 0 ];
1209
+
1210
+ geometry.groups = [ currentGroup ];
1211
+
1212
+ for ( let i = 1; i < groups.length; i ++ ) {
1213
+
1214
+ const group = groups[ i ];
1215
+
1216
+ if ( currentGroup.materialIndex === group.materialIndex ) {
1217
+
1218
+ currentGroup.count += group.count;
1219
+
1220
+ } else {
1221
+
1222
+ currentGroup = group;
1223
+ geometry.groups.push( currentGroup );
1224
+
1225
+ }
1226
+
1227
+ }
1228
+
1229
+ return geometry;
1230
+
1231
+ }
1232
+
1233
+
1234
+ /**
1235
+ * Modifies the supplied geometry if it is non-indexed, otherwise creates a new,
1236
+ * non-indexed geometry. Returns the geometry with smooth normals everywhere except
1237
+ * faces that meet at an angle greater than the crease angle.
1238
+ *
1239
+ * @param {BufferGeometry} geometry
1240
+ * @param {number} [creaseAngle]
1241
+ * @return {BufferGeometry}
1242
+ */
1243
+ function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ ) {
1244
+
1245
+ const creaseDot = Math.cos( creaseAngle );
1246
+ const hashMultiplier = ( 1 + 1e-10 ) * 1e2;
1247
+
1248
+ // reusable vectors
1249
+ const verts = [ new Vector3(), new Vector3(), new Vector3() ];
1250
+ const tempVec1 = new Vector3();
1251
+ const tempVec2 = new Vector3();
1252
+ const tempNorm = new Vector3();
1253
+ const tempNorm2 = new Vector3();
1254
+
1255
+ // hashes a vector
1256
+ function hashVertex( v ) {
1257
+
1258
+ const x = ~ ~ ( v.x * hashMultiplier );
1259
+ const y = ~ ~ ( v.y * hashMultiplier );
1260
+ const z = ~ ~ ( v.z * hashMultiplier );
1261
+ return `${x},${y},${z}`;
1262
+
1263
+ }
1264
+
1265
+ // BufferGeometry.toNonIndexed() warns if the geometry is non-indexed
1266
+ // and returns the original geometry
1267
+ const resultGeometry = geometry.index ? geometry.toNonIndexed() : geometry;
1268
+ const posAttr = resultGeometry.attributes.position;
1269
+ const vertexMap = {};
1270
+
1271
+ // find all the normals shared by commonly located vertices
1272
+ for ( let i = 0, l = posAttr.count / 3; i < l; i ++ ) {
1273
+
1274
+ const i3 = 3 * i;
1275
+ const a = verts[ 0 ].fromBufferAttribute( posAttr, i3 + 0 );
1276
+ const b = verts[ 1 ].fromBufferAttribute( posAttr, i3 + 1 );
1277
+ const c = verts[ 2 ].fromBufferAttribute( posAttr, i3 + 2 );
1278
+
1279
+ tempVec1.subVectors( c, b );
1280
+ tempVec2.subVectors( a, b );
1281
+
1282
+ // add the normal to the map for all vertices
1283
+ const normal = new Vector3().crossVectors( tempVec1, tempVec2 ).normalize();
1284
+ for ( let n = 0; n < 3; n ++ ) {
1285
+
1286
+ const vert = verts[ n ];
1287
+ const hash = hashVertex( vert );
1288
+ if ( ! ( hash in vertexMap ) ) {
1289
+
1290
+ vertexMap[ hash ] = [];
1291
+
1292
+ }
1293
+
1294
+ vertexMap[ hash ].push( normal );
1295
+
1296
+ }
1297
+
1298
+ }
1299
+
1300
+ // average normals from all vertices that share a common location if they are within the
1301
+ // provided crease threshold
1302
+ const normalArray = new Float32Array( posAttr.count * 3 );
1303
+ const normAttr = new BufferAttribute( normalArray, 3, false );
1304
+ for ( let i = 0, l = posAttr.count / 3; i < l; i ++ ) {
1305
+
1306
+ // get the face normal for this vertex
1307
+ const i3 = 3 * i;
1308
+ const a = verts[ 0 ].fromBufferAttribute( posAttr, i3 + 0 );
1309
+ const b = verts[ 1 ].fromBufferAttribute( posAttr, i3 + 1 );
1310
+ const c = verts[ 2 ].fromBufferAttribute( posAttr, i3 + 2 );
1311
+
1312
+ tempVec1.subVectors( c, b );
1313
+ tempVec2.subVectors( a, b );
1314
+
1315
+ tempNorm.crossVectors( tempVec1, tempVec2 ).normalize();
1316
+
1317
+ // average all normals that meet the threshold and set the normal value
1318
+ for ( let n = 0; n < 3; n ++ ) {
1319
+
1320
+ const vert = verts[ n ];
1321
+ const hash = hashVertex( vert );
1322
+ const otherNormals = vertexMap[ hash ];
1323
+ tempNorm2.set( 0, 0, 0 );
1324
+
1325
+ for ( let k = 0, lk = otherNormals.length; k < lk; k ++ ) {
1326
+
1327
+ const otherNorm = otherNormals[ k ];
1328
+ if ( tempNorm.dot( otherNorm ) > creaseDot ) {
1329
+
1330
+ tempNorm2.add( otherNorm );
1331
+
1332
+ }
1333
+
1334
+ }
1335
+
1336
+ tempNorm2.normalize();
1337
+ normAttr.setXYZ( i3 + n, tempNorm2.x, tempNorm2.y, tempNorm2.z );
1338
+
1339
+ }
1340
+
1341
+ }
1342
+
1343
+ resultGeometry.setAttribute( 'normal', normAttr );
1344
+ return resultGeometry;
1345
+
1346
+ }
1347
+
1348
+ function mergeBufferGeometries( geometries, useGroups = false ) {
1349
+
1350
+ console.warn( 'THREE.BufferGeometryUtils: mergeBufferGeometries() has been renamed to mergeGeometries().' ); // @deprecated, r151
1351
+ return mergeGeometries( geometries, useGroups );
1352
+
1353
+ }
1354
+
1355
+ function mergeBufferAttributes( attributes ) {
1356
+
1357
+ console.warn( 'THREE.BufferGeometryUtils: mergeBufferAttributes() has been renamed to mergeAttributes().' ); // @deprecated, r151
1358
+ return mergeAttributes( attributes );
1359
+
1360
+ }
1361
+
1362
+ export {
1363
+ computeMikkTSpaceTangents,
1364
+ mergeGeometries,
1365
+ mergeBufferGeometries,
1366
+ mergeAttributes,
1367
+ mergeBufferAttributes,
1368
+ interleaveAttributes,
1369
+ estimateBytesUsed,
1370
+ mergeVertices,
1371
+ toTrianglesDrawMode,
1372
+ computeMorphedAttributes,
1373
+ mergeGroups,
1374
+ toCreasedNormals
1375
+ };
assets/vendor/three-r160/build/three.module.js ADDED
The diff for this file is too large to render. See raw diff
 
cases.html CHANGED
@@ -82,7 +82,7 @@ table.cs td,table.cs th{white-space:nowrap}.panel code{font-size:.82em;backgroun
82
  .in-open{border:1px solid var(--primary);background:#fff;color:var(--primary-dark);border-radius:8px;padding:.45rem .9rem;font:600 .85rem Inter,sans-serif;cursor:pointer}
83
  .in-open:hover{background:var(--primary);color:#fff}
84
  @media(max-width:900px){.in-i2s{grid-template-columns:1fr}}
85
- </style><script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"}}</script><link href="assets/buildingbench.css?v=20261001" rel="stylesheet"/></head><body class="case-page"><a class="skip-link" href="#casebar">Skip to benchmark explorer</a>
86
  <header class="site-header"><div class="brand"><a aria-label="Code4Scene home" class="brand-home" href="index.html#top"><svg aria-hidden="true" fill="none" viewbox="0 0 30 32"><path d="M15 2 27 9v14l-12 7L3 23V9L15 2Z M3 9l12 7 12-7 M15 16v14 M9 5.5l12 7v14 M21 5.5l-12 7v14" stroke="currentColor" stroke-linejoin="round" stroke-width="1.6"></path></svg><span>Code4Scene</span></a><a class="brand-by" href="https://simworld.org/">by SimWorld</a></div><nav aria-label="Main navigation"><a href="index.html#top">Benchmark</a><a href="index.html#leaderboard">Leaderboard</a><a class="nav-active" href="index.html#cases">Visualization</a><a href="https://arxiv.org/abs/2609.36777" rel="noopener" target="_blank">Paper ↗</a><a href="https://github.com/SimWorld-AI/Code4Scene" rel="noopener" target="_blank">Github ↗</a></nav></header>
87
  <section class="alt" style="padding-top:34px;padding-bottom:40px"><div class="container">
88
  <div class="section-header" style="margin-bottom:1.6rem"><span class="section-label">Cases</span><h2 class="section-title">Every scene in 3D, with its scores</h2>
 
82
  .in-open{border:1px solid var(--primary);background:#fff;color:var(--primary-dark);border-radius:8px;padding:.45rem .9rem;font:600 .85rem Inter,sans-serif;cursor:pointer}
83
  .in-open:hover{background:var(--primary);color:#fff}
84
  @media(max-width:900px){.in-i2s{grid-template-columns:1fr}}
85
+ </style><script type="importmap">{"imports":{"three":"./assets/vendor/three-r160/build/three.module.js","three/addons/":"./assets/vendor/three-r160/addons/"}}</script><link href="assets/buildingbench.css?v=20261001" rel="stylesheet"/></head><body class="case-page"><a class="skip-link" href="#casebar">Skip to benchmark explorer</a>
86
  <header class="site-header"><div class="brand"><a aria-label="Code4Scene home" class="brand-home" href="index.html#top"><svg aria-hidden="true" fill="none" viewbox="0 0 30 32"><path d="M15 2 27 9v14l-12 7L3 23V9L15 2Z M3 9l12 7 12-7 M15 16v14 M9 5.5l12 7v14 M21 5.5l-12 7v14" stroke="currentColor" stroke-linejoin="round" stroke-width="1.6"></path></svg><span>Code4Scene</span></a><a class="brand-by" href="https://simworld.org/">by SimWorld</a></div><nav aria-label="Main navigation"><a href="index.html#top">Benchmark</a><a href="index.html#leaderboard">Leaderboard</a><a class="nav-active" href="index.html#cases">Visualization</a><a href="https://arxiv.org/abs/2609.36777" rel="noopener" target="_blank">Paper ↗</a><a href="https://github.com/SimWorld-AI/Code4Scene" rel="noopener" target="_blank">Github ↗</a></nav></header>
87
  <section class="alt" style="padding-top:34px;padding-bottom:40px"><div class="container">
88
  <div class="section-header" style="margin-bottom:1.6rem"><span class="section-label">Cases</span><h2 class="section-title">Every scene in 3D, with its scores</h2>
data/progressive-scenes.json ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "38071c69a86d174dc55f.bin": {
3
+ "geometry": "models/progressive-v1/geo-b0e37abcf8f8538d5c7c567d.bin",
4
+ "originalBytes": 7634384,
5
+ "geometryBytes": 5524292,
6
+ "textureBytes": 2144862,
7
+ "images": 286,
8
+ "bundles": 9
9
+ },
10
+ "46734878f740aae816d8.bin": {
11
+ "geometry": "models/progressive-v1/geo-cfc8f9530bbdf82e9ad5803e.bin",
12
+ "originalBytes": 8173240,
13
+ "geometryBytes": 6144796,
14
+ "textureBytes": 2056820,
15
+ "images": 232,
16
+ "bundles": 8
17
+ },
18
+ "66da6868d20ebb100197.bin": {
19
+ "geometry": "models/progressive-v1/geo-2cb0699fcdfbb0077b14a03a.bin",
20
+ "originalBytes": 7634592,
21
+ "geometryBytes": 5524504,
22
+ "textureBytes": 2144862,
23
+ "images": 286,
24
+ "bundles": 9
25
+ },
26
+ "6afbbaec6b7a4271b21f.bin": {
27
+ "geometry": "models/progressive-v1/geo-ab194913839cc37985edc5c8.bin",
28
+ "originalBytes": 7634280,
29
+ "geometryBytes": 5524192,
30
+ "textureBytes": 2144862,
31
+ "images": 286,
32
+ "bundles": 9
33
+ },
34
+ "a32fa4ebc640e2278635.bin": {
35
+ "geometry": "models/progressive-v1/geo-6917456ac7ecb3f644ad6e37.bin",
36
+ "originalBytes": 2649336,
37
+ "geometryBytes": 1790032,
38
+ "textureBytes": 869844,
39
+ "images": 86,
40
+ "bundles": 3
41
+ },
42
+ "cb082e689f15e654daab.bin": {
43
+ "geometry": "models/progressive-v1/geo-8d8ac2389fb2144d0ada046a.bin",
44
+ "originalBytes": 11484648,
45
+ "geometryBytes": 9642436,
46
+ "textureBytes": 1864432,
47
+ "images": 188,
48
+ "bundles": 6
49
+ },
50
+ "cc1bdce0533a50e81385.bin": {
51
+ "geometry": "models/progressive-v1/geo-054a9fa272d6216f02828f09.bin",
52
+ "originalBytes": 4680596,
53
+ "geometryBytes": 2348676,
54
+ "textureBytes": 2364754,
55
+ "images": 278,
56
+ "bundles": 9
57
+ },
58
+ "db5c0c5d2b78f9e47bb7.bin": {
59
+ "geometry": "models/progressive-v1/geo-fbdfbee814c5ec5bbbd7b049.bin",
60
+ "originalBytes": 8173352,
61
+ "geometryBytes": 6144908,
62
+ "textureBytes": 2056820,
63
+ "images": 232,
64
+ "bundles": 8
65
+ },
66
+ "e6a6333939ccfe57d584.bin": {
67
+ "geometry": "models/progressive-v1/geo-f52df859622b51e5a8e79b7f.bin",
68
+ "originalBytes": 8173556,
69
+ "geometryBytes": 6145112,
70
+ "textureBytes": 2056820,
71
+ "images": 232,
72
+ "bundles": 8
73
+ },
74
+ "fa367ea5d33ed24f577a.bin": {
75
+ "geometry": "models/progressive-v1/geo-9e85a029420d9485d131d029.bin",
76
+ "originalBytes": 2822472,
77
+ "geometryBytes": 1888144,
78
+ "textureBytes": 947084,
79
+ "images": 105,
80
+ "bundles": 4
81
+ },
82
+ "feb923d4f4f257315904.bin": {
83
+ "geometry": "models/progressive-v1/geo-fc7f349eacd0387d378791ba.bin",
84
+ "originalBytes": 4280676,
85
+ "geometryBytes": 3122684,
86
+ "textureBytes": 1170514,
87
+ "images": 106,
88
+ "bundles": 4
89
+ }
90
+ }
index.html CHANGED
@@ -19,7 +19,7 @@ svg.ch .dim{opacity:.42}svg.ch g.row:hover{opacity:1}svg.ch .hot{fill:var(--c4s-
19
  .chip.ok{color:var(--c4s-ok);border-color:currentColor}.chip.no{color:var(--c4s-hot);border-color:currentColor}.chip small{font-weight:500;color:var(--text-muted);margin-left:4px}
20
  .bigstats{display:flex;flex-wrap:wrap;gap:12px 34px;margin:0 0 12px}.bigstats b{display:block;font-size:30px;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
21
  .bigstats span{font-size:13px;color:var(--text-secondary)}
22
- </style><link href="assets/buildingbench.css?v=20261001e" rel="stylesheet"/><script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"}}</script></head><body><a class="skip-link" href="#leaderboard">Skip to benchmark explorer</a>
23
  <header class="site-header"><div class="brand"><a aria-label="Code4Scene home" class="brand-home" href="index.html#top"><svg aria-hidden="true" fill="none" viewBox="0 0 30 32"><path d="M15 2 27 9v14l-12 7L3 23V9L15 2Z M3 9l12 7 12-7 M15 16v14 M9 5.5l12 7v14 M21 5.5l-12 7v14" stroke="currentColor" stroke-linejoin="round" stroke-width="1.6"></path></svg><span>Code4Scene</span></a><a class="brand-by" href="https://simworld.org/">by SimWorld</a></div><nav aria-label="Main navigation"><a class="nav-active" href="index.html#top">Benchmark</a><a href="index.html#leaderboard">Leaderboard</a><a href="index.html#cases">Visualization</a><a href="https://arxiv.org/abs/2609.36777" rel="noopener" target="_blank">Paper ↗</a><a href="https://github.com/SimWorld-AI/Code4Scene" rel="noopener" target="_blank">Github ↗</a></nav></header><main class="page-main"><section class="intro" id="top"><div class="intro-copy"><h1>Constructing and Editing<br/><em>3D Scenes with Code</em></h1><p class="intro-text">Benchmarking coding agents that turn text and reference images into engine-native 3D scenes. Measuring spatial reasoning, task fulfillment and precise control of scene state.</p><div class="intro-links"><a href="https://arxiv.org/abs/2609.36777">Read the paper ↗</a><a href="https://github.com/SimWorld-AI/Code4Scene">Code &amp; Data ↗</a><a href="#hero-video">Watch the teaser ↘</a></div></div><div class="featured featured-video" id="hero-video"><video aria-label="Code4Scene overview video" controls="" playsinline="" poster="media/teaser-poster.jpg" preload="metadata"><source src="media/teaser.mp4" type="video/mp4"/>Your browser does not support embedded video. <a href="media/teaser.mp4">Watch the Code4Scene video</a>.</video><div class="featured-video-caption"><strong>Code4Scene in 90 seconds</strong><span>Construction, editing and evaluation</span></div></div></section><section class="task-section" id="task-format"><div class="container"><h2 class="section-title">Task Format</h2><div class="task-shell"><div aria-label="Task format" class="task-tabs" role="tablist"><button aria-controls="task-t2s" aria-selected="true" data-task="t2s" id="task-tab-t2s" role="tab">T2S · Construction</button><button aria-controls="task-i2s" aria-selected="false" data-task="i2s" id="task-tab-i2s" role="tab" tabindex="-1">I2S · Editing</button></div><div aria-labelledby="task-tab-t2s" class="task-content" data-task="t2s" id="task-t2s" role="tabpanel"><h3>Nordic Harbour <span>· Text-to-Scene</span></h3><p class="task-subtitle">An open-ended scene description, an asset catalog and the Unreal Engine editor.</p><div class="task-columns"><article class="task-panel"><div class="panel-heading"><strong>Agent input</strong><span>Text + assets</span></div><div class="prompt-preview"><span class="eyebrow">Task instruction</span><p>Build me a compact, sunlit Copenhagen-style canal district with narrow cobblestone streets and rectangular waterways enclosing dense urban blocks.</p><p class="prompt-detail">Townhouses, canals, bridges and a waterfront promenade — with detailed requirements for materials, layout and street furniture.</p></div><p class="task-foot"><strong>Given:</strong> an empty level and the content pack’s asset catalog.</p></article><article class="task-panel output-panel"><div class="panel-heading"><strong>Agent output</strong><span>GPT-6 Astra</span></div><div class="live-scene" data-agent="astra" data-case="nordic-harbour"><canvas aria-label="Interactive 3D scene: Nordic Harbour. Drag to orbit, scroll or use plus and minus to zoom." tabindex="0"></canvas><span class="scene-loading" role="status">Loading 3D scene…</span><span class="scene-hint">Drag to orbit · Scroll to zoom</span><button aria-label="Pause scene rotation" class="scene-motion">Ⅱ</button></div><a class="task-foot text-button" href="cases.html#nordic-harbour">Compare agents and evaluator views ↗</a></article><article class="task-panel evaluation-panel"><div class="panel-heading"><strong>Evaluation</strong><span>Engine-native scene</span></div><div class="score-block"><span>CASE SCORE</span><strong>0.877</strong><dl><div><dt>Detailed Alignment</dt><dd>0.757</dd></div><div><dt>Overview Alignment</dt><dd>0.909</dd></div><div><dt>Physical Safety</dt><dd>0.900</dd></div></dl></div><p class="task-foot">Task fulfillment, artifact integrity and static physical validity.</p><p class="task-formula">0.2 × Detailed + 0.6 × Overview + 0.2 × Physical</p></article></div><details class="task-full"><summary><span>+ Full task instruction</span><span>TEXT-TO-SCENE</span></summary><p>Build me a compact, sunlit Copenhagen-style canal district with narrow cobblestone streets and rectangular waterways enclosing dense urban blocks. Arrange roughly thirty attached four- to six-storey townhouses in continuous street and canal-front rows, using slender façades, repetitive white-framed windows, arched doors and carriage entrances, modest cornices, and steep tiled roofs packed with dormers and brick chimneys. Include occasional exposed party walls between staggered building rows. Finish the façades in weathered muted red, mustard yellow, dusty blue, warm grey, beige, and exposed brick, with red, orange, and charcoal roof tiles. Run a principal waterfront street along a stone quay, crossed by smaller streets and linked across the canals by short, gently arched stone-and-metal bridges, including one opening onto a broad paved promenade with rounded landings. Form the canal edges from stepped pale stone, dark timber retaining walls, and iron railings around reflective rippling water. Add regularly spaced leafy trees, ornate black lamps, stone bollards, benches, European road signs, zebra crossings, puddles, grime, and sparse weeds.</p></details></div><div aria-labelledby="task-tab-i2s" class="task-content" data-task="i2s" hidden="" id="task-i2s" role="tabpanel"><h3>Old Industrial Pallet Bay <span>· Image-to-Scene</span></h3><p class="task-subtitle">Repair the corrupted scene using its reference image; preserve all unrelated actors.</p><div class="task-columns"><article class="task-panel input-panel"><div class="panel-heading"><strong>Agent input</strong><span>Scene + reference image</span></div><div class="live-scene" data-agent="astra" data-case="comp-31-old-industrial-pallet-bay-repair-v2" data-state="input"><canvas aria-label="Interactive 3D: Old Industrial Pallet Bay, input. Drag to orbit; scroll to zoom." tabindex="0"></canvas><span class="scene-loading" role="status">Loading 3D scene…</span><span class="scene-hint">Drag to orbit · Scroll to zoom</span><button aria-label="Resume scene rotation" class="scene-motion">▶</button></div><a class="task-reference" href="media/i2s/comp-31-old-industrial-pallet-bay-repair-v2/ref-1.jpg?v=1790549706" rel="noopener" target="_blank"><img alt="Provided reference image for Old Industrial Pallet Bay" loading="lazy" src="media/i2s/comp-31-old-industrial-pallet-bay-repair-v2/ref-1.jpg?v=1790549706"/><span>Provided reference image ↗</span></a><p class="task-foot">A translated and rotated pallet stack, plus an unintended duplicate.</p></article><article class="task-panel output-panel"><div class="panel-heading"><strong>Agent output</strong><span>GPT-6 Astra</span></div><div class="live-scene" data-agent="astra" data-case="comp-31-old-industrial-pallet-bay-repair-v2" data-state="repair"><canvas aria-label="Interactive 3D: Old Industrial Pallet Bay, repair. Drag to orbit; scroll to zoom." tabindex="0"></canvas><span class="scene-loading" role="status">Loading 3D scene…</span><span class="scene-hint">Drag to orbit · Scroll to zoom</span><button aria-label="Resume scene rotation" class="scene-motion">▶</button></div><div aria-label="Output scene state" class="scene-versions"><button class="on" data-state="repair">Repair</button><button data-state="input">Corrupted input</button><button data-state="gt">Ground truth</button></div><p class="task-foot">Inspect the saved repair from the same reference camera.</p><a class="text-button task-foot" href="cases.html#comp-31-old-industrial-pallet-bay-repair-v2">Compare agents and evaluator views ↗</a></article><article class="task-panel evaluation-panel"><div class="panel-heading"><strong>Evaluation</strong><span>Actor-level repair</span></div><div class="score-block"><span>CASE SCORE</span><strong>1.000</strong><dl><div><dt>Repair F1</dt><dd>1.000</dd></div><div><dt>Physical Safety</dt><dd>1.000</dd></div><div><dt>TP / FP / FN</dt><dd>2 / 0 / 0</dd></div></dl></div><p class="task-foot">Restore each target within 5 cm, 5° and 5%, without unintended edits.</p><p class="task-formula">0.8 × Repair F1 + 0.2 × Physical</p></article></div><details class="task-full"><summary><span>+ Full task instruction</span><span>IMAGE-TO-SCENE</span></summary><p>Repair the current outdoor scene so it matches the provided reference image. Make only the minimum changes needed, and preserve all unrelated actors and properties.</p></details></div></div><div class="availability"><span><b>320</b> full benchmark target · 160 construction + 160 editing, including private cases</span><span><b>201</b> public cases · 129 construction + 72 editing (22 indoor, 50 outdoor)</span></div></div></section><section id="leaderboard"><div class="container">
24
  <div class="section-header centered"><span class="section-label">Leaderboard</span><h2 class="section-title">Leaderboard</h2>
25
  <p class="section-desc">14 coding-agent configurations · paper results on the original 95 public cases (20 construction + 75 editing). These results predate the 201-case public release.</p></div>
@@ -58,4 +58,4 @@ svg.ch .dim{opacity:.42}svg.ch g.row:hover{opacity:1}svg.ch .hot{fill:var(--c4s-
58
  <a href="https://simworld.org/" rel="noopener" target="_blank">SimWorld Home</a></p>
59
  <p style="margin-top:.5rem;font-size:.75rem;color:rgba(255,255,255,.25)">Part of the SimWorld project.</p></footer>
60
  <script src="assets/paper.js?v=5427505a"></script>
61
- <script defer="" src="assets/leaderboard-bars.js?v=20261001c"></script><script src="data/site.js?v=20261001b"></script><script src="assets/pareto.js?v=20261001c" type="module"></script><script src="assets/scene-cards.js?v=20261001c" type="module"></script><script src="assets/task-tabs.js?v=20261001c" type="module"></script><script src="assets/leaderboard-controls.js?v=20261001c" type="module"></script></body></html>
 
19
  .chip.ok{color:var(--c4s-ok);border-color:currentColor}.chip.no{color:var(--c4s-hot);border-color:currentColor}.chip small{font-weight:500;color:var(--text-muted);margin-left:4px}
20
  .bigstats{display:flex;flex-wrap:wrap;gap:12px 34px;margin:0 0 12px}.bigstats b{display:block;font-size:30px;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
21
  .bigstats span{font-size:13px;color:var(--text-secondary)}
22
+ </style><link href="assets/buildingbench.css?v=20261002a" rel="stylesheet"/><script type="importmap">{"imports":{"three":"./assets/vendor/three-r160/build/three.module.js","three/addons/":"./assets/vendor/three-r160/addons/"}}</script></head><body><a class="skip-link" href="#leaderboard">Skip to benchmark explorer</a>
23
  <header class="site-header"><div class="brand"><a aria-label="Code4Scene home" class="brand-home" href="index.html#top"><svg aria-hidden="true" fill="none" viewBox="0 0 30 32"><path d="M15 2 27 9v14l-12 7L3 23V9L15 2Z M3 9l12 7 12-7 M15 16v14 M9 5.5l12 7v14 M21 5.5l-12 7v14" stroke="currentColor" stroke-linejoin="round" stroke-width="1.6"></path></svg><span>Code4Scene</span></a><a class="brand-by" href="https://simworld.org/">by SimWorld</a></div><nav aria-label="Main navigation"><a class="nav-active" href="index.html#top">Benchmark</a><a href="index.html#leaderboard">Leaderboard</a><a href="index.html#cases">Visualization</a><a href="https://arxiv.org/abs/2609.36777" rel="noopener" target="_blank">Paper ↗</a><a href="https://github.com/SimWorld-AI/Code4Scene" rel="noopener" target="_blank">Github ↗</a></nav></header><main class="page-main"><section class="intro" id="top"><div class="intro-copy"><h1>Constructing and Editing<br/><em>3D Scenes with Code</em></h1><p class="intro-text">Benchmarking coding agents that turn text and reference images into engine-native 3D scenes. Measuring spatial reasoning, task fulfillment and precise control of scene state.</p><div class="intro-links"><a href="https://arxiv.org/abs/2609.36777">Read the paper ↗</a><a href="https://github.com/SimWorld-AI/Code4Scene">Code &amp; Data ↗</a><a href="#hero-video">Watch the teaser ↘</a></div></div><div class="featured featured-video" id="hero-video"><video aria-label="Code4Scene overview video" controls="" playsinline="" poster="media/teaser-poster.jpg" preload="metadata"><source src="media/teaser.mp4" type="video/mp4"/>Your browser does not support embedded video. <a href="media/teaser.mp4">Watch the Code4Scene video</a>.</video><div class="featured-video-caption"><strong>Code4Scene in 90 seconds</strong><span>Construction, editing and evaluation</span></div></div></section><section class="task-section" id="task-format"><div class="container"><h2 class="section-title">Task Format</h2><div class="task-shell"><div aria-label="Task format" class="task-tabs" role="tablist"><button aria-controls="task-t2s" aria-selected="true" data-task="t2s" id="task-tab-t2s" role="tab">T2S · Construction</button><button aria-controls="task-i2s" aria-selected="false" data-task="i2s" id="task-tab-i2s" role="tab" tabindex="-1">I2S · Editing</button></div><div aria-labelledby="task-tab-t2s" class="task-content" data-task="t2s" id="task-t2s" role="tabpanel"><h3>Nordic Harbour <span>· Text-to-Scene</span></h3><p class="task-subtitle">An open-ended scene description, an asset catalog and the Unreal Engine editor.</p><div class="task-columns"><article class="task-panel"><div class="panel-heading"><strong>Agent input</strong><span>Text + assets</span></div><div class="prompt-preview"><span class="eyebrow">Task instruction</span><p>Build me a compact, sunlit Copenhagen-style canal district with narrow cobblestone streets and rectangular waterways enclosing dense urban blocks.</p><p class="prompt-detail">Townhouses, canals, bridges and a waterfront promenade — with detailed requirements for materials, layout and street furniture.</p></div><p class="task-foot"><strong>Given:</strong> an empty level and the content pack’s asset catalog.</p></article><article class="task-panel output-panel"><div class="panel-heading"><strong>Agent output</strong><span>GPT-6 Astra</span></div><div class="live-scene" data-agent="astra" data-case="nordic-harbour"><canvas aria-label="Interactive 3D scene: Nordic Harbour. Drag to orbit, scroll or use plus and minus to zoom." tabindex="0"></canvas><span class="scene-loading" role="status">Loading 3D scene…</span><span class="scene-hint">Drag to orbit · Scroll to zoom</span><button aria-label="Pause scene rotation" class="scene-motion">Ⅱ</button></div><a class="task-foot text-button" href="cases.html#nordic-harbour">Compare agents and evaluator views ↗</a></article><article class="task-panel evaluation-panel"><div class="panel-heading"><strong>Evaluation</strong><span>Engine-native scene</span></div><div class="score-block"><span>CASE SCORE</span><strong>0.877</strong><dl><div><dt>Detailed Alignment</dt><dd>0.757</dd></div><div><dt>Overview Alignment</dt><dd>0.909</dd></div><div><dt>Physical Safety</dt><dd>0.900</dd></div></dl></div><p class="task-foot">Task fulfillment, artifact integrity and static physical validity.</p><p class="task-formula">0.2 × Detailed + 0.6 × Overview + 0.2 × Physical</p></article></div><details class="task-full"><summary><span>+ Full task instruction</span><span>TEXT-TO-SCENE</span></summary><p>Build me a compact, sunlit Copenhagen-style canal district with narrow cobblestone streets and rectangular waterways enclosing dense urban blocks. Arrange roughly thirty attached four- to six-storey townhouses in continuous street and canal-front rows, using slender façades, repetitive white-framed windows, arched doors and carriage entrances, modest cornices, and steep tiled roofs packed with dormers and brick chimneys. Include occasional exposed party walls between staggered building rows. Finish the façades in weathered muted red, mustard yellow, dusty blue, warm grey, beige, and exposed brick, with red, orange, and charcoal roof tiles. Run a principal waterfront street along a stone quay, crossed by smaller streets and linked across the canals by short, gently arched stone-and-metal bridges, including one opening onto a broad paved promenade with rounded landings. Form the canal edges from stepped pale stone, dark timber retaining walls, and iron railings around reflective rippling water. Add regularly spaced leafy trees, ornate black lamps, stone bollards, benches, European road signs, zebra crossings, puddles, grime, and sparse weeds.</p></details></div><div aria-labelledby="task-tab-i2s" class="task-content" data-task="i2s" hidden="" id="task-i2s" role="tabpanel"><h3>Old Industrial Pallet Bay <span>· Image-to-Scene</span></h3><p class="task-subtitle">Repair the corrupted scene using its reference image; preserve all unrelated actors.</p><div class="task-columns"><article class="task-panel input-panel"><div class="panel-heading"><strong>Agent input</strong><span>Scene + reference image</span></div><div class="live-scene" data-agent="astra" data-case="comp-31-old-industrial-pallet-bay-repair-v2" data-state="input"><canvas aria-label="Interactive 3D: Old Industrial Pallet Bay, input. Drag to orbit; scroll to zoom." tabindex="0"></canvas><span class="scene-loading" role="status">Loading 3D scene…</span><span class="scene-hint">Drag to orbit · Scroll to zoom</span><button aria-label="Resume scene rotation" class="scene-motion">▶</button></div><a class="task-reference" href="media/i2s/comp-31-old-industrial-pallet-bay-repair-v2/ref-1.jpg?v=1790549706" rel="noopener" target="_blank"><img alt="Provided reference image for Old Industrial Pallet Bay" loading="lazy" src="media/i2s/comp-31-old-industrial-pallet-bay-repair-v2/ref-1.jpg?v=1790549706"/><span>Provided reference image ↗</span></a><p class="task-foot">A translated and rotated pallet stack, plus an unintended duplicate.</p></article><article class="task-panel output-panel"><div class="panel-heading"><strong>Agent output</strong><span>GPT-6 Astra</span></div><div class="live-scene" data-agent="astra" data-case="comp-31-old-industrial-pallet-bay-repair-v2" data-state="repair"><canvas aria-label="Interactive 3D: Old Industrial Pallet Bay, repair. Drag to orbit; scroll to zoom." tabindex="0"></canvas><span class="scene-loading" role="status">Loading 3D scene…</span><span class="scene-hint">Drag to orbit · Scroll to zoom</span><button aria-label="Resume scene rotation" class="scene-motion">▶</button></div><div aria-label="Output scene state" class="scene-versions"><button class="on" data-state="repair">Repair</button><button data-state="input">Corrupted input</button><button data-state="gt">Ground truth</button></div><p class="task-foot">Inspect the saved repair from the same reference camera.</p><a class="text-button task-foot" href="cases.html#comp-31-old-industrial-pallet-bay-repair-v2">Compare agents and evaluator views ↗</a></article><article class="task-panel evaluation-panel"><div class="panel-heading"><strong>Evaluation</strong><span>Actor-level repair</span></div><div class="score-block"><span>CASE SCORE</span><strong>1.000</strong><dl><div><dt>Repair F1</dt><dd>1.000</dd></div><div><dt>Physical Safety</dt><dd>1.000</dd></div><div><dt>TP / FP / FN</dt><dd>2 / 0 / 0</dd></div></dl></div><p class="task-foot">Restore each target within 5 cm, 5° and 5%, without unintended edits.</p><p class="task-formula">0.8 × Repair F1 + 0.2 × Physical</p></article></div><details class="task-full"><summary><span>+ Full task instruction</span><span>IMAGE-TO-SCENE</span></summary><p>Repair the current outdoor scene so it matches the provided reference image. Make only the minimum changes needed, and preserve all unrelated actors and properties.</p></details></div></div><div class="availability"><span><b>320</b> full benchmark target · 160 construction + 160 editing, including private cases</span><span><b>201</b> public cases · 129 construction + 72 editing (22 indoor, 50 outdoor)</span></div></div></section><section id="leaderboard"><div class="container">
24
  <div class="section-header centered"><span class="section-label">Leaderboard</span><h2 class="section-title">Leaderboard</h2>
25
  <p class="section-desc">14 coding-agent configurations · paper results on the original 95 public cases (20 construction + 75 editing). These results predate the 201-case public release.</p></div>
 
58
  <a href="https://simworld.org/" rel="noopener" target="_blank">SimWorld Home</a></p>
59
  <p style="margin-top:.5rem;font-size:.75rem;color:rgba(255,255,255,.25)">Part of the SimWorld project.</p></footer>
60
  <script src="assets/paper.js?v=5427505a"></script>
61
+ <script defer="" src="assets/leaderboard-bars.js?v=20261001c"></script><script src="data/site.js?v=20261001b"></script><script src="assets/pareto.js?v=20261001c" type="module"></script><script src="assets/scene-cards.js?v=20261002a" type="module"></script><script src="assets/task-tabs.js?v=20261001c" type="module"></script><script src="assets/leaderboard-controls.js?v=20261001c" type="module"></script></body></html>
models/progressive-v1/geo-054a9fa272d6216f02828f09.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ecf992a31a1ebf23cdd96e5bb60471bbfe0f6dddc3a67da8d9df4872688c2f30
3
+ size 2348676
models/progressive-v1/geo-2cb0699fcdfbb0077b14a03a.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bff1001b5a26f15eee108e1132b5111ba48471cfde831ca77a68aa05fdf58750
3
+ size 5524504
models/progressive-v1/geo-6917456ac7ecb3f644ad6e37.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce528fce8d85c638def4b71cc2fd2d57b5afbcd77f39d267a70e315d58836a9e
3
+ size 1790032
models/progressive-v1/geo-8d8ac2389fb2144d0ada046a.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6a30f9a475d25a987a8e31ed315abedaa5e77c7367f856966d831dcddebbd81c
3
+ size 9642436
models/progressive-v1/geo-9e85a029420d9485d131d029.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f3f5b4e386c1899d5bd595feee5bf2da6035ebe81863b9485979b8b441a0b1a5
3
+ size 1888144
models/progressive-v1/geo-ab194913839cc37985edc5c8.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:35c2562176df2b8910475416aeb6458cb2bff747c9b07d14f32c57a56b5e66ea
3
+ size 5524192
models/progressive-v1/geo-b0e37abcf8f8538d5c7c567d.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e80ec4defc3922ccefa5e71212d9d3fc21882c5db88285455615719315fa9bc8
3
+ size 5524292
models/progressive-v1/geo-cfc8f9530bbdf82e9ad5803e.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c13dd111bc77897f60d64b4ffe1ce256d3e44f950381762f7e0046a046150a82
3
+ size 6144796
models/progressive-v1/geo-f52df859622b51e5a8e79b7f.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c8b5581c2c5ce1999e1a3d3adae0f610a904820963ff4be84bb70e19bf072e3
3
+ size 6145112
models/progressive-v1/geo-fbdfbee814c5ec5bbbd7b049.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6ce75ed4b3087e84bf62c608cff771f1a7d100126374590f263f7b1516f78afb
3
+ size 6144908
models/progressive-v1/geo-fc7f349eacd0387d378791ba.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0049511473d87d3c2e2bf823a7fcb86664f3c882dafad4a733a8e0fb98963c9e
3
+ size 3122684
models/progressive-v1/tex-012c8e3c3ec399a5e3814072.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b7b600f787d580d23d63f19b506ef498d0f747d76f5cb9c69ba3a2ce13ce9ed6
3
+ size 235174
models/progressive-v1/tex-145b1eebf364866f45d8e439.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4132e4e523875f648427847c8558ba6be15834bc9ab90dfbd69cd8f7830b1ef4
3
+ size 160870
models/progressive-v1/tex-1c281547b397376efd0199f4.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06e4053254de95a64ddefa098cee389a73fbc909419cca880cf7a2937ce33b41
3
+ size 127330
models/progressive-v1/tex-1f1e882fba2f5973b698e4eb.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3fa34efaa8af420eae7667f6848fd5c5c7106c1044813cab468d6c6481bb49e2
3
+ size 226390
models/progressive-v1/tex-1fedf9beb0efec481eb12bf4.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ed7958a032f08d1577f4a9fc92abcfd28b77ca0abc50501deec97c3e73bef4e8
3
+ size 209744
models/progressive-v1/tex-2452f0e9439a921b6ddef41f.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:74c27a4385573ee86a08a37c4f9e9d7c79e873a2f23494d76916095f409d516d
3
+ size 261254
models/progressive-v1/tex-2a7f522dfd3141f76017fb18.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:10d89d6558a56c8f556ae45dad38e5b87dca1973dafc5ccbefaaee900f03131a
3
+ size 319288
models/progressive-v1/tex-2d0d7e1fa9f1d976efb71cee.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eada6caa3f407057a0ba2fc77e857d92945e6d5bec62733c6047a05a138988c8
3
+ size 316882
models/progressive-v1/tex-466f0b10c87df10345ad2f25.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c608b34f8c02913d99fe3500510ca7622904dc4a540c15bd6fe196474bb478ff
3
+ size 149358
models/progressive-v1/tex-4d8b036a2ee06405bef3e76c.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4029ae6f45fc4f2c738e08ef7cc07e9576a664ce57ef5ec82a82d621f23a6bc1
3
+ size 390286
models/progressive-v1/tex-4f27ae3f9204f54fd17a9d19.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:88db55fc545bf4bfb3131549fe8a68262d0fc2a0886c4ddaa02d43ac68022cc0
3
+ size 258896
models/progressive-v1/tex-60a1d5975059cf1464d7e63d.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5acb7c239d9612f05250503aa1d9b96d34fc751b711b47013ccf90ce650829ca
3
+ size 180404
models/progressive-v1/tex-62e57d9806cfa50c8907036d.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c7f17e45f852be3c9d65bcfa3781592c6b26ee4de6484143e9ebf6b8860b979
3
+ size 163086
models/progressive-v1/tex-7617ace0a61438d8778a6ca1.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec0a23f23ef5abbd913f141e445bda8db1bf18e63cda568fa29fec1ed67d2ab6
3
+ size 191250
models/progressive-v1/tex-787aa264f2ab67e4714dd87d.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b5e5170b9116ce6a828b4fda401efdf96cd101ab60ddd1313cba4ccdc2352b5c
3
+ size 167396
models/progressive-v1/tex-7e84a3d9e41d34dc95c56b50.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:78b1af387ea1ddab2ff2180201b576f3cf37110002aa712c2c93a85e3dd21aaf
3
+ size 297352
models/progressive-v1/tex-80312b3d120467ae6a6d0c5c.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a624930bccb545fef20b993ab2e809d38cae476ff8712800347d7ff834f282b7
3
+ size 275902
models/progressive-v1/tex-87528eb6654a28944db234fa.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5a071b8bfc3182611733fa1a50bb793ed4c71e40879ad3641db477750e0997bb
3
+ size 289438
models/progressive-v1/tex-89b9c55ddabdd596bb39e1b0.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5d48186a4b603a37110ae537a7fd00262b7aaee4a1a216843c53d7204bdeebf4
3
+ size 203236
models/progressive-v1/tex-8d83f93625bdd185c8322e5a.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3ea3b8bd64805b396e4907b26277a5dd70ea4b9e85e552724785f6e7f261540d
3
+ size 326008
models/progressive-v1/tex-8eb5e2b95996e3395d479ec7.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8d2891460a4d34a397c6d7109da7e1c6c24f9925711a29182e1306304f1575a1
3
+ size 225566
models/progressive-v1/tex-8ed4918e0b472598b2b0fefc.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c788047b178d871e2b6498911a250463b07b18b5e2fd573e675c20f39b8d49d4
3
+ size 425156
models/progressive-v1/tex-918b22ae7e82d5e17e5166bb.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9d9e72963f79b99447416c45ec78b380a46b0a90b6b6338e23960cf3ef899132
3
+ size 151560
models/progressive-v1/tex-919c52e07425864831344ed8.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:857a2412597ffd42ad7e41a284d2c9b9dd02a9828d105efbea608a2c8fcd0c65
3
+ size 298696