This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A web-based triangle mesh viewer with progressive LOD (Level of Detail) using Potree's octree streaming infrastructure. Converts OFF mesh files to a custom format combining centroids (for Potree octree indexing) with full triangle geometry (for rendering), enabling efficient viewing of large meshes (~1M+ triangles) in the browser.
OFF → PLY+BIN → LAS → Potree Octree
-
off2centroids.py: Parse OFF mesh file, compute triangle centroids with colors and original_index, output:centroids.ply: Centroid points (x, y, z, r, g, b, original_index)potree/triangles.bin: Binary file with 9 floats per triangle (v0.xyz, v1.xyz, v2.xyz)
-
convert.py: Orchestrates full pipeline:- OFF → PLY+BIN (via off2centroids.py)
- PLY → LAS (via pdal translate with extra_dims)
- LAS → Potree octree (via PotreeConverter)
Key Insight: Centroids are indexed by Potree's octree for LOD, while full
triangle geometry lives in a separate binary file indexed by original_index.
Three.js + potree-core + Custom Triangle LOD Manager
Located in viewer/:
src/main.js: Scene setup, camera, TrackballControls, main render loopsrc/TriangleLODManager.js: Syncs triangle meshes with Potree's visible nodes- Watches potree-core's LOD node visibility
- Loads potree/triangles.bin via HTTP Range requests (progressive loading)
- Creates/destroys Three.js meshes based on visible nodes
- Maps Potree node points → original_index → triangle geometry
src/TrackballHelper.js: Camera control utilities (WASD movement, mouse orbit)
Key Insight: TriangleLODManager uses HTTP Range requests to fetch only the triangles needed for currently visible Potree nodes, avoiding loading the entire file upfront.
# Convert OFF mesh to Potree format
uv run python convert.py input.off output_dir/
# Run individual tests
uv run python off2centroids_test.py
uv run python ply2las_test.py
uv run python potree_convert_test.pycd viewer/
# Development server (Vite)
bun dev
# Build for production
bun build
# Lint
bun lint
# Format code
bun format
bun format:check# Serve from repo root
python3 -m http.server 8080
# Open in browser (old standalone HTML)
http://localhost:8080/triangle_splatting.html?data=output_dir
# Open in browser (new Vite viewer)
cd viewer && bun dev
# Then open http://localhost:5173/?data=../output_garden- PotreeConverter: Found via
POTREE_CONVERTERenv var,--potree-converterCLI arg, or PATH lookup. Download from https://github.com/potree/PotreeConverter/releases - uv: Python package manager (required)
- Standard PLY point cloud with custom property
original_index(uint32) - Preserved through LAS conversion via
ply2las.py(pure Python, laspy) - Indexed by Potree's octree for spatial LOD queries
potree/
├── metadata.json # Octree bounds, scale, point count + SH config
├── hierarchy.bin # Node hierarchy (which nodes exist)
├── octree.bin # Point data (centroids with original_index)
├── triangles.bin # Triangle vertex offsets (9 float32 × 36 bytes each)
└── sh0.bin, sh1.bin...# SH coefficients per degree (optional)
- Lives inside
potree/alongside the octree data - Binary format:
[triangle0, triangle1, ...] - Each triangle: 9 consecutive float32 values (x1,y1,z1, x2,y2,z2, x3,y3,z3)
- Size: 36 bytes per triangle (9 floats × 4 bytes)
- Accessed via HTTP Range requests:
byte_offset = original_index * 36
- KISS - Prefer simple solutions (e.g., static binary file over complex database)
- DRY - Search codebase for existing patterns before creating new ones
- YAGNI - Build only what's needed now (no premature abstractions)
- Leverage Libraries - Use maintained libraries. Don't reinvent wheels. In this repo, for example, use Three.js, potree-core, PDAL instead of reimplementing.
- No Magic - Explicit configuration (hardcoded paths in convert.py are acceptable for local tools)
- Clear Separation of Concerns - One purpose per file/class. Models ≠ logic. Data pipeline (Python) ≠ Viewer (JS)
- Dependency Injection - Pass dependencies in (config, clients), don't import globally.
- Small, Focused Interfaces - Minimal abstract base classes. Easy to extend.
- Named Objects Over Tuples - For 3+ return values, use Pydantic models with named fields instead of positional tuples.
BEFORE writing ANY new code:
- Search the codebase thoroughly - Use Grep/Glob to find similar functionality
- Reuse existing patterns - Follow established patterns (collectors, models, adapters)
- Check for library solutions - Can libraries solve this? (maybe search in internet if unsure and needed)
- Spend time planning - In plan mode, invest CONSIDERABLE time creating detailed plans
Planning checklist (MANDATORY):
- Identified exact problem (WHERE code is, WHAT issue is, WHY it matters, HOW to fix)
- Searched codebase for existing solutions to reuse
- Verified no library can solve this
- Questioned every abstraction (YAGNI - do we really need it?)
- Planned specific steps (exact files, line numbers, actions)
- Identified risks and mitigations
- Kept it simple (KISS - simplest approach that works)
- Use
git add <specific-file>notgit add -A - Review diffs before committing
- No
git commit --amendunless explicitly asked - CRITICAL: Do NOT reference Claude in commit messages or code
NEVER do these things - they violate our core principles:
- DON'T create new files unless absolutely necessary -> Edit existing files
- DON'T proactively create documentation files (.md, README) -> Only when explicitly requested
- DON'T add emojis to code or documentation -> Only use when explicitly requested
- DON'T use
git add -Aorgit add .-> Usegit add <specific-file> - DON'T commit without reviewing changes -> Always review diffs first
- DON'T skip writing tests for new functionality -> Write tests for changes
- DON'T use
git commit --amendunless explicitly asked -> Create new commits
- DON'T reinvent existing functionality -> Search codebase and reuse patterns
- DON'T add unnecessary abstractions -> Build only what's needed now (YAGNI)
- DON'T return 3+ values in unnamed tuples -> Use Pydantic models with named fields
- DON'T mix models with business logic -> Separate concerns clearly
- DON'T import globally -> Use dependency injection
- DON'T add "magic" behavior (guessing, parsing, fallbacks) -> Explicit configuration only
- DON'T skip planning phase -> Use plan mode and complete planning checklist
- DON'T ignore existing libraries -> Use LiteLLM, Pydantic, instructor, etc.
- DON'T do more than asked -> Do what's asked, nothing more, nothing less