feat: stage 2 real CAD conveyor, hybrid physics drops, mobile policy (WIP)

- CAD pipeline: conveer.FCStd -> FreeCAD headless -> conveyor-web.glb
  (555KB Draco, 42 named nodes) integrated into main scene; gates and
  rollers animated from domain state
- Domain-driven adapter sorterVisualState.ts; per-SKU visual physics
  profiles (VISUAL_PHYSICS_ESTIMATE)
- Hybrid Rapier physics: kinematic belt travel -> dynamic drop handoff
  -> verified freeze; shared collider layout (physicsWorldLayout) used
  by runtime and headless validation sim (physicsDropSim + 13 tests)
- Gravity chutes / open roll-cage front redesign (drop tuning in progress:
  4/7 routes verified headless)
- Premium industrial visual: PBR, ACES, PCFSoft shadows, Lightformer env,
  UI cleanup behind ?debug=1
- Mobile capability policy: SVG fallback / Mobile Low / full tiers,
  Telegram WebView forced to SVG, e2e stubs for weak devices
- Performance benchmarks cad-light/physics/full pass; stage2 artifacts

tsc clean. Physics drop validation for 3 SKU routes still in progress.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Даня Архипов
2026-07-29 21:05:46 +00:00
parent 394c513037
commit 40e9b18e8d
46 changed files with 5721 additions and 175 deletions

2
.gitignore vendored
View File

@@ -21,3 +21,5 @@ playwright-report/
blob-report/
releases/backup-*/
scripts/.perf-tmp.cjs
tools/stage2-cad/out/
__pycache__

View File

@@ -0,0 +1,108 @@
{
"profile": "cad-full",
"path": "/?perf=1&quality=demo",
"viewport": {
"width": 1920,
"height": 1080
},
"runs": 2,
"fpsAvg": 59.9,
"fpsMin": 28.49,
"frameTimeP95Ms": 17.2,
"drawCalls": 166,
"triangles": 365218,
"heapMb": 53.82,
"renderer": "ANGLE (NVIDIA Corporation, NVIDIA GeForce GTX 1080/PCIe/SSE2, OpenGL ES 3.2)",
"physics": {
"count": 600,
"avgMs": 0.0655000012119611,
"p95Ms": 0.19999998807907104,
"maxMs": 0.9000000357627869
},
"dropsVerified": [
{
"caseId": "box_b",
"zone": "B",
"inside": true
},
{
"caseId": "lunchbox_b",
"zone": "B",
"inside": true
}
],
"device": {
"vendor": "Google Inc. (NVIDIA Corporation)",
"hardwareConcurrency": 6,
"deviceMemory": 16,
"devicePixelRatio": 1,
"viewport": {
"width": 1920,
"height": 1080
}
},
"consoleErrors": [],
"sceneLoadMs": 486.5,
"ctxLost": 0,
"modelRequests": [
"http://127.0.0.1:3101/models/sorter/conveyor-web.glb",
"http://127.0.0.1:3101/models/box-300.stl",
"http://127.0.0.1:3101/models/lunchbox.stl",
"http://127.0.0.1:3101/models/box-400.stl",
"http://127.0.0.1:3101/models/plate.stl",
"http://127.0.0.1:3101/models/bottle.stl",
"http://127.0.0.1:3101/models/pen.stl"
],
"memoryLoop": [
{
"cycle": 1,
"heapBytes": 52565544,
"dropResults": 0
},
{
"cycle": 2,
"heapBytes": 35445133,
"dropResults": 0
},
{
"cycle": 3,
"heapBytes": 65863994,
"dropResults": 0
},
{
"cycle": 4,
"heapBytes": 38948871,
"dropResults": 0
},
{
"cycle": 5,
"heapBytes": 52642098,
"dropResults": 0
},
{
"cycle": 6,
"heapBytes": 51737094,
"dropResults": 0
},
{
"cycle": 7,
"heapBytes": 58675947,
"dropResults": 0
},
{
"cycle": 8,
"heapBytes": 53790182,
"dropResults": 0
},
{
"cycle": 9,
"heapBytes": 57980834,
"dropResults": 0
},
{
"cycle": 10,
"heapBytes": 54462476,
"dropResults": 0
}
]
}

View File

@@ -0,0 +1,51 @@
{
"profile": "cad-light",
"path": "/?perf=1&quality=medium",
"viewport": {
"width": 1920,
"height": 1080
},
"runs": 2,
"fpsAvg": 59.9,
"fpsMin": 28.41,
"frameTimeP95Ms": 17.3,
"drawCalls": 112,
"triangles": 176789,
"heapMb": 30.8,
"renderer": "ANGLE (NVIDIA Corporation, NVIDIA GeForce GTX 1080/PCIe/SSE2, OpenGL ES 3.2)",
"physics": null,
"dropsVerified": [
{
"caseId": "box_b",
"zone": "B",
"inside": true
},
{
"caseId": "lunchbox_b",
"zone": "B",
"inside": true
}
],
"device": {
"vendor": "Google Inc. (NVIDIA Corporation)",
"hardwareConcurrency": 6,
"deviceMemory": 16,
"devicePixelRatio": 1,
"viewport": {
"width": 1920,
"height": 1080
}
},
"consoleErrors": [],
"sceneLoadMs": 425,
"ctxLost": 0,
"modelRequests": [
"http://127.0.0.1:3101/models/sorter/conveyor-web.glb",
"http://127.0.0.1:3101/models/box-300.stl",
"http://127.0.0.1:3101/models/lunchbox.stl",
"http://127.0.0.1:3101/models/box-400.stl",
"http://127.0.0.1:3101/models/plate.stl",
"http://127.0.0.1:3101/models/bottle.stl",
"http://127.0.0.1:3101/models/pen.stl"
]
}

View File

@@ -0,0 +1,56 @@
{
"profile": "cad-physics",
"path": "/?perf=1&quality=high",
"viewport": {
"width": 1920,
"height": 1080
},
"runs": 2,
"fpsAvg": 60.010000000000005,
"fpsMin": 31.95,
"frameTimeP95Ms": 17.299999999999997,
"drawCalls": 168,
"triangles": 365442,
"heapMb": 53.57,
"renderer": "ANGLE (NVIDIA Corporation, NVIDIA GeForce GTX 1080/PCIe/SSE2, OpenGL ES 3.2)",
"physics": {
"count": 600,
"avgMs": 0.06783333351214726,
"p95Ms": 0.19999998807907104,
"maxMs": 0.3999999761581421
},
"dropsVerified": [
{
"caseId": "box_b",
"zone": "B",
"inside": true
},
{
"caseId": "lunchbox_b",
"zone": "B",
"inside": true
}
],
"device": {
"vendor": "Google Inc. (NVIDIA Corporation)",
"hardwareConcurrency": 6,
"deviceMemory": 16,
"devicePixelRatio": 1,
"viewport": {
"width": 1920,
"height": 1080
}
},
"consoleErrors": [],
"sceneLoadMs": 553.5,
"ctxLost": 0,
"modelRequests": [
"http://127.0.0.1:3101/models/sorter/conveyor-web.glb",
"http://127.0.0.1:3101/models/box-300.stl",
"http://127.0.0.1:3101/models/lunchbox.stl",
"http://127.0.0.1:3101/models/box-400.stl",
"http://127.0.0.1:3101/models/plate.stl",
"http://127.0.0.1:3101/models/bottle.stl",
"http://127.0.0.1:3101/models/pen.stl"
]
}

View File

