Spaces:
Running
Running
Render geometry before progressive textures and self-host Three.js
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +8 -0
- assets/buildingbench.css +2 -0
- assets/progressive-gltf.js +87 -0
- assets/scene-assets.js +1 -1
- assets/scene-cards.js +20 -29
- assets/vendor/three-r160/LICENSE +21 -0
- assets/vendor/three-r160/addons/controls/OrbitControls.js +1417 -0
- assets/vendor/three-r160/addons/libs/meshopt_decoder.module.js +178 -0
- assets/vendor/three-r160/addons/loaders/GLTFLoader.js +0 -0
- assets/vendor/three-r160/addons/objects/Sky.js +219 -0
- assets/vendor/three-r160/addons/utils/BufferGeometryUtils.js +1375 -0
- assets/vendor/three-r160/build/three.module.js +0 -0
- cases.html +1 -1
- data/progressive-scenes.json +90 -0
- index.html +2 -2
- models/progressive-v1/geo-054a9fa272d6216f02828f09.bin +3 -0
- models/progressive-v1/geo-2cb0699fcdfbb0077b14a03a.bin +3 -0
- models/progressive-v1/geo-6917456ac7ecb3f644ad6e37.bin +3 -0
- models/progressive-v1/geo-8d8ac2389fb2144d0ada046a.bin +3 -0
- models/progressive-v1/geo-9e85a029420d9485d131d029.bin +3 -0
- models/progressive-v1/geo-ab194913839cc37985edc5c8.bin +3 -0
- models/progressive-v1/geo-b0e37abcf8f8538d5c7c567d.bin +3 -0
- models/progressive-v1/geo-cfc8f9530bbdf82e9ad5803e.bin +3 -0
- models/progressive-v1/geo-f52df859622b51e5a8e79b7f.bin +3 -0
- models/progressive-v1/geo-fbdfbee814c5ec5bbbd7b049.bin +3 -0
- models/progressive-v1/geo-fc7f349eacd0387d378791ba.bin +3 -0
- models/progressive-v1/tex-012c8e3c3ec399a5e3814072.bin +3 -0
- models/progressive-v1/tex-145b1eebf364866f45d8e439.bin +3 -0
- models/progressive-v1/tex-1c281547b397376efd0199f4.bin +3 -0
- models/progressive-v1/tex-1f1e882fba2f5973b698e4eb.bin +3 -0
- models/progressive-v1/tex-1fedf9beb0efec481eb12bf4.bin +3 -0
- models/progressive-v1/tex-2452f0e9439a921b6ddef41f.bin +3 -0
- models/progressive-v1/tex-2a7f522dfd3141f76017fb18.bin +3 -0
- models/progressive-v1/tex-2d0d7e1fa9f1d976efb71cee.bin +3 -0
- models/progressive-v1/tex-466f0b10c87df10345ad2f25.bin +3 -0
- models/progressive-v1/tex-4d8b036a2ee06405bef3e76c.bin +3 -0
- models/progressive-v1/tex-4f27ae3f9204f54fd17a9d19.bin +3 -0
- models/progressive-v1/tex-60a1d5975059cf1464d7e63d.bin +3 -0
- models/progressive-v1/tex-62e57d9806cfa50c8907036d.bin +3 -0
- models/progressive-v1/tex-7617ace0a61438d8778a6ca1.bin +3 -0
- models/progressive-v1/tex-787aa264f2ab67e4714dd87d.bin +3 -0
- models/progressive-v1/tex-7e84a3d9e41d34dc95c56b50.bin +3 -0
- models/progressive-v1/tex-80312b3d120467ae6a6d0c5c.bin +3 -0
- models/progressive-v1/tex-87528eb6654a28944db234fa.bin +3 -0
- models/progressive-v1/tex-89b9c55ddabdd596bb39e1b0.bin +3 -0
- models/progressive-v1/tex-8d83f93625bdd185c8322e5a.bin +3 -0
- models/progressive-v1/tex-8eb5e2b95996e3395d479ec7.bin +3 -0
- models/progressive-v1/tex-8ed4918e0b472598b2b0fefc.bin +3 -0
- models/progressive-v1/tex-918b22ae7e82d5e17e5166bb.bin +3 -0
- 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=
|
| 3 |
-
import {
|
| 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
|
| 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 |
-
|
| 118 |
-
|
| 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":"
|
| 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=
|
| 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 & 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=
|
|
|
|
| 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 & 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
|