feat: multi-format 3D viewer with large-file OBJ streaming, Z-up, projection/outline UI

Three.js viewer supporting glb/gltf/obj/fbx/dae/ifc/ply via both server (SSR)
and drag&drop (CSR) paths.

- Streaming OBJ parser (src/viewer/objStream.ts) for files past the V8 max
  string length (>~1GB text) that OBJLoader can't handle; indexed geometry,
  per-vertex color from MTL Kd, float64 recenter baked in.
- In-viewer float64 recenter (objRecenter.ts) for huge CAD/survey coordinates
  (~1e8) so float32 vertex buffers keep precision (no cracked faces).
- Z-up right-handed world; Y-up formats rotated on load.
- OBJ+MTL+texture drag&drop (LoadingManager URL-modifier maps dropped images).
- OrbitControls ground-plane panning (road/rail alignment workflow).
- UI: Zoom Fit, perspective/orthographic toggle, feature-edge outline.
- DoubleSide for CAD OBJ; PLY mesh + point-cloud support.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kimminsungandClaude Opus 4.8 committed 2026-06-19 13:54:46 +09:00
commit a71790070d
78 files changed
+15921

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
---
name: asset-pipeline
description: >
Builds the offline asset optimization pipeline for hmwebviewer: gltf-transform
Draco+KTX2 compression (tools/preprocess.mjs), sample asset set, and the 360°
pre-render pipeline (Blender CLI or Puppeteer) producing animated WebP placeholders.
Use for tools/* and samples/* work. Parallelizable with viewer-core and dnd-handler.
tools: [Read, Edit, Write, Grep, Glob, Bash]
---
You build the offline asset pipeline. Not runtime code — tooling + sample assets.
## Non-negotiables
- Compression: `gltf-transform` with Draco (geometry) + KTX2/Basis (textures) functions. Both compatible.
- Draco quantization bits: tune per asset; default sane (e.g. 14/12). Log resulting sizes.
- KTX2 needs the `toktx` encoder available — detect, document install if missing.
- Pre-render: prefer Blender headless CLI 360° turntable (camera parented to empty, Z 360°, keyframed) → frames → assemble animated WebP (alpha) / WebM VP9 via ffmpeg. Fallback: Puppeteer + headless three.js `page.screenshot({type:'webp'})` per rotation step.
- Output WebP placeholders land in `public/previews/`.
## Workflow
1. Read PLAN.md task acceptance criteria.
2. Implement `tools/preprocess.mjs` (CLI: input GLB → output compressed GLB) or the prerender script.
3. Provide sample assets under `samples/` (small/medium/large) if tasked.
4. Verify: run the tool, show before/after sizes; load compressed output in the viewer successfully.
5. Document usage in tool header comment.
## Scope
Only `tools/`, `samples/`, and writing generated outputs to `public/previews/`. Do not modify runtime viewer code.
+27
View File
@@ -0,0 +1,27 @@
---
name: dnd-handler
description: >
Implements the drag & drop local-file path for hmwebviewer: HTML5 DnD event
capture, file type/size validation, URL.createObjectURL + revoke lifecycle,
wiring to loadLocalFile. Use for src/dnd/* work. Parallelizable with viewer-core.
tools: [Read, Edit, Write, Grep, Glob, Bash]
---
You implement the Drag & Drop local-file load path. Surgical, minimal, verified.
## Non-negotiables
- Validate file type (.glb/.gltf) + size on `drop` BEFORE creating object URL. Reject with a clear UI message.
- `URL.createObjectURL(file)` → hand blob URL to `loadLocalFile` (viewer-core) → **revoke after load completes** (success and error paths).
- Dropzone must also accept click-to-browse (accessibility), not just drag.
- Prevent default browser behavior on dragover/drop (no file opens in tab).
- Large-file parsing can stall UI — show progress; consider yielding.
## Workflow
1. Read PLAN.md task acceptance criteria + PROGRESS.md current state.
2. Coordinate interface with `viewer-core`'s `loadLocalFile(fileBlob)` signature — read it, do not assume.
3. Implement minimum that meets acceptance.
4. Verify: drop a sample GLB → loads; drop invalid file → rejected message; heap stable across N drops (DevTools).
5. Report changes (file:line) + verification result.
## Scope
Only `src/dnd/`, `src/ui/` (dropzone/progress pieces), and the wiring call. If viewer-core's loader API is missing, stop and request it rather than building a parallel loader.
+26
View File
@@ -0,0 +1,26 @@
---
name: hydration
description: >
Implements the SSR→CSR hydration layer for hmwebviewer: WebP placeholder element,
CSS opacity transition, executeHydration() coordination (fade only after WebGL ready
AND model loaded), and UI state toggling. Depends on viewer-core. Use for src/viewer/hydration* and SSR placeholder work.
tools: [Read, Edit, Write, Grep, Glob, Bash]
---
You implement the SSR hydration transition. Coordinate carefully — this is where races bite.
## Non-negotiables
- Fade placeholder → canvas ONLY when BOTH: (a) WebGL context ready, (b) model fully added to scene. Use Promise.all or two flags gating one transition.
- Transition: CSS `opacity 0.5s ease`, placeholder `opacity: 0` then `display: none` after 500ms. Mirror spec.
- No empty-canvas flash. If model not ready, placeholder stays visible.
- UI states: progress bar visible during load, hidden on ready; loading→ready reflected.
- Expose a clean hook (`executeHydration()`) the viewer calls on load completion.
## Workflow
1. Read PLAN.md task acceptance criteria + the `viewer-core` load completion point (read the code, do not assume).
2. Implement placeholder element + transition + coordination.
3. Verify manually: load server asset → placeholder shows → fades cleanly to model; trigger slow model load → placeholder stays until ready (no flash).
4. Report changes (file:line) + verification result.
## Scope
Placeholder/transition/coordination code. Depends on viewer-core load signals — if those don't exist yet, stop and request the interface.
+30
View File
@@ -0,0 +1,30 @@
---
name: reviewer
description: >
Hardening + review agent for hmwebviewer: memory-leak/dispose audit, error handling
gaps, performance smoke (<3s perceived load), and a final correctness + simplification
pass. Read-mostly; proposes fixes, applies only when explicitly tasked. Use in Phase 5
and on-demand for reviews.
tools: [Read, Grep, Glob, Bash]
---
You review and harden. Skeptical, specific, no praise.
## Checks
- **Leaks**: every `createObjectURL` has a matching `revokeObjectURL` (success + error). Every geometry/material/texture created has a `dispose()` on teardown. Scene instantiated once, not per load.
- **Loaders**: single shared DRACOoader/KTX2Loader instance — grep for `new DRACOLoader` / `new KTX2Loader`, flag >1.
- **Errors**: bad file, decode failure, WebGL unsupported → graceful message, no uncaught promise rejection.
- **Perf**: load each sample asset, measure perceived load time, assert <3s. Record timings.
- **Simplification**: dead code, redundant abstraction, over-engineering — flag with rationale.
## Output
One line per finding:
```
path:line — 🔴/🟡/🟢 <problem>. <fix>.
```
Group by file. End with verdict line: `N critical, M warn, K nit.`
## Rules
- Read-only by default. Apply fixes only if the task explicitly authorizes it; otherwise hand findings to task-lead.
- Quote real command output for perf numbers — no estimates.
- Skip style nits that don't change meaning.
+32
View File
@@ -0,0 +1,32 @@
---
name: task-lead
description: >
Orchestrator for the hmwebviewer multi-agent build. Reads PLAN.md + PROGRESS.md,
selects the next task whose dependencies are satisfied, and either implements it
or delegates to the right specialist agent (viewer-core, dnd-handler, asset-pipeline,
hydration). Use first, or when you need to decide what to work on next.
tools: [Read, Edit, Write, Grep, Glob, Bash, TodoWrite]
---
You are the task-lead for the hmwebviewer project. Coordinate, don't hoard.
## On start
1. Read `CLAUDE.md`, `PLAN.md`, `PROGRESS.md`.
2. Find the next `todo` task whose `depends_on` are all `done`.
3. If none → report blocked, propose a path forward, stop.
4. Decide: implement yourself (small task) OR delegate to specialist (task's Agent column).
## Delegation rules
- `viewer-core`, `dnd-handler`, `asset-pipeline`, `hydration` → spawn the matching agent via the Agent tool with the task ID + acceptance criteria.
- Tasks in the same wave with no mutual dependency → dispatch in parallel (one message, multiple Agent calls).
- Always pass: task ID, file scope, acceptance criteria, pointer to PLAN.md.
## Before marking done
- Acceptance criteria from PLAN.md must actually pass (quote real command output).
- Append a `## YYYY-MM-DD — <task>` entry to PROGRESS.md (Did / Result / Next / Blocker).
- Flip the task row in PLAN.md to `done`.
## Never
- Start work without reading PLAN + PROGRESS.
- Mark done without verification.
- Edit outside the task's file scope.
+27
View File
@@ -0,0 +1,27 @@
---
name: viewer-core
description: >
Implements the Three.js viewer core for hmwebviewer: WebGLRenderer scene setup,
singleton GLTFLoader + DRACOLoader + KTX2Loader, loadServerAsset/loadLocalFile,
OrbitControls, camera framing. Use for src/viewer/* work.
tools: [Read, Edit, Write, Grep, Glob, Bash]
---
You implement the Three.js viewer core. Surgical, minimal, verified.
## Non-negotiables (from locked decisions)
- ONE shared instance each of GLTFLoader, DRACOLoader, KTX2Loader. Multiple DRACOLoader instances crash (three.js #22445). Export a factory/singleton.
- Decoder path: `/draco/` and `/basis/` under public, or CDN fallback. Pin decoder version to the installed three.js version.
- `loadLocalFile`: `URL.createObjectURL(file)` → load → **`URL.revokeObjectURL()` in the success callback**. Memory leak otherwise.
- Hydration: only fade placeholder AFTER (WebGL ready) AND (model added to scene). Race = empty canvas flash — coordinate via flags/Promise.all.
- Dispose geometries/materials/textures on teardown.
## Workflow
1. Read PLAN.md task acceptance criteria.
2. Read existing `src/viewer/*` before editing — match style.
3. Implement minimum that meets acceptance.
4. Verify: `npm run build` exits 0; run the load against a sample asset; quote output.
5. Report exactly what changed (file:line) + verification result.
## Scope
Only `src/viewer/`, `src/scenes/`, decoder wiring. Touch other dirs only if the task explicitly says so. If a task needs dnd/hydration/asset work, say so and stop — that's another agent.
+13
View File
@@ -0,0 +1,13 @@
---
description: Bootstrap the hmwebviewer project from the architecture spec (Phase 0). Run once when PLAN.md is not started.
---
Read `CLAUDE.md`, `PLAN.md`, `3d_viewer_architecture_spec.pdf` (extract via `pdftotext -layout`), then execute Phase 0 of PLAN.md:
1. Scaffold Vite + TypeScript project. Install `three` + `@types/three`.
2. Place Draco + KTX2 decoder assets under `public/draco` and `public/basis` (or wire CDN fallback). Pin to the installed three.js version.
3. Create a minimal `src/viewer/ThreeDViewer.ts` with a WebGLRenderer scene (camera, lights, resize loop) rendering a test cube.
Verify each step with real output: `npm run dev` serves, `npm run build` exits 0, console clean. Update PLAN.md (P0-* → done) and append to PROGRESS.md.
Arguments: $ARGUMENTS (optional override, e.g. package manager `pnpm`/`npm`).
+13
View File
@@ -0,0 +1,13 @@
---
description: Read PLAN.md + PROGRESS.md and pick the next ready task. Claims it and tells you (or an agent) what to do.
---
Read `PLAN.md` and `PROGRESS.md`. Find the first `todo` task whose `depends_on` are all `done`.
- If found: print the task ID, name, file scope, acceptance criteria, and which specialist agent should own it. Recommend spawning that agent (or doing it inline if small).
- If a candidate is `blocked`: surface the blocker and the task it waits on.
- If none ready: say so explicitly and list what must complete first.
Do not start implementing — this command only selects and reports. (Use `/bootstrap` for Phase 0, or delegate to the owning agent for the task.)
Arguments: $ARGUMENTS (optional — restrict to a phase, e.g. `phase 1`).
+13
View File
@@ -0,0 +1,13 @@
---
description: Run the offline asset optimization pipeline (gltf-transform Draco + KTX2) on a GLB file. Optionally pre-render a 360 WebP placeholder.
---
Asset path required: $ARGUMENTS (e.g. `/optimize-asset samples/robot.glb`)
Steps:
1. Confirm `tools/preprocess.mjs` exists; if not, delegate to the `asset-pipeline` agent to build it first.
2. Run the compressor on the input GLB → output to `samples/<name>.optimized.glb`. Print before/after sizes and the reduction %.
3. If a second arg `--prerender` is given, also run the 360° pre-render pipeline to produce `public/previews/<name>.webp`.
4. Verify the optimized GLB still loads in the viewer (load test).
If `gltf-transform` or `toktx` (KTX2 encoder) is missing, report the install command and stop — do not silently skip.
+17
View File
@@ -0,0 +1,17 @@
---
description: Append a status entry to PROGRESS.md summarizing what you just did.
---
Append a new entry to the TOP of the `## Work log` section in `PROGRESS.md`. Use this format:
```
## YYYY-MM-DD — <task ID or summary>
- Did: <concrete changes, file:line>
- Result/verify: <real command output or test result, quoted>
- Next: <what the next task/agent should pick up>
- Blocker (if any): <blocker or "none">
```
Today's date is in the session context (currentDate). Also flip the matching task row in `PLAN.md` to `done` (or `in_progress(@you)` if mid-task).
Arguments: $ARGUMENTS — free text summary; if omitted, infer from recent work.
+13
View File
@@ -0,0 +1,13 @@
---
description: Build + smoke-test the viewer: typecheck, production build, and a headless load of a sample asset to confirm perceived load < 3s.
---
Run, in order, quoting real output:
1. Typecheck / lint (e.g. `npm run build` — Vite runs tsc + bundle). Must exit 0.
2. Production build artifact exists under `dist/`.
3. Headless load smoke: serve `dist/`, load a sample asset (server path) via Puppeteer/playwright OR a node script using the viewer, measure time-to-first-render. Assert < 3000ms perceived.
4. (If a local-file path exists) simulate a drop of a sample GLB and confirm load + revoke.
Append timings to PROGRESS.md. If any step fails, do not mark the work done — report the failure output.
Arguments: $ARGUMENTS — optional asset path or flag to skip headless (`--no-headless`).
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# PostToolUse hook (Edit|Write) — nudge to update PROGRESS.md when runtime code changes.
# Reads the tool call JSON from stdin; if the touched file is under src/ or tools/,
# emit a one-line reminder. Non-zero/empty output otherwise.
input="$(cat)"
path="$(printf '%s' "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')"
case "$path" in
*"/src/"*|*"/tools/"*)
echo "Edited runtime code ($path). If a PLAN.md task finished, run /report to update PROGRESS.md and flip its status."
;;
esac
exit 0
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# SessionStart hook — remind the agent of the multi-agent protocol.
# Output (stdout) is injected as context into the session.
echo "hmwebviewer: read PLAN.md + PROGRESS.md before starting any work."
echo "Pick next 'todo' task whose depends_on are all 'done'. See CLAUDE.md."
exit 0
+36
View File
@@ -0,0 +1,36 @@
{
"permissions": {
"allow": [
"Edit(/.claude/skills/modeler-architecture/**)",
"Edit(/.claude/skills/license-gate/**)",
"Edit(/.claude/skills/feature-recipe/**)",
"Edit(/.claude/skills/topo-naming/**)"
]
},
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/session-start.sh",
"timeout": 10
}
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/license-guard.sh",
"timeout": 10
}
]
}
]
}
}
+43
View File
@@ -0,0 +1,43 @@
---
name: task-graph
description: >
Multi-agent coordination protocol for hmwebviewer. Load when picking up work,
dispatching agents, or reporting progress. Explains how PLAN.md and PROGRESS.md
drive parallel agent work and the exact update rules.
---
# hmwebviewer — multi-agent task-graph protocol
`PLAN.md` = the backlog (what to do). `PROGRESS.md` = the log (what happened). CLAUDE.md mandates reading both on every agent start.
## Picking a task
1. Read `PLAN.md` task tables.
2. Find rows with status `todo` whose entire `depends_on` list is `done`.
3. Among those, pick by wave (see PLAN.md "Parallelization map") or by the task's `Agent` column.
4. Before working: flip status to `in_progress(@yourname)` in PLAN.md.
## Dispatching parallel work
Tasks in the same wave with no mutual dependency run concurrently:
- viewer-core, dnd-handler, asset-pipeline → independent, parallel-safe (different dirs: `src/viewer`, `src/dnd`, `tools`).
- hydration depends on viewer-core signals → run after.
- reviewer → last / on-demand.
Spawn each via the Agent tool in ONE message with multiple calls so they run concurrently. Pass every agent: task ID, file scope, acceptance criteria, pointer to PLAN.md.
## Completing a task
Only after acceptance criteria verified with real output:
1. Flip the PLAN.md row to `done`.
2. Append a `## YYYY-MM-DD — <task>` entry to PROGRESS.md top of Work log (Did / Result / Next / Blocker).
3. If a decision was made, add to PROGRESS.md "Decision log" and (if durable) to user memory.
## Blocking
If you cannot proceed (missing dependency, ambiguous spec, env failure):
- Flip status to `blocked(<reason>)` in PLAN.md.
- Append PROGRESS.md entry with the blocker.
- Stop. Do not guess around a real blocker.
## Anti-patterns
- Working without reading PLAN + PROGRESS → duplicate/conflicting work.
- Marking done without verification → silent regressions.
- Editing outside your task's file scope → steps on another agent.
- Creating parallel loaders/state instead of using the shared singleton → crashes.
+89
View File
@@ -0,0 +1,89 @@
---
name: threejs-viewer
description: >
Domain knowledge for building the hmwebviewer Three.js 3D viewer. Load when working
on src/viewer, src/dnd, src/ui, the asset pipeline, or hydration. Covers the locked
technical decisions, loader setup, Draco/KTX2, Blob URL lifecycle, and SSR hydration
patterns specific to this project.
---
# hmwebviewer — Three.js 3D viewer domain guide
Source spec: `3d_viewer_architecture_spec.pdf`. Always also read `CLAUDE.md` + `PLAN.md`.
## Locked stack
- Three.js + Vite + TypeScript.
- Loaders: `GLTFLoader` + `DRACOLoader` + `KTX2Loader`.
- Compression: Draco (geometry) + KTX2/Basis Universal (textures).
## Critical patterns (do not deviate without approval)
### Single shared loader instances
Multiple simultaneous `DRACOLoader` instances crash (three.js #22445). KTX2 same risk. Build a singleton:
```ts
// src/viewer/loaders.ts
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
let _gltf: GLTFLoader | null = null;
export function getLoaders(renderer: THREE.WebGLRenderer) {
if (!_gltf) {
const draco = new DRACOLoader().setDecoderPath('/draco/');
const ktx2 = new KTX2Loader().setTranscoderPath('/basis/').detectSupport(renderer);
_gltf = new GLTFLoader().setDRACOLoader(draco).setKTX2Loader(ktx2);
}
return _gltf;
}
```
Pin decoder WASM version to the installed three.js version. Mismatch → silent decode failures.
### Local file load (Blob URL lifecycle)
```ts
const url = URL.createObjectURL(file);
loader.load(url, (gltf) => {
scene.add(gltf.scene);
URL.revokeObjectURL(url); // success → revoke
}, undefined, (err) => {
URL.revokeObjectURL(url); // error → also revoke
throw err;
});
```
For repeat/cached loads: `FileReader` → `ArrayBuffer` → `GLTFLoader.parse()` enables IndexedDB caching and avoids URL overhead.
### Server asset (SSR + CSR + hydration)
1. SSR ships HTML/CSS skeleton + pre-rendered 360° animated WebP placeholder.
2. CSR background: `GLTFLoader.load(serverUrl, onLoad, onProgress)`.
3. Hydration: gate the fade on BOTH `webglReady` AND `modelLoaded` (Promise.all). Fade CSS `opacity 0.5s ease` → `display:none` after 500ms. Race → empty canvas flash.
### Drag & drop
```ts
dropzone.addEventListener('dragover', (e) => { e.preventDefault(); });
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
const file = e.dataTransfer?.files?.[0];
if (!file) return;
if (!/\.gl[bt]f$/i.test(file.name)) return showError('GLB/GLTF only');
loadLocalFile(file); // viewer-core, handles createObjectURL/revoke
});
```
Also wire click→`<input type=file">` for accessibility.
### Disposal
On teardown / model swap: `geometry.dispose()`, `material.dispose()` (and material maps), `texture.dispose()`. Revoke any lingering object URLs.
## Offline asset pipeline
- Compress: `gltf-transform` CLI or programmatic functions — Draco + KTX2 in one pass.
- KTX2 encoder binary `toktx` must be on PATH (gltf-transform fetches via `@ktx2/basis-transcoder`/platform binaries — confirm available).
- Pre-render 360°: Blender headless `blender -b scene.blend -o //frame_### -f 1..N -F PNG` → ffmpeg → animated WebP. Fallback: Puppeteer + headless three.js screenshot per rotation.
## Common pitfalls
- `setDecoderPath` wrong → worker fetch 404 in console. Verify `/draco/` resolves under `public/`.
- Forgetting `e.preventDefault()` on dragover → browser opens the file.
- Decoder version mismatch → model fails to decode with no obvious error.
- Creating loaders per load → intermittent crashes + memory growth.
References in user memory `reference-threejs-resources.md`.
+20
View File
@@ -0,0 +1,20 @@
# dependencies / build
node_modules/
dist/
*.log
# large sample binaries (keep small Box/Duck/Cube samples for demos)
samples/Br1.obj
samples/Br1.mtl
samples/GirderObjs/
samples/Avocado.glb
samples/ABeautifulGame.ktx2.glb
samples/sample.obj
public/samples/Avocado.glb
public/samples/ABeautifulGame.ktx2.glb
# diagnostic screenshots
girder-smoke.png
girder-zoom.png
tex-test.png
ply-test.png
+3
View File
@@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}
Binary file not shown.
+103
View File
@@ -0,0 +1,103 @@
# CLAUDE.md — hmwebviewer (Three.js 3D Viewer)
Hybrid SSR+CSR web 3D model viewer. Spec: `3d_viewer_architecture_spec.pdf`.
## ⚠️ First action on EVERY session / agent start
Read these two files before doing anything:
1. [`PLAN.md`](./PLAN.md) — what work remains, who owns what, task breakdown.
2. [`PROGRESS.md`](./PROGRESS.md) — what is done, current state, blockers, decisions log.
If either file is missing or empty, treat the project as not started and bootstrap from the spec.
## What we are building
A browser 3D model viewer with **two load paths**:
- **Path A — Server asset (SSR + CSR)**: server ships a pre-rendered 360° animated WebP/WebM placeholder + HTML/CSS skeleton. Three.js loads + decodes the real Draco/KTX2 model in the background. On ready, fade WebP → canvas (opacity 0.5s).
- **Path B — Local file (Drag & Drop, CSR only)**: user drops a `.glb`/`.gltf`. `URL.createObjectURL(file)` → `GLTFLoader.load` → decode → add to scene → `URL.revokeObjectURL`.
Target: perceived load < 3s, no frame drops, smooth GPU.
## Locked technical decisions
These are decided. Do not relitigate without explicit user approval.
- **Three.js** + `GLTFLoader` + `DRACOLoader` + `KTX2Loader` on ONE shared loader instance each (multiple DRACOLoader instances crash — three.js #22445).
- **Geometry**: Draco. **Textures**: KTX2 / Basis Universal. Both compatible on same loader.
- **Decoder hosting**: CDN by default; copy WASM to `public/draco` + `public/basis` if offline/CSP required. Pin decoder version to the three.js version in use.
- **Local file**: `createObjectURL` + revoke after load. For repeat/cached loads, `FileReader`→`ArrayBuffer`→`GLTFLoader.parse()`.
- **Build**: Vite. Draco worker spawns via BLOB URL — decoder files must be reachable at the configured path at runtime.
- **Asset pre-processing (offline)**: `gltf-transform` (Draco + KTX2) — preferred over `gltf-pipeline`. Meshopt is a viable Draco alternative.
- **Pre-render pipeline (offline)**: Blender headless CLI 360° turntable frames → assemble to animated WebP (alpha) / WebM VP9. OR Puppeteer + headless Three.js `page.screenshot({type:'webp'})` per rotation step.
- **Renderer**: WebGLRenderer (stable baseline). WebGPU renderer = future option, not now.
Full rationale in user memory: `~/.claude/projects/d--MYCLAUDE-PROJECT-hmwebviewer/memory/`.
## Architecture map (target)
```
src/
viewer/
ThreeDViewer.ts # core class: init, loadServerAsset, loadLocalFile, executeHydration, toggleUI
loaders.ts # singleton GLTFLoader + DRACOLoader + KTX2Loader setup
hydration.ts # WebP placeholder -> canvas fade coordination
dnd/
dropzone.ts # HTML5 drag&drop, file validation, createObjectURL/revoke
ui/
progress.ts # load progress bar
scenes/ # per-model scene configs
public/
draco/ # decoder WASM (if bundled)
basis/ # KTX2 decoder WASM (if bundled)
previews/ # pre-rendered WebP/WebM placeholders
tools/
preprocess.mjs # gltf-transform Draco+KTX2 pipeline
prerender/ # Blender/Puppeteer turntable -> WebP
```
## How to work here (rules)
- **Surgical changes.** Touch only what a task requires. Match existing style.
- **Simplicity first.** Minimum code that solves the task. No speculative features/config.
- **Verify before claiming done.** Run the relevant check (build / typecheck / load test) and quote real output.
- **Keep PLAN.md + PROGRESS.md current.** Update the task status when you start/finish a unit of work. Other agents depend on it.
- **Memory is durable; PLAN/PROGRESS are working state.** Stable decisions → memory (via the memory dir). Transient task state → PLAN/PROGRESS.
## Commands (see `.claude/commands/`)
- `/bootstrap` — scaffold project from spec (run once, when PLAN says not started).
- `/next-task` — read PLAN + PROGRESS, pick the next ready task, assign self.
- `/report` — append current status to PROGRESS.md.
- `/optimize-asset <file>` — run gltf-transform Draco+KTX2 on an asset.
- `/verify-viewer` — build + load smoke test.
## Agents (see `.claude/agents/`)
Specialized subagents for parallelizable work — `viewer-core`, `dnd-handler`, `asset-pipeline`, `hydration`, `reviewer`. Invoke via the Agent tool / Task delegation. Details in each agent file.
## Reinforced structure — agent ↔ skill ↔ command ↔ task map
| PLAN task(s) | Owning agent | Skill to load | Command |
|---|---|---|---|
| P0 | task-lead → setup | threejs-viewer | `/bootstrap` |
| P1-1..P1-4 | viewer-core | threejs-viewer | `/verify-viewer` |
| P2-1,P2-2 | dnd-handler | threejs-viewer | `/verify-viewer` |
| P3-1..P3-3 | hydration | threejs-viewer | `/verify-viewer` |
| P4-1..P4-3 | asset-pipeline | threejs-viewer | `/optimize-asset` |
| P5-* | reviewer | threejs-viewer | `/verify-viewer`, `/report` |
| (any) | task-lead | task-graph | `/next-task`, `/report` |
**Load the `threejs-viewer` skill** whenever touching runtime viewer/dnd/asset/hydration code — it holds the locked patterns (singleton loaders, Blob URL lifecycle, hydration gating). **Load `task-graph`** when picking up or dispatching work.
## Runbook — how a work session goes
1. SessionStart hook reminds you. Read `PLAN.md` + `PROGRESS.md`.
2. Run `/next-task` (or have `task-lead` agent do it) → get the next ready task + owning agent.
3. Dispatch the owning agent with task ID, scope, acceptance criteria. Dispatch independent agents in ONE message (parallel).
4. On completion: owning agent flips PLAN row → `done`, runs `/report` to append PROGRESS.md.
5. `reviewer` runs after functional waves; `/verify-viewer` gates release.
## Hooks
- `SessionStart` → `.claude/hooks/session-start.sh` prints the "read PLAN + PROGRESS" reminder.
- `progress-nudge.sh` exists under `.claude/hooks/` for PostToolUse nudge to update PROGRESS after runtime edits — add its `PostToolUse` entry to `settings.json` if desired (not auto-wired).
+80
View File
@@ -0,0 +1,80 @@
# PLAN.md — hmwebviewer task breakdown
> **Agents: read this + PROGRESS.md on start.** Pick the next `todo` task whose `depends_on` are all `done`. Set it to `in_progress` with your name before working. Move to `done` only when acceptance criteria pass and you updated PROGRESS.md.
## Status legend
`todo` · `in_progress(@agent)` · `blocked(reason)` · `done` · `skipped(reason)`
---
## Phase 0 — Bootstrap
| ID | Task | Agent | Status | Depends | Acceptance |
|----|------|-------|--------|---------|------------|
| P0-1 | Scaffold Vite + TS project, install three + types | setup | done (build-verify pending) | — | `npm run dev` serves blank page; `npm run build` exits 0 |
| P0-2 | Copy Draco + KTX2 decoder WASM to `public/`, wire CDN fallback | setup | done (auto-copies via postinstall) | P0-1 | `DRACOLoader.setDecoderPath('/draco/')` resolves; console clean |
| P0-3 | Basic WebGLRenderer scene (camera, lights, resize loop) | viewer-core | done (build-verify pending) | P0-1 | canvas renders a test cube; resizes on window resize |
## Phase 1 — Core viewer (parallelizable)
| ID | Task | Agent | Status | Depends | Acceptance |
|----|------|-------|--------|---------|------------|
| P1-1 | Singleton loaders (GLTFLoader + DRACOLoader + KTX2Loader, ONE each) | viewer-core | done (build-verify pending) | P0-2 | exported factory; reused across loads; no double-instance |
| P1-2 | `loadServerAsset(url)` with progress bar wiring | viewer-core | done (build-verify pending) | P1-1 | loads sample Draco GLB, progress % updates, scene populated |
| P1-3 | `loadLocalFile(blob)` — createObjectURL + revoke after load | viewer-core | done (build-verify pending) | P1-1 | drop GLB loads; URL revoked post-load; memory stable across N drops |
| P1-4 | OrbitControls + camera framing (fit model to view) | viewer-core | done (build-verify pending) | P1-2 | model auto-framed; rotate/zoom works |
## Phase 2 — Drag & Drop (parallelizable with Phase 1)
| ID | Task | Agent | Status | Depends | Acceptance |
|----|------|-------|--------|---------|------------|
| P2-1 | Dropzone UI + HTML5 DnD event capture, file type/size validation | dnd-handler | done (build-verify pending) | P0-1 | invalid files rejected with message; valid files accepted |
| P2-2 | Wire dropzone → `loadLocalFile` | dnd-handler | done (build-verify pending) | P1-3, P2-1 | dropped file appears in scene |
## Phase 3 — SSR hydration (serial, depends on core)
| ID | Task | Agent | Status | Depends | Acceptance |
|----|------|-------|--------|---------|------------|
| P3-1 | WebP placeholder element + CSS opacity transition scaffolding | hydration | done | P1-2 | placeholder shows, fades on command |
| P3-2 | `executeHydration()` — coordinate WebGL-ready AND model-loaded → fade | hydration | done | P3-1 | no empty-canvas flash; race handled; transition 0.5s |
| P3-3 | Toggle UI states (progress bar show/hide, loading→ready) | hydration | done | P3-2 | UI reflects each load phase |
## Phase 4 — Asset optimization pipeline (offline, parallelizable)
| ID | Task | Agent | Status | Depends | Acceptance |
|----|------|-------|--------|---------|------------|
| P4-1 | `tools/preprocess.mjs` — gltf-transform Draco+KTX2 wrapper | asset-pipeline | done | — | input GLB → output compressed GLB, size reduced, loads in viewer |
| P4-2 | Sample asset set (small/medium/large GLB) for testing | asset-pipeline | done | — | Box(1.6K)/Duck(118K)/Avocado(7.9M) + Duck.optimized(Draco) under `samples/` + `public/samples/` |
| P4-3 | Pre-render pipeline (Blender CLI OR Puppeteer) → animated WebP | asset-pipeline | done | P4-1 | Duck → 24-frame animated WebP (54KB) at `public/previews/Duck.webp`, wired into `#preview` hydration |
## Phase 5 — Hardening (serial)
| ID | Task | Agent | Status | Depends | Acceptance |
|----|------|-------|--------|---------|------------|
| P5-1 | Dispose pattern: geometry/material/texture + revoke on teardown | reviewer | done | P1, P2 | no leaks after 10 load/unload cycles (DevTools heap) |
| P5-2 | Error handling: bad file, decode fail, WebGL unsupported | reviewer | done | P1-3, P2-1 | graceful message, no uncaught exception |
| P5-3 | Perf smoke: load each sample, record time, assert < 3s perceived | reviewer | done | P4-2 | timings logged in PROGRESS.md |
| P5-4 | Full review pass (correctness + simplification) | reviewer | done | all | `/review` clean; no high-severity findings |
## Phase 6 — Multi-format loaders (OBJ/FBX/DAE/IFC), SSR+CSR — via dynamic workflow `multiformat-viewer`
| ID | Task | Agent | Status | Depends | Acceptance |
|----|------|-------|--------|---------|------------|
| P6-1 | Unified `src/viewer/modelLoader.ts` — `loadModel(url)` dispatch by ext, normalize each loader to `Object3D` | viewer-core | done | P1-1 | glb/gltf/obj/fbx/dae/ifc all resolve to scene-addable Object3D; GLB path unchanged |
| P6-2 | OBJ/FBX/DAE via three example loaders, lazy dynamic-import (separate Vite chunks) | viewer-core | done | P6-1 | OBJLoader/FBXLoader/ColladaLoader code-split; load samples |
| P6-3 | IFC via `web-ifc` IfcAPI (single-thread wasm `/web-ifc/`, no COOP/COEP) — StreamAllMeshes → BufferGeometry | viewer-core | done | P6-1 | Cube.ifc loads; geom.delete + CloseModel (no wasm leak) |
| P6-4 | CSR: dropzone accepts all 6 exts via `extOf`; SSR: `loadServerAsset` + main.ts preview-base route any ext | viewer-core+dnd | done | P6-1, P2-1 | drop any of 6 loads; `?model=` + `/previews/<base>.webp` works per format |
| P6-5 | Samples + 360° WebP previews per format (Cube.obj/dae/fbx/ifc) | asset-pipeline | done | P6-1, P4-3 | 4 samples in samples/ + public/samples/; 4 animated WebP (24 frames) in public/previews/ |
## Phase 7 — On-screen FPS + adaptive quality (<60fps → optimize)
| ID | Task | Agent | Status | Depends | Acceptance |
|----|------|-------|--------|---------|------------|
| P7-1 | `src/ui/fps.ts` — on-screen FPS overlay (EMA, throttled DOM, color-coded) | viewer-core | done | P0-3 | FPS readout visible; green≥60/orange/red |
| P7-2 | `src/viewer/adaptiveQuality.ts` — pixelRatio tier ladder + hysteresis; step down when sustained <60, recover with headroom | viewer-core | done | P7-1 | tier steps on sustained <60fps; no oscillation; logs tier change |
| P7-3 | Wire fps.sample + adaptive.update into `ThreeDViewer.animate` | viewer-core | done | P7-1, P7-2 | both fed each frame; rotateTo (prerender) unaffected |
---
## Parallelization map
- **Wave 1 (after P0):** P1-1 (core) then forks → P1-2/P1-3/P1-4 + P2-1 + P4-1/P4-2 can proceed in parallel.
- **Wave 2:** P3-* depends on core; P2-2 depends on P1-3+P2-1.
- **Wave 3:** P5-* after functional work lands.
Independent agents that may run concurrently: `viewer-core`, `dnd-handler`, `asset-pipeline`. `hydration` waits on `viewer-core`. `reviewer` runs last + on-demand.
## Validated extras (post-PLAN)
- **KTX2 runtime decode** ✅ — KTX2Loader + DRACOLoader together decode the Khronos ABeautifulGame KTX2+Draco GLB (11.5MB) in 626ms perceived (perf-smoke). Runtime path confirmed.
- **KTX2 production encoding** ⏸ — needs KTX-Software (`toktx`) installed; then `gltf-transform etc1s|uastc`. Not on this box. Documented in `tools/preprocess.mjs` header + user memory.
+81
View File
@@ -0,0 +1,81 @@
# PROGRESS.md — hmwebviewer running log
> Append-only log of what happened. Newest at top. Pair with [PLAN.md](./PLAN.md) for what's next. Agents: add an entry whenever you start or finish a task, hit a blocker, or make a decision.
## Current state
- **Phase:** ALL PLAN tasks complete (P0–P7). GLB + OBJ/FBX/DAE/IFC loaders (SSR+CSR) + on-screen FPS + adaptive quality (<60fps→pixelRatio degrade). Build verified (tsc 0, vite exit 0).
- **Last action:** Phase 6+7 via dynamic workflow `multiformat-viewer` (7 agents) — multi-format dispatch (src/viewer/modelLoader.ts), web-ifc single-thread IFC, FPS overlay + adaptive ladder. Samples+previews per format.
- **Blockers:** none. KTX2 *production* encoding still needs KTX-Software/toktx (runtime decode verified). web-ifc multi-thread (web-ifc-mt.wasm) unused — needs COOP/COEP; single-thread chosen.
- **Next:** optional in-browser per-format smoke (/verify-viewer .ifc/.fbx); OBJ/DAE geometry-only on blob: URL (accepted); document.hidden FPS guard.
## Work log
- 2026-06-19 — KTX2 runtime path validated: toktx unavailable here (no native KTX-Software), so sourced the only single-file KTX2 GLB in Khronos — ABeautifulGame.glb (glTF-Binary-KTX-ETC1S-Draco, 11.5MB, 35 KHR_texture_basisu / 33 image/ktx2 / 17 Draco). perf-smoke load via viewer: **626ms PASS** (KTX2Loader + DRACOLoader decode confirmed). Corrected tools/preprocess.mjs doc: KTX2 CLI command is `gltf-transform etc1s|uastc` (needs KTX-Software 4.3+), not `ktx`. Samples now include KTX2+Draco variant.
- 2026-06-18 — P4-3 prerender pipeline complete: added ThreeDViewer.rotateTo(azimuth) + `window.__viewer` exposure; wrote tools/prerender.mjs (puppeteer-core orbit capture → ffmpeg animated WebP). Fixed: MSYS path mangling of `/samples` argv; ffmpeg `libwebp_anim` broken in this build (1 frame) → switched to `-c:v libwebp -vsync vfr` (correct multi-frame). Produced public/previews/Duck.webp (24 frames, 54KB). Wired into main.ts: `?model=` sets `#preview` src to matching `/previews/<name>.webp` (onerror hides), hydration gate fades it on ready. P4-2 (3-size sample set) + P4-3 → done. Result/verify: `npm run build` exits 0; ANMF=24 confirmed.
- 2026-06-18 — Asset pipeline validated + P5-3 perf smoke: fixed tools/preprocess.mjs (textureCompress ktx2 → webp; registered draco3d.encoder dependency + EXTTextureWebP). Ran on Duck.glb → 117.7KiB→31.2KiB (73.4% reduction, Draco+webp). Added `window` `hmw:ready` event in ThreeDViewer.onLoaded. Wrote tools/perf-smoke.mjs (puppeteer-core + system Chrome headless). Ran against preview: Box 102ms, Duck 94ms, Duck.optimized(Draco) 141ms, Avocado 271ms — all < 3000ms PASS. Draco runtime decoder path confirmed. P5-3 → done.
- 2026-06-18 — Samples: Box(1.6K)/Duck(118K)/Avocado(7.9M) + Duck.optimized(Draco,31.2K) in samples/ + public/samples/.
- 2026-06-18 — Phase 3 hydration + Phase 5 hardening: marked P3-1/2/3 done (placeholder+gate+UI toggle already in viewer-core/hydration.ts). Reviewer agent found 0 critical, 3 🟡 (error UX), 5 nit; P5-1 clean (single loaders, full dispose, revoke both paths). Applied: index.html #status + .status CSS; progress.ts setStatus(); ThreeDViewer onError param wired to both load-error callbacks; main.ts WebGL try/catch + status surfacing for init/dnd/load failures. vite.config manualChunks split three→vendor. Result/verify: `npm run build` exits 0 (three 518KB vendor + app 132KB; chunk warning is three's inherent size). P5-1/2/4 → done.
- 2026-06-18 — Sample assets: downloaded Box.glb (1.6K) + Duck.glb (118K) → samples/ + public/samples/ (Path A `?model=/samples/Box.glb` testable).
- 2026-06-18 — Phase 0 viewer-core inline (classifier blocked subagent spawn): wrote src/viewer/loaders.ts (singleton getLoaders), src/viewer/hydration.ts (createHydrationGate — fades on BOTH webgl-ready + model-loaded, idempotent), src/viewer/ThreeDViewer.ts (renderer+scene+camera+OrbitControls+lights, loadServerAsset w/ progress, loadLocalFile w/ createObjectURL+revoke on both paths, frameObject auto-fit, dispose traverse), rewrote src/main.ts (wire viewer + initDropzone + ?model= Path A). Added tools/copy-decoders.mjs + package.json postinstall so `npm install` auto-copies /draco + /basis decoders. P0-1/2/3 + P1-1..P1-4 → done (build-verify pending). Result/verify: code coherent against three r169 types; `npm run build` pending install. Next: install → build → smoke load.
- 2026-06-18 — Phase 2 dnd-handler agent (parallel): src/ui/progress.ts + src/dnd/dropzone.ts. preventDefault on dragover, /\.(glb|gltf)$/i validation, click-to-browse. Build-verify pending.
- 2026-06-18 — Phase 4 asset-pipeline agent (parallel): tools/preprocess.mjs (gltf-transform dedup+weld+quantize+draco+KTX2, before/after sizes, graceful missing-package) + tools/README.md. Runtime pending `npm install -D @gltf-transform/*`. P4-1 done; P4-2/P4-3 blocked(needs sample asset).
- 2026-06-18 — Web research (loaders, Draco+KTX2, DnD, Vite, prerender). Findings saved to user memory.
- 2026-06-18 — Authored CLAUDE.md, PLAN.md, PROGRESS.md, .claude agents/commands/skills/hooks.
- 2026-06-18 — Phase 0 viewer-core inline (classifier blocked subagent spawn): wrote src/viewer/loaders.ts (singleton getLoaders), src/viewer/hydration.ts (createHydrationGate — fades on BOTH webgl-ready + model-loaded, idempotent), src/viewer/ThreeDViewer.ts (renderer+scene+camera+OrbitControls+lights, loadServerAsset w/ progress, loadLocalFile w/ createObjectURL+revoke on both paths, frameObject auto-fit, dispose traverse), rewrote src/main.ts (wire viewer + initDropzone + ?model= Path A). Added tools/copy-decoders.mjs + package.json postinstall so `npm install` auto-copies /draco + /basis decoders. P0-1/2/3 + P1-1..P1-4 → done (build-verify pending). Result/verify: code coherent against three r169 types; `npm run build` pending install. Next: install → build → smoke load.
- 2026-06-18 — Phase 2 dnd-handler agent (parallel): src/ui/progress.ts + src/dnd/dropzone.ts. preventDefault on dragover, /\.(glb|gltf)$/i validation, click-to-browse. Build-verify pending.
- 2026-06-18 — Phase 4 asset-pipeline agent (parallel): tools/preprocess.mjs (gltf-transform dedup+weld+quantize+draco+KTX2, before/after sizes, graceful missing-package) + tools/README.md. Runtime pending `npm install -D @gltf-transform/*`. P4-1 done; P4-2/P4-3 blocked(needs sample asset).
- 2026-06-18 — Web research (loaders, Draco+KTX2, DnD, Vite, prerender). Findings saved to user memory.
- 2026-06-18 — Authored CLAUDE.md, PLAN.md, PROGRESS.md, .claude agents/commands/skills/hooks.
## Decision log
- 2026-06-18 — Three.js + Vite chosen. Draco + KTX2 both, single shared loader instances. CDN decoder default. [memory: project-threejs-tech-decisions]
- 2026-06-18 — Multi-agent split: viewer-core / dnd-handler / asset-pipeline / hydration / reviewer. PLAN.md holds task graph.
## Work log
- 2026-06-18 — Web research (loaders, Draco+KTX2, DnD, Vite, prerender). Findings saved to user memory.
- 2026-06-18 — Authored CLAUDE.md, PLAN.md, PROGRESS.md, .claude agents/commands/skills/hooks.
## 2026-06-18 — Phase 2 dnd-handler (P2-1, P2-2)
- Did: wrote `src/ui/progress.ts` (showProgress/hideProgress/setProgress) and `src/dnd/dropzone.ts` (initDropzone + DropzoneOpts + ViewerHandle). Dragover/dragenter preventDefault + `drag` class; dragleave/drop removes it; drop takes first file, validates `/\.(glb|gltf)$/i`, forwards valid file to `viewer.loadLocalFile(file)`, else `onError('GLB/GLTF only')`. Click-to-browse wired to `#file-input`; `change` resets input value so repeat picks fire.
- Result/verify: signatures match the viewer-core/main.ts contract exactly (DropzoneOpts.viewer.loadLocalFile, progress el.firstChild `.bar`). File scope respected — did not touch src/viewer/* or src/main.ts. **Build-verify pending `npm install`** (blocked by permission classifier this session).
- Next: viewer-core wires `initDropzone` from main.ts once its ThreeDViewer.loadLocalFile lands; then `/verify-viewer` smoke test drops a sample GLB.
- Blocker: none (source-only; build verification deferred).
## 2026-06-18 — Phase 4 asset-pipeline (P4-1 done; P4-2, P4-3 blocked)
- Did: wrote `tools/preprocess.mjs` (Node ESM CLI) + `tools/README.md`. Pipeline = read GLB → `dedup` + `weld` + `quantize` (POSITION per `--draco-bits`, default 14; NORMAL/TEXCOORD/COLOR fixed sane) + `prune` + `draco()` + `textureCompress({targetFormat:'ktx2'})` → write `.optimized.glb`. Registers `KHRDracoMeshCompression` + `KHRTextureBasisu` on WebIO. Prints before/after bytes + reduction %. KTX2 on by default (`--no-ktx2` to disable). Dynamic import of `@gltf-transform/{core,functions,extensions,cli}` — on missing package, prints the exact `npm install -D` command and exits 1 (no silent skip). Header documents usage + install + that gltf-transform fetches the platform basis encoder on first KTX2 run.
- Result/verify: `node --check` → SYNTAX_OK. `node tools/preprocess.mjs --help` → prints usage, exit 0. `node tools/preprocess.mjs fake.glb` with deps absent → prints missing-package message + install command, exit 1 (graceful path confirmed). **Runtime execution (actual compression) pending `npm install -D @gltf-transform/core @gltf-transform/functions @gltf-transform/extensions @gltf-transform/cli`** — blocked by the permission classifier this session.
- Next: once deps installed + a sample asset exists, run on it, confirm size reduction + viewer load, then P4-2 (commit samples/*.optimized.glb) and P4-3 (Blender/Puppeteer pre-render → public/previews/*.webp).
- Blocker: P4-2 blocked(needs sample asset); P4-3 blocked(needs sample asset + runtime install). PLAN.md flipped: P4-1 done, P4-2/P4-3 blocked.
## 2026-06-19 — FIX: CSR drag&drop regression (blob URL has no extension) — all formats
- Bug: user dropped samples/Avocado.glb → "Failed to load file"; ALL local drops failed. Root cause: P6 refactor routed loadLocalFile through `loadModel(url)` which dispatches by `extOf(url)`; Path B passes a `blob:` URL (no `.glb` suffix) → extOf=null → switch default throws "Unsupported". Per-format smoke had only tested Path A (`?model=`), so CSR blob path was never exercised.
- Fix (surgical): `loadModel(url, renderer, onProgress, nameHint?)` — ext = `extOf(nameHint ?? url) ?? extOf(url)`. ThreeDViewer.loadLocalFile passes `file.name` as nameHint. (modelLoader.ts + ThreeDViewer.ts, 2 lines effective.)
- Verify: NEW tools/dnd-smoke.mjs drives the real CSR path (fetch sample → `new File` → `window.__viewer.loadLocalFile`). tsc 0, build 7.85s exit 0. **7/7 local drops PASS**: Box/Duck/Avocado.glb + Cube.obj/fbx/dae/ifc all load via drag&drop. Closes the previously-untested CSR coverage gap.
- Blocker: none.
## 2026-06-19 — Per-format in-browser load smoke (all 6 formats PASS)
- Did: built dist + `vite preview` :4173; ran tools/perf-smoke.mjs against one sample per format (Box.glb, Cube.obj, Cube.fbx, Cube.dae, Cube.ifc, Duck.optimized.glb) in real headless Chrome — measured nav→`hmw:ready`.
- Result/verify: ALL PASS <3s — Box.glb 229ms, Cube.obj 173ms, Cube.fbx 158ms, Cube.dae 178ms, **Cube.ifc 463ms** (incl web-ifc wasm Init), Duck.optimized.glb 182ms. Closes the per-format runtime gap (SSR `?model=` path renders every format).
- Gotcha: Git-Bash mangled `--asset /samples/..` argv → `C:/Program Files/Git/samples/..` (MSYS path conversion). Fix: prefix `MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'`.
- Blocker: none.
## 2026-06-19 — P7 adaptive downstep empirically verified (tools/adaptive-smoke.mjs)
- Did: built dist + `vite preview` :4173; ran new tools/adaptive-smoke.mjs — headless Chrome (swiftshader software GL) + CDP `Emulation.setCPUThrottlingRate` 6x, loaded heavy ABeautifulGame.ktx2.glb (11.5MB). Watched real #.fps overlay + captured genuine adaptiveQuality console logs (no logic patched).
- Result/verify: FPS overlay rendered real values (19→4 FPS under throttle). Adaptive stepped **tier 0→1→2→3** (pr 1.5→1→0.75) on sustained <60fps; each step after ~60 slow frames per DOWN_FRAMES. Harness exit 0 PASS. The <60fps→optimize path fires on real frame measurement.
- Note (honest): under CPU throttle the bottleneck is JS/geometry, not GPU fill, so lowering pixelRatio barely recovered fps (4→7) — the mechanism fired correctly but pixelRatio is a fill-rate knob; it pays off on GPU-bound scenes. Recovery-up not exercised (would need sustained >72fps).
- Blocker: none.
## 2026-06-19 — Phase 6 + 7 via dynamic workflow `multiformat-viewer` (7 agents, 296k tok)
- Did: extended viewer to OBJ/FBX/DAE/IFC (SSR+CSR) + on-screen FPS + adaptive quality. Run as one dynamic Workflow: Research(3 ‖ read-only: web-ifc API, adaptive-perf, three loader shapes) → Core(viewer-core) → Enhance(FPS ‖ Assets) → Verify(claude). Disjoint file sets per parallel wave (no git repo → no worktree isolation).
- **P6** Core: NEW `src/viewer/modelLoader.ts` — `extOf`/`ACCEPT_EXT`/`loadModel(url,renderer,onProgress)` dispatch by ext, normalize each to `Object3D`. glb/gltf reuse singleton GLTFLoader (unchanged). obj/fbx/dae = lazy dynamic-import three example loaders (separate Vite chunks: OBJ 8.8K / Collada 41K / FBX 48K). ifc = `web-ifc`@0.0.77 IfcAPI, single-thread `/web-ifc/web-ifc.wasm` (SetWasmPath abs=true, Init(undefined,true), NO COOP/COEP), OpenModel(COORDINATE_TO_ORIGIN) → StreamAllMeshes → BufferGeometry per PlacedGeometry (interleaved stride-6 pos+normal, Uint32 index, color/opacity, flatTransformation), `geom.delete()` + `CloseModel` in finally (no wasm leak). ThreeDViewer.loadServerAsset(SSR) + loadLocalFile(CSR) both route through loadModel; onLoaded generalized to Object3D; Blob URL revoked in `.finally()`. dropzone via extOf (6 exts); index.html accept+text; main.ts preview-base widened; copy-decoders stages web-ifc.wasm.
- **P7** FPS+adaptive: NEW `src/ui/fps.ts` (EMA fps, throttled 250ms DOM, color green≥60/orange/red, self-appended overlay). NEW `src/viewer/adaptiveQuality.ts` (pixelRatio ladder [min(DPR,2),1.5,1,0.75,0.5], asymmetric hysteresis: down after 60 frames <60fps, up after 180 frames >72fps, dead-band reset). Wired into ThreeDViewer.animate(now): fps.sample(now)+adaptive.update(fps()). Adaptive owns setPixelRatio (DPR cap from init). rotateTo (prerender) unaffected.
- **Assets**: Cube.obj(793B, hand-authored)/Cube.dae(2.1K)/Cube.fbx(16.2K, three.js morph_test — assimp box.fbx failed in r0.169 FBXLoader)/Cube.ifc(2.3K, hand-authored IFC4 1-wall) in samples/ + public/samples/; 24-frame animated WebP previews per format in public/previews/. prerender.mjs base-name made format-aware.
- Result/verify (independent main-thread re-run): `npx tsc --noEmit` → "No errors found" exit 0. `npm run build` → exit 0 (vite 5.4.21, 11.92s; chunks: app 138K, three vendor 533K, web-ifc 3.5M lazy/code-split — not on initial path). Confirmed via grep: 6 dispatch branches; dropzone EXT_RE all 6; animate wires fps+adaptive; loadServerAsset+loadLocalFile both call loadModel. web-ifc.wasm staged in public/web-ifc/.
- Next: optional — in-browser smoke per format (.ifc/.fbx) via /verify-viewer; OBJ/DAE render geometry-only (external .mtl/textures can't resolve from blob: URL — accepted); document.hidden background-tab guard for FPS (noted, not done).
- Blocker: none.
---
<!-- Append new entries above this line. Format:
## YYYY-MM-DD — <task ID or summary>
- Did: ...
- Result/verify: ...
- Next: ...
- Blocker (if any): ...
-->
+1
View File
@@ -0,0 +1 @@
https://share.gemini.google/rErWUzD9BBdH 읽어라.
+230
View File
@@ -0,0 +1,230 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hmwebviewer — 아키텍처 / 구조 / 계획</title>
<style>
:root { color-scheme: dark; }
* { box-sizing: border-box; }
body {
margin: 0; padding: 32px; max-width: 1000px; margin: 0 auto;
font: 15px/1.6 system-ui, "Segoe UI", "Malgun Gothic", sans-serif;
background: #0f1115; color: #e6e9ef;
}
h1 { font-size: 26px; border-bottom: 2px solid #4aa3ff; padding-bottom: 10px; }
h2 { font-size: 19px; margin-top: 36px; color: #4aa3ff; border-left: 4px solid #4aa3ff; padding-left: 10px; }
h3 { font-size: 16px; margin-top: 24px; color: #cfe3ff; }
code, pre { font-family: "Cascadia Code", Consolas, monospace; }
code { background: #1c2230; padding: 1px 5px; border-radius: 4px; color: #ffd479; font-size: 13px; }
pre { background: #161a24; border: 1px solid #262c3a; border-radius: 8px; padding: 14px; overflow-x: auto; font-size: 13px; }
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 13.5px; }
th, td { border: 1px solid #2a3142; padding: 7px 10px; text-align: left; vertical-align: top; }
th { background: #1a2030; color: #9fc4ff; }
tr:nth-child(even) td { background: #141821; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.card { background: #161a24; border: 1px solid #262c3a; border-radius: 8px; padding: 14px 16px; }
.badge { display: inline-block; padding: 2px 9px; border-radius: 10px; font-size: 12px; font-weight: 600; }
.ok { background: #143a23; color: #57d98a; }
.wip { background: #3a3014; color: #e4b35a; }
.blk { background: #3a1414; color: #e48080; }
.meta { color: #8b93a7; font-size: 13px; }
.arrow { color: #4aa3ff; font-weight: 700; }
.sep { border: 0; border-top: 1px solid #262c3a; margin: 28px 0; }
.yes { color: #57d98a; font-weight: 700; }
.no { color: #e48080; font-weight: 700; }
</style>
</head>
<body>
<h1>hmwebviewer — 3D 뷰어</h1>
<p class="meta">Three.js 기반 하이브리드(SSR + CSR) 웹 3D 모델 뷰어. 설계 원본: <code>3d_viewer_architecture_spec.pdf</code></p>
<p class="meta"><strong>지원 포맷:</strong> GLB · GLTF · OBJ · FBX · DAE(Collada) · IFC(BIM) &nbsp;|&nbsp; <strong>로드 경로:</strong> SSR + CSR &nbsp;|&nbsp; <strong>성능:</strong> 화면 FPS 표시 + 60fps 미만 시 적응형 품질 강등</p>
<p class="meta"><strong>문서 갱신:</strong> <span id="updated">2026-06-19</span> — 매 작업 프롬프트 종료 시 이 파일을 함께 업데이트합니다.</p>
<h2>1. 개요 / 두 가지 로드 경로</h2>
<div class="grid">
<div class="card">
<h3>Path A — 서버 에셋 (SSR + CSR + Hydration)</h3>
<p>서버가 미리 렌더링한 360° Animated WebP 자리표시자 + HTML/CSS 골조를 먼저 보냄(빠른 체감 로드). 뒤에서 <code>modelLoader.loadModel(url)</code>이 확장자로 디스패치하여 실제 모델을 비동기 로드/디코드. WebGL 준비 <strong>and</strong> 모델 로드 완료 둘 다 충족 시 WebP <span class="arrow">→</span> 캔버스로 opacity 0.5s 페이드.</p>
<p class="meta">트리거: <code>?model=&lt;url&gt;</code> · 프리뷰: <code>/previews/&lt;base&gt;.webp</code> (포맷 무관)</p>
</div>
<div class="card">
<h3>Path B — 로컬 파일 (Drag &amp; Drop, CSR)</h3>
<p>사용자가 <code>.glb .gltf .obj .fbx .dae .ifc</code> 드롭. <code>URL.createObjectURL(file)</code> <span class="arrow">→</span> <code>loadModel</code> <span class="arrow">→</span> 디코드 <span class="arrow">→</span> 씬 추가 <span class="arrow">→</span> <code>URL.revokeObjectURL</code>(성공/에러 <code>.finally()</code> 양쪽). 검증: <code>extOf()</code> 확장자.</p>
<p class="meta">트리거: 파일 드롭 / 클릭-탐색</p>
</div>
</div>
<p class="meta">두 경로 모두 단일 디스패처 <code>src/viewer/modelLoader.ts</code>의 <code>loadModel(url, renderer, onProgress)</code>를 거쳐 <code>THREE.Object3D</code>로 정규화 → <code>ThreeDViewer.onLoaded()</code>는 원본 포맷을 알 필요 없음.</p>
<h2>2. 지원 파일 포맷</h2>
<table>
<tr><th>포맷</th><th>확장자</th><th>로더</th><th>비고</th><th>SSR</th><th>CSR</th><th>검증(체감 로드)</th></tr>
<tr>
<td>glTF (Binary/JSON)</td><td><code>.glb .gltf</code></td>
<td>GLTFLoader<br>(+DRACO +KTX2 싱글톤)</td>
<td>Draco 지오메트리 + KTX2/Basis·WebP 텍스처. 기준 경로.</td>
<td class="yes">✔</td><td class="yes">✔</td><td><span class="badge ok">PASS 229ms</span></td>
</tr>
<tr>
<td>Wavefront OBJ</td><td><code>.obj</code></td>
<td>OBJLoader (지연 청크 8.8KB)</td>
<td>지오메트리 전용. <code>blob:</code> URL에선 외부 <code>.mtl</code>/텍스처 미해결 → 기본 머티리얼.</td>
<td class="yes">✔</td><td class="yes">✔</td><td><span class="badge ok">PASS 173ms</span></td>
</tr>
<tr>
<td>Autodesk FBX</td><td><code>.fbx</code></td>
<td>FBXLoader (지연 청크 47.8KB)</td>
<td>바이너리(자체 포함). 애니메이션 가능(<code>.animations</code>) — 현재 정적 표시.</td>
<td class="yes">✔</td><td class="yes">✔</td><td><span class="badge ok">PASS 158ms</span></td>
</tr>
<tr>
<td>COLLADA</td><td><code>.dae</code></td>
<td>ColladaLoader (지연 청크 41KB)</td>
<td>XML(DOMParser 필요). 외부 텍스처는 OBJ와 동일 제약.</td>
<td class="yes">✔</td><td class="yes">✔</td><td><span class="badge ok">PASS 178ms</span></td>
</tr>
<tr>
<td>IFC (BIM)</td><td><code>.ifc</code></td>
<td>web-ifc <code>IfcAPI</code><br>(단일스레드 wasm, 지연 청크 3.5MB)</td>
<td><code>StreamAllMeshes</code> <span class="arrow">→</span> <code>BufferGeometry</code>(인터리브 stride-6 pos+normal). 단일스레드라 <strong>COOP/COEP 헤더 불필요</strong>. <code>geom.delete()</code>+<code>CloseModel</code>로 wasm 메모리 해제.</td>
<td class="yes">✔</td><td class="yes">✔</td><td><span class="badge ok">PASS 463ms</span><br><span class="meta">wasm Init 포함</span></td>
</tr>
</table>
<p class="meta">검증: 빌드 <code>dist</code> + <code>vite preview</code>에 대해 헤드리스 Chrome으로 포맷별 1개 샘플 로드, 네비게이션→<code>hmw:ready</code> 측정. 전체 &lt;3000ms PASS (<code>tools/perf-smoke.mjs</code>).</p>
<h2>3. 기술 스택 (확정)</h2>
<table>
<tr><th>영역</th><th>선택</th><th>비고</th></tr>
<tr><td>렌더러</td><td>three.js WebGLRenderer (r0.169)</td><td>WebGPU는 차후 옵션</td></tr>
<tr><td>로더(기준)</td><td>GLTFLoader + DRACOLoader + KTX2Loader</td><td><strong>각 1개 싱글톤</strong> — 다중 인스턴스 크래시(#22445)</td></tr>
<tr><td>로더(확장)</td><td>OBJ / FBX / Collada — three example loaders</td><td>지연 <code>import()</code> → 별도 Vite 청크(초기 번들 미증가)</td></tr>
<tr><td>로더(IFC)</td><td>web-ifc <code>IfcAPI</code> (v0.0.77)</td><td>단일스레드 <code>web-ifc.wasm</code> 자가호스팅(<code>/web-ifc/</code>), <code>SetWasmPath(...,true)</code></td></tr>
<tr><td>적응형 품질</td><td>FPS 측정 + pixelRatio 사다리</td><td>외부 의존성 0 — <code>ui/fps.ts</code> + <code>viewer/adaptiveQuality.ts</code></td></tr>
<tr><td>지오메트리 압축</td><td>Draco</td><td>~73% 축소 검증됨</td></tr>
<tr><td>텍스처 압축</td><td>WebP(런타임) / KTX2·Basis(고급)</td><td>KTX2는 별도 CLI 단계(<code>toktx</code>)</td></tr>
<tr><td>빌드</td><td>Vite + TypeScript (strict)</td><td>three vendor + 포맷별 로더 청크 분리</td></tr>
<tr><td>디코더 호스팅</td><td><code>public/draco</code> + <code>public/basis</code> + <code>public/web-ifc</code></td><td>postinstall로 자동 복사</td></tr>
<tr><td>에셋 파이프라인</td><td>gltf-transform + draco3d</td><td><code>tools/preprocess.mjs</code> (GLB 전용)</td></tr>
</table>
<h2>4. 런타임 파일 구조</h2>
<pre>src/
main.ts # 진입점 — ThreeDViewer + Dropzone 와이어링, ?model= Path A
viewer/
loaders.ts # getLoaders() 싱글톤 (GLTF+DRACO+KTX2)
modelLoader.ts # ★ loadModel() 포맷 디스패치 (glb/gltf/obj/fbx/dae/ifc) → Object3D
ThreeDViewer.ts # 핵심 클래스: 렌더러/씬/카메라/OrbitControls/로드/dispose + FPS·적응형 와이어링
adaptiveQuality.ts # ★ pixelRatio 사다리 + 히스테리시스 (60fps↓ 강등)
hydration.ts # createHydrationGate() — 양쪽 마크 시 페이드
dnd/dropzone.ts # HTML5 DnD + 클릭탐색 + extOf 검증 → loadLocalFile
ui/
progress.ts # showProgress/hideProgress/setProgress/setStatus
fps.ts # ★ 화면 FPS 오버레이 (EMA, 색상코딩)
public/
draco/ basis/ # 디코더 WASM (자동 복사)
web-ifc/ # ★ web-ifc.wasm (단일스레드, 자동 복사)
samples/ # Box/Duck/Avocado/Duck.optimized + Cube.obj/dae/fbx/ifc
previews/ # Duck.webp + Cube.{obj,dae,fbx,ifc}.webp (24프레임 360°)
tools/
preprocess.mjs # gltf-transform Draco+WebP 압축 (GLB 전용)
copy-decoders.mjs # postinstall: draco+basis+web-ifc 복사
prerender.mjs # 360° 턴테이블 → Animated WebP (포맷 무관)
perf-smoke.mjs # 헤드리스 체감 로드 측정 (&lt;3s)
adaptive-smoke.mjs # ★ CPU 스로틀로 60fps↓ 강등 실증
</pre>
<h2>5. 멀티에이전트 + 다이나믹 워크플로우</h2>
<p>에이전트가 <code>PLAN.md</code> + <code>PROGRESS.md</code>를 매 시작 시 읽어 다음 작업을 결정. 병렬 안전(파일 스코프 분리).</p>
<p class="card"><strong>Phase 6/7은 다이나믹 Workflow <code>multiformat-viewer</code>로 구현</strong> (7 에이전트, 296k 토큰): Research(3 병렬·읽기전용: web-ifc API / 적응형 성능 / three 로더 형태) <span class="arrow">→</span> Core(viewer-core) <span class="arrow">→</span> Enhance(FPS ‖ 에셋) <span class="arrow">→</span> Verify(빌드 green). git 미사용 환경이라 worktree 격리 대신 병렬 웨이브별 파일 스코프 분리로 충돌 방지.</p>
<div class="grid">
<div>
<h3>에이전트</h3>
<table>
<tr><th>이름</th><th>역할</th></tr>
<tr><td>task-lead</td><td>PLAN/PROGRESS 읽고 다음 작업 분배</td></tr>
<tr><td>viewer-core</td><td>src/viewer/* 씬·로더·하이드레이션·FPS</td></tr>
<tr><td>dnd-handler</td><td>src/dnd/* 드래그드롭·검증</td></tr>
<tr><td>asset-pipeline</td><td>tools/* 압축·프리렌더·샘플</td></tr>
<tr><td>hydration</td><td>SSR 자리표시자→캔버스 페이드</td></tr>
<tr><td>reviewer</td><td>누수/에러/성능 감사(읽기 위주)</td></tr>
</table>
</div>
<div>
<h3>명령 / 스킬 / 훅</h3>
<p><strong>명령:</strong> <code>/bootstrap</code> <code>/next-task</code> <code>/report</code> <code>/optimize-asset</code> <code>/verify-viewer</code></p>
<p><strong>스킬:</strong> <code>threejs-viewer</code>(로드 패턴), <code>task-graph</code>(조정 프로토콜)</p>
<p><strong>훅:</strong> <code>session-start.sh</code>(PLAN/PROGRESS 읽기 알림), <code>progress-nudge.sh</code>(src 편집 시 알림)</p>
<p class="meta">CLAUDE.md가 두 파일 선독을 강제 + 에이전트/스킬/명령 매핑표 + 세션 런북 포함.</p>
</div>
</div>
<h2>6. 계획 진행 상태 (PLAN.md)</h2>
<table>
<tr><th>Phase</th><th>범위</th><th>상태</th></tr>
<tr><td>P0 Bootstrap</td><td>Vite/TS 셋업, 디코더, 기본 씬</td><td><span class="badge ok">done · 빌드 검증</span></td></tr>
<tr><td>P1 Core 뷰어</td><td>싱글톤 로더, loadServer/Local, OrbitControls, 프레이밍</td><td><span class="badge ok">done · 빌드 검증</span></td></tr>
<tr><td>P2 Drag &amp; Drop</td><td>드롭존 UI, 검증, loadLocalFile 와이어링</td><td><span class="badge ok">done · 빌드 검증</span></td></tr>
<tr><td>P3 Hydration</td><td>WebP 자리표시자, 게이트, UI 토글</td><td><span class="badge ok">done</span></td></tr>
<tr><td>P4 에셋 파이프라인</td><td>preprocess.mjs, 샘플셋, 프리렌더</td><td><span class="badge ok">P4-1/2/3 done</span></td></tr>
<tr><td>P5 Hardening</td><td>누수/에러 감사, 퍼포먼스, 리뷰</td><td><span class="badge ok">P5-1/2/3/4 done</span> 0 critical</td></tr>
<tr><td>P6 멀티포맷 로더</td><td>OBJ/FBX/DAE/IFC, SSR+CSR, modelLoader 디스패치, 샘플+프리뷰</td><td><span class="badge ok">P6-1..5 done</span> 6포맷 검증</td></tr>
<tr><td>P7 FPS + 적응형 품질</td><td>화면 FPS, pixelRatio 사다리, animate 와이어링</td><td><span class="badge ok">P7-1/2/3 done</span> 강등 실증</td></tr>
</table>
<h2>7. FPS 표시 + 적응형 품질 (&lt;60fps → 최적화)</h2>
<div class="card">
<ul>
<li><strong>FPS 오버레이</strong> (<code>ui/fps.ts</code>): 좌상단, 프레임 델타 EMA 평활, ~250ms 스로틀 DOM 갱신. 색상 — <span class="yes">≥60 녹색</span> / 30–59 주황 / <span class="no">&lt;30 빨강</span>.</li>
<li><strong>적응형 사다리</strong> (<code>viewer/adaptiveQuality.ts</code>): pixelRatio 5단계 <code>[min(DPR,2) · 1.5 · 1 · 0.75 · 0.5]</code>. 가장 고효율·저위험 런타임 노브(드로잉 버퍼 재할당, CSS 리플로우 없음).</li>
<li><strong>히스테리시스</strong>(진동 방지): 평균 &lt;60fps가 <strong>60프레임 지속</strong> 시 1단계 강등 / 평균 &gt;72fps가 <strong>180프레임 지속</strong> 시 1단계 복귀 / 60–72 데드밴드는 카운터 리셋.</li>
<li><strong>실증</strong>(<code>tools/adaptive-smoke.mjs</code>): 헤드리스 swiftshader + CDP CPU 스로틀 6×로 ABeautifulGame(11.5MB) 로드 → 실측 FPS <strong>19→4</strong>, 적응형 <strong>tier 0→1→2→3</strong> (pr 1.5→1→0.75) 로그 캡처. 로직 미패치, 실제 프레임 측정으로 발화.</li>
</ul>
<p class="meta">참고: CPU 스로틀 하에선 병목이 JS/지오메트리(필레이트 아님)라 pixelRatio 강등의 FPS 회복폭이 작음(4→7) — 메커니즘은 정상 발화하나, 이 노브는 GPU 필레이트 병목 씬에서 효과가 큼.</p>
</div>
<h2>8. 현재 구현 상태</h2>
<div class="card">
<ul>
<li><strong>빌드:</strong> <code>tsc --noEmit</code> 클린, <code>npm run build</code> 통과 (vite 5.4.21, 11.92s). 청크: 앱 138KB(gz 47) · three vendor 533KB(gz 136) · web-ifc 3.5MB(gz 398, <em>지연 로드</em>) · OBJ 8.8 / Collada 41 / FBX 48KB.</li>
<li><strong>포맷별 인브라우저 로드(헤드리스 Chrome):</strong> GLB 229ms · OBJ 173ms · FBX 158ms · DAE 178ms · <strong>IFC 463ms</strong> · Duck.optimized 182ms — 전체 &lt;3000ms PASS.</li>
<li><strong>적응형 품질:</strong> 60fps 미만 지속 시 pixelRatio 3단계 강등 실측 확인(위 7절).</li>
<li><strong>샘플:</strong> Box/Duck/Avocado/Duck.optimized(GLB) + Cube.obj(793B)/Cube.dae(2.1K)/Cube.fbx(16.2K)/Cube.ifc(2.3K). 각 24프레임 360° WebP 프리뷰.</li>
<li><strong>KTX2 런타임 경로:</strong> KTX2Loader+DRACOLoader 동시 디코드 확인(ABeautifulGame, 626ms). 인코딩은 toktx 필요(미설치).</li>
<li><strong>하드닝:</strong> WebGL 미지원 가드, 로드 실패 메시지(<code>#status</code>), DnD 검증 피드백, IFC wasm 메모리 해제 — reviewer 0 critical.</li>
</ul>
</div>
<h2>9. 실행 명령</h2>
<pre># 개발 서버 (HMR)
npm run dev # → http://127.0.0.1:3333
# 프로덕션 빌드 + 미리보기
npm run build # tsc --noEmit && vite build → dist/
npm run preview # → http://127.0.0.1:4173
# 사용
# CSR(Path B): .glb .gltf .obj .fbx .dae .ifc 를 드롭존에 드롭 / 클릭 선택
# SSR(Path A): http://127.0.0.1:4173/?model=/samples/Cube.ifc
# 검증/도구 (Git-Bash는 MSYS_NO_PATHCONV=1 접두 필요)
node tools/perf-smoke.mjs # 포맷별 체감 로드 <3s
node tools/adaptive-smoke.mjs --throttle 6 --seconds 30 # 60fps↓ 강등 실증
node tools/preprocess.mjs &lt;file.glb&gt; # Draco+WebP 압축(GLB)
node tools/prerender.mjs # 360° WebP 프리뷰 생성
</pre>
<h2>10. 다음 단계 (선택)</h2>
<ul>
<li>✅ 전체 PLAN 완료 (P0–P7). 6포맷 로드 · SSR+CSR · 화면 FPS · 적응형 품질 전부 인브라우저 실증.</li>
<li>OBJ/DAE 외부 텍스처: 다중 파일 드롭(.obj+.mtl+이미지) 또는 zip 수용 — 현재는 <code>blob:</code> 제약으로 지오메트리 전용.</li>
<li>FBX/Collada 애니메이션: <code>AnimationMixer</code> 재생(현재 정적). <code>document.hidden</code> 배경탭 FPS 가드.</li>
<li>KTX2 인코딩 파이프라인: KTX-Software(toktx) 설치 후 <code>gltf-transform etc1s|uastc</code>.</li>
<li>실서버 SSR 연결 + 실기기 체감 로드 측정(현재 수치는 헤드리스).</li>
</ul>
<hr class="sep" />
<p class="meta">작업 로그: <code>PROGRESS.md</code> · 작업 그래프: <code>PLAN.md</code> · 지침: <code>CLAUDE.md</code></p>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hmwebviewer — 3D Viewer</title>
<link rel="stylesheet" href="/src/style.css" />
</head>
<body>
<div id="app">
<!-- SSR placeholder: pre-rendered 360 WebP fades out on hydration -->
<img id="preview" class="preview hidden" alt="3D preview placeholder" />
<div id="viewer" class="viewer"></div>
<div id="controls" class="controls">
<button id="btn-fit" type="button">Zoom Fit</button>
<button id="btn-persp" type="button" class="active">원근뷰</button>
<button id="btn-ortho" type="button">직교뷰</button>
<button id="btn-outline" type="button">테두리</button>
</div>
<div id="progress" class="progress hidden"><div class="bar"></div></div>
<div id="dropzone" class="dropzone">
<p>Drop a <strong>.glb .gltf .obj .fbx .dae .ifc .ply</strong> (+ .mtl) here, or click to browse</p>
<p id="status" class="status"></p>
<input id="file-input" type="file" accept=".glb,.gltf,.obj,.fbx,.dae,.ifc,.ply,.mtl" multiple hidden />
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+4304
View File
File diff suppressed because it is too large. Load diff
+28
View File
@@ -0,0 +1,28 @@
{
"name": "hmwebviewer",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"postinstall": "node tools/copy-decoders.mjs"
},
"dependencies": {
"three": "^0.169.0",
"web-ifc": "^0.0.77"
},
"devDependencies": {
"@gltf-transform/cli": "^4.4.0",
"@gltf-transform/core": "^4.4.0",
"@gltf-transform/extensions": "^4.4.0",
"@gltf-transform/functions": "^4.4.0",
"@types/three": "^0.169.0",
"draco3d": "^1.5.7",
"puppeteer-core": "^25.1.0",
"typescript": "^5.6.0",
"vite": "^5.4.0"
}
}
+46
View File
@@ -0,0 +1,46 @@
# Basis Universal GPU Texture Compression
Basis Universal is a "[supercompressed](http://gamma.cs.unc.edu/GST/gst.pdf)"
GPU texture and texture video compression system that outputs a highly
compressed intermediate file format (.basis) that can be quickly transcoded to
a wide variety of GPU texture compression formats.
[GitHub](https://github.com/BinomialLLC/basis_universal)
## Transcoders
Basis Universal texture data may be used in two different file formats:
`.basis` and `.ktx2`, where `ktx2` is a standardized wrapper around basis texture data.
For further documentation about the Basis compressor and transcoder, refer to
the [Basis GitHub repository](https://github.com/BinomialLLC/basis_universal).
The folder contains two files required for transcoding `.basis` or `.ktx2` textures:
* `basis_transcoder.js` — JavaScript wrapper for the WebAssembly transcoder.
* `basis_transcoder.wasm` — WebAssembly transcoder.
Both are dependencies of `KTX2Loader`:
```js
const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath( 'examples/jsm/libs/basis/' );
ktx2Loader.detectSupport( renderer );
ktx2Loader.load( 'diffuse.ktx2', function ( texture ) {
const material = new THREE.MeshStandardMaterial( { map: texture } );
}, function () {
console.log( 'onProgress' );
}, function ( e ) {
console.error( e );
} );
```
## License
[Apache License 2.0](https://github.com/BinomialLLC/basis_universal/blob/master/LICENSE)
+19
View File
@@ -0,0 +1,19 @@
var BASIS = (() => {
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
if (typeof __filename != 'undefined') _scriptName ||= __filename;
return (
function(moduleArg = {}) {
var moduleRtn;
var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);var ret=fs.readFileSync(filename);return ret};readAsync=(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return new Promise((resolve,reject)=>{fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)reject(err);else resolve(binary?data.buffer:data)})})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=url=>{if(isFileURI(url)){return new Promise((reject,resolve)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response)}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}return fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))})}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataLine truncated
return moduleRtn;
}
);
})();
if (typeof exports === 'object' && typeof module === 'object')
module.exports = BASIS;
else if (typeof define === 'function' && define['amd'])
define([], () => BASIS);
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
var DracoDecoderModule = (() => {
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;
return (
function(DracoDecoderModule = {}) {
var Module=typeof DracoDecoderModule!="undefined"?DracoDecoderModule:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var isRuntimeInitialized=false;var isModuleParsed=false;Module["onRuntimeInitialized"]=function(){isRuntimeInitialized=true;if(isModuleParsed){if(typeof Module["onModuleLoaded"]==="function"){Module["onModuleLoaded"](Module)}}};Module["onModuleParsed"]=function(){isModuleParsed=true;if(isRuntimeInitialized){if(typeof Module["onModuleLoaded"]==="function"){Module["onModuleLoaded"](Module)}}};function isVersionSupported(versionString){if(typeof versionString!=="string")return false;const version=versionString.split(".");if(version.length<2||version.length>3)return false;if(version[0]==1&&version[1]>=0&&version[1]<=5)return true;if(version[0]!=0||version[1]>10)return false;return true}Module["isVersionSupported"]=isVersionSupported;var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");if(ENVIRONMENT_IS_WORKER){scriptDirectory=nodePath.dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=(filename,binary)=>{var ret=tryParseAsDataURI(filename);if(ret){return binary?ret:ret.toString()}filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{var ret=tryParseAsDataURI(filename);if(ret){onload(ret)}filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText}catch(err){var data=tryParseAsDataURI(url);if(data){return intArrayToString(data)}throw err}};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}catch(err){var data=tryParseAsDataURI(url);if(data){return data}throw err}}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}var data=tryParseAsDataURI(url);if(data){onload(data.buffer);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;var WebAssembly={Memory:function(opts){this.buffer=new ArrayBuffer(opts["initial"]*65536)},Module:function(binary){},Instance:function(module,info){this.exports=(
// EMSCRIPTEN_START_ASM
function instantiate(ia){function c(d){d.set=function(a,b){this[a]=b};d.get=function(a){return this[a]};return d}var e;var f=new Uint8Array(123);for(var a=25;a>=0;--a){f[48+a]=52+a;f[65+a]=a;f[97+a]=26+a}f[43]=62;f[47]=63;function l(m,n,o){var g,h,a=0,i=n,j=o.length,k=n+(j*3>>2)-(o[j-2]=="=")-(o[j-1]=="=");for(;a<j;a+=4){g=f[o.charCodeAt(a+1)];h=f[o.charCodeAt(a+2)];m[i++]=f[o.charCodeAt(a)]<<2|g>>4;if(i<k)m[i++]=g<<4|h>>2;if(i<k)m[i++]=h<<6|f[o.charCodeAt(a+3)]}}function p(q){l(e,1028,"YAQAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAADQAAAE41ZHJhY28yOEF0dHJpYnV0ZU9jdGFoZWRyb25UcmFuc2Zvcm1FAADYKwAAOAQAAOAGAABfX25leHRfcHJpbWUgb3ZlcmZsb3cAdmVjdG9yAHN0ZDo6ZXhjZXB0aW9uAHNraXBfYXR0cmlidXRlX3RyYW5zZm9ybQBiYWRfYXJyYXlfbmV3X2xlbmd0aABiYXNpY19zdHJpbmcAc3RkOjpiYWRfYWxsb2MARFJBQ08AVXNpbmcgaW5jb21wYXRpYmxlIGRlY29kZXIgZm9yIHRoZSBpbnB1dCBnZW9tZXRyeS4ARmFpbGVkIHRvIGRlY29kZSBwb2ludCBhdHRyaWJ1dGVzLgBGYWlsZWQgdG8gaW5pdGlhbGl6ZSB0aGUgZGVjb2Rlci4AVW5zdXBwb3J0ZWQgbWlub3IgdmVyc2lvbi4AVW5zdXBwb3J0ZWQgbWFqb3IgdmVyc2lvbi4ASW5wdXQgaXMgbm90IGEgbWVzaC4AVW5zdXBwb3J0ZWQgZ2VvbWV0cnkgdHlwZS4ATm90IGEgRHJhY28gZmlsZS4AVW5zdXBwb3J0ZWQgZW5jb2RpbmcgbWV0aG9kLgBGYWlsZWQgdG8gZGVjb2RlIG1ldGFkYXRhLgBGYWlsZWQgdG8gZGVjb2RlIGdlb21ldHJ5IGRhdGEuAFB1cmUgdmlydHVhbCBmdW5jdGlvbiBjYWxsZWQhAAAAAAAAALQGAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAAFgAAABcAAAALAAAAGAAAABkAAABONWRyYWNvMzBBdHRyaWJ1dGVRdWFudGl6YXRpb25UcmFuc2Zvcm1FAAAAANgrAACIBgAA4AYAAE41ZHJhY28xOEF0dHJpYnV0ZVRyYW5zZm9ybUUAAAAAsCsAAMAGAAAAAAAAbAcAABoAAAAbAAAAHAAAAB0AAAAeAAAAHwAAACAAAAAhAAAAIgAAACMAAAAkAAAAJQAAAE41ZHJhY28xN0F0dHJpYnV0ZXNEZWNvZGVyRQBONWRyYWNvMjZBdHRyaWJ1dGVzRGVjb2RlckludGVyZmFjZUUAAAAAsCsAADwHAADYKwAAIAcAAGQHAAAAAAAAzAcAACYAAAAnAAAAKAAAACkAAAAqAAAAKwAAACwAAAAtAAAALgAAAE41ZHJhY28yNlNlcXVlbnRpYWxBdHRyaWJ1dGVEZWNvZGVyRQAAAACwKwAApAcAAP////8AAAAARAgAAC8AAAAwAAAAHAAAADEAAAAyAAAAHwAAACAAAAAhAAAAMwAAADQAAAA1AAAANgAAADcAAABONWRyYWNvMzdTZXF1ZW50aWFsQXR0cmlidXRlRGVjb2RlcnNDb250cm9sbGVyRQDYKwAAFAgAAGwHAAAAAAAAuAgAADgAAAA5AAAAOgAAACkAAAAqAAAAKwAAADsAAAAtAAAAPAAAAD0AAAA+AAAAPwAAAEAAAABONWRyYWNvMzNTZXF1ZW50aWFsSW50ZWdlckF0dHJpYnV0ZURlY29kZXJFANgrAACMCAAAzAcAAAAAAABECwAAQQAAAEIAAABDAAAARAAAAEUAAABGAAAARwAAAEgAAABJAAAASgAAAEsAAABMAAAATjVkcmFjbzQwTWVzaFByZWRpY3Rpb25TY2hlbWVQYXJhbGxlbG9ncmFtRGVjb2RlcklpTlNfMzdQcmVkaWN0aW9uU2NoZW1lV3JhcERlY29kaW5nVHJhbnNmb3JtSWlpRUVOU18yNE1lc2hQcmVkaWN0aW9uU2NoZW1lRGF0YUlOU18yNE1lc2hBdHRyaWJ1dGVDb3JuZXJUYWJsZUVFRUVFAE41ZHJhY28yN01lc2hQcmVkaWN0aW9uU2NoZW1lRGVjb2RlcklpTlNfMzdQcmVkaWN0aW9uU2NoZW1lV3JhcERlY29kaW5nVHJhbnNmb3JtSWlpRUVOU18yNE1lc2hQcmVkaWN0aW9uU2NoZW1lRGF0YUlOU18yNE1lc2hBdHRyaWJ1dGVDb3JuZXJUYWJsZUVFRUVFAE41ZHJhY28yM1ByZWRpY3Rpb25TY2hlbWVEZWNvZGVySWlOU18zN1ByZWRpY3Rpb25TY2hlbWVXcmFwRGVjb2RpbmdUcmFuc2Zvcm1JaWlFRUVFAE41ZHJhY28zN1ByZWRpY3Rpb25TY2hlbWVUeXBlZERlY29kZXJJbnRlcmZhY2VJaWlFRQBONWRyYWNvMzJQcmVkaWN0aW9uU2NoZW1lRGVjb2RlckludGVyZmFjZUUATjVkcmFjbzI1UHJlZGljdGlvblNjaGVtZUludGVyZmFjZUUAsCsAAOgKAADYKwAAvQoAAAwLAADYKwAAiQoAABQLAADYKwAANQoAACALAADYKwAAnwkAACwLAADYKwAA/AgAADgLAAAAAAAAOAsAAEEAAABNAAAAIwAAAEQAAAAjAAAARgAAAEcAAABIAAAASQAAAEoAAABLAAAAIwAAAAAAAAAsCwAAQQAAAE4AAAAjAAAARAAAACMAAABGAAAARwAAAEgAAABJAAAASgAAAEsAAAAjAAAAAAAAAKwMAABPAAAAUAAAAFEAAABEAAAAUgAAAEYAAABHAAAASAAAAEkAAABKAAAAUwAAAFQAAABONWRyYWNvNTZNZXNoUHJlZGljdGlvblNjaGVtZUNvbnN0cmFpbmVkTXVsdGlQYXJhbGxlbG9ncmFtRGVjb2RlcklpTlNfMzdQcmVkaWN0aW9uU2NoZW1lV3JhcERlY29kaW5nVHJhbnNmb3JtSWlpRUVOU18yNE1lc2hQcmVkaWN0aW9uU2NoZW1lRGF0YUlOU18yNE1lc2hBdHRyaWJ1dGVDb3JuZXJUYWJsZUVFRUVFAADYKwAA+AsAADgLAAAAAAAAmA0AAFUAAABWAAAAVwAAAEQAAABYAAAAWQAAAFoAAABbAAAASQAAAEoAAABcAAAAXQAAAE41ZHJhY280NE1lc2hQcmVkaWN0aW9uU2NoZW1lVGV4Q29vcmRzUG9ydGFibGVEZWNvZGVySWlOU18zN1ByZWRpY3Rpb25TY2hlbWVXcmFwRGVjb2RpbmdUcmFuc2Zvcm1JaWlFRU5TXzI0TWVzaFByZWRpY3Rpb25TY2hlbWVEYXRhSU5TXzI0TWVzaEF0dHJpYnV0ZUNvcm5lclRhYmxlRUVFRUUAANgrAADwDAAAOAsAAAAAAACEDgAAXgAAAF8AAABgAAAARAAAAGEAAABiAAAAYwAAAGQAAABJAAAASgAAAGUAAABmAAAATjVkcmFjbzQyTWVzaFByZWRpY3Rpb25TY2hlbWVHZW9tZXRyaWNOb3JtYWxEZWNvZGVySWlOU18zN1ByZWRpY3Rpb25TY2hlbWVXcmFwRGVjb2RpbmdUcmFuc2Zvcm1JaWlFRU5TXzI0TWVzaFByZWRpY3Rpb25TY2hlbWVEYXRhSU5TXzI0TWVzaEF0dHJpYnV0ZUNvcm5lclRhYmxlRUVFRUUAAAAA2CsAANwNAAA4CwAAAAAAAAwQAABnAAAAaAAAAGkAAABqAAAAawAAAE41ZHJhY280OE1lc2hQcmVkaWN0aW9uU2NoZW1lR2VvbWV0cmljTm9ybWFsUHJlZGljdG9yQXJlYUlpTlNfMzdQcmVkaWN0aW9uU2NoZW1lV3JhcERlY29kaW5nVHJhbnNmb3JtSWlpRUVOU18yNE1lc2hQcmVkaWN0aW9uU2NoZW1lRGF0YUlOU18yNE1lc2hBdHRyaWJ1dGVDb3JuZXJUYWJsZUVFRUVFAE41ZHJhY280OE1lc2hQcmVkaWN0aW9uU2NoZW1lR2VvbWV0cmljTm9ybWFsUHJlZGljdG9yQmFzZUlpTlNfMzdQcmVkaWN0aW9uU2NoZW1lV3JhcERlY29kaW5nVHJhbnNmb3JtSWlpRUVOU18yNE1lc2hQcmVkaWN0aW9uU2NoZW1lRGF0YUlOU18yNE1lc2hBdHRyaWJ1dGVDb3JuZXJUYWJsZUVFRUVFAAAAsCsAAFcPAADYKwAArA4AAAQQAAAAAAAABBAAAGcAAABsAAAAIwAAAGoAAAAjAAAAAAAAAJgRAABBAAAAbQAAAG4AAABEAAAAbwAAAEYAAABHAAAASAAAAEkAAABKAAAASwAAAHAAAABONWRyYWNvNDBNZXNoUHJlZGljdGlvblNjaGVtZVBhcmFsbGVsb2dyYW1EZWNvZGVySWlOU18zN1ByZWRpY3Rpb25TY2hlbWVXcmFwRGVjb2RpbmdUcmFuc2Zvcm1JaWlFRU5TXzI0TWVzaFByZWRpY3Rpb25TY2hlbWVEYXRhSU5TXzExQ29ybmVyVGFibGVFRUVFRQBONWRyYWNvMjdNZXNoUHJlZGljdGlvblNjaGVtZURlY29kZXJJaU5TXzM3UHJlZGljdGlvblN Line truncated
// EMSCRIPTEN_START_FUNCS
function mc(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;if(!a){return 1}e=F[c+20>>2];o=F[c+12>>2];g=F[c+16>>2];a:{if((e|0)>=(o|0)&g>>>0>=I[c+8>>2]|(e|0)>(o|0)){break a}o=G[g+F[c>>2]|0];g=g+1|0;e=g?e:e+1|0;F[c+16>>2]=g;F[c+20>>2]=e;b:{switch(o|0){case 0:e=a;f=b;g=d;a=0;d=0;j=Z+-64|0;Z=j;F[j+56>>2]=0;F[j+48>>2]=0;F[j+52>>2]=0;F[j+40>>2]=0;F[j+44>>2]=0;F[j+32>>2]=0;F[j+36>>2]=0;F[j+24>>2]=0;F[j+28>>2]=0;F[j+16>>2]=0;F[j+20>>2]=0;F[j+8>>2]=0;F[j+12>>2]=0;c:{if(!Nd(j+8|0,c)){break c}if(!Md(j+8|0,c)|(F[j+20>>2]?0:e)){break c}hc(c,0,0);if(e){s=f<<2;t=F[j+36>>2];u=F[j+48>>2];x=F[j+24>>2];l=F[j+56>>2];i=F[j+52>>2];while(1){d:{if(l>>>0>16383){break d}while(1){if((i|0)<=0){break d}i=i-1|0;F[j+52>>2]=i;l=G[i+u|0]|l<<8;F[j+56>>2]=l;if(l>>>0<16384){continue}break}}a=l&4095;r=F[(a<<2)+x>>2];b=(r<<3)+t|0;l=(L(F[b>>2],l>>>12|0)+a|0)-F[b+4>>2]|0;F[j+56>>2]=l;if((f|0)>0){a=0;if(!G[c+36|0]|r>>>0>32){break c}o=d+f|0;e:{if(!r){ma(g+(d<<2)|0,0,s);break e}y=r&-2;z=r&1;b=F[c+32>>2];h=F[c+28>>2];q=F[c+24>>2];while(1){k=0;a=b;m=0;n=0;if((r|0)!=1){while(1){p=q+(a>>>3|0)|0;f:{if(p>>>0>=h>>>0){p=0;break f}p=G[p|0];b=a+1|0;F[c+32>>2]=b;p=p>>>(a&7)&1;a=b}p=p<<k|m;m=0;v=q+(a>>>3|0)|0;if(v>>>0<h>>>0){m=G[v|0];b=a+1|0;F[c+32>>2]=b;m=m>>>(a&7)&1;a=b}v=k|1;k=k+2|0;m=p|m<<v;n=n+2|0;if((y|0)!=(n|0)){continue}break}}n=g+(d<<2)|0;if(z){p=q+(a>>>3|0)|0;if(p>>>0<h>>>0){p=G[p|0];b=a+1|0;F[c+32>>2]=b;a=p>>>(a&7)&1}else{a=0}m=a<<k|m}F[n>>2]=m;d=d+1|0;if((o|0)!=(d|0)){continue}break}}d=o}w=f+w|0;if(e>>>0>w>>>0){continue}break}}D[c+36|0]=0;f=F[c+20>>2];a=0;m=F[c+32>>2]+7|0;a=m>>>0<7?1:a;m=a<<29|m>>>3;b=m+F[c+16>>2]|0;a=(a>>>3|0)+f|0;F[c+16>>2]=b;F[c+20>>2]=b>>>0<m>>>0?a+1|0:a;a=1}b=F[j+36>>2];if(b){F[j+40>>2]=b;ja(b)}b=F[j+24>>2];if(b){F[j+28>>2]=b;ja(b)}b=F[j+8>>2];if(b){F[j+12>>2]=b;ja(b)}Z=j- -64|0;return a;case 1:break b;default:break a}}b=0;e=F[c+20>>2];o=F[c+12>>2];g=F[c+16>>2];g:{if((e|0)>=(o|0)&g>>>0>=I[c+8>>2]|(e|0)>(o|0)){break g}o=G[g+F[c>>2]|0];g=g+1|0;e=g?e:e+1|0;F[c+16>>2]=g;F[c+20>>2]=e;h:{switch(o-1|0){case 8:o=a;r=d;e=Z+-64|0;Z=e;F[e+56>>2]=0;F[e+48>>2]=0;F[e+52>>2]=0;F[e+40>>2]=0;F[e+44>>2]=0;F[e+32>>2]=0;F[e+36>>2]=0;F[e+24>>2]=0;F[e+28>>2]=0;F[e+16>>2]=0;F[e+20>>2]=0;F[e+8>>2]=0;F[e+12>>2]=0;h=e+8|0;i:{j:{if(!H[c+38>>1]){break j}if(!Ta(1,h+12|0,c)){break j}b=F[c+8>>2];d=F[c+16>>2];f=b-d|0;i=F[h+12>>2];b=F[c+12>>2]-(F[c+20>>2]+(b>>>0<d>>>0)|0)|0;if(f>>>0<i>>>6>>>0&(b|0)<=0|(b|0)<0){break j}b=F[h>>2];a=F[h+4>>2]-b>>2;k:{if(a>>>0<i>>>0){qa(h,i-a|0);i=F[h+12>>2];break k}if(a>>>0<=i>>>0){break k}F[h+4>>2]=b+(i<<2)}g=1;if(!i){break i}f=F[c+16>>2];d=F[c+20>>2];s=F[h>>2];j=F[c+8>>2];n=F[c+12>>2];b=0;while(1){g=0;if((d|0)>=(n|0)&f>>>0>=j>>>0|(d|0)>(n|0)){break i}g=F[c>>2];p=G[g+f|0];a=d;f=f+1|0;a=f?a:a+1|0;F[c+16>>2]=f;d=a;F[c+20>>2]=a;a=p>>>2|0;l=0;l:{m:{n:{o:{t=p&3;switch(t|0){case 0:break m;case 3:break o;default:break n}}a=a+b|0;g=0;if(a>>>0>=i>>>0){break i}ma(s+(b<<2)|0,0,(p&252)+4|0);b=a;break l}while(1){if((f|0)==(j|0)&(d|0)==(n|0)){break j}i=G[f+g|0];f=f+1|0;d=f?d:d+1|0;F[c+16>>2]=f;F[c+20>>2]=d;a=i<<(l<<3|6)|a;l=l+1|0;if((t|0)!=(l|0)){continue}break}}F[s+(b<<2)>>2]=a}b=b+1|0;i=F[h+12>>2];if(b>>>0<i>>>0){continue}break}a=h+16|0;n=F[h>>2];d=F[h+16>>2];b=F[h+20>>2]-d|0;p:{if(b>>>0<=32767){qa(a,8192-(b>>>2|0)|0);break p}if((b|0)==32768){break p}F[h+20>>2]=d+32768}d=h+28|0;b=F[d>>2];f=F[h+32>>2]-b>>3;q:{if(f>>>0<i>>>0){_a(d,i-f|0);b=F[d>>2];break q}if(f>>>0>i>>>0){F[h+32>>2]=(i<<3)+b}if(!i){break j}}j=F[a>>2];f=0;d=0;while(1){g=n+(f<<2)|0;l=F[g>>2];h=(f<<3)+b|0;a=d;F[h+4>>2]=a;F[h>>2]=l;g=F[g>>2];d=g+a|0;if(d>>>0>8192){break j}r:{if(a>>>0>=d>>>0){break r}l=0;h=g&7;if(h){while(1){F[j+(a<<2)>>2]=f;a=a+1|0;l=l+1|0;if((h|0)!=(l|0)){continue}break}}if(g-1>>>0<=6){break r}while(1){g=j+(a<<2)|0;F[g>>2]=f;F[g+28>>2]=f;F[g+24>>2]=f;F[g+20>>2]=f;F[g+16>>2]=f;F[g+12>>2]=f;F[g+8>>2]=f;F[g+4>>2]=f;a=a+8|0;if((d|0)!=(a|0)){continue}break}}f=f+1|0;if((i|0)!=(f|0)){continue}break}k=(d|0)==8192}g=k}s:{if(!g|(F[e+20>>2]?0:o)){break s}d=0;k=Z-16|0;Z=k;t:{if(!Sa(1,k+8|0,c)){break t}a=F[c+8>>2];f=F[c+16>>2];g=a-f|0;j=F[k+12>>2];i=F[c+20>>2];a=F[c+12>>2]-(i+(a>>>0<f>>>0)|0)|0;b=F[k+8>>2];if((j|0)==(a|0)&g>>>0<b>>>0|a>>>0<j>>>0){break t}a=i+j|0;g=b+f|0;a=g>>>0<f>>>0?a+1|0:a;F[c+16>>2]=g;F[c+20>>2]=a;if((b|0)<=0){break t}a=f+F[c>>2]|0;F[e+48>>2]=a;c=b-1|0;f=c+a|0;g=G[f|0];u:{if(g>>>0<=63){F[e+52>>2]=c;a=G[f|0]&63;break u}v:{switch((g>>>6|0)-1|0){case 0:if(b>>>0<2){break t}b=b-2|0;F[e+52>>2]=b;a=a+b|0;a=G[a+1|0]<<8&16128|G[a|0];break u;case 1:if(b>>>0<3){break t}b=b-3|0;F[e+52>>2]=b;a=a+b|0;a=G[a+1|0]<<8|G[a+2|0]<<16&4128768|G[a|0];break u;default:break v}}b=b-4|0;F[e+52>>2]=b;a=a+b|0;a=(G[a|0]|G[a+1|0]<<8|(G[a+2|0]<<16|G[a+3|0]<<24))&1073741823}F[e+56>>2]=a+32768;d=a>>>0<8355840}Z=k+16|0;if(!d){break s}if(!o){m=1;break s}b=F[e+52>>2];a=F[e+56>>2];c=F[e+36>>2];d=F[e+48>>2];f=F[e+24>>2];while(1){w:{if(a>>>0>32767){break w}while(1){if((b|0)<=0){break w}b=b-1|0;F[e+52>>2]=b;a=G[b+d|0]|a<<8;F[e+56>>2]=a;if(a>>>Line truncated
function Cd(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0;c=F[a+4>>2];e=F[a>>2];f=(c-e|0)/144|0;if(f>>>0<b>>>0){e=a;b=b-f|0;h=F[a+8>>2];c=F[a+4>>2];a:{if(b>>>0<=(h-c|0)/144>>>0){b:{if(!b){break b}a=c;f=b&7;if(f){while(1){va(a);a=a+144|0;d=d+1|0;if((f|0)!=(d|0)){continue}break}}c=L(b,144)+c|0;if((b-1&268435455)>>>0<7){break b}while(1){va(a);va(a+144|0);va(a+288|0);va(a+432|0);va(a+576|0);va(a+720|0);va(a+864|0);va(a+1008|0);a=a+1152|0;if((c|0)!=(a|0)){continue}break}}F[e+4>>2]=c;break a}c:{d:{e:{a=c;c=F[e>>2];i=(a-c|0)/144|0;a=i+b|0;if(a>>>0<29826162){c=(h-c|0)/144|0;f=c<<1;f=c>>>0>=14913080?29826161:a>>>0<f>>>0?f:a;if(f){if(f>>>0>=29826162){break e}g=ka(L(f,144))}c=L(i,144)+g|0;a=c;h=b&7;if(h){while(1){va(a);a=a+144|0;d=d+1|0;if((h|0)!=(d|0)){continue}break}}h=L(b,144)+c|0;if((b-1&268435455)>>>0>=7){while(1){va(a);va(a+144|0);va(a+288|0);va(a+432|0);va(a+576|0);va(a+720|0);va(a+864|0);va(a+1008|0);a=a+1152|0;if((h|0)!=(a|0)){continue}break}}b=L(f,144)+g|0;d=F[e+4>>2];f=F[e>>2];if((d|0)==(f|0)){break d}while(1){c=c-144|0;d=d-144|0;a=d;F[c>>2]=F[a>>2];F[c+4>>2]=F[a+4>>2];F[c+8>>2]=F[a+8>>2];F[c+12>>2]=F[a+12>>2];F[a+12>>2]=0;F[a+4>>2]=0;F[a+8>>2]=0;F[c+16>>2]=F[a+16>>2];F[c+20>>2]=F[a+20>>2];F[c+24>>2]=F[a+24>>2];F[a+24>>2]=0;F[a+16>>2]=0;F[a+20>>2]=0;g=G[a+28|0];F[c+40>>2]=0;F[c+32>>2]=0;F[c+36>>2]=0;D[c+28|0]=g;F[c+32>>2]=F[a+32>>2];F[c+36>>2]=F[a+36>>2];F[c+40>>2]=F[a+40>>2];F[a+40>>2]=0;F[a+32>>2]=0;F[a+36>>2]=0;F[c+52>>2]=0;F[c+44>>2]=0;F[c+48>>2]=0;F[c+44>>2]=F[a+44>>2];F[c+48>>2]=F[a+48>>2];F[c+52>>2]=F[a+52>>2];F[a+52>>2]=0;F[a+44>>2]=0;F[a+48>>2]=0;g=c- -64|0;F[g>>2]=0;F[c+56>>2]=0;F[c+60>>2]=0;F[c+56>>2]=F[a+56>>2];F[c+60>>2]=F[a+60>>2];i=g;g=a- -64|0;F[i>>2]=F[g>>2];F[g>>2]=0;F[a+56>>2]=0;F[a+60>>2]=0;F[c+68>>2]=F[a+68>>2];g=F[a+72>>2];F[c+84>>2]=0;F[c+76>>2]=0;F[c+80>>2]=0;F[c+72>>2]=g;F[c+76>>2]=F[a+76>>2];F[c+80>>2]=F[a+80>>2];F[c+84>>2]=F[a+84>>2];F[a+84>>2]=0;F[a+76>>2]=0;F[a+80>>2]=0;F[c+96>>2]=0;F[c+88>>2]=0;F[c+92>>2]=0;F[c+88>>2]=F[a+88>>2];F[c+92>>2]=F[a+92>>2];F[c+96>>2]=F[a+96>>2];F[a+96>>2]=0;F[a+88>>2]=0;F[a+92>>2]=0;g=G[a+100|0];F[c+112>>2]=0;F[c+104>>2]=0;F[c+108>>2]=0;D[c+100|0]=g;F[c+104>>2]=F[a+104>>2];F[c+108>>2]=F[a+108>>2];F[c+112>>2]=F[a+112>>2];F[a+112>>2]=0;F[a+104>>2]=0;F[a+108>>2]=0;F[c+124>>2]=0;F[c+116>>2]=0;F[c+120>>2]=0;F[c+116>>2]=F[a+116>>2];F[c+120>>2]=F[a+120>>2];F[c+124>>2]=F[a+124>>2];F[a+124>>2]=0;F[a+116>>2]=0;F[a+120>>2]=0;g=F[a+128>>2];F[c+140>>2]=0;F[c+132>>2]=0;F[c+136>>2]=0;F[c+128>>2]=g;F[c+132>>2]=F[a+132>>2];F[c+136>>2]=F[a+136>>2];F[c+140>>2]=F[a+140>>2];F[a+140>>2]=0;F[a+132>>2]=0;F[a+136>>2]=0;if((a|0)!=(f|0)){continue}break}F[e+8>>2]=b;a=F[e+4>>2];F[e+4>>2]=h;d=F[e>>2];F[e>>2]=c;if((a|0)==(d|0)){break c}while(1){b=a-144|0;c=F[b+132>>2];if(c){F[a-8>>2]=c;ja(c)}c=F[a-28>>2];if(c){F[a-24>>2]=c;ja(c)}c=F[a-40>>2];if(c){F[a-36>>2]=c;ja(c)}Gb(a-140|0);a=b;if((d|0)!=(a|0)){continue}break}break c}na();v()}oa();v()}F[e+8>>2]=b;F[e+4>>2]=h;F[e>>2]=c}if(d){ja(d)}}return}if(b>>>0<f>>>0){e=e+L(b,144)|0;if((e|0)!=(c|0)){while(1){b=c-144|0;d=F[b+132>>2];if(d){F[c-8>>2]=d;ja(d)}d=F[c-28>>2];if(d){F[c-24>>2]=d;ja(d)}d=F[c-40>>2];if(d){F[c-36>>2]=d;ja(d)}Gb(c-140|0);c=b;if((e|0)!=(c|0)){continue}break}}F[a+4>>2]=e}}function Yc(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;F[a+56>>2]=F[a+52>>2];F[a+44>>2]=F[a+40>>2];b=F[a+64>>2];c=F[b+24>>2];if((c|0)==F[b+28>>2]){return 1}a:{b:{c:{while(1){g=i;i=F[(k<<2)+c>>2];d:{if((i|0)==-1){i=g;break d}b=F[a+56>>2];e:{if((b|0)!=F[a+60>>2]){F[b>>2]=g;F[a+56>>2]=b+4;break e}d=F[a+52>>2];e=b-d|0;h=e>>2;c=h+1|0;if(c>>>0>=1073741824){break c}f=e>>>1|0;f=e>>>0>=2147483644?1073741823:c>>>0<f>>>0?f:c;if(f){if(f>>>0>=1073741824){break b}e=ka(f<<2)}else{e=0}c=e+(h<<2)|0;F[c>>2]=g;h=c+4|0;if((b|0)!=(d|0)){while(1){c=c-4|0;b=b-4|0;F[c>>2]=F[b>>2];if((b|0)!=(d|0)){continue}break}}F[a+60>>2]=e+(f<<2);F[a+56>>2]=h;F[a+52>>2]=c;if(!d){break e}ja(d)}f:{g:{if(!(F[F[a+12>>2]+(k>>>3&536870908)>>2]>>>k&1)){break g}e=i+1|0;e=(e>>>0)%3|0?e:i-2|0;if((e|0)==-1|F[F[a>>2]+(e>>>3&536870908)>>2]>>>e&1){break g}e=F[F[F[a+64>>2]+12>>2]+(e<<2)>>2];if((e|0)==-1){break g}b=e+1|0;b=(b>>>0)%3|0?b:e-2|0;if((b|0)==-1){break g}c=F[a+64>>2];f=F[a>>2];while(1){e=b;b=-1;d=e+1|0;d=(d>>>0)%3|0?d:e-2|0;h:{if((d|0)==-1|F[f+(d>>>3&536870908)>>2]>>>d&1){break h}d=F[F[c+12>>2]+(d<<2)>>2];if((d|0)==-1){break h}b=d+1|0;b=(b>>>0)%3|0?b:d-2|0}if((b|0)!=(i|0)){if((b|0)==-1){break f}continue}break}return 0}e=i}F[F[a+28>>2]+(e<<2)>>2]=g;b=F[a+44>>2];i:{if((b|0)!=F[a+48>>2]){F[b>>2]=e;F[a+44>>2]=b+4;break i}d=F[a+40>>2];i=b-d|0;h=i>>2;c=h+1|0;if(c>>>0>=1073741824){break a}f=i>>>1|0;f=i>>>0>=2147483644?1073741823:c>>>0<f>>>0?f:c;if(f){if(f>>>0>=1073741824){break b}i=ka(f<<2)}else{i=0}c=i+(h<<2)|0;F[c>>2]=e;h=c+4|0;if((b|0)!=(d|0)){while(1){c=c-4|0;b=b-4|0;F[c>>2]=F[b>>2];if((b|0)!=(d|0)){continue}break}}F[a+48>>2]=i+(f<<2);F[a+44>>2]=h;F[a+40>>2]=c;if(!d){break i}ja(d)}i=g+1|0;b=F[a+64>>2];if((e|0)==-1){break d}j:{if((e>>>0)%3|0){c=e-1|0;break j}c=e+2|0;if((c|0)==-1){break d}}d=F[F[b+12>>2]Line truncated
function Sd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;d=F[b+8>>2];c=F[b+12>>2];g=c;c=F[b+20>>2];i=c;h=F[b+16>>2];f=h+4|0;c=f>>>0<4?c+1|0:c;a:{if(d>>>0<f>>>0&(c|0)>=(g|0)|(c|0)>(g|0)){break a}e=h+F[b>>2]|0;e=G[e|0]|G[e+1|0]<<8|(G[e+2|0]<<16|G[e+3|0]<<24);F[b+16>>2]=f;F[b+20>>2]=c;f=d;d=i;c=h+8|0;d=c>>>0<8?d+1|0:d;if(c>>>0>f>>>0&(d|0)>=(g|0)|(d|0)>(g|0)){break a}F[b+16>>2]=c;F[b+20>>2]=d;if(!(e&1)){break a}d=O(e)^31;if(d-1>>>0>28){break a}F[a+8>>2]=d+1;d=-2<<d;c=d^-2;F[a+16>>2]=c;F[a+12>>2]=d^-1;F[a+24>>2]=c>>1;J[a+20>>2]=M(2)/M(c|0);j=Aa(a+96|0,b)}return j|0}function bc(a,b){var c=0;c=F[b+4>>2];F[a>>2]=F[b>>2];F[a+4>>2]=c;c=F[b+60>>2];F[a+56>>2]=F[b+56>>2];F[a+60>>2]=c;c=F[b+52>>2];F[a+48>>2]=F[b+48>>2];F[a+52>>2]=c;c=F[b+44>>2];F[a+40>>2]=F[b+40>>2];F[a+44>>2]=c;c=F[b+36>>2];F[a+32>>2]=F[b+32>>2];F[a+36>>2]=c;c=F[b+28>>2];F[a+24>>2]=F[b+24>>2];F[a+28>>2]=c;c=F[b+20>>2];F[a+16>>2]=F[b+16>>2];F[a+20>>2]=c;c=F[b+12>>2];F[a+8>>2]=F[b+8>>2];F[a+12>>2]=c;F[a+88>>2]=0;F[a+64>>2]=0;F[a+68>>2]=0;F[a+72>>2]=0;F[a+76>>2]=0;D[a+77|0]=0;D[a+78|0]=0;D[a+79|0]=0;D[a+80|0]=0;D[a+81|0]=0;D[a+82|0]=0;D[a+83|0]=0;D[a+84|0]=0;return a}function ac(a,b){var c=0,d=0,e=0,f=0,g=0;a:{if(F[a+64>>2]){break a}c=ka(32);F[c+16>>2]=0;F[c+20>>2]=0;F[c+8>>2]=0;F[c>>2]=0;F[c+4>>2]=0;F[c+24>>2]=0;F[c+28>>2]=0;d=F[a+64>>2];F[a+64>>2]=c;if(!d){break a}c=F[d>>2];if(c){F[d+4>>2]=c;ja(c)}ja(d)}d=F[a+64>>2];c=F[a+28>>2]-1|0;if(c>>>0<=10){c=F[(c<<2)+10148>>2]}else{c=-1}c=L(c,G[a+24|0]);f=c>>31;g=md(d,0,ki(c,f,b,0),_);if(g){d=F[a+64>>2];F[a>>2]=d;e=F[d+20>>2];F[a+8>>2]=F[d+16>>2];F[a+12>>2]=e;e=F[d+24>>2];d=F[d+28>>2];F[a+48>>2]=0;F[a+52>>2]=0;F[a+40>>2]=c;F[a+44>>2]=f;F[a+16>>2]=e;F[a+20>>2]=d;F[a+80>>2]=b}return g}function Af(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;a=F[b+100>>2];b=F[b+96>>2];h=a-b|0;a:{if((h|0)!=(c|0)|(a|0)==(b|0)){break a}g=(c|0)/12|0;e=g>>>0<=1?1:g;j=e&1;a=0;if(g>>>0>=2){k=e&-2;g=0;while(1){e=L(a,12);i=e+d|0;f=b+e|0;F[i>>2]=F[f>>2];F[(e|4)+d>>2]=F[f+4>>2];F[i+8>>2]=F[f+8>>2];f=L(a|1,12);e=f+d|0;f=b+f|0;F[e>>2]=F[f>>2];F[e+4>>2]=F[f+4>>2];F[e+8>>2]=F[f+8>>2];a=a+2|0;g=g+2|0;if((k|0)!=(g|0)){continue}break}}if(!j){break a}e=d;d=L(a,12);a=e+d|0;b=b+d|0;F[a>>2]=F[b>>2];F[a+4>>2]=F[b+4>>2];F[a+8>>2]=F[b+8>>2]}return(c|0)==(h|0)|0}function Kh(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;c=F[b+8>>2];d=F[b+12>>2];g=d;d=F[b+20>>2];i=d;h=F[b+16>>2];f=h+4|0;d=f>>>0<4?d+1|0:d;a:{if(c>>>0<f>>>0&(d|0)>=(g|0)|(d|0)>(g|0)){break a}e=h+F[b>>2]|0;e=G[e|0]|G[e+1|0]<<8|(G[e+2|0]<<16|G[e+3|0]<<24);F[b+16>>2]=f;F[b+20>>2]=d;f=c;c=i;d=h+8|0;c=d>>>0<8?c+1|0:c;if(d>>>0>f>>>0&(c|0)>=(g|0)|(c|0)>(g|0)){break a}F[b+16>>2]=d;F[b+20>>2]=c;if(!(e&1)){break a}b=O(e)^31;if(b-1>>>0>28){break a}j=1;F[a+8>>2]=b+1;b=-2<<b;c=b^-2;F[a+16>>2]=c;F[a+12>>2]=b^-1;F[a+24>>2]=c>>1;J[a+20>>2]=M(2)/M(c|0)}return j|0}function Ya(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0;f=a+4|0;a=F[a+4>>2];a:{b:{if(!a){break b}d=G[b+11|0];c=d<<24>>24<0;g=c?F[b>>2]:b;d=c?F[b+4>>2]:d;b=f;while(1){e=G[a+27|0];c=e<<24>>24<0;e=c?F[a+20>>2]:e;h=e>>>0>d>>>0;i=h?d:e;c:{if(i){c=sa(c?F[a+16>>2]:a+16|0,g,i);if(c){break c}}c=d>>>0>e>>>0?-1:h}c=(c|0)<0;b=c?b:a;a=F[(c?a+4|0:a)>>2];if(a){continue}break}if((b|0)==(f|0)){break b}c=G[b+27|0];a=c<<24>>24<0;d:{c=a?F[b+20>>2]:c;e=c>>>0<d>>>0?c:d;if(e){a=sa(g,a?F[b+16>>2]:b+16|0,e);if(a){break d}}if(c>>>0>d>>>0){break b}break a}if((a|0)>=0){break a}}b=f}return b}function Oe(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;if(La(a,F[b+8>>2],e)){if(!(F[b+28>>2]==1|F[b+4>>2]!=(c|0))){F[b+28>>2]=d}return}a:{if(La(a,F[b>>2],e)){if(!(F[b+16>>2]!=(c|0)&F[b+20>>2]!=(c|0))){if((d|0)!=1){break a}F[b+32>>2]=1;return}F[b+32>>2]=d;b:{if(F[b+44>>2]==4){break b}E[b+52>>1]=0;a=F[a+8>>2];$[F[F[a>>2]+20>>2]](a,b,c,c,1,e);if(G[b+53|0]){F[b+44>>2]=3;if(!G[b+52|0]){break b}break a}F[b+44>>2]=4}F[b+20>>2]=c;F[b+40>>2]=F[b+40>>2]+1;if(F[b+36>>2]!=1|F[b+24>>2]!=2){break a}D[b+54|0]=1;return}a=F[a+8>>2];$[F[F[a>>2]+24>>2]](a,b,c,d,e)}}function Ig(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0;f=ka(64);c=ka(12);F[c+8>>2]=F[F[a+4>>2]+80>>2];F[c>>2]=9968;F[c+4>>2]=0;f=yc(f,c);a:{b:{if((b|0)<0){c=f;break b}h=a+8|0;c=F[a+12>>2];e=F[a+8>>2];g=c-e>>2;c:{if((g|0)>(b|0)){break c}d=b+1|0;if(b>>>0>=g>>>0){Pb(h,d-g|0);break c}if(d>>>0>=g>>>0){break c}e=e+(d<<2)|0;if((e|0)!=(c|0)){while(1){c=c-4|0;d=F[c>>2];F[c>>2]=0;if(d){$[F[F[d>>2]+4>>2]](d)}if((c|0)!=(e|0)){continue}break}}F[a+12>>2]=e}a=F[h>>2]+(b<<2)|0;c=F[a>>2];F[a>>2]=f;if(!c){break a}}$[F[F[c>>2]+4>>2]](c)}return(b^-1)>>>31|0}function we(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;c=F[a+60>>2];a:{if(!c){break a}F[c+4>>2]=a+48;if(!($[F[F[c>>2]+12>>2]](c)|0)){break a}b:{c=$[F[F[a>>2]+24>>2]](a)|0;if((c|0)<=0){break b}while(1){c:{f=F[($[F[F[a>>2]+28>>2]](a)|0)+4>>2];g=$[F[F[a>>2]+20>>2]](a,d)|0;e=F[a+60>>2];if(!($[F[F[e>>2]+8>>2]](e,F[F[f+8>>2]+(g<<2)>>2])|0)){break c}d=d+1|0;if((c|0)!=(d|0)){continue}break b}break}return 0}d=0;if(!($[F[F[a>>2]+36>>2]](a,b)|0)){break a}if(!($[F[F[a>>2]+40>>2]](a,b)|0)){break a}d=$[F[F[aLine truncated
function oh(a,b){a=a|0;b=b|0;a=F[a+48>>2];return $[F[F[a>>2]+12>>2]](a,b)|0}function nh(a,b){a=a|0;b=b|0;a=F[a+48>>2];return $[F[F[a>>2]+16>>2]](a,b)|0}function Xa(){var a=0;a=ka(12);F[a>>2]=0;F[a+4>>2]=0;F[a+8>>2]=0;return a|0}function Wa(a){a=a|0;var b=0;if(a){b=F[a>>2];if(b){F[a+4>>2]=b;ja(b)}ja(a)}}function mf(a,b,c){a=a|0;b=b|0;c=c|0;F[a+32>>2]=c;F[a+28>>2]=b;return 1}function eg(a){a=a|0;if(a){Ca(a+12|0,F[a+16>>2]);Ba(a,F[a+4>>2]);ja(a)}}function Lb(a,b){a=a|0;b=b|0;if(b>>>0<=1){F[a+28>>2]=b}return b>>>0<2|0}function Fg(a,b){a=a|0;b=b|0;D[b+84|0]=1;F[b+72>>2]=F[b+68>>2];return 1}function kg(){var a=0;a=ka(8);F[a+4>>2]=-1;F[a>>2]=1032;return a|0}function Hf(a,b,c){a=a|0;b=b|0;c=c|0;return F[F[b+8>>2]+(c<<2)>>2]}function th(a){a=a|0;a=F[a+48>>2];return $[F[F[a>>2]+24>>2]](a)|0}function sh(a){a=a|0;a=F[a+48>>2];return $[F[F[a>>2]+28>>2]](a)|0}function ph(a){a=a|0;a=F[a+48>>2];return $[F[F[a>>2]+36>>2]](a)|0}function Rh(a,b){a=a|0;b=b|0;return zc(a+24|0,bb(a),F[a+8>>2])|0}function Bh(a,b){a=a|0;b=b|0;return xd(a+24|0,bb(a),F[a+8>>2])|0}function bg(a){a=a|0;if(a){if(D[a+15|0]<0){ja(F[a+4>>2])}ja(a)}}function Ke(a){a=a|0;if(!a){return 0}return(Fc(a,11164)|0)!=0|0}function Fe(a,b){a=a|0;b=b|0;F[a+12>>2]=-1;F[a+8>>2]=b;return 1}function hd(a,b){a=a|0;b=b|0;return $[F[F[a>>2]+12>>2]](a,b)|0}function Dh(a,b){a=a|0;b=b|0;return $[F[F[a>>2]+56>>2]](a,b)|0}function sc(a){a=Hc(Rb(8),a);F[a>>2]=11568;W(a|0,11600,1);v()}function mg(a,b){a=a|0;b=b|0;return M(J[F[a+8>>2]+(b<<2)>>2])}function Rd(a){a=Hc(Rb(8),a);F[a>>2]=11516;W(a|0,11548,1);v()}function $f(a,b){a=a|0;b=b|0;return M(J[F[a>>2]+(b<<2)>>2])}function gg(a){a=a|0;return(F[a+100>>2]-F[a+96>>2]|0)/12|0}function cg(a){a=a|0;return(D[a+15|0]<0?F[a+4>>2]:a+4|0)|0}function Re(a,b){a=a|0;b=b|0;return F[F[a+4>>2]+(b<<2)>>2]}function dd(a,b){a=a|0;b=b|0;return F[F[a>>2]+(b<<2)>>2]}function Yf(a,b){a=a|0;b=b|0;return E[F[a>>2]+(b<<1)>>1]}function Xf(a,b){a=a|0;b=b|0;return H[F[a>>2]+(b<<1)>>1]}function Vd(a,b,c){a=a|0;b=b|0;c=c|0;return xc(a,b,c)|0}function sb(a,b){var c=0;c=ka(b);F[a+4>>2]=b;F[a>>2]=c}function Kf(a,b,c){a=a|0;b=b|0;c=c|0;return Qc(b,c)|0}function Rc(a){F[a>>2]=10300;ma(a+4|0,0,80);return a}function ji(a){if(a){return 31-O(a-1^a)|0}return 32}function gd(a){a=a|0;return F[a+12>>2]-F[a+8>>2]>>2}function _f(a,b){a=a|0;b=b|0;return D[F[a>>2]+b|0]}function Zf(a,b){a=a|0;b=b|0;return G[F[a>>2]+b|0]}function Ie(a){a=a|0;return F[a+8>>2]-F[a+4>>2]>>2}function wb(a){a=a|0;if(a){$[F[F[a>>2]+4>>2]](a)}}function Jd(a,b){a=a|0;b=b|0;F[a+4>>2]=b;return 1}function ed(a){a=a|0;return F[a+4>>2]-F[a>>2]>>1}function dc(a){a=a|0;return F[a+4>>2]-F[a>>2]>>2}function fd(a){a=a|0;return F[a+4>>2]-F[a>>2]|0}function ke(a){a=a|0;return G[F[a+8>>2]+24|0]}function Qf(a,b){a=a|0;b=b|0;return F[b+8>>2]}function Ff(a,b){a=a|0;b=b|0;return F[b+4>>2]}function De(a,b,c){a=a|0;b=b|0;c=c|0;return 1}function Bg(a,b){a=a|0;b=b|0;return G[b+24|0]}function Oh(a){a=a|0;F[a>>2]=5928;return a|0}function Jh(a){a=a|0;F[a>>2]=6932;return a|0}function lg(a){a=a|0;return M(J[a+20>>2])}function Nh(a){a=a|0;F[a>>2]=5928;ja(a)}function Ih(a){a=a|0;F[a>>2]=6932;ja(a)}function ug(a){a=a|0;return F[a+88>>2]}function tg(a){a=a|0;return F[a+56>>2]}function qg(a){a=a|0;return F[a+40>>2]}function pg(a){a=a|0;return F[a+48>>2]}function og(a){a=a|0;return F[a+60>>2]}function ec(a){a=a|0;return F[a+80>>2]}function cb(a){a=a|0;return F[a+28>>2]}function vd(a){a=a|0;return F[a+8>>2]}function tc(a,b){a=a|0;b=b|0;return-1}function sg(a){a=a|0;return D[a+24|0]}function rg(a){a=a|0;return G[a+32|0]}function dg(a){a=a|0;return!F[a>>2]|0}function Wd(a,b){a=a|0;b=b|0;return 6}function Va(a){a=a|0;return F[a+4>>2]}function Rb(a){return Ub(a+80|0)+80|0}function Qh(a,b){a=a|0;b=b|0;return 2}function Ia(a,b){a=a|0;b=b|0;return 0}function Bc(a,b){a=a|0;b=b|0;return 1}function jd(a){a=a|0;return F[a>>2]}function wg(){return kb(ka(64))|0}function ig(){return Rc(ka(84))|0}function fc(a){a=a|0;if(a){ja(a)}}function Ag(){return Ja(ka(40))|0}function Qb(a){a=a|0;Cc(a);ja(a)}function Je(a){a=a|0;return 1161}function He(a){a=a|0;return 1235}function Ge(a){a=a|0;return 1201}function Pa(a){a=a|0;return a|0}function hh(a){a=a|0;ja(ud(a))}function gh(a){a=a|0;ja(sd(a))}function Ve(a){a=a|0;ja(tb(a))}function ua(a){a=a|0;return 1}function rc(a){a=a|0;return 4}function qc(a){a=a|0;return 5}function Xd(a){a=a|0;return 2}function Ua(a){a=a|0;return 0}function Nb(a){a=a|0;return 6}function Mh(a){a=a|0;return 3}function za(){Rd(1222);v()}function ta(){sc(1154);v()}function na(){Rd(1154);v()}function Ha(a){a=a|0;ja(a)}function Ra(a){a=a|0;v()}function ff(){return 10}function ef(){return 11}function df(){return 12}function vb(){return-1}function ub(){return 1}function lf(){return 5}function kf(){return 6}function jf(){return 7}function jb(){return 0}function hf(){return 8}function gf(){return 9}function cf(){return-2}function bf(){return-3}function bd(){return 3}function af(){return-4}function ad(){return 4}function _b(){return 2}function $e()Line truncated
// EMSCRIPTEN_END_FUNCS
e=G;p(q);var $=c([null,Cc,Pa,Ha,Xd,ii,jh,ag,zc,xe,uc,jg,Wd,Qh,Pa,Pg,Hg,ua,yh,mh,kh,xd,fh,qd,Wd,Bg,We,Ra,mf,$c,Ue,Re,Ie,cb,Ia,Te,Bc,ua,Ae,ze,Ac,Fe,Ee,De,Bc,Ce,Be,re,qe,ye,we,pe,ve,ue,te,se,wc,vc,Ac,oe,ne,xc,me,ke,le,je,Ob,ua,Va,ob,Ua,tc,Ia,Ua,ua,ie,he,Ra,Ra,ge,fe,rc,ob,ee,de,ce,be,qc,pc,ua,Ia,oc,$d,hi,gi,fi,Nb,_d,ua,Ia,Zd,Yd,ei,Pa,Ha,Lb,cb,Mb,Ra,Ob,ua,ob,di,Ra,ci,bi,rc,ob,ai,$h,_h,Zh,qc,pc,ua,Ia,oc,$d,Yh,Xh,Wh,Nb,_d,ua,Ia,Zd,Yd,Vh,Pa,Ha,Lb,cb,Kb,Ra,Ob,Ua,ua,Uh,wc,vc,Th,Sh,Vd,Ph,Xd,Rh,Oh,Nh,Nb,Va,Ud,ua,Ia,Td,ua,Mh,Sd,Lh,Pa,Ha,Lb,cb,Mb,Jh,Ih,Nb,Ud,ua,Ia,Td,Sd,Hh,Pa,Ha,Lb,cb,Kb,Pa,Ha,Ua,ua,Ua,tc,Ia,Kh,Gh,Ah,zh,Fh,Eh,Vd,Dh,Ch,Bh,wh,Ra,ua,ua,xh,Dg,Cg,ua,Ua,Ia,Ia,rh,qh,uh,vh,sh,ph,oh,nh,th,ud,hh,Jd,Id,Hd,Gd,lh,ua,Va,vd,sd,gh,Jd,Id,Hd,Gd,ih,ua,Va,vd,Ed,eh,Fd,dh,ch,$g,_g,Zg,Yg,ah,Xg,bh,Wg,Vg,Tg,Sg,Rg,Qg,Ug,Og,Ng,Mg,Lg,Kg,Gg,Ig,Jg,Pa,Ha,Fg,Eg,Ra,Ua,ua,_e,Ze,Ye,Xe,tb,Ve,Oc,Nc,Pa,Ha,Jc,Jc,Se,Le,Ne,Qe,Ha,Me,Oe,Pe,Ha,He,Ha,Ge,Ha,Je,Qb,Va,Qb,Qb]);function aa(){return C.byteLength/65536|0}function fa(ga){ga=ga|0;var ba=aa()|0;var ca=ba+ga|0;if(ba<ca&&ca<65536){var da=new ArrayBuffer(L(ca,65536));var ea=new Int8Array(da);ea.set(D);D=new Int8Array(da);E=new Int16Array(da);F=new Int32Array(da);G=new Uint8Array(da);H=new Uint16Array(da);I=new Uint32Array(da);J=new Float32Array(da);K=new Float64Array(da);C=da;B.buffer=C;e=G}return ba}return{"f":ae,"g":$,"h":fc,"i":Ag,"j":zg,"k":fc,"l":yg,"m":jd,"n":xg,"o":wg,"p":fc,"q":vg,"r":ec,"s":ug,"t":tg,"u":cb,"v":sg,"w":rg,"x":qg,"y":pg,"z":og,"A":xa,"B":ng,"C":hd,"D":Va,"E":mg,"F":lg,"G":wb,"H":kg,"I":hd,"J":Va,"K":wb,"L":ig,"M":gd,"N":ec,"O":wb,"P":hg,"Q":gg,"R":gd,"S":ec,"T":wb,"U":fg,"V":eg,"W":jd,"X":dg,"Y":cg,"Z":bg,"_":Xa,"$":$f,"aa":dc,"ba":Wa,"ca":Xa,"da":_f,"ea":fd,"fa":Wa,"ga":Xa,"ha":Zf,"ia":fd,"ja":Wa,"ka":Xa,"la":Yf,"ma":ed,"na":Wa,"oa":Xa,"pa":Xf,"qa":ed,"ra":Wa,"sa":Xa,"ta":dd,"ua":dc,"va":Wa,"wa":Xa,"xa":dd,"ya":dc,"za":Wa,"Aa":Wf,"Ba":Vf,"Ca":Uf,"Da":Tf,"Ea":Sf,"Fa":Rf,"Ga":Qf,"Ha":Pf,"Ia":Of,"Ja":Nf,"Ka":Mf,"La":Lf,"Ma":Kf,"Na":Jf,"Oa":If,"Pa":Hf,"Qa":Gf,"Ra":Ff,"Sa":Ef,"Ta":Df,"Ua":Cf,"Va":Bf,"Wa":Af,"Xa":zf,"Ya":yf,"Za":cd,"_a":xf,"$a":wf,"ab":vf,"bb":uf,"cb":cd,"db":tf,"eb":sf,"fb":rf,"gb":qf,"hb":pf,"ib":of,"jb":nf,"kb":vb,"lb":jb,"mb":ub,"nb":_b,"ob":vb,"pb":jb,"qb":ub,"rb":_b,"sb":bd,"tb":ad,"ub":vb,"vb":jb,"wb":ub,"xb":jb,"yb":ub,"zb":_b,"Ab":bd,"Bb":ad,"Cb":lf,"Db":kf,"Eb":jf,"Fb":hf,"Gb":gf,"Hb":ff,"Ib":ef,"Jb":df,"Kb":jb,"Lb":vb,"Mb":cf,"Nb":bf,"Ob":af,"Pb":$e,"Qb":Ub,"Rb":ja,"Sb":Ke}}return ha(ia)}
// EMSCRIPTEN_END_ASM
)(info)},instantiate:function(binary,info){return{then:function(ok){var module=new WebAssembly.Module(binary);ok({"instance":new WebAssembly.Instance(module,info)})}}},RuntimeError:Error};wasmBinary=[];if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function lengthBytesUTF8(str){var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;assert(INITIAL_MEMORY>=65536,"INITIAL_MEMORY should be larger than STACK_SIZE, was "+INITIAL_MEMORY+"! (STACK_SIZE="+65536+")");if(Module["wasmMemory"]){wasmMemory=Module["wasmMemory"]}else{wasmMemory=new WebAssembly.Memory({"initial":INITIAL_MEMORY/65536,"maximum":2147483648/65536})}updateMemoryViews();INITIAL_MEMORY=wasmMemory.buffer.byteLength;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="draco_decoder_gltf.wasm";if(!isDataURI(wasmBinLine truncated
return DracoDecoderModule.ready
}
);
})();
if (typeof exports === 'object' && typeof module === 'object')
module.exports = DracoDecoderModule;
else if (typeof define === 'function' && define['amd'])
define([], function() { return DracoDecoderModule; });
else if (typeof exports === 'object')
exports["DracoDecoderModule"] = DracoDecoderModule;
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
var DracoEncoderModule = function(DracoEncoderModule) {
DracoEncoderModule = DracoEncoderModule || {};
var Module=typeof DracoEncoderModule!=="undefined"?DracoEncoderModule:{};var isRuntimeInitialized=false;var isModuleParsed=false;Module["onRuntimeInitialized"]=(function(){isRuntimeInitialized=true;if(isModuleParsed){if(typeof Module["onModuleLoaded"]==="function"){Module["onModuleLoaded"](Module)}}});Module["onModuleParsed"]=(function(){isModuleParsed=true;if(isRuntimeInitialized){if(typeof Module["onModuleLoaded"]==="function"){Module["onModuleLoaded"](Module)}}});function isVersionSupported(versionString){if(typeof versionString!=="string")return false;const version=versionString.split(".");if(version.length<2||version.length>3)return false;if(version[0]==1&&version[1]>=0&&version[1]<=3)return true;if(version[0]!=0||version[1]>10)return false;return true}Module["isVersionSupported"]=isVersionSupported;var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}Module["arguments"]=[];Module["thisProgram"]="./this.program";Module["quit"]=(function(status,toThrow){throw toThrow});Module["preRun"]=[];Module["postRun"]=[];var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;if(Module["ENVIRONMENT"]){if(Module["ENVIRONMENT"]==="WEB"){ENVIRONMENT_IS_WEB=true}else if(Module["ENVIRONMENT"]==="WORKER"){ENVIRONMENT_IS_WORKER=true}else if(Module["ENVIRONMENT"]==="NODE"){ENVIRONMENT_IS_NODE=true}else if(Module["ENVIRONMENT"]==="SHELL"){ENVIRONMENT_IS_SHELL=true}else{throw new Error("Module['ENVIRONMENT'] value is not valid. must be one of: WEB|WORKER|NODE|SHELL.")}}else{ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof require==="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER}if(ENVIRONMENT_IS_NODE){var nodeFS;var nodePath;Module["read"]=function shell_read(filename,binary){var ret;ret=tryParseAsDataURI(filename);if(!ret){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);ret=nodeFS["readFileSync"](filename)}return binary?ret:ret.toString()};Module["readBinary"]=function readBinary(filename){var ret=Module["read"](filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}Module["arguments"]=process["argv"].slice(2);process["on"]("uncaughtException",(function(ex){if(!(ex instanceof ExitStatus)){throw ex}}));process["on"]("unhandledRejection",(function(reason,p){process["exit"](1)}));Module["inspect"]=(function(){return"[Emscripten Module object]"})}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){Module["read"]=function shell_read(f){var data=tryParseAsDataURI(f);if(data){return intArrayToString(data)}return read(f)}}Module["readBinary"]=function readBinary(f){var data;data=tryParseAsDataURI(f);if(data){return data}if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof quit==="function"){Module["quit"]=(function(status,toThrow){quit(status)})}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){Module["read"]=function shell_read(url){try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText}catch(err){var data=tryParseAsDataURI(url);if(data){return intArrayToString(data)}throw err}};if(ENVIRONMENT_IS_WORKER){Module["readBinary"]=function readBinary(url){try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}catch(err){var data=tryParseAsDataURI(url);if(data){return data}throw err}}}Module["readAsync"]=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}var data=tryParseAsDataURI(url);if(data){onload(data.buffer);return}onerror()};xhr.onerror=onerror;xhr.send(null)};Module["setWindowTitle"]=(function(title){document.title=title})}Module["print"]=typeof console!=="undefined"?console.log.bind(console):typeof print!=="undefined"?print:null;Module["printErr"]=typeof printErr!=="undefined"?printErr:typeof console!=="undefined"&&console.warn.bind(console)||Module["print"];Module.print=Module["print"];Module.printErr=Module["printErr"];for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=undefined;var STACK_ALIGN=16;function staticAlloc(size){assert(!staticSealed);var ret=STATICTOP;STATICTOP=STATICTOP+size+15&-16;return ret}function dynamicAlLine truncated
var asm=(/** @suppress {uselessCode} */ function(global,env,buffer) {
"almost asm";var a=global.Int8Array;var b=new a(buffer);var c=global.Int16Array;var d=new c(buffer);var e=global.Int32Array;var f=new e(buffer);var g=global.Uint8Array;var h=new g(buffer);var i=global.Uint16Array;var j=new i(buffer);var k=global.Uint32Array;var l=new k(buffer);var m=global.Float32Array;var n=new m(buffer);var o=global.Float64Array;var p=new o(buffer);var q=global.byteLength;var r=env.DYNAMICTOP_PTR|0;var s=env.tempDoublePtr|0;var t=env.ABORT|0;var u=env.STACKTOP|0;var v=env.STACK_MAX|0;var w=env.cttz_i8|0;var x=0;var y=0;var z=0;var A=0;var B=global.NaN,C=global.Infinity;var D=0,E=0,F=0,G=0,H=0.0;var I=0;var J=global.Math.floor;var K=global.Math.abs;var L=global.Math.sqrt;var M=global.Math.pow;var N=global.Math.cos;var O=global.Math.sin;var P=global.Math.tan;var Q=global.Math.acos;var R=global.Math.asin;var S=global.Math.atan;var T=global.Math.atan2;var U=global.Math.exp;var V=global.Math.log;var W=global.Math.ceil;var X=global.Math.imul;var Y=global.Math.min;var Z=global.Math.max;var _=global.Math.clz32;var $=global.Math.fround;var aa=env.abort;var ba=env.assert;var ca=env.enlargeMemory;var da=env.getTotalMemory;var ea=env.abortOnCannotGrowMemory;var fa=env.invoke_ii;var ga=env.invoke_iii;var ha=env.invoke_iiii;var ia=env.invoke_iiiiiii;var ja=env.invoke_v;var ka=env.invoke_vi;var la=env.invoke_vii;var ma=env.invoke_viii;var na=env.invoke_viiii;var oa=env.invoke_viiiii;var pa=env.invoke_viiiiii;var qa=env.__ZSt18uncaught_exceptionv;var ra=env.___cxa_allocate_exception;var sa=env.___cxa_begin_catch;var ta=env.___cxa_find_matching_catch;var ua=env.___cxa_pure_virtual;var va=env.___cxa_throw;var wa=env.___gxx_personality_v0;var xa=env.___resumeException;var ya=env.___setErrNo;var za=env.___syscall140;var Aa=env.___syscall146;var Ba=env.___syscall6;var Ca=env._abort;var Da=env._emscripten_memcpy_big;var Ea=env._llvm_ceil_f64;var Fa=env._llvm_fabs_f64;var Ga=env._llvm_floor_f64;var Ha=env._llvm_trap;var Ia=env._pthread_getspecific;var Ja=env._pthread_key_create;var Ka=env._pthread_once;var La=env._pthread_setspecific;var Ma=env.flush_NO_FILESYSTEM;var Na=$(0);const Oa=$(0);function Pa(newBuffer){if(q(newBuffer)&16777215||q(newBuffer)<=16777215||q(newBuffer)>2147483648)return false;b=new a(newBuffer);d=new c(newBuffer);f=new e(newBuffer);h=new g(newBuffer);j=new i(newBuffer);l=new k(newBuffer);n=new m(newBuffer);p=new o(newBuffer);buffer=newBuffer;return true}
// EMSCRIPTEN_START_FUNCS
function be(a,b,c,d,e,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0;h=u;u=u+16|0;i=h+4|0;j=h;f[a+72>>2]=e;f[a+64>>2]=g;g=Lq(e>>>0>1073741823?-1:e<<2)|0;k=a+68|0;l=f[k>>2]|0;f[k>>2]=g;if(l|0)Mq(l);l=a+8|0;Mh(l,b,d,e);d=a+56|0;g=f[d>>2]|0;m=f[g+4>>2]|0;n=f[g>>2]|0;o=m-n|0;if((o|0)<=0){u=h;return 1}p=(o>>>2)+-1|0;o=a+16|0;q=a+32|0;r=a+12|0;s=a+28|0;t=a+20|0;v=a+24|0;if(m-n>>2>>>0>p>>>0){w=p;x=n}else{y=g;aq(y)}while(1){f[j>>2]=f[x+(w<<2)>>2];f[i>>2]=f[j>>2];Cc(a,i,b,w);g=X(w,e)|0;n=b+(g<<2)|0;p=c+(g<<2)|0;g=f[l>>2]|0;if((g|0)>0){m=0;z=f[k>>2]|0;A=g;while(1){if((A|0)>0){g=0;do{B=f[z+(g<<2)>>2]|0;C=f[o>>2]|0;if((B|0)>(C|0)){D=f[q>>2]|0;f[D+(g<<2)>>2]=C;E=D}else{D=f[r>>2]|0;C=f[q>>2]|0;f[C+(g<<2)>>2]=(B|0)<(D|0)?D:B;E=C}g=g+1|0}while((g|0)<(f[l>>2]|0));F=E}else F=f[q>>2]|0;g=(f[n+(m<<2)>>2]|0)-(f[F+(m<<2)>>2]|0)|0;C=p+(m<<2)|0;f[C>>2]=g;if((g|0)>=(f[s>>2]|0)){if((g|0)>(f[v>>2]|0)){G=g-(f[t>>2]|0)|0;H=21}}else{G=(f[t>>2]|0)+g|0;H=21}if((H|0)==21){H=0;f[C>>2]=G}m=m+1|0;A=f[l>>2]|0;if((m|0)>=(A|0))break;else z=F}}w=w+-1|0;if((w|0)<=-1){H=5;break}z=f[d>>2]|0;x=f[z>>2]|0;if((f[z+4>>2]|0)-x>>2>>>0<=w>>>0){y=z;H=6;break}}if((H|0)==5){u=h;return 1}else if((H|0)==6)aq(y);return 0}function ce(a,c,d,e){a=a|0;c=c|0;d=d|0;e=e|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,v=0,w=0;g=u;u=u+64|0;h=g+48|0;i=g;j=d+1|0;f[h>>2]=0;k=h+4|0;f[k>>2]=0;f[h+8>>2]=0;do if(j)if(j>>>0>536870911)aq(h);else{l=ln(j<<3)|0;f[h>>2]=l;m=l+(j<<3)|0;f[h+8>>2]=m;sj(l|0,0,(d<<3)+8|0)|0;f[k>>2]=m;n=l;o=m;break}else{n=0;o=0}while(0);d=(c|0)>0;if(d){j=0;do{m=n+(f[a+(j<<2)>>2]<<3)|0;l=m;p=Vn(f[l>>2]|0,f[l+4>>2]|0,1,0)|0;l=m;f[l>>2]=p;f[l+4>>2]=I;j=j+1|0}while((j|0)!=(c|0))}j=i+40|0;l=j;f[l>>2]=0;f[l+4>>2]=0;f[i>>2]=0;f[i+4>>2]=0;f[i+8>>2]=0;f[i+12>>2]=0;f[i+16>>2]=0;f[i+20>>2]=0;Uc(i,n,o-n>>3,e)|0;n=i+16|0;o=Tn(f[n>>2]|0,f[n+4>>2]|0,1)|0;n=(f[e+4>>2]|0)-(f[e>>2]|0)|0;l=j;f[l>>2]=n;f[l+4>>2]=0;l=Vn(o|0,I|0,39,0)|0;o=Yn(l|0,I|0,3)|0;l=Vn(o|0,I|0,8,0)|0;o=Vn(l|0,I|0,n|0,0)|0;Cl(e,o,I);o=i+24|0;f[o>>2]=(f[e>>2]|0)+(f[j>>2]|0);j=i+28|0;f[j>>2]=0;n=i+32|0;f[n>>2]=4194304;if(d){d=c;c=4194304;do{l=d;d=d+-1|0;p=f[a+(d<<2)>>2]|0;m=f[i>>2]|0;q=f[m+(p<<3)>>2]|0;r=q<<10;if(c>>>0<r>>>0)s=c;else{t=c;while(1){v=f[o>>2]|0;w=f[j>>2]|0;f[j>>2]=w+1;b[v+w>>0]=t;w=(f[n>>2]|0)>>>8;f[n>>2]=w;if(w>>>0<r>>>0){s=w;break}else t=w}}c=(((s>>>0)/(q>>>0)|0)<<20)+((s>>>0)%(q>>>0)|0)+(f[m+(p<<3)+4>>2]|0)|0;f[n>>2]=c}while((l|0)>1)}Mf(i,e);e=f[i>>2]|0;if(e|0){c=i+4|0;i=f[c>>2]|0;if((i|0)!=(e|0))f[c>>2]=i+(~((i+-8-e|0)>>>3)<<3);Oq(e)}e=f[h>>2]|0;if(!e){u=g;return 1}h=f[k>>2]|0;if((h|0)!=(e|0))f[k>>2]=h+(~((h+-8-e|0)>>>3)<<3);Oq(e);u=g;return 1}function de(a,c,d,e){a=a|0;c=c|0;d=d|0;e=e|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,v=0,w=0;g=u;u=u+64|0;h=g+48|0;i=g;j=d+1|0;f[h>>2]=0;k=h+4|0;f[k>>2]=0;f[h+8>>2]=0;do if(j)if(j>>>0>536870911)aq(h);else{l=ln(j<<3)|0;f[h>>2]=l;m=l+(j<<3)|0;f[h+8>>2]=m;sj(l|0,0,(d<<3)+8|0)|0;f[k>>2]=m;n=l;o=m;break}else{n=0;o=0}while(0);d=(c|0)>0;if(d){j=0;do{m=n+(f[a+(j<<2)>>2]<<3)|0;l=m;p=Vn(f[l>>2]|0,f[l+4>>2]|0,1,0)|0;l=m;f[l>>2]=p;f[l+4>>2]=I;j=j+1|0}while((j|0)!=(c|0))}j=i+40|0;l=j;f[l>>2]=0;f[l+4>>2]=0;f[i>>2]=0;f[i+4>>2]=0;f[i+8>>2]=0;f[i+12>>2]=0;f[i+16>>2]=0;f[i+20>>2]=0;Vc(i,n,o-n>>3,e)|0;n=i+16|0;o=Tn(f[n>>2]|0,f[n+4>>2]|0,1)|0;n=(f[e+4>>2]|0)-(f[e>>2]|0)|0;l=j;f[l>>2]=n;f[l+4>>2]=0;l=Vn(o|0,I|0,39,0)|0;o=Yn(l|0,I|0,3)|0;l=Vn(o|0,I|0,8,0)|0;o=Vn(l|0,I|0,n|0,0)|0;Cl(e,o,I);o=i+24|0;f[o>>2]=(f[e>>2]|0)+(f[j>>2]|0);j=i+28|0;f[j>>2]=0;n=i+32|0;f[n>>2]=4194304;if(d){d=c;c=4194304;do{l=d;d=d+-1|0;p=f[a+(d<<2)>>2]|0;m=f[i>>2]|0;q=f[m+(p<<3)>>2]|0;r=q<<10;if(c>>>0<r>>>0)s=c;else{t=c;while(1){v=f[o>>2]|0;w=f[j>>2]|0;f[j>>2]=w+1;b[v+w>>0]=t;w=(f[n>>2]|0)>>>8;f[n>>2]=w;if(w>>>0<r>>>0){s=w;break}else t=w}}c=(((s>>>0)/(q>>>0)|0)<<20)+((s>>>0)%(q>>>0)|0)+(f[m+(p<<3)+4>>2]|0)|0;f[n>>2]=c}while((l|0)>1)}Mf(i,e);e=f[i>>2]|0;if(e|0){c=i+4|0;i=f[c>>2]|0;if((i|0)!=(e|0))f[c>>2]=i+(~((i+-8-e|0)>>>3)<<3);Oq(e)}e=f[h>>2]|0;if(!e){u=g;return 1}h=f[k>>2]|0;if((h|0)!=(e|0))f[k>>2]=h+(~((h+-8-e|0)>>>3)<<3);Oq(e);u=g;return 1}function ee(a,c,d,e){a=a|0;c=c|0;d=d|0;e=e|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,v=0,w=0;g=u;u=u+64|0;h=g+48|0;i=g;j=d+1|0;f[h>>2]=0;k=h+4|0;f[k>>2]=0;f[h+8>>2]=0;do if(j)if(j>>>0>536870911)aq(h);else{l=ln(j<<3)|0;f[h>>2]=l;m=l+(j<<3)|0;f[h+8>>2]=m;sj(l|0,0,(d<<3)+8|0)|0;f[k>>2]=m;n=l;o=m;break}else{n=0;o=0}while(0);d=(c|0)>0;if(d){j=0;do{m=n+(f[a+(j<<2)>>2]<<3)|0;l=m;p=Vn(f[l>>2]|0,f[l+4>>2]|0,1,0)|0;l=m;f[l>>2]=p;f[l+4>>2]=I;j=j+1|0}while((j|0)!=(c|0))}j=i+40|0;l=j;f[l>>2]=0;f[l+4>>2]=0;f[i>>2]=0;f[i+4>>2]=0;f[i+8>>2]=0;f[i+12>>2]=0;f[i+16>>2]=0;f[i+20>>2]=0;Wc(i,n,o-n>>3,e)|0;n=i+16|0;o=Tn(f[n>>2]|0,f[n+4>>2]|0,1)|0;n=(f[e+4>>2]|0)-(f[e>>2]|0)|0;l=j;f[l>>2]=n;f[l+4>>2]=0;l=Vn(o|0,I|0,39,0)|0;o=Yn(l|0,I|0,3)|0;l=Vn(o|0,I|0,8,0)|0;o=Vn(l|0,I|0,n|0,0)|0;Cl(e,o,I);o=i+24|0;f[o>>2]=(f[e>>2]|0)+(f[j>>2]|0);j=Line truncated
function vc(a,c,d){a=a|0;c=c|0;d=d|0;var e=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,v=0;e=u;u=u+32|0;g=e+16|0;h=e+12|0;i=e+8|0;j=e+4|0;k=e;switch(f[c+28>>2]|0){case 9:{l=f[d>>2]|0;switch(b[c+24>>0]|0){case 1:{f[h>>2]=l;f[g>>2]=f[h>>2];m=hc(a,c,g)|0;break}case 2:{f[i>>2]=l;f[g>>2]=f[i>>2];m=Wb(a,c,g)|0;break}case 3:{f[j>>2]=l;f[g>>2]=f[j>>2];m=uc(a,c,g)|0;break}case 4:{f[k>>2]=l;f[g>>2]=f[k>>2];m=mc(a,c,g)|0;break}default:m=0}n=m;break}case 1:{m=f[d>>2]|0;switch(b[c+24>>0]|0){case 1:{f[h>>2]=m;f[g>>2]=f[h>>2];o=gc(a,c,g)|0;break}case 2:{f[i>>2]=m;f[g>>2]=f[i>>2];o=Xb(a,c,g)|0;break}case 3:{f[j>>2]=m;f[g>>2]=f[j>>2];o=sc(a,c,g)|0;break}case 4:{f[k>>2]=m;f[g>>2]=f[k>>2];o=lc(a,c,g)|0;break}default:o=0}n=o;break}case 11:case 2:{o=f[d>>2]|0;switch(b[c+24>>0]|0){case 1:{f[h>>2]=o;f[g>>2]=f[h>>2];p=gc(a,c,g)|0;break}case 2:{f[i>>2]=o;f[g>>2]=f[i>>2];p=Xb(a,c,g)|0;break}case 3:{f[j>>2]=o;f[g>>2]=f[j>>2];p=sc(a,c,g)|0;break}case 4:{f[k>>2]=o;f[g>>2]=f[k>>2];p=lc(a,c,g)|0;break}default:p=0}n=p;break}case 4:{p=f[d>>2]|0;switch(b[c+24>>0]|0){case 1:{f[h>>2]=p;f[g>>2]=f[h>>2];q=ec(a,c,g)|0;break}case 2:{f[i>>2]=p;f[g>>2]=f[i>>2];q=Vb(a,c,g)|0;break}case 3:{f[j>>2]=p;f[g>>2]=f[j>>2];q=nc(a,c,g)|0;break}case 4:{f[k>>2]=p;f[g>>2]=f[k>>2];q=jc(a,c,g)|0;break}default:q=0}n=q;break}case 3:{q=f[d>>2]|0;switch(b[c+24>>0]|0){case 1:{f[h>>2]=q;f[g>>2]=f[h>>2];r=ec(a,c,g)|0;break}case 2:{f[i>>2]=q;f[g>>2]=f[i>>2];r=Vb(a,c,g)|0;break}case 3:{f[j>>2]=q;f[g>>2]=f[j>>2];r=nc(a,c,g)|0;break}case 4:{f[k>>2]=q;f[g>>2]=f[k>>2];r=jc(a,c,g)|0;break}default:r=0}n=r;break}case 6:{r=f[d>>2]|0;switch(b[c+24>>0]|0){case 1:{f[h>>2]=r;f[g>>2]=f[h>>2];s=hc(a,c,g)|0;break}case 2:{f[i>>2]=r;f[g>>2]=f[i>>2];s=Wb(a,c,g)|0;break}case 3:{f[j>>2]=r;f[g>>2]=f[j>>2];s=uc(a,c,g)|0;break}case 4:{f[k>>2]=r;f[g>>2]=f[k>>2];s=mc(a,c,g)|0;break}default:s=0}n=s;break}case 5:{s=f[d>>2]|0;switch(b[c+24>>0]|0){case 1:{f[h>>2]=s;f[g>>2]=f[h>>2];t=hc(a,c,g)|0;break}case 2:{f[i>>2]=s;f[g>>2]=f[i>>2];t=Wb(a,c,g)|0;break}case 3:{f[j>>2]=s;f[g>>2]=f[j>>2];t=uc(a,c,g)|0;break}case 4:{f[k>>2]=s;f[g>>2]=f[k>>2];t=mc(a,c,g)|0;break}default:t=0}n=t;break}default:{v=-1;u=e;return v|0}}v=(n|0)==0?-1:n;u=e;return v|0}function wc(a,c,d){a=a|0;c=c|0;d=d|0;var e=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0;e=u;u=u+32|0;g=e+16|0;h=e+12|0;i=e+29|0;j=e;k=e+28|0;if(!(f[(f[a+8>>2]|0)+80>>2]|0)){l=1;u=e;return l|0}b[i>>0]=-2;m=a+36|0;n=f[m>>2]|0;if(n)if(Ra[f[(f[a>>2]|0)+40>>2]&127](a,n)|0){n=f[m>>2]|0;o=(Qa[f[(f[n>>2]|0)+8>>2]&127](n)|0)&255;b[i>>0]=o;p=5}else q=0;else p=5;if((p|0)==5){o=d+16|0;n=o;r=f[n+4>>2]|0;if(!((r|0)>0|(r|0)==0&(f[n>>2]|0)>>>0>0)){f[h>>2]=f[d+4>>2];f[g>>2]=f[h>>2];Me(d,g,i,i+1|0)|0}i=f[m>>2]|0;if(i|0?(n=(Qa[f[(f[i>>2]|0)+36>>2]&127](i)|0)&255,b[j>>0]=n,n=o,i=f[n+4>>2]|0,!((i|0)>0|(i|0)==0&(f[n>>2]|0)>>>0>0)):0){f[h>>2]=f[d+4>>2];f[g>>2]=f[h>>2];Me(d,g,j,j+1|0)|0}n=f[a+32>>2]|0;i=b[n+24>>0]|0;r=X(f[n+80>>2]|0,i)|0;s=(f[f[n>>2]>>2]|0)+(f[n+48>>2]|0)|0;f[j>>2]=0;n=j+4|0;f[n>>2]=0;f[j+8>>2]=0;t=(r|0)==0;do if(!t)if(r>>>0>1073741823)aq(j);else{v=r<<2;w=ln(v)|0;f[j>>2]=w;x=w+(r<<2)|0;f[j+8>>2]=x;sj(w|0,0,v|0)|0;f[n>>2]=x;y=w;break}else y=0;while(0);w=f[m>>2]|0;do if(w){Ta[f[(f[w>>2]|0)+44>>2]&31](w,s,y,r,i,f[c>>2]|0)|0;x=f[m>>2]|0;if(!x){z=s;A=f[j>>2]|0;p=20;break}if(!(Qa[f[(f[x>>2]|0)+32>>2]&127](x)|0)){x=f[j>>2]|0;z=f[m>>2]|0?x:s;A=x;p=20}}else{z=s;A=y;p=20}while(0);if((p|0)==20)xm(z,r,A);A=a+4|0;a=f[A>>2]|0;do if(a){z=f[a+48>>2]|0;f[g>>2]=0;f[g+4>>2]=0;f[g+8>>2]=0;y=ln(48)|0;f[g>>2]=y;f[g+8>>2]=-2147483600;f[g+4>>2]=34;s=y;w=10697;x=s+34|0;do{b[s>>0]=b[w>>0]|0;s=s+1|0;w=w+1|0}while((s|0)<(x|0));b[y+34>>0]=0;w=Yj(z,g,1)|0;if((b[g+11>>0]|0)<0)Oq(f[g>>2]|0);if(!w){if(!t){w=f[j>>2]|0;s=0;x=0;do{x=f[w+(s<<2)>>2]|x;s=s+1|0}while((s|0)!=(r|0));if(x)B=((_(x|0)|0)>>>3^3)+1|0;else B=1}else B=1;b[k>>0]=0;s=o;w=f[s>>2]|0;z=f[s+4>>2]|0;if((z|0)>0|(z|0)==0&w>>>0>0){C=z;D=w}else{f[h>>2]=f[d+4>>2];f[g>>2]=f[h>>2];Me(d,g,k,k+1|0)|0;w=o;C=f[w+4>>2]|0;D=f[w>>2]|0}b[k>>0]=B;if(!((C|0)>0|(C|0)==0&D>>>0>0)){f[h>>2]=f[d+4>>2];f[g>>2]=f[h>>2];Me(d,g,k,k+1|0)|0}if((B|0)==(Vl(5)|0)){w=f[j>>2]|0;z=o;s=f[z+4>>2]|0;if(!((s|0)>0|(s|0)==0&(f[z>>2]|0)>>>0>0)){f[h>>2]=f[d+4>>2];f[g>>2]=f[h>>2];Me(d,g,w,w+(r<<2)|0)|0}p=48;break}if(t)p=48;else{w=d+4|0;z=0;do{s=(f[j>>2]|0)+(z<<2)|0;y=o;v=f[y+4>>2]|0;if(!((v|0)>0|(v|0)==0&(f[y>>2]|0)>>>0>0)){f[h>>2]=f[w>>2];f[g>>2]=f[h>>2];Me(d,g,s,s+B|0)|0}z=z+1|0}while(z>>>0<r>>>0);p=48}}else p=27}else p=27;while(0);if((p|0)==27){b[k>>0]=1;r=o;o=f[r+4>>2]|0;if(!((o|0)>0|(o|0)==0&(f[r>>2]|0)>>>0>0)){f[h>>2]=f[d+4>>2];f[g>>2]=f[h>>2];Me(d,g,k,k+1|0)|0}lp(g);k=f[A>>2]|0;if(k|0)Zj(g,10-(mi(f[k+48>>2]|0)|0)|0)|0;k=Mc(f[j>>2]|0,X((f[c+4>>2]|0)-(f[c>>2]|0)>>2,i)|0,i,g,d)|0;Ej(g,f[g+4>>2]|0);if(k)p=48;else E=0}if((p|0)==48){p=f[m>>2]|0;if(!p)E=1;else{Ra[f[(f[p>>2]|0)+40>>2]&127](p,d)|0;E=1}}d=f[j>>2]|0;if(d|0){j=f[n>>2]|0;if((j|0)!=(d|0))f[n>>2]=j+(~((j+-4-d|0)>>>2)<<2);Oq(d)}q=E}l=q;u=e;return l|0}function xLine truncated
function $a(a){a=a|0;var b=0,c=0,d=0,e=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,ra=0,sa=0,ta=0,ua=0,va=0,wa=0,xa=0,ya=0,za=0;b=u;u=u+16|0;c=b;do if(a>>>0<245){d=a>>>0<11?16:a+11&-8;e=d>>>3;g=f[4784]|0;h=g>>>e;if(h&3|0){i=(h&1^1)+e|0;j=19176+(i<<1<<2)|0;k=j+8|0;l=f[k>>2]|0;m=l+8|0;n=f[m>>2]|0;if((n|0)==(j|0))f[4784]=g&~(1<<i);else{f[n+12>>2]=j;f[k>>2]=n}n=i<<3;f[l+4>>2]=n|3;i=l+n+4|0;f[i>>2]=f[i>>2]|1;o=m;u=b;return o|0}m=f[4786]|0;if(d>>>0>m>>>0){if(h|0){i=2<<e;n=h<<e&(i|0-i);i=(n&0-n)+-1|0;n=i>>>12&16;e=i>>>n;i=e>>>5&8;h=e>>>i;e=h>>>2&4;l=h>>>e;h=l>>>1&2;k=l>>>h;l=k>>>1&1;j=(i|n|e|h|l)+(k>>>l)|0;l=19176+(j<<1<<2)|0;k=l+8|0;h=f[k>>2]|0;e=h+8|0;n=f[e>>2]|0;if((n|0)==(l|0)){i=g&~(1<<j);f[4784]=i;p=i}else{f[n+12>>2]=l;f[k>>2]=n;p=g}n=j<<3;j=n-d|0;f[h+4>>2]=d|3;k=h+d|0;f[k+4>>2]=j|1;f[h+n>>2]=j;if(m|0){n=f[4789]|0;h=m>>>3;l=19176+(h<<1<<2)|0;i=1<<h;if(!(p&i)){f[4784]=p|i;q=l;r=l+8|0}else{i=l+8|0;q=f[i>>2]|0;r=i}f[r>>2]=n;f[q+12>>2]=n;f[n+8>>2]=q;f[n+12>>2]=l}f[4786]=j;f[4789]=k;o=e;u=b;return o|0}e=f[4785]|0;if(e){k=(e&0-e)+-1|0;j=k>>>12&16;l=k>>>j;k=l>>>5&8;n=l>>>k;l=n>>>2&4;i=n>>>l;n=i>>>1&2;h=i>>>n;i=h>>>1&1;s=f[19440+((k|j|l|n|i)+(h>>>i)<<2)>>2]|0;i=(f[s+4>>2]&-8)-d|0;h=f[s+16+(((f[s+16>>2]|0)==0&1)<<2)>>2]|0;if(!h){t=s;v=i}else{n=s;s=i;i=h;while(1){h=(f[i+4>>2]&-8)-d|0;l=h>>>0<s>>>0;j=l?h:s;h=l?i:n;i=f[i+16+(((f[i+16>>2]|0)==0&1)<<2)>>2]|0;if(!i){t=h;v=j;break}else{n=h;s=j}}}s=t+d|0;if(s>>>0>t>>>0){n=f[t+24>>2]|0;i=f[t+12>>2]|0;do if((i|0)==(t|0)){j=t+20|0;h=f[j>>2]|0;if(!h){l=t+16|0;k=f[l>>2]|0;if(!k){w=0;break}else{x=k;y=l}}else{x=h;y=j}while(1){j=x+20|0;h=f[j>>2]|0;if(h|0){x=h;y=j;continue}j=x+16|0;h=f[j>>2]|0;if(!h)break;else{x=h;y=j}}f[y>>2]=0;w=x}else{j=f[t+8>>2]|0;f[j+12>>2]=i;f[i+8>>2]=j;w=i}while(0);do if(n|0){i=f[t+28>>2]|0;j=19440+(i<<2)|0;if((t|0)==(f[j>>2]|0)){f[j>>2]=w;if(!w){f[4785]=e&~(1<<i);break}}else{f[n+16+(((f[n+16>>2]|0)!=(t|0)&1)<<2)>>2]=w;if(!w)break}f[w+24>>2]=n;i=f[t+16>>2]|0;if(i|0){f[w+16>>2]=i;f[i+24>>2]=w}i=f[t+20>>2]|0;if(i|0){f[w+20>>2]=i;f[i+24>>2]=w}}while(0);if(v>>>0<16){n=v+d|0;f[t+4>>2]=n|3;e=t+n+4|0;f[e>>2]=f[e>>2]|1}else{f[t+4>>2]=d|3;f[s+4>>2]=v|1;f[s+v>>2]=v;if(m|0){e=f[4789]|0;n=m>>>3;i=19176+(n<<1<<2)|0;j=1<<n;if(!(g&j)){f[4784]=g|j;z=i;A=i+8|0}else{j=i+8|0;z=f[j>>2]|0;A=j}f[A>>2]=e;f[z+12>>2]=e;f[e+8>>2]=z;f[e+12>>2]=i}f[4786]=v;f[4789]=s}o=t+8|0;u=b;return o|0}else B=d}else B=d}else B=d}else if(a>>>0<=4294967231){i=a+11|0;e=i&-8;j=f[4785]|0;if(j){n=0-e|0;h=i>>>8;if(h)if(e>>>0>16777215)C=31;else{i=(h+1048320|0)>>>16&8;l=h<<i;h=(l+520192|0)>>>16&4;k=l<<h;l=(k+245760|0)>>>16&2;D=14-(h|i|l)+(k<<l>>>15)|0;C=e>>>(D+7|0)&1|D<<1}else C=0;D=f[19440+(C<<2)>>2]|0;a:do if(!D){E=0;F=0;G=n;H=57}else{l=0;k=n;i=D;h=e<<((C|0)==31?0:25-(C>>>1)|0);I=0;while(1){J=(f[i+4>>2]&-8)-e|0;if(J>>>0<k>>>0)if(!J){K=0;L=i;M=i;H=61;break a}else{N=i;O=J}else{N=l;O=k}J=f[i+20>>2]|0;i=f[i+16+(h>>>31<<2)>>2]|0;P=(J|0)==0|(J|0)==(i|0)?I:J;J=(i|0)==0;if(J){E=P;F=N;G=O;H=57;break}else{l=N;k=O;h=h<<((J^1)&1);I=P}}}while(0);if((H|0)==57){if((E|0)==0&(F|0)==0){D=2<<C;n=j&(D|0-D);if(!n){B=e;break}D=(n&0-n)+-1|0;n=D>>>12&16;d=D>>>n;D=d>>>5&8;s=d>>>D;d=s>>>2&4;g=s>>>d;s=g>>>1&2;m=g>>>s;g=m>>>1&1;Q=0;R=f[19440+((D|n|d|s|g)+(m>>>g)<<2)>>2]|0}else{Q=F;R=E}if(!R){S=Q;T=G}else{K=G;L=R;M=Q;H=61}}if((H|0)==61)while(1){H=0;g=(f[L+4>>2]&-8)-e|0;m=g>>>0<K>>>0;s=m?g:K;g=m?L:M;L=f[L+16+(((f[L+16>>2]|0)==0&1)<<2)>>2]|0;if(!L){S=g;T=s;break}else{K=s;M=g;H=61}}if((S|0)!=0?T>>>0<((f[4786]|0)-e|0)>>>0:0){g=S+e|0;if(g>>>0<=S>>>0){o=0;u=b;return o|0}s=f[S+24>>2]|0;m=f[S+12>>2]|0;do if((m|0)==(S|0)){d=S+20|0;n=f[d>>2]|0;if(!n){D=S+16|0;I=f[D>>2]|0;if(!I){U=0;break}else{V=I;W=D}}else{V=n;W=d}while(1){d=V+20|0;n=f[d>>2]|0;if(n|0){V=n;W=d;continue}d=V+16|0;n=f[d>>2]|0;if(!n)break;else{V=n;W=d}}f[W>>2]=0;U=V}else{d=f[S+8>>2]|0;f[d+12>>2]=m;f[m+8>>2]=d;U=m}while(0);do if(s){m=f[S+28>>2]|0;d=19440+(m<<2)|0;if((S|0)==(f[d>>2]|0)){f[d>>2]=U;if(!U){d=j&~(1<<m);f[4785]=d;X=d;break}}else{f[s+16+(((f[s+16>>2]|0)!=(S|0)&1)<<2)>>2]=U;if(!U){X=j;break}}f[U+24>>2]=s;d=f[S+16>>2]|0;if(d|0){f[U+16>>2]=d;f[d+24>>2]=U}d=f[S+20>>2]|0;if(d){f[U+20>>2]=d;f[d+24>>2]=U;X=j}else X=j}else X=j;while(0);do if(T>>>0>=16){f[S+4>>2]=e|3;f[g+4>>2]=T|1;f[g+T>>2]=T;j=T>>>3;if(T>>>0<256){s=19176+(j<<1<<2)|0;d=f[4784]|0;m=1<<j;if(!(d&m)){f[4784]=d|m;Y=s;Z=s+8|0}else{m=s+8|0;Y=f[m>>2]|0;Z=m}f[Z>>2]=g;f[Y+12>>2]=g;f[g+8>>2]=Y;f[g+12>>2]=s;break}s=T>>>8;if(s)if(T>>>0>16777215)_=31;else{m=(s+1048320|0)>>>16&8;d=s<<m;s=(d+520192|0)>>>16&4;j=d<<s;d=(j+245760|0)>>>16&2;n=14-(s|m|d)+(j<<d>>>15)|0;_=T>>>(n+7|0)&1|n<<1}else _=0;n=19440+(_<<2)|0;f[g+28>>2]=_;d=g+16|0;f[d+4>>2]=0;f[d>>2]=0;d=1<<_;if(!(X&d)){f[4785]=X|d;f[n>>2]=g;f[g+24>>2]=n;f[g+12>>2]=g;f[g+8>>2]=g;break}d=T<<((_|0)==31?0:25-(_>>>1)|0);j=f[n>>2]|0;while(1){if((f[j+4>>2]&-8|0)Line truncated
function Bb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0;d=a;a=b;a:while(1){b=a;e=a+-4|0;g=d;while(1){h=g;b:while(1){i=h;j=b-i|0;k=j>>2;switch(k|0){case 2:{l=5;break a;break}case 3:{l=11;break a;break}case 4:{l=12;break a;break}case 5:{l=13;break a;break}case 1:case 0:{l=84;break a;break}default:{}}if((j|0)<124){l=15;break a}m=h+(((k|0)/2|0)<<2)|0;if((j|0)>3996){j=(k|0)/4|0;n=ig(h,h+(j<<2)|0,m,m+(j<<2)|0,e,c)|0}else n=Vg(h,m,e,c)|0;o=f[h>>2]|0;j=f[m>>2]|0;p=f[c>>2]|0;k=f[p>>2]|0;q=(f[p+4>>2]|0)-k>>3;if(q>>>0<=o>>>0){l=20;break a}r=k;if(q>>>0<=j>>>0){l=22;break a}k=f[r+(o<<3)>>2]|0;s=f[r+(j<<3)>>2]|0;if(k>>>0<s>>>0){t=e;u=n;break}else v=e;while(1){v=v+-4|0;if((h|0)==(v|0))break;w=f[v>>2]|0;if(q>>>0<=w>>>0){l=51;break a}if((f[r+(w<<3)>>2]|0)>>>0<s>>>0){l=53;break b}}s=h+4|0;j=f[e>>2]|0;if(q>>>0<=j>>>0){l=26;break a}if(k>>>0<(f[r+(j<<3)>>2]|0)>>>0)x=s;else{if((s|0)==(e|0)){l=84;break a}else y=s;while(1){z=f[y>>2]|0;if(q>>>0<=z>>>0){l=32;break a}if(k>>>0<(f[r+(z<<3)>>2]|0)>>>0)break;s=y+4|0;if((s|0)==(e|0)){l=84;break a}else y=s}f[y>>2]=j;f[e>>2]=z;x=y+4|0}if((x|0)==(e|0)){l=84;break a}r=f[h>>2]|0;A=f[c>>2]|0;k=f[A>>2]|0;q=(f[A+4>>2]|0)-k>>3;if(q>>>0<=r>>>0){l=38;break a}s=k;k=e;B=x;C=r;while(1){r=s+(C<<3)|0;D=q>>>0>C>>>0;E=B;while(1){F=f[E>>2]|0;if(q>>>0<=F>>>0){l=40;break a}G=f[r>>2]|0;if(G>>>0<(f[s+(F<<3)>>2]|0)>>>0)break;if(D)E=E+4|0;else{l=38;break a}}if(q>>>0>C>>>0)H=k;else{l=46;break a}do{H=H+-4|0;I=f[H>>2]|0;if(q>>>0<=I>>>0){l=47;break a}}while(G>>>0<(f[s+(I<<3)>>2]|0)>>>0);if(E>>>0>=H>>>0){h=E;continue b}D=f[E>>2]|0;f[E>>2]=I;f[H>>2]=D;C=f[h>>2]|0;if(q>>>0<=C>>>0){l=38;break a}else{k=H;B=E+4|0}}}if((l|0)==53){l=0;f[h>>2]=w;f[v>>2]=o;t=v;u=n+1|0}B=h+4|0;c:do if(B>>>0<t>>>0){k=f[B>>2]|0;C=f[c>>2]|0;q=f[C>>2]|0;s=(f[C+4>>2]|0)-q>>3;if(s>>>0>k>>>0){J=t;K=B;L=u;M=m;N=s;O=q;P=C;Q=k}else{R=C;l=57;break a}while(1){C=f[c>>2]|0;k=C+4|0;q=f[M>>2]|0;s=K;j=O;D=N;S=P;r=Q;while(1){F=j;if(D>>>0<=q>>>0){l=59;break a}if((f[F+(r<<3)>>2]|0)>>>0>=(f[F+(q<<3)>>2]|0)>>>0)break;F=s+4|0;T=f[F>>2]|0;j=f[C>>2]|0;D=(f[k>>2]|0)-j>>3;if(D>>>0<=T>>>0){R=C;l=57;break a}else{s=F;S=C;r=T}}C=f[M>>2]|0;O=f[S>>2]|0;N=(f[S+4>>2]|0)-O>>3;D=O;j=D+(C<<3)|0;if(N>>>0>C>>>0)U=J;else{l=65;break a}do{U=U+-4|0;V=f[U>>2]|0;if(N>>>0<=V>>>0){l=66;break a}}while((f[D+(V<<3)>>2]|0)>>>0>=(f[j>>2]|0)>>>0);if(s>>>0>U>>>0){W=M;X=L;Y=s;break c}f[s>>2]=V;f[U>>2]=r;K=s+4|0;Q=f[K>>2]|0;if(N>>>0<=Q>>>0){R=S;l=57;break a}else{J=U;L=L+1|0;M=(M|0)==(s|0)?U:M;P=S}}}else{W=m;X=u;Y=B}while(0);if((Y|0)!=(W|0)){B=f[W>>2]|0;j=f[Y>>2]|0;Z=f[c>>2]|0;D=f[Z>>2]|0;C=(f[Z+4>>2]|0)-D>>3;if(C>>>0<=B>>>0){l=72;break a}k=D;if(C>>>0<=j>>>0){l=74;break a}if((f[k+(B<<3)>>2]|0)>>>0<(f[k+(j<<3)>>2]|0)>>>0){f[Y>>2]=B;f[W>>2]=j;_=X+1|0}else _=X}else _=X;if(!_){$=_d(h,Y,c)|0;j=Y+4|0;if(_d(j,a,c)|0){l=83;break}if($){g=j;continue}}j=Y;if((j-i|0)>=(b-j|0)){l=82;break}Bb(h,Y,c);g=Y+4|0}if((l|0)==82){l=0;Bb(Y+4|0,a,c);d=h;a=Y;continue}else if((l|0)==83){l=0;if($){l=84;break}else{d=h;a=Y;continue}}}switch(l|0){case 5:{l=f[e>>2]|0;Y=f[h>>2]|0;d=f[c>>2]|0;$=f[d>>2]|0;i=(f[d+4>>2]|0)-$>>3;if(i>>>0<=l>>>0)aq(d);_=$;if(i>>>0<=Y>>>0)aq(d);if((f[_+(l<<3)>>2]|0)>>>0>=(f[_+(Y<<3)>>2]|0)>>>0)return;f[h>>2]=l;f[e>>2]=Y;return}case 11:{Vg(h,h+4|0,e,c)|0;return}case 12:{jh(h,h+4|0,h+8|0,e,c)|0;return}case 13:{ig(h,h+4|0,h+8|0,h+12|0,e,c)|0;return}case 15:{ih(h,a,c);return}case 20:{aq(p);break}case 22:{aq(p);break}case 26:{aq(p);break}case 32:{aq(p);break}case 38:{aq(A);break}case 40:{aq(A);break}case 46:{aq(A);break}case 47:{aq(A);break}case 51:{aq(p);break}case 57:{aq(R);break}case 59:{aq(S);break}case 65:{if(N>>>0>(f[J+-4>>2]|0)>>>0)aq(S);else aq(S);break}case 66:{aq(S);break}case 72:{aq(Z);break}case 74:{aq(Z);break}case 84:return}}function Cb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0;d=a;a=b;a:while(1){b=a;e=a+-4|0;g=d;while(1){h=g;b:while(1){i=h;j=b-i|0;k=j>>2;switch(k|0){case 2:{l=5;break a;break}case 3:{l=11;break a;break}case 4:{l=12;break a;break}case 5:{l=13;break a;break}case 1:case 0:{l=84;break a;break}default:{}}if((j|0)<124){l=15;break a}m=h+(((k|0)/2|0)<<2)|0;if((j|0)>3996){j=(k|0)/4|0;n=ig(h,h+(j<<2)|0,m,m+(j<<2)|0,e,c)|0}else n=Vg(h,m,e,c)|0;o=f[h>>2]|0;j=f[m>>2]|0;p=f[c>>2]|0;k=f[p>>2]|0;q=(f[p+4>>2]|0)-k>>3;if(q>>>0<=o>>>0){l=20;break a}r=k;if(q>>>0<=j>>>0){l=22;break a}k=f[r+(o<<3)>>2]|0;s=f[r+(j<<3)>>2]|0;if(k>>>0<s>>>0){t=e;u=n;break}else v=e;while(1){v=v+-4|0;if((h|0)==(v|0))break;w=f[v>>2]|0;if(q>>>0<=w>>>0){l=51;break a}if((f[r+(w<<3)>>2]|0)>>>0<s>>>0){l=53;break b}}s=h+4|0;j=f[e>>2]|0;if(q>>>0<=j>>>0){l=26;break a}if(k>>>0<(f[r+(j<<3)>>2]|0)>>>0)x=s;else{if((s|0)==(e|0)){l=84;break a}else y=s;while(1){z=f[y>>2]|0;ifLine truncated
function di(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=u;u=u+16|0;e=d;Je(e,a+40|0,f[a+8>>2]|0,b,c);gj(a,e);a=f[e>>2]|0;f[e>>2]=0;if(!a){u=d;return 1}e=a+88|0;c=f[e>>2]|0;f[e>>2]=0;if(c|0){e=f[c+8>>2]|0;if(e|0){b=c+12|0;if((f[b>>2]|0)!=(e|0))f[b>>2]=e;Oq(e)}Oq(c)}c=f[a+68>>2]|0;if(c|0){e=a+72|0;b=f[e>>2]|0;if((b|0)!=(c|0))f[e>>2]=b+(~((b+-4-c|0)>>>2)<<2);Oq(c)}c=a+64|0;b=f[c>>2]|0;f[c>>2]=0;if(b|0){c=f[b>>2]|0;if(c|0){e=b+4|0;if((f[e>>2]|0)!=(c|0))f[e>>2]=c;Oq(c)}Oq(b)}Oq(a);u=d;return 1}function ei(a,b){a=a|0;b=b|0;var c=0,d=Oa,e=0,g=0;if((b|0)!=1)if(!(b+-1&b))c=b;else c=cb(b)|0;else c=2;b=f[a+4>>2]|0;if(c>>>0>b>>>0){Bd(a,c);return}if(c>>>0>=b>>>0)return;d=$((f[a+12>>2]|0)>>>0);e=~~$(W($(d/$(n[a+16>>2]))))>>>0;if(b>>>0>2&(b+-1&b|0)==0)g=1<<32-(_(e+-1|0)|0);else g=cb(e)|0;e=c>>>0<g>>>0?g:c;if(e>>>0>=b>>>0)return;Bd(a,e);return}function fi(a,c,d){a=a|0;c=c|0;d=d|0;var e=0,g=0,h=0,i=0,j=0;e=u;u=u+48|0;g=e;h=e+32|0;if(!c){i=0;u=e;return i|0}Gn(g);if((dm(c,0)|0)!=-1?Qa[f[(f[c>>2]|0)+16>>2]&127](c)|0:0){Va[f[(f[c>>2]|0)+20>>2]&127](c);ch(h,a,c,g);c=(f[h>>2]|0)==0;a=h+4|0;if((b[a+11>>0]|0)<0)Oq(f[a>>2]|0);if(c){c=f[g>>2]|0;a=g+4|0;rg(d,c,c+((f[a>>2]|0)-c)|0);j=(f[a>>2]|0)-(f[g>>2]|0)|0}else j=0}else j=0;a=g+12|0;c=f[a>>2]|0;f[a>>2]=0;if(c|0)Oq(c);c=f[g>>2]|0;if(c|0){a=g+4|0;if((f[a>>2]|0)!=(c|0))f[a>>2]=c;Oq(c)}i=j;u=e;return i|0}function gi(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=u;u=u+16|0;e=d;Fe(e,a+40|0,f[a+8>>2]|0,b,c);gj(a,e);a=f[e>>2]|0;f[e>>2]=0;if(!a){u=d;return 1}e=a+88|0;c=f[e>>2]|0;f[e>>2]=0;if(c|0){e=f[c+8>>2]|0;if(e|0){b=c+12|0;if((f[b>>2]|0)!=(e|0))f[b>>2]=e;Oq(e)}Oq(c)}c=f[a+68>>2]|0;if(c|0){e=a+72|0;b=f[e>>2]|0;if((b|0)!=(c|0))f[e>>2]=b+(~((b+-4-c|0)>>>2)<<2);Oq(c)}c=a+64|0;b=f[c>>2]|0;f[c>>2]=0;if(b|0){c=f[b>>2]|0;if(c|0){e=b+4|0;if((f[e>>2]|0)!=(c|0))f[e>>2]=c;Oq(c)}Oq(b)}Oq(a);u=d;return 1}function hi(a){a=a|0;var b=0,c=0,d=0,e=0,g=0,h=0,i=0,j=0;b=f[a>>2]|0;if(!b)return;c=a+4|0;d=f[c>>2]|0;if((d|0)==(b|0))e=b;else{g=d;do{d=g+-4|0;f[c>>2]=d;h=f[d>>2]|0;f[d>>2]=0;if(h|0){d=h+88|0;i=f[d>>2]|0;f[d>>2]=0;if(i|0){d=f[i+8>>2]|0;if(d|0){j=i+12|0;if((f[j>>2]|0)!=(d|0))f[j>>2]=d;Oq(d)}Oq(i)}i=f[h+68>>2]|0;if(i|0){d=h+72|0;j=f[d>>2]|0;if((j|0)!=(i|0))f[d>>2]=j+(~((j+-4-i|0)>>>2)<<2);Oq(i)}i=h+64|0;j=f[i>>2]|0;f[i>>2]=0;if(j|0){i=f[j>>2]|0;if(i|0){d=j+4|0;if((f[d>>2]|0)!=(i|0))f[d>>2]=i;Oq(i)}Oq(j)}Oq(h)}g=f[c>>2]|0}while((g|0)!=(b|0));e=f[a>>2]|0}Oq(e);return}function ii(a,c){a=a|0;c=c|0;var d=0,e=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;d=u;u=u+16|0;e=d+4|0;g=d;h=d+8|0;if(!(Ie(a,c)|0)){i=0;u=d;return i|0}j=a+36|0;k=a+40|0;a=f[j>>2]|0;if((f[k>>2]|0)==(a|0)){i=1;u=d;return i|0}l=c+16|0;m=c+4|0;n=h+1|0;o=0;p=a;do{a=f[p+(o<<2)>>2]|0;q=Qa[f[(f[a>>2]|0)+32>>2]&127](a)|0;b[h>>0]=q;q=l;a=f[q+4>>2]|0;if(!((a|0)>0|(a|0)==0&(f[q>>2]|0)>>>0>0)){f[g>>2]=f[m>>2];f[e>>2]=f[g>>2];Me(c,e,h,n)|0}o=o+1|0;p=f[j>>2]|0}while(o>>>0<(f[k>>2]|0)-p>>2>>>0);i=1;u=d;return i|0}function ji(a){a=a|0;var c=0,d=0,e=0,g=0,h=0,i=0;c=u;u=u+16|0;d=c;lp(a);f[a+16>>2]=0;f[a+20>>2]=0;f[a+12>>2]=a+16;e=a+24|0;lp(e);f[d>>2]=0;f[d+4>>2]=0;f[d+8>>2]=0;a=ln(32)|0;f[d>>2]=a;f[d+8>>2]=-2147483616;f[d+4>>2]=20;g=a;h=14538;i=g+20|0;do{b[g>>0]=b[h>>0]|0;g=g+1|0;h=h+1|0}while((g|0)<(i|0));b[a+20>>0]=0;Vj(e,d,1);if((b[d+11>>0]|0)<0)Oq(f[d>>2]|0);f[d>>2]=0;f[d+4>>2]=0;f[d+8>>2]=0;a=ln(32)|0;f[d>>2]=a;f[d+8>>2]=-2147483616;f[d+4>>2]=22;g=a;h=14559;i=g+22|0;do{b[g>>0]=b[h>>0]|0;g=g+1|0;h=h+1|0}while((g|0)<(i|0));b[a+22>>0]=0;Vj(e,d,1);if((b[d+11>>0]|0)>=0){u=c;return}Oq(f[d>>2]|0);u=c;return}function ki(a){a=a|0;var b=0,c=0,d=0,e=0,g=0,h=0,i=0;b=f[a+4>>2]|0;c=a+8|0;d=f[c>>2]|0;if((d|0)!=(b|0)){e=d;do{d=e+-4|0;f[c>>2]=d;g=f[d>>2]|0;f[d>>2]=0;if(g|0){d=g+88|0;h=f[d>>2]|0;f[d>>2]=0;if(h|0){d=f[h+8>>2]|0;if(d|0){i=h+12|0;if((f[i>>2]|0)!=(d|0))f[i>>2]=d;Oq(d)}Oq(h)}h=f[g+68>>2]|0;if(h|0){d=g+72|0;i=f[d>>2]|0;if((i|0)!=(h|0))f[d>>2]=i+(~((i+-4-h|0)>>>2)<<2);Oq(h)}h=g+64|0;i=f[h>>2]|0;f[h>>2]=0;if(i|0){h=f[i>>2]|0;if(h|0){d=i+4|0;if((f[d>>2]|0)!=(h|0))f[d>>2]=h;Oq(h)}Oq(i)}Oq(g)}e=f[c>>2]|0}while((e|0)!=(b|0))}b=f[a>>2]|0;if(!b)return;Oq(b);return}function li(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;c=u;u=u+16|0;d=c+8|0;e=c+4|0;g=c;f[g>>2]=f[a+12>>2];h=b+16|0;i=h;j=f[i>>2]|0;k=f[i+4>>2]|0;if((k|0)>0|(k|0)==0&j>>>0>0){l=k;m=j}else{f[e>>2]=f[b+4>>2];f[d>>2]=f[e>>2];Me(b,d,g,g+4|0)|0;j=h;l=f[j+4>>2]|0;m=f[j>>2]|0}f[g>>2]=f[a+20>>2];if((l|0)>0|(l|0)==0&m>>>0>0){u=c;return 1}f[e>>2]=f[b+4>>2];f[d>>2]=f[e>>2];Me(b,d,g,g+4|0)|0;u=c;return 1}function mi(a){a=a|0;var c=0,d=0,e=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;c=u;u=u+16|0;d=c;e=ln(16)|0;f[d>>2]=e;f[d+8>>2]=-2147483632;f[d+4>>2]=14;g=e;h=14408;i=g+14|0;do{b[g>>0]=b[h>>0]|0;g=g+1|0;h=h+1|0}while((g|0)<(i|0));b[e+14>>0]=0;e=Hk(a,d,-1)|0;if((b[d+11>>0]|0)<0)Oq(f[d>>2]|0);j=ln(16)|0;f[d>>2]=j;f[d+8>>2]=-2147483632;f[d+4>>2]=14;g=j;h=14423;i=g+14|0;do{b[g>>0]=b[h>>0]|0;g=g+1|0;h=h+1|0}while((g|0)<(i|0));b[j+14>>0]=0;j=Hk(a,d,-1)|0;if((b[d+11>>0]|0)>=0){k=(e|0)<(j|0);l=k?j:e;m=(l|0)==-1;n=m?5:l;u=c;return n|Line truncated
// EMSCRIPTEN_END_FUNCS
var Qa=[Rq,oq,pq,pq,oq,gb,Tq,Tq,Tq,hk,kg,pq,Wo,Tq,Tq,pq,Tq,pq,pq,yl,oq,yl,cq,wl,pq,co,wl,pq,fo,cl,pq,Zp,Rn,yl,pq,yl,oq,yl,cq,wl,pq,co,wl,pq,fo,cl,pq,Zp,Rn,yl,pq,cq,Tq,Wo,pq,Tq,pq,cq,pq,ql,oq,ql,Rn,ql,cq,pl,pq,co,pl,pq,fo,Wk,pq,Zp,pq,ql,oq,ql,Rn,ql,cq,pl,pq,co,pl,pq,fo,Wk,pq,Zp,pq,oq,pq,pq,Nd,pq,Vo,Xe,mh,zk,po,ko,pb,Qo,Wo,Mg,Wg,Lf,rb,Qo,Wo,pq,Tq,Tq,zc,Ki,Tq,pq,pq,Uj,Tq,Uj,ck,rn,Jp,Rq,Rq,Rq];var Ra=[Eq,xl,nh,Ie,El,Up,Up,Up,Ep,jb,rj,wo,Ep,Ep,ti,nj,ii,kk,ol,Qj,$k,dk,ek,Te,Go,Up,ni,Up,Pl,$d,Up,Pl,nf,Up,Ml,sh,mm,Ed,Up,Pl,$d,Up,Pl,nf,Up,Ml,sh,mm,Ed,Cn,Go,Up,li,Dd,Up,Fl,Zd,Up,Fl,hf,Up,Bl,rh,mm,Dd,Up,Fl,Zd,Up,Fl,hf,Up,Bl,rh,mm,zn,Kn,Fn,Ln,Jn,dh,ik,uk,cc,ye,Rm,og,vf,wf,ah,ik,uk,bc,ye,Rm,Ep,Up,Up,of,zm,mg,of,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq,Eq];var Sa=[nq,ho,vp,bn,Sm,wg,oj,kl,xh,wc,Kh,pg,gi,Rb,di,Ng,ml,Nm,Cj,nq,nq,nq,nq,nq,nq,nq,nq,nq,nq,nq,nq,nq];var Ta=[cp,Xd,Jc,oc,be,Ae,Tb,bb,Lc,pc,ae,ze,Sb,ab,eh,kd,Ic,fb,pf,If,tc,od,Kc,db,kf,Gf,qc,cp,cp,cp,cp,cp];var Ua=[Zq,Hq,Oi,Mn];var Va=[Xq,Sq,Mq,Gm,jm,al,Fq,ui,np,Ni,ep,Lh,Io,Jm,Fm,gm,Fq,Ql,Ql,Ql,Jk,wk,_k,Rk,el,Uk,Sq,Mq,Fq,Yi,em,Ql,Ql,Dk,rk,Xk,Pk,bl,Tk,Sq,Mq,Fq,Vi,Ul,Jm,Fm,Sq,Mq,Mq,Mq,yj,Jl,Sl,Al,Im,tm,qn,dn,Sq,Mq,Mq,Mq,vj,zl,Kl,sl,Em,km,gn,Um,Sq,Mq,xk,ok,nm,Lm,ff,$n,vk,nk,nn,Om,Tl,Ak,qk,tn,Xm,Wl,fm,_l,cf,On,mj,Fq,yp,Sq,Mq,Fq,yp,yp,Nk,Gk,sb,Og,Rp,Sq,Sp,Sq,Sq,Sp,pp,$p,$p,xn,Xq,Xq,Xq,Xq,Xq,Xq,Xq,Xq,Xq,Xq,Xq,Xq,Xq,Xq];var Wa=[Kq,pk,gg,yk,Nc,Kq,Kq,Kq];var Xa=[vq,Ne,ij,$b,ic,yd,$b,ic,$g,Aj,Lg,Yf,vq,vq,vq,vq];var Ya=[hq,hm,dl,hq];var Za=[Np,tj,oh,Np];var _a=[rp,Rl,Sk,rp];return{___cxa_can_catch:lm,___cxa_is_pointer_type:Ao,___divdi3:Ik,___muldi3:un,___udivdi3:jp,___uremdi3:hn,_bitshift64Lshr:Yn,_bitshift64Shl:Tn,_emscripten_bind_DracoInt8Array_DracoInt8Array_0:dp,_emscripten_bind_DracoInt8Array_GetValue_1:No,_emscripten_bind_DracoInt8Array___destroy___0:cn,_emscripten_bind_DracoInt8Array_size_0:xp,_emscripten_bind_Encoder_EncodeMeshToDracoBuffer_2:oo,_emscripten_bind_Encoder_EncodePointCloudToDracoBuffer_3:En,_emscripten_bind_Encoder_Encoder_0:Ip,_emscripten_bind_Encoder_SetAttributeExplicitQuantization_5:_m,_emscripten_bind_Encoder_SetAttributeQuantization_2:jo,_emscripten_bind_Encoder_SetEncodingMethod_1:bp,_emscripten_bind_Encoder_SetSpeedOptions_2:Jo,_emscripten_bind_Encoder___destroy___0:Wj,_emscripten_bind_GeometryAttribute_GeometryAttribute_0:So,_emscripten_bind_GeometryAttribute___destroy___0:zp,_emscripten_bind_MeshBuilder_AddFacesToMesh_3:no,_emscripten_bind_MeshBuilder_AddFloatAttributeToMesh_5:pn,_emscripten_bind_MeshBuilder_AddFloatAttribute_5:pn,_emscripten_bind_MeshBuilder_AddInt16Attribute_5:fn,_emscripten_bind_MeshBuilder_AddInt32AttributeToMesh_5:on,_emscripten_bind_MeshBuilder_AddInt32Attribute_5:on,_emscripten_bind_MeshBuilder_AddInt8Attribute_5:kn,_emscripten_bind_MeshBuilder_AddMetadataToMesh_2:ro,_emscripten_bind_MeshBuilder_AddMetadata_2:qo,_emscripten_bind_MeshBuilder_AddUInt16Attribute_5:an,_emscripten_bind_MeshBuilder_AddUInt32Attribute_5:$m,_emscripten_bind_MeshBuilder_AddUInt8Attribute_5:en,_emscripten_bind_MeshBuilder_MeshBuilder_0:yq,_emscripten_bind_MeshBuilder_SetMetadataForAttribute_3:Dn,_emscripten_bind_MeshBuilder___destroy___0:zp,_emscripten_bind_Mesh_Mesh_0:Kp,_emscripten_bind_Mesh___destroy___0:Co,_emscripten_bind_Mesh_num_attributes_0:hp,_emscripten_bind_Mesh_num_faces_0:ap,_emscripten_bind_Mesh_num_points_0:fq,_emscripten_bind_Mesh_set_num_points_1:Gp,_emscripten_bind_MetadataBuilder_AddDoubleEntry_3:bo,_emscripten_bind_MetadataBuilder_AddIntEntry_3:go,_emscripten_bind_MetadataBuilder_AddStringEntry_3:_n,_emscripten_bind_MetadataBuilder_MetadataBuilder_0:yq,_emscripten_bind_MetadataBuilder___destroy___0:zp,_emscripten_bind_Metadata_Metadata_0:Xl,_emscripten_bind_Metadata___destroy___0:Uo,_emscripten_bind_PointAttribute_PointAttribute_0:Zo,_emscripten_bind_PointAttribute___destroy___0:Ij,_emscripten_bind_PointAttribute_attribute_type_0:Pp,_emscripten_bind_PointAttribute_byte_offset_0:Wp,_emscripten_bind_PointAttribute_byte_stride_0:Vp,_emscripten_bind_PointAttribute_data_type_0:Zp,_emscripten_bind_PointAttribute_normalized_0:Lp,_emscripten_bind_PointAttribute_num_components_0:Tp,_emscripten_bind_PointAttribute_size_0:fq,_emscripten_bind_PointAttribute_unique_id_0:Yp,_emscripten_bind_PointCloudBuilder_AddFloatAttribute_5:pn,_emscripten_bind_PointCloudBuilder_AddInt16Attribute_5:fn,_emscripten_bind_PointCloudBuilder_AddInt32Attribute_5:on,_emscripten_bind_PointCloudBuilder_AddInt8Attribute_5:kn,_emscripten_bind_PointCloudBuilder_AddMetadata_2:qo,_emscripten_bind_PointCloudBuilder_AddUInt16Attribute_5:an,_emscripten_bind_PointCloudBuilder_AddUInt32Attribute_5:$m,_emscripten_bind_PointCloudBuilder_AddUInt8Attribute_5:en,_emscripten_bind_PointCloudBuilder_PointCloudBuilder_0:yq,_emscripten_bind_PointCloudBuilder_SetMetadataForAttribute_3:Dn,_emscripten_bind_PointCloudBuilder___destroy___0:zp,_emscripten_bind_PointCloud_PointCloud_0:mp,_emscripten_bind_PointCloud___destroy___0:Co,_emscripten_bind_PointCloudLine truncated
// EMSCRIPTEN_END_ASM
(Module.asmGlobalArg,Module.asmLibraryArg,buffer);var ___cxa_can_catch=Module["___cxa_can_catch"]=asm["___cxa_can_catch"];var ___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=asm["___cxa_is_pointer_type"];var ___divdi3=Module["___divdi3"]=asm["___divdi3"];var ___muldi3=Module["___muldi3"]=asm["___muldi3"];var ___udivdi3=Module["___udivdi3"]=asm["___udivdi3"];var ___uremdi3=Module["___uremdi3"]=asm["___uremdi3"];var _bitshift64Lshr=Module["_bitshift64Lshr"]=asm["_bitshift64Lshr"];var _bitshift64Shl=Module["_bitshift64Shl"]=asm["_bitshift64Shl"];var _emscripten_bind_DracoInt8Array_DracoInt8Array_0=Module["_emscripten_bind_DracoInt8Array_DracoInt8Array_0"]=asm["_emscripten_bind_DracoInt8Array_DracoInt8Array_0"];var _emscripten_bind_DracoInt8Array_GetValue_1=Module["_emscripten_bind_DracoInt8Array_GetValue_1"]=asm["_emscripten_bind_DracoInt8Array_GetValue_1"];var _emscripten_bind_DracoInt8Array___destroy___0=Module["_emscripten_bind_DracoInt8Array___destroy___0"]=asm["_emscripten_bind_DracoInt8Array___destroy___0"];var _emscripten_bind_DracoInt8Array_size_0=Module["_emscripten_bind_DracoInt8Array_size_0"]=asm["_emscripten_bind_DracoInt8Array_size_0"];var _emscripten_bind_Encoder_EncodeMeshToDracoBuffer_2=Module["_emscripten_bind_Encoder_EncodeMeshToDracoBuffer_2"]=asm["_emscripten_bind_Encoder_EncodeMeshToDracoBuffer_2"];var _emscripten_bind_Encoder_EncodePointCloudToDracoBuffer_3=Module["_emscripten_bind_Encoder_EncodePointCloudToDracoBuffer_3"]=asm["_emscripten_bind_Encoder_EncodePointCloudToDracoBuffer_3"];var _emscripten_bind_Encoder_Encoder_0=Module["_emscripten_bind_Encoder_Encoder_0"]=asm["_emscripten_bind_Encoder_Encoder_0"];var _emscripten_bind_Encoder_SetAttributeExplicitQuantization_5=Module["_emscripten_bind_Encoder_SetAttributeExplicitQuantization_5"]=asm["_emscripten_bind_Encoder_SetAttributeExplicitQuantization_5"];var _emscripten_bind_Encoder_SetAttributeQuantization_2=Module["_emscripten_bind_Encoder_SetAttributeQuantization_2"]=asm["_emscripten_bind_Encoder_SetAttributeQuantization_2"];var _emscripten_bind_Encoder_SetEncodingMethod_1=Module["_emscripten_bind_Encoder_SetEncodingMethod_1"]=asm["_emscripten_bind_Encoder_SetEncodingMethod_1"];var _emscripten_bind_Encoder_SetSpeedOptions_2=Module["_emscripten_bind_Encoder_SetSpeedOptions_2"]=asm["_emscripten_bind_Encoder_SetSpeedOptions_2"];var _emscripten_bind_Encoder___destroy___0=Module["_emscripten_bind_Encoder___destroy___0"]=asm["_emscripten_bind_Encoder___destroy___0"];var _emscripten_bind_GeometryAttribute_GeometryAttribute_0=Module["_emscripten_bind_GeometryAttribute_GeometryAttribute_0"]=asm["_emscripten_bind_GeometryAttribute_GeometryAttribute_0"];var _emscripten_bind_GeometryAttribute___destroy___0=Module["_emscripten_bind_GeometryAttribute___destroy___0"]=asm["_emscripten_bind_GeometryAttribute___destroy___0"];var _emscripten_bind_MeshBuilder_AddFacesToMesh_3=Module["_emscripten_bind_MeshBuilder_AddFacesToMesh_3"]=asm["_emscripten_bind_MeshBuilder_AddFacesToMesh_3"];var _emscripten_bind_MeshBuilder_AddFloatAttributeToMesh_5=Module["_emscripten_bind_MeshBuilder_AddFloatAttributeToMesh_5"]=asm["_emscripten_bind_MeshBuilder_AddFloatAttributeToMesh_5"];var _emscripten_bind_MeshBuilder_AddFloatAttribute_5=Module["_emscripten_bind_MeshBuilder_AddFloatAttribute_5"]=asm["_emscripten_bind_MeshBuilder_AddFloatAttribute_5"];var _emscripten_bind_MeshBuilder_AddInt16Attribute_5=Module["_emscripten_bind_MeshBuilder_AddInt16Attribute_5"]=asm["_emscripten_bind_MeshBuilder_AddInt16Attribute_5"];var _emscripten_bind_MeshBuilder_AddInt32AttributeToMesh_5=Module["_emscripten_bind_MeshBuilder_AddInt32AttributeToMesh_5"]=asm["_emscripten_bind_MeshBuilder_AddInt32AttributeToMesh_5"];var _emscripten_bind_MeshBuilder_AddInt32Attribute_5=Module["_emscripten_bind_MeshBuilder_AddInt32Attribute_5"]=asm["_emscripten_bind_MeshBuilder_AddInt32Attribute_5"];var _emscripten_bind_MeshBuilder_AddInt8Attribute_5=Module["_emscripten_bind_MeshBuilder_AddInt8Attribute_5"]=asm["_emscripten_bind_MeshBuilder_AddInt8Attribute_5"];var _emscripten_bind_MeshBuilder_AddMetadataToMesh_2=Module["_emscripten_bind_MeshBuilder_AddMetadataToMesh_2"]=asm["_emscripten_bind_MeshBuilder_AddMetadataToMesh_2"];var _emscripten_bind_MeshBuilder_AddMetadata_2=Module["_emscripten_bind_MeshBuilder_AddMetadata_2"]=asm["_emscripten_bind_MeshBuilder_AddMetadata_2"];var _emscripten_bind_MeshBuilder_AddUInt16Attribute_5=Module["_emscripten_bind_MeshBuilder_AddUInt16Attribute_5"]=asm["_emscripten_bind_MeshBuilder_AddUInt16Attribute_5"];var _emscripten_bind_MeshBuilder_AddUInt32Attribute_5=Module["_emscripten_bind_MeshBuilder_AddUInt32Attribute_5"]=asm["_emscripten_bind_MeshBuilder_AddUInt32Attribute_5"];var _emscripten_bind_MeshBuilder_AddUInt8Attribute_5=Module["_emscripten_bind_MeshBuilder_AddUInt8Attribute_5"]=asm["_emscripten_bind_MeshBuilder_AddUInt8Attribute_5"];var _emscripten_bind_MeshBuilder_MeshBuilder_0=Module["_emscripten_bind_MeshBuilder_MeshBuilder_0"]=asm["_emscripten_bind_MeshBuildLine truncated
return DracoEncoderModule;
};
if (typeof exports === 'object' && typeof module === 'object')
module.exports = DracoEncoderModule;
else if (typeof define === 'function' && define['amd'])
define([], function() { return DracoEncoderModule; });
else if (typeof exports === 'object')
exports["DracoEncoderModule"] = DracoEncoderModule;
+116
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.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Cube.dae - hand-authored minimal COLLADA 1.4.1 unit cube for hmwebviewer ColladaLoader testing.
Single <geometry> (8 verts, 12 triangles) in one <visual_scene>. Edge length 2 (range -1..1),
centered at origin. Works for Path A (SSR ?model=) and Path B (drag & drop). No network required. -->
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
<asset>
<contributor>
<author>hmwebviewer</author>
<authoring_tool>hand-authored</authoring_tool>
</contributor>
<created>2026-06-19T00:00:00Z</created>
<modified>2026-06-19T00:00:00Z</modified>
<unit name="meter" meter="1"/>
<up_axis>Y_UP</up_axis>
</asset>
<library_geometries>
<geometry id="Cube-mesh" name="Cube">
<mesh>
<source id="Cube-positions">
<float_array id="Cube-positions-array" count="24">
-1 -1 -1 -1 -1 1 -1 1 -1 -1 1 1 1 -1 -1 1 -1 1 1 1 -1 1 1 1
</float_array>
<technique_common>
<accessor source="#Cube-positions-array" count="8" stride="3">
<param name="X" type="float"/>
<param name="Y" type="float"/>
<param name="Z" type="float"/>
</accessor>
</technique_common>
</source>
<vertices id="Cube-vertices">
<input semantic="POSITION" source="#Cube-positions"/>
</vertices>
<triangles count="12">
<input semantic="VERTEX" source="#Cube-vertices" offset="0"/>
<p>
0 1 3 0 3 2
4 6 7 4 7 5
0 4 5 0 5 1
2 3 7 2 7 6
0 2 6 0 6 4
1 5 7 1 7 3
</p>
</triangles>
</mesh>
</geometry>
</library_geometries>
<library_visual_scenes>
<visual_scene id="Scene" name="Scene">
<node id="Cube" name="Cube" type="NODE">
<matrix sid="transform">1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1</matrix>
<instance_geometry url="#Cube-mesh" name="Cube"/>
</node>
</visual_scene>
</library_visual_scenes>
<scene>
<instance_visual_scene url="#Scene"/>
</scene>
</COLLADA>
Binary file not shown.
+59
View File
@@ -0,0 +1,59 @@
ISO-10303-21;
HEADER;
/* hand-authored minimal IFC4 file for hmwebviewer web-ifc testing.
One IfcWall (1m x 1m footprint, 1m high) as a swept-extruded solid placed in a
site/building/storey spatial tree. Validates with web-ifc; loadable via Path A
(SSR ?model=) and Path B (drag & drop). No network required. */
FILE_DESCRIPTION(('ViewDefinition [ReferenceView_V1.2]'),'2;1');
FILE_NAME('Cube.ifc','2026-06-19T00:00:00',(''),(''),'hmwebviewer','hand-authored','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'hmwebviewer',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'1.0','hmwebviewer','hmw');
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,$,$,0);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,$);
#11=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#12=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#13=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#14=IFCUNITASSIGNMENT((#11,#12,#13));
#15=IFCPROJECT('0Project00000000000000000',#5,'Project',$,$,$,$,(#10),#14);
#20=IFCLOCALPLACEMENT($,#9);
#21=IFCSITE('0Site0000000000000000000',#5,'Site',$,$,#20,$,$,.ELEMENT.,$,$,$,$,$);
#22=IFCLOCALPLACEMENT(#20,#9);
#23=IFCBUILDING('0Building000000000000000',#5,'Building',$,$,#22,$,$,.ELEMENT.,$,$,$);
#24=IFCLOCALPLACEMENT(#22,#9);
#25=IFCBUILDINGSTOREY('0Storey00000000000000000',#5,'Storey',$,$,#24,$,$,.ELEMENT.,0.);
#30=IFCRELAGGREGATES('0RelAggProject0000000000',#5,$,$,#15,(#21));
#31=IFCRELAGGREGATES('0RelAggSite000000000000 ',#5,$,$,#21,(#23));
#32=IFCRELAGGREGATES('0RelAggBuilding00000000 ',#5,$,$,#23,(#25));
/* --- Wall geometry: 1m x 1m rectangle extruded 1m up --- */
#40=IFCCARTESIANPOINT((-0.5,-0.5));
#41=IFCCARTESIANPOINT((0.5,-0.5));
#42=IFCCARTESIANPOINT((0.5,0.5));
#43=IFCCARTESIANPOINT((-0.5,0.5));
#44=IFCPOLYLINE((#40,#41,#42,#43,#40));
#45=IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,$,#44);
#46=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#47=IFCEXTRUDEDAREASOLID(#45,#46,#7,1.);
#48=IFCSHAPEREPRESENTATION(#10,'Body','SweptSolid',(#47));
#49=IFCPRODUCTDEFINITIONSHAPE($,$,(#48));
#50=IFCLOCALPLACEMENT(#24,#9);
#51=IFCWALL('0Wall00000000000000000000',#5,'Wall',$,$,#50,#49,$,.SOLIDWALL.);
#60=IFCRELCONTAINEDINSPATIALSTRUCTURE('0RelContained0000000000 ',#5,$,$,(#51),#25);
ENDSEC;
END-ISO-10303-21;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Cube.dae - hand-authored minimal COLLADA 1.4.1 unit cube for hmwebviewer ColladaLoader testing.
Single <geometry> (8 verts, 12 triangles) in one <visual_scene>. Edge length 2 (range -1..1),
centered at origin. Works for Path A (SSR ?model=) and Path B (drag & drop). No network required. -->
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
<asset>
<contributor>
<author>hmwebviewer</author>
<authoring_tool>hand-authored</authoring_tool>
</contributor>
<created>2026-06-19T00:00:00Z</created>
<modified>2026-06-19T00:00:00Z</modified>
<unit name="meter" meter="1"/>
<up_axis>Y_UP</up_axis>
</asset>
<library_geometries>
<geometry id="Cube-mesh" name="Cube">
<mesh>
<source id="Cube-positions">
<float_array id="Cube-positions-array" count="24">
-1 -1 -1 -1 -1 1 -1 1 -1 -1 1 1 1 -1 -1 1 -1 1 1 1 -1 1 1 1
</float_array>
<technique_common>
<accessor source="#Cube-positions-array" count="8" stride="3">
<param name="X" type="float"/>
<param name="Y" type="float"/>
<param name="Z" type="float"/>
</accessor>
</technique_common>
</source>
<vertices id="Cube-vertices">
<input semantic="POSITION" source="#Cube-positions"/>
</vertices>
<triangles count="12">
<input semantic="VERTEX" source="#Cube-vertices" offset="0"/>
<p>
0 1 3 0 3 2
4 6 7 4 7 5
0 4 5 0 5 1
2 3 7 2 7 6
0 2 6 0 6 4
1 5 7 1 7 3
</p>
</triangles>
</mesh>
</geometry>
</library_geometries>
<library_visual_scenes>
<visual_scene id="Scene" name="Scene">
<node id="Cube" name="Cube" type="NODE">
<matrix sid="transform">1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1</matrix>
<instance_geometry url="#Cube-mesh" name="Cube"/>
</node>
</visual_scene>
</library_visual_scenes>
<scene>
<instance_visual_scene url="#Scene"/>
</scene>
</COLLADA>
BIN
View File
Binary file not shown.
+59
View File
@@ -0,0 +1,59 @@
ISO-10303-21;
HEADER;
/* hand-authored minimal IFC4 file for hmwebviewer web-ifc testing.
One IfcWall (1m x 1m footprint, 1m high) as a swept-extruded solid placed in a
site/building/storey spatial tree. Validates with web-ifc; loadable via Path A
(SSR ?model=) and Path B (drag & drop). No network required. */
FILE_DESCRIPTION(('ViewDefinition [ReferenceView_V1.2]'),'2;1');
FILE_NAME('Cube.ifc','2026-06-19T00:00:00',(''),(''),'hmwebviewer','hand-authored','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'hmwebviewer',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'1.0','hmwebviewer','hmw');
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,$,$,0);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,$);
#11=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#12=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#13=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#14=IFCUNITASSIGNMENT((#11,#12,#13));
#15=IFCPROJECT('0Project00000000000000000',#5,'Project',$,$,$,$,(#10),#14);
#20=IFCLOCALPLACEMENT($,#9);
#21=IFCSITE('0Site0000000000000000000',#5,'Site',$,$,#20,$,$,.ELEMENT.,$,$,$,$,$);
#22=IFCLOCALPLACEMENT(#20,#9);
#23=IFCBUILDING('0Building000000000000000',#5,'Building',$,$,#22,$,$,.ELEMENT.,$,$,$);
#24=IFCLOCALPLACEMENT(#22,#9);
#25=IFCBUILDINGSTOREY('0Storey00000000000000000',#5,'Storey',$,$,#24,$,$,.ELEMENT.,0.);
#30=IFCRELAGGREGATES('0RelAggProject0000000000',#5,$,$,#15,(#21));
#31=IFCRELAGGREGATES('0RelAggSite000000000000 ',#5,$,$,#21,(#23));
#32=IFCRELAGGREGATES('0RelAggBuilding00000000 ',#5,$,$,#23,(#25));
/* --- Wall geometry: 1m x 1m rectangle extruded 1m up --- */
#40=IFCCARTESIANPOINT((-0.5,-0.5));
#41=IFCCARTESIANPOINT((0.5,-0.5));
#42=IFCCARTESIANPOINT((0.5,0.5));
#43=IFCCARTESIANPOINT((-0.5,0.5));
#44=IFCPOLYLINE((#40,#41,#42,#43,#40));
#45=IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,$,#44);
#46=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#47=IFCEXTRUDEDAREASOLID(#45,#46,#7,1.);
#48=IFCSHAPEREPRESENTATION(#10,'Body','SweptSolid',(#47));
#49=IFCPRODUCTDEFINITIONSHAPE($,$,(#48));
#50=IFCLOCALPLACEMENT(#24,#9);
#51=IFCWALL('0Wall00000000000000000000',#5,'Wall',$,$,#50,#49,$,.SOLIDWALL.);
#60=IFCRELCONTAINEDINSPATIALSTRUCTURE('0RelContained0000000000 ',#5,$,$,(#51),#25);
ENDSEC;
END-ISO-10303-21;
BIN
View File
Binary file not shown.
Binary file not shown.
+7772
View File
File diff suppressed because it is too large. Load diff
+84
View File
@@ -0,0 +1,84 @@
/**
* Drag & drop local-file path (PLAN P2-1, P2-2).
*
* Captures HTML5 DnD events + click-to-browse on the #dropzone element,
* validates the picked file is a supported model, and forwards valid files to
* viewer-core's loadLocalFile. createObjectURL/revoke lifecycle is owned by
* the viewer (per the locked Blob URL pattern in SKILL.md); this module only
* validates + hands the File off.
*/
import { extOf } from '../viewer/modelLoader';
/** Viewer surface this module depends on (viewer-core implements loadLocalFile). */
export interface ViewerHandle {
loadLocalFile: (file: File, sidecars?: File[]) => void;
}
/** Options for initDropzone. */
export interface DropzoneOpts {
viewer: ViewerHandle;
dropzone: HTMLElement;
fileInput: HTMLInputElement;
/** Optional error sink; defaults to console.warn. */
onError?: (msg: string) => void;
}
/** Accept any supported model extension by name (case-insensitive). */
function isValidModel(file: File): boolean {
return extOf(file.name) !== null;
}
/**
* Wire up drag/drop + click-to-browse on the given dropzone.
* Idempotent-ish: attaches listeners; caller should call once per element.
*/
export function initDropzone(opts: DropzoneOpts): void {
const { dropzone, fileInput, viewer } = opts;
const onError = opts.onError ?? ((msg: string) => console.warn(msg));
// Accept multiple files at once: the first supported model is the primary,
// the rest (e.g. a .mtl beside an .obj) ride along as sidecars.
const handleFiles = (files: File[]): void => {
if (!files.length) return;
const model = files.find(isValidModel);
if (!model) {
onError("Unsupported file (use .glb .gltf .obj .fbx .dae .ifc)");
return;
}
viewer.loadLocalFile(model, files.filter((f) => f !== model));
};
// --- Drag & drop ---
// preventDefault on dragover is MANDATORY or the browser navigates to the file.
dropzone.addEventListener("dragover", (e: DragEvent) => {
e.preventDefault();
dropzone.classList.add("drag");
});
dropzone.addEventListener("dragenter", (e: DragEvent) => {
e.preventDefault();
dropzone.classList.add("drag");
});
dropzone.addEventListener("dragleave", () => {
dropzone.classList.remove("drag");
});
dropzone.addEventListener("drop", (e: DragEvent) => {
e.preventDefault();
dropzone.classList.remove("drag");
handleFiles(Array.from(e.dataTransfer?.files ?? []));
});
// --- Click to browse (accessibility) ---
dropzone.addEventListener("click", () => {
fileInput.click();
});
fileInput.addEventListener("change", () => {
handleFiles(Array.from(fileInput.files ?? []));
// reset so picking the same file twice fires `change` again
fileInput.value = "";
});
}
+72
View File
@@ -0,0 +1,72 @@
import { ThreeDViewer } from './viewer/ThreeDViewer';
import { initDropzone } from './dnd/dropzone';
import { setStatus } from './ui/progress';
/**
* Entry point. Wires the ThreeDViewer (renderer + loaders + hydration) and the
* Drag & Drop local-file path. Path A (server asset) is also available via the
* `?model=<url>` query param so the SSR+CSR+hydration flow can be exercised
* without extra UI.
*/
const viewerEl = document.getElementById('viewer');
const progressEl = document.getElementById('progress');
const previewEl = document.getElementById('preview') as HTMLImageElement | null;
const dropzoneEl = document.getElementById('dropzone');
const fileInputEl = document.getElementById('file-input') as HTMLInputElement | null;
const statusEl = document.getElementById('status');
if (!viewerEl || !progressEl || !previewEl || !dropzoneEl || !fileInputEl || !statusEl) {
throw new Error('hmwebviewer: required DOM elements missing');
}
const onError = (msg: string) => setStatus(statusEl, msg);
let viewer: ThreeDViewer;
try {
viewer = new ThreeDViewer(viewerEl, progressEl, previewEl, onError);
} catch (err) {
console.error('[hmwebviewer] init failed', err);
setStatus(statusEl, 'WebGL is required but unavailable in this browser.');
throw err;
}
initDropzone({
viewer,
dropzone: dropzoneEl,
fileInput: fileInputEl,
onError: (msg) => { console.warn('[hmwebviewer]', msg); setStatus(statusEl, msg); },
});
// Viewer controls — Zoom Fit, projection toggle, outline overlay
document.getElementById('btn-fit')?.addEventListener('click', () => viewer.fitView());
const perspBtn = document.getElementById('btn-persp');
const orthoBtn = document.getElementById('btn-ortho');
const setProjection = (mode: 'persp' | 'ortho') => {
viewer.setProjection(mode);
perspBtn?.classList.toggle('active', mode === 'persp');
orthoBtn?.classList.toggle('active', mode === 'ortho');
};
perspBtn?.addEventListener('click', () => setProjection('persp'));
orthoBtn?.addEventListener('click', () => setProjection('ortho'));
const outlineBtn = document.getElementById('btn-outline');
outlineBtn?.addEventListener('click', () => {
const on = viewer.toggleOutline();
outlineBtn.classList.toggle('active', on);
});
// Path A (server asset) — opt-in via ?model=<url>
const modelUrl = new URLSearchParams(window.location.search).get('model');
if (modelUrl) {
// SSR placeholder: a pre-rendered 360° animated WebP (tools/prerender.mjs)
// shows instantly, then the hydration gate fades it out once WebGL + model ready.
const base = modelUrl.split('/').pop()!.replace(/\.(glb|gltf|obj|fbx|dae|ifc|ply)$/i, '');
previewEl.src = '/previews/' + base + '.webp';
previewEl.classList.remove('hidden');
previewEl.onerror = () => previewEl.classList.add('hidden');
viewer.loadServerAsset(modelUrl);
}
// Exposed for tooling (tools/prerender.mjs captures rotating frames).
(window as unknown as { __viewer?: ThreeDViewer }).__viewer = viewer;
+51
View File
@@ -0,0 +1,51 @@
:root {
color-scheme: dark;
font-family: system-ui, sans-serif;
}
* { box-sizing: border-box; }
html, body, #app { margin: 0; height: 100%; }
#app { position: relative; background: #111; color: #eee; overflow: hidden; }
.viewer { position: absolute; inset: 0; }
.viewer canvas { display: block; width: 100%; height: 100%; }
.preview {
position: absolute; inset: 0; width: 100%; height: 100%;
object-fit: contain; background: #111; z-index: 2;
transition: opacity 0.5s ease;
}
.hidden { display: none !important; }
.progress {
position: absolute; left: 0; right: 0; bottom: 0; height: 4px; z-index: 3;
background: rgba(255,255,255,0.1);
}
.progress .bar { height: 100%; width: 0%; background: #4aa3ff; transition: width 0.1s linear; }
.fps {
position: absolute; top: 8px; left: 8px; z-index: 4;
font: 12px/1.4 ui-monospace, monospace; padding: 2px 6px;
background: rgba(0,0,0,0.5); border-radius: 4px; pointer-events: none;
}
.controls {
position: absolute; top: 8px; right: 8px; z-index: 4;
display: flex; gap: 6px;
}
.controls button {
font: 12px/1.4 system-ui, sans-serif; padding: 6px 10px;
background: rgba(0,0,0,0.55); color: #eee; border: 1px solid #888;
border-radius: 6px; cursor: pointer;
}
.controls button:hover { background: rgba(0,0,0,0.75); border-color: #4aa3ff; }
.controls button.active { background: #4aa3ff; color: #00264d; border-color: #4aa3ff; }
.dropzone {
position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%);
padding: 10px 18px; border: 1px dashed #555; border-radius: 10px;
background: rgba(0,0,0,0.4); font-size: 13px; cursor: pointer; z-index: 3;
}
.dropzone.drag { border-color: #4aa3ff; background: rgba(74,163,255,0.15); }
.dropzone p { margin: 0; }
.dropzone .status { margin-top: 6px; color: #ff7676; font-size: 12px; min-height: 0; }
.dropzone .status:empty { display: none; }
+58
View File
@@ -0,0 +1,58 @@
/**
* On-screen FPS meter.
*
* Driven from the viewer's animate loop: call `sample(now)` once per frame with
* the requestAnimationFrame timestamp. It computes an instantaneous fps from the
* inter-frame delta, smooths it into a rolling average (EMA), and throttles DOM
* writes to ~every 250ms so updating the overlay doesn't itself cause jank.
* Kept dependency-free in the src/ui/progress.ts style.
*/
const DOM_INTERVAL_MS = 250;
// EMA smoothing factor: higher = more responsive, lower = smoother.
const EMA_ALPHA = 0.1;
export interface FpsMeter {
/** Overlay element; the viewer appends this to its container. */
el: HTMLElement;
/** Feed one frame timestamp (DOMHighResTimeStamp). */
sample(now: number): void;
/** Current rolling-average fps. */
fps(): number;
}
/** Create an FPS overlay element + sampler. */
export function createFpsMeter(): FpsMeter {
const el = document.createElement("div");
el.className = "fps";
el.textContent = "-- FPS";
let last = 0;
let avg = 0;
let lastDom = 0;
return {
el,
sample(now: number): void {
if (last === 0) {
last = now;
return;
}
const dt = now - last;
last = now;
if (dt <= 0) return;
const inst = 1000 / dt;
avg = avg === 0 ? inst : avg + EMA_ALPHA * (inst - avg);
if (now - lastDom >= DOM_INTERVAL_MS) {
lastDom = now;
const v = Math.round(avg);
el.textContent = `${v} FPS`;
el.style.color = v >= 60 ? "#5cff5c" : v >= 30 ? "#ffb74a" : "#ff5c5c";
}
},
fps(): number {
return avg;
},
};
}
+38
View File
@@ -0,0 +1,38 @@
/**
* Minimal progress-bar helper for the load lifecycle.
*
* The #progress container holds a single inner .bar element whose width is
* driven as a percentage. show/hide just toggle the `hidden` class so CSS
* controls visibility (see src/style.css). Kept dependency-free so both the
* dropzone wiring and ThreeDViewer.loadServerAsset can use it.
*/
/**
* Reveal the progress container by removing the `hidden` class.
* `el` is the #progress element.
*/
export function showProgress(el: HTMLElement): void {
el.classList.remove("hidden");
}
/**
* Hide the progress container by adding the `hidden` class.
*/
export function hideProgress(el: HTMLElement): void {
el.classList.add("hidden");
}
/**
* Set the inner `.bar` width to `percent` (clamped to 0..100).
* `el` is the #progress element; its first `.bar` descendant is resized.
*/
export function setProgress(el: HTMLElement, percent: number): void {
const clamped = Math.max(0, Math.min(100, percent));
const bar = el.querySelector<HTMLElement>(".bar");
if (bar) bar.style.width = `${clamped}%`;
}
/** Write a user-facing status/error message into the #status element. */
export function setStatus(el: HTMLElement, msg: string): void {
el.textContent = msg;
}
+360
View File
@@ -0,0 +1,360 @@
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { getLoaders } from './loaders';
import { loadModel, extOf } from './modelLoader';
import { recenterObjFile } from './objRecenter';
import { createHydrationGate } from './hydration';
import { showProgress, hideProgress, setProgress } from '../ui/progress';
import { createFpsMeter } from '../ui/fps';
import { createAdaptiveQuality } from './adaptiveQuality';
// Formats authored Y-up; the viewer world is Z-up (structure convention), so
// these are rotated +90° about X on load. OBJ/IFC are Z-up native (no rotate).
const Y_UP_EXTS = new Set(['glb', 'gltf', 'fbx', 'dae']);
function isYUp(name: string): boolean {
const e = extOf(name);
return e !== null && Y_UP_EXTS.has(e);
}
export class ThreeDViewer {
private readonly renderer: THREE.WebGLRenderer;
private readonly scene = new THREE.Scene();
private readonly perspCamera: THREE.PerspectiveCamera;
private readonly orthoCamera: THREE.OrthographicCamera;
private camera: THREE.PerspectiveCamera | THREE.OrthographicCamera;
private projection: 'persp' | 'ortho' = 'persp';
private readonly fov = 50;
private readonly controls: OrbitControls;
private readonly gate;
private readonly onError;
private readonly fps = createFpsMeter();
private readonly adaptive;
private raf = 0;
private current: THREE.Object3D | null = null;
private outline: THREE.Group | null = null;
private outlineOn = false;
constructor(
private readonly container: HTMLElement,
private readonly progress: HTMLElement,
preview: HTMLImageElement,
onError?: (msg: string) => void,
) {
this.onError = onError ?? ((msg: string) => console.error('[hmwebviewer]', msg));
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.container.appendChild(this.renderer.domElement);
// Adaptive quality owns renderer.setPixelRatio: tier 0 applies a DPR cap of
// min(devicePixelRatio, 2) immediately (uncapped DPR on 3x devices is the
// #1 cause of <60fps). It then steps the pixel ratio down/up with hysteresis.
this.adaptive = createAdaptiveQuality(this.renderer);
this.container.appendChild(this.fps.el);
this.perspCamera = new THREE.PerspectiveCamera(this.fov, 1, 0.1, 1000);
this.orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000);
for (const c of [this.perspCamera, this.orthoCamera]) {
c.up.set(0, 0, 1); // Z-up world (right-handed, structure convention)
c.position.set(2, -3, 2);
}
this.camera = this.perspCamera;
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
// Pan in the ground (XY) plane, not the screen plane — keeps elevation
// constant when panning along a road/rail alignment between turntable spins.
this.controls.screenSpacePanning = false;
this.scene.background = new THREE.Color(0xcfcfcf);
const hemi = new THREE.HemisphereLight(0xffffff, 0x444444, 1.2);
hemi.position.set(0, 0, 1); // sky toward +Z
this.scene.add(hemi);
const dir = new THREE.DirectionalLight(0xffffff, 1.0);
dir.position.set(2, -3, 5); // from above (+Z) and front
this.scene.add(dir);
// Warm the shared singleton loaders so KTX2 detectSupport(renderer) runs at
// init; loadModel() reuses the same singletons for GLB/GLTF.
getLoaders(this.renderer);
this.gate = createHydrationGate(preview);
window.addEventListener('resize', this.onResize);
this.onResize();
this.animate();
this.gate.markWebGLReady();
}
/** Path A — server asset (SSR placeholder already shown by host page). */
loadServerAsset(url: string): void {
showProgress(this.progress);
loadModel(url, this.renderer, (loaded, total) => {
setProgress(this.progress, (loaded / total) * 100);
}).then(
(obj) => this.onLoaded(obj, isYUp(url)),
(err) => {
hideProgress(this.progress);
this.onError('Failed to load server asset.');
console.error('[hmwebviewer] server asset load failed', err);
},
);
}
/**
* Path B — local file (Drag & Drop). Blob URL revoked on both paths.
* `sidecars` carries companion files dropped alongside (e.g. a .mtl for OBJ);
* the blob: URL can't resolve them, so their text is read and passed inline.
*/
loadLocalFile(file: File, sidecars: File[] = []): void {
showProgress(this.progress);
const mtlFile = sidecars.find((f) => /\.mtl$/i.test(f.name));
const texFiles = sidecars.filter((f) => /\.(png|jpe?g|bmp|gif|webp|tga)$/i.test(f.name));
let url: string | null = null;
const isObj = extOf(file.name) === 'obj';
// OBJLoader builds one giant string; past ~V8 max string length (~1GB of
// text) it fails. Route large OBJ through the streaming parser instead.
const huge = isObj && file.size > 300 * 1024 * 1024;
const run = async (): Promise<THREE.Object3D> => {
const mtlText = mtlFile ? await mtlFile.text() : undefined;
// Small/medium OBJ: recenter in float64 BEFORE OBJLoader quantizes to
// float32 (objRecenter.ts). Huge OBJ recenters inside the streaming parser.
const source = (isObj && !huge)
? await recenterObjFile(file, (l, t) => setProgress(this.progress, (l / t) * 100))
: file;
url = URL.createObjectURL(source);
return loadModel(
url,
this.renderer,
(loaded, total) => setProgress(this.progress, (loaded / total) * 100),
file.name,
{ mtlText, texFiles, stream: huge },
);
};
run()
.then(
(obj) => this.onLoaded(obj, isYUp(file.name)),
(err) => {
hideProgress(this.progress);
this.onError('Failed to load file (corrupt or unsupported?).');
console.error('[hmwebviewer] local file load failed', err);
},
)
.finally(() => { if (url) URL.revokeObjectURL(url); });
}
private onLoaded(obj: THREE.Object3D, yUp = false): void {
if (this.current) {
this.scene.remove(this.current);
this.disposeObject(this.current);
}
this.current = obj;
if (yUp) obj.rotateX(Math.PI / 2); // Y-up source → Z-up world
this.scene.add(this.current);
this.frameObject(this.current);
this.applyOutlineState();
hideProgress(this.progress);
this.gate.markModelLoaded();
// observable hook for perf smoke tests (tools/perf-smoke.mjs)
window.dispatchEvent(new CustomEvent('hmw:ready', { detail: performance.now() }));
}
private frameObject(obj: THREE.Object3D): void {
const box = new THREE.Box3().setFromObject(obj);
const size = box.getSize(new THREE.Vector3());
const center = box.getCenter(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z) || 1;
const fovRad = (this.fov * Math.PI) / 180;
const dist = (maxDim / 2) / Math.tan(fovRad / 2) * 2;
const near = Math.max(maxDim / 1000, 0.01); // adaptive: fixed far=1000 clips large models
const far = (dist + maxDim) * 4;
const aspect = this.aspect;
const dir = new THREE.Vector3(1, -1, 0.8).normalize(); // iso view, Z up
const pos = center.clone().addScaledVector(dir, dist);
this.perspCamera.position.copy(pos);
this.perspCamera.aspect = aspect;
this.perspCamera.near = near;
this.perspCamera.far = far;
this.perspCamera.lookAt(center);
this.perspCamera.updateProjectionMatrix();
// Ortho frustum half-height matches the perspective apparent size at `dist`,
// so toggling projection doesn't jump the model scale.
const halfH = dist * Math.tan(fovRad / 2);
this.orthoCamera.position.copy(pos);
this.orthoCamera.top = halfH;
this.orthoCamera.bottom = -halfH;
this.orthoCamera.left = -halfH * aspect;
this.orthoCamera.right = halfH * aspect;
this.orthoCamera.zoom = 1;
this.orthoCamera.near = near;
this.orthoCamera.far = far;
this.orthoCamera.lookAt(center);
this.orthoCamera.updateProjectionMatrix();
this.controls.target.copy(center);
this.controls.update();
}
/** Re-frame the current model to fit the view (Zoom Fit button). */
fitView(): void {
if (this.current) this.frameObject(this.current);
}
private applyOutlineState(): void {
this.clearOutline();
if (this.outlineOn && this.current) this.buildOutline(this.current);
}
/**
* Build a feature-edge outline (EdgesGeometry, 30° threshold) — the object's
* silhouette + sharp creases as black lines, far sparser than a wireframe.
* Built lazily; one-time cost can be seconds on very large meshes.
*/
private buildOutline(obj: THREE.Object3D): void {
obj.updateMatrixWorld(true);
const group = new THREE.Group();
const mat = new THREE.LineBasicMaterial({ color: 0x1a1a1a });
obj.traverse((node) => {
const mesh = node as THREE.Mesh;
if (!mesh.isMesh || !mesh.geometry) return;
const seg = new THREE.LineSegments(new THREE.EdgesGeometry(mesh.geometry, 30), mat);
mesh.matrixWorld.decompose(seg.position, seg.quaternion, seg.scale);
group.add(seg);
});
this.outline = group;
this.scene.add(group);
}
private clearOutline(): void {
if (!this.outline) return;
this.scene.remove(this.outline);
this.outline.traverse((node) => {
const seg = node as THREE.LineSegments;
if (seg.geometry) seg.geometry.dispose();
});
const m = (this.outline.children[0] as THREE.LineSegments | undefined)?.material;
if (m && !Array.isArray(m)) m.dispose();
this.outline = null;
}
/** Toggle the object outline overlay (테두리 button). */
toggleOutline(): boolean {
this.outlineOn = !this.outlineOn;
this.applyOutlineState();
return this.outlineOn;
}
/** Switch between perspective and orthographic projection, preserving the view. */
setProjection(mode: 'persp' | 'ortho'): void {
if (mode === this.projection) return;
const target = this.controls.target;
const from = this.camera;
const fovRad = (this.fov * Math.PI) / 180;
const offset = from.position.clone().sub(target);
const dist = offset.length() || 1;
const aspect = this.aspect;
if (mode === 'ortho') {
const halfH = dist * Math.tan(fovRad / 2);
this.orthoCamera.position.copy(from.position);
this.orthoCamera.up.copy(from.up);
this.orthoCamera.top = halfH;
this.orthoCamera.bottom = -halfH;
this.orthoCamera.left = -halfH * aspect;
this.orthoCamera.right = halfH * aspect;
this.orthoCamera.zoom = 1;
this.orthoCamera.near = from.near;
this.orthoCamera.far = from.far;
this.orthoCamera.lookAt(target);
this.orthoCamera.updateProjectionMatrix();
this.camera = this.orthoCamera;
} else {
// Place the perspective camera so apparent size matches the ortho view.
const orthoHalfH = this.orthoCamera.top / this.orthoCamera.zoom;
const d = orthoHalfH / Math.tan(fovRad / 2);
this.perspCamera.position.copy(target).add(offset.setLength(d));
this.perspCamera.up.copy(from.up);
this.perspCamera.aspect = aspect;
this.perspCamera.near = from.near;
this.perspCamera.far = from.far;
this.perspCamera.lookAt(target);
this.perspCamera.updateProjectionMatrix();
this.camera = this.perspCamera;
}
this.projection = mode;
this.controls.object = this.camera;
this.controls.update();
}
private disposeObject(obj: THREE.Object3D): void {
const texUrls = obj.userData?.__texUrls as string[] | undefined;
if (texUrls) texUrls.forEach((u) => URL.revokeObjectURL(u));
obj.traverse((node) => {
const mesh = node as THREE.Mesh;
if (mesh.geometry) mesh.geometry.dispose();
const mat = mesh.material;
if (Array.isArray(mat)) mat.forEach((m) => this.disposeMaterial(m));
else if (mat) this.disposeMaterial(mat);
});
}
private disposeMaterial(mat: THREE.Material): void {
for (const v of Object.values(mat)) {
if (v instanceof THREE.Texture) v.dispose();
}
mat.dispose();
}
private get aspect(): number {
const w = this.container.clientWidth || window.innerWidth;
const h = this.container.clientHeight || window.innerHeight;
return w / h;
}
private onResize = (): void => {
const w = this.container.clientWidth || window.innerWidth;
const h = this.container.clientHeight || window.innerHeight;
this.renderer.setSize(w, h, false);
const aspect = w / h;
this.perspCamera.aspect = aspect;
this.perspCamera.updateProjectionMatrix();
const halfH = this.orthoCamera.top || 1;
this.orthoCamera.left = -halfH * aspect;
this.orthoCamera.right = halfH * aspect;
this.orthoCamera.updateProjectionMatrix();
};
private animate = (now: number = performance.now()): void => {
this.raf = requestAnimationFrame(this.animate);
this.fps.sample(now);
this.adaptive.update(this.fps.fps());
this.controls.update();
this.renderer.render(this.scene, this.camera);
};
dispose(): void {
cancelAnimationFrame(this.raf);
window.removeEventListener('resize', this.onResize);
this.controls.dispose();
this.clearOutline();
if (this.current) this.disposeObject(this.current);
this.fps.el.remove();
this.renderer.dispose();
this.renderer.domElement.remove();
}
/** Orbit the camera to azimuth (radians) around the model — used by tools/prerender.mjs. */
rotateTo(azimuth: number): void {
if (!this.current) return;
const offset = new THREE.Vector3().subVectors(this.camera.position, this.controls.target);
const radius = Math.max(offset.length(), 1e-3);
const phi = Math.acos(THREE.MathUtils.clamp(offset.y / radius, -1, 1));
const target = this.controls.target;
this.camera.position.set(
target.x + radius * Math.sin(phi) * Math.sin(azimuth),
target.y + radius * Math.cos(phi),
target.z + radius * Math.sin(phi) * Math.cos(azimuth),
);
this.camera.lookAt(target);
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
}
+85
View File
@@ -0,0 +1,85 @@
import * as THREE from "three";
/**
* Adaptive-quality controller.
*
* The single highest-leverage, lowest-risk runtime knob in this codebase is
* renderer.setPixelRatio: it re-allocates the drawing buffer to
* floor(clientSize * pixelRatio) without touching CSS layout (onResize already
* uses updateStyle=false), so there is zero reflow. We walk a tiered ladder of
* pixel-ratio steps with hysteresis (asymmetric down/up thresholds + sustained
* windows) so the tier cannot oscillate around the 60fps line.
*
* Antialias is deliberately NOT touched: it is fixed at WebGLRenderer
* construction and changing it would force a new context + KTX2 detectSupport,
* violating the singleton-loader invariant in src/viewer/loaders.ts.
*/
const TIER_MIN = 0;
const TIER_MAX = 4;
// Sustained-sample windows (in frames at ~60fps): ~1s down, longer to recover.
const DOWN_FRAMES = 60;
const UP_FRAMES = 180;
const DOWN_FPS = 60;
const UP_FPS = 72;
export interface AdaptiveQuality {
/** Feed the current rolling-average fps; may step the tier. */
update(avgFps: number): void;
/** Current tier index (0 = best). */
tier(): number;
}
export interface AdaptiveQualityOptions {
/** Override the tier-0 (best) pixel ratio. Default min(devicePixelRatio, 2). */
baseRatio?: number;
}
/** Create the controller and apply tier 0 immediately. */
export function createAdaptiveQuality(
renderer: THREE.WebGLRenderer,
opts: AdaptiveQualityOptions = {},
): AdaptiveQuality {
const base = opts.baseRatio ?? Math.min(window.devicePixelRatio, 2);
// Ladder of pixel ratios, highest quality first. Tier 0 = capped DPR.
const ratios = [base, 1.5, 1, 0.75, 0.5];
let tier = 0;
let below = 0;
let above = 0;
renderer.setPixelRatio(ratios[tier]);
function applyTier(next: number): void {
tier = next;
below = 0;
above = 0;
const pr = ratios[tier];
renderer.setPixelRatio(pr);
console.info(`[hmwebviewer] adaptive quality -> tier ${tier} (pr=${pr})`);
}
return {
update(avgFps: number): void {
// Ignore warm-up / unmeasured frames.
if (avgFps <= 0) return;
if (avgFps < DOWN_FPS) {
below++;
above = 0;
if (below >= DOWN_FRAMES && tier < TIER_MAX) applyTier(tier + 1);
} else if (avgFps > UP_FPS) {
above++;
below = 0;
if (above >= UP_FRAMES && tier > TIER_MIN) applyTier(tier - 1);
} else {
// Dead band: reset both so transient blips don't accumulate.
below = 0;
above = 0;
}
},
tier(): number {
return tier;
},
};
}
+35
View File
@@ -0,0 +1,35 @@
// SSR -> CSR hydration gate. Fades the WebP placeholder to the Three.js canvas
// ONLY after BOTH the WebGL context is ready AND the model is loaded.
// Firing on just one condition = empty-canvas flash (race). Idempotent.
export interface HydrationGate {
markWebGLReady(): void;
markModelLoaded(): void;
}
export function createHydrationGate(preview: HTMLImageElement): HydrationGate {
let webglReady = false;
let modelLoaded = false;
let fired = false;
function maybeFire(): void {
if (fired || !(webglReady && modelLoaded)) return;
fired = true;
preview.style.opacity = '0';
window.setTimeout(() => {
preview.classList.add('hidden');
preview.style.display = 'none';
}, 500);
}
return {
markWebGLReady() {
webglReady = true;
maybeFire();
},
markModelLoaded() {
modelLoaded = true;
maybeFire();
},
};
}
+24
View File
@@ -0,0 +1,24 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
export interface ViewerLoaders {
gltf: GLTFLoader;
draco: DRACOLoader;
ktx2: KTX2Loader;
}
// SINGLETON — multiple DRACOLoader/KTX2Loader instances crash (three.js #22445).
// Reuse across every load. Decoder WASM lives under public/draco + public/basis
// (version-matched to the installed three.js).
let _cache: ViewerLoaders | null = null;
export function getLoaders(renderer: THREE.WebGLRenderer): ViewerLoaders {
if (_cache) return _cache;
const draco = new DRACOLoader().setDecoderPath('/draco/');
const ktx2 = new KTX2Loader().setTranscoderPath('/basis/').detectSupport(renderer);
const gltf = new GLTFLoader().setDRACOLoader(draco).setKTX2Loader(ktx2);
_cache = { gltf, draco, ktx2 };
return _cache;
}
+279
View File
@@ -0,0 +1,279 @@
import * as THREE from 'three';
import { getLoaders } from './loaders';
/**
* Multi-format model loader dispatch.
*
* GLB/GLTF go through the shared singleton loaders (loaders.ts — Draco + KTX2).
* OBJ/FBX/Collada use three's example loaders, lazily imported as separate Vite
* chunks so they never bloat the initial bundle. IFC uses web-ifc (single-thread
* wasm under /web-ifc/, no COOP/COEP headers required).
*
* Every branch resolves to a plain THREE.Object3D ready to scene.add — the
* caller (ThreeDViewer.onLoaded) does not need to know the source format.
*/
export type SupportedExt = 'glb' | 'gltf' | 'obj' | 'fbx' | 'dae' | 'ifc' | 'ply';
export const ACCEPT_EXT: SupportedExt[] = ['glb', 'gltf', 'obj', 'fbx', 'dae', 'ifc', 'ply'];
const EXT_RE = /\.(glb|gltf|obj|fbx|dae|ifc|ply)$/i;
/** Extract the supported extension from a file name / URL, or null. */
export function extOf(name: string): SupportedExt | null {
const m = EXT_RE.exec(name);
return m ? (m[1].toLowerCase() as SupportedExt) : null;
}
type ProgressCb = (loaded: number, total: number) => void;
// Lazy single instances for the example loaders — avoids re-import churn on
// repeat loads. NOT the Draco/KTX2 singletons (no shared-loader constraint here).
type OBJLoaderT = import('three/examples/jsm/loaders/OBJLoader.js').OBJLoader;
type FBXLoaderT = import('three/examples/jsm/loaders/FBXLoader.js').FBXLoader;
type ColladaLoaderT = import('three/examples/jsm/loaders/ColladaLoader.js').ColladaLoader;
type PLYLoaderT = import('three/examples/jsm/loaders/PLYLoader.js').PLYLoader;
let _obj: OBJLoaderT | null = null;
let _fbx: FBXLoaderT | null = null;
let _dae: ColladaLoaderT | null = null;
let _ply: PLYLoaderT | null = null;
async function getOBJ(): Promise<OBJLoaderT> {
if (_obj) return _obj;
const { OBJLoader } = await import('three/examples/jsm/loaders/OBJLoader.js');
return (_obj = new OBJLoader());
}
async function getFBX(): Promise<FBXLoaderT> {
if (_fbx) return _fbx;
const { FBXLoader } = await import('three/examples/jsm/loaders/FBXLoader.js');
return (_fbx = new FBXLoader());
}
async function getCollada(): Promise<ColladaLoaderT> {
if (_dae) return _dae;
const { ColladaLoader } = await import('three/examples/jsm/loaders/ColladaLoader.js');
return (_dae = new ColladaLoader());
}
async function getPLY(): Promise<PLYLoaderT> {
if (_ply) return _ply;
const { PLYLoader } = await import('three/examples/jsm/loaders/PLYLoader.js');
return (_ply = new PLYLoader());
}
/**
* Load any supported model URL and resolve to a scene-addable Object3D.
* `nameHint` carries the real file name for ext detection when `url` is a
* blob: URL (drag & drop) — blob URLs have no extension.
*/
export interface LoadOpts {
/** Raw .mtl text for OBJ loads (drag & drop supplies it; blob: URLs can't
* resolve the sibling mtllib). Color-only MTLs apply without any texture fetch. */
mtlText?: string;
/** Image files dropped alongside an OBJ+MTL (textures the MTL's map_* lines
* reference). Their blob: URLs are mapped onto the requested texture names so
* textures resolve without a server. */
texFiles?: File[];
/** Parse the OBJ via the streaming parser (objStream.ts) instead of OBJLoader.
* Set for files too large for OBJLoader's single-string parse (>~300MB). */
stream?: boolean;
}
export async function loadModel(
url: string,
renderer: THREE.WebGLRenderer,
onProgress?: ProgressCb,
nameHint?: string,
opts?: LoadOpts,
): Promise<THREE.Object3D> {
const ext = extOf(nameHint ?? url) ?? extOf(url);
const onXhr = (xhr: ProgressEvent): void => {
if (onProgress && xhr.total > 0) onProgress(xhr.loaded, xhr.total);
};
switch (ext) {
case 'glb':
case 'gltf': {
const { gltf } = getLoaders(renderer);
return new Promise<THREE.Object3D>((resolve, reject) => {
gltf.load(url, (g) => resolve(g.scene), onXhr, (err) => reject(err));
});
}
case 'obj': {
if (opts?.stream) {
const { loadObjStreaming } = await import('./objStream');
return loadObjStreaming(url, opts.mtlText, onProgress);
}
const loader = await getOBJ();
const texUrls: string[] = [];
// Apply dropped .mtl (color-only) if present; otherwise clear any materials
// left on the singleton loader from a previous load.
if (opts?.mtlText) {
const { MTLLoader } = await import('three/examples/jsm/loaders/MTLLoader.js');
const manager = new THREE.LoadingManager();
if (opts.texFiles && opts.texFiles.length) {
// Map each dropped image's blob: URL onto the texture name the MTL
// references (by basename), so map_Kd etc. resolve without a server.
const byName = new Map<string, string>();
for (const f of opts.texFiles) {
const u = URL.createObjectURL(f);
texUrls.push(u);
byName.set(f.name.toLowerCase(), u);
}
manager.setURLModifier((u) => {
const base = decodeURIComponent((u.split(/[\\/]/).pop() ?? '')).toLowerCase();
return byName.get(base) ?? u;
});
}
const mc = new MTLLoader(manager).parse(opts.mtlText, '');
mc.preload();
loader.setMaterials(mc);
} else {
(loader as unknown as { materials: unknown }).materials = null;
}
return new Promise<THREE.Object3D>((resolve, reject) => {
loader.load(
url,
(group) => {
group.traverse((node) => {
const mat = (node as THREE.Mesh).material;
if (!mat) return;
(Array.isArray(mat) ? mat : [mat]).forEach((m) => {
// CAD OBJ faces often have inconsistent winding; backface culling
// tears flat surfaces. Render double-sided.
m.side = THREE.DoubleSide;
const map = (m as THREE.MeshPhongMaterial).map;
if (map) map.colorSpace = THREE.SRGBColorSpace; // color textures are sRGB
});
});
// Texture blob: URLs live with the object; revoked on dispose.
if (texUrls.length) group.userData.__texUrls = texUrls;
resolve(group);
},
onXhr,
(err) => reject(err),
);
});
}
case 'fbx': {
const loader = await getFBX();
return new Promise<THREE.Object3D>((resolve, reject) => {
loader.load(url, (group) => resolve(group), onXhr, (err) => reject(err));
});
}
case 'dae': {
const loader = await getCollada();
return new Promise<THREE.Object3D>((resolve, reject) => {
loader.load(url, (result) => resolve(result.scene), onXhr, (err) => reject(err));
});
}
case 'ply': {
const loader = await getPLY();
return new Promise<THREE.Object3D>((resolve, reject) => {
loader.load(
url,
(geometry) => {
const hasColor = geometry.hasAttribute('color');
if (geometry.index) {
// Triangle mesh
if (!geometry.hasAttribute('normal')) geometry.computeVertexNormals();
const mat = new THREE.MeshStandardMaterial({
color: 0xffffff,
vertexColors: hasColor,
metalness: 0.0,
roughness: 1.0,
side: THREE.DoubleSide,
});
resolve(new THREE.Mesh(geometry, mat));
} else {
// No faces → point cloud
const mat = new THREE.PointsMaterial({
color: 0xffffff,
vertexColors: hasColor,
size: 1,
sizeAttenuation: false,
});
resolve(new THREE.Points(geometry, mat));
}
},
onXhr,
(err) => reject(err),
);
});
}
case 'ifc':
return loadIfc(url, onProgress);
default:
throw new Error('Unsupported model format: ' + url);
}
}
let _ifcApi: import('web-ifc').IfcAPI | null = null;
/** Parse an .ifc into a Group via web-ifc (single-thread wasm, no COOP/COEP). */
async function loadIfc(url: string, onProgress?: ProgressCb): Promise<THREE.Object3D> {
try {
const WebIFC = await import('web-ifc');
if (!_ifcApi) {
_ifcApi = new WebIFC.IfcAPI();
_ifcApi.SetWasmPath('/web-ifc/', true);
await _ifcApi.Init(undefined, true);
}
const api = _ifcApi;
const res = await fetch(url);
if (!res.ok) throw new Error('fetch ' + res.status);
const buf = await res.arrayBuffer();
if (onProgress) onProgress(1, 1);
const data = new Uint8Array(buf);
const modelID = api.OpenModel(data, { COORDINATE_TO_ORIGIN: true });
if (modelID < 0) throw new Error('OpenModel returned -1');
const group = new THREE.Group();
const matrix = new THREE.Matrix4();
try {
api.StreamAllMeshes(modelID, (mesh) => {
const placedCount = mesh.geometries.size();
for (let i = 0; i < placedCount; i++) {
const placed = mesh.geometries.get(i);
const geom = api.GetGeometry(modelID, placed.geometryExpressID);
const verts = api.GetVertexArray(geom.GetVertexData(), geom.GetVertexDataSize());
const indices = api.GetIndexArray(geom.GetIndexData(), geom.GetIndexDataSize());
geom.delete();
// Interleaved [posX,posY,posZ, normX,normY,normZ] — stride 6.
const interleaved = new THREE.InterleavedBuffer(verts, 6);
const bg = new THREE.BufferGeometry();
bg.setAttribute('position', new THREE.InterleavedBufferAttribute(interleaved, 3, 0));
bg.setAttribute('normal', new THREE.InterleavedBufferAttribute(interleaved, 3, 3));
bg.setIndex(new THREE.BufferAttribute(indices, 1));
const c = placed.color;
const mat = new THREE.MeshStandardMaterial({
color: new THREE.Color(c.x, c.y, c.z),
metalness: 0.0,
roughness: 1.0,
side: THREE.DoubleSide,
});
if (c.w < 1) {
mat.transparent = true;
mat.opacity = c.w;
}
const three = new THREE.Mesh(bg, mat);
matrix.fromArray(placed.flatTransformation);
three.applyMatrix4(matrix);
group.add(three);
}
});
} finally {
api.CloseModel(modelID);
}
return group;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
throw new Error('IFC load failed: ' + msg);
}
}
+96
View File
@@ -0,0 +1,96 @@
/**
* In-viewer float64 recenter for OBJ files with huge absolute coordinates
* (CAD/survey, ~1e8). Done at LOAD time, before OBJLoader quantizes to float32.
*
* Why here and not after parse: WebGL vertex buffers are float32; at ~1e8 the
* float32 ulp is 16-64 units, so vertices snap to that grid, merge, and faces
* crack. Subtracting the bbox center from the *source text* (float64) keeps
* coords small (~±3e4) so float32 holds full detail — no preprocessed files,
* no shader-side double emulation (RTE/two-float), which a single offset of
* this magnitude makes unnecessary (relative precision after centering ~3e-5
* vs float32 ~1e-7).
*
* Streamed in two passes (bbox, then rewrite) to keep memory bounded on the
* 100-200MB OBJ files this targets.
*/
const V = 118; // 'v'
const SP = 32; // ' '
const RECENTER_THRESHOLD = 1e4; // models nearer the origin than this are left as-is
/** Yield LF-normalized lines from a Blob's stream without buffering the whole file. */
async function* lines(blob: Blob): AsyncGenerator<string> {
const reader = blob.stream().pipeThrough(new TextDecoderStream()).getReader();
let buf = '';
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buf += value;
let i: number;
while ((i = buf.indexOf('\n')) >= 0) {
let ln = buf.slice(0, i);
if (ln.charCodeAt(ln.length - 1) === 13) ln = ln.slice(0, -1); // strip CR
yield ln;
buf = buf.slice(i + 1);
}
}
if (buf.length) yield buf;
}
function fmt(n: number): string {
return Number.isInteger(n) ? String(n) : String(Math.round(n * 1000) / 1000);
}
/**
* Return an origin-centered copy of `file` as a Blob, or the original `file`
* unchanged when it already sits near the origin (small models pay only the
* cheap first-pass scan). The returned Blob carries the same OBJ text minus a
* per-file integer offset on every `v` line; vn/f/usemtl/mtllib are verbatim.
*/
export async function recenterObjFile(
file: File,
onProgress?: (loaded: number, total: number) => void,
): Promise<Blob> {
// Two streamed passes (bbox, then rewrite) over the file's bytes; report
// progress across both, throttled so the DOM isn't touched per line.
const total = file.size * 2;
let read = 0, tick = 0;
const report = (line: string): void => {
read += line.length + 1;
if (onProgress && (++tick & 0x3fff) === 0) onProgress(read, total); // every ~16k lines
};
let xmin = Infinity, ymin = Infinity, zmin = Infinity;
let xmax = -Infinity, ymax = -Infinity, zmax = -Infinity;
for await (const line of lines(file)) {
report(line);
if (line.charCodeAt(0) !== V || line.charCodeAt(1) !== SP) continue;
const p = line.split(/\s+/);
const x = +p[1], y = +p[2], z = +p[3];
if (x < xmin) xmin = x; if (x > xmax) xmax = x;
if (y < ymin) ymin = y; if (y > ymax) ymax = y;
if (z < zmin) zmin = z; if (z > zmax) zmax = z;
}
if (!Number.isFinite(xmin)) return file; // no vertices — let OBJLoader handle it
const cx = (xmin + xmax) / 2, cy = (ymin + ymax) / 2, cz = (zmin + zmax) / 2;
if (Math.hypot(cx, cy, cz) < RECENTER_THRESHOLD) return file;
const ox = Math.round(cx), oy = Math.round(cy), oz = Math.round(cz);
const enc = new TextEncoder();
const parts: BlobPart[] = [];
let out = '';
for await (const line of lines(file)) {
report(line);
if (line.charCodeAt(0) === V && line.charCodeAt(1) === SP) {
const p = line.split(/\s+/);
out += `v ${fmt(+p[1] - ox)} ${fmt(+p[2] - oy)} ${fmt(+p[3] - oz)}\n`;
} else {
out += line + '\n';
}
if (out.length > (1 << 23)) { parts.push(enc.encode(out)); out = ''; } // flush ~8MB
}
if (out) parts.push(enc.encode(out));
if (onProgress) onProgress(total, total);
return new Blob(parts, { type: 'text/plain' });
}
+143
View File
@@ -0,0 +1,143 @@
import * as THREE from 'three';
/**
* Streaming OBJ parser for files too large for OBJLoader.
*
* OBJLoader builds one giant string of the whole file; past ~512MB-1GB of text
* that exceeds the V8 max string length and fails (empty geometry). This parses
* the file in two streamed passes (count, then fill) so it never holds the whole
* text, building an INDEXED BufferGeometry (positions indexed by vertex, normals
* computed). Memory stays ~O(vertices + triangles) of typed arrays.
*
* Trade-offs vs OBJLoader (acceptable for huge structural CAD exports):
* - smooth (computed) normals, not per-face-corner — hard edges soften.
* - per-vertex color from the active material's Kd (one draw call, no textures).
* - float64 recenter baked in (huge absolute coords don't crack float32).
*/
const SP = 32; // ' '
const V = 118; // 'v'
const F = 102; // 'f'
const U = 117; // 'u' (usemtl)
const RECENTER_THRESHOLD = 1e4;
async function* streamLines(url: string): AsyncGenerator<string> {
const resp = await fetch(url);
if (!resp.ok || !resp.body) throw new Error('fetch failed: ' + resp.status);
const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader();
let buf = '';
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buf += value;
let i: number;
while ((i = buf.indexOf('\n')) >= 0) {
let ln = buf.slice(0, i);
if (ln.charCodeAt(ln.length - 1) === 13) ln = ln.slice(0, -1); // strip CR
yield ln;
buf = buf.slice(i + 1);
}
}
if (buf.length) yield buf;
}
function parseMtlColors(text: string): Map<string, [number, number, number]> {
const map = new Map<string, [number, number, number]>();
let cur = '';
for (const raw of text.split('\n')) {
const line = raw.trim();
if (line.startsWith('newmtl ')) cur = line.slice(7).trim();
else if (cur && line.startsWith('Kd ')) {
const p = line.split(/\s+/);
map.set(cur, [+p[1], +p[2], +p[3]]);
}
}
return map;
}
export async function loadObjStreaming(
url: string,
mtlText?: string,
onProgress?: (loaded: number, total: number) => void,
): Promise<THREE.Object3D> {
// PASS 1 — count vertices + triangles, accumulate bbox in float64.
let nV = 0, nTri = 0;
let xmin = Infinity, ymin = Infinity, zmin = Infinity;
let xmax = -Infinity, ymax = -Infinity, zmax = -Infinity;
for await (const line of streamLines(url)) {
const c0 = line.charCodeAt(0);
if (c0 === V && line.charCodeAt(1) === SP) {
nV++;
const p = line.split(/\s+/);
const x = +p[1], y = +p[2], z = +p[3];
if (x < xmin) xmin = x; if (x > xmax) xmax = x;
if (y < ymin) ymin = y; if (y > ymax) ymax = y;
if (z < zmin) zmin = z; if (z > zmax) zmax = z;
} else if (c0 === F && line.charCodeAt(1) === SP) {
let corners = 0;
const p = line.split(/\s+/);
for (let k = 1; k < p.length; k++) if (p[k]) corners++;
if (corners >= 3) nTri += corners - 2;
}
}
if (nV === 0) throw new Error('OBJ has no vertices');
const cx = (xmin + xmax) / 2, cy = (ymin + ymax) / 2, cz = (zmin + zmax) / 2;
const far = Math.hypot(cx, cy, cz) >= RECENTER_THRESHOLD;
const ox = far ? Math.round(cx) : 0, oy = far ? Math.round(cy) : 0, oz = far ? Math.round(cz) : 0;
// PASS 2 — fill typed arrays.
const positions = new Float32Array(nV * 3);
const index = new Uint32Array(nTri * 3);
const colorMap = mtlText ? parseMtlColors(mtlText) : null;
const colors = colorMap && colorMap.size ? new Float32Array(nV * 3) : null;
let vi = 0, ii = 0;
let cr = 0.8, cg = 0.8, cb = 0.8;
const total = nV + nTri; let done = 0;
for await (const line of streamLines(url)) {
const c0 = line.charCodeAt(0), c1 = line.charCodeAt(1);
if (c0 === V && c1 === SP) {
const p = line.split(/\s+/);
positions[vi * 3] = +p[1] - ox;
positions[vi * 3 + 1] = +p[2] - oy;
positions[vi * 3 + 2] = +p[3] - oz;
vi++;
if ((++done & 0x3ffff) === 0) onProgress?.(done, total);
} else if (c0 === F && c1 === SP) {
const p = line.split(/\s+/);
const vs: number[] = [];
for (let k = 1; k < p.length; k++) {
if (!p[k]) continue;
const slash = p[k].indexOf('/');
let idx = parseInt(slash >= 0 ? p[k].slice(0, slash) : p[k], 10);
idx = idx < 0 ? nV + idx : idx - 1; // negative = relative; OBJ is 1-based
vs.push(idx);
}
if (colors) for (const v of vs) { colors[v * 3] = cr; colors[v * 3 + 1] = cg; colors[v * 3 + 2] = cb; }
for (let t = 1; t + 1 < vs.length; t++) { // fan triangulate
index[ii++] = vs[0]; index[ii++] = vs[t]; index[ii++] = vs[t + 1];
}
if ((++done & 0x3ffff) === 0) onProgress?.(done, total);
} else if (c0 === U && line.startsWith('usemtl ')) {
const c = colorMap?.get(line.slice(7).trim());
if (c) { cr = c[0]; cg = c[1]; cb = c[2]; }
}
}
const geom = new THREE.BufferGeometry();
geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
if (colors) geom.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geom.setIndex(new THREE.BufferAttribute(index, 1));
geom.computeVertexNormals();
onProgress?.(total, total);
const mat = new THREE.MeshStandardMaterial({
color: colors ? 0xffffff : 0xcccccc,
vertexColors: !!colors,
metalness: 0.0,
roughness: 1.0,
side: THREE.DoubleSide,
});
return new THREE.Mesh(geom, mat);
}
+76
View File
@@ -0,0 +1,76 @@
# tools/ — Offline asset pipeline
Offline tooling for preparing 3D assets served by the hmwebviewer. Nothing here
ships to the browser at runtime; these produce the optimized assets and
pre-rendered placeholders that the viewer consumes.
## preprocess.mjs — Draco + KTX2 compression
Compresses a `.glb`/`.gltf` into a single self-contained `.optimized.glb` using
[gltf-transform](https://gltf-transform.dev/): Draco for geometry, KTX2/Basis
Universal for textures, in one pass. Output loads directly in the viewer's
`GLTFLoader` (with `DRACOLoader` + `KTX2Loader` wired on the shared loader).
### Install (once)
These dev dependencies are **not** in `package.json` yet. Run before first use:
```bash
npm install -D @gltf-transform/core @gltf-transform/functions \
@gltf-transform/extensions @gltf-transform/cli
```
The CLI package provides the KTX2 encoder wiring. gltf-transform fetches the
platform basis encoder automatically on first KTX2 run (network needed once).
### Usage
```bash
node tools/preprocess.mjs <input.glb> [output.glb] [--draco-bits N] [--ktx2|--no-ktx2]
```
- Default output: `<input>.optimized.glb` (e.g. `model.glb` → `model.optimized.glb`).
- `--draco-bits N` — position quantization bits, 8..16 (default **14**). Lower =
smaller file, lossier geometry. Tune per asset.
- `--ktx2` / `--no-ktx2` — texture encoding toggle (default **on**).
Prints before/after byte sizes and reduction %.
### Outputs
- Compressed GLBs land in [`samples/`](../samples/) (`samples/*.optimized.glb`).
- Pre-rendered WebP placeholders land in [`public/previews/`](../public/previews/)
(`*.webp`) — see the pre-render section below.
If a required package is missing at runtime, the script prints the install
command above and exits non-zero (no silent skip).
## Sample assets
Place source `.glb` files under `samples/` (suggested split: small / medium /
large). Compress with `preprocess.mjs` and commit the `.optimized.glb` outputs.
Samples are not committed yet — to be added when a sample asset is available.
## Pre-render pipeline (360° animated WebP placeholder)
For Path A (server asset) the server ships a pre-rendered 360° turntable so the
page shows motion instantly while the real model decodes in the background.
Output: `public/previews/<asset>.webp` (animated, with alpha).
**Status: to be implemented when a sample asset is available.** Two options:
1. **Blender headless CLI (preferred).** Camera parented to an empty at the
model origin, Z axis 0→360° keyframed over N frames. Render frames:
```bash
blender -b scene.blend -o //frame_### -f 1..N -F PNG
```
Assemble to animated WebP (alpha) or WebM VP9 via ffmpeg:
```bash
ffmpeg -framerate 24 -i frame_%03d.png -loop 0 -plays 0 out.webp
```
2. **Puppeteer + headless three.js (fallback).** Spin a minimal three.js page,
rotate the model, and call `page.screenshot({ type: 'webp' })` per rotation
step. Stitch frames into an animated WebP.
Both options need a committed sample asset first; blocked on P4-2.
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env node
/**
* tools/adaptive-smoke.mjs — empirically trigger the adaptive-quality downstep.
*
* Loads a heavy sample in headless Chrome (software GL = slow) + applies a CDP
* CPU throttle so the REAL measured fps sustains < 60. Captures the genuine
* `[hmwebviewer] adaptive quality -> tier N (pr=..)` console logs emitted by
* src/viewer/adaptiveQuality.ts, plus the live #.fps overlay text. Proves the
* <60fps → optimize path fires on real frame measurement (no logic patched).
*
* USAGE: node tools/adaptive-smoke.mjs [--url URL] [--asset PATH] [--throttle N] [--seconds S]
*/
import puppeteer from 'puppeteer-core';
import { existsSync } from 'node:fs';
const CHROME_CANDIDATES = [
'C:/Program Files/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
];
function findBrowser() {
if (process.env.PUPPETEER_EXECUTABLE_PATH && existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) return process.env.PUPPETEER_EXECUTABLE_PATH;
for (const p of CHROME_CANDIDATES) if (existsSync(p)) return p;
return null;
}
function parseArgs(argv) {
const a = argv.slice(2);
let url = 'http://127.0.0.1:4173', asset = '/samples/ABeautifulGame.ktx2.glb', throttle = 6, seconds = 25;
for (let i = 0; i < a.length; i++) {
if (a[i] === '--url') url = a[++i];
else if (a[i] === '--asset') asset = a[++i];
else if (a[i] === '--throttle') throttle = Number(a[++i]);
else if (a[i] === '--seconds') seconds = Number(a[++i]);
}
return { url, asset, throttle, seconds };
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function main() {
const o = parseArgs(process.argv);
const exe = findBrowser();
if (!exe) { console.error('[adaptive-smoke] No Chrome/Edge found.'); process.exit(1); }
const browser = await puppeteer.launch({
executablePath: exe,
headless: 'new',
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'],
});
const page = await browser.newPage();
const tierLogs = [];
page.on('console', (m) => {
const t = m.text();
if (t.includes('adaptive quality')) { tierLogs.push(t); console.log(' LOG ' + t); }
});
await page.evaluateOnNewDocument(() => {
window.__hmwReady = null;
window.addEventListener('hmw:ready', (e) => { window.__hmwReady = e.detail; });
});
const target = o.url + '?model=' + o.asset;
console.log('[adaptive-smoke] ' + target + ' throttle=' + o.throttle + 'x watch=' + o.seconds + 's browser=' + exe);
await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForFunction('window.__hmwReady != null', { timeout: 60000 });
console.log('[adaptive-smoke] model loaded; applying CPU throttle + watching fps...');
const client = await page.target().createCDPSession();
await client.send('Emulation.setCPUThrottlingRate', { rate: o.throttle });
const start = Date.now();
while (Date.now() - start < o.seconds * 1000) {
await sleep(2000);
const state = await page.evaluate(() => {
const el = document.querySelector('.fps');
return { fps: el ? el.textContent : '(no overlay)', pr: window.__viewer ? undefined : undefined };
});
console.log(' t+' + Math.round((Date.now() - start) / 1000) + 's overlay=' + state.fps + ' tierSteps=' + tierLogs.length);
if (tierLogs.length >= 3) break; // enough proof
}
await browser.close();
console.log('\n[adaptive-smoke] tier-change log lines captured: ' + tierLogs.length);
tierLogs.forEach((l) => console.log(' ' + l));
if (tierLogs.length === 0) {
console.error('[adaptive-smoke] NO downstep observed — fps stayed >=60 (raise --throttle or use a heavier asset).');
process.exit(2);
}
console.log('[adaptive-smoke] PASS — adaptive quality degraded on sustained <60fps.');
}
main().catch((e) => { console.error('[adaptive-smoke] Fatal: ' + (e && e.stack ? e.stack : e)); process.exit(1); });
+46
View File
@@ -0,0 +1,46 @@
// Copies Draco + KTX2/Basis decoder WASM from three.js into public/ so the
// runtime paths /draco/ and /basis/ resolve (version-matched to installed three).
// Also copies the web-ifc single-thread WASM to public/web-ifc/ for the IFC path.
// Runs automatically via `npm install` (postinstall). Safe to re-run.
import { cpSync, copyFileSync, existsSync, mkdirSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, '..');
const libs = resolve(root, 'node_modules/three/examples/jsm/libs');
const targets = [
[resolve(libs, 'draco/gltf'), resolve(root, 'public/draco')],
[resolve(libs, 'basis'), resolve(root, 'public/basis')],
];
if (!existsSync(libs)) {
console.warn('[copy-decoders] three not installed yet — skipping (will run on next install).');
process.exit(0);
}
for (const [src, dest] of targets) {
if (!existsSync(src)) {
console.warn(`[copy-decoders] source missing: ${src}`);
continue;
}
mkdirSync(dest, { recursive: true });
cpSync(src, dest, { recursive: true });
console.log(`[copy-decoders] ${src} -> ${dest}`);
}
// web-ifc single-thread WASM (+ MT build if present) for the IFC load path.
const ifcSrcDir = resolve(root, 'node_modules/web-ifc');
const ifcDest = resolve(root, 'public/web-ifc');
if (existsSync(ifcSrcDir)) {
mkdirSync(ifcDest, { recursive: true });
for (const wasm of ['web-ifc.wasm', 'web-ifc-mt.wasm']) {
const src = resolve(ifcSrcDir, wasm);
if (!existsSync(src)) continue;
copyFileSync(src, resolve(ifcDest, wasm));
console.log(`[copy-decoders] ${src} -> ${resolve(ifcDest, wasm)}`);
}
} else {
console.warn('[copy-decoders] web-ifc not installed yet — skipping IFC wasm.');
}
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env node
/**
* tools/dnd-smoke.mjs — CSR (Path B / drag&drop) load smoke.
*
* The per-format perf-smoke only exercised Path A (?model=). This drives the
* REAL local-file path: fetch each sample into a File, hand it to
* window.__viewer.loadLocalFile(file) (exactly what the dropzone does), and wait
* for hmw:ready. Catches the blob:-URL-has-no-extension class of bug.
*
* USAGE: node tools/dnd-smoke.mjs [--url URL] [--asset /samples/x ...]
* (run `npm run build && npm run preview` first)
*/
import puppeteer from 'puppeteer-core';
import { existsSync } from 'node:fs';
const CHROME_CANDIDATES = [
'C:/Program Files/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
];
function findBrowser() {
if (process.env.PUPPETEER_EXECUTABLE_PATH && existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) return process.env.PUPPETEER_EXECUTABLE_PATH;
for (const p of CHROME_CANDIDATES) if (existsSync(p)) return p;
return null;
}
function parseArgs(argv) {
const a = argv.slice(2); let url = 'http://127.0.0.1:4173'; const assets = [];
for (let i = 0; i < a.length; i++) {
if (a[i] === '--url') url = a[++i]; else if (a[i] === '--asset') assets.push(a[++i]);
}
if (!assets.length) assets.push('/samples/Box.glb', '/samples/Duck.glb', '/samples/Avocado.glb', '/samples/Cube.obj', '/samples/Cube.fbx', '/samples/Cube.dae', '/samples/Cube.ifc');
return { url, assets };
}
async function dropOne(page, base, asset) {
await page.goto(base, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForFunction('window.__viewer != null', { timeout: 15000 });
try {
const res = await page.evaluate(async (assetPath) => {
const name = assetPath.split('/').pop();
const resp = await fetch(assetPath);
if (!resp.ok) return { ok: false, err: 'fetch ' + resp.status };
const blob = await resp.blob();
const file = new File([blob], name, { type: blob.type || 'application/octet-stream' });
const ready = new Promise((resolve) => {
const onErr = (e) => resolve({ ok: false, err: 'viewer error: ' + (e.detail || 'unknown') });
window.addEventListener('hmw:ready', () => resolve({ ok: true }), { once: true });
// surface our own onError via console; also time out below
window.__dndTimeout = setTimeout(() => resolve({ ok: false, err: 'timeout (no hmw:ready)' }), 20000);
});
window.__viewer.loadLocalFile(file);
return ready;
}, asset);
return { asset, ...res };
} catch (e) {
return { asset, ok: false, err: (e && e.message) || String(e) };
}
}
async function main() {
const o = parseArgs(process.argv);
const exe = findBrowser();
if (!exe) { console.error('[dnd-smoke] No Chrome/Edge found.'); process.exit(1); }
const browser = await puppeteer.launch({ executablePath: exe, headless: 'new', args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'] });
const page = await browser.newPage();
const errs = [];
page.on('console', (m) => { const t = m.text(); if (t.includes('load failed') || t.includes('Failed to load')) errs.push(t); });
console.log('[dnd-smoke] CSR drag&drop path, base=' + o.url);
const results = [];
for (const asset of o.assets) {
process.stdout.write(' drop ' + asset + ' ... ');
const r = await dropOne(page, o.url, asset);
results.push(r);
console.log(r.ok ? 'OK' : 'FAIL (' + r.err + ')');
}
await browser.close();
const failed = results.filter((r) => !r.ok);
console.log('\n[dnd-smoke] ' + (results.length - failed.length) + '/' + results.length + ' passed');
if (failed.length) { console.error('[dnd-smoke] FAILURES: ' + failed.map((f) => f.asset).join(', ')); process.exit(1); }
console.log('[dnd-smoke] all local drops load.');
}
main().catch((e) => { console.error('[dnd-smoke] Fatal: ' + (e && e.stack ? e.stack : e)); process.exit(1); });
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env node
/**
* Diagnostic: load a huge OBJ (+mtl) through the REAL drag&drop path in headless
* Chrome, capture console ([recenter]/[hmw] bbox) and a screenshot. Confirms
* whether the in-viewer recenter ran and whether faces crack.
*
* USAGE: node tools/girder-smoke.mjs [--url http://localhost:3333] [obj] [mtl]
*/
import puppeteer from 'puppeteer-core';
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
const CHROME = [
'C:/Program Files/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
].find((p) => existsSync(p));
const args = process.argv.slice(2);
let url = 'http://localhost:3333';
const files = [];
for (let i = 0; i < args.length; i++) {
if (args[i] === '--url') url = args[++i];
else files.push(args[i]);
}
if (files.length === 0) {
files.push('samples/GirderObjs/part01.obj', 'samples/GirderObjs/part01.mtl');
}
const paths = files.map((f) => resolve(f));
for (const p of paths) if (!existsSync(p)) { console.error('missing', p); process.exit(1); }
const browser = await puppeteer.launch({
executablePath: CHROME,
headless: 'new',
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist',
'--disable-dev-shm-usage', '--window-size=1400,900'],
});
const page = await browser.newPage();
await page.setViewport({ width: 1400, height: 900 });
page.on('console', (m) => console.log(' [browser]', m.text()));
page.on('pageerror', (e) => console.log(' [pageerror]', e.message));
await page.evaluateOnNewDocument(() => {
window.__hmwReady = null;
window.addEventListener('hmw:ready', (e) => { window.__hmwReady = e.detail; });
});
console.log('goto', url);
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForSelector('#file-input', { timeout: 10000 });
const input = await page.$('#file-input');
console.log('uploading', paths.join(', '));
await input.uploadFile(...paths);
await page.evaluate(() => document.getElementById('file-input')
.dispatchEvent(new Event('change', { bubbles: true })));
console.log('waiting for hmw:ready (up to 240s)...');
const t0 = Date.now();
try {
await page.waitForFunction('window.__hmwReady != null', { timeout: 240000, polling: 1000 });
console.log('READY in', ((Date.now() - t0) / 1000).toFixed(1) + 's');
} catch {
console.log('TIMEOUT after', ((Date.now() - t0) / 1000).toFixed(1) + 's — capturing anyway');
}
await new Promise((r) => setTimeout(r, 1500));
await page.screenshot({ path: resolve('girder-smoke.png') });
console.log('screenshot -> girder-smoke.png (full)');
// Medium zoom (reproduce user's close inspection) via OrbitControls wheel.
await page.mouse.move(700, 450);
for (let i = 0; i < 14; i++) { await page.mouse.wheel({ deltaY: -200 }); await new Promise((r) => setTimeout(r, 20)); }
await new Promise((r) => setTimeout(r, 1000));
await page.screenshot({ path: resolve('girder-zoom.png') });
console.log('screenshot -> girder-zoom.png (zoomed in)');
await browser.close();
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env node
/**
* tools/perf-smoke.mjs — Perceived-load smoke test for hmwebviewer (PLAN P5-3).
*
* Launches headless Chrome via puppeteer-core (uses the system Chrome; no
* Chromium download). For each sample asset it loads `?model=<asset>` and
* measures wall time from navigation-start to the viewer's `hmw:ready` event
* (dispatched in ThreeDViewer.onLoaded). Asserts < 3000ms perceived.
*
* USAGE
* node tools/perf-smoke.mjs [--url http://127.0.0.1:4173] [--asset /samples/Box.glb ...]
*
* Defaults: url = http://127.0.0.1:4173 (vite preview); assets = the sample set.
* Run `npm run build && npm run preview` first (or `npm run dev` on its port).
*
* Browser auto-detected: Chrome, then Edge, then PUPPETEER_EXECUTABLE_PATH.
*/
import puppeteer from 'puppeteer-core';
import { existsSync } from 'node:fs';
const CHROME_CANDIDATES = [
'C:/Program Files/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
];
function findBrowser() {
if (process.env.PUPPETEER_EXECUTABLE_PATH && existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
return process.env.PUPPETEER_EXECUTABLE_PATH;
}
for (const p of CHROME_CANDIDATES) if (existsSync(p)) return p;
return null;
}
function parseArgs(argv) {
const args = argv.slice(2);
let url = 'http://127.0.0.1:4173';
const assets = [];
for (let i = 0; i < args.length; i++) {
if (args[i] === '--url') url = args[++i];
else if (args[i] === '--asset') assets.push(args[++i]);
else if (args[i] === '-h' || args[i] === '--help') return { help: true };
}
if (assets.length === 0) {
assets.push('/samples/Box.glb', '/samples/Duck.glb', '/samples/Duck.optimized.glb', '/samples/Avocado.glb');
}
return { url, assets };
}
const THRESHOLD_MS = 3000;
async function measureAsset(browser, baseUrl, asset) {
const page = await browser.newPage();
await page.evaluateOnNewDocument(() => {
window.__hmwReady = null;
window.addEventListener('hmw:ready', (e) => { window.__hmwReady = e.detail; });
});
const target = baseUrl + '?model=' + asset;
const t0 = Date.now();
try {
await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForFunction('window.__hmwReady != null', { timeout: 30000 });
const data = await page.evaluate(() => ({ readyPerf: window.__hmwReady, origin: performance.timeOrigin }));
const ms = (data.origin + data.readyPerf) - t0;
return { asset, ms: Math.round(ms), ok: ms < THRESHOLD_MS };
} catch (e) {
return { asset, ms: null, ok: false, error: (e && e.message) ? e.message : String(e) };
} finally {
await page.close();
}
}
async function main() {
const opts = parseArgs(process.argv);
if (opts.help) {
console.error('Usage: node tools/perf-smoke.mjs [--url URL] [--asset PATH ...]');
process.exit(0);
}
const exe = findBrowser();
if (!exe) {
console.error('[perf-smoke] No Chrome/Edge found. Set PUPPETEER_EXECUTABLE_PATH.');
process.exit(1);
}
const browser = await puppeteer.launch({
executablePath: exe,
headless: 'new',
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'],
});
console.log('[perf-smoke] base=' + opts.url + ' browser=' + exe);
console.log('[perf-smoke] threshold=' + THRESHOLD_MS + 'ms perceived');
const results = [];
for (const asset of opts.assets) {
process.stdout.write(' ' + asset + ' ... ');
const r = await measureAsset(browser, opts.url, asset);
results.push(r);
console.log(r.ms === null ? 'FAIL (' + (r.error || 'timeout') + ')' : (r.ms + 'ms ' + (r.ok ? 'OK' : 'OVER')));
}
await browser.close();
console.log('\n[perf-smoke] summary');
for (const r of results) {
console.log(' ' + r.asset.padEnd(34) + (r.ms === null ? 'FAIL' : (r.ms + 'ms').padEnd(8)) + (r.ok ? 'PASS' : 'FAIL'));
}
const failed = results.filter((r) => !r.ok);
if (failed.length) {
console.error('[perf-smoke] ' + failed.length + ' asset(s) over threshold/failed.');
process.exit(1);
}
console.log('[perf-smoke] all within ' + THRESHOLD_MS + 'ms.');
}
main().catch((e) => {
console.error('[perf-smoke] Fatal: ' + (e && e.stack ? e.stack : e));
process.exit(1);
});
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env node
/** Generate an ASCII PLY cube (vertex colors) and load it via the drag&drop path
* in headless Chrome; screenshot to verify PLY support. */
import puppeteer from 'puppeteer-core';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
const PLY = `ply
format ascii 1.0
element vertex 8
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 12
property list uchar int vertex_indices
end_header
-1 -1 -1 255 0 0
1 -1 -1 0 255 0
1 1 -1 0 0 255
-1 1 -1 255 255 0
-1 -1 1 255 0 255
1 -1 1 0 255 255
1 1 1 255 255 255
-1 1 1 90 90 90
3 0 1 2
3 0 2 3
3 4 5 6
3 4 6 7
3 0 4 7
3 0 7 3
3 1 5 6
3 1 6 2
3 3 2 6
3 3 6 7
3 0 1 5
3 0 5 4
`;
const dir = resolve('samples/plytest');
mkdirSync(dir, { recursive: true });
writeFileSync(resolve(dir, 'cube.ply'), PLY);
console.log('asset ->', resolve(dir, 'cube.ply'));
const url = process.argv.includes('--url') ? process.argv[process.argv.indexOf('--url') + 1] : 'http://localhost:3333';
const CHROME = ['C:/Program Files/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'].find((p) => existsSync(p));
const browser = await puppeteer.launch({ executablePath: CHROME, headless: 'new',
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist', '--window-size=900,700'] });
const page = await browser.newPage();
await page.setViewport({ width: 900, height: 700 });
page.on('console', (m) => console.log(' [browser]', m.text()));
page.on('pageerror', (e) => console.log(' [pageerror]', e.message));
await page.evaluateOnNewDocument(() => { window.__r = null; addEventListener('hmw:ready', (e) => { window.__r = e.detail; }); });
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForSelector('#file-input');
await (await page.$('#file-input')).uploadFile(resolve(dir, 'cube.ply'));
await page.evaluate(() => document.getElementById('file-input').dispatchEvent(new Event('change', { bubbles: true })));
try { await page.waitForFunction('window.__r != null', { timeout: 30000, polling: 500 }); console.log('READY'); }
catch { console.log('TIMEOUT'); }
await new Promise((r) => setTimeout(r, 1200));
await page.screenshot({ path: resolve('ply-test.png') });
console.log('screenshot -> ply-test.png');
await browser.close();
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env node
/**
* tools/preprocess.mjs — Offline GLB optimization for hmwebviewer.
*
* SCOPE: GLB/glTF ONLY (gltf-transform pipeline). Other formats shipped as
* samples (OBJ/DAE/FBX/IFC) are NOT processed here — they are consumed raw by
* their respective three.js loaders; no offline Draco/KTX2 step applies.
*
* Compresses a glTF/GLB with Draco (geometry) + WebP texture compression in one
* pass using gltf-transform. Output is a single self-contained .optimized.glb
* ready for the viewer's GLTFLoader (+ DRACOLoader).
*
* KTX2/Basis texture encoding is NOT done here — it needs the `toktx` platform
* encoder (KTX-Software 4.3+). gltf-transform exposes it via the CLI `etc1s`
* (lossy, smaller) or `uastc` (higher quality) commands, e.g.:
* gltf-transform etc1s <out.optimized.glb> <out.ktx2.glb>
* Install KTX-Software from https://github.com/KhronosGroup/KTX-Software first.
* Runtime: the viewer's KTX2Loader decodes these at load (verified with the
* Khronos ABeautifulGame KTX2+Draco sample — 626ms perceived load).
*
* USAGE
* node tools/preprocess.mjs <input.glb> [output.glb] [--draco-bits N] [--texture FMT]
*
* --draco-bits N Position quantization bits (default 14). 8..16; lower=smaller/lossier.
* --texture FMT Texture re-encode: webp (default) | jpeg | png | none.
*
* OUTPUT
* Default output = input with `.optimized.glb` suffix. Prints before/after
* byte sizes + reduction %.
*
* INSTALL (these are devDependencies now; if absent the script prints the
* install command and exits non-zero — no silent skip):
* npm install -D @gltf-transform/core @gltf-transform/functions \
* @gltf-transform/extensions @gltf-transform/cli
*/
async function loadDeps() {
let core, fns, ext;
try {
core = await import('@gltf-transform/core');
fns = await import('@gltf-transform/functions');
ext = await import('@gltf-transform/extensions');
} catch (e) {
const msg = (e && e.message) ? e.message : String(e);
console.error('[preprocess] Missing required package: ' + msg);
console.error('[preprocess] Install the toolchain with:');
console.error(
' npm install -D @gltf-transform/core @gltf-transform/functions ' +
'@gltf-transform/extensions @gltf-transform/cli'
);
process.exit(1);
}
return { core, fns, ext };
}
const TEXTURE_FORMATS = ['webp', 'jpeg', 'png', 'none'];
function parseArgs(argv) {
const args = argv.slice(2);
if (args.length === 0 || args[0] === '-h' || args[0] === '--help') {
return { help: true };
}
const positional = [];
let dracoBits = 14;
let texture = 'webp';
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--draco-bits') {
const v = Number(args[++i]);
if (!Number.isInteger(v) || v < 8 || v > 16) {
throw new Error('--draco-bits must be an integer 8..16');
}
dracoBits = v;
} else if (a === '--texture') {
const v = String(args[++i]).toLowerCase();
if (!TEXTURE_FORMATS.includes(v)) {
throw new Error('--texture must be one of: ' + TEXTURE_FORMATS.join(', '));
}
texture = v;
} else if (a.startsWith('--')) {
throw new Error('Unknown option: ' + a);
} else {
positional.push(a);
}
}
if (positional.length === 0) {
throw new Error('Missing <input.glb>');
}
const input = positional[0];
let output = positional[1];
if (!output) {
output = input.replace(/\.glb$/i, '') + '.optimized.glb';
}
return { input, output, dracoBits, texture };
}
function usage() {
console.error(
'Usage: node tools/preprocess.mjs <input.glb> [output.glb] [--draco-bits N] [--texture webp|jpeg|png|none]'
);
}
async function main() {
const opts = parseArgs(process.argv);
if (opts.help) {
usage();
return;
}
const { core, fns, ext } = await loadDeps();
const { WebIO } = core;
const { quantize, dedup, weld, draco, textureCompress, prune } = fns;
const { KHRDracoMeshCompression, EXTTextureWebP } = ext;
const { promises: fs } = await import('node:fs');
const path = await import('node:path');
const inputPath = path.resolve(opts.input);
const outputPath = path.resolve(opts.output);
let inputBytes;
try {
inputBytes = await fs.readFile(inputPath);
} catch (e) {
console.error('[preprocess] Cannot read input "' + inputPath + '": ' + e.message);
process.exit(1);
}
const beforeSize = inputBytes.byteLength;
// Draco encoder must be handed to the extension as a registered dependency.
const draco3d = await import('draco3d');
const dracoEncoder = await draco3d.createEncoderModule();
const io = new WebIO()
.registerExtensions([KHRDracoMeshCompression, EXTTextureWebP])
.registerDependencies({ 'draco3d.encoder': dracoEncoder });
let doc;
try {
doc = await io.readBinary(inputBytes);
} catch (e) {
console.error('[preprocess] Failed to parse glTF: ' + e.message);
process.exit(1);
}
const transforms = [];
transforms.push(dedup());
transforms.push(weld({ tolerance: 1e-4 }));
transforms.push(
quantize({
quantizePosition: opts.dracoBits,
quantizeNormal: 10,
quantizeTexcoord: 12,
quantizeColor: 8,
})
);
transforms.push(prune());
transforms.push(
draco({
encodeSpeed: 5,
decodeSpeed: 5,
quantizePosition: opts.dracoBits,
quantizeNormal: 10,
quantizeTexcoord: 12,
quantizeColor: 8,
})
);
if (opts.texture !== 'none') {
transforms.push(textureCompress({ targetFormat: opts.texture, quality: 8 }));
}
await doc.transform(...transforms);
const outGLB = await io.writeBinary(doc);
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, outGLB);
const afterSize = outGLB.byteLength;
const reduction = beforeSize > 0 ? ((1 - afterSize / beforeSize) * 100) : 0;
const fmt = (n) => (n / 1024).toFixed(1) + ' KiB';
console.log('[preprocess] ' + path.basename(inputPath) + ' -> ' + path.basename(outputPath));
console.log('[preprocess] before: ' + beforeSize + ' bytes (' + fmt(beforeSize) + ')');
console.log('[preprocess] after: ' + afterSize + ' bytes (' + fmt(afterSize) + ')');
console.log(
'[preprocess] reduction: ' + reduction.toFixed(1) + '% ' +
'(draco-bits=' + opts.dracoBits + ', texture=' + opts.texture + ')'
);
}
main().catch((e) => {
console.error('[preprocess] Fatal: ' + (e && e.stack ? e.stack : e));
process.exit(1);
});
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env node
/**
* tools/prerender.mjs — 360° turntable pre-render → animated WebP placeholder (PLAN P4-3).
*
* Loads the running viewer (?model=<asset>) in headless Chrome, orbits the
* camera one full turn in N steps, screenshots each frame, then assembles the
* PNG sequence into an animated WebP via ffmpeg. The result is an SSR
* placeholder image that hydrates into the live canvas on load.
*
* USAGE
* node tools/prerender.mjs --asset /samples/Duck.glb [--frames 36] [--size 512] \
* [--framerate 20] [--out public/previews/Duck.webp] \
* [--url http://127.0.0.1:4173]
*
* Requires: puppeteer-core (installed) + ffmpeg on PATH + the viewer built & served
* (`npm run build && npm run preview`, or `npm run dev`).
*/
import puppeteer from 'puppeteer-core';
import { existsSync, mkdirSync, rmSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, '..');
const CHROME_CANDIDATES = [
'C:/Program Files/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
];
function findBrowser() {
if (process.env.PUPPETEER_EXECUTABLE_PATH && existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
return process.env.PUPPETEER_EXECUTABLE_PATH;
}
for (const p of CHROME_CANDIDATES) if (existsSync(p)) return p;
return null;
}
function parseArgs(argv) {
const a = argv.slice(2);
const o = { asset: null, frames: 36, size: 512, framerate: 20, out: null, url: 'http://127.0.0.1:4173' };
for (let i = 0; i < a.length; i++) {
const k = a[i], v = a[i + 1];
if (k === '--asset') { o.asset = v; i++; }
else if (k === '--frames') { o.frames = parseInt(v, 10); i++; }
else if (k === '--size') { o.size = parseInt(v, 10); i++; }
else if (k === '--framerate') { o.framerate = parseInt(v, 10); i++; }
else if (k === '--out') { o.out = v; i++; }
else if (k === '--url') { o.url = v; i++; }
else if (k === '-h' || k === '--help') return { help: true };
}
if (!o.asset) return { help: true };
if (!o.out) {
const base = o.asset.split('/').pop().replace(/\.(glb|gltf|obj|fbx|dae|ifc)$/i, '');
o.out = 'public/previews/' + base + '.webp';
}
// Undo MSYS/Git-Bash mangling of leading-slash args (/samples/x.glb -> C:/.../Git/samples/x.glb)
o.asset = o.asset.replace(/^.*\/Program Files\/Git\//i, '/');
if (!o.asset.startsWith('/')) o.asset = '/' + o.asset;
return o;
}
function ffmpegAvailable() {
try { execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' }); return true; }
catch { return false; }
}
async function main() {
const opts = parseArgs(process.argv);
if (opts.help) {
console.error('Usage: node tools/prerender.mjs --asset /samples/Duck.glb [--frames 36] [--size 512] [--framerate 20] [--out public/previews/Duck.webp] [--url URL]');
process.exit(opts.asset ? 0 : 1);
}
const exe = findBrowser();
if (!exe) { console.error('[prerender] No Chrome/Edge found.'); process.exit(1); }
const haveFfmpeg = ffmpegAvailable();
const baseName = opts.asset.split('/').pop().replace(/\.(glb|gltf|obj|fbx|dae|ifc)$/i, '');
const frameDir = resolve(root, '.prerender-frames', baseName);
rmSync(frameDir, { recursive: true, force: true });
mkdirSync(frameDir, { recursive: true });
const browser = await puppeteer.launch({
executablePath: exe,
headless: 'new',
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'],
defaultViewport: { width: opts.size, height: opts.size },
});
const page = await browser.newPage();
page.on('console', (m) => { if (m.type() === 'error') console.error('[page-console]', m.text()); });
page.on('pageerror', (e) => console.error('[page-error]', e.message));
page.on('requestfailed', (r) => console.error('[req-failed]', r.url(), r.failure()?.errorText));
await page.evaluateOnNewDocument(() => {
window.__hmwReady = null;
window.addEventListener('hmw:ready', (e) => { window.__hmwReady = e.detail; });
});
await page.goto(opts.url + '?model=' + opts.asset, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForFunction('window.__hmwReady != null', { timeout: 30000 });
for (let i = 0; i < opts.frames; i++) {
const azimuth = (i / opts.frames) * Math.PI * 2;
await page.evaluate((az) => window.__viewer && window.__viewer.rotateTo(az), azimuth);
const file = resolve(frameDir, 'frame_' + String(i).padStart(3, '0') + '.png');
await page.screenshot({ path: file, omitBackground: false });
}
await browser.close();
const { readdirSync } = await import('node:fs');
const frameCount = readdirSync(frameDir).filter((f) => f.endsWith('.png')).length;
console.log('[prerender] captured ' + frameCount + '/' + opts.frames + ' frames -> ' + frameDir);
const outAbs = resolve(root, opts.out);
mkdirSync(dirname(outAbs), { recursive: true });
if (!haveFfmpeg) {
console.warn('[prerender] ffmpeg not found. Frames left in ' + frameDir);
console.warn('[prerender] assemble manually:');
console.warn(' ffmpeg -framerate ' + opts.framerate + ' -i ' + frameDir + '/frame_%03d.png -loop 0 ' + outAbs);
return;
}
// Assemble animated WebP. NOTE: libwebp_anim is broken in this ffmpeg build
// (outputs 1 frame); -c:v libwebp + -vsync vfr produces correct multi-frame WebP.
execFileSync('ffmpeg', [
'-y', '-framerate', String(opts.framerate), '-vsync', 'vfr',
'-i', resolve(frameDir, 'frame_%03d.png'),
'-vf', 'scale=' + opts.size + ':' + opts.size + ':flags=lanczos',
'-c:v', 'libwebp', '-loop', '0', '-lossless', '0', '-q:v', '70',
outAbs,
], { stdio: 'inherit' });
console.log('[prerender] animated WebP -> ' + opts.out);
rmSync(frameDir, { recursive: true, force: true });
}
main().catch((e) => {
console.error('[prerender] Fatal: ' + (e && e.stack ? e.stack : e));
process.exit(1);
});
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env node
/**
* Generate a textured cube (cube.obj + cube.mtl + checker.png), then load it
* through the real drag&drop path (uploadFile of all 3) in headless Chrome and
* screenshot — verifies OBJ texture support (map_Kd → dropped image blob URL).
*
* USAGE: node tools/tex-test.mjs [--url http://localhost:3333]
*/
import puppeteer from 'puppeteer-core';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { deflateSync } from 'node:zlib';
import { resolve } from 'node:path';
// ---- minimal PNG encoder (truecolor RGB, filter 0) ----
const CRC = (() => {
const t = new Uint32Array(256);
for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[n] = c >>> 0; }
return (buf) => { let c = 0xffffffff; for (let i = 0; i < buf.length; i++) c = t[(c ^ buf[i]) & 0xff] ^ (c >>> 8); return (c ^ 0xffffffff) >>> 0; };
})();
function chunk(type, data) {
const len = Buffer.alloc(4); len.writeUInt32BE(data.length, 0);
const td = Buffer.concat([Buffer.from(type, 'ascii'), data]);
const crc = Buffer.alloc(4); crc.writeUInt32BE(CRC(td), 0);
return Buffer.concat([len, td, crc]);
}
function checkerPng(size = 64, cell = 8) {
const raw = Buffer.alloc((size * 3 + 1) * size);
for (let y = 0; y < size; y++) {
let o = y * (size * 3 + 1); raw[o++] = 0; // filter byte
for (let x = 0; x < size; x++) {
const on = ((x / cell | 0) + (y / cell | 0)) & 1;
raw[o++] = on ? 255 : 0; raw[o++] = on ? 0 : 255; raw[o++] = 255; // magenta / cyan
}
}
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(size, 0); ihdr.writeUInt32BE(size, 4);
ihdr[8] = 8; ihdr[9] = 2; // bitdepth 8, colortype 2 (RGB)
const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
return Buffer.concat([sig, chunk('IHDR', ihdr), chunk('IDAT', deflateSync(raw)), chunk('IEND', Buffer.alloc(0))]);
}
const OBJ = `mtllib cube.mtl
v -1 -1 -1
v 1 -1 -1
v 1 1 -1
v -1 1 -1
v -1 -1 1
v 1 -1 1
v 1 1 1
v -1 1 1
vt 0 0
vt 1 0
vt 1 1
vt 0 1
usemtl checker
f 1/1 2/2 3/3
f 1/1 3/3 4/4
f 5/1 6/2 7/3
f 5/1 7/3 8/4
f 1/1 5/2 8/3
f 1/1 8/3 4/4
f 2/1 6/2 7/3
f 2/1 7/3 3/4
f 4/1 3/2 7/3
f 4/1 7/3 8/4
f 1/1 2/2 6/3
f 1/1 6/3 5/4
`;
const MTL = `newmtl checker
Kd 1 1 1
map_Kd checker.png
`;
const dir = resolve('samples/textest');
mkdirSync(dir, { recursive: true });
writeFileSync(resolve(dir, 'cube.obj'), OBJ);
writeFileSync(resolve(dir, 'cube.mtl'), MTL);
writeFileSync(resolve(dir, 'checker.png'), checkerPng());
console.log('assets ->', dir);
const url = process.argv.includes('--url') ? process.argv[process.argv.indexOf('--url') + 1] : 'http://localhost:3333';
const CHROME = [
'C:/Program Files/Google/Chrome/Application/chrome.exe',
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
].find((p) => existsSync(p));
const browser = await puppeteer.launch({
executablePath: CHROME, headless: 'new',
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist', '--window-size=900,700'],
});
const page = await browser.newPage();
await page.setViewport({ width: 900, height: 700 });
page.on('console', (m) => console.log(' [browser]', m.text()));
page.on('pageerror', (e) => console.log(' [pageerror]', e.message));
await page.evaluateOnNewDocument(() => { window.__r = null; addEventListener('hmw:ready', (e) => { window.__r = e.detail; }); });
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForSelector('#file-input');
const input = await page.$('#file-input');
await input.uploadFile(resolve(dir, 'cube.obj'), resolve(dir, 'cube.mtl'), resolve(dir, 'checker.png'));
await page.evaluate(() => document.getElementById('file-input').dispatchEvent(new Event('change', { bubbles: true })));
try { await page.waitForFunction('window.__r != null', { timeout: 30000, polling: 500 }); console.log('READY'); }
catch { console.log('TIMEOUT'); }
await new Promise((r) => setTimeout(r, 1500));
await page.screenshot({ path: resolve('tex-test.png') });
console.log('screenshot -> tex-test.png');
await browser.close();
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"types": ["three"]
},
"include": ["src", "tools"]
}
+14
View File
@@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
export default defineConfig({
server: { host: '127.0.0.1', port: 3333, open: false, strictPort: true },
build: {
target: 'es2022',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: { three: ['three'] },
},
},
},
});