@@ -0,0 +1,31 @@
{
"profile": "mobile-fallback",
"path": "/",
"viewport": {
"width": 390,
"height": 844
},
"runs": 1,
"fpsAvg": null,
"fpsMin": null,
"frameTimeP95Ms": null,
"drawCalls": null,
"triangles": null,
"heapMb": null,
"renderer": null,
"physics": null,
"dropsVerified": [],
"device": null,
"consoleErrors": [],
"sceneLoadMs": 143,
"ctxLost": 0,
"modelRequests": [],
"fallback": {
"fallbackVisible": true,
"canvasCount": 0,
"ctxLost": 0,
"modelRequests": [],
"consoleErrors": [],
"sceneLoadMs": 143
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

View File

@@ -14,6 +14,14 @@ import { test, expect } from '@playwright/test';
const MOBILE_UA =
'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36';
/** Stage 2 §16: fallback tests emulate a WEAK device (capable phones get Mobile Low 3D). */
async function stubWeakDevice(page: import('@playwright/test').Page) {
await page.addInitScript(() => {
Object.defineProperty(navigator, 'hardwareConcurrency', { value: 4, configurable: true });
Object.defineProperty(navigator, 'deviceMemory', { value: 2, configurable: true });
});
}
function collectErrors(page: import('@playwright/test').Page) {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
@@ -35,6 +43,7 @@ test.describe('mobile fallback (390x844)', () => {
test('shows honest SVG fallback, no false WebGL error, UI stays functional', async ({ page }) => {
const { pageErrors, consoleErrors } = collectErrors(page);
await stubWeakDevice(page);
await page.goto('/');
@@ -164,6 +173,40 @@ test.describe('webgl context loss and recovery', () => {
});
});
test.describe('stage2 mobile capability policy', () => {
test('capable phone gets Mobile Low 3D (no SVG fallback)', async ({ page }) => {
const { pageErrors } = collectErrors(page);
await page.addInitScript(() => {
Object.defineProperty(navigator, 'hardwareConcurrency', { value: 8, configurable: true });
Object.defineProperty(navigator, 'deviceMemory', { value: 8, configurable: true });
});
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/');
// Mobile Low mounts the real 3D canvas at low quality
await expect(page.locator('canvas')).toBeVisible({ timeout: 60_000 });
await expect(page.getByTestId('main-svg-fallback')).toHaveCount(0);
await page.getByTestId('demo-play').click();
await page.waitForTimeout(4000);
expect(pageErrors, `pageerrors: ${pageErrors.join('; ')}`).toEqual([]);
});
test('Telegram WebView is forced to SVG until real-device verification', async ({ page }) => {
const { pageErrors } = collectErrors(page);
await page.addInitScript(() => {
Object.defineProperty(navigator, 'hardwareConcurrency', { value: 8, configurable: true });
Object.defineProperty(navigator, 'deviceMemory', { value: 8, configurable: true });
// iOS-style Telegram WebView marker
(window as unknown as { TelegramWebviewProxy?: unknown }).TelegramWebviewProxy = { postEvent: () => undefined };
});
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/');
const fallback = page.getByTestId('main-svg-fallback');
await expect(fallback).toBeVisible({ timeout: 30_000 });
await expect(page.locator('canvas')).toHaveCount(0);
expect(pageErrors, `pageerrors: ${pageErrors.join('; ')}`).toEqual([]);
});
});
test.describe('stage0 prototype mode', () => {
test('query params enable cinematic scene; auto cam toggle is truthful', async ({ page }) => {
const { pageErrors, consoleErrors } = collectErrors(page);

View File

@@ -100,6 +100,11 @@ test.describe('Stage 1 real models', () => {
page.on('request', (req) => {
if (MODEL_RE.test(req.url())) modelRequests.push(req.url());
});
// Stage 2 §16: fallback path is for weak devices — stub low capability.
await page.addInitScript(() => {
Object.defineProperty(navigator, 'hardwareConcurrency', { value: 4, configurable: true });
Object.defineProperty(navigator, 'deviceMemory', { value: 2, configurable: true });
});
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-testid="main-svg-fallback"]', { timeout: 30000 });

2897
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,9 +27,11 @@
"perf:replay-stability": "node scripts/replay-stability.mjs"
},
"dependencies": {
"@dimforge/rapier3d-compat": "^0.19.3",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.1",
"@react-three/postprocessing": "^3.0.4",
"@react-three/rapier": "^2.2.0",
"@vitejs/plugin-react": "latest",
"postprocessing": "^6.39.4",
"react": "latest",
@@ -40,6 +42,9 @@
"vite": "latest"
},
"devDependencies": {
"@gltf-transform/cli": "^4.4.2",
"@gltf-transform/core": "^4.4.2",
"@gltf-transform/functions": "^4.4.2",
"@playwright/test": "^1.61.1",
"@types/node": "^26.1.0",
"@types/react": "^19.2.17",

31
public/draco/README.md Normal file
View File

@@ -0,0 +1,31 @@
# Draco 3D Data Compression
Draco is an open-source library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics.
[Website](https://google.github.io/draco/) | [GitHub](https://github.com/google/draco)
## Contents
This folder contains three utilities:
* `draco_decoder.js` — Emscripten-compiled decoder, compatible with any modern browser.
* `draco_decoder.wasm` — WebAssembly decoder, compatible with newer browsers and devices.
* `draco_wasm_wrapper.js` — JavaScript wrapper for the WASM decoder.
Each file is provided in two variations:
* **Default:** Latest stable builds, tracking the project's [master branch](https://github.com/google/draco).
* **glTF:** Builds targeted by the [glTF mesh compression extension](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression), tracking the [corresponding Draco branch](https://github.com/google/draco/tree/gltf_2.0_draco_extension).
Either variation may be used with `DRACOLoader`:
```js
var dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('path/to/decoders/');
```
Further [documentation on GitHub](https://github.com/google/draco/tree/master/javascript/example#static-loading-javascript-decoder).
## License
[Apache License 2.0](https://github.com/google/draco/blob/master/LICENSE)

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,117 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(k){var n=0;return function(){return n<k.length?{done:!1,value:k[n++]}:{done:!0}}};$jscomp.arrayIterator=function(k){return{next:$jscomp.arrayIteratorImpl(k)}};$jscomp.makeIterator=function(k){var n="undefined"!=typeof Symbol&&Symbol.iterator&&k[Symbol.iterator];return n?n.call(k):$jscomp.arrayIterator(k)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;
$jscomp.ISOLATE_POLYFILLS=!1;$jscomp.FORCE_POLYFILL_PROMISE=!1;$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;$jscomp.getGlobal=function(k){k=["object"==typeof globalThis&&globalThis,k,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var n=0;n<k.length;++n){var l=k[n];if(l&&l.Math==Math)return l}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(k,n,l){if(k==Array.prototype||k==Object.prototype)return k;k[n]=l.value;return k};$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";
var $jscomp$lookupPolyfilledValue=function(k,n){var l=$jscomp.propertyToPolyfillSymbol[n];if(null==l)return k[n];l=k[l];return void 0!==l?l:k[n]};$jscomp.polyfill=function(k,n,l,p){n&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(k,n,l,p):$jscomp.polyfillUnisolated(k,n,l,p))};
$jscomp.polyfillUnisolated=function(k,n,l,p){l=$jscomp.global;k=k.split(".");for(p=0;p<k.length-1;p++){var h=k[p];if(!(h in l))return;l=l[h]}k=k[k.length-1];p=l[k];n=n(p);n!=p&&null!=n&&$jscomp.defineProperty(l,k,{configurable:!0,writable:!0,value:n})};
$jscomp.polyfillIsolated=function(k,n,l,p){var h=k.split(".");k=1===h.length;p=h[0];p=!k&&p in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var A=0;A<h.length-1;A++){var f=h[A];if(!(f in p))return;p=p[f]}h=h[h.length-1];l=$jscomp.IS_SYMBOL_NATIVE&&"es6"===l?p[h]:null;n=n(l);null!=n&&(k?$jscomp.defineProperty($jscomp.polyfills,h,{configurable:!0,writable:!0,value:n}):n!==l&&(void 0===$jscomp.propertyToPolyfillSymbol[h]&&(l=1E9*Math.random()>>>0,$jscomp.propertyToPolyfillSymbol[h]=$jscomp.IS_SYMBOL_NATIVE?
$jscomp.global.Symbol(h):$jscomp.POLYFILL_PREFIX+l+"$"+h),$jscomp.defineProperty(p,$jscomp.propertyToPolyfillSymbol[h],{configurable:!0,writable:!0,value:n})))};
$jscomp.polyfill("Promise",function(k){function n(){this.batch_=null}function l(f){return f instanceof h?f:new h(function(q,v){q(f)})}if(k&&(!($jscomp.FORCE_POLYFILL_PROMISE||$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION&&"undefined"===typeof $jscomp.global.PromiseRejectionEvent)||!$jscomp.global.Promise||-1===$jscomp.global.Promise.toString().indexOf("[native code]")))return k;n.prototype.asyncExecute=function(f){if(null==this.batch_){this.batch_=[];var q=this;this.asyncExecuteFunction(function(){q.executeBatch_()})}this.batch_.push(f)};
var p=$jscomp.global.setTimeout;n.prototype.asyncExecuteFunction=function(f){p(f,0)};n.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var f=this.batch_;this.batch_=[];for(var q=0;q<f.length;++q){var v=f[q];f[q]=null;try{v()}catch(z){this.asyncThrow_(z)}}}this.batch_=null};n.prototype.asyncThrow_=function(f){this.asyncExecuteFunction(function(){throw f;})};var h=function(f){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];this.isRejectionHandled_=!1;var q=this.createResolveAndReject_();
try{f(q.resolve,q.reject)}catch(v){q.reject(v)}};h.prototype.createResolveAndReject_=function(){function f(z){return function(O){v||(v=!0,z.call(q,O))}}var q=this,v=!1;return{resolve:f(this.resolveTo_),reject:f(this.reject_)}};h.prototype.resolveTo_=function(f){if(f===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(f instanceof h)this.settleSameAsPromise_(f);else{a:switch(typeof f){case "object":var q=null!=f;break a;case "function":q=!0;break a;default:q=!1}q?this.resolveToNonPromiseObj_(f):
this.fulfill_(f)}};h.prototype.resolveToNonPromiseObj_=function(f){var q=void 0;try{q=f.then}catch(v){this.reject_(v);return}"function"==typeof q?this.settleSameAsThenable_(q,f):this.fulfill_(f)};h.prototype.reject_=function(f){this.settle_(2,f)};h.prototype.fulfill_=function(f){this.settle_(1,f)};h.prototype.settle_=function(f,q){if(0!=this.state_)throw Error("Cannot settle("+f+", "+q+"): Promise already settled in state"+this.state_);this.state_=f;this.result_=q;2===this.state_&&this.scheduleUnhandledRejectionCheck_();
this.executeOnSettledCallbacks_()};h.prototype.scheduleUnhandledRejectionCheck_=function(){var f=this;p(function(){if(f.notifyUnhandledRejection_()){var q=$jscomp.global.console;"undefined"!==typeof q&&q.error(f.result_)}},1)};h.prototype.notifyUnhandledRejection_=function(){if(this.isRejectionHandled_)return!1;var f=$jscomp.global.CustomEvent,q=$jscomp.global.Event,v=$jscomp.global.dispatchEvent;if("undefined"===typeof v)return!0;"function"===typeof f?f=new f("unhandledrejection",{cancelable:!0}):
"function"===typeof q?f=new q("unhandledrejection",{cancelable:!0}):(f=$jscomp.global.document.createEvent("CustomEvent"),f.initCustomEvent("unhandledrejection",!1,!0,f));f.promise=this;f.reason=this.result_;return v(f)};h.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var f=0;f<this.onSettledCallbacks_.length;++f)A.asyncExecute(this.onSettledCallbacks_[f]);this.onSettledCallbacks_=null}};var A=new n;h.prototype.settleSameAsPromise_=function(f){var q=this.createResolveAndReject_();
f.callWhenSettled_(q.resolve,q.reject)};h.prototype.settleSameAsThenable_=function(f,q){var v=this.createResolveAndReject_();try{f.call(q,v.resolve,v.reject)}catch(z){v.reject(z)}};h.prototype.then=function(f,q){function v(t,x){return"function"==typeof t?function(D){try{z(t(D))}catch(R){O(R)}}:x}var z,O,ba=new h(function(t,x){z=t;O=x});this.callWhenSettled_(v(f,z),v(q,O));return ba};h.prototype.catch=function(f){return this.then(void 0,f)};h.prototype.callWhenSettled_=function(f,q){function v(){switch(z.state_){case 1:f(z.result_);
break;case 2:q(z.result_);break;default:throw Error("Unexpected state: "+z.state_);}}var z=this;null==this.onSettledCallbacks_?A.asyncExecute(v):this.onSettledCallbacks_.push(v);this.isRejectionHandled_=!0};h.resolve=l;h.reject=function(f){return new h(function(q,v){v(f)})};h.race=function(f){return new h(function(q,v){for(var z=$jscomp.makeIterator(f),O=z.next();!O.done;O=z.next())l(O.value).callWhenSettled_(q,v)})};h.all=function(f){var q=$jscomp.makeIterator(f),v=q.next();return v.done?l([]):new h(function(z,
O){function ba(D){return function(R){t[D]=R;x--;0==x&&z(t)}}var t=[],x=0;do t.push(void 0),x++,l(v.value).callWhenSettled_(ba(t.length-1),O),v=q.next();while(!v.done)})};return h},"es6","es3");$jscomp.owns=function(k,n){return Object.prototype.hasOwnProperty.call(k,n)};$jscomp.assign=$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.assign?Object.assign:function(k,n){for(var l=1;l<arguments.length;l++){var p=arguments[l];if(p)for(var h in p)$jscomp.owns(p,h)&&(k[h]=p[h])}return k};
$jscomp.polyfill("Object.assign",function(k){return k||$jscomp.assign},"es6","es3");$jscomp.checkStringArgs=function(k,n,l){if(null==k)throw new TypeError("The 'this' value for String.prototype."+l+" must not be null or undefined");if(n instanceof RegExp)throw new TypeError("First argument to String.prototype."+l+" must not be a regular expression");return k+""};
$jscomp.polyfill("String.prototype.startsWith",function(k){return k?k:function(n,l){var p=$jscomp.checkStringArgs(this,n,"startsWith");n+="";var h=p.length,A=n.length;l=Math.max(0,Math.min(l|0,p.length));for(var f=0;f<A&&l<h;)if(p[l++]!=n[f++])return!1;return f>=A}},"es6","es3");
$jscomp.polyfill("Array.prototype.copyWithin",function(k){function n(l){l=Number(l);return Infinity===l||-Infinity===l?l:l|0}return k?k:function(l,p,h){var A=this.length;l=n(l);p=n(p);h=void 0===h?A:n(h);l=0>l?Math.max(A+l,0):Math.min(l,A);p=0>p?Math.max(A+p,0):Math.min(p,A);h=0>h?Math.max(A+h,0):Math.min(h,A);if(l<p)for(;p<h;)p in this?this[l++]=this[p++]:(delete this[l++],p++);else for(h=Math.min(h,A+p-l),l+=h-p;h>p;)--h in this?this[--l]=this[h]:delete this[--l];return this}},"es6","es3");
$jscomp.typedArrayCopyWithin=function(k){return k?k:Array.prototype.copyWithin};$jscomp.polyfill("Int8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8ClampedArray.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
$jscomp.polyfill("Uint16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float64Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
var DracoDecoderModule=function(){var k="undefined"!==typeof document&&document.currentScript?document.currentScript.src:void 0;"undefined"!==typeof __filename&&(k=k||__filename);return function(n){function l(e){return a.locateFile?a.locateFile(e,U):U+e}function p(e,b,c){var d=b+c;for(c=b;e[c]&&!(c>=d);)++c;if(16<c-b&&e.buffer&&va)return va.decode(e.subarray(b,c));for(d="";b<c;){var g=e[b++];if(g&128){var u=e[b++]&63;if(192==(g&224))d+=String.fromCharCode((g&31)<<6|u);else{var X=e[b++]&63;g=224==
(g&240)?(g&15)<<12|u<<6|X:(g&7)<<18|u<<12|X<<6|e[b++]&63;65536>g?d+=String.fromCharCode(g):(g-=65536,d+=String.fromCharCode(55296|g>>10,56320|g&1023))}}else d+=String.fromCharCode(g)}return d}function h(e,b){return e?p(ea,e,b):""}function A(){var e=ja.buffer;a.HEAP8=Y=new Int8Array(e);a.HEAP16=new Int16Array(e);a.HEAP32=ca=new Int32Array(e);a.HEAPU8=ea=new Uint8Array(e);a.HEAPU16=new Uint16Array(e);a.HEAPU32=V=new Uint32Array(e);a.HEAPF32=new Float32Array(e);a.HEAPF64=new Float64Array(e)}function f(e){if(a.onAbort)a.onAbort(e);
e="Aborted("+e+")";da(e);wa=!0;e=new WebAssembly.RuntimeError(e+". Build with -sASSERTIONS for more info.");ka(e);throw e;}function q(e){try{if(e==P&&fa)return new Uint8Array(fa);if(ma)return ma(e);throw"both async and sync fetching of the wasm failed";}catch(b){f(b)}}function v(){if(!fa&&(xa||ha)){if("function"==typeof fetch&&!P.startsWith("file://"))return fetch(P,{credentials:"same-origin"}).then(function(e){if(!e.ok)throw"failed to load wasm binary file at '"+P+"'";return e.arrayBuffer()}).catch(function(){return q(P)});
if(na)return new Promise(function(e,b){na(P,function(c){e(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return q(P)})}function z(e){for(;0<e.length;)e.shift()(a)}function O(e){this.excPtr=e;this.ptr=e-24;this.set_type=function(b){V[this.ptr+4>>2]=b};this.get_type=function(){return V[this.ptr+4>>2]};this.set_destructor=function(b){V[this.ptr+8>>2]=b};this.get_destructor=function(){return V[this.ptr+8>>2]};this.set_refcount=function(b){ca[this.ptr>>2]=b};this.set_caught=function(b){Y[this.ptr+
12>>0]=b?1:0};this.get_caught=function(){return 0!=Y[this.ptr+12>>0]};this.set_rethrown=function(b){Y[this.ptr+13>>0]=b?1:0};this.get_rethrown=function(){return 0!=Y[this.ptr+13>>0]};this.init=function(b,c){this.set_adjusted_ptr(0);this.set_type(b);this.set_destructor(c);this.set_refcount(0);this.set_caught(!1);this.set_rethrown(!1)};this.add_ref=function(){ca[this.ptr>>2]+=1};this.release_ref=function(){var b=ca[this.ptr>>2];ca[this.ptr>>2]=b-1;return 1===b};this.set_adjusted_ptr=function(b){V[this.ptr+
16>>2]=b};this.get_adjusted_ptr=function(){return V[this.ptr+16>>2]};this.get_exception_ptr=function(){if(ya(this.get_type()))return V[this.excPtr>>2];var b=this.get_adjusted_ptr();return 0!==b?b:this.excPtr}}function ba(){function e(){if(!la&&(la=!0,a.calledRun=!0,!wa)){za=!0;z(oa);Aa(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;)Ba.unshift(a.postRun.shift());z(Ba)}}if(!(0<aa)){if(a.preRun)for("function"==
typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)Ca.unshift(a.preRun.shift());z(Ca);0<aa||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);e()},1)):e())}}function t(){}function x(e){return(e||t).__cache__}function D(e,b){var c=x(b),d=c[e];if(d)return d;d=Object.create((b||t).prototype);d.ptr=e;return c[e]=d}function R(e){if("string"===typeof e){for(var b=0,c=0;c<e.length;++c){var d=e.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=
d?(b+=4,++c):b+=3}b=Array(b+1);c=0;d=b.length;if(0<d){d=c+d-1;for(var g=0;g<e.length;++g){var u=e.charCodeAt(g);if(55296<=u&&57343>=u){var X=e.charCodeAt(++g);u=65536+((u&1023)<<10)|X&1023}if(127>=u){if(c>=d)break;b[c++]=u}else{if(2047>=u){if(c+1>=d)break;b[c++]=192|u>>6}else{if(65535>=u){if(c+2>=d)break;b[c++]=224|u>>12}else{if(c+3>=d)break;b[c++]=240|u>>18;b[c++]=128|u>>12&63}b[c++]=128|u>>6&63}b[c++]=128|u&63}}b[c]=0}e=r.alloc(b,Y);r.copy(b,Y,e);return e}return e}function pa(e){if("object"===typeof e){var b=
r.alloc(e,Y);r.copy(e,Y,b);return b}return e}function Z(){throw"cannot construct a VoidPtr, no constructor in IDL";}function S(){this.ptr=Da();x(S)[this.ptr]=this}function Q(){this.ptr=Ea();x(Q)[this.ptr]=this}function W(){this.ptr=Fa();x(W)[this.ptr]=this}function w(){this.ptr=Ga();x(w)[this.ptr]=this}function C(){this.ptr=Ha();x(C)[this.ptr]=this}function F(){this.ptr=Ia();x(F)[this.ptr]=this}function G(){this.ptr=Ja();x(G)[this.ptr]=this}function E(){this.ptr=Ka();x(E)[this.ptr]=this}function T(){this.ptr=
La();x(T)[this.ptr]=this}function B(){throw"cannot construct a Status, no constructor in IDL";}function H(){this.ptr=Ma();x(H)[this.ptr]=this}function I(){this.ptr=Na();x(I)[this.ptr]=this}function J(){this.ptr=Oa();x(J)[this.ptr]=this}function K(){this.ptr=Pa();x(K)[this.ptr]=this}function L(){this.ptr=Qa();x(L)[this.ptr]=this}function M(){this.ptr=Ra();x(M)[this.ptr]=this}function N(){this.ptr=Sa();x(N)[this.ptr]=this}function y(){this.ptr=Ta();x(y)[this.ptr]=this}function m(){this.ptr=Ua();x(m)[this.ptr]=
this}n=void 0===n?{}:n;var a="undefined"!=typeof n?n:{},Aa,ka;a.ready=new Promise(function(e,b){Aa=e;ka=b});var Va=!1,Wa=!1;a.onRuntimeInitialized=function(){Va=!0;if(Wa&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.onModuleParsed=function(){Wa=!0;if(Va&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.isVersionSupported=function(e){if("string"!==typeof e)return!1;e=e.split(".");return 2>e.length||3<e.length?!1:1==e[0]&&0<=e[1]&&5>=e[1]?!0:0!=e[0]||10<e[1]?!1:!0};var Xa=
Object.assign({},a),xa="object"==typeof window,ha="function"==typeof importScripts,Ya="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,U="";if(Ya){var Za=require("fs"),qa=require("path");U=ha?qa.dirname(U)+"/":__dirname+"/";var $a=function(e,b){e=e.startsWith("file://")?new URL(e):qa.normalize(e);return Za.readFileSync(e,b?void 0:"utf8")};var ma=function(e){e=$a(e,!0);e.buffer||(e=new Uint8Array(e));return e};var na=function(e,b,c){e=e.startsWith("file://")?
new URL(e):qa.normalize(e);Za.readFile(e,function(d,g){d?c(d):b(g.buffer)})};1<process.argv.length&&process.argv[1].replace(/\\/g,"/");process.argv.slice(2);a.inspect=function(){return"[Emscripten Module object]"}}else if(xa||ha)ha?U=self.location.href:"undefined"!=typeof document&&document.currentScript&&(U=document.currentScript.src),k&&(U=k),U=0!==U.indexOf("blob:")?U.substr(0,U.replace(/[?#].*/,"").lastIndexOf("/")+1):"",$a=function(e){var b=new XMLHttpRequest;b.open("GET",e,!1);b.send(null);
return b.responseText},ha&&(ma=function(e){var b=new XMLHttpRequest;b.open("GET",e,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),na=function(e,b,c){var d=new XMLHttpRequest;d.open("GET",e,!0);d.responseType="arraybuffer";d.onload=function(){200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};var ud=a.print||console.log.bind(console),da=a.printErr||console.warn.bind(console);Object.assign(a,Xa);Xa=null;var fa;a.wasmBinary&&(fa=a.wasmBinary);
"object"!=typeof WebAssembly&&f("no native wasm support detected");var ja,wa=!1,va="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0,Y,ea,ca,V,Ca=[],oa=[],Ba=[],za=!1,aa=0,ra=null,ia=null;var P="draco_decoder.wasm";P.startsWith("data:application/octet-stream;base64,")||(P=l(P));var vd=0,wd=[null,[],[]],xd={b:function(e,b,c){(new O(e)).init(b,c);vd++;throw e;},a:function(){f("")},g:function(e,b,c){ea.copyWithin(e,b,b+c)},e:function(e){var b=ea.length;e>>>=0;if(2147483648<e)return!1;for(var c=
1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,e+100663296);var g=Math;d=Math.max(e,d);g=g.min.call(g,2147483648,d+(65536-d%65536)%65536);a:{d=ja.buffer;try{ja.grow(g-d.byteLength+65535>>>16);A();var u=1;break a}catch(X){}u=void 0}if(u)return!0}return!1},f:function(e){return 52},d:function(e,b,c,d,g){return 70},c:function(e,b,c,d){for(var g=0,u=0;u<c;u++){var X=V[b>>2],ab=V[b+4>>2];b+=8;for(var sa=0;sa<ab;sa++){var ta=ea[X+sa],ua=wd[e];0===ta||10===ta?((1===e?ud:da)(p(ua,0)),ua.length=0):ua.push(ta)}g+=
ab}V[d>>2]=g;return 0}};(function(){function e(g,u){a.asm=g.exports;ja=a.asm.h;A();oa.unshift(a.asm.i);aa--;a.monitorRunDependencies&&a.monitorRunDependencies(aa);0==aa&&(null!==ra&&(clearInterval(ra),ra=null),ia&&(g=ia,ia=null,g()))}function b(g){e(g.instance)}function c(g){return v().then(function(u){return WebAssembly.instantiate(u,d)}).then(function(u){return u}).then(g,function(u){da("failed to asynchronously prepare wasm: "+u);f(u)})}var d={a:xd};aa++;a.monitorRunDependencies&&a.monitorRunDependencies(aa);
if(a.instantiateWasm)try{return a.instantiateWasm(d,e)}catch(g){da("Module.instantiateWasm callback failed with error: "+g),ka(g)}(function(){return fa||"function"!=typeof WebAssembly.instantiateStreaming||P.startsWith("data:application/octet-stream;base64,")||P.startsWith("file://")||Ya||"function"!=typeof fetch?c(b):fetch(P,{credentials:"same-origin"}).then(function(g){return WebAssembly.instantiateStreaming(g,d).then(b,function(u){da("wasm streaming compile failed: "+u);da("falling back to ArrayBuffer instantiation");
return c(b)})})})().catch(ka);return{}})();var bb=a._emscripten_bind_VoidPtr___destroy___0=function(){return(bb=a._emscripten_bind_VoidPtr___destroy___0=a.asm.k).apply(null,arguments)},Da=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=function(){return(Da=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=a.asm.l).apply(null,arguments)},cb=a._emscripten_bind_DecoderBuffer_Init_2=function(){return(cb=a._emscripten_bind_DecoderBuffer_Init_2=a.asm.m).apply(null,arguments)},db=a._emscripten_bind_DecoderBuffer___destroy___0=
function(){return(db=a._emscripten_bind_DecoderBuffer___destroy___0=a.asm.n).apply(null,arguments)},Ea=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=function(){return(Ea=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=a.asm.o).apply(null,arguments)},eb=a._emscripten_bind_AttributeTransformData_transform_type_0=function(){return(eb=a._emscripten_bind_AttributeTransformData_transform_type_0=a.asm.p).apply(null,arguments)},fb=a._emscripten_bind_AttributeTransformData___destroy___0=
function(){return(fb=a._emscripten_bind_AttributeTransformData___destroy___0=a.asm.q).apply(null,arguments)},Fa=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=function(){return(Fa=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=a.asm.r).apply(null,arguments)},gb=a._emscripten_bind_GeometryAttribute___destroy___0=function(){return(gb=a._emscripten_bind_GeometryAttribute___destroy___0=a.asm.s).apply(null,arguments)},Ga=a._emscripten_bind_PointAttribute_PointAttribute_0=function(){return(Ga=
a._emscripten_bind_PointAttribute_PointAttribute_0=a.asm.t).apply(null,arguments)},hb=a._emscripten_bind_PointAttribute_size_0=function(){return(hb=a._emscripten_bind_PointAttribute_size_0=a.asm.u).apply(null,arguments)},ib=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=function(){return(ib=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=a.asm.v).apply(null,arguments)},jb=a._emscripten_bind_PointAttribute_attribute_type_0=function(){return(jb=a._emscripten_bind_PointAttribute_attribute_type_0=
a.asm.w).apply(null,arguments)},kb=a._emscripten_bind_PointAttribute_data_type_0=function(){return(kb=a._emscripten_bind_PointAttribute_data_type_0=a.asm.x).apply(null,arguments)},lb=a._emscripten_bind_PointAttribute_num_components_0=function(){return(lb=a._emscripten_bind_PointAttribute_num_components_0=a.asm.y).apply(null,arguments)},mb=a._emscripten_bind_PointAttribute_normalized_0=function(){return(mb=a._emscripten_bind_PointAttribute_normalized_0=a.asm.z).apply(null,arguments)},nb=a._emscripten_bind_PointAttribute_byte_stride_0=
function(){return(nb=a._emscripten_bind_PointAttribute_byte_stride_0=a.asm.A).apply(null,arguments)},ob=a._emscripten_bind_PointAttribute_byte_offset_0=function(){return(ob=a._emscripten_bind_PointAttribute_byte_offset_0=a.asm.B).apply(null,arguments)},pb=a._emscripten_bind_PointAttribute_unique_id_0=function(){return(pb=a._emscripten_bind_PointAttribute_unique_id_0=a.asm.C).apply(null,arguments)},qb=a._emscripten_bind_PointAttribute___destroy___0=function(){return(qb=a._emscripten_bind_PointAttribute___destroy___0=
a.asm.D).apply(null,arguments)},Ha=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=function(){return(Ha=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=a.asm.E).apply(null,arguments)},rb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=function(){return(rb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=a.asm.F).apply(null,arguments)},sb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=
function(){return(sb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=a.asm.G).apply(null,arguments)},tb=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=function(){return(tb=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=a.asm.H).apply(null,arguments)},ub=a._emscripten_bind_AttributeQuantizationTransform_range_0=function(){return(ub=a._emscripten_bind_AttributeQuantizationTransform_range_0=a.asm.I).apply(null,arguments)},vb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=
function(){return(vb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=a.asm.J).apply(null,arguments)},Ia=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=function(){return(Ia=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=a.asm.K).apply(null,arguments)},wb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=function(){return(wb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=a.asm.L).apply(null,
arguments)},xb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=function(){return(xb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=a.asm.M).apply(null,arguments)},yb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=function(){return(yb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=a.asm.N).apply(null,arguments)},Ja=a._emscripten_bind_PointCloud_PointCloud_0=function(){return(Ja=a._emscripten_bind_PointCloud_PointCloud_0=a.asm.O).apply(null,
arguments)},zb=a._emscripten_bind_PointCloud_num_attributes_0=function(){return(zb=a._emscripten_bind_PointCloud_num_attributes_0=a.asm.P).apply(null,arguments)},Ab=a._emscripten_bind_PointCloud_num_points_0=function(){return(Ab=a._emscripten_bind_PointCloud_num_points_0=a.asm.Q).apply(null,arguments)},Bb=a._emscripten_bind_PointCloud___destroy___0=function(){return(Bb=a._emscripten_bind_PointCloud___destroy___0=a.asm.R).apply(null,arguments)},Ka=a._emscripten_bind_Mesh_Mesh_0=function(){return(Ka=
a._emscripten_bind_Mesh_Mesh_0=a.asm.S).apply(null,arguments)},Cb=a._emscripten_bind_Mesh_num_faces_0=function(){return(Cb=a._emscripten_bind_Mesh_num_faces_0=a.asm.T).apply(null,arguments)},Db=a._emscripten_bind_Mesh_num_attributes_0=function(){return(Db=a._emscripten_bind_Mesh_num_attributes_0=a.asm.U).apply(null,arguments)},Eb=a._emscripten_bind_Mesh_num_points_0=function(){return(Eb=a._emscripten_bind_Mesh_num_points_0=a.asm.V).apply(null,arguments)},Fb=a._emscripten_bind_Mesh___destroy___0=function(){return(Fb=
a._emscripten_bind_Mesh___destroy___0=a.asm.W).apply(null,arguments)},La=a._emscripten_bind_Metadata_Metadata_0=function(){return(La=a._emscripten_bind_Metadata_Metadata_0=a.asm.X).apply(null,arguments)},Gb=a._emscripten_bind_Metadata___destroy___0=function(){return(Gb=a._emscripten_bind_Metadata___destroy___0=a.asm.Y).apply(null,arguments)},Hb=a._emscripten_bind_Status_code_0=function(){return(Hb=a._emscripten_bind_Status_code_0=a.asm.Z).apply(null,arguments)},Ib=a._emscripten_bind_Status_ok_0=function(){return(Ib=
a._emscripten_bind_Status_ok_0=a.asm._).apply(null,arguments)},Jb=a._emscripten_bind_Status_error_msg_0=function(){return(Jb=a._emscripten_bind_Status_error_msg_0=a.asm.$).apply(null,arguments)},Kb=a._emscripten_bind_Status___destroy___0=function(){return(Kb=a._emscripten_bind_Status___destroy___0=a.asm.aa).apply(null,arguments)},Ma=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=function(){return(Ma=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=a.asm.ba).apply(null,arguments)},
Lb=a._emscripten_bind_DracoFloat32Array_GetValue_1=function(){return(Lb=a._emscripten_bind_DracoFloat32Array_GetValue_1=a.asm.ca).apply(null,arguments)},Mb=a._emscripten_bind_DracoFloat32Array_size_0=function(){return(Mb=a._emscripten_bind_DracoFloat32Array_size_0=a.asm.da).apply(null,arguments)},Nb=a._emscripten_bind_DracoFloat32Array___destroy___0=function(){return(Nb=a._emscripten_bind_DracoFloat32Array___destroy___0=a.asm.ea).apply(null,arguments)},Na=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=
function(){return(Na=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=a.asm.fa).apply(null,arguments)},Ob=a._emscripten_bind_DracoInt8Array_GetValue_1=function(){return(Ob=a._emscripten_bind_DracoInt8Array_GetValue_1=a.asm.ga).apply(null,arguments)},Pb=a._emscripten_bind_DracoInt8Array_size_0=function(){return(Pb=a._emscripten_bind_DracoInt8Array_size_0=a.asm.ha).apply(null,arguments)},Qb=a._emscripten_bind_DracoInt8Array___destroy___0=function(){return(Qb=a._emscripten_bind_DracoInt8Array___destroy___0=
a.asm.ia).apply(null,arguments)},Oa=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=function(){return(Oa=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=a.asm.ja).apply(null,arguments)},Rb=a._emscripten_bind_DracoUInt8Array_GetValue_1=function(){return(Rb=a._emscripten_bind_DracoUInt8Array_GetValue_1=a.asm.ka).apply(null,arguments)},Sb=a._emscripten_bind_DracoUInt8Array_size_0=function(){return(Sb=a._emscripten_bind_DracoUInt8Array_size_0=a.asm.la).apply(null,arguments)},Tb=a._emscripten_bind_DracoUInt8Array___destroy___0=
function(){return(Tb=a._emscripten_bind_DracoUInt8Array___destroy___0=a.asm.ma).apply(null,arguments)},Pa=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=function(){return(Pa=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=a.asm.na).apply(null,arguments)},Ub=a._emscripten_bind_DracoInt16Array_GetValue_1=function(){return(Ub=a._emscripten_bind_DracoInt16Array_GetValue_1=a.asm.oa).apply(null,arguments)},Vb=a._emscripten_bind_DracoInt16Array_size_0=function(){return(Vb=a._emscripten_bind_DracoInt16Array_size_0=
a.asm.pa).apply(null,arguments)},Wb=a._emscripten_bind_DracoInt16Array___destroy___0=function(){return(Wb=a._emscripten_bind_DracoInt16Array___destroy___0=a.asm.qa).apply(null,arguments)},Qa=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=function(){return(Qa=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=a.asm.ra).apply(null,arguments)},Xb=a._emscripten_bind_DracoUInt16Array_GetValue_1=function(){return(Xb=a._emscripten_bind_DracoUInt16Array_GetValue_1=a.asm.sa).apply(null,arguments)},
Yb=a._emscripten_bind_DracoUInt16Array_size_0=function(){return(Yb=a._emscripten_bind_DracoUInt16Array_size_0=a.asm.ta).apply(null,arguments)},Zb=a._emscripten_bind_DracoUInt16Array___destroy___0=function(){return(Zb=a._emscripten_bind_DracoUInt16Array___destroy___0=a.asm.ua).apply(null,arguments)},Ra=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=function(){return(Ra=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=a.asm.va).apply(null,arguments)},$b=a._emscripten_bind_DracoInt32Array_GetValue_1=
function(){return($b=a._emscripten_bind_DracoInt32Array_GetValue_1=a.asm.wa).apply(null,arguments)},ac=a._emscripten_bind_DracoInt32Array_size_0=function(){return(ac=a._emscripten_bind_DracoInt32Array_size_0=a.asm.xa).apply(null,arguments)},bc=a._emscripten_bind_DracoInt32Array___destroy___0=function(){return(bc=a._emscripten_bind_DracoInt32Array___destroy___0=a.asm.ya).apply(null,arguments)},Sa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=function(){return(Sa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=
a.asm.za).apply(null,arguments)},cc=a._emscripten_bind_DracoUInt32Array_GetValue_1=function(){return(cc=a._emscripten_bind_DracoUInt32Array_GetValue_1=a.asm.Aa).apply(null,arguments)},dc=a._emscripten_bind_DracoUInt32Array_size_0=function(){return(dc=a._emscripten_bind_DracoUInt32Array_size_0=a.asm.Ba).apply(null,arguments)},ec=a._emscripten_bind_DracoUInt32Array___destroy___0=function(){return(ec=a._emscripten_bind_DracoUInt32Array___destroy___0=a.asm.Ca).apply(null,arguments)},Ta=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=
function(){return(Ta=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=a.asm.Da).apply(null,arguments)},fc=a._emscripten_bind_MetadataQuerier_HasEntry_2=function(){return(fc=a._emscripten_bind_MetadataQuerier_HasEntry_2=a.asm.Ea).apply(null,arguments)},gc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=function(){return(gc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=a.asm.Fa).apply(null,arguments)},hc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=function(){return(hc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=
a.asm.Ga).apply(null,arguments)},ic=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=function(){return(ic=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=a.asm.Ha).apply(null,arguments)},jc=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=function(){return(jc=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=a.asm.Ia).apply(null,arguments)},kc=a._emscripten_bind_MetadataQuerier_NumEntries_1=function(){return(kc=a._emscripten_bind_MetadataQuerier_NumEntries_1=a.asm.Ja).apply(null,arguments)},
lc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=function(){return(lc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=a.asm.Ka).apply(null,arguments)},mc=a._emscripten_bind_MetadataQuerier___destroy___0=function(){return(mc=a._emscripten_bind_MetadataQuerier___destroy___0=a.asm.La).apply(null,arguments)},Ua=a._emscripten_bind_Decoder_Decoder_0=function(){return(Ua=a._emscripten_bind_Decoder_Decoder_0=a.asm.Ma).apply(null,arguments)},nc=a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=function(){return(nc=
a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=a.asm.Na).apply(null,arguments)},oc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=function(){return(oc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=a.asm.Oa).apply(null,arguments)},pc=a._emscripten_bind_Decoder_GetAttributeId_2=function(){return(pc=a._emscripten_bind_Decoder_GetAttributeId_2=a.asm.Pa).apply(null,arguments)},qc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=function(){return(qc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=
a.asm.Qa).apply(null,arguments)},rc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=function(){return(rc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=a.asm.Ra).apply(null,arguments)},sc=a._emscripten_bind_Decoder_GetAttribute_2=function(){return(sc=a._emscripten_bind_Decoder_GetAttribute_2=a.asm.Sa).apply(null,arguments)},tc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=function(){return(tc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=a.asm.Ta).apply(null,arguments)},
uc=a._emscripten_bind_Decoder_GetMetadata_1=function(){return(uc=a._emscripten_bind_Decoder_GetMetadata_1=a.asm.Ua).apply(null,arguments)},vc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=function(){return(vc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=a.asm.Va).apply(null,arguments)},wc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=function(){return(wc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=a.asm.Wa).apply(null,arguments)},xc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=
function(){return(xc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=a.asm.Xa).apply(null,arguments)},yc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=function(){return(yc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=a.asm.Ya).apply(null,arguments)},zc=a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=function(){return(zc=a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=a.asm.Za).apply(null,arguments)},Ac=a._emscripten_bind_Decoder_GetAttributeFloat_3=function(){return(Ac=
a._emscripten_bind_Decoder_GetAttributeFloat_3=a.asm._a).apply(null,arguments)},Bc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=function(){return(Bc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=a.asm.$a).apply(null,arguments)},Cc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=function(){return(Cc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=a.asm.ab).apply(null,arguments)},Dc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=function(){return(Dc=
a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=a.asm.bb).apply(null,arguments)},Ec=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=function(){return(Ec=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=a.asm.cb).apply(null,arguments)},Fc=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=function(){return(Fc=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=a.asm.db).apply(null,arguments)},Gc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=
function(){return(Gc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=a.asm.eb).apply(null,arguments)},Hc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=function(){return(Hc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=a.asm.fb).apply(null,arguments)},Ic=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=function(){return(Ic=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=a.asm.gb).apply(null,arguments)},Jc=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=
function(){return(Jc=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=a.asm.hb).apply(null,arguments)},Kc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=function(){return(Kc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=a.asm.ib).apply(null,arguments)},Lc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=function(){return(Lc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=a.asm.jb).apply(null,arguments)},Mc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=
function(){return(Mc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=a.asm.kb).apply(null,arguments)},Nc=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=function(){return(Nc=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=a.asm.lb).apply(null,arguments)},Oc=a._emscripten_bind_Decoder___destroy___0=function(){return(Oc=a._emscripten_bind_Decoder___destroy___0=a.asm.mb).apply(null,arguments)},Pc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=function(){return(Pc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=
a.asm.nb).apply(null,arguments)},Qc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=function(){return(Qc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=a.asm.ob).apply(null,arguments)},Rc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=function(){return(Rc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=a.asm.pb).apply(null,arguments)},Sc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=
function(){return(Sc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=a.asm.qb).apply(null,arguments)},Tc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=function(){return(Tc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=a.asm.rb).apply(null,arguments)},Uc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=function(){return(Uc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=a.asm.sb).apply(null,arguments)},Vc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=
function(){return(Vc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=a.asm.tb).apply(null,arguments)},Wc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=function(){return(Wc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=a.asm.ub).apply(null,arguments)},Xc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=function(){return(Xc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=a.asm.vb).apply(null,arguments)},Yc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=
function(){return(Yc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=a.asm.wb).apply(null,arguments)},Zc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=function(){return(Zc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=a.asm.xb).apply(null,arguments)},$c=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=function(){return($c=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=a.asm.yb).apply(null,arguments)},ad=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=
function(){return(ad=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=a.asm.zb).apply(null,arguments)},bd=a._emscripten_enum_draco_DataType_DT_INVALID=function(){return(bd=a._emscripten_enum_draco_DataType_DT_INVALID=a.asm.Ab).apply(null,arguments)},cd=a._emscripten_enum_draco_DataType_DT_INT8=function(){return(cd=a._emscripten_enum_draco_DataType_DT_INT8=a.asm.Bb).apply(null,arguments)},dd=a._emscripten_enum_draco_DataType_DT_UINT8=function(){return(dd=a._emscripten_enum_draco_DataType_DT_UINT8=
a.asm.Cb).apply(null,arguments)},ed=a._emscripten_enum_draco_DataType_DT_INT16=function(){return(ed=a._emscripten_enum_draco_DataType_DT_INT16=a.asm.Db).apply(null,arguments)},fd=a._emscripten_enum_draco_DataType_DT_UINT16=function(){return(fd=a._emscripten_enum_draco_DataType_DT_UINT16=a.asm.Eb).apply(null,arguments)},gd=a._emscripten_enum_draco_DataType_DT_INT32=function(){return(gd=a._emscripten_enum_draco_DataType_DT_INT32=a.asm.Fb).apply(null,arguments)},hd=a._emscripten_enum_draco_DataType_DT_UINT32=
function(){return(hd=a._emscripten_enum_draco_DataType_DT_UINT32=a.asm.Gb).apply(null,arguments)},id=a._emscripten_enum_draco_DataType_DT_INT64=function(){return(id=a._emscripten_enum_draco_DataType_DT_INT64=a.asm.Hb).apply(null,arguments)},jd=a._emscripten_enum_draco_DataType_DT_UINT64=function(){return(jd=a._emscripten_enum_draco_DataType_DT_UINT64=a.asm.Ib).apply(null,arguments)},kd=a._emscripten_enum_draco_DataType_DT_FLOAT32=function(){return(kd=a._emscripten_enum_draco_DataType_DT_FLOAT32=a.asm.Jb).apply(null,
arguments)},ld=a._emscripten_enum_draco_DataType_DT_FLOAT64=function(){return(ld=a._emscripten_enum_draco_DataType_DT_FLOAT64=a.asm.Kb).apply(null,arguments)},md=a._emscripten_enum_draco_DataType_DT_BOOL=function(){return(md=a._emscripten_enum_draco_DataType_DT_BOOL=a.asm.Lb).apply(null,arguments)},nd=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=function(){return(nd=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=a.asm.Mb).apply(null,arguments)},od=a._emscripten_enum_draco_StatusCode_OK=function(){return(od=
a._emscripten_enum_draco_StatusCode_OK=a.asm.Nb).apply(null,arguments)},pd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=function(){return(pd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=a.asm.Ob).apply(null,arguments)},qd=a._emscripten_enum_draco_StatusCode_IO_ERROR=function(){return(qd=a._emscripten_enum_draco_StatusCode_IO_ERROR=a.asm.Pb).apply(null,arguments)},rd=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=function(){return(rd=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=
a.asm.Qb).apply(null,arguments)},sd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=function(){return(sd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=a.asm.Rb).apply(null,arguments)},td=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=function(){return(td=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=a.asm.Sb).apply(null,arguments)};a._malloc=function(){return(a._malloc=a.asm.Tb).apply(null,arguments)};a._free=function(){return(a._free=a.asm.Ub).apply(null,arguments)};
var ya=function(){return(ya=a.asm.Vb).apply(null,arguments)};a.___start_em_js=15856;a.___stop_em_js=15954;var la;ia=function b(){la||ba();la||(ia=b)};if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();ba();t.prototype=Object.create(t.prototype);t.prototype.constructor=t;t.prototype.__class__=t;t.__cache__={};a.WrapperObject=t;a.getCache=x;a.wrapPointer=D;a.castObject=function(b,c){return D(b.ptr,c)};a.NULL=D(0);a.destroy=function(b){if(!b.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";
b.__destroy__();delete x(b.__class__)[b.ptr]};a.compare=function(b,c){return b.ptr===c.ptr};a.getPointer=function(b){return b.ptr};a.getClass=function(b){return b.__class__};var r={buffer:0,size:0,pos:0,temps:[],needed:0,prepare:function(){if(r.needed){for(var b=0;b<r.temps.length;b++)a._free(r.temps[b]);r.temps.length=0;a._free(r.buffer);r.buffer=0;r.size+=r.needed;r.needed=0}r.buffer||(r.size+=128,r.buffer=a._malloc(r.size),r.buffer||f(void 0));r.pos=0},alloc:function(b,c){r.buffer||f(void 0);b=
b.length*c.BYTES_PER_ELEMENT;b=b+7&-8;r.pos+b>=r.size?(0<b||f(void 0),r.needed+=b,c=a._malloc(b),r.temps.push(c)):(c=r.buffer+r.pos,r.pos+=b);return c},copy:function(b,c,d){d>>>=0;switch(c.BYTES_PER_ELEMENT){case 2:d>>>=1;break;case 4:d>>>=2;break;case 8:d>>>=3}for(var g=0;g<b.length;g++)c[d+g]=b[g]}};Z.prototype=Object.create(t.prototype);Z.prototype.constructor=Z;Z.prototype.__class__=Z;Z.__cache__={};a.VoidPtr=Z;Z.prototype.__destroy__=Z.prototype.__destroy__=function(){bb(this.ptr)};S.prototype=
Object.create(t.prototype);S.prototype.constructor=S;S.prototype.__class__=S;S.__cache__={};a.DecoderBuffer=S;S.prototype.Init=S.prototype.Init=function(b,c){var d=this.ptr;r.prepare();"object"==typeof b&&(b=pa(b));c&&"object"===typeof c&&(c=c.ptr);cb(d,b,c)};S.prototype.__destroy__=S.prototype.__destroy__=function(){db(this.ptr)};Q.prototype=Object.create(t.prototype);Q.prototype.constructor=Q;Q.prototype.__class__=Q;Q.__cache__={};a.AttributeTransformData=Q;Q.prototype.transform_type=Q.prototype.transform_type=
function(){return eb(this.ptr)};Q.prototype.__destroy__=Q.prototype.__destroy__=function(){fb(this.ptr)};W.prototype=Object.create(t.prototype);W.prototype.constructor=W;W.prototype.__class__=W;W.__cache__={};a.GeometryAttribute=W;W.prototype.__destroy__=W.prototype.__destroy__=function(){gb(this.ptr)};w.prototype=Object.create(t.prototype);w.prototype.constructor=w;w.prototype.__class__=w;w.__cache__={};a.PointAttribute=w;w.prototype.size=w.prototype.size=function(){return hb(this.ptr)};w.prototype.GetAttributeTransformData=
w.prototype.GetAttributeTransformData=function(){return D(ib(this.ptr),Q)};w.prototype.attribute_type=w.prototype.attribute_type=function(){return jb(this.ptr)};w.prototype.data_type=w.prototype.data_type=function(){return kb(this.ptr)};w.prototype.num_components=w.prototype.num_components=function(){return lb(this.ptr)};w.prototype.normalized=w.prototype.normalized=function(){return!!mb(this.ptr)};w.prototype.byte_stride=w.prototype.byte_stride=function(){return nb(this.ptr)};w.prototype.byte_offset=
w.prototype.byte_offset=function(){return ob(this.ptr)};w.prototype.unique_id=w.prototype.unique_id=function(){return pb(this.ptr)};w.prototype.__destroy__=w.prototype.__destroy__=function(){qb(this.ptr)};C.prototype=Object.create(t.prototype);C.prototype.constructor=C;C.prototype.__class__=C;C.__cache__={};a.AttributeQuantizationTransform=C;C.prototype.InitFromAttribute=C.prototype.InitFromAttribute=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!rb(c,b)};C.prototype.quantization_bits=
C.prototype.quantization_bits=function(){return sb(this.ptr)};C.prototype.min_value=C.prototype.min_value=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return tb(c,b)};C.prototype.range=C.prototype.range=function(){return ub(this.ptr)};C.prototype.__destroy__=C.prototype.__destroy__=function(){vb(this.ptr)};F.prototype=Object.create(t.prototype);F.prototype.constructor=F;F.prototype.__class__=F;F.__cache__={};a.AttributeOctahedronTransform=F;F.prototype.InitFromAttribute=F.prototype.InitFromAttribute=
function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!wb(c,b)};F.prototype.quantization_bits=F.prototype.quantization_bits=function(){return xb(this.ptr)};F.prototype.__destroy__=F.prototype.__destroy__=function(){yb(this.ptr)};G.prototype=Object.create(t.prototype);G.prototype.constructor=G;G.prototype.__class__=G;G.__cache__={};a.PointCloud=G;G.prototype.num_attributes=G.prototype.num_attributes=function(){return zb(this.ptr)};G.prototype.num_points=G.prototype.num_points=function(){return Ab(this.ptr)};
G.prototype.__destroy__=G.prototype.__destroy__=function(){Bb(this.ptr)};E.prototype=Object.create(t.prototype);E.prototype.constructor=E;E.prototype.__class__=E;E.__cache__={};a.Mesh=E;E.prototype.num_faces=E.prototype.num_faces=function(){return Cb(this.ptr)};E.prototype.num_attributes=E.prototype.num_attributes=function(){return Db(this.ptr)};E.prototype.num_points=E.prototype.num_points=function(){return Eb(this.ptr)};E.prototype.__destroy__=E.prototype.__destroy__=function(){Fb(this.ptr)};T.prototype=
Object.create(t.prototype);T.prototype.constructor=T;T.prototype.__class__=T;T.__cache__={};a.Metadata=T;T.prototype.__destroy__=T.prototype.__destroy__=function(){Gb(this.ptr)};B.prototype=Object.create(t.prototype);B.prototype.constructor=B;B.prototype.__class__=B;B.__cache__={};a.Status=B;B.prototype.code=B.prototype.code=function(){return Hb(this.ptr)};B.prototype.ok=B.prototype.ok=function(){return!!Ib(this.ptr)};B.prototype.error_msg=B.prototype.error_msg=function(){return h(Jb(this.ptr))};
B.prototype.__destroy__=B.prototype.__destroy__=function(){Kb(this.ptr)};H.prototype=Object.create(t.prototype);H.prototype.constructor=H;H.prototype.__class__=H;H.__cache__={};a.DracoFloat32Array=H;H.prototype.GetValue=H.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Lb(c,b)};H.prototype.size=H.prototype.size=function(){return Mb(this.ptr)};H.prototype.__destroy__=H.prototype.__destroy__=function(){Nb(this.ptr)};I.prototype=Object.create(t.prototype);I.prototype.constructor=
I;I.prototype.__class__=I;I.__cache__={};a.DracoInt8Array=I;I.prototype.GetValue=I.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Ob(c,b)};I.prototype.size=I.prototype.size=function(){return Pb(this.ptr)};I.prototype.__destroy__=I.prototype.__destroy__=function(){Qb(this.ptr)};J.prototype=Object.create(t.prototype);J.prototype.constructor=J;J.prototype.__class__=J;J.__cache__={};a.DracoUInt8Array=J;J.prototype.GetValue=J.prototype.GetValue=function(b){var c=
this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Rb(c,b)};J.prototype.size=J.prototype.size=function(){return Sb(this.ptr)};J.prototype.__destroy__=J.prototype.__destroy__=function(){Tb(this.ptr)};K.prototype=Object.create(t.prototype);K.prototype.constructor=K;K.prototype.__class__=K;K.__cache__={};a.DracoInt16Array=K;K.prototype.GetValue=K.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Ub(c,b)};K.prototype.size=K.prototype.size=function(){return Vb(this.ptr)};
K.prototype.__destroy__=K.prototype.__destroy__=function(){Wb(this.ptr)};L.prototype=Object.create(t.prototype);L.prototype.constructor=L;L.prototype.__class__=L;L.__cache__={};a.DracoUInt16Array=L;L.prototype.GetValue=L.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Xb(c,b)};L.prototype.size=L.prototype.size=function(){return Yb(this.ptr)};L.prototype.__destroy__=L.prototype.__destroy__=function(){Zb(this.ptr)};M.prototype=Object.create(t.prototype);M.prototype.constructor=
M;M.prototype.__class__=M;M.__cache__={};a.DracoInt32Array=M;M.prototype.GetValue=M.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return $b(c,b)};M.prototype.size=M.prototype.size=function(){return ac(this.ptr)};M.prototype.__destroy__=M.prototype.__destroy__=function(){bc(this.ptr)};N.prototype=Object.create(t.prototype);N.prototype.constructor=N;N.prototype.__class__=N;N.__cache__={};a.DracoUInt32Array=N;N.prototype.GetValue=N.prototype.GetValue=function(b){var c=
this.ptr;b&&"object"===typeof b&&(b=b.ptr);return cc(c,b)};N.prototype.size=N.prototype.size=function(){return dc(this.ptr)};N.prototype.__destroy__=N.prototype.__destroy__=function(){ec(this.ptr)};y.prototype=Object.create(t.prototype);y.prototype.constructor=y;y.prototype.__class__=y;y.__cache__={};a.MetadataQuerier=y;y.prototype.HasEntry=y.prototype.HasEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return!!fc(d,b,c)};y.prototype.GetIntEntry=
y.prototype.GetIntEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return gc(d,b,c)};y.prototype.GetIntEntryArray=y.prototype.GetIntEntryArray=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);d&&"object"===typeof d&&(d=d.ptr);hc(g,b,c,d)};y.prototype.GetDoubleEntry=y.prototype.GetDoubleEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=
c&&"object"===typeof c?c.ptr:R(c);return ic(d,b,c)};y.prototype.GetStringEntry=y.prototype.GetStringEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return h(jc(d,b,c))};y.prototype.NumEntries=y.prototype.NumEntries=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return kc(c,b)};y.prototype.GetEntryName=y.prototype.GetEntryName=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=
c.ptr);return h(lc(d,b,c))};y.prototype.__destroy__=y.prototype.__destroy__=function(){mc(this.ptr)};m.prototype=Object.create(t.prototype);m.prototype.constructor=m;m.prototype.__class__=m;m.__cache__={};a.Decoder=m;m.prototype.DecodeArrayToPointCloud=m.prototype.DecodeArrayToPointCloud=function(b,c,d){var g=this.ptr;r.prepare();"object"==typeof b&&(b=pa(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return D(nc(g,b,c,d),B)};m.prototype.DecodeArrayToMesh=m.prototype.DecodeArrayToMesh=
function(b,c,d){var g=this.ptr;r.prepare();"object"==typeof b&&(b=pa(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return D(oc(g,b,c,d),B)};m.prototype.GetAttributeId=m.prototype.GetAttributeId=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return pc(d,b,c)};m.prototype.GetAttributeIdByName=m.prototype.GetAttributeIdByName=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?
c.ptr:R(c);return qc(d,b,c)};m.prototype.GetAttributeIdByMetadataEntry=m.prototype.GetAttributeIdByMetadataEntry=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);d=d&&"object"===typeof d?d.ptr:R(d);return rc(g,b,c,d)};m.prototype.GetAttribute=m.prototype.GetAttribute=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(sc(d,b,c),w)};m.prototype.GetAttributeByUniqueId=m.prototype.GetAttributeByUniqueId=
function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(tc(d,b,c),w)};m.prototype.GetMetadata=m.prototype.GetMetadata=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return D(uc(c,b),T)};m.prototype.GetAttributeMetadata=m.prototype.GetAttributeMetadata=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(vc(d,b,c),T)};m.prototype.GetFaceFromMesh=m.prototype.GetFaceFromMesh=function(b,
c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!wc(g,b,c,d)};m.prototype.GetTriangleStripsFromMesh=m.prototype.GetTriangleStripsFromMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return xc(d,b,c)};m.prototype.GetTrianglesUInt16Array=m.prototype.GetTrianglesUInt16Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);
d&&"object"===typeof d&&(d=d.ptr);return!!yc(g,b,c,d)};m.prototype.GetTrianglesUInt32Array=m.prototype.GetTrianglesUInt32Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!zc(g,b,c,d)};m.prototype.GetAttributeFloat=m.prototype.GetAttributeFloat=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ac(g,b,c,d)};m.prototype.GetAttributeFloatForAllPoints=
m.prototype.GetAttributeFloatForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Bc(g,b,c,d)};m.prototype.GetAttributeIntForAllPoints=m.prototype.GetAttributeIntForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Cc(g,b,c,d)};m.prototype.GetAttributeInt8ForAllPoints=m.prototype.GetAttributeInt8ForAllPoints=
function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Dc(g,b,c,d)};m.prototype.GetAttributeUInt8ForAllPoints=m.prototype.GetAttributeUInt8ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ec(g,b,c,d)};m.prototype.GetAttributeInt16ForAllPoints=m.prototype.GetAttributeInt16ForAllPoints=function(b,c,d){var g=this.ptr;
b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Fc(g,b,c,d)};m.prototype.GetAttributeUInt16ForAllPoints=m.prototype.GetAttributeUInt16ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Gc(g,b,c,d)};m.prototype.GetAttributeInt32ForAllPoints=m.prototype.GetAttributeInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&
(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Hc(g,b,c,d)};m.prototype.GetAttributeUInt32ForAllPoints=m.prototype.GetAttributeUInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ic(g,b,c,d)};m.prototype.GetAttributeDataArrayForAllPoints=m.prototype.GetAttributeDataArrayForAllPoints=function(b,c,d,g,u){var X=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&
"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);g&&"object"===typeof g&&(g=g.ptr);u&&"object"===typeof u&&(u=u.ptr);return!!Jc(X,b,c,d,g,u)};m.prototype.SkipAttributeTransform=m.prototype.SkipAttributeTransform=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);Kc(c,b)};m.prototype.GetEncodedGeometryType_Deprecated=m.prototype.GetEncodedGeometryType_Deprecated=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Lc(c,b)};m.prototype.DecodeBufferToPointCloud=
m.prototype.DecodeBufferToPointCloud=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(Mc(d,b,c),B)};m.prototype.DecodeBufferToMesh=m.prototype.DecodeBufferToMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return D(Nc(d,b,c),B)};m.prototype.__destroy__=m.prototype.__destroy__=function(){Oc(this.ptr)};(function(){function b(){a.ATTRIBUTE_INVALID_TRANSFORM=Pc();a.ATTRIBUTE_NO_TRANSFORM=Qc();
a.ATTRIBUTE_QUANTIZATION_TRANSFORM=Rc();a.ATTRIBUTE_OCTAHEDRON_TRANSFORM=Sc();a.INVALID=Tc();a.POSITION=Uc();a.NORMAL=Vc();a.COLOR=Wc();a.TEX_COORD=Xc();a.GENERIC=Yc();a.INVALID_GEOMETRY_TYPE=Zc();a.POINT_CLOUD=$c();a.TRIANGULAR_MESH=ad();a.DT_INVALID=bd();a.DT_INT8=cd();a.DT_UINT8=dd();a.DT_INT16=ed();a.DT_UINT16=fd();a.DT_INT32=gd();a.DT_UINT32=hd();a.DT_INT64=id();a.DT_UINT64=jd();a.DT_FLOAT32=kd();a.DT_FLOAT64=ld();a.DT_BOOL=md();a.DT_TYPES_COUNT=nd();a.OK=od();a.DRACO_ERROR=pd();a.IO_ERROR=qd();
a.INVALID_PARAMETER=rd();a.UNSUPPORTED_VERSION=sd();a.UNKNOWN_VERSION=td()}za?b():oa.unshift(b)})();if("function"===typeof a.onModuleParsed)a.onModuleParsed();a.Decoder.prototype.GetEncodedGeometryType=function(b){if(b.__class__&&b.__class__===a.DecoderBuffer)return a.Decoder.prototype.GetEncodedGeometryType_Deprecated(b);if(8>b.byteLength)return a.INVALID_GEOMETRY_TYPE;switch(b[7]){case 0:return a.POINT_CLOUD;case 1:return a.TRIANGULAR_MESH;default:return a.INVALID_GEOMETRY_TYPE}};return n.ready}}();
"object"===typeof exports&&"object"===typeof module?module.exports=DracoDecoderModule:"function"===typeof define&&define.amd?define([],function(){return DracoDecoderModule}):"object"===typeof exports&&(exports.DracoDecoderModule=DracoDecoderModule);

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,116 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(h){var n=0;return function(){return n<h.length?{done:!1,value:h[n++]}:{done:!0}}};$jscomp.arrayIterator=function(h){return{next:$jscomp.arrayIteratorImpl(h)}};$jscomp.makeIterator=function(h){var n="undefined"!=typeof Symbol&&Symbol.iterator&&h[Symbol.iterator];return n?n.call(h):$jscomp.arrayIterator(h)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;
$jscomp.ISOLATE_POLYFILLS=!1;$jscomp.FORCE_POLYFILL_PROMISE=!1;$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;$jscomp.getGlobal=function(h){h=["object"==typeof globalThis&&globalThis,h,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var n=0;n<h.length;++n){var k=h[n];if(k&&k.Math==Math)return k}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(h,n,k){if(h==Array.prototype||h==Object.prototype)return h;h[n]=k.value;return h};$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";
var $jscomp$lookupPolyfilledValue=function(h,n){var k=$jscomp.propertyToPolyfillSymbol[n];if(null==k)return h[n];k=h[k];return void 0!==k?k:h[n]};$jscomp.polyfill=function(h,n,k,p){n&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(h,n,k,p):$jscomp.polyfillUnisolated(h,n,k,p))};
$jscomp.polyfillUnisolated=function(h,n,k,p){k=$jscomp.global;h=h.split(".");for(p=0;p<h.length-1;p++){var l=h[p];if(!(l in k))return;k=k[l]}h=h[h.length-1];p=k[h];n=n(p);n!=p&&null!=n&&$jscomp.defineProperty(k,h,{configurable:!0,writable:!0,value:n})};
$jscomp.polyfillIsolated=function(h,n,k,p){var l=h.split(".");h=1===l.length;p=l[0];p=!h&&p in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var y=0;y<l.length-1;y++){var f=l[y];if(!(f in p))return;p=p[f]}l=l[l.length-1];k=$jscomp.IS_SYMBOL_NATIVE&&"es6"===k?p[l]:null;n=n(k);null!=n&&(h?$jscomp.defineProperty($jscomp.polyfills,l,{configurable:!0,writable:!0,value:n}):n!==k&&(void 0===$jscomp.propertyToPolyfillSymbol[l]&&(k=1E9*Math.random()>>>0,$jscomp.propertyToPolyfillSymbol[l]=$jscomp.IS_SYMBOL_NATIVE?
$jscomp.global.Symbol(l):$jscomp.POLYFILL_PREFIX+k+"$"+l),$jscomp.defineProperty(p,$jscomp.propertyToPolyfillSymbol[l],{configurable:!0,writable:!0,value:n})))};
$jscomp.polyfill("Promise",function(h){function n(){this.batch_=null}function k(f){return f instanceof l?f:new l(function(q,u){q(f)})}if(h&&(!($jscomp.FORCE_POLYFILL_PROMISE||$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION&&"undefined"===typeof $jscomp.global.PromiseRejectionEvent)||!$jscomp.global.Promise||-1===$jscomp.global.Promise.toString().indexOf("[native code]")))return h;n.prototype.asyncExecute=function(f){if(null==this.batch_){this.batch_=[];var q=this;this.asyncExecuteFunction(function(){q.executeBatch_()})}this.batch_.push(f)};
var p=$jscomp.global.setTimeout;n.prototype.asyncExecuteFunction=function(f){p(f,0)};n.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var f=this.batch_;this.batch_=[];for(var q=0;q<f.length;++q){var u=f[q];f[q]=null;try{u()}catch(A){this.asyncThrow_(A)}}}this.batch_=null};n.prototype.asyncThrow_=function(f){this.asyncExecuteFunction(function(){throw f;})};var l=function(f){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];this.isRejectionHandled_=!1;var q=this.createResolveAndReject_();
try{f(q.resolve,q.reject)}catch(u){q.reject(u)}};l.prototype.createResolveAndReject_=function(){function f(A){return function(F){u||(u=!0,A.call(q,F))}}var q=this,u=!1;return{resolve:f(this.resolveTo_),reject:f(this.reject_)}};l.prototype.resolveTo_=function(f){if(f===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(f instanceof l)this.settleSameAsPromise_(f);else{a:switch(typeof f){case "object":var q=null!=f;break a;case "function":q=!0;break a;default:q=!1}q?this.resolveToNonPromiseObj_(f):
this.fulfill_(f)}};l.prototype.resolveToNonPromiseObj_=function(f){var q=void 0;try{q=f.then}catch(u){this.reject_(u);return}"function"==typeof q?this.settleSameAsThenable_(q,f):this.fulfill_(f)};l.prototype.reject_=function(f){this.settle_(2,f)};l.prototype.fulfill_=function(f){this.settle_(1,f)};l.prototype.settle_=function(f,q){if(0!=this.state_)throw Error("Cannot settle("+f+", "+q+"): Promise already settled in state"+this.state_);this.state_=f;this.result_=q;2===this.state_&&this.scheduleUnhandledRejectionCheck_();
this.executeOnSettledCallbacks_()};l.prototype.scheduleUnhandledRejectionCheck_=function(){var f=this;p(function(){if(f.notifyUnhandledRejection_()){var q=$jscomp.global.console;"undefined"!==typeof q&&q.error(f.result_)}},1)};l.prototype.notifyUnhandledRejection_=function(){if(this.isRejectionHandled_)return!1;var f=$jscomp.global.CustomEvent,q=$jscomp.global.Event,u=$jscomp.global.dispatchEvent;if("undefined"===typeof u)return!0;"function"===typeof f?f=new f("unhandledrejection",{cancelable:!0}):
"function"===typeof q?f=new q("unhandledrejection",{cancelable:!0}):(f=$jscomp.global.document.createEvent("CustomEvent"),f.initCustomEvent("unhandledrejection",!1,!0,f));f.promise=this;f.reason=this.result_;return u(f)};l.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var f=0;f<this.onSettledCallbacks_.length;++f)y.asyncExecute(this.onSettledCallbacks_[f]);this.onSettledCallbacks_=null}};var y=new n;l.prototype.settleSameAsPromise_=function(f){var q=this.createResolveAndReject_();
f.callWhenSettled_(q.resolve,q.reject)};l.prototype.settleSameAsThenable_=function(f,q){var u=this.createResolveAndReject_();try{f.call(q,u.resolve,u.reject)}catch(A){u.reject(A)}};l.prototype.then=function(f,q){function u(w,B){return"function"==typeof w?function(R){try{A(w(R))}catch(Z){F(Z)}}:B}var A,F,v=new l(function(w,B){A=w;F=B});this.callWhenSettled_(u(f,A),u(q,F));return v};l.prototype.catch=function(f){return this.then(void 0,f)};l.prototype.callWhenSettled_=function(f,q){function u(){switch(A.state_){case 1:f(A.result_);
break;case 2:q(A.result_);break;default:throw Error("Unexpected state: "+A.state_);}}var A=this;null==this.onSettledCallbacks_?y.asyncExecute(u):this.onSettledCallbacks_.push(u);this.isRejectionHandled_=!0};l.resolve=k;l.reject=function(f){return new l(function(q,u){u(f)})};l.race=function(f){return new l(function(q,u){for(var A=$jscomp.makeIterator(f),F=A.next();!F.done;F=A.next())k(F.value).callWhenSettled_(q,u)})};l.all=function(f){var q=$jscomp.makeIterator(f),u=q.next();return u.done?k([]):new l(function(A,
F){function v(R){return function(Z){w[R]=Z;B--;0==B&&A(w)}}var w=[],B=0;do w.push(void 0),B++,k(u.value).callWhenSettled_(v(w.length-1),F),u=q.next();while(!u.done)})};return l},"es6","es3");$jscomp.owns=function(h,n){return Object.prototype.hasOwnProperty.call(h,n)};$jscomp.assign=$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.assign?Object.assign:function(h,n){for(var k=1;k<arguments.length;k++){var p=arguments[k];if(p)for(var l in p)$jscomp.owns(p,l)&&(h[l]=p[l])}return h};
$jscomp.polyfill("Object.assign",function(h){return h||$jscomp.assign},"es6","es3");$jscomp.checkStringArgs=function(h,n,k){if(null==h)throw new TypeError("The 'this' value for String.prototype."+k+" must not be null or undefined");if(n instanceof RegExp)throw new TypeError("First argument to String.prototype."+k+" must not be a regular expression");return h+""};
$jscomp.polyfill("String.prototype.startsWith",function(h){return h?h:function(n,k){var p=$jscomp.checkStringArgs(this,n,"startsWith");n+="";var l=p.length,y=n.length;k=Math.max(0,Math.min(k|0,p.length));for(var f=0;f<y&&k<l;)if(p[k++]!=n[f++])return!1;return f>=y}},"es6","es3");
$jscomp.polyfill("Array.prototype.copyWithin",function(h){function n(k){k=Number(k);return Infinity===k||-Infinity===k?k:k|0}return h?h:function(k,p,l){var y=this.length;k=n(k);p=n(p);l=void 0===l?y:n(l);k=0>k?Math.max(y+k,0):Math.min(k,y);p=0>p?Math.max(y+p,0):Math.min(p,y);l=0>l?Math.max(y+l,0):Math.min(l,y);if(k<p)for(;p<l;)p in this?this[k++]=this[p++]:(delete this[k++],p++);else for(l=Math.min(l,y+p-k),k+=l-p;l>p;)--l in this?this[--k]=this[l]:delete this[--k];return this}},"es6","es3");
$jscomp.typedArrayCopyWithin=function(h){return h?h:Array.prototype.copyWithin};$jscomp.polyfill("Int8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8ClampedArray.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
$jscomp.polyfill("Uint16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float64Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
var DracoDecoderModule=function(){var h="undefined"!==typeof document&&document.currentScript?document.currentScript.src:void 0;"undefined"!==typeof __filename&&(h=h||__filename);return function(n){function k(e){return a.locateFile?a.locateFile(e,U):U+e}function p(e,b){if(e){var c=ia;var d=e+b;for(b=e;c[b]&&!(b>=d);)++b;if(16<b-e&&c.buffer&&ra)c=ra.decode(c.subarray(e,b));else{for(d="";e<b;){var g=c[e++];if(g&128){var t=c[e++]&63;if(192==(g&224))d+=String.fromCharCode((g&31)<<6|t);else{var aa=c[e++]&
63;g=224==(g&240)?(g&15)<<12|t<<6|aa:(g&7)<<18|t<<12|aa<<6|c[e++]&63;65536>g?d+=String.fromCharCode(g):(g-=65536,d+=String.fromCharCode(55296|g>>10,56320|g&1023))}}else d+=String.fromCharCode(g)}c=d}}else c="";return c}function l(){var e=ja.buffer;a.HEAP8=W=new Int8Array(e);a.HEAP16=new Int16Array(e);a.HEAP32=ca=new Int32Array(e);a.HEAPU8=ia=new Uint8Array(e);a.HEAPU16=new Uint16Array(e);a.HEAPU32=Y=new Uint32Array(e);a.HEAPF32=new Float32Array(e);a.HEAPF64=new Float64Array(e)}function y(e){if(a.onAbort)a.onAbort(e);
e="Aborted("+e+")";da(e);sa=!0;e=new WebAssembly.RuntimeError(e+". Build with -sASSERTIONS for more info.");ka(e);throw e;}function f(e){try{if(e==P&&ea)return new Uint8Array(ea);if(ma)return ma(e);throw"both async and sync fetching of the wasm failed";}catch(b){y(b)}}function q(){if(!ea&&(ta||fa)){if("function"==typeof fetch&&!P.startsWith("file://"))return fetch(P,{credentials:"same-origin"}).then(function(e){if(!e.ok)throw"failed to load wasm binary file at '"+P+"'";return e.arrayBuffer()}).catch(function(){return f(P)});
if(na)return new Promise(function(e,b){na(P,function(c){e(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return f(P)})}function u(e){for(;0<e.length;)e.shift()(a)}function A(e){this.excPtr=e;this.ptr=e-24;this.set_type=function(b){Y[this.ptr+4>>2]=b};this.get_type=function(){return Y[this.ptr+4>>2]};this.set_destructor=function(b){Y[this.ptr+8>>2]=b};this.get_destructor=function(){return Y[this.ptr+8>>2]};this.set_refcount=function(b){ca[this.ptr>>2]=b};this.set_caught=function(b){W[this.ptr+
12>>0]=b?1:0};this.get_caught=function(){return 0!=W[this.ptr+12>>0]};this.set_rethrown=function(b){W[this.ptr+13>>0]=b?1:0};this.get_rethrown=function(){return 0!=W[this.ptr+13>>0]};this.init=function(b,c){this.set_adjusted_ptr(0);this.set_type(b);this.set_destructor(c);this.set_refcount(0);this.set_caught(!1);this.set_rethrown(!1)};this.add_ref=function(){ca[this.ptr>>2]+=1};this.release_ref=function(){var b=ca[this.ptr>>2];ca[this.ptr>>2]=b-1;return 1===b};this.set_adjusted_ptr=function(b){Y[this.ptr+
16>>2]=b};this.get_adjusted_ptr=function(){return Y[this.ptr+16>>2]};this.get_exception_ptr=function(){if(ua(this.get_type()))return Y[this.excPtr>>2];var b=this.get_adjusted_ptr();return 0!==b?b:this.excPtr}}function F(){function e(){if(!la&&(la=!0,a.calledRun=!0,!sa)){va=!0;u(oa);wa(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;)xa.unshift(a.postRun.shift());u(xa)}}if(!(0<ba)){if(a.preRun)for("function"==
typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)ya.unshift(a.preRun.shift());u(ya);0<ba||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);e()},1)):e())}}function v(){}function w(e){return(e||v).__cache__}function B(e,b){var c=w(b),d=c[e];if(d)return d;d=Object.create((b||v).prototype);d.ptr=e;return c[e]=d}function R(e){if("string"===typeof e){for(var b=0,c=0;c<e.length;++c){var d=e.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=
d?(b+=4,++c):b+=3}b=Array(b+1);c=0;d=b.length;if(0<d){d=c+d-1;for(var g=0;g<e.length;++g){var t=e.charCodeAt(g);if(55296<=t&&57343>=t){var aa=e.charCodeAt(++g);t=65536+((t&1023)<<10)|aa&1023}if(127>=t){if(c>=d)break;b[c++]=t}else{if(2047>=t){if(c+1>=d)break;b[c++]=192|t>>6}else{if(65535>=t){if(c+2>=d)break;b[c++]=224|t>>12}else{if(c+3>=d)break;b[c++]=240|t>>18;b[c++]=128|t>>12&63}b[c++]=128|t>>6&63}b[c++]=128|t&63}}b[c]=0}e=r.alloc(b,W);r.copy(b,W,e);return e}return e}function Z(e){if("object"===
typeof e){var b=r.alloc(e,W);r.copy(e,W,b);return b}return e}function X(){throw"cannot construct a VoidPtr, no constructor in IDL";}function S(){this.ptr=za();w(S)[this.ptr]=this}function Q(){this.ptr=Aa();w(Q)[this.ptr]=this}function V(){this.ptr=Ba();w(V)[this.ptr]=this}function x(){this.ptr=Ca();w(x)[this.ptr]=this}function D(){this.ptr=Da();w(D)[this.ptr]=this}function G(){this.ptr=Ea();w(G)[this.ptr]=this}function H(){this.ptr=Fa();w(H)[this.ptr]=this}function E(){this.ptr=Ga();w(E)[this.ptr]=
this}function T(){this.ptr=Ha();w(T)[this.ptr]=this}function C(){throw"cannot construct a Status, no constructor in IDL";}function I(){this.ptr=Ia();w(I)[this.ptr]=this}function J(){this.ptr=Ja();w(J)[this.ptr]=this}function K(){this.ptr=Ka();w(K)[this.ptr]=this}function L(){this.ptr=La();w(L)[this.ptr]=this}function M(){this.ptr=Ma();w(M)[this.ptr]=this}function N(){this.ptr=Na();w(N)[this.ptr]=this}function O(){this.ptr=Oa();w(O)[this.ptr]=this}function z(){this.ptr=Pa();w(z)[this.ptr]=this}function m(){this.ptr=
Qa();w(m)[this.ptr]=this}n=void 0===n?{}:n;var a="undefined"!=typeof n?n:{},wa,ka;a.ready=new Promise(function(e,b){wa=e;ka=b});var Ra=!1,Sa=!1;a.onRuntimeInitialized=function(){Ra=!0;if(Sa&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.onModuleParsed=function(){Sa=!0;if(Ra&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.isVersionSupported=function(e){if("string"!==typeof e)return!1;e=e.split(".");return 2>e.length||3<e.length?!1:1==e[0]&&0<=e[1]&&5>=e[1]?!0:0!=e[0]||10<
e[1]?!1:!0};var Ta=Object.assign({},a),ta="object"==typeof window,fa="function"==typeof importScripts,Ua="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,U="";if(Ua){var Va=require("fs"),pa=require("path");U=fa?pa.dirname(U)+"/":__dirname+"/";var Wa=function(e,b){e=e.startsWith("file://")?new URL(e):pa.normalize(e);return Va.readFileSync(e,b?void 0:"utf8")};var ma=function(e){e=Wa(e,!0);e.buffer||(e=new Uint8Array(e));return e};var na=function(e,
b,c){e=e.startsWith("file://")?new URL(e):pa.normalize(e);Va.readFile(e,function(d,g){d?c(d):b(g.buffer)})};1<process.argv.length&&process.argv[1].replace(/\\/g,"/");process.argv.slice(2);a.inspect=function(){return"[Emscripten Module object]"}}else if(ta||fa)fa?U=self.location.href:"undefined"!=typeof document&&document.currentScript&&(U=document.currentScript.src),h&&(U=h),U=0!==U.indexOf("blob:")?U.substr(0,U.replace(/[?#].*/,"").lastIndexOf("/")+1):"",Wa=function(e){var b=new XMLHttpRequest;b.open("GET",
e,!1);b.send(null);return b.responseText},fa&&(ma=function(e){var b=new XMLHttpRequest;b.open("GET",e,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),na=function(e,b,c){var d=new XMLHttpRequest;d.open("GET",e,!0);d.responseType="arraybuffer";d.onload=function(){200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};a.print||console.log.bind(console);var da=a.printErr||console.warn.bind(console);Object.assign(a,Ta);Ta=null;var ea;a.wasmBinary&&
(ea=a.wasmBinary);"object"!=typeof WebAssembly&&y("no native wasm support detected");var ja,sa=!1,ra="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0,W,ia,ca,Y,ya=[],oa=[],xa=[],va=!1,ba=0,qa=null,ha=null;var P="draco_decoder_gltf.wasm";P.startsWith("data:application/octet-stream;base64,")||(P=k(P));var pd=0,qd={b:function(e,b,c){(new A(e)).init(b,c);pd++;throw e;},a:function(){y("")},d:function(e,b,c){ia.copyWithin(e,b,b+c)},c:function(e){var b=ia.length;e>>>=0;if(2147483648<e)return!1;
for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,e+100663296);var g=Math;d=Math.max(e,d);g=g.min.call(g,2147483648,d+(65536-d%65536)%65536);a:{d=ja.buffer;try{ja.grow(g-d.byteLength+65535>>>16);l();var t=1;break a}catch(aa){}t=void 0}if(t)return!0}return!1}};(function(){function e(g,t){a.asm=g.exports;ja=a.asm.e;l();oa.unshift(a.asm.f);ba--;a.monitorRunDependencies&&a.monitorRunDependencies(ba);0==ba&&(null!==qa&&(clearInterval(qa),qa=null),ha&&(g=ha,ha=null,g()))}function b(g){e(g.instance)}
function c(g){return q().then(function(t){return WebAssembly.instantiate(t,d)}).then(function(t){return t}).then(g,function(t){da("failed to asynchronously prepare wasm: "+t);y(t)})}var d={a:qd};ba++;a.monitorRunDependencies&&a.monitorRunDependencies(ba);if(a.instantiateWasm)try{return a.instantiateWasm(d,e)}catch(g){da("Module.instantiateWasm callback failed with error: "+g),ka(g)}(function(){return ea||"function"!=typeof WebAssembly.instantiateStreaming||P.startsWith("data:application/octet-stream;base64,")||
P.startsWith("file://")||Ua||"function"!=typeof fetch?c(b):fetch(P,{credentials:"same-origin"}).then(function(g){return WebAssembly.instantiateStreaming(g,d).then(b,function(t){da("wasm streaming compile failed: "+t);da("falling back to ArrayBuffer instantiation");return c(b)})})})().catch(ka);return{}})();var Xa=a._emscripten_bind_VoidPtr___destroy___0=function(){return(Xa=a._emscripten_bind_VoidPtr___destroy___0=a.asm.h).apply(null,arguments)},za=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=
function(){return(za=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=a.asm.i).apply(null,arguments)},Ya=a._emscripten_bind_DecoderBuffer_Init_2=function(){return(Ya=a._emscripten_bind_DecoderBuffer_Init_2=a.asm.j).apply(null,arguments)},Za=a._emscripten_bind_DecoderBuffer___destroy___0=function(){return(Za=a._emscripten_bind_DecoderBuffer___destroy___0=a.asm.k).apply(null,arguments)},Aa=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=function(){return(Aa=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=
a.asm.l).apply(null,arguments)},$a=a._emscripten_bind_AttributeTransformData_transform_type_0=function(){return($a=a._emscripten_bind_AttributeTransformData_transform_type_0=a.asm.m).apply(null,arguments)},ab=a._emscripten_bind_AttributeTransformData___destroy___0=function(){return(ab=a._emscripten_bind_AttributeTransformData___destroy___0=a.asm.n).apply(null,arguments)},Ba=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=function(){return(Ba=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=
a.asm.o).apply(null,arguments)},bb=a._emscripten_bind_GeometryAttribute___destroy___0=function(){return(bb=a._emscripten_bind_GeometryAttribute___destroy___0=a.asm.p).apply(null,arguments)},Ca=a._emscripten_bind_PointAttribute_PointAttribute_0=function(){return(Ca=a._emscripten_bind_PointAttribute_PointAttribute_0=a.asm.q).apply(null,arguments)},cb=a._emscripten_bind_PointAttribute_size_0=function(){return(cb=a._emscripten_bind_PointAttribute_size_0=a.asm.r).apply(null,arguments)},db=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=
function(){return(db=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=a.asm.s).apply(null,arguments)},eb=a._emscripten_bind_PointAttribute_attribute_type_0=function(){return(eb=a._emscripten_bind_PointAttribute_attribute_type_0=a.asm.t).apply(null,arguments)},fb=a._emscripten_bind_PointAttribute_data_type_0=function(){return(fb=a._emscripten_bind_PointAttribute_data_type_0=a.asm.u).apply(null,arguments)},gb=a._emscripten_bind_PointAttribute_num_components_0=function(){return(gb=a._emscripten_bind_PointAttribute_num_components_0=
a.asm.v).apply(null,arguments)},hb=a._emscripten_bind_PointAttribute_normalized_0=function(){return(hb=a._emscripten_bind_PointAttribute_normalized_0=a.asm.w).apply(null,arguments)},ib=a._emscripten_bind_PointAttribute_byte_stride_0=function(){return(ib=a._emscripten_bind_PointAttribute_byte_stride_0=a.asm.x).apply(null,arguments)},jb=a._emscripten_bind_PointAttribute_byte_offset_0=function(){return(jb=a._emscripten_bind_PointAttribute_byte_offset_0=a.asm.y).apply(null,arguments)},kb=a._emscripten_bind_PointAttribute_unique_id_0=
function(){return(kb=a._emscripten_bind_PointAttribute_unique_id_0=a.asm.z).apply(null,arguments)},lb=a._emscripten_bind_PointAttribute___destroy___0=function(){return(lb=a._emscripten_bind_PointAttribute___destroy___0=a.asm.A).apply(null,arguments)},Da=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=function(){return(Da=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=a.asm.B).apply(null,arguments)},mb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=
function(){return(mb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=a.asm.C).apply(null,arguments)},nb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=function(){return(nb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=a.asm.D).apply(null,arguments)},ob=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=function(){return(ob=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=a.asm.E).apply(null,arguments)},pb=
a._emscripten_bind_AttributeQuantizationTransform_range_0=function(){return(pb=a._emscripten_bind_AttributeQuantizationTransform_range_0=a.asm.F).apply(null,arguments)},qb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=function(){return(qb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=a.asm.G).apply(null,arguments)},Ea=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=function(){return(Ea=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=
a.asm.H).apply(null,arguments)},rb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=function(){return(rb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=a.asm.I).apply(null,arguments)},sb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=function(){return(sb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=a.asm.J).apply(null,arguments)},tb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=function(){return(tb=
a._emscripten_bind_AttributeOctahedronTransform___destroy___0=a.asm.K).apply(null,arguments)},Fa=a._emscripten_bind_PointCloud_PointCloud_0=function(){return(Fa=a._emscripten_bind_PointCloud_PointCloud_0=a.asm.L).apply(null,arguments)},ub=a._emscripten_bind_PointCloud_num_attributes_0=function(){return(ub=a._emscripten_bind_PointCloud_num_attributes_0=a.asm.M).apply(null,arguments)},vb=a._emscripten_bind_PointCloud_num_points_0=function(){return(vb=a._emscripten_bind_PointCloud_num_points_0=a.asm.N).apply(null,
arguments)},wb=a._emscripten_bind_PointCloud___destroy___0=function(){return(wb=a._emscripten_bind_PointCloud___destroy___0=a.asm.O).apply(null,arguments)},Ga=a._emscripten_bind_Mesh_Mesh_0=function(){return(Ga=a._emscripten_bind_Mesh_Mesh_0=a.asm.P).apply(null,arguments)},xb=a._emscripten_bind_Mesh_num_faces_0=function(){return(xb=a._emscripten_bind_Mesh_num_faces_0=a.asm.Q).apply(null,arguments)},yb=a._emscripten_bind_Mesh_num_attributes_0=function(){return(yb=a._emscripten_bind_Mesh_num_attributes_0=
a.asm.R).apply(null,arguments)},zb=a._emscripten_bind_Mesh_num_points_0=function(){return(zb=a._emscripten_bind_Mesh_num_points_0=a.asm.S).apply(null,arguments)},Ab=a._emscripten_bind_Mesh___destroy___0=function(){return(Ab=a._emscripten_bind_Mesh___destroy___0=a.asm.T).apply(null,arguments)},Ha=a._emscripten_bind_Metadata_Metadata_0=function(){return(Ha=a._emscripten_bind_Metadata_Metadata_0=a.asm.U).apply(null,arguments)},Bb=a._emscripten_bind_Metadata___destroy___0=function(){return(Bb=a._emscripten_bind_Metadata___destroy___0=
a.asm.V).apply(null,arguments)},Cb=a._emscripten_bind_Status_code_0=function(){return(Cb=a._emscripten_bind_Status_code_0=a.asm.W).apply(null,arguments)},Db=a._emscripten_bind_Status_ok_0=function(){return(Db=a._emscripten_bind_Status_ok_0=a.asm.X).apply(null,arguments)},Eb=a._emscripten_bind_Status_error_msg_0=function(){return(Eb=a._emscripten_bind_Status_error_msg_0=a.asm.Y).apply(null,arguments)},Fb=a._emscripten_bind_Status___destroy___0=function(){return(Fb=a._emscripten_bind_Status___destroy___0=
a.asm.Z).apply(null,arguments)},Ia=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=function(){return(Ia=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=a.asm._).apply(null,arguments)},Gb=a._emscripten_bind_DracoFloat32Array_GetValue_1=function(){return(Gb=a._emscripten_bind_DracoFloat32Array_GetValue_1=a.asm.$).apply(null,arguments)},Hb=a._emscripten_bind_DracoFloat32Array_size_0=function(){return(Hb=a._emscripten_bind_DracoFloat32Array_size_0=a.asm.aa).apply(null,arguments)},Ib=
a._emscripten_bind_DracoFloat32Array___destroy___0=function(){return(Ib=a._emscripten_bind_DracoFloat32Array___destroy___0=a.asm.ba).apply(null,arguments)},Ja=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=function(){return(Ja=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=a.asm.ca).apply(null,arguments)},Jb=a._emscripten_bind_DracoInt8Array_GetValue_1=function(){return(Jb=a._emscripten_bind_DracoInt8Array_GetValue_1=a.asm.da).apply(null,arguments)},Kb=a._emscripten_bind_DracoInt8Array_size_0=
function(){return(Kb=a._emscripten_bind_DracoInt8Array_size_0=a.asm.ea).apply(null,arguments)},Lb=a._emscripten_bind_DracoInt8Array___destroy___0=function(){return(Lb=a._emscripten_bind_DracoInt8Array___destroy___0=a.asm.fa).apply(null,arguments)},Ka=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=function(){return(Ka=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=a.asm.ga).apply(null,arguments)},Mb=a._emscripten_bind_DracoUInt8Array_GetValue_1=function(){return(Mb=a._emscripten_bind_DracoUInt8Array_GetValue_1=
a.asm.ha).apply(null,arguments)},Nb=a._emscripten_bind_DracoUInt8Array_size_0=function(){return(Nb=a._emscripten_bind_DracoUInt8Array_size_0=a.asm.ia).apply(null,arguments)},Ob=a._emscripten_bind_DracoUInt8Array___destroy___0=function(){return(Ob=a._emscripten_bind_DracoUInt8Array___destroy___0=a.asm.ja).apply(null,arguments)},La=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=function(){return(La=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=a.asm.ka).apply(null,arguments)},Pb=a._emscripten_bind_DracoInt16Array_GetValue_1=
function(){return(Pb=a._emscripten_bind_DracoInt16Array_GetValue_1=a.asm.la).apply(null,arguments)},Qb=a._emscripten_bind_DracoInt16Array_size_0=function(){return(Qb=a._emscripten_bind_DracoInt16Array_size_0=a.asm.ma).apply(null,arguments)},Rb=a._emscripten_bind_DracoInt16Array___destroy___0=function(){return(Rb=a._emscripten_bind_DracoInt16Array___destroy___0=a.asm.na).apply(null,arguments)},Ma=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=function(){return(Ma=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=
a.asm.oa).apply(null,arguments)},Sb=a._emscripten_bind_DracoUInt16Array_GetValue_1=function(){return(Sb=a._emscripten_bind_DracoUInt16Array_GetValue_1=a.asm.pa).apply(null,arguments)},Tb=a._emscripten_bind_DracoUInt16Array_size_0=function(){return(Tb=a._emscripten_bind_DracoUInt16Array_size_0=a.asm.qa).apply(null,arguments)},Ub=a._emscripten_bind_DracoUInt16Array___destroy___0=function(){return(Ub=a._emscripten_bind_DracoUInt16Array___destroy___0=a.asm.ra).apply(null,arguments)},Na=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=
function(){return(Na=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=a.asm.sa).apply(null,arguments)},Vb=a._emscripten_bind_DracoInt32Array_GetValue_1=function(){return(Vb=a._emscripten_bind_DracoInt32Array_GetValue_1=a.asm.ta).apply(null,arguments)},Wb=a._emscripten_bind_DracoInt32Array_size_0=function(){return(Wb=a._emscripten_bind_DracoInt32Array_size_0=a.asm.ua).apply(null,arguments)},Xb=a._emscripten_bind_DracoInt32Array___destroy___0=function(){return(Xb=a._emscripten_bind_DracoInt32Array___destroy___0=
a.asm.va).apply(null,arguments)},Oa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=function(){return(Oa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=a.asm.wa).apply(null,arguments)},Yb=a._emscripten_bind_DracoUInt32Array_GetValue_1=function(){return(Yb=a._emscripten_bind_DracoUInt32Array_GetValue_1=a.asm.xa).apply(null,arguments)},Zb=a._emscripten_bind_DracoUInt32Array_size_0=function(){return(Zb=a._emscripten_bind_DracoUInt32Array_size_0=a.asm.ya).apply(null,arguments)},$b=a._emscripten_bind_DracoUInt32Array___destroy___0=
function(){return($b=a._emscripten_bind_DracoUInt32Array___destroy___0=a.asm.za).apply(null,arguments)},Pa=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=function(){return(Pa=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=a.asm.Aa).apply(null,arguments)},ac=a._emscripten_bind_MetadataQuerier_HasEntry_2=function(){return(ac=a._emscripten_bind_MetadataQuerier_HasEntry_2=a.asm.Ba).apply(null,arguments)},bc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=function(){return(bc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=
a.asm.Ca).apply(null,arguments)},cc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=function(){return(cc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=a.asm.Da).apply(null,arguments)},dc=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=function(){return(dc=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=a.asm.Ea).apply(null,arguments)},ec=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=function(){return(ec=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=a.asm.Fa).apply(null,
arguments)},fc=a._emscripten_bind_MetadataQuerier_NumEntries_1=function(){return(fc=a._emscripten_bind_MetadataQuerier_NumEntries_1=a.asm.Ga).apply(null,arguments)},gc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=function(){return(gc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=a.asm.Ha).apply(null,arguments)},hc=a._emscripten_bind_MetadataQuerier___destroy___0=function(){return(hc=a._emscripten_bind_MetadataQuerier___destroy___0=a.asm.Ia).apply(null,arguments)},Qa=a._emscripten_bind_Decoder_Decoder_0=
function(){return(Qa=a._emscripten_bind_Decoder_Decoder_0=a.asm.Ja).apply(null,arguments)},ic=a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=function(){return(ic=a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=a.asm.Ka).apply(null,arguments)},jc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=function(){return(jc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=a.asm.La).apply(null,arguments)},kc=a._emscripten_bind_Decoder_GetAttributeId_2=function(){return(kc=a._emscripten_bind_Decoder_GetAttributeId_2=
a.asm.Ma).apply(null,arguments)},lc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=function(){return(lc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=a.asm.Na).apply(null,arguments)},mc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=function(){return(mc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=a.asm.Oa).apply(null,arguments)},nc=a._emscripten_bind_Decoder_GetAttribute_2=function(){return(nc=a._emscripten_bind_Decoder_GetAttribute_2=a.asm.Pa).apply(null,arguments)},
oc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=function(){return(oc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=a.asm.Qa).apply(null,arguments)},pc=a._emscripten_bind_Decoder_GetMetadata_1=function(){return(pc=a._emscripten_bind_Decoder_GetMetadata_1=a.asm.Ra).apply(null,arguments)},qc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=function(){return(qc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=a.asm.Sa).apply(null,arguments)},rc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=
function(){return(rc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=a.asm.Ta).apply(null,arguments)},sc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=function(){return(sc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=a.asm.Ua).apply(null,arguments)},tc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=function(){return(tc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=a.asm.Va).apply(null,arguments)},uc=a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=function(){return(uc=
a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=a.asm.Wa).apply(null,arguments)},vc=a._emscripten_bind_Decoder_GetAttributeFloat_3=function(){return(vc=a._emscripten_bind_Decoder_GetAttributeFloat_3=a.asm.Xa).apply(null,arguments)},wc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=function(){return(wc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=a.asm.Ya).apply(null,arguments)},xc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=function(){return(xc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=
a.asm.Za).apply(null,arguments)},yc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=function(){return(yc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=a.asm._a).apply(null,arguments)},zc=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=function(){return(zc=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=a.asm.$a).apply(null,arguments)},Ac=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=function(){return(Ac=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=
a.asm.ab).apply(null,arguments)},Bc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=function(){return(Bc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=a.asm.bb).apply(null,arguments)},Cc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=function(){return(Cc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=a.asm.cb).apply(null,arguments)},Dc=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=function(){return(Dc=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=
a.asm.db).apply(null,arguments)},Ec=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=function(){return(Ec=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=a.asm.eb).apply(null,arguments)},Fc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=function(){return(Fc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=a.asm.fb).apply(null,arguments)},Gc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=function(){return(Gc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=
a.asm.gb).apply(null,arguments)},Hc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=function(){return(Hc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=a.asm.hb).apply(null,arguments)},Ic=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=function(){return(Ic=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=a.asm.ib).apply(null,arguments)},Jc=a._emscripten_bind_Decoder___destroy___0=function(){return(Jc=a._emscripten_bind_Decoder___destroy___0=a.asm.jb).apply(null,arguments)},Kc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=
function(){return(Kc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=a.asm.kb).apply(null,arguments)},Lc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=function(){return(Lc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=a.asm.lb).apply(null,arguments)},Mc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=function(){return(Mc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=
a.asm.mb).apply(null,arguments)},Nc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=function(){return(Nc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=a.asm.nb).apply(null,arguments)},Oc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=function(){return(Oc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=a.asm.ob).apply(null,arguments)},Pc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=function(){return(Pc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=
a.asm.pb).apply(null,arguments)},Qc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=function(){return(Qc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=a.asm.qb).apply(null,arguments)},Rc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=function(){return(Rc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=a.asm.rb).apply(null,arguments)},Sc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=function(){return(Sc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=
a.asm.sb).apply(null,arguments)},Tc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=function(){return(Tc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=a.asm.tb).apply(null,arguments)},Uc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=function(){return(Uc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=a.asm.ub).apply(null,arguments)},Vc=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=function(){return(Vc=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=
a.asm.vb).apply(null,arguments)},Wc=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=function(){return(Wc=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=a.asm.wb).apply(null,arguments)},Xc=a._emscripten_enum_draco_DataType_DT_INVALID=function(){return(Xc=a._emscripten_enum_draco_DataType_DT_INVALID=a.asm.xb).apply(null,arguments)},Yc=a._emscripten_enum_draco_DataType_DT_INT8=function(){return(Yc=a._emscripten_enum_draco_DataType_DT_INT8=a.asm.yb).apply(null,arguments)},Zc=
a._emscripten_enum_draco_DataType_DT_UINT8=function(){return(Zc=a._emscripten_enum_draco_DataType_DT_UINT8=a.asm.zb).apply(null,arguments)},$c=a._emscripten_enum_draco_DataType_DT_INT16=function(){return($c=a._emscripten_enum_draco_DataType_DT_INT16=a.asm.Ab).apply(null,arguments)},ad=a._emscripten_enum_draco_DataType_DT_UINT16=function(){return(ad=a._emscripten_enum_draco_DataType_DT_UINT16=a.asm.Bb).apply(null,arguments)},bd=a._emscripten_enum_draco_DataType_DT_INT32=function(){return(bd=a._emscripten_enum_draco_DataType_DT_INT32=
a.asm.Cb).apply(null,arguments)},cd=a._emscripten_enum_draco_DataType_DT_UINT32=function(){return(cd=a._emscripten_enum_draco_DataType_DT_UINT32=a.asm.Db).apply(null,arguments)},dd=a._emscripten_enum_draco_DataType_DT_INT64=function(){return(dd=a._emscripten_enum_draco_DataType_DT_INT64=a.asm.Eb).apply(null,arguments)},ed=a._emscripten_enum_draco_DataType_DT_UINT64=function(){return(ed=a._emscripten_enum_draco_DataType_DT_UINT64=a.asm.Fb).apply(null,arguments)},fd=a._emscripten_enum_draco_DataType_DT_FLOAT32=
function(){return(fd=a._emscripten_enum_draco_DataType_DT_FLOAT32=a.asm.Gb).apply(null,arguments)},gd=a._emscripten_enum_draco_DataType_DT_FLOAT64=function(){return(gd=a._emscripten_enum_draco_DataType_DT_FLOAT64=a.asm.Hb).apply(null,arguments)},hd=a._emscripten_enum_draco_DataType_DT_BOOL=function(){return(hd=a._emscripten_enum_draco_DataType_DT_BOOL=a.asm.Ib).apply(null,arguments)},id=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=function(){return(id=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=
a.asm.Jb).apply(null,arguments)},jd=a._emscripten_enum_draco_StatusCode_OK=function(){return(jd=a._emscripten_enum_draco_StatusCode_OK=a.asm.Kb).apply(null,arguments)},kd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=function(){return(kd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=a.asm.Lb).apply(null,arguments)},ld=a._emscripten_enum_draco_StatusCode_IO_ERROR=function(){return(ld=a._emscripten_enum_draco_StatusCode_IO_ERROR=a.asm.Mb).apply(null,arguments)},md=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=
function(){return(md=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=a.asm.Nb).apply(null,arguments)},nd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=function(){return(nd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=a.asm.Ob).apply(null,arguments)},od=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=function(){return(od=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=a.asm.Pb).apply(null,arguments)};a._malloc=function(){return(a._malloc=a.asm.Qb).apply(null,arguments)};
a._free=function(){return(a._free=a.asm.Rb).apply(null,arguments)};var ua=function(){return(ua=a.asm.Sb).apply(null,arguments)};a.___start_em_js=11660;a.___stop_em_js=11758;var la;ha=function b(){la||F();la||(ha=b)};if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();F();v.prototype=Object.create(v.prototype);v.prototype.constructor=v;v.prototype.__class__=v;v.__cache__={};a.WrapperObject=v;a.getCache=w;a.wrapPointer=B;a.castObject=function(b,
c){return B(b.ptr,c)};a.NULL=B(0);a.destroy=function(b){if(!b.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";b.__destroy__();delete w(b.__class__)[b.ptr]};a.compare=function(b,c){return b.ptr===c.ptr};a.getPointer=function(b){return b.ptr};a.getClass=function(b){return b.__class__};var r={buffer:0,size:0,pos:0,temps:[],needed:0,prepare:function(){if(r.needed){for(var b=0;b<r.temps.length;b++)a._free(r.temps[b]);r.temps.length=0;a._free(r.buffer);r.buffer=0;r.size+=
r.needed;r.needed=0}r.buffer||(r.size+=128,r.buffer=a._malloc(r.size),r.buffer||y(void 0));r.pos=0},alloc:function(b,c){r.buffer||y(void 0);b=b.length*c.BYTES_PER_ELEMENT;b=b+7&-8;r.pos+b>=r.size?(0<b||y(void 0),r.needed+=b,c=a._malloc(b),r.temps.push(c)):(c=r.buffer+r.pos,r.pos+=b);return c},copy:function(b,c,d){d>>>=0;switch(c.BYTES_PER_ELEMENT){case 2:d>>>=1;break;case 4:d>>>=2;break;case 8:d>>>=3}for(var g=0;g<b.length;g++)c[d+g]=b[g]}};X.prototype=Object.create(v.prototype);X.prototype.constructor=
X;X.prototype.__class__=X;X.__cache__={};a.VoidPtr=X;X.prototype.__destroy__=X.prototype.__destroy__=function(){Xa(this.ptr)};S.prototype=Object.create(v.prototype);S.prototype.constructor=S;S.prototype.__class__=S;S.__cache__={};a.DecoderBuffer=S;S.prototype.Init=S.prototype.Init=function(b,c){var d=this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);Ya(d,b,c)};S.prototype.__destroy__=S.prototype.__destroy__=function(){Za(this.ptr)};Q.prototype=Object.create(v.prototype);
Q.prototype.constructor=Q;Q.prototype.__class__=Q;Q.__cache__={};a.AttributeTransformData=Q;Q.prototype.transform_type=Q.prototype.transform_type=function(){return $a(this.ptr)};Q.prototype.__destroy__=Q.prototype.__destroy__=function(){ab(this.ptr)};V.prototype=Object.create(v.prototype);V.prototype.constructor=V;V.prototype.__class__=V;V.__cache__={};a.GeometryAttribute=V;V.prototype.__destroy__=V.prototype.__destroy__=function(){bb(this.ptr)};x.prototype=Object.create(v.prototype);x.prototype.constructor=
x;x.prototype.__class__=x;x.__cache__={};a.PointAttribute=x;x.prototype.size=x.prototype.size=function(){return cb(this.ptr)};x.prototype.GetAttributeTransformData=x.prototype.GetAttributeTransformData=function(){return B(db(this.ptr),Q)};x.prototype.attribute_type=x.prototype.attribute_type=function(){return eb(this.ptr)};x.prototype.data_type=x.prototype.data_type=function(){return fb(this.ptr)};x.prototype.num_components=x.prototype.num_components=function(){return gb(this.ptr)};x.prototype.normalized=
x.prototype.normalized=function(){return!!hb(this.ptr)};x.prototype.byte_stride=x.prototype.byte_stride=function(){return ib(this.ptr)};x.prototype.byte_offset=x.prototype.byte_offset=function(){return jb(this.ptr)};x.prototype.unique_id=x.prototype.unique_id=function(){return kb(this.ptr)};x.prototype.__destroy__=x.prototype.__destroy__=function(){lb(this.ptr)};D.prototype=Object.create(v.prototype);D.prototype.constructor=D;D.prototype.__class__=D;D.__cache__={};a.AttributeQuantizationTransform=
D;D.prototype.InitFromAttribute=D.prototype.InitFromAttribute=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!mb(c,b)};D.prototype.quantization_bits=D.prototype.quantization_bits=function(){return nb(this.ptr)};D.prototype.min_value=D.prototype.min_value=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return ob(c,b)};D.prototype.range=D.prototype.range=function(){return pb(this.ptr)};D.prototype.__destroy__=D.prototype.__destroy__=function(){qb(this.ptr)};G.prototype=
Object.create(v.prototype);G.prototype.constructor=G;G.prototype.__class__=G;G.__cache__={};a.AttributeOctahedronTransform=G;G.prototype.InitFromAttribute=G.prototype.InitFromAttribute=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!rb(c,b)};G.prototype.quantization_bits=G.prototype.quantization_bits=function(){return sb(this.ptr)};G.prototype.__destroy__=G.prototype.__destroy__=function(){tb(this.ptr)};H.prototype=Object.create(v.prototype);H.prototype.constructor=H;H.prototype.__class__=
H;H.__cache__={};a.PointCloud=H;H.prototype.num_attributes=H.prototype.num_attributes=function(){return ub(this.ptr)};H.prototype.num_points=H.prototype.num_points=function(){return vb(this.ptr)};H.prototype.__destroy__=H.prototype.__destroy__=function(){wb(this.ptr)};E.prototype=Object.create(v.prototype);E.prototype.constructor=E;E.prototype.__class__=E;E.__cache__={};a.Mesh=E;E.prototype.num_faces=E.prototype.num_faces=function(){return xb(this.ptr)};E.prototype.num_attributes=E.prototype.num_attributes=
function(){return yb(this.ptr)};E.prototype.num_points=E.prototype.num_points=function(){return zb(this.ptr)};E.prototype.__destroy__=E.prototype.__destroy__=function(){Ab(this.ptr)};T.prototype=Object.create(v.prototype);T.prototype.constructor=T;T.prototype.__class__=T;T.__cache__={};a.Metadata=T;T.prototype.__destroy__=T.prototype.__destroy__=function(){Bb(this.ptr)};C.prototype=Object.create(v.prototype);C.prototype.constructor=C;C.prototype.__class__=C;C.__cache__={};a.Status=C;C.prototype.code=
C.prototype.code=function(){return Cb(this.ptr)};C.prototype.ok=C.prototype.ok=function(){return!!Db(this.ptr)};C.prototype.error_msg=C.prototype.error_msg=function(){return p(Eb(this.ptr))};C.prototype.__destroy__=C.prototype.__destroy__=function(){Fb(this.ptr)};I.prototype=Object.create(v.prototype);I.prototype.constructor=I;I.prototype.__class__=I;I.__cache__={};a.DracoFloat32Array=I;I.prototype.GetValue=I.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Gb(c,
b)};I.prototype.size=I.prototype.size=function(){return Hb(this.ptr)};I.prototype.__destroy__=I.prototype.__destroy__=function(){Ib(this.ptr)};J.prototype=Object.create(v.prototype);J.prototype.constructor=J;J.prototype.__class__=J;J.__cache__={};a.DracoInt8Array=J;J.prototype.GetValue=J.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Jb(c,b)};J.prototype.size=J.prototype.size=function(){return Kb(this.ptr)};J.prototype.__destroy__=J.prototype.__destroy__=function(){Lb(this.ptr)};
K.prototype=Object.create(v.prototype);K.prototype.constructor=K;K.prototype.__class__=K;K.__cache__={};a.DracoUInt8Array=K;K.prototype.GetValue=K.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Mb(c,b)};K.prototype.size=K.prototype.size=function(){return Nb(this.ptr)};K.prototype.__destroy__=K.prototype.__destroy__=function(){Ob(this.ptr)};L.prototype=Object.create(v.prototype);L.prototype.constructor=L;L.prototype.__class__=L;L.__cache__={};a.DracoInt16Array=
L;L.prototype.GetValue=L.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Pb(c,b)};L.prototype.size=L.prototype.size=function(){return Qb(this.ptr)};L.prototype.__destroy__=L.prototype.__destroy__=function(){Rb(this.ptr)};M.prototype=Object.create(v.prototype);M.prototype.constructor=M;M.prototype.__class__=M;M.__cache__={};a.DracoUInt16Array=M;M.prototype.GetValue=M.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Sb(c,b)};
M.prototype.size=M.prototype.size=function(){return Tb(this.ptr)};M.prototype.__destroy__=M.prototype.__destroy__=function(){Ub(this.ptr)};N.prototype=Object.create(v.prototype);N.prototype.constructor=N;N.prototype.__class__=N;N.__cache__={};a.DracoInt32Array=N;N.prototype.GetValue=N.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Vb(c,b)};N.prototype.size=N.prototype.size=function(){return Wb(this.ptr)};N.prototype.__destroy__=N.prototype.__destroy__=function(){Xb(this.ptr)};
O.prototype=Object.create(v.prototype);O.prototype.constructor=O;O.prototype.__class__=O;O.__cache__={};a.DracoUInt32Array=O;O.prototype.GetValue=O.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Yb(c,b)};O.prototype.size=O.prototype.size=function(){return Zb(this.ptr)};O.prototype.__destroy__=O.prototype.__destroy__=function(){$b(this.ptr)};z.prototype=Object.create(v.prototype);z.prototype.constructor=z;z.prototype.__class__=z;z.__cache__={};a.MetadataQuerier=
z;z.prototype.HasEntry=z.prototype.HasEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return!!ac(d,b,c)};z.prototype.GetIntEntry=z.prototype.GetIntEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return bc(d,b,c)};z.prototype.GetIntEntryArray=z.prototype.GetIntEntryArray=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===
typeof c?c.ptr:R(c);d&&"object"===typeof d&&(d=d.ptr);cc(g,b,c,d)};z.prototype.GetDoubleEntry=z.prototype.GetDoubleEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return dc(d,b,c)};z.prototype.GetStringEntry=z.prototype.GetStringEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return p(ec(d,b,c))};z.prototype.NumEntries=z.prototype.NumEntries=function(b){var c=this.ptr;
b&&"object"===typeof b&&(b=b.ptr);return fc(c,b)};z.prototype.GetEntryName=z.prototype.GetEntryName=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return p(gc(d,b,c))};z.prototype.__destroy__=z.prototype.__destroy__=function(){hc(this.ptr)};m.prototype=Object.create(v.prototype);m.prototype.constructor=m;m.prototype.__class__=m;m.__cache__={};a.Decoder=m;m.prototype.DecodeArrayToPointCloud=m.prototype.DecodeArrayToPointCloud=function(b,c,d){var g=
this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return B(ic(g,b,c,d),C)};m.prototype.DecodeArrayToMesh=m.prototype.DecodeArrayToMesh=function(b,c,d){var g=this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return B(jc(g,b,c,d),C)};m.prototype.GetAttributeId=m.prototype.GetAttributeId=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&
(c=c.ptr);return kc(d,b,c)};m.prototype.GetAttributeIdByName=m.prototype.GetAttributeIdByName=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return lc(d,b,c)};m.prototype.GetAttributeIdByMetadataEntry=m.prototype.GetAttributeIdByMetadataEntry=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);d=d&&"object"===typeof d?d.ptr:R(d);return mc(g,b,c,d)};m.prototype.GetAttribute=
m.prototype.GetAttribute=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(nc(d,b,c),x)};m.prototype.GetAttributeByUniqueId=m.prototype.GetAttributeByUniqueId=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(oc(d,b,c),x)};m.prototype.GetMetadata=m.prototype.GetMetadata=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return B(pc(c,b),T)};m.prototype.GetAttributeMetadata=m.prototype.GetAttributeMetadata=
function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(qc(d,b,c),T)};m.prototype.GetFaceFromMesh=m.prototype.GetFaceFromMesh=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!rc(g,b,c,d)};m.prototype.GetTriangleStripsFromMesh=m.prototype.GetTriangleStripsFromMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);
return sc(d,b,c)};m.prototype.GetTrianglesUInt16Array=m.prototype.GetTrianglesUInt16Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!tc(g,b,c,d)};m.prototype.GetTrianglesUInt32Array=m.prototype.GetTrianglesUInt32Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!uc(g,b,c,d)};m.prototype.GetAttributeFloat=m.prototype.GetAttributeFloat=
function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!vc(g,b,c,d)};m.prototype.GetAttributeFloatForAllPoints=m.prototype.GetAttributeFloatForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!wc(g,b,c,d)};m.prototype.GetAttributeIntForAllPoints=m.prototype.GetAttributeIntForAllPoints=function(b,c,d){var g=this.ptr;
b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!xc(g,b,c,d)};m.prototype.GetAttributeInt8ForAllPoints=m.prototype.GetAttributeInt8ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!yc(g,b,c,d)};m.prototype.GetAttributeUInt8ForAllPoints=m.prototype.GetAttributeUInt8ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=
b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!zc(g,b,c,d)};m.prototype.GetAttributeInt16ForAllPoints=m.prototype.GetAttributeInt16ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ac(g,b,c,d)};m.prototype.GetAttributeUInt16ForAllPoints=m.prototype.GetAttributeUInt16ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&
(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Bc(g,b,c,d)};m.prototype.GetAttributeInt32ForAllPoints=m.prototype.GetAttributeInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Cc(g,b,c,d)};m.prototype.GetAttributeUInt32ForAllPoints=m.prototype.GetAttributeUInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===
typeof d&&(d=d.ptr);return!!Dc(g,b,c,d)};m.prototype.GetAttributeDataArrayForAllPoints=m.prototype.GetAttributeDataArrayForAllPoints=function(b,c,d,g,t){var aa=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);g&&"object"===typeof g&&(g=g.ptr);t&&"object"===typeof t&&(t=t.ptr);return!!Ec(aa,b,c,d,g,t)};m.prototype.SkipAttributeTransform=m.prototype.SkipAttributeTransform=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);Fc(c,
b)};m.prototype.GetEncodedGeometryType_Deprecated=m.prototype.GetEncodedGeometryType_Deprecated=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Gc(c,b)};m.prototype.DecodeBufferToPointCloud=m.prototype.DecodeBufferToPointCloud=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(Hc(d,b,c),C)};m.prototype.DecodeBufferToMesh=m.prototype.DecodeBufferToMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===
typeof c&&(c=c.ptr);return B(Ic(d,b,c),C)};m.prototype.__destroy__=m.prototype.__destroy__=function(){Jc(this.ptr)};(function(){function b(){a.ATTRIBUTE_INVALID_TRANSFORM=Kc();a.ATTRIBUTE_NO_TRANSFORM=Lc();a.ATTRIBUTE_QUANTIZATION_TRANSFORM=Mc();a.ATTRIBUTE_OCTAHEDRON_TRANSFORM=Nc();a.INVALID=Oc();a.POSITION=Pc();a.NORMAL=Qc();a.COLOR=Rc();a.TEX_COORD=Sc();a.GENERIC=Tc();a.INVALID_GEOMETRY_TYPE=Uc();a.POINT_CLOUD=Vc();a.TRIANGULAR_MESH=Wc();a.DT_INVALID=Xc();a.DT_INT8=Yc();a.DT_UINT8=Zc();a.DT_INT16=
$c();a.DT_UINT16=ad();a.DT_INT32=bd();a.DT_UINT32=cd();a.DT_INT64=dd();a.DT_UINT64=ed();a.DT_FLOAT32=fd();a.DT_FLOAT64=gd();a.DT_BOOL=hd();a.DT_TYPES_COUNT=id();a.OK=jd();a.DRACO_ERROR=kd();a.IO_ERROR=ld();a.INVALID_PARAMETER=md();a.UNSUPPORTED_VERSION=nd();a.UNKNOWN_VERSION=od()}va?b():oa.unshift(b)})();if("function"===typeof a.onModuleParsed)a.onModuleParsed();a.Decoder.prototype.GetEncodedGeometryType=function(b){if(b.__class__&&b.__class__===a.DecoderBuffer)return a.Decoder.prototype.GetEncodedGeometryType_Deprecated(b);
if(8>b.byteLength)return a.INVALID_GEOMETRY_TYPE;switch(b[7]){case 0:return a.POINT_CLOUD;case 1:return a.TRIANGULAR_MESH;default:return a.INVALID_GEOMETRY_TYPE}};return n.ready}}();"object"===typeof exports&&"object"===typeof module?module.exports=DracoDecoderModule:"function"===typeof define&&define.amd?define([],function(){return DracoDecoderModule}):"object"===typeof exports&&(exports.DracoDecoderModule=DracoDecoderModule);

