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>
5.9 KiB
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:
PLAN.md— what work remains, who owns what, task breakdown.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+KTX2Loaderon 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/basisif 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 overgltf-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
- SessionStart hook reminds you. Read
PLAN.md+PROGRESS.md. - Run
/next-task(or havetask-leadagent do it) → get the next ready task + owning agent. - Dispatch the owning agent with task ID, scope, acceptance criteria. Dispatch independent agents in ONE message (parallel).
- On completion: owning agent flips PLAN row →
done, runs/reportto append PROGRESS.md. reviewerruns after functional waves;/verify-viewergates release.
Hooks
SessionStart→.claude/hooks/session-start.shprints the "read PLAN + PROGRESS" reminder.progress-nudge.shexists under.claude/hooks/for PostToolUse nudge to update PROGRESS after runtime edits — add itsPostToolUseentry tosettings.jsonif desired (not auto-wired).