Binary file not shown.

View File

@@ -0,0 +1,178 @@
/**
* Stage 2 benchmark — CAD conveyor + physics + premium visuals.
*
* Profiles (Stage 2 §15/§20):
* cad-light — CAD conveyor, medium quality (no shadows/env), physics on.
* cad-physics — high quality + physics step timing (window.__PHYSICS_PERF__).
* cad-full — full premium look (PCF shadows, studio env, cinematic camera).
* mobile-fallback — 390x844 SVG fallback, zero /models/* requests expected.
*
* Usage: node scripts/stage2-benchmark.mjs [--url ...] [--runs 3] [--duration 32000]
*/
import { chromium } from 'playwright';
import { mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const args = process.argv.slice(2);
function argValue(name, fallback) {
const i = args.indexOf(`--${name}`);
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
}
const BASE = argValue('url', 'http://127.0.0.1:3101');
const RUNS = Number(argValue('runs', '3'));
const DURATION_MS = Number(argValue('duration', '32000'));
const OUT_DIR = argValue('out', 'docs/stage2_real_sorter');
const ONLY = argValue('profile', null);
const EXEC = '/root/.cache/ms-playwright/chromium-1228/chrome-linux64/chrome';
const GPU_ARGS = [
'--use-gl=angle', '--use-angle=gl-egl', '--enable-gpu',
'--ignore-gpu-blocklist', '--disable-software-rasterizer', '--disable-vulkan-surface',
];
const PROFILES = [
{ name: 'cad-light', file: 'performance-cad-light.json', path: '/?perf=1&quality=medium', viewport: { width: 1920, height: 1080 } },
{ name: 'cad-physics', file: 'performance-cad-physics.json', path: '/?perf=1&quality=high', viewport: { width: 1920, height: 1080 }, physics: true },
{ name: 'cad-full', file: 'performance-cad-full.json', path: '/?perf=1&quality=demo', viewport: { width: 1920, height: 1080 }, physics: true, memoryLoop: true },
{
name: 'mobile-fallback', file: 'performance-mobile-fallback.json', path: '/', viewport: { width: 390, height: 844 },
mobile: true, dpr: 3, fallbackCheck: true,
userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36',
},
];
function median(values) {
const s = [...values].sort((a, b) => a - b);
const mid = Math.floor(s.length / 2);
return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
}
async function collectRun(browser, profile) {
const context = await browser.newContext({
viewport: profile.viewport,
deviceScaleFactor: profile.dpr ?? 1,
userAgent: profile.userAgent,
isMobile: profile.mobile ?? false,
hasTouch: profile.mobile ?? false,
});
const page = await context.newPage();
const consoleErrors = [];
page.on('console', (msg) => { if (msg.type() === 'error') consoleErrors.push(msg.text()); });
page.on('pageerror', (err) => consoleErrors.push(`pageerror: ${err.message}`));
await page.addInitScript(() => {
window.__CTX_LOST__ = 0;
window.addEventListener('webglcontextlost', () => window.__CTX_LOST__++, true);
});
const modelRequests = [];
page.on('request', (req) => { if (/\/models\/.*\.(stl|glb|gltf)/i.test(req.url())) modelRequests.push(req.url()); });
const loadStart = Date.now();
await page.goto(`${BASE}${profile.path}`, { waitUntil: 'domcontentloaded', timeout: 30_000 });
if (profile.fallbackCheck) {
await page.waitForSelector('[data-testid="main-svg-fallback"]', { timeout: 30_000 });
const sceneLoadMs = Date.now() - loadStart;
await page.waitForTimeout(6000);
const canvasCount = await page.locator('canvas').count();
const ctxLost = await page.evaluate(() => window.__CTX_LOST__);
await context.close();
return { fallbackVisible: true, canvasCount, ctxLost, modelRequests, consoleErrors, sceneLoadMs };
}
await page.waitForSelector('canvas', { timeout: 30_000 });
const sceneLoadMs = Date.now() - loadStart;
const play = page.locator('[data-testid="demo-play"]');
if (await play.count()) await play.first().click({ timeout: 5000 }).catch(() => undefined);
await page.waitForFunction(() => typeof window.__PERF_SNAPSHOT__ === 'object', null, { timeout: 15_000 });
await page.waitForTimeout(5000); // shader warmup
await page.evaluate(() => window.__PERF_RESET__?.());
await page.waitForTimeout(DURATION_MS);
const snapshot = await page.evaluate(() => window.__PERF_SNAPSHOT__);
const physicsPerf = profile.physics ? await page.evaluate(() => window.__PHYSICS_PERF__ ?? null) : null;
const drops = await page.evaluate(() => window.__DROP_RESULTS ?? []);
const ctxLost = await page.evaluate(() => window.__CTX_LOST__);
const device = await page.evaluate(() => {
const canvas = document.querySelector('canvas');
const gl2 = canvas?.getContext('webgl2');
const gl = gl2 ?? canvas?.getContext('webgl');
let vendor = 'unknown';
if (gl) {
const dbg = gl.getExtension('WEBGL_debug_renderer_info');
vendor = dbg ? String(gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL)) : String(gl.getParameter(gl.VENDOR));
}
return {
vendor,
hardwareConcurrency: navigator.hardwareConcurrency ?? null,
deviceMemory: navigator.deviceMemory ?? null,
devicePixelRatio: window.devicePixelRatio,
viewport: { width: window.innerWidth, height: window.innerHeight },
};
});
await context.close();
return { snapshot, physicsPerf, drops, device, consoleErrors, sceneLoadMs, ctxLost, modelRequests };
}
async function memoryLoop(browser, profile) {
const context = await browser.newContext({ viewport: profile.viewport });
const page = await context.newPage();
const samples = [];
for (let i = 0; i < 10; i++) {
await page.goto(`${BASE}${profile.path}`, { waitUntil: 'domcontentloaded', timeout: 30_000 });
await page.waitForSelector('canvas', { timeout: 30_000 });
const play = page.locator('[data-testid="demo-play"]');
if (await play.count()) await play.first().click({ timeout: 5000 }).catch(() => undefined);
await page.waitForTimeout(8000);
const heap = await page.evaluate(() => (performance.memory ? performance.memory.usedJSHeapSize : null));
const bodies = await page.evaluate(() => (window.__DROP_RESULTS ?? []).length);
samples.push({ cycle: i + 1, heapBytes: heap, dropResults: bodies });
await page.evaluate(() => window.__PERF_RESET__?.());
}
await context.close();
return samples;
}
mkdirSync(OUT_DIR, { recursive: true });
const browser = await chromium.launch({ executablePath: EXEC, args: GPU_ARGS });
for (const profile of PROFILES) {
if (ONLY && profile.name !== ONLY) continue;
console.log(`\n=== ${profile.name} ===`);
const runs = [];
for (let r = 0; r < (profile.fallbackCheck ? 1 : RUNS); r++) {
runs.push(await collectRun(browser, profile));
console.log(` run ${r + 1} done`);
}
const fpsRuns = runs.map((x) => x.snapshot?.averageFps).filter((v) => typeof v === 'number');
const p95Runs = runs.map((x) => x.snapshot?.p95FrameTimeMs).filter((v) => typeof v === 'number');
const result = {
profile: profile.name,
path: profile.path,
viewport: profile.viewport,
runs: runs.length,
fpsAvg: fpsRuns.length ? fpsRuns.reduce((s, v) => s + v, 0) / fpsRuns.length : null,
fpsMin: Math.min(...runs.map((x) => x.snapshot?.minimumFps ?? Infinity)),
frameTimeP95Ms: p95Runs.length ? median(p95Runs) : null,
drawCalls: runs[0]?.snapshot?.drawCalls ?? null,
triangles: runs[0]?.snapshot?.triangles ?? null,
heapMb: runs.at(-1)?.snapshot?.heapMb ?? null,
renderer: runs[0]?.snapshot?.renderer ?? null,
physics: runs.find((x) => x.physicsPerf)?.physicsPerf ?? null,
dropsVerified: (runs.at(-1)?.drops ?? []).map((d) => ({ caseId: d.caseId, zone: d.expectedZone, inside: d.insideExpectedReceiver })),
device: runs[0]?.device ?? null,
consoleErrors: [...new Set(runs.flatMap((x) => x.consoleErrors))],
sceneLoadMs: median(runs.map((x) => x.sceneLoadMs)),
ctxLost: Math.max(...runs.map((x) => x.ctxLost ?? 0)),
modelRequests: [...new Set(runs.flatMap((x) => x.modelRequests))],
fallback: profile.fallbackCheck ? runs[0] : undefined,
};
if (profile.memoryLoop) {
console.log(' memory loop (10 cycles)...');
result.memoryLoop = await memoryLoop(browser, profile);
}
writeFileSync(join(OUT_DIR, profile.file), JSON.stringify(result, null, 1));
console.log(` -> ${profile.file}: fpsAvg=${result.fpsAvg?.toFixed(1)} p95=${result.frameTimeP95Ms?.toFixed(2)}ms physicsP95=${result.physics?.p95Ms?.toFixed(3)}ms`);
}
await browser.close();
console.log('\nbenchmark complete');

27
scripts/stage2-drops.mjs Normal file
View File

@@ -0,0 +1,27 @@
// Stage 2: run through the demo playlist and collect drop results per case.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
page.on('pageerror', (e) => console.log('PAGEERROR:', e.message));
await page.goto('http://localhost:3101/', { waitUntil: 'networkidle' });
await page.getByRole('button', { name: /play demo|start demo/i }).first().click();
// 2x speed to shorten wall time
const speedBtn = page.getByRole('button', { name: /1x|2x/ }).first();
if (await speedBtn.count()) { /* speed toggling optional */ }
const seen = new Set();
for (let i = 0; i < 100; i++) {
await page.waitForTimeout(2000);
const drops = await page.evaluate(() => window.__DROP_RESULTS ?? []);
for (const d of drops) {
if (!seen.has(d.caseId)) {
seen.add(d.caseId);
console.log(`DROP ${d.caseId} ${d.itemId} -> zone ${d.expectedZone} pos=(${d.finalPosition.map((v) => v.toFixed(2)).join(',')}) inside=${d.insideExpectedReceiver} timeout=${d.settledByTimeout}`);
}
}
if (seen.size >= 9) break;
}
console.log('total drops:', seen.size);
await browser.close();

35
scripts/stage2-verify.mjs Normal file
View File

@@ -0,0 +1,35 @@
// Stage 2 visual verification screenshots (Linux-side Playwright).
import { chromium } from 'playwright';
import fs from 'node:fs';
const OUT = 'docs/stage2_real_sorter/screenshots';
fs.mkdirSync(OUT, { recursive: true });
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
page.on('pageerror', (e) => console.log('PAGEERROR:', e.message));
page.on('console', (m) => { if (m.type() === 'error') console.log('CONSOLE:', m.text()); });
await page.goto('http://localhost:3101/', { waitUntil: 'networkidle' });
await page.getByRole('button', { name: /play demo|start demo/i }).first().click();
const snap = async (name) => {
await page.screenshot({ path: `${OUT}/${name}.png` });
console.log('snap', name);
};
// overview during detection
await page.waitForTimeout(2500);
await snap('check-01-overview');
// wait for routing/drop of case 1 (SKU-001 -> B)
await page.waitForTimeout(6500);
await snap('check-02-routing');
await page.waitForTimeout(2500);
await snap('check-03-settled');
// drop results registry
const drops = await page.evaluate(() => window.__DROP_RESULTS ?? []);
console.log('DROP_RESULTS:', JSON.stringify(drops, null, 1));
await browser.close();

View File

@@ -0,0 +1,148 @@
/**
* Stage 2 — CAD-derived conveyor (runtime GLB from 3d_models/conveer.FCStd).
*
* Asset: public/models/sorter/conveyor-web.glb (159k tris, 555 KB, Draco).
* Node statuses: REAL_CAD (all groups; see docs/stage2_real_sorter/mechanism-map.md).
* Animation (domain-synced via refs/useFrame, no per-frame React state):
* - stop-gate/* barriers: vertical lift stroke 80 mm, eased ~300 ms
* - rollers/Ролик*, rollers/Вал: spin ω = beltVelocity / 0.025 m
* GLB vertices are baked in module coordinates — animated meshes are re-pivoted
* around their own bbox center at load time.
*/
import { useEffect, useMemo, useRef } from 'react';
import * as THREE from 'three';
import { useFrame, useLoader } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
export const CONVEYOR_CAD_URL = '/models/sorter/conveyor-web.glb';
/** World placement: CAD camera arch lands on the domain inspection point. */
export const CONVEYOR_CAD_POSITION: [number, number, number] = [-2.02, 0.594, 0];
/** CAD module world span after placement (m). */
export const CONVEYOR_CAD_SPAN_X: [number, number] = [-2.086, -0.076];
const GATE_STROKE_M = 0.08;
const GATE_SPEED_PER_SEC = 1 / 0.3;
const MATERIALS: Record<string, THREE.MeshStandardMaterial> = {};
function materialFor(slot: string): THREE.MeshStandardMaterial {
if (!MATERIALS[slot]) {
const defs: Record<string, { color: string; metalness: number; roughness: number }> = {
'painted-metal': { color: '#8b9cae', metalness: 0.55, roughness: 0.45 },
'brushed-metal': { color: '#b9c2cc', metalness: 0.9, roughness: 0.3 },
'rubber-belt': { color: '#39424a', metalness: 0.0, roughness: 0.92 },
'dark-mechanical': { color: '#3a4148', metalness: 0.6, roughness: 0.5 },
'safety-yellow': { color: '#f5b301', metalness: 0.2, roughness: 0.55 },
};
MATERIALS[slot] = new THREE.MeshStandardMaterial(defs[slot] ?? defs['painted-metal']);
}
return MATERIALS[slot];
}
function slotFor(nodeName: string): string {
const grp = nodeName.split('/')[0];
switch (grp) {
case 'conveyor-belt': return 'rubber-belt';
case 'rollers': return 'brushed-metal';
case 'motor-and-drive': return 'dark-mechanical';
case 'stop-gate': return 'safety-yellow';
case 'pusher-servo': return 'dark-mechanical';
case 'inspection-frame': return 'painted-metal';
default: return 'painted-metal';
}
}
/** Re-pivot a world-baked mesh around its own bbox center; returns the pivot group. */
function repivot(mesh: THREE.Mesh): THREE.Group {
const parent = mesh.parent;
const bbox = new THREE.Box3().setFromObject(mesh);
const center = bbox.getCenter(new THREE.Vector3());
const pivot = new THREE.Group();
pivot.name = `pivot/${mesh.name}`;
pivot.position.copy(center);
pivot.userData.baseY = center.y;
mesh.position.sub(center);
pivot.add(mesh);
parent?.add(pivot);
return pivot;
}
export function ConveyorCadModel({
gateOpen,
beltVelocityMps,
rollerOmegaRadPerSec,
shadows = false,
}: {
gateOpen: boolean;
beltVelocityMps: number;
rollerOmegaRadPerSec: number;
shadows?: boolean;
}) {
const gltf = useLoader(GLTFLoader, CONVEYOR_CAD_URL, (loader) => {
const draco = new DRACOLoader();
draco.setDecoderPath('/draco/');
loader.setDRACOLoader(draco);
});
const gatePivots = useRef<THREE.Group[]>([]);
const rollerPivots = useRef<THREE.Group[]>([]);
const gateOpenAmount = useRef(0);
const rollerAngle = useRef(0);
const root = useMemo(() => {
gatePivots.current = [];
rollerPivots.current = [];
const scene = gltf.scene.clone(true);
const toPivot: THREE.Mesh[] = [];
scene.traverse((obj) => {
const mesh = obj as THREE.Mesh;
if (!mesh.isMesh) return;
const name = mesh.name || mesh.parent?.name || '';
mesh.material = materialFor(slotFor(name));
mesh.castShadow = shadows;
mesh.receiveShadow = shadows;
if (name.startsWith('stop-gate/') || name.startsWith('rollers/Ролик') || name.startsWith('rollers/Вал')) {
toPivot.push(mesh);
}
});
for (const mesh of toPivot) {
const pivot = repivot(mesh);
if (mesh.name.startsWith('stop-gate/')) gatePivots.current.push(pivot);
else rollerPivots.current.push(pivot);
}
return scene;
}, [gltf, shadows]);
useEffect(() => () => {
// Materials are module-cached; geometry belongs to the shared GLTF cache.
}, []);
useFrame((_, rawDelta) => {
const delta = Math.min(rawDelta, 0.05);
// Stop-gate barriers: eased lift (extend/hold/retract), no teleport.
const target = gateOpen ? 1 : 0;
const cur = gateOpenAmount.current;
const next = cur + Math.sign(target - cur) * Math.min(Math.abs(target - cur), GATE_SPEED_PER_SEC * delta);
gateOpenAmount.current = next;
const eased = next < 0.5 ? 2 * next * next : 1 - Math.pow(-2 * next + 2, 2) / 2;
for (const pivot of gatePivots.current) {
pivot.position.y = (pivot.userData.baseY as number) + GATE_STROKE_M * eased;
}
// Rollers: ω = v / r, angle integrated in a ref (belt velocity already 0 on halt).
rollerAngle.current += rollerOmegaRadPerSec * delta;
for (const pivot of rollerPivots.current) {
pivot.rotation.z = rollerAngle.current;
}
});
return <primitive object={root} position={CONVEYOR_CAD_POSITION} />;
}
export function preloadConveyorCad() {
useLoader.preload(GLTFLoader, CONVEYOR_CAD_URL, (loader) => {
const draco = new DRACOLoader();
draco.setDecoderPath('/draco/');
loader.setDRACOLoader(draco);
});
}

View File

@@ -58,20 +58,20 @@ function FallbackPrimitive({ type, color, accentColor, emissiveIntensity, roughn
);
}
export const PhysicalPlaybackItem = memo(function PhysicalPlaybackItem({
/** Inner visual content of an item (shared by kinematic and physics drivers). */
export function ItemVisualContent({
caseData,
elapsedMs,
slotIndex = 0,
jitter,
phase,
surface,
isSettled,
castShadow = false,
verifySku = null,
}: {
caseData: PlaylistCase;
elapsedMs: number;
slotIndex?: number;
jitter?: { x: number; z: number; yaw: number };
phase: string;
surface: string;
isSettled: boolean;
castShadow?: boolean;
/** Stage 1 verification: SKU to overlay (null = off, 'follow' handled by caller passing current SKU). */
verifySku?: string | null;
}) {
const itemData = useMemo(() => resolveItem(caseData.itemId), [caseData.itemId]);
@@ -80,25 +80,12 @@ export const PhysicalPlaybackItem = memo(function PhysicalPlaybackItem({
const asset = getModelAsset(itemId);
const dims = getRenderedItemDimensions(itemData.dimensionsMm);
const pose = getPhysicalItemPose({
caseId: caseData.id,
slotIndex,
dimensionsMm: itemData.dimensionsMm,
targetCategory: classification.category,
elapsedMs,
faultType: caseData.faultType,
jitter,
});
const { position, rotation, phase, surface, isSettled } = pose;
const isRouting = phase === 'routing';
const onTransport = surface === 'main_belt'
|| surface === 'inspection_station'
|| surface === 'routing_junction'
|| surface === 'b_transfer';
if (elapsedMs < 0) return null;
const routeAccent = COLORS[classification.category] ?? COLORS.sensorAccent;
const material = ITEM_MATERIALS[itemId] ?? { color: '#d8c3a5', roughness: 0.75 };
const bodyColor = phase === 'fault' ? '#ef4444' : material.color;
@@ -137,7 +124,7 @@ export const PhysicalPlaybackItem = memo(function PhysicalPlaybackItem({
const verifying = verifySku != null && verifySku === itemId && asset != null;
return (
<group position={position} rotation={rotation}>
<>
{useReal && asset ? (
<group position={[0, pivotOffsetY, 0]}>
<RealItemModel
@@ -179,6 +166,53 @@ export const PhysicalPlaybackItem = memo(function PhysicalPlaybackItem({
<meshStandardMaterial color="#475569" transparent opacity={0.15} />
</mesh>
)}
</>
);
}
export const PhysicalPlaybackItem = memo(function PhysicalPlaybackItem({
caseData,
elapsedMs,
slotIndex = 0,
jitter,
castShadow = false,
verifySku = null,
}: {
caseData: PlaylistCase;
elapsedMs: number;
slotIndex?: number;
jitter?: { x: number; z: number; yaw: number };
castShadow?: boolean;
/** Stage 1 verification: SKU to overlay (null = off, 'follow' handled by caller passing current SKU). */
verifySku?: string | null;
}) {
const itemData = useMemo(() => resolveItem(caseData.itemId), [caseData.itemId]);
const classification = useMemo(() => classifyItem(itemData), [itemData]);
const pose = getPhysicalItemPose({
caseId: caseData.id,
slotIndex,
dimensionsMm: itemData.dimensionsMm,
targetCategory: classification.category,
elapsedMs,
faultType: caseData.faultType,
jitter,
});
const { position, rotation, phase, surface, isSettled } = pose;
if (elapsedMs < 0) return null;
return (
<group position={position} rotation={rotation}>
<ItemVisualContent
caseData={caseData}
phase={phase}
surface={surface}
isSettled={isSettled}
castShadow={castShadow}
verifySku={verifySku}
/>
</group>
);
});

View File

@@ -0,0 +1,218 @@
/**
* Stage 2 — item with hybrid kinematic/dynamic authority (Rapier).
*
* Authority flow (see docs/stage2_real_sorter/physics-architecture.md):
* 1. kinematicPosition — follows getPhysicalItemPose exactly (domain truth);
* 2. at getDropHandoffTimeMs → dynamic with deterministic initial velocity
* (B: belt edge carry-over; C/D: pusher impulse, scaled per SKU profile);
* 3. gravity/collision/friction/restitution/angular velocity govern the drop;
* 4. on sleep (or controlled 4.5 s timeout) the final position is verified
* against the DOMAIN-decided receiver volume and the body is frozen
* (kinematic) — no drift, clean replay, no teleportation at any point.
*/
import { memo, useEffect, useMemo, useRef } from 'react';
import * as THREE from 'three';
import { useFrame } from '@react-three/fiber';
import {
RigidBody,
CuboidCollider,
CapsuleCollider,
CylinderCollider,
type RapierRigidBody,
} from '@react-three/rapier';
import { RigidBodyType } from '@dimforge/rapier3d-compat';
import { getPhysicalItemPose, getDropHandoffTimeMs } from '../../domain/physicalItemMotion';
import { getVisualPhysicsProfile } from '../../domain/visualPhysicsProfiles';
import { resolveItem } from '../../data/resolveItem';
import { classifyItem } from '../../domain/classifier';
import { receiverContains } from '../../domain/receiverVolumes';
import type { PlaylistCase } from '../../domain/demoPlaylist';
import { ItemVisualContent } from './PhysicalPlaybackItem';
import { recordDropResult } from './SorterPhysics';
type Authority = 'kinematic' | 'dynamic' | 'frozen';
/** Controlled settle budget after handoff (ms of case time). */
const SETTLE_BUDGET_MS = 4500;
function colliderDensity(profile: ReturnType<typeof getVisualPhysicsProfile>): number {
if (profile.collider === 'cuboid' && profile.cuboidHalfExtents) {
const [hx, hy, hz] = profile.cuboidHalfExtents;
return profile.approximateMassKg / (8 * hx * hy * hz);
}
const [r, hh] = profile.capsule ?? [0.05, 0.1];
const volume = profile.collider === 'capsule'
? Math.PI * r * r * (2 * hh + (4 / 3) * r)
: Math.PI * r * r * 2 * hh;
return profile.approximateMassKg / volume;
}
export const PhysicalPlaybackItemPhysics = memo(function PhysicalPlaybackItemPhysics({
caseData,
elapsedMs,
slotIndex = 0,
jitter,
castShadow = false,
verifySku = null,
}: {
caseData: PlaylistCase;
elapsedMs: number;
slotIndex?: number;
jitter?: { x: number; z: number; yaw: number };
castShadow?: boolean;
verifySku?: string | null;
}) {
const itemData = useMemo(() => resolveItem(caseData.itemId), [caseData.itemId]);
const classification = useMemo(() => classifyItem(itemData), [itemData]);
const category = classification.category as 'B' | 'C' | 'D';
const itemId = itemData.id.replace('-LC', '');
const profile = getVisualPhysicsProfile(itemId);
const handoffMs = getDropHandoffTimeMs(classification.category, caseData.faultType);
const bodyRef = useRef<RapierRigidBody>(null);
const authority = useRef<Authority>('kinematic');
const frozenPose = useRef<{ p: [number, number, number]; q: THREE.Quaternion } | null>(null);
const handedOffAtMs = useRef<number | null>(null);
const verified = useRef(false);
const pose = getPhysicalItemPose({
caseId: caseData.id,
slotIndex,
dimensionsMm: itemData.dimensionsMm,
targetCategory: classification.category,
elapsedMs,
faultType: caseData.faultType,
jitter,
});
const handoffPose = useMemo(() => {
if (handoffMs == null) return null;
return getPhysicalItemPose({
caseId: caseData.id,
slotIndex,
dimensionsMm: itemData.dimensionsMm,
targetCategory: classification.category,
elapsedMs: handoffMs,
faultType: caseData.faultType,
jitter,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [handoffMs, caseData.id]);
// Reset authority whenever a new case mounts this body.
useEffect(() => {
authority.current = 'kinematic';
frozenPose.current = null;
handedOffAtMs.current = null;
verified.current = false;
}, [caseData.id]);
useFrame(() => {
const body = bodyRef.current;
if (!body) return;
if (authority.current === 'kinematic') {
// Kinematic drive: domain pose is truth (belt travel, inspection dwell).
const p = pose.position;
const e = new THREE.Euler(pose.rotation[0], pose.rotation[1], pose.rotation[2]);
const q = new THREE.Quaternion().setFromEuler(e);
body.setNextKinematicTranslation({ x: p[0], y: p[1], z: p[2] });
body.setNextKinematicRotation({ x: q.x, y: q.y, z: q.z, w: q.w });
// Physics handoff at pusher contact / belt edge — never for fault cases.
if (handoffMs != null && handoffPose && elapsedMs >= handoffMs) {
const hp = handoffPose.position;
body.setTranslation({ x: hp[0], y: hp[1], z: hp[2] }, true);
const he = new THREE.Euler(handoffPose.rotation[0], handoffPose.rotation[1], handoffPose.rotation[2]);
const hq = new THREE.Quaternion().setFromEuler(he);
body.setRotation({ x: hq.x, y: hq.y, z: hq.z, w: hq.w }, true);
body.setBodyType(RigidBodyType.Dynamic, true);
// Deterministic initial velocity: belt carry-over + pusher impulse.
const dirZ = category === 'D' ? -1 : 1;
const v = category === 'B'
? { x: 1.0, y: 0, z: 0 }
: { x: 0.55, y: 0, z: dirZ * 1.35 * profile.pusherImpulseScale };
body.setLinvel(v, true);
if (profile.canRoll) {
body.setAngvel({ x: category === 'B' ? 2.0 : 0.8, y: 0.4, z: 0 }, true);
} else {
body.setAngvel({ x: 0, y: 0.25, z: 0 }, true);
}
authority.current = 'dynamic';
handedOffAtMs.current = elapsedMs;
}
return;
}
if (authority.current === 'dynamic') {
const slept = body.isSleeping();
const timedOut = handedOffAtMs.current != null && elapsedMs - handedOffAtMs.current > SETTLE_BUDGET_MS;
if ((slept || timedOut) && !verified.current) {
verified.current = true;
const t = body.translation();
const p: [number, number, number] = [t.x, t.y, t.z];
recordDropResult({
caseId: caseData.id,
itemId,
expectedZone: category,
finalPosition: p,
insideExpectedReceiver: receiverContains(category, p),
settledByTimeout: !slept,
timestampMs: Date.now(),
});
const r = body.rotation();
frozenPose.current = { p, q: new THREE.Quaternion(r.x, r.y, r.z, r.w) };
body.setBodyType(RigidBodyType.KinematicPositionBased, false);
body.setLinvel({ x: 0, y: 0, z: 0 }, false);
body.setAngvel({ x: 0, y: 0, z: 0 }, false);
authority.current = 'frozen';
}
return;
}
// frozen: hold the verified rest pose (no drift across replays).
if (frozenPose.current) {
const { p, q } = frozenPose.current;
body.setNextKinematicTranslation({ x: p[0], y: p[1], z: p[2] });
body.setNextKinematicRotation({ x: q.x, y: q.y, z: q.z, w: q.w });
}
});
if (elapsedMs < 0) return null;
const density = colliderDensity(profile);
const ccd = itemId === 'SKU-009'; // pen: small + fast → continuous collision
return (
<RigidBody
ref={bodyRef}
type="kinematicPosition"
colliders={false}
friction={profile.friction}
restitution={profile.restitution}
linearDamping={profile.linearDamping}
angularDamping={profile.angularDamping}
ccd={ccd}
enabledRotations={[true, true, true]}
position={pose.position}
>
{profile.collider === 'cuboid' && profile.cuboidHalfExtents && (
<CuboidCollider args={profile.cuboidHalfExtents} density={density} />
)}
{profile.collider === 'capsule' && profile.capsule && (
<CapsuleCollider args={[profile.capsule[1], profile.capsule[0]]} density={density} />
)}
{profile.collider === 'cylinder' && profile.capsule && (
<CylinderCollider args={[profile.capsule[1], profile.capsule[0]]} density={density} />
)}
<ItemVisualContent
caseData={caseData}
phase={pose.phase}
surface={pose.surface}
isSettled={authority.current === 'frozen' ? true : pose.isSettled}
castShadow={castShadow}
verifySku={verifySku}
/>
</RigidBody>
);
});

View File

@@ -26,6 +26,13 @@ interface CageInstances {
wheels: THREE.Matrix4[];
}
/**
* Stage 2: roll cages are 3-sided with an OPEN FRONT on the conveyor-facing
* side (real roll-container design) plus a 40mm sill — items enter through
* the opening from the gravity chute. Matches physicsWorldLayout colliders.
*/
export type CageOpenSide = 'z-' | 'z+' | 'none';
function boxInstance(x: number, y: number, z: number, sx: number, sy: number, sz: number): THREE.Matrix4 {
return new THREE.Matrix4().compose(
new THREE.Vector3(x, y, z),
@@ -34,11 +41,12 @@ function boxInstance(x: number, y: number, z: number, sx: number, sy: number, sz
);
}
/** Deterministic instance layout for the cage (built once). */
function buildInstances(): CageInstances {
/** Deterministic instance layout for the cage (built once per open side). */
function buildInstances(openSide: CageOpenSide): CageInstances {
const boxes: THREE.Matrix4[] = [];
const yBot = WHEEL_D; // bottom of frame body
const yTop = H; // top of frame body (exterior top)
const openSign = openSide === 'z-' ? -1 : openSide === 'z+' ? 1 : 0;
// 4 corner posts
for (const sx of [-1, 1]) {
@@ -46,13 +54,19 @@ function buildInstances(): CageInstances {
boxes.push(boxInstance(sx * (W / 2 - FT / 2), yBot + BODY_H / 2, sz * (D / 2 - FT / 2), FT, BODY_H, FT));
}
}
// bottom + top frame rectangles
// bottom + top frame rectangles (skip the open side's tubes; sill added below)
for (const y of [yBot + FT / 2, yTop - FT / 2]) {
boxes.push(boxInstance(0, y, D / 2 - FT / 2, W, FT, FT));
boxes.push(boxInstance(0, y, -(D / 2 - FT / 2), W, FT, FT));
for (const sz of [-1, 1]) {
if (sz === openSign && y === yTop - FT / 2) continue; // open front: no top tube
boxes.push(boxInstance(0, y, sz * (D / 2 - FT / 2), W, FT, FT));
}
boxes.push(boxInstance(W / 2 - FT / 2, y, 0, FT, FT, D - FT * 2));
boxes.push(boxInstance(-(W / 2 - FT / 2), y, 0, FT, FT, D - FT * 2));
}
// 40mm sill across the open front (matches entry-sill collider)
if (openSign !== 0) {
boxes.push(boxInstance(0, yBot + 0.02, openSign * (D / 2 - FT / 2), W, 0.04, FT));
}
// grid walls between frames (interior span)
const yGridBot = yBot + FT;
@@ -66,6 +80,7 @@ function buildInstances(): CageInstances {
const vCols = Math.floor((xInner * 2) / GRID_STEP) - 1; // exclude corners (posts)
const hRows = Math.max(1, Math.round(gridH / GRID_STEP) - 1);
for (const sz of [-1, 1]) {
if (sz === openSign) continue; // open front: no grid wall
const z = sz * (D / 2 - ROD / 2);
for (let i = 1; i <= vCols; i++) {
const x = -xInner + (i * (xInner * 2)) / (vCols + 1);
@@ -106,12 +121,13 @@ function buildInstances(): CageInstances {
return { boxes, wheels };
}
export default function RollCageMesh({ color, active = false, shadows = false }: {
export default function RollCageMesh({ color, active = false, shadows = false, openSide = 'none' }: {
color: string;
active?: boolean;
shadows?: boolean;
openSide?: CageOpenSide;
}) {
const instances = useMemo(buildInstances, []);
const instances = useMemo(() => buildInstances(openSide), [openSide]);
const boxGeo = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
const wheelGeo = useMemo(() => new THREE.CylinderGeometry(WR, WR, 0.03, 12), []);
const boxesRef = useRef<THREE.InstancedMesh>(null);

View File

@@ -9,7 +9,7 @@
*/
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { Grid, OrbitControls, Html, Line } from '@react-three/drei';
import { Grid, OrbitControls, Html, Line, Environment, Lightformer } from '@react-three/drei';
import { Suspense, lazy, useRef, useMemo, useState, useEffect } from 'react';
import type { Mesh, Group } from 'three';
import * as THREE from 'three';
@@ -21,6 +21,10 @@ import { getMeasurementData, shouldShowLaserBeam, shouldShowStepperPulse, should
import { ITEMS } from '../../data/items';
import type { Category } from '../../domain/types';
import { PhysicalPlaybackItem } from './PhysicalPlaybackItem';
import { PhysicalPlaybackItemPhysics } from './PhysicalPlaybackItemPhysics';
import { ConveyorCadModel, CONVEYOR_CAD_SPAN_X, preloadConveyorCad } from './ConveyorCadModel';
import { SorterPhysicsWorld } from './SorterPhysics';
import { deriveSorterVisualState } from '../../domain/sorterVisualState';
import { DEMO_PLAYLIST, PLAYLIST_LENGTH } from '../../domain/demoPlaylist';
import { cumulativePlaylistDurationMs, getPlaylistCaseDurationMs } from '../../domain/continuousPlayback';
import { INDUSTRIAL_PALETTE } from '../../domain/industrialTheme';
@@ -55,6 +59,7 @@ import {
CAGE_FLOOR_Y,
CONVEYOR_SPEED_MPS,
} from '../../domain/physicalLayout';
import { CHUTE_PITCH, CHUTE_LENGTH, CHUTE_MID_Y, CHUTE_MID_Z, CHUTE_X, CHUTE_HALF_W } from '../../domain/physicsWorldLayout';
import PerfCollector from './PerfCollector';
import PerfOverlay from './PerfOverlay';
import RollCageMesh from './RollCageMesh';
@@ -131,7 +136,7 @@ const MAX_VISIBLE_ITEMS = 6;
/** Heavy animated overlays off by default for smooth demo. */
const ENABLE_DEMO_EFFECTS = false;
const CONVEYOR_LENGTH = CONVEYOR_END_X - CONVEYOR_START_X;
const CONVEYOR_CENTER_X = (CONVEYOR_START_X + CONVEYOR_END_X) / 2;
void CONVEYOR_LENGTH;
/** Cinematic camera controller - smoothly transitions between camera angles */
function CinematicCameraController({
@@ -524,89 +529,110 @@ function SupportLeg({ x }: { x: number }) {
* Conveyor belt - realistic roller conveyor
* Belt top surface at 0.7m (BELT_TOP_Y)
*/
function ConveyorBelt({ pulseActive, elapsedMs, simplified, shadows = false }: { pulseActive: boolean; elapsedMs: number; simplified: boolean; shadows?: boolean }) {
/**
* Belt extension segment (SPEC_DERIVED) — entry/exit sections flanking the
* CAD conveyor module. Same 500 mm width / 700 mm belt top as the real unit.
*/
function BeltSection({ startX, endX, simplified, shadows }: { startX: number; endX: number; simplified: boolean; shadows: boolean }) {
const spacing = simplified ? ROLLER_SPACING_M * 2 : ROLLER_SPACING_M;
const rollerCount = Math.floor(CONVEYOR_LENGTH / spacing);
const stripeOffsets = simplified ? [-2, 0, 2] : [-3, -1, 0.5, 2];
const length = endX - startX;
const centerX = (startX + endX) / 2;
const rollerCount = Math.max(1, Math.floor(length / spacing));
const rollerPositions = useMemo(() => {
const positions: [number, number, number][] = [];
for (let i = 0; i < rollerCount; i++) {
positions.push([CONVEYOR_START_X + spacing / 2 + i * spacing, ROLLER_Y, 0]);
positions.push([startX + spacing / 2 + i * spacing, ROLLER_Y, 0]);
}
return positions;
}, [rollerCount, spacing]);
}, [rollerCount, spacing, startX]);
const legPositions = useMemo(() => {
const positions: number[] = [];
for (let x = CONVEYOR_START_X + 0.5; x < CONVEYOR_END_X - 0.3; x += 2.0) {
for (let x = startX + 0.5; x < endX - 0.3; x += 2.0) {
positions.push(x);
}
return positions;
}, []);
}, [startX, endX]);
return (
<group>
{/* Main belt surface - matte PVC/tarpaulin look at 0.7m */}
<mesh position={[CONVEYOR_CENTER_X, BELT_Y - BELT_THICKNESS_M / 2, 0]} receiveShadow={shadows}>
<boxGeometry args={[CONVEYOR_LENGTH, BELT_THICKNESS_M, CONVEYOR_WIDTH_M]} />
<meshStandardMaterial
color={COLORS.belt}
roughness={0.85}
metalness={0.05}
/>
<mesh position={[centerX, BELT_Y - BELT_THICKNESS_M / 2, 0]} receiveShadow={shadows}>
<boxGeometry args={[length, BELT_THICKNESS_M, CONVEYOR_WIDTH_M]} />
<meshStandardMaterial color={COLORS.belt} roughness={0.85} metalness={0.05} />
</mesh>
<mesh position={[centerX, BELT_Y + SIDE_GUARD_HEIGHT_M / 2, CONVEYOR_WIDTH_M / 2 + 0.02]}>
<boxGeometry args={[length, SIDE_GUARD_HEIGHT_M, 0.025]} />
<meshStandardMaterial color={COLORS.sideGuards} metalness={0.4} roughness={0.5} />
</mesh>
<mesh position={[centerX, BELT_Y + SIDE_GUARD_HEIGHT_M / 2, -CONVEYOR_WIDTH_M / 2 - 0.02]}>
<boxGeometry args={[length, SIDE_GUARD_HEIGHT_M, 0.025]} />
<meshStandardMaterial color={COLORS.sideGuards} metalness={0.4} roughness={0.5} />
</mesh>
<mesh position={[centerX, FRAME_TOP_Y + 0.025, CONVEYOR_WIDTH_M / 2 + 0.01]} castShadow={shadows}>
<boxGeometry args={[length, 0.05, 0.04]} />
<meshStandardMaterial color={COLORS.conveyorFrame} metalness={0.5} roughness={0.4} />
</mesh>
<mesh position={[centerX, FRAME_TOP_Y + 0.025, -CONVEYOR_WIDTH_M / 2 - 0.01]} castShadow={shadows}>
<boxGeometry args={[length, 0.05, 0.04]} />
<meshStandardMaterial color={COLORS.conveyorFrame} metalness={0.5} roughness={0.4} />
</mesh>
{rollerPositions.map((pos, i) => (
<StaticRoller key={i} position={pos} />
))}
{legPositions.map((x, i) => (
<SupportLeg key={i} x={x} />
))}
</group>
);
}
/**
* Conveyor: CAD module (REAL_CAD) + entry/exit belt extensions (SPEC_DERIVED).
* Gate/roller/belt motion is driven by the domain visual-state adapter.
*/
function ConveyorBelt({ pulseActive, elapsedMs, simplified, shadows = false, gateOpen = false, beltVelocityMps = 0, rollerOmega = 0 }: { pulseActive: boolean; elapsedMs: number; simplified: boolean; shadows?: boolean; gateOpen?: boolean; beltVelocityMps?: number; rollerOmega?: number }) {
const stripeOffsets = simplified ? [-2, 0, 2] : [-3, -1, 0.5, 2];
const [cadStart, cadEnd] = CONVEYOR_CAD_SPAN_X;
return (
<group>
{/* CAD-derived conveyor module (real machine: frame, belt, rollers,
drive, metering gates, servo diverters, camera arch) */}
<Suspense fallback={null}>
<ConveyorCadModel
gateOpen={gateOpen}
beltVelocityMps={beltVelocityMps}
rollerOmegaRadPerSec={rollerOmega}
shadows={shadows}
/>
</Suspense>
{/* Entry extension: zone A feed into the CAD module */}
<BeltSection startX={CONVEYOR_START_X} endX={cadStart} simplified={simplified} shadows={shadows} />
{/* Exit extension: CAD module discharge to the B spur */}
<BeltSection startX={cadEnd} endX={CONVEYOR_END_X} simplified={simplified} shadows={shadows} />
{/* Belt stripes — deterministic movement synced to item (offset = time * 1 m/s) */}
{stripeOffsets.map((offset, i) => (
<BeltStripe key={i} baseOffset={offset} elapsedMs={elapsedMs} />
))}
{/* Side guards - brushed metal above belt */}
<mesh position={[CONVEYOR_CENTER_X, BELT_Y + SIDE_GUARD_HEIGHT_M / 2, CONVEYOR_WIDTH_M / 2 + 0.02]}>
<boxGeometry args={[CONVEYOR_LENGTH, SIDE_GUARD_HEIGHT_M, 0.025]} />
<meshStandardMaterial color={COLORS.sideGuards} metalness={0.4} roughness={0.5} />
</mesh>
<mesh position={[CONVEYOR_CENTER_X, BELT_Y + SIDE_GUARD_HEIGHT_M / 2, -CONVEYOR_WIDTH_M / 2 - 0.02]}>
<boxGeometry args={[CONVEYOR_LENGTH, SIDE_GUARD_HEIGHT_M, 0.025]} />
<meshStandardMaterial color={COLORS.sideGuards} metalness={0.4} roughness={0.5} />
</mesh>
{/* Frame rails - industrial metal */}
<mesh position={[CONVEYOR_CENTER_X, FRAME_TOP_Y + 0.025, CONVEYOR_WIDTH_M / 2 + 0.01]} castShadow={shadows}>
<boxGeometry args={[CONVEYOR_LENGTH, 0.05, 0.04]} />
<meshStandardMaterial color={COLORS.conveyorFrame} metalness={0.5} roughness={0.4} />
</mesh>
<mesh position={[CONVEYOR_CENTER_X, FRAME_TOP_Y + 0.025, -CONVEYOR_WIDTH_M / 2 - 0.01]} castShadow={shadows}>
<boxGeometry args={[CONVEYOR_LENGTH, 0.05, 0.04]} />
<meshStandardMaterial color={COLORS.conveyorFrame} metalness={0.5} roughness={0.4} />
</mesh>
{/* Rollers — static geometry (no per-frame rotation hooks) */}
{rollerPositions.map((pos, i) => (
<StaticRoller key={i} position={pos} />
))}
{/* Drive roller at end (larger) */}
<mesh position={[CONVEYOR_END_X - 0.1, ROLLER_Y, 0]} rotation={[0, 0, Math.PI / 2]}>
<cylinderGeometry args={[DRIVE_ROLLER_RADIUS_M, DRIVE_ROLLER_RADIUS_M, CONVEYOR_WIDTH_M - 0.02, 16]} />
<meshStandardMaterial color="#64748b" metalness={0.5} roughness={0.4} />
</mesh>
{/* Tension roller at start (larger) */}
<mesh position={[CONVEYOR_START_X + 0.1, ROLLER_Y, 0]} rotation={[0, 0, Math.PI / 2]}>
<cylinderGeometry args={[DRIVE_ROLLER_RADIUS_M * 0.9, DRIVE_ROLLER_RADIUS_M * 0.9, CONVEYOR_WIDTH_M - 0.02, 16]} />
<meshStandardMaterial color="#64748b" metalness={0.5} roughness={0.4} />
</mesh>
{/* Support legs from floor */}
{legPositions.map((x, i) => (
<SupportLeg key={i} x={x} />
))}
{/* Stepper motor at drive end */}
<StepperMotor position={[CONVEYOR_END_X - 0.1, ROLLER_Y, 0]} pulseActive={pulseActive} />
{/* End caps / guards */}
<mesh position={[CONVEYOR_START_X, BELT_Y - 0.05, 0]}>
<boxGeometry args={[0.05, 0.12, CONVEYOR_WIDTH_M + 0.1]} />
@@ -680,10 +706,10 @@ function BReceiverBin({ active }: { active: boolean }) {
</mesh>
))}
{/* Drop chute into bin */}
{/* Drop chute into bin — descends toward +X (sign matches collider) */}
<mesh
position={[(transferEndX + entryX) / 2, (BELT_Y + floorY) / 2 + 0.02, centerZ]}
rotation={[0, 0, Math.atan2(BELT_Y - floorY, entryX - transferEndX)]}
rotation={[0, 0, -Math.atan2(BELT_Y - floorY, entryX - transferEndX)]}
>
<boxGeometry args={[Math.hypot(entryX - transferEndX, BELT_Y - floorY), 0.015, CONVEYOR_WIDTH_M - 0.08]} />
<meshStandardMaterial color="#4ade80" transparent opacity={0.5} emissive={COLORS.routeB} emissiveIntensity={emissive} side={THREE.DoubleSide} />
@@ -765,7 +791,7 @@ function RollCage({ position, label, color, active, shadows = false }: {
<meshStandardMaterial color={color} transparent opacity={active ? 0.25 : 0.08} />
</mesh>
<RollCageMesh color={color} active={active} shadows={shadows} />
<RollCageMesh color={color} active={active} shadows={shadows} openSide={label === 'C' ? 'z-' : 'z+'} />
{/* Label */}
<Html position={[0, height + 0.15, 0]} center>
@@ -784,41 +810,45 @@ function RollCage({ position, label, color, active, shadows = false }: {
}
/** Chute/deflector for routing items to C/D */
/** Chute/deflector for routing items to C/D.
* Visual geometry matches the physics collider and the domain kinematic
* chute line: steep gravity chute (~0.394 rad) from belt edge (y 0.66)
* down to the open cage front (y 0.14) — see domain/physicsWorldLayout. */
function RouteChute({ gateX, targetZ, color, active }: {
gateX: number;
targetZ: number;
color: string;
active: boolean;
}) {
const chuteLength = Math.abs(targetZ) - CONVEYOR_WIDTH_M / 2 - 0.1;
const chuteWidth = 0.4;
const chuteWidth = CHUTE_HALF_W * 2;
const direction = targetZ > 0 ? 1 : -1;
const midZ = (CONVEYOR_WIDTH_M / 2 + 0.1) * direction + (chuteLength / 2) * direction;
const midZ = direction * CHUTE_MID_Z;
const rot: [number, number, number] = [direction * CHUTE_PITCH, 0, 0];
void gateX;
return (
<group>
{/* Chute surface - angled slightly down */}
<mesh
position={[gateX + 0.3, BELT_TOP_Y - 0.02, midZ]}
rotation={[direction * -0.1, 0, 0]}
{/* Chute surface — steep gravity chute toward the cage open front */}
<mesh
position={[CHUTE_X, CHUTE_MID_Y - 0.015, midZ]}
rotation={rot}
>
<boxGeometry args={[chuteWidth, 0.02, chuteLength]} />
<meshStandardMaterial
color={color}
transparent
<boxGeometry args={[chuteWidth, 0.02, CHUTE_LENGTH]} />
<meshStandardMaterial
color={color}
transparent
opacity={active ? 0.7 : 0.3}
emissive={color}
emissiveIntensity={active ? 0.2 : 0}
/>
</mesh>
{/* Side rails */}
<mesh position={[gateX + 0.3 - chuteWidth / 2 - 0.015, BELT_TOP_Y + 0.02, midZ]}>
<boxGeometry args={[0.02, 0.06, chuteLength]} />
{/* Side rails — follow the chute pitch */}
<mesh position={[CHUTE_X - CHUTE_HALF_W - 0.01, CHUTE_MID_Y + 0.03, midZ]} rotation={rot}>
<boxGeometry args={[0.02, 0.07, CHUTE_LENGTH]} />
<meshStandardMaterial color={color} metalness={0.5} roughness={0.4} />
</mesh>
<mesh position={[gateX + 0.3 + chuteWidth / 2 + 0.015, BELT_TOP_Y + 0.02, midZ]}>
<boxGeometry args={[0.02, 0.06, chuteLength]} />
<mesh position={[CHUTE_X + CHUTE_HALF_W + 0.01, CHUTE_MID_Y + 0.03, midZ]} rotation={rot}>
<boxGeometry args={[0.02, 0.07, CHUTE_LENGTH]} />
<meshStandardMaterial color={color} metalness={0.5} roughness={0.4} />
</mesh>
</group>
@@ -1148,6 +1178,7 @@ function ContinuousScene({
stage0,
stage1,
maxVisibleItems = MAX_VISIBLE_ITEMS,
shadowsEnabled = false,
}: {
playback: ContinuousPlaybackState;
simplified: boolean;
@@ -1156,6 +1187,8 @@ function ContinuousScene({
stage0?: Stage0Config;
stage1?: Stage1Config;
maxVisibleItems?: number;
/** Stage 2 visual pass: PCF shadows + studio environment on capable quality modes. */
shadowsEnabled?: boolean;
}) {
const category = playback.targetCategory;
const phase = playback.currentPhase;
@@ -1164,14 +1197,18 @@ function ContinuousScene({
// Stage 0 prototype flags (inert when stage0 is absent — default route unchanged)
const proto = stage0?.enabled ?? false;
const protoShadows = proto && stage0!.shadows;
const protoShadows = proto ? (proto && stage0!.shadows) : shadowsEnabled;
const protoCamera = proto && stage0!.camera;
const shotOverride = protoCamera && stage0!.shot ? shotToPhaseCategory(stage0!.shot) : null;
const darkBg = proto && stage0!.darkBackground;
// Stage 2: premium industrial dark environment is the default look.
const darkBg = proto ? stage0!.darkBackground : true;
const cameraHighlight = shouldHighlightCamera(phase);
const showScan = shouldShowScanEffect(phase);
// Stage 2: single domain -> visual/physics adapter (business logic stays truth)
const visualState = deriveSorterVisualState(playback);
// Measurement system states
const measurementData = getMeasurementData(playback);
const showLaser = shouldShowLaserBeam(phase);
@@ -1223,6 +1260,7 @@ function ContinuousScene({
for (const asset of getPreloadAssets()) {
if (asset.runtimePath) preloadRealItemModel(asset.runtimePath);
}
preloadConveyorCad();
}, []);
const itemScale = Math.max(dims.width, dims.depth, dims.height);
@@ -1248,9 +1286,9 @@ function ContinuousScene({
: 'x';
const itemColor = category ? COLORS[`route${category}` as keyof typeof COLORS] : COLORS.sensorAccent;
// Cinematic camera: heavy effects (legacy flag) OR stage0 prototype camera.
// Manual shot override keeps the camera driving even while paused.
const cinematicActive = (effectsEnabled || protoCamera) && autoCameraEnabled
// Stage 2: cinematic auto-camera is the default presentation (§13.5 —
// OrbitControls remain only as the manual/debug mode when auto cam is off).
const cinematicActive = autoCameraEnabled
&& (playback.status === 'running' || shotOverride !== null);
return (
@@ -1284,11 +1322,34 @@ function ContinuousScene({
</>
) : (
<>
{/* Soft natural lighting — no shadow maps (Canvas shadows=false for perf) */}
<ambientLight intensity={0.7} />
<hemisphereLight args={['#f8fafc', '#d0dae8', 0.5]} />
<directionalLight position={[8, 12, 6]} intensity={0.9} />
<directionalLight position={[-5, 8, -4]} intensity={0.35} />
{/* Stage 2 default: premium industrial rig — low ambient, key with
PCF shadows, soft fill, cool rim (Stage 0 proven values) */}
<ambientLight intensity={0.28} />
<hemisphereLight args={['#2a3c58', '#141c2a', 0.8]} />
<directionalLight
position={[6, 9, 4]}
intensity={2.2}
castShadow={protoShadows}
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
shadow-camera-left={-7}
shadow-camera-right={7}
shadow-camera-top={7}
shadow-camera-bottom={-7}
shadow-camera-near={1}
shadow-camera-far={25}
shadow-bias={-0.0004}
/>
<directionalLight position={[-5, 6, -3]} intensity={0.35} />
<directionalLight position={[2, 5, -8]} intensity={0.7} color="#bcd7ff" />
{/* Procedural studio environment (no external HDRI — offline-safe) */}
{protoShadows && (
<Environment resolution={128} frames={1}>
<Lightformer intensity={1.6} position={[0, 5, 0]} rotation-x={Math.PI / 2} scale={[8, 8, 1]} color="#dfe9ff" />
<Lightformer intensity={0.7} position={[-5, 2, -4]} rotation-y={Math.PI / 3} scale={[4, 2, 1]} color="#b8c8e8" />
<Lightformer intensity={0.5} position={[5, 1.5, 3]} rotation-y={-Math.PI / 4} scale={[3, 1.5, 1]} color="#ffe9c8" />
</Environment>
)}
</>
)}
@@ -1297,23 +1358,36 @@ function ContinuousScene({
args={[16, 12]}
cellSize={0.5}
cellThickness={0.4}
cellColor={COLORS.gridCell}
cellColor={darkBg ? '#22303f' : COLORS.gridCell}
sectionSize={2}
sectionThickness={0.8}
sectionColor={COLORS.gridSection}
sectionColor={darkBg ? '#2f4256' : COLORS.gridSection}
fadeDistance={12}
infiniteGrid={false}
position={[0, 0.001, 0]}
/>
{/* Floor */}
{/* Floor — dark polished concrete with soft reflections */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]} receiveShadow={protoShadows}>
<planeGeometry args={[16, 12]} />
<meshStandardMaterial color={COLORS.floor} roughness={0.9} metalness={0} />
<meshStandardMaterial
color={darkBg ? '#1c2534' : COLORS.floor}
roughness={darkBg ? 0.55 : 0.9}
metalness={darkBg ? 0.25 : 0}
envMapIntensity={0.8}
/>
</mesh>
{/* Conveyor - belt top at 0.7m */}
<ConveyorBelt pulseActive={showPulse} elapsedMs={totalElapsedMs} simplified={liteScene} shadows={protoShadows} />
{/* Conveyor: CAD module + SPEC_DERIVED extensions — belt top at 0.7m */}
<ConveyorBelt
pulseActive={showPulse}
elapsedMs={totalElapsedMs}
simplified={liteScene}
shadows={protoShadows}
gateOpen={visualState.gateOpen}
beltVelocityMps={visualState.beltVelocityMps}
rollerOmega={visualState.rollerOmegaRadPerSec}
/>
{/* Zone A - spawn point */}
<ZoneMarker
@@ -1392,18 +1466,21 @@ function ContinuousScene({
{/* Route arrows on belt surface */}
<RouteArrows activeRoute={activeRoute} />
{/* Physically simulated items */}
{sceneItems.map(item => (
<PhysicalPlaybackItem
key={item.id}
caseData={item.caseData}
elapsedMs={item.elapsedMs}
slotIndex={item.slotIndex}
verifySku={verifySku}
jitter={item.slotIndex === currentCaseIndex ? positionJitter : undefined}
castShadow={protoShadows}
/>
))}
{/* Items: kinematic on the belt (domain truth), rigid-body physics
after the drop handoff, verified against the domain receiver */}
<SorterPhysicsWorld running={playback.status === 'running'} speed={playback.speed}>
{sceneItems.map(item => (
<PhysicalPlaybackItemPhysics
key={item.id}
caseData={item.caseData}
elapsedMs={item.elapsedMs}
slotIndex={item.slotIndex}
verifySku={verifySku}
jitter={item.slotIndex === currentCaseIndex ? positionJitter : undefined}
castShadow={protoShadows}
/>
))}
</SorterPhysicsWorld>
{/* Outline/BBox for the CURRENT item only */}
<BoundingBoxVisual
@@ -1430,16 +1507,14 @@ function ContinuousScene({
/>
)}
{/* Cinematic camera controller — opt-in (legacy effects flag or stage0 prototype) */}
{(effectsEnabled || protoCamera) && (
<CinematicCameraController
playback={playback}
enabled={cinematicActive}
viewportType={viewportType}
overridePhase={shotOverride?.phase ?? null}
overrideCategory={shotOverride?.category ?? null}
/>
)}
{/* Cinematic camera controller — default on; manual orbit = debug mode */}
<CinematicCameraController
playback={playback}
enabled={cinematicActive}
viewportType={viewportType}
overridePhase={shotOverride?.phase ?? null}
overrideCategory={shotOverride?.category ?? null}
/>
{/* OrbitControls - enabled when not in cinematic mode */}
<OrbitControls
@@ -1541,11 +1616,13 @@ export default function SorterDigitalTwinContinuous({
shadows={shadowsEnabled ? 'soft' : false}
gl={{ antialias, powerPreference: 'high-performance' }}
onCreated={({ gl }) => {
if (proto) {
gl.outputColorSpace = THREE.SRGBColorSpace;
gl.toneMapping = stage0!.toneMapping ? THREE.ACESFilmicToneMapping : THREE.NoToneMapping;
gl.toneMappingExposure = 1.0;
gl.shadowMap.type = THREE.PCFSoftShadowMap;
// Stage 2: premium industrial look is the default (ACES + PCFSoft).
gl.outputColorSpace = THREE.SRGBColorSpace;
gl.toneMapping = THREE.ACESFilmicToneMapping;
gl.toneMappingExposure = 1.15;
gl.shadowMap.type = THREE.PCFSoftShadowMap;
if (proto && !stage0!.toneMapping) {
gl.toneMapping = THREE.NoToneMapping;
}
const canvas = gl.domElement;
const handleLost = (event: Event) => {
@@ -1570,6 +1647,7 @@ export default function SorterDigitalTwinContinuous({
stage0={stage0}
stage1={stage1}
maxVisibleItems={quality.maxVisibleItems}
shadowsEnabled={shadowsEnabled}
/>
{perfEnabled ? (
<PerfCollector

View File

@@ -0,0 +1,117 @@
/**
* Stage 2 — physics world for the sorter drop segment.
*
* Hybrid authority (docs/stage2_real_sorter/physics-architecture.md):
* - items on the belt are KINEMATIC (domain pose is truth);
* - at the drop handoff (pusher contact / belt edge) the body switches to
* DYNAMIC with deterministic initial velocity;
* - static colliders mirror the visible chute / receiver geometry
* (documented hidden colliders, same dimensions as the visuals).
*
* Determinism: fixed dt = 1/60, max 4 substeps/frame, no unseeded randomness.
* Physics freezes when the domain clock is paused (documented simulation
* assumption — belt, gate and items halt together; EMERGENCY_STOP creates no
* new impulses).
*/
import { useRef, type ReactNode } from 'react';
import { useFrame } from '@react-three/fiber';
import { Physics, RigidBody, CuboidCollider, useRapier } from '@react-three/rapier';
import { getStaticColliders } from '../../domain/physicsWorldLayout';
export const PHYSICS_DT = 1 / 60;
const MAX_SUBSTEPS = 4;
/** Drop verification record (debug/e2e introspection, no secrets). */
export interface DropResult {
caseId: string;
itemId: string;
expectedZone: 'B' | 'C' | 'D';
finalPosition: [number, number, number];
insideExpectedReceiver: boolean;
settledByTimeout: boolean;
timestampMs: number;
}
declare global {
interface Window { __DROP_RESULTS?: DropResult[] }
}
export function recordDropResult(result: DropResult) {
if (typeof window !== 'undefined') {
window.__DROP_RESULTS = [...(window.__DROP_RESULTS ?? []).slice(-49), result];
}
}
/** Steps the Rapier world with a fixed dt, scaled by domain playback speed. */
function RapierStepper({ running, speed }: { running: boolean; speed: number }) {
const { world } = useRapier();
const accumulator = useRef(0);
const perfEnabled = useRef(
typeof window !== 'undefined'
&& new URLSearchParams(window.location.search).get('perf') === '1',
);
const samples = useRef<number[]>([]);
useFrame((_, delta) => {
if (!running) return;
accumulator.current += Math.min(delta, 0.1) * speed;
let steps = 0;
while (accumulator.current >= PHYSICS_DT && steps < MAX_SUBSTEPS) {
if (perfEnabled.current) {
const t0 = performance.now();
world.step();
const ms = performance.now() - t0;
samples.current.push(ms);
if (samples.current.length > 600) samples.current.shift();
const sorted = [...samples.current].sort((a, b) => a - b);
(window as unknown as { __PHYSICS_PERF__?: unknown }).__PHYSICS_PERF__ = {
count: samples.current.length,
avgMs: samples.current.reduce((s, v) => s + v, 0) / samples.current.length,
p95Ms: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
maxMs: sorted[sorted.length - 1] ?? 0,
};
} else {
world.step();
}
accumulator.current -= PHYSICS_DT;
steps += 1;
}
if (steps === MAX_SUBSTEPS) accumulator.current = 0;
});
return null;
}
/** Static colliders for the whole working area (fixed bodies, cheap cuboids).
* Layout data lives in domain/physicsWorldLayout — shared with headless tests. */
export function SorterStaticColliders() {
return (
<RigidBody type="fixed" colliders={false}>
{getStaticColliders().map((c) => (
<CuboidCollider
key={c.id}
args={c.halfExtents}
position={c.position}
rotation={c.rotation}
friction={c.friction}
/>
))}
</RigidBody>
);
}
export function SorterPhysicsWorld({
running,
speed,
children,
}: {
running: boolean;
speed: number;
children: ReactNode;
}) {
return (
<Physics updateLoop="independent" paused timeStep={PHYSICS_DT} gravity={[0, -9.81, 0]}>
<RapierStepper running={running} speed={speed} />
<SorterStaticColliders />
{children}
</Physics>
);
}

View File

@@ -0,0 +1,87 @@
/**
* Stage 2 §16 — mobile capability policy.
*
* Decides what a mobile client gets, WITHOUT using viewport width alone:
* - 'svg' — weak/unknown device or Telegram WebView → SVG fallback;
* - 'mobile-low' — capable modern phone → 3D at Mobile Low quality
* (DPR<=1.25, no post, LOD2-equivalent scene, 30 FPS target,
* single active dynamic item);
* - 'full' — not a mobile device (desktop policy applies elsewhere).
*
* Signals: UA class, Telegram WebView markers, hardwareConcurrency,
* deviceMemory, WebGL renderer string. First-FPS watchdog is applied
* separately at runtime (MobileFpsWatchdog).
*/
export type MobileTier = 'svg' | 'mobile-low' | 'full';
export interface MobileSignals {
userAgent: string;
hardwareConcurrency: number | null;
deviceMemoryGb: number | null;
webglRenderer: string | null;
hasTelegramProxy: boolean;
}
const MOBILE_UA_RE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile/i;
const TELEGRAM_UA_RE = /Telegram/i;
const SOFTWARE_GL_RE = /swiftshader|llvmpipe|softpipe|software|basic render/i;
export function isMobileUa(userAgent: string): boolean {
return MOBILE_UA_RE.test(userAgent);
}
export function isTelegramWebView(signals: Pick<MobileSignals, 'userAgent' | 'hasTelegramProxy'>): boolean {
return TELEGRAM_UA_RE.test(signals.userAgent) || signals.hasTelegramProxy;
}
/**
* Telegram WebView policy (Stage 2 §16): forced SVG until verified on a real
* Telegram WebView — conservative, honest (no 3D claims without a device).
*/
export function decideMobileTier(signals: MobileSignals): MobileTier {
if (!isMobileUa(signals.userAgent)) return 'full';
if (isTelegramWebView(signals)) return 'svg';
if (signals.webglRenderer && SOFTWARE_GL_RE.test(signals.webglRenderer)) return 'svg';
const cores = signals.hardwareConcurrency ?? 0;
const mem = signals.deviceMemoryGb ?? 0;
// Capable-device gate: enough cores and memory for Mobile Low.
if (cores >= 8 && (mem === 0 || mem >= 4)) return 'mobile-low';
if (cores >= 6 && mem >= 6) return 'mobile-low';
return 'svg';
}
/** Runtime signals collector (browser-only; SSR-safe). */
export function collectMobileSignals(): MobileSignals {
if (typeof navigator === 'undefined') {
return { userAgent: '', hardwareConcurrency: null, deviceMemoryGb: null, webglRenderer: null, hasTelegramProxy: false };
}
let renderer: string | null = null;
try {
const canvas = document.createElement('canvas');
const gl = (canvas.getContext('webgl2') ?? canvas.getContext('webgl')) as WebGLRenderingContext | null;
if (gl) {
const dbg = gl.getExtension('WEBGL_debug_renderer_info');
renderer = dbg
? String(gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL))
: String(gl.getParameter(gl.RENDERER));
gl.getExtension('WEBGL_lose_context')?.loseContext();
}
} catch {
renderer = null;
}
const nav = navigator as Navigator & { deviceMemory?: number };
const w = window as unknown as { TelegramWebviewProxy?: unknown };
return {
userAgent: navigator.userAgent,
hardwareConcurrency: navigator.hardwareConcurrency ?? null,
deviceMemoryGb: nav.deviceMemory ?? null,
webglRenderer: renderer,
hasTelegramProxy: typeof w !== 'undefined' && w.TelegramWebviewProxy != null,
};
}
/** First-FPS watchdog threshold: below this sustained FPS mobile drops to SVG. */
export const MOBILE_MIN_FPS = 24;
/** Watchdog sampling window (ms) after 3D mount. */
export const MOBILE_FPS_SAMPLE_MS = 6000;

View File

@@ -90,6 +90,30 @@ function applyJitter(pos: Vec3, rotY: number, jitter?: PoseInput['jitter']): { p
};
}
/**
* Stage 2 — case time (ms within case) at which the item is handed from
* kinematic authority to rigid-body physics:
* - B: fraction 0.35 of the B travel span (end of b_transfer spur, belt edge);
* - C/D: fraction 0.12 of the C/D routing span — item fully past the belt
* edge and ON the gravity chute (tall items must clear the belt slab);
* - fault cases: null (no physics handoff — domain fault pose is truth).
*/
export function getDropHandoffTimeMs(
targetCategory: Category | null,
faultType?: FaultType,
): number | null {
if (faultType) return null;
const category: 'B' | 'C' | 'D' = (targetCategory as 'B' | 'C' | 'D') || 'B';
const { starts } = phaseStartsFrom(CASE_PHASES);
const routingStart = starts['routing'];
if (category === 'B') {
const exitStart = starts['exit'];
return routingStart + 0.35 * (exitStart - routingStart);
}
const clearStart = starts['clear_gap'];
return routingStart + 0.12 * (clearStart - routingStart);
}
/** Jam / E-stop motion: freeze at junction, then recover (no settle into bin). */
function getFaultPose(input: PoseInput): PhysicalItemPose {
const { dimensionsMm, elapsedMs, faultType, jitter } = input;

View File

@@ -0,0 +1,160 @@
/**
* Stage 2 §18 — reproducible physics drop validation (headless Rapier).
*
* Runs the SAME world the runtime uses (physicsWorldLayout colliders,
* visualPhysicsProfiles, identical handoff pose/velocities) and verifies:
* deterministic, physically plausible, domain-receiver-correct drops.
*/
import { describe, it, expect, beforeAll } from 'vitest';
import RAPIER from '@dimforge/rapier3d-compat';
import { initRapier, simulateDrop, type DropSimResult } from './physicsDropSim';
import { getStaticColliders } from './physicsWorldLayout';
import { getDropHandoffTimeMs } from './physicalItemMotion';
import { receiverContains } from './receiverVolumes';
import { classifyItem } from './classifier';
import { resolveItem } from '../data/resolveItem';
beforeAll(async () => {
await initRapier();
});
function repeatDrops(sku: string, zone: 'B' | 'C' | 'D', n: number): DropSimResult[] {
return Array.from({ length: n }, () => simulateDrop(sku, zone));
}
function expectDeterministic(results: DropSimResult[]) {
const first = results[0];
for (const r of results) {
for (let i = 0; i < 3; i += 1) {
expect(Math.abs(r.finalPosition[i] - first.finalPosition[i])).toBeLessThan(1e-9);
}
}
}
function expectPhysicallyPlausible(r: DropSimResult) {
// Ballistic flight actually happened (not teleport): real speed, real fall time
expect(r.maxSpeedMps).toBeGreaterThan(0.8);
expect(r.stepsSimulated).toBeGreaterThan(30); // > 0.5 s of simulated fall
// Never passed through the belt/floor (translation-based clearance guard)
expect(r.minClearanceM).toBeGreaterThan(-0.03);
}
describe('Stage 2 §18.1–4 — route correctness, 10/10 per route', () => {
it('box route B — 10 repeats all in B, deterministic', () => {
const results = repeatDrops('SKU-001', 'B', 10);
for (const r of results) {
expect(r.insideExpectedReceiver).toBe(true);
expectPhysicallyPlausible(r);
}
expectDeterministic(results);
});
it('oversized carton route C — 10/10 in C', () => {
const results = repeatDrops('SKU-004', 'C', 10);
for (const r of results) {
expect(r.insideExpectedReceiver).toBe(true);
expectPhysicallyPlausible(r);
}
expectDeterministic(results);
});
it('plate route D — 10/10 in D, no floor penetration', () => {
const results = repeatDrops('SKU-006', 'D', 10);
for (const r of results) {
expect(r.insideExpectedReceiver).toBe(true);
expectPhysicallyPlausible(r);
}
expectDeterministic(results);
});
it('bottle route D — 10/10 in D, rotates during fall', () => {
const results = repeatDrops('SKU-007', 'D', 10);
for (const r of results) {
expect(r.insideExpectedReceiver).toBe(true);
expectPhysicallyPlausible(r);
expect(r.totalRotationRad).toBeGreaterThan(0.5); // distinguishable from box slide
}
expectDeterministic(results);
});
});
describe('Stage 2 §18.5–6 — distinct item behaviors', () => {
it('cylinder rolls (large integrated rotation) but stays in D receiver', () => {
const r = simulateDrop('SKU-008', 'D');
expect(r.insideExpectedReceiver).toBe(true);
expect(r.totalRotationRad).toBeGreaterThan(1.5);
});
it('pen (light, fast-spinning) never tunnels through colliders', () => {
const r = simulateDrop('SKU-009', 'C');
expect(r.insideExpectedReceiver).toBe(true);
expect(r.minClearanceM).toBeGreaterThan(-0.03);
expect(r.totalRotationRad).toBeGreaterThan(0.3);
});
it('pouf (large, heavy) rotates slower than the pen', () => {
const pouf = simulateDrop('SKU-011', 'C');
const pen = simulateDrop('SKU-009', 'C');
expect(pouf.insideExpectedReceiver).toBe(true);
expect(pouf.totalRotationRad).toBeLessThan(pen.totalRotationRad + 2.0);
});
});
describe('Stage 2 §18.7–8 — fault scenarios create no physics impulses', () => {
it('jam case: no physics handoff (item held at junction by domain)', () => {
expect(getDropHandoffTimeMs('C', 'jam')).toBeNull();
});
it('emergency stop case: no physics handoff (no new impulses)', () => {
expect(getDropHandoffTimeMs('B', 'emergency_stop')).toBeNull();
});
});
describe('Stage 2 §18.9–10 — replay reset and no body leaks', () => {
it('fresh worlds give bit-identical drops (replay determinism)', () => {
const a = simulateDrop('SKU-001', 'B');
const b = simulateDrop('SKU-001', 'B');
expect(a.finalPosition).toEqual(b.finalPosition);
expect(a.finalQuaternion).toEqual(b.finalQuaternion);
});
it('10 drop cycles in one world leave no body/collider leak', () => {
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
try {
const baseline = world.bodies.len();
for (let i = 0; i < 10; i += 1) {
const body = world.createRigidBody(
RAPIER.RigidBodyDesc.dynamic().setTranslation(0, 2, 0),
);
world.createCollider(RAPIER.ColliderDesc.cuboid(0.1, 0.1, 0.1), body);
for (let s = 0; s < 30; s += 1) world.step();
world.removeRigidBody(body);
expect(world.bodies.len()).toBe(baseline);
}
// Static layout colliders can be created and freed cleanly too
for (const c of getStaticColliders()) {
world.createCollider(
RAPIER.ColliderDesc.cuboid(...c.halfExtents).setTranslation(...c.position),
);
}
} finally {
world.free();
}
});
});
describe('Stage 2 §18.11–12 — domain result authority', () => {
it('classifier result is unchanged by the physics layer', () => {
expect(classifyItem(resolveItem('SKU-001')).category).toBe('B');
expect(classifyItem(resolveItem('SKU-004')).category).toBe('C');
expect(classifyItem(resolveItem('SKU-006')).category).toBe('D');
});
it('wrong-receiver drops are detected, never silently "fixed"', () => {
// Deliberately reversed pusher direction: bottle aimed away from D.
const bad = simulateDrop('SKU-007', 'D', { linvelScale: -1 });
expect(bad.insideExpectedReceiver).toBe(false);
expect(receiverContains('D', bad.finalPosition)).toBe(false);
});
});

View File

@@ -0,0 +1,184 @@
/**
* Stage 2 §11/§18 — headless drop simulator (Node / vitest, no React).
*
* Reproduces EXACTLY the runtime physics handoff from
* PhysicalPlaybackItemPhysics:
* same static colliders (domain/physicsWorldLayout),
* same per-SKU colliders + density + friction + restitution (visualPhysicsProfiles),
* same handoff pose (getPhysicalItemPose at the same handoff fraction),
* same deterministic initial velocities,
* same settle rule (sleep or controlled timeout) and the same
* domain-receiver verification (receiverVolumes).
*
* Deterministic: fixed timestep 1/60, identical initial conditions, no RNG.
*/
import RAPIER from '@dimforge/rapier3d-compat';
import { getStaticColliders } from './physicsWorldLayout';
import { getVisualPhysicsProfile, type VisualPhysicsProfile } from './visualPhysicsProfiles';
import { getPhysicalItemPose, getDropHandoffTimeMs } from './physicalItemMotion';
import { receiverContains, type ReceiverZone } from './receiverVolumes';
import { resolveItem } from '../data/resolveItem';
import { BELT_TOP_Y } from './physicalLayout';
export const SIM_DT = 1 / 60;
export const SIM_SETTLE_SECONDS = 6.0;
let rapierReady = false;
export async function initRapier(): Promise<void> {
if (!rapierReady) {
await RAPIER.init();
rapierReady = true;
}
}
type Quat = { x: number; y: number; z: number; w: number };
/** three.js Euler XYZ order → quaternion (same math as THREE.Quaternion.setFromEuler). */
function quatFromEuler(x: number, y: number, z: number): Quat {
const c1 = Math.cos(x / 2); const c2 = Math.cos(y / 2); const c3 = Math.cos(z / 2);
const s1 = Math.sin(x / 2); const s2 = Math.sin(y / 2); const s3 = Math.sin(z / 2);
return {
x: s1 * c2 * c3 + c1 * s2 * s3,
y: c1 * s2 * c3 - s1 * c2 * s3,
z: c1 * c2 * s3 + s1 * s2 * c3,
w: c1 * c2 * c3 - s1 * s2 * s3,
};
}
function colliderDescFor(profile: VisualPhysicsProfile): RAPIER.ColliderDesc {
let desc: RAPIER.ColliderDesc;
if (profile.collider === 'cuboid' && profile.cuboidHalfExtents) {
const [hx, hy, hz] = profile.cuboidHalfExtents;
desc = RAPIER.ColliderDesc.cuboid(hx, hy, hz);
desc.setDensity(profile.approximateMassKg / (8 * hx * hy * hz));
} else if (profile.collider === 'capsule' && profile.capsule) {
const [r, hh] = profile.capsule;
desc = RAPIER.ColliderDesc.capsule(hh, r);
desc.setDensity(profile.approximateMassKg / (Math.PI * r * r * (2 * hh + (4 / 3) * r)));
} else {
const [r, hh] = profile.capsule ?? [0.05, 0.1];
desc = RAPIER.ColliderDesc.cylinder(hh, r);
desc.setDensity(profile.approximateMassKg / (Math.PI * r * r * 2 * hh));
}
desc.setFriction(profile.friction);
desc.setRestitution(profile.restitution);
return desc;
}
/** Handoff state mirroring PhysicalPlaybackItemPhysics: same pose, same velocities. */
function handoffState(skuId: string, category: ReceiverZone) {
const profile = getVisualPhysicsProfile(skuId);
const item = resolveItem(skuId);
const handoffMs = getDropHandoffTimeMs(category, undefined);
if (handoffMs == null) throw new Error(`no handoff for ${skuId}/${category}`);
const pose = getPhysicalItemPose({
caseId: `sim_${skuId}_${category}`,
slotIndex: 0,
dimensionsMm: item.dimensionsMm,
targetCategory: category,
elapsedMs: handoffMs,
faultType: undefined,
});
const h = item.dimensionsMm.height / 1000;
const dirZ = category === 'D' ? -1 : 1;
const linvel = category === 'B'
? { x: 1.0, y: 0, z: 0 }
: { x: 0.55, y: 0, z: dirZ * 1.35 * profile.pusherImpulseScale };
const angvel = profile.canRoll
? { x: category === 'B' ? 2.0 : 0.8, y: 0.4, z: 0 }
: { x: 0, y: 0.25, z: 0 };
return { profile, pose, linvel, angvel, itemHeightM: h };
}
export interface DropSimResult {
skuId: string;
expectedZone: ReceiverZone;
finalPosition: [number, number, number];
finalQuaternion: [number, number, number, number];
insideExpectedReceiver: boolean;
settledBySleep: boolean;
settledByTimeout: boolean;
stepsSimulated: number;
/** Lowest belt/floor clearance observed (translation.y - itemHalfHeight). */
minClearanceM: number;
/** Integrated |angvel| over the drop — distinguishes rolling from teleport-like slides. */
totalRotationRad: number;
/** Max speed during flight — proves ballistic motion, not teleportation. */
maxSpeedMps: number;
}
export function simulateDrop(
skuId: string,
category: ReceiverZone,
overrides?: { linvelScale?: number },
): DropSimResult {
const { profile, pose, linvel, angvel, itemHeightM } = handoffState(skuId, category);
const scale = overrides?.linvelScale ?? 1;
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
world.timestep = SIM_DT;
try {
for (const c of getStaticColliders()) {
const q = quatFromEuler(c.rotation[0], c.rotation[1], c.rotation[2]);
const desc = RAPIER.ColliderDesc.cuboid(c.halfExtents[0], c.halfExtents[1], c.halfExtents[2])
.setTranslation(c.position[0], c.position[1], c.position[2])
.setRotation(q)
.setFriction(c.friction);
world.createCollider(desc);
}
const q = quatFromEuler(pose.rotation[0], pose.rotation[1], pose.rotation[2]);
const bodyDesc = RAPIER.RigidBodyDesc.dynamic()
.setTranslation(pose.position[0], pose.position[1], pose.position[2])
.setRotation(q)
.setLinvel(linvel.x * scale, linvel.y * scale, linvel.z * scale)
.setAngvel(angvel)
.setLinearDamping(profile.linearDamping)
.setAngularDamping(profile.angularDamping)
.setCcdEnabled(profile.approximateMassKg < 0.05); // same rule as runtime (pen)
const body = world.createRigidBody(bodyDesc);
world.createCollider(colliderDescFor(profile), body);
const halfH = itemHeightM / 2;
let minClearance = Infinity;
let totalRotation = 0;
let maxSpeed = 0;
let steps = 0;
let slept = false;
const maxSteps = Math.round(SIM_SETTLE_SECONDS / SIM_DT);
for (let i = 0; i < maxSteps; i += 1) {
world.step();
steps += 1;
const t = body.translation();
const v = body.linvel();
const w = body.angvel();
minClearance = Math.min(minClearance, t.y - halfH);
totalRotation += Math.hypot(w.x, w.y, w.z) * SIM_DT;
maxSpeed = Math.max(maxSpeed, Math.hypot(v.x, v.y, v.z));
if (i > 12 && body.isSleeping()) { slept = true; break; }
}
const t = body.translation();
const r = body.rotation();
const p: [number, number, number] = [t.x, t.y, t.z];
return {
skuId,
expectedZone: category,
finalPosition: p,
finalQuaternion: [r.x, r.y, r.z, r.w],
insideExpectedReceiver: receiverContains(category, p),
settledBySleep: slept,
settledByTimeout: !slept,
stepsSimulated: steps,
minClearanceM: minClearance,
totalRotationRad: totalRotation,
maxSpeedMps: maxSpeed,
};
} finally {
world.free();
}
}
/** Drop starting height above the belt (for reporting). */
export { BELT_TOP_Y };

View File

@@ -0,0 +1,128 @@
/**
* Stage 2 — single source of truth for static physics colliders.
*
* Pure data (no React, no Rapier): consumed by
* - SorterStaticColliders (runtime, @react-three/rapier CuboidColliders);
* - physicsDropSim (headless Node validation with rapier3d-compat).
*
* Geometry contract (visual == collider == domain kinematic surface):
* - C/D gravity chutes follow the domain chute line (conveyorNetwork
* CHUTE_C/D_ENTRY -> EXIT): from belt edge y=0.66 down to cage front edge
* y=0.14 — real gravity-chute pitch ~0.39 rad (smooth coated steel,
* friction 0.2), not a gentle ramp where items would stall.
* - Cage C/D entry side is OPEN (real roll cages are 3-sided + open front)
* with a 40mm sill; the chute deposits items through the open front.
* - B drop chute descends from spur end (2.15, 0.70) into the open front of
* the B receiver bin (past its low entry lip).
* Every collider mirrors VISIBLE scene geometry — no fake guides (§11.6).
*/
import { ZONES, B_RECEIVER, ROLL_CAGE, CONVEYOR_WIDTH_M, BELT_TOP_Y, CAGE_FLOOR_Y } from './physicalLayout';
export interface StaticColliderDef {
id: string;
halfExtents: [number, number, number];
position: [number, number, number];
/** Euler XYZ radians. */
rotation: [number, number, number];
friction: number;
}
/**
* C/D chute pitch: line from (z=±0.35, y=0.66) to (z=±1.6, y=0.14).
* tan = 0.52/1.25 ≈ 0.416 > μ_combined for every SKU profile -> items slide.
*/
export const CHUTE_PITCH = Math.atan2(0.52, 1.25); // ≈ 0.394 rad
export const CHUTE_LENGTH = Math.hypot(1.25, 0.52); // ≈ 1.354 m
export const CHUTE_MID_Y = 0.40;
export const CHUTE_MID_Z = 0.975;
/** Chute plate center X (gate exit) and plate half width. */
export const CHUTE_X = ZONES.GATE.x + 0.3;
export const CHUTE_HALF_W = 0.25;
/** B drop chute: line from (2.15, 0.70) to (2.85, 0.13), pitch ≈ 0.684 rad. */
export const B_CHUTE_PITCH = Math.atan2(BELT_TOP_Y - (CAGE_FLOOR_Y + 0.05), 0.7);
function chuteColliders(targetZ: number, label: 'C' | 'D'): StaticColliderDef[] {
const dir = targetZ > 0 ? 1 : -1;
const midZ = dir * CHUTE_MID_Z;
const rot: [number, number, number] = [dir * CHUTE_PITCH, 0, 0];
return [
{
id: `chute-${label}-floor`,
halfExtents: [CHUTE_HALF_W, 0.01, CHUTE_LENGTH / 2],
// top surface: y≈0.655 at belt edge (z 0.35) -> 0.135 at cage edge (z 1.6)
position: [CHUTE_X, CHUTE_MID_Y - 0.015, midZ],
rotation: rot,
friction: 0.2, // smooth coated steel — gravity chute (VISUAL_PHYSICS_ESTIMATE)
},
{
id: `chute-${label}-rail-left`,
halfExtents: [0.01, 0.035, CHUTE_LENGTH / 2],
position: [CHUTE_X - CHUTE_HALF_W - 0.01, CHUTE_MID_Y + 0.03, midZ],
rotation: rot,
friction: 0.3,
},
{
id: `chute-${label}-rail-right`,
halfExtents: [0.01, 0.035, CHUTE_LENGTH / 2],
position: [CHUTE_X + CHUTE_HALF_W + 0.01, CHUTE_MID_Y + 0.03, midZ],
rotation: rot,
friction: 0.3,
},
];
}
function receiverColliders(): StaticColliderDef[] {
const bin = B_RECEIVER;
const hx = bin.width / 2;
const hz = bin.depth / 2;
const cage = ROLL_CAGE;
const chx = cage.width / 2;
const chz = cage.depth / 2;
const defs: StaticColliderDef[] = [
// B bin: floor + 3 walls + low entry lip (mirrors BReceiverBin visuals —
// open front on the chute side, no hidden wall)
{ id: 'b-bin-floor', halfExtents: [hx, 0.02, hz], position: [bin.centerX, bin.floorY - 0.02, bin.centerZ], rotation: [0, 0, 0], friction: 0.8 },
{ id: 'b-bin-wall-z+', halfExtents: [hx, bin.wallHeight / 2, 0.015], position: [bin.centerX, bin.floorY + bin.wallHeight / 2, bin.centerZ + hz], rotation: [0, 0, 0], friction: 0.6 },
{ id: 'b-bin-wall-z-', halfExtents: [hx, bin.wallHeight / 2, 0.015], position: [bin.centerX, bin.floorY + bin.wallHeight / 2, bin.centerZ - hz], rotation: [0, 0, 0], friction: 0.6 },
{ id: 'b-bin-wall-x+', halfExtents: [0.015, bin.wallHeight / 2, hz], position: [bin.centerX + hx, bin.floorY + bin.wallHeight / 2, bin.centerZ], rotation: [0, 0, 0], friction: 0.6 },
{ id: 'b-bin-entry-lip', halfExtents: [0.015, 0.04, hz], position: [bin.centerX - hx, bin.floorY + 0.04, bin.centerZ], rotation: [0, 0, 0], friction: 0.6 },
];
const entryX = bin.centerX - hx + 0.1;
const chuteLen = Math.hypot(entryX - bin.transferEndX, BELT_TOP_Y - bin.floorY);
defs.push({
id: 'b-drop-chute',
halfExtents: [chuteLen / 2, 0.008, (CONVEYOR_WIDTH_M - 0.08) / 2],
// top surface runs (2.15, 0.696) -> (2.85, 0.126): flush with spur end
position: [(bin.transferEndX + entryX) / 2, 0.395, bin.centerZ],
rotation: [0, 0, -B_CHUTE_PITCH], // descends toward +X (into the bin)
friction: 0.25,
});
for (const [label, zone] of [['C', ZONES.C], ['D', ZONES.D]] as const) {
const entrySide = label === 'C' ? -1 : 1; // open front faces the conveyor
defs.push(
{ id: `cage-${label}-floor`, halfExtents: [chx, 0.04, chz], position: [zone.x, 0.04, zone.z], rotation: [0, 0, 0], friction: 0.8 },
{ id: `cage-${label}-wall-x-`, halfExtents: [0.02, cage.height / 2, chz], position: [zone.x - chx, cage.height / 2, zone.z], rotation: [0, 0, 0], friction: 0.5 },
{ id: `cage-${label}-wall-x+`, halfExtents: [0.02, cage.height / 2, chz], position: [zone.x + chx, cage.height / 2, zone.z], rotation: [0, 0, 0], friction: 0.5 },
// closed back wall (far side from the conveyor)
{ id: `cage-${label}-wall-back`, halfExtents: [chx, cage.height / 2, 0.02], position: [zone.x, cage.height / 2, zone.z - entrySide * chz], rotation: [0, 0, 0], friction: 0.5 },
// 40mm sill on the open entry side (mirrors RollCageMesh openSide sill)
{ id: `cage-${label}-entry-sill`, halfExtents: [chx, 0.02, 0.015], position: [zone.x, CAGE_FLOOR_Y + 0.02, zone.z + entrySide * chz], rotation: [0, 0, 0], friction: 0.5 },
);
}
return defs;
}
export function getStaticColliders(): StaticColliderDef[] {
return [
{ id: 'world-floor', halfExtents: [8, 0.05, 6], position: [0, -0.05, 0], rotation: [0, 0, 0], friction: 0.8 },
// Belt safety slab — items never pass through the belt surface.
// Ends at the B spur end (2.15): beyond it the B drop chute takes over.
{ id: 'belt-slab', halfExtents: [(2.15 + 4.2) / 2, 0.012, CONVEYOR_WIDTH_M / 2], position: [(2.15 - 4.2) / 2, BELT_TOP_Y - 0.014, 0], rotation: [0, 0, 0], friction: 0.7 },
// B transfer spur
{ id: 'b-spur', halfExtents: [0.325, 0.02, (CONVEYOR_WIDTH_M - 0.06) / 2], position: [1.825, BELT_TOP_Y - 0.02, 0], rotation: [0, 0, 0], friction: 0.4 },
...chuteColliders(ZONES.C.z, 'C'),
...chuteColliders(ZONES.D.z, 'D'),
...receiverColliders(),
];
}

View File

@@ -51,7 +51,7 @@ const PRESETS: Record<QualityMode, QualitySettings> = {
mode: 'demo',
dprMax: 1.5,
antialias: true,
shadows: false, // contact shadows via mesh only — stable demo FPS
shadows: true, // Stage 2: PCFSoft contact shadows in the default premium look
maxVisibleItems: 6,
effectsEnabled: false,
rollerDetail: 'full',

View File

@@ -0,0 +1,24 @@
/**
* Stage 2 — domain-decided receiver volumes (single source of truth).
*
* Used by the runtime physics handoff (PhysicalPlaybackItemPhysics) and the
* headless validation sim (physicsDropSim) to verify that a settled body
* landed in the receiver the DOMAIN classifier chose (Stage 2 §11.7).
* The classifier result is never influenced by physics — this only verifies.
*/
import { ZONES, B_RECEIVER, ROLL_CAGE } from './physicalLayout';
export type ReceiverZone = 'B' | 'C' | 'D';
export function receiverContains(zone: ReceiverZone, p: [number, number, number]): boolean {
const [x, y, z] = p;
if (y < 0.01 || y > 1.2) return false;
if (zone === 'B') {
const b = B_RECEIVER;
return Math.abs(x - b.centerX) <= b.width / 2 + 0.05 && Math.abs(z - b.centerZ) <= b.depth / 2 + 0.05;
}
const cage = zone === 'C' ? ZONES.C : ZONES.D;
return Math.abs(x - cage.x) <= ROLL_CAGE.width / 2 + 0.05
&& Math.abs(z - cage.z) <= ROLL_CAGE.depth / 2 + 0.05;
}

View File

@@ -0,0 +1,99 @@
/**
* Stage 2 — sorterVisualState adapter.
*
* Single mapping layer: domain playback state -> visual mechanism commands +
* physics handoff. Business logic (classifier, playlist, phase machine) stays
* the only source of truth; this adapter NEVER decides categories or routes —
* it projects domain decisions into beltVelocity / gate / pusher / physics
* authority for the 3D layer (mirrors the playbackToSimulation pattern).
*/
import type { Category } from './types';
import type { CasePhase, ContinuousPlaybackState } from './continuousPlayback';
import { CONVEYOR_SPEED_MPS } from './physicalLayout';
export type GatePhase = 'closed' | 'hold-open';
export type ScanState = 'idle' | 'detecting' | 'measuring';
export type AlarmState = 'none' | 'fault' | 'estop';
export type ItemAuthority = 'domain' | 'physics';
export interface SorterVisualState {
/** Belt linear velocity m/s (0 when paused/fault/e-stop). */
beltVelocityMps: number;
/** Roller angular velocity rad/s = v / r (r = 25 mm CAD roller). */
rollerOmegaRadPerSec: number;
/** Stop-gate / metering barrier state (CAD barriers on the junction). */
gatePhase: GatePhase;
gateOpen: boolean;
/** Pusher/diverter servo command. */
pusher: { active: boolean; category: Category | null };
/** Measurement rig state for camera/laser visualization. */
scanState: ScanState;
alarmState: AlarmState;
/** Domain-decided target (never physics-decided). */
targetZone: Category | null;
expectedLandingZone: 'B' | 'C' | 'D' | null;
/**
* Item authority: 'domain' while the item pose is kinematic
* (getPhysicalItemPose), 'physics' after the drop handoff
* (routing phase, on chute surface).
*/
itemAuthority: ItemAuthority;
/**
* Case-time fraction within the routing phase at which authority
* transfers to physics (pusher contact / belt edge). B: 0.35 (end of
* b_transfer spur); C/D: 0.06 (pusher contact at gate exit).
*/
dropHandoffFraction: number;
}
/** Phases in which the post-inspection metering gate is open (item released from inspection). */
const GATE_OPEN_PHASES: ReadonlySet<CasePhase> = new Set([
'measurement', 'classification', 'command_sent', 'routing', 'exit', 'clear_gap', 'recover',
]);
const FAULT_PHASES: ReadonlySet<CasePhase> = new Set(['fault_hold', 'emergency_hold']);
export function deriveSorterVisualState(playback: ContinuousPlaybackState): SorterVisualState {
const phase = playback.currentPhase;
const category = playback.targetCategory;
const running = playback.status === 'running';
const fault = phase === 'fault_hold';
const estop = phase === 'emergency_hold';
const halted = !running || fault || estop || playback.currentCase.faultType != null && FAULT_PHASES.has(phase);
const beltVelocity = halted ? 0 : CONVEYOR_SPEED_MPS * playback.speed;
const gateOpen = !fault && !estop && GATE_OPEN_PHASES.has(phase);
let itemAuthority: ItemAuthority = 'domain';
let dropHandoffFraction = 1.1; // unreachable by default (no physics handoff)
if (!playback.currentCase.faultType && phase === 'routing' && category) {
dropHandoffFraction = category === 'B' ? 0.35 : 0.12;
itemAuthority = 'physics';
}
return {
beltVelocityMps: beltVelocity,
rollerOmegaRadPerSec: beltVelocity / 0.025,
gatePhase: gateOpen ? 'hold-open' : 'closed',
gateOpen,
pusher: {
active: !fault && !estop && (phase === 'routing' || phase === 'exit') && (category === 'C' || category === 'D'),
category: category === 'C' || category === 'D' ? category : null,
},
scanState: phase === 'detection' ? 'detecting' : phase === 'measurement' ? 'measuring' : 'idle',
alarmState: estop ? 'estop' : fault ? 'fault' : 'none',
targetZone: category,
expectedLandingZone: category,
itemAuthority,
dropHandoffFraction,
};
}
/** Case-time (ms) at which the current item is handed to physics, if any. */
export function dropHandoffCaseTimeMs(playback: ContinuousPlaybackState, phaseStarts: Record<string, number>, routingDurationMs: number): number | null {
const vs = deriveSorterVisualState(playback);
if (vs.dropHandoffFraction > 1) return null;
const routingStart = phaseStarts['routing'];
if (routingStart == null) return null;
return routingStart + vs.dropHandoffFraction * routingDurationMs;
}

View File

@@ -0,0 +1,98 @@
/**
* Stage 2 — visual physics profiles per SKU.
*
* ALL values are VISUAL_PHYSICS_ESTIMATE: they are tuned for physically
* plausible drop/roll/rest behavior in the digital twin. They are NOT
* certified masses or dynamics data from the manufacturer.
*/
export type ColliderKind = 'cuboid' | 'capsule' | 'cylinder';
export interface VisualPhysicsProfile {
sku: string;
/** VISUAL_PHYSICS_ESTIMATE kg */
approximateMassKg: number;
friction: number;
restitution: number;
linearDamping: number;
angularDamping: number;
collider: ColliderKind;
/**
* Collider half-extents [x,y,z] for cuboid; [radius, halfHeight] for
* capsule/cylinder. Derived from validated Stage 1 dimensions (m).
*/
cuboidHalfExtents?: [number, number, number];
capsule?: [number, number];
/** Vertical center-of-mass offset factor (-0.5..0.5 of height). */
centerOfMassOffsetY: number;
/** Multiplier on pusher impulse for C/D routing (light items fly further). */
pusherImpulseScale: number;
/** True for items expected to roll (cylinder/bottle). */
canRoll: boolean;
}
const PROFILES: Record<string, VisualPhysicsProfile> = {
'SKU-001': { // cardboard box 300x200x200
sku: 'SKU-001', approximateMassKg: 0.8, friction: 0.55, restitution: 0.15,
linearDamping: 0.2, angularDamping: 0.4, collider: 'cuboid',
cuboidHalfExtents: [0.15, 0.1, 0.1], centerOfMassOffsetY: 0,
pusherImpulseScale: 1.0, canRoll: false,
},
'SKU-002': { // lunchbox 280x180x120 — rigid box behavior
sku: 'SKU-002', approximateMassKg: 0.6, friction: 0.5, restitution: 0.2,
linearDamping: 0.2, angularDamping: 0.4, collider: 'cuboid',
cuboidHalfExtents: [0.14, 0.06, 0.09], centerOfMassOffsetY: 0,
pusherImpulseScale: 1.05, canRoll: false,
},
'SKU-004': { // oversized carton 900x200x500
sku: 'SKU-004', approximateMassKg: 3.2, friction: 0.6, restitution: 0.1,
linearDamping: 0.25, angularDamping: 0.5, collider: 'cuboid',
cuboidHalfExtents: [0.45, 0.25, 0.1], centerOfMassOffsetY: -0.05,
pusherImpulseScale: 0.75, canRoll: false,
},
'SKU-006': { // ceramic plate d260 h30 — thin cuboid
sku: 'SKU-006', approximateMassKg: 0.55, friction: 0.45, restitution: 0.25,
linearDamping: 0.15, angularDamping: 0.25, collider: 'cuboid',
cuboidHalfExtents: [0.13, 0.015, 0.13], centerOfMassOffsetY: 0,
pusherImpulseScale: 1.1, canRoll: false,
},
'SKU-007': { // bottle d90 h280 — capsule, rolls/spins, falls to side
sku: 'SKU-007', approximateMassKg: 0.45, friction: 0.5, restitution: 0.35,
linearDamping: 0.1, angularDamping: 0.15, collider: 'capsule',
capsule: [0.045, 0.095], centerOfMassOffsetY: -0.04,
pusherImpulseScale: 1.15, canRoll: true,
},
'SKU-008': { // cylinder d120 h200 — rolls
sku: 'SKU-008', approximateMassKg: 1.4, friction: 0.45, restitution: 0.3,
linearDamping: 0.08, angularDamping: 0.12, collider: 'cylinder',
capsule: [0.06, 0.1], centerOfMassOffsetY: 0,
pusherImpulseScale: 0.9, canRoll: true,
},
'SKU-009': { // pen 12x145x12 — light, spins fast
sku: 'SKU-009', approximateMassKg: 0.02, friction: 0.4, restitution: 0.25,
linearDamping: 0.1, angularDamping: 0.8, collider: 'capsule',
capsule: [0.006, 0.066], centerOfMassOffsetY: 0,
pusherImpulseScale: 1.3, canRoll: true,
},
'SKU-011': { // pouf d450 h400 — big, slow rotation
sku: 'SKU-011', approximateMassKg: 2.8, friction: 0.6, restitution: 0.2,
linearDamping: 0.4, angularDamping: 0.6, collider: 'cylinder',
capsule: [0.225, 0.175], centerOfMassOffsetY: 0,
pusherImpulseScale: 0.85, canRoll: false,
},
};
export const DEFAULT_PHYSICS_PROFILE: VisualPhysicsProfile = {
sku: 'default', approximateMassKg: 1.0, friction: 0.8, restitution: 0.2,
linearDamping: 0.2, angularDamping: 0.4, collider: 'cuboid',
cuboidHalfExtents: [0.15, 0.1, 0.1], centerOfMassOffsetY: 0,
pusherImpulseScale: 1.0, canRoll: false,
};
export function getVisualPhysicsProfile(itemId: string): VisualPhysicsProfile {
return PROFILES[itemId] ?? DEFAULT_PHYSICS_PROFILE;
}
export function allVisualPhysicsProfiles(): VisualPhysicsProfile[] {
return Object.values(PROFILES);
}

View File

@@ -10,6 +10,7 @@ import { getViewportType, type ViewportType } from '../domain/cinematicCamera';
import { detectQualityMode } from '../domain/qualityMode';
import { parseStage0Config, collectDeviceSignals, choosePrototypeQuality } from '../domain/stage0';
import { parseStage1Config } from '../domain/stage1';
import { decideMobileTier, collectMobileSignals, MOBILE_MIN_FPS, MOBILE_FPS_SAMPLE_MS, type MobileTier } from '../domain/mobilePolicy';
import { playbackToSimulation } from '../domain/playbackAdapter';
import SorterScene from '../components/SorterScene';
import { resolveItem } from '../data/resolveItem';
@@ -123,11 +124,46 @@ export default function MainPage({
[],
);
// Stage 2 §13.6: technical HUD (speed, case dots, hotkeys, command rows)
// is opt-in via ?debug=1; the default HUD stays presentation-clean.
const debugMode = useMemo(() => {
if (typeof window === 'undefined') return false;
return new URLSearchParams(window.location.search).get('debug') === '1';
}, []);
// Stage 2 §16: mobile tier from capability signals (not viewport width).
const mobileTier: MobileTier = useMemo(() => decideMobileTier(collectMobileSignals()), []);
// First-FPS watchdog: sustained low FPS on mobile drops to SVG permanently.
const [mobileFpsDead, setMobileFpsDead] = useState(false);
useEffect(() => {
if (mobileTier !== 'mobile-low') return;
const started = performance.now();
let frames = 0;
let raf = 0;
const tick = () => {
frames += 1;
const elapsed = performance.now() - started;
if (elapsed >= MOBILE_FPS_SAMPLE_MS) {
const fps = (frames / elapsed) * 1000;
if (fps < MOBILE_MIN_FPS) setMobileFpsDead(true);
return;
}
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [mobileTier]);
// NOTE: contextLost must NOT flip show3D — the canvas stays mounted (hidden)
// during the recovery window so `webglcontextrestored` can actually arrive.
// Default route: 3D only on >= 640px with WebGL (unchanged heuristic).
// Prototype mode: 3D forced on any width so mobile GPU profiles can be measured.
const show3D = stage0.enabled ? webgl : prefer3DByDefault(width, webgl);
// Mobile policy: svg tier never mounts 3D; mobile-low mounts 3D at low quality.
const show3D = stage0.enabled
? webgl
: mobileTier === 'svg'
? false
: mobileTier === 'mobile-low'
? webgl && !mobileFpsDead
: prefer3DByDefault(width, webgl);
// WebGL context recovery: on loss the canvas stays mounted (hidden) so a
// real `webglcontextrestored` can arrive; exactly one safe retry is allowed,
@@ -174,7 +210,11 @@ export default function MainPage({
const deviceSignals = useMemo(() => collectDeviceSignals(), []);
const qualityMode =
qualityOverride ??
(stage0.enabled ? choosePrototypeQuality(width, deviceSignals) : detectQualityMode(width));
(stage0.enabled
? choosePrototypeQuality(width, deviceSignals)
: mobileTier === 'mobile-low'
? 'low' // Mobile Low: DPR<=1.25, no shadows/post, 30 FPS target
: detectQualityMode(width));
const simplified = width < 900 || qualityMode === 'low';
const viewportType: ViewportType = getViewportType(width);
@@ -281,10 +321,12 @@ export default function MainPage({
{category ?? '—'}
</span>
</div>
<div className="hud-row">
<span className="hud-label">Command</span>
<span className="hud-value" data-testid="demo-command">{command}</span>
</div>
{debugMode && (
<div className="hud-row">
<span className="hud-label">Command</span>
<span className="hud-value" data-testid="demo-command">{command}</span>
</div>
)}
{playback.classification && (
<div className="hud-row hud-row-small" data-testid="demo-proof">
<span className="hud-value proof-text">
@@ -293,25 +335,29 @@ export default function MainPage({
</span>
</div>
)}
<div className="hud-row">
<span className="hud-label">Speed</span>
<span className="hud-value">{playback.speed.toFixed(1)}× · 1.0 m/s</span>
</div>
{debugMode && (
<>
<div className="hud-row">
<span className="hud-label">Speed</span>
<span className="hud-value">{playback.speed.toFixed(1)}× · 1.0 m/s</span>
</div>
<div className="hud-divider" />
<div className="hud-row">
<span className="hud-label">Case</span>
<span className="hud-value" data-testid="demo-case-label">
{playback.currentCaseIndex + 1}/{PLAYLIST_LENGTH}
</span>
</div>
<div className="hud-row hud-row-small">
<span className="hud-value">{currentCase.description}</span>
</div>
</>
)}
{playback.warning && (
<div className="hud-row hud-warning">
<span className="hud-value warning-text">⚠ {playback.warning}</span>
</div>
)}
<div className="hud-divider" />
<div className="hud-row">
<span className="hud-label">Case</span>
<span className="hud-value" data-testid="demo-case-label">
{playback.currentCaseIndex + 1}/{PLAYLIST_LENGTH}
</span>
</div>
<div className="hud-row hud-row-small">
<span className="hud-value">{currentCase.description}</span>
</div>
</div>
)}
@@ -322,7 +368,7 @@ export default function MainPage({
/>
</div>
{!presentationMode && (
{!presentationMode && debugMode && (
<div className="main-progress">
{DEMO_PLAYLIST.map((c, idx) => (
<button
@@ -389,7 +435,7 @@ export default function MainPage({
</button>
)}
{!presentationMode && (
{!presentationMode && debugMode && (
<div className="speed-controls" role="group" aria-label="Playback speed">
{SPEEDS.map((s) => (
<button
@@ -458,7 +504,9 @@ export default function MainPage({
<Link to="/details" className="details-link">
Details →
</Link>
<div className="hotkey-hint">Space play · N/B seek · 1–0 jump · R reset · P present · E log · F fullscreen</div>
{debugMode && (
<div className="hotkey-hint">Space play · N/B seek · 1–0 jump · R reset · P present · E log · F fullscreen</div>
)}
</>
)}

View File

@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Assemble conveyor GLBs from FreeCAD-exported meshes.
Pipeline: manifest+STL (CAD-global mm, Z-up) -> mechanism grouping ->
dedupe (translation-invariant) -> decimation budget -> material slots ->
axis bake (-x, z, y+250)/1000 (proper rotation, det=+1) -> GLB.
Outputs:
public/models/sorter/conveyor-source.glb (full 0.25mm tessellation)
public/models/sorter/conveyor-web.glb (optimized, <=250k tris target)
tools/stage2-cad/out/glb-stats.json
"""
import json
import os
import numpy as np
import trimesh
OUT = "/home/coder/arhipovdan/app/tools/stage2-cad/out"
DEST = "/home/coder/arhipovdan/app/public/models/sorter"
os.makedirs(DEST, exist_ok=True)
with open(os.path.join(OUT, "manifest.json"), encoding="utf-8") as fh:
manifest = json.load(fh)
# --- mechanism grouping (documented in docs/stage2_real_sorter/mechanism-map.md) ---
def group_of(entry):
label = entry["label"]
name = entry["name"]
if "Лента" in label:
return "conveyor-belt"
if "Барьер" in label:
return "stop-gate"
if "Крепление камеры" in label or "камера" in label or "Крепление камеры" in label:
return "inspection-frame"
if "Серво" in label or "серво" in label or "Держатель" in label:
return "pusher-servo"
if "Роликовый узел" in label:
return "rollers"
if "Ролик" in label or "Вал" in label or "Подшибник" in label:
return "rollers"
if "NEMA" in label or "Шкив" in label or "ремень" in label or "двигател" in label:
return "motor-and-drive"
if "Кронштейн" in label and name in ("Body009", "Link"):
return "rollers" # roller support bracket (roller assembly child)
return "static-frame"
MATERIAL_SLOT = {
"static-frame": "painted-metal",
"conveyor-belt": "rubber-belt",
"rollers": "brushed-metal",
"motor-and-drive": "dark-mechanical",
"inspection-frame": "painted-metal",
"stop-gate": "safety-yellow",
"pusher-servo": "dark-mechanical",
}
# Per-group decimation target for the web GLB (triangles per UNIQUE mesh).
DECIMATE_TARGET = {
"conveyor-belt": 1200,
"rollers": 9000,
"motor-and-drive": 4000,
"static-frame": 2500,
"stop-gate": 7000,
"pusher-servo": 9000,
"inspection-frame": 12000,
}
def bake(mesh):
"""CAD mm Z-up -> GLB meters Y-up. (x,y,z) -> (-x, z, y+250)/1000, det=+1."""
v = mesh.vertices
x = -v[:, 0] / 1000.0
y = v[:, 2] / 1000.0
z = (v[:, 1] + 250.0) / 1000.0
out = trimesh.Trimesh(vertices=np.column_stack([x, y, z]), faces=mesh.faces.copy(), process=False)
out.fix_normals()
return out
def dedupe_key(mesh):
"""Translation-invariant key: rounded vertices relative to bbox min + face count."""
rel = mesh.vertices - mesh.vertices.min(axis=0)
return (len(mesh.faces), hash(np.round(rel, 5).tobytes()))
def decimate(mesh, target):
if len(mesh.faces) <= target:
return mesh
try:
import fast_simplification
pts = np.asarray(mesh.vertices, dtype=np.float32)
fcs = np.asarray(mesh.faces, dtype=np.int64)
r_pts, r_fcs = fast_simplification.simplify(pts, fcs, target_reduction=1.0 - target / len(mesh.faces))
out = trimesh.Trimesh(vertices=r_pts, faces=r_fcs, process=False)
out.fix_normals()
return out
except Exception as exc: # noqa: BLE001
print(f" decimation failed ({exc}); keeping {len(mesh.faces)} tris")
return mesh
def build(decimating, label):
scene = trimesh.Scene()
cache = {}
stats = []
for entry in manifest:
grp = group_of(entry)
mesh = trimesh.load(os.path.join(OUT, entry["stl"]), process=False)
mesh = bake(mesh)
before = len(mesh.faces)
key = dedupe_key(mesh)
if key in cache:
geom_name, offset = cache[key]
node_transform = np.eye(4)
node_transform[:3, 3] = mesh.vertices.min(axis=0) - offset
stats.append({"node": f"{grp}/{entry['label']}", "group": grp, "tris": 0, "instanced": True})
scene.add_geometry(
scene.geometry[geom_name],
node_name=f"{grp}/{entry['label']}",
geom_name=geom_name,
transform=node_transform,
)
continue
if decimating:
mesh = decimate(mesh, DECIMATE_TARGET.get(grp, 4000))
geom_name = f"g_{grp}_{entry['name']}"
cache[key] = (geom_name, mesh.vertices.min(axis=0))
mesh.metadata["material_slot"] = MATERIAL_SLOT.get(grp, "painted-metal")
scene.add_geometry(mesh, node_name=f"{grp}/{entry['label']}", geom_name=geom_name)
stats.append({
"node": f"{grp}/{entry['label']}",
"group": grp,
"tris": len(mesh.faces),
"trisBeforeDecimation": before,
"instanced": False,
"materialSlot": MATERIAL_SLOT.get(grp),
"cadSource": entry["stl"],
})
path = os.path.join(DEST, f"conveyor-{label}.glb")
scene.export(path)
total = sum(s["tris"] for s in stats if not s["instanced"])
print(f"{label}: nodes={len(stats)} unique-tris={total} file={os.path.getsize(path)} bytes")
return path, stats, total
src_path, src_stats, src_tris = build(decimating=False, label="source")
web_path, web_stats, web_tris = build(decimating=True, label="web")
with open(os.path.join(OUT, "glb-stats.json"), "w", encoding="utf-8") as fh:
json.dump({
"source": {"path": src_path, "uniqueTriangles": src_tris, "bytes": os.path.getsize(src_path), "nodes": src_stats},
"web": {"path": web_path, "uniqueTriangles": web_tris, "bytes": os.path.getsize(web_path), "nodes": web_stats},
}, fh, ensure_ascii=False, indent=1)
print("done")

View File

@@ -0,0 +1,116 @@
# FreeCAD 1.0.2 headless export macro — conveer.FCStd -> per-object STL + manifest JSON.
# Run: /tmp/squashfs-root/usr/bin/freecadcmd tools/stage2-cad/freecad-export.py
#
# Toolchain: FreeCAD 1.0.2 (official AppImage, github.com/FreeCAD/FreeCAD releases).
# The document is opened by FreeCAD itself, so App::Link / group semantics and
# global placements are resolved by the CAD kernel, not by reimplemented logic.
import json
import os
import struct
import sys
import FreeCAD
import Mesh
import Part
DOC_PATH = "/home/coder/arhipovdan/app/3d_models/conveer.FCStd"
OUT_DIR = "/home/coder/arhipovdan/app/tools/stage2-cad/out"
os.makedirs(OUT_DIR, exist_ok=True)
doc = FreeCAD.openDocument(DOC_PATH)
inventory = []
renderables = []
for obj in doc.Objects:
entry = {
"name": obj.Name,
"label": obj.Label,
"typeId": obj.TypeId,
"visibility": getattr(obj, "Visibility", None),
"hasShape": hasattr(obj, "Shape"),
}
if hasattr(obj, "Shape"):
try:
bb = obj.Shape.BoundBox
entry["shapeBboxMm"] = [bb.XMin, bb.YMin, bb.ZMin, bb.XMax, bb.YMax, bb.ZMax]
entry["shapeIsNull"] = obj.Shape.isNull()
except Exception as exc: # noqa: BLE001
entry["shapeError"] = str(exc)
if obj.TypeId == "App::Link":
try:
linked = obj.LinkedObject
entry["linkedObject"] = getattr(linked, "Name", str(linked))
except Exception as exc: # noqa: BLE001
entry["linkedObject"] = f"ERROR:{exc}"
inventory.append(entry)
# Renderable set = what the viewport shows: visible top-level geometry objects.
# PartDesign inner features (Pad/Pocket/Fillet/...) are not rendered separately —
# only their Body tip is. Groups and origins carry no geometry.
RENDERABLE_TYPES = {"PartDesign::Body", "App::Link", "Part::Feature"}
for obj in doc.Objects:
if obj.TypeId not in RENDERABLE_TYPES:
continue
if not getattr(obj, "Visibility", False):
continue
if not hasattr(obj, "Shape"):
continue
try:
shape = obj.Shape
if shape.isNull():
continue
bb = shape.BoundBox
if bb.XLength == 0 and bb.YLength == 0 and bb.ZLength == 0:
continue
except Exception:
continue
renderables.append(obj)
print(f"objects={len(inventory)} renderables={len(renderables)}")
manifest = []
for obj in renderables:
shape = obj.Shape
# Verified on this document (probe.py): obj.Shape is already in
# document-global coordinates for both PartDesign::Body and App::Link —
# placement is baked into the stored shape and must NOT be re-applied.
# Tessellate as-is; no additional transform.
try:
verts, facets = shape.tessellate(0.25) # 0.25 mm linear deflection
except Exception as exc: # noqa: BLE001
print(f"SKIP tessellate failed: {obj.Name}: {exc}")
continue
if not facets:
continue
stl_name = f"{obj.Name}.stl"
stl_path = os.path.join(OUT_DIR, stl_name)
# binary STL, normals left zero (recomputed downstream)
with open(stl_path, "wb") as fh:
fh.write(b"\0" * 80)
fh.write(struct.pack("<I", len(facets)))
for tri in facets:
fh.write(struct.pack("<3f", 0.0, 0.0, 0.0))
for idx in tri:
v = verts[idx]
fh.write(struct.pack("<3f", float(v.x), float(v.y), float(v.z)))
fh.write(struct.pack("<H", 0))
bb = shape.BoundBox
manifest.append({
"name": obj.Name,
"label": obj.Label,
"typeId": obj.TypeId,
"stl": stl_name,
"triangles": len(facets),
"globalBboxMm": [bb.XMin, bb.YMin, bb.ZMin, bb.XMax, bb.YMax, bb.ZMax],
"linkedObject": next((e.get("linkedObject") for e in inventory if e["name"] == obj.Name), None),
})
safe_label = obj.Label.encode("ascii", "replace").decode("ascii")
print(f" {obj.Name:14s} {safe_label[:28]:28.28s} tris={len(facets):6d} bbox=({bb.XMin:.0f},{bb.YMin:.0f},{bb.ZMin:.0f})..({bb.XMax:.0f},{bb.YMax:.0f},{bb.ZMax:.0f})")
with open(os.path.join(OUT_DIR, "manifest.json"), "w", encoding="utf-8") as fh:
json.dump(manifest, fh, ensure_ascii=False, indent=1)
with open(os.path.join(OUT_DIR, "fcstd-inventory.json"), "w", encoding="utf-8") as fh:
json.dump(inventory, fh, ensure_ascii=False, indent=1)
print(f"exported {len(manifest)} meshes -> {OUT_DIR}")
FreeCAD.closeDocument(doc.Name)

49
tools/stage2-cad/probe.py Normal file
View File

@@ -0,0 +1,49 @@
# Probe FreeCAD semantics for links/placements on conveer.FCStd
import FreeCAD
doc = FreeCAD.openDocument("/home/coder/arhipovdan/app/3d_models/conveer.FCStd")
def bb(s):
b = s.BoundBox
return f"({b.XMin:.0f},{b.YMin:.0f},{b.ZMin:.0f})..({b.XMax:.0f},{b.YMax:.0f},{b.ZMax:.0f})"
def pmt(p):
return f"pos=({p.Base.x:.0f},{p.Base.y:.0f},{p.Base.z:.0f}) axis=({p.Rotation.Axis.x:.2f},{p.Rotation.Axis.y:.2f},{p.Rotation.Axis.z:.2f}) angle={p.Rotation.Angle:.3f}"
names = ["Body002", "Body003", "Body010", "Body018", "Body019", "Link005", "Link007", "Link016", "Link025", "Link027", "Link028", "Link000", "Link"]
for n in names:
o = doc.getObject(n)
if o is None:
print(n, "MISSING")
continue
print(f"== {n} type={o.TypeId} label={o.Label.encode('ascii','replace').decode()}")
print(" placement:", pmt(o.Placement) if hasattr(o, "Placement") else "n/a")
if hasattr(o, "getGlobalPlacement"):
try:
print(" global:", pmt(o.getGlobalPlacement()))
except Exception as e:
print(" global ERROR:", e)
else:
print(" global: NO METHOD")
try:
print(" shape bbox:", bb(o.Shape))
except Exception as e:
print(" shape ERROR:", e)
if o.TypeId == "App::Link":
try:
lo = o.LinkedObject
print(" linked:", lo.Name, lo.TypeId, lo.Label.encode('ascii','replace').decode())
if hasattr(lo, "Shape"):
print(" linked shape bbox:", bb(lo.Shape))
if hasattr(lo, "Placement"):
print(" linked placement:", pmt(lo.Placement))
if lo.TypeId == "App::DocumentObjectGroup":
for ch in lo.Group:
print(f" child {ch.Name} {ch.TypeId} place={pmt(ch.Placement) if hasattr(ch,'Placement') else 'n/a'} bbox={bb(ch.Shape) if hasattr(ch,'Shape') else 'n/a'}")
except Exception as e:
print(" linked ERROR:", e)
# group membership: which group contains this object
for g in doc.Objects:
if g.TypeId == "App::DocumentObjectGroup" and hasattr(g, "Group") and o in g.Group:
print(" member of group:", g.Name, g.Label.encode('ascii','replace').decode())
FreeCAD.closeDocument(doc.Name)