Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
167 commits
Select commit Hold shift + click to select a range
860ef87
adapting camera to new ultra
vim-sroberge Jan 20, 2026
4301fa7
checkpoint, screenshot works
vim-sroberge Feb 2, 2026
3bc38da
red geometry
vim-sroberge Feb 2, 2026
6e8403d
position shader
vim-sroberge Feb 2, 2026
a98cb09
working readback
vim-sroberge Feb 2, 2026
af7206a
works
vim-sroberge Feb 2, 2026
1144c35
gpu picking
vim-sroberge Feb 2, 2026
167c84e
gpu picker
vim-sroberge Feb 3, 2026
36e1c23
gpu picking integration
vim-sroberge Feb 3, 2026
8201e81
back to normal main
vim-sroberge Feb 3, 2026
b65b644
multivim support
vim-sroberge Feb 3, 2026
59fce71
claude md update
vim-sroberge Feb 3, 2026
9255879
working with normals
vim-sroberge Feb 3, 2026
f944531
int packing as float
vim-sroberge Feb 3, 2026
61aa9e4
clean up
vim-sroberge Feb 3, 2026
db2ca2e
packing at build time
vim-sroberge Feb 3, 2026
2d36c48
claude md
vim-sroberge Feb 3, 2026
ccbe3dd
updated and unified vim collection
vim-sroberge Feb 3, 2026
3462d88
vim index provided by loading pipeline
vim-sroberge Feb 3, 2026
2155f23
open vs load
vim-sroberge Feb 3, 2026
7099990
async queue instead of promise loop
vim-sroberge Feb 4, 2026
b48f44e
simplifying load
vim-sroberge Feb 4, 2026
2b2be76
removed open
vim-sroberge Feb 4, 2026
f240d09
cleaning up load result
vim-sroberge Feb 4, 2026
6c9c64d
load cleanup
vim-sroberge Feb 4, 2026
7cacb80
common progress
vim-sroberge Feb 4, 2026
53189e0
gizmo to match ultra
vim-sroberge Feb 5, 2026
74b0fa5
almost good camera detached orbit
vim-sroberge Feb 5, 2026
89391cb
zoom resets orbit target
vim-sroberge Feb 5, 2026
3156bd8
claude md
vim-sroberge Feb 5, 2026
6c2c0c8
type fix
vim-sroberge Feb 5, 2026
83b9c6b
fixed bb not beeing computed
vim-sroberge Feb 5, 2026
75d1dc4
fixed raycast result
vim-sroberge Feb 5, 2026
3a6c3c2
gpu pickable markers
vim-sroberge Feb 5, 2026
137bf0f
reemomved more vimx
vim-sroberge Feb 6, 2026
2ddcf00
removing more vimx
vim-sroberge Feb 6, 2026
1dbce29
cleaning up loading
vim-sroberge Feb 6, 2026
f635a27
fixd method call
vim-sroberge Feb 9, 2026
7d3b8b3
fixed delta time
vim-sroberge Feb 9, 2026
f8fb92b
removed debug for picking
vim-sroberge Feb 9, 2026
72c9acf
comments
vim-sroberge Feb 9, 2026
6c09fdf
documentation
vim-sroberge Feb 9, 2026
417f453
restoring free orbit mode
vim-sroberge Feb 9, 2026
bdf75fb
kinda works
vim-sroberge Feb 9, 2026
2b2d0f7
working
vim-sroberge Feb 10, 2026
4a1a59b
working orbit gizmo
vim-sroberge Feb 10, 2026
046471b
refactored extracted sphere coord
vim-sroberge Feb 10, 2026
9218a36
rotation
vim-sroberge Feb 11, 2026
6ab0472
camera api cleanup
vim-sroberge Feb 11, 2026
67ab06b
typo frustrum -> frustum
vim-sroberge Feb 11, 2026
e210c86
unified move
vim-sroberge Feb 11, 2026
625ab45
Ste distanc private
vim-sroberge Feb 11, 2026
1598449
rmoved lookat parametr
vim-sroberge Feb 11, 2026
f700225
reuse veectors
vim-sroberge Feb 11, 2026
ff5a30b
docs
vim-sroberge Feb 11, 2026
6643b29
made rotation x horizontal, y vertical
vim-sroberge Feb 11, 2026
862a6cc
refactor
vim-sroberge Feb 11, 2026
3146516
missing lock
vim-sroberge Feb 11, 2026
1e1aa1e
reset orbit outside screen
vim-sroberge Feb 11, 2026
003fd3c
orbit target on slect
vim-sroberge Feb 11, 2026
a423247
floating target after reset
vim-sroberge Feb 11, 2026
42ebb7b
keep last speed upon reset
vim-sroberge Feb 11, 2026
c6e5b31
fixed framing
vim-sroberge Feb 11, 2026
42225b3
faster scroll
vim-sroberge Feb 11, 2026
56fa0e3
restored touch
vim-sroberge Feb 11, 2026
4af8690
working touch
vim-sroberge Feb 11, 2026
9fe1a16
send bothh pinch and pan
vim-sroberge Feb 11, 2026
0bbda33
better touch
vim-sroberge Feb 12, 2026
7480d25
input constants
vim-sroberge Feb 12, 2026
068e36e
rmovd input allocations
vim-sroberge Feb 12, 2026
b0b8316
dev docs
vim-sroberge Feb 12, 2026
20fd93a
removed spacebar bindings
vim-sroberge Feb 12, 2026
3173d53
input folder and refactors
vim-sroberge Feb 12, 2026
8314178
small fixes
vim-sroberge Feb 12, 2026
e6f0321
some tmp vctors
vim-sroberge Feb 12, 2026
8255601
polish
vim-sroberge Feb 12, 2026
0ac49ed
moved code in the if
vim-sroberge Feb 12, 2026
9b64dec
return progress on signal
vim-sroberge Feb 12, 2026
f2c061f
documentation
vim-sroberge Feb 12, 2026
3a449e8
dont create empty meshes
vim-sroberge Feb 12, 2026
0f7b028
cleaning up g3dsubset
vim-sroberge Feb 12, 2026
854e288
array maps
vim-sroberge Feb 12, 2026
b88da18
working colors
vim-sroberge Feb 12, 2026
b51a92c
color in a teexture
vim-sroberge Feb 13, 2026
6fdddb3
faster eelement mapping
vim-sroberge Feb 13, 2026
67ca0e9
fasteer geomry
vim-sroberge Feb 13, 2026
9ed766d
fastr bb
vim-sroberge Feb 13, 2026
f601bea
bb inverted
vim-sroberge Feb 13, 2026
1f53b93
optims
vim-sroberge Feb 13, 2026
2ce41e9
numeric color key
vim-sroberge Feb 13, 2026
a3debbe
g3dsubset and offseet
vim-sroberge Feb 13, 2026
d33b08c
optims
vim-sroberge Feb 13, 2026
8c27098
optims
vim-sroberge Feb 13, 2026
fe92afa
material set
vim-sroberge Feb 13, 2026
7057934
fix boken mesh
vim-sroberge Feb 13, 2026
fb91c6e
working
vim-sroberge Feb 13, 2026
bc0595a
material api
vim-sroberge Feb 13, 2026
b327534
pretty good
vim-sroberge Feb 14, 2026
23748eb
transpareency sction
vim-sroberge Feb 14, 2026
f1d8a42
tmp tst
vim-sroberge Feb 14, 2026
1583f08
texelFetch and wblgl2
vim-sroberge Feb 15, 2026
fc69674
outline optims
vim-sroberge Feb 16, 2026
7b877f7
rendring optims
vim-sroberge Feb 16, 2026
fa7a11c
rendr optims
vim-sroberge Feb 16, 2026
abb94e7
claud stuff
vim-sroberge Feb 16, 2026
218e7ee
removeed dead code
vim-sroberge Feb 16, 2026
80e4fa0
fixeed typo
vim-sroberge Feb 16, 2026
a1caf01
reemovd unused code
vim-sroberge Feb 16, 2026
2bafb64
removed ignore material
vim-sroberge Feb 16, 2026
296f262
removed skybox
vim-sroberge Feb 16, 2026
00ed217
remove aa setting from outline and renderer
vim-sroberge Feb 16, 2026
f4a8def
removed environement
vim-sroberge Feb 16, 2026
37b2fce
cache for modelMaterial arrays
vim-sroberge Feb 16, 2026
4cad79e
nwe pattern for wrapper, inner three elemeent called three
vim-sroberge Feb 16, 2026
7956496
moree wrapper -> three
vim-sroberge Feb 16, 2026
8986658
outline api wording
vim-sroberge Feb 16, 2026
a279504
updated comments
vim-sroberge Feb 16, 2026
a019be5
comment
vim-sroberge Feb 16, 2026
8a09708
commnt
vim-sroberge Feb 16, 2026
128eb37
commnt
vim-sroberge Feb 16, 2026
f891a48
-1 instead of undefined smallGhost
vim-sroberge Feb 16, 2026
ad989cd
named layer
vim-sroberge Feb 16, 2026
5fc4bdd
removed mem traciking
vim-sroberge Feb 16, 2026
6e01e03
papercuts
vim-sroberge Feb 16, 2026
314d115
simplified outline a bit
vim-sroberge Feb 16, 2026
c0ea6e2
fixed double click
vim-sroberge Feb 17, 2026
6baac89
moved claude stuff
vim-sroberge Feb 17, 2026
7b44d01
removed dead code
vim-sroberge Feb 17, 2026
a8ae7b1
cleaning public api
vim-sroberge Feb 17, 2026
c0f1173
material api clean up
vim-sroberge Feb 17, 2026
2509f89
material api cleanup
vim-sroberge Feb 17, 2026
c6ab758
selectable interface
vim-sroberge Feb 17, 2026
8bf36e3
interface renames
vim-sroberge Feb 18, 2026
cce63a2
input api cleanup
vim-sroberge Feb 18, 2026
5764b7e
vim interface
vim-sroberge Feb 18, 2026
41644e8
fixed barrels
vim-sroberge Feb 18, 2026
1c87586
claude md
vim-sroberge Feb 18, 2026
ba1652e
cleaning exports
vim-sroberge Feb 18, 2026
7040bb3
element3d interface
vim-sroberge Feb 18, 2026
7525158
barrel clean up
vim-sroberge Feb 18, 2026
6b4c9f3
api tightening
vim-sroberge Feb 18, 2026
c4618a0
tightening api
vim-sroberge Feb 18, 2026
cc743a8
IMarker
vim-sroberge Feb 18, 2026
b5b5f62
private gizmo
vim-sroberge Feb 18, 2026
6213a4d
gizmo cleanup
vim-sroberge Feb 18, 2026
c0afbe9
skills
vim-sroberge Feb 19, 2026
452e490
skill
vim-sroberge Feb 19, 2026
f3a3a6b
tighter barrels for react viewer
vim-sroberge Feb 19, 2026
820ce33
react viewer api
vim-sroberge Feb 19, 2026
eb94990
ultra api
vim-sroberge Feb 19, 2026
2a08771
api cleanup
vim-sroberge Feb 19, 2026
ad68fef
internal
vim-sroberge Feb 20, 2026
427c56a
tighter ultra api
vim-sroberge Feb 20, 2026
e916f65
unique names for classes for cleaner api bundle
vim-sroberge Feb 20, 2026
f4b4786
interfaces to hide viewers
vim-sroberge Feb 20, 2026
344655b
standard icons
vim-sroberge Feb 20, 2026
374e052
camera api
vim-sroberge Feb 23, 2026
ad6f522
signal interface and ultra cleanup
vim-sroberge Feb 23, 2026
3e96426
docs
vim-sroberge Feb 23, 2026
b1155bf
react.camera -> react.framing
vim-sroberge Feb 23, 2026
cb51eac
react util cleanup
vim-sroberge Feb 23, 2026
f058be0
small improvements
vim-sroberge Feb 23, 2026
b21369d
small fixes
vim-sroberge Feb 23, 2026
1fdf554
docs
vim-sroberge Feb 23, 2026
ccfe642
done ?
vim-sroberge Feb 23, 2026
1dd1130
readme
vim-sroberge Feb 23, 2026
1c2f9f0
updated color overrides
vim-sroberge Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
720 changes: 720 additions & 0 deletions .claude/docs/INPUT.md

Large diffs are not rendered by default.

203 changes: 203 additions & 0 deletions .claude/docs/RENDERING_OPTIMIZATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# WebGL Shader Materials & Rendering Patterns

This document describes the shader material architecture and rendering patterns used in the WebGL viewer. All materials use GLSL ES 3.0 (WebGL 2).

## Material Architecture

The `Materials` singleton (`materials.ts`) owns and manages all material instances. It exposes a public `IMaterials` interface for property configuration and keeps system materials (mask, outline, merge) internal.

### Material Inventory

| Material | File | Purpose | GLSL Version |
|----------|------|---------|--------------|
| **StandardMaterial** | `standardMaterial.ts` | Default opaque/transparent rendering. Patches Three.js Lambert shader via `onBeforeCompile` to inject palette coloring, visibility, and section strokes. | GLSL1 (Three.js managed) |
| **ModelMaterial** | `modelMaterial.ts` | Fast rendering mode. Custom shader using screen-space derivative normals and pre-normalized lighting. | GLSL3 |
| **GhostMaterial** | `ghostMaterial.ts` | Transparent fill for hidden/ghosted elements in isolation mode. | GLSL3 |
| **PickingMaterial** | `pickingMaterial.ts` | GPU object picking. Outputs packed element ID, depth, and surface normal to Float32 render target. | GLSL3 |
| **MaskMaterial** | `maskMaterial.ts` | Selection mask pass. Writes depth only for selected elements; non-selected vertices are clipped. | GLSL3 |
| **OutlineMaterial** | `outlineMaterial.ts` | Post-process edge detection on depth buffer. Outputs outline intensity to RedFormat texture. | GLSL3 |
| **MergeMaterial** | `mergeMaterial.ts` | Final compositing pass. Blends scene texture with outline texture using configurable color. | GLSL3 |
| **TransferMaterial** | `transferMaterial.ts` | Simple texture passthrough (blit). | GLSL3 |

### MaterialSet

`MaterialSet` (`materialSet.ts`) groups materials by role: `opaque`, `transparent`, and `hidden` (ghost). The `applyMaterial()` helper in `materials.ts` resolves a `MaterialSet` into the correct `THREE.Material` or `[visible, hidden]` array for a given mesh based on its `userData.transparent` flag.

### Color Palette System

All scene materials (StandardMaterial, ModelMaterial) use a shared color palette texture for submesh coloring. The `Materials` singleton owns a single 128x128 RGBA `DataTexture` (16,384 colors max) and distributes it to all materials via `setSubmeshColorTexture()`.

The palette is built by `colorPalette.ts`:
- Extracts unique colors from submesh material data
- If unique colors exceed 16,384, applies uniform quantization (25 levels per channel = 15,625 max)
- Packs into a `Float32Array` for texture upload

Shaders look up colors using `texelFetch` with integer coordinates derived from a per-vertex `submeshIndex` attribute:

```glsl
int x = int(submeshIndex) % 128;
int y = int(submeshIndex) / 128;
vColor = texelFetch(submeshColorTexture, ivec2(x, y), 0).rgb;
```

Instance color overrides are blended using the `colored` attribute (1 = instance color, 0 = palette color):

```glsl
#ifdef USE_INSTANCING
vColor = colored * instanceColor + (1.0 - colored) * vColor;
#endif
```

---

## Shader Patterns

### Pre-Normalized Light Direction (ModelMaterial)

The light direction is a compile-time constant, avoiding per-fragment `normalize()`:

```glsl
// (sqrt(2), sqrt(3), sqrt(5)) normalized: magnitude = sqrt(10)
const vec3 LIGHT_DIR = vec3(0.447214, 0.547723, 0.707107);
float light = dot(normal, LIGHT_DIR);
light = 0.5 + (light * 0.5); // Remap to [0.5, 1.0]
```

### Pre-Divided Opacity (GhostMaterial)

The ghost opacity uniform stores the final shader value directly (`7/255 = 0.0275`), so the fragment shader uses it as-is without per-fragment division:

```glsl
fragColor = vec4(fillColor, opacity);
```

The `GhostMaterial` class getter/setter expose the raw value without conversion.

### Vertex Shader Early Culling

All visibility-aware materials (Ghost, Model, Mask, Picking) use the same pattern to cull invisible geometry in the vertex shader by placing vertices behind the near plane:

```glsl
if (ignore > 0.0) {
gl_Position = vec4(0.0, 0.0, -2.0, 1.0);
return;
}
```

This is faster than fragment `discard` because no fragments are generated for clipped triangles.

### Static Temp Vector Reuse (PickingMaterial)

The `PickingMaterial` class uses a static `THREE.Vector3` for camera direction updates, avoiding per-frame allocations:

```typescript
private static _tempDir = new THREE.Vector3()

updateCamera(camera: THREE.Camera): void {
camera.getWorldDirection(PickingMaterial._tempDir)
this.three.uniforms.uCameraPos.value.copy(camera.position)
this.three.uniforms.uCameraDir.value.copy(PickingMaterial._tempDir)
}
```

### Picking Output Format

The picking material encodes four values into a Float32 RGBA render target:

| Channel | Value | Encoding |
|---------|-------|----------|
| R | Element ID | `uintBitsToFloat(packedId)` where `packedId = (vimIndex << 24) \| elementIndex` |
| G | Depth | Distance along camera direction (0 = miss) |
| B | Normal X | Screen-space derivative normal |
| A | Normal Y | Screen-space derivative normal |

Normal Z is reconstructed as `sqrt(1 - x^2 - y^2)`, always positive since the normal faces the camera.

### Section Stroke Rendering (StandardMaterial)

The standard material renders colored strokes where geometry intersects clipping planes. The stroke width scales with fragment depth using a configurable falloff exponent:

```glsl
float thick = pow(vFragDepth, sectionStrokeFalloff) * sectionStrokeWidth;
```

Perpendicular surfaces are excluded by comparing the surface normal against the clipping plane normal.

---

## Shader Optimization Principles

### General Rules

1. **Move computations out of shaders** whenever possible:
- Constants: Pre-compute in JavaScript or as shader `const`
- Per-frame: Compute in uniforms
- Per-vertex: Keep in vertex shader
- Per-fragment: Only when necessary

2. **Avoid per-fragment operations:**
- `normalize()` on constants
- Divisions (use pre-multiplied reciprocals)
- Expensive math functions (`sqrt`, `pow`, `sin`, `cos`)
- Prefer simple arithmetic (`+`, `-`, `*`)
- Prefer dot products, cross products
- Texture lookups are GPU-cached and relatively cheap

3. **Memory access patterns:**
- `texelFetch()` for indexed access (faster than `texture()` when no filtering needed)
- `uniform` reads are GPU-cached
- `in` (varying) interpolation cost depends on geometry complexity

4. **Branching:**
- Early returns in vertex shader skip all subsequent work
- Fragment shader branches may execute both paths on GPU (warp divergence)

### Relative Cost of Operations

| Operation | Cost | Example |
|-----------|------|---------|
| Uniform read | 1x | `uniform float value` |
| Texture fetch | 2-4x | `texture(sampler, uv)` |
| Addition/Subtraction | 1x | `a + b` |
| Multiplication | 1x | `a * b` |
| Division | 3-5x | `a / b` |
| `normalize()` | 10-15x | `sqrt` + 3 divides |
| `sin()`, `cos()` | 8-12x | Approximated in hardware |

---

## GLSL3 Syntax Reference

All custom shader materials use `glslVersion: THREE.GLSL3`. The StandardMaterial uses Three.js managed GLSL1 (via `onBeforeCompile` patching).

| GLSL1 | GLSL3 |
|-------|-------|
| `attribute` | `in` (vertex shader) |
| `varying` | `out` (vertex), `in` (fragment) |
| `gl_FragColor` | `out vec4 fragColor` |
| `texture2D()` | `texture()` |
| N/A | `texelFetch()` for indexed access |

---

## References

### Material Files

- `src/vim-web/core-viewers/webgl/loader/materials/standardMaterial.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/modelMaterial.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/ghostMaterial.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/pickingMaterial.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/maskMaterial.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/outlineMaterial.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/mergeMaterial.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/transferMaterial.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/materials.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/materialSet.ts`
- `src/vim-web/core-viewers/webgl/loader/materials/colorPalette.ts`

### Related Documentation

- [CLAUDE.md](../../CLAUDE.md) - Main project documentation
- [INPUT.md](./INPUT.md) - Input system architecture
- [optimization.md](./optimization.md) - Loading pipeline performance
143 changes: 143 additions & 0 deletions .claude/docs/optimization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# VIM Loading Performance

How the WebGL loading pipeline works and how to profile it.

## Loading Phases

VIM file loading consists of four sequential phases:

1. **Network/Parsing** - Fetch and parse BFast container (G3d geometry, VimDocument, ElementMapping)
2. **Geometry Building** - Create Three.js meshes from G3d data (primary optimization target)
3. **GPU Upload** - Transfer geometry buffers to GPU
4. **Rendering** - First frame render

## Mesh Building Pipeline

```
VimMeshFactory.add(G3dSubset)
|-- Split by instance count: <=5 instances -> merged, >5 -> instanced
|-- Merged path (InsertableMeshFactory)
| |-- G3dSubset.chunks(16M indices) -- chunk large meshes
| |-- Create InsertableMesh per chunk
| |-- insertFromG3d() -- bake matrices, build geometry
| +-- scene.addMesh() -- register submeshes
+-- Instanced path (InstancedMeshFactory)
|-- Create THREE.InstancedMesh per unique geometry
|-- setMatrices() -- GPU instancing matrices
+-- scene.addMesh() -- register submeshes
```

**Chunk Size**: 16M indices (not vertices) per merged mesh. This threshold was chosen because GPU picking eliminates the need for CPU raycast traversal, removing the constraint that kept chunk sizes small.

## Current Performance Patterns

### Lazy Element3D Creation

Element3D objects are **not** created during mesh loading. When `scene.addMesh()` is called, it only builds an `instance -> submesh[]` map. Element3D objects are created lazily on first access via `vim.getElement()` or `vim.getElementFromIndex()`.

This avoids constructing thousands of full Element3D objects during the loading hot path when most will never be accessed.

**Key file**: `src/vim-web/core-viewers/webgl/loader/scene.ts` -- `registerSubmesh()` only populates `_instanceToMeshes`.

### Buffer Reuse in Hot Loops

In `InsertableGeometry.insertFromG3d()`, which iterates over every vertex and index in merged meshes:

- **Matrix buffer**: A single `Float32Array(16)` is allocated once outside the per-instance loop, then reused for each instance's transform matrix. This avoids per-instance allocation in a loop that runs thousands of times.
- **Color index hoisting**: The `submeshColor` lookup (`g3d.submeshColor[sub]`) is hoisted out of the inner per-index loop and computed once per submesh.
- **Direct array access**: Typed array references (`indices`, `submeshIndices`) are assigned once outside loops to avoid repeated property lookups.

**Key file**: `src/vim-web/core-viewers/webgl/loader/progressive/insertableGeometry.ts`

### Color Palette System

The color palette optimization reduces GPU memory by replacing per-vertex color attributes with a texture-based lookup. It is **always enabled**.

Two components:
1. **`submeshColor: Uint16Array`** - Always present. Maps each submesh index to a color palette index.
2. **`colorPalette: Float32Array | undefined`** - A flat RGB array of unique colors, used as a texture. Set to `undefined` only if the model exceeds 16,384 unique colors (128x128 texture limit).

If a model has too many unique colors, quantization is applied (25 levels per channel, yielding up to 15,625 distinct colors) to bring the palette within limits. If quantization still exceeds the limit, the palette is disabled (`undefined`) and the shader falls back to per-vertex colors.

**Key files**:
- `src/vim-web/core-viewers/webgl/loader/materials/colorPalette.ts` - Palette building with quantization
- `src/vim-web/core-viewers/webgl/loader/progressive/mappedG3d.ts` - `MappedG3d` type definition
- `src/vim-web/core-viewers/webgl/loader/progressive/insertableGeometry.ts` - `submeshColor` lookup in geometry building

## How to Profile

### Timing Instrumentation Pattern

Add cumulative timing to identify hotspots. This pattern collects timing across many calls (e.g., thousands of mesh builds) rather than measuring a single call:

```typescript
class SomeFactory {
private static _cumulativeTiming = {
phase1: 0,
phase2: 0,
calls: 0
}

someMethod() {
const t0 = performance.now()
// ... phase 1 work
const t1 = performance.now()
SomeFactory._cumulativeTiming.phase1 += t1 - t0

// ... phase 2 work
const t2 = performance.now()
SomeFactory._cumulativeTiming.phase2 += t2 - t1

SomeFactory._cumulativeTiming.calls++
}

static logAndResetTiming(label: string) {
const t = SomeFactory._cumulativeTiming
console.log(`[SomeFactory] ${label} breakdown (${t.calls} calls):`)
console.log(` Phase 1: ${t.phase1.toFixed(2)}ms`)
console.log(` Phase 2: ${t.phase2.toFixed(2)}ms`)
// Reset for next batch
t.phase1 = 0
t.phase2 = 0
t.calls = 0
}
}
```

### Profiling Steps

1. **Add timing instrumentation** using the cumulative pattern above
2. **Look for gaps** - If outer timing is much larger than the sum of inner phases, the overhead between phases is the real bottleneck
3. **Use Chrome DevTools** - Performance tab, look for long tasks and GC pauses
4. **Test on real models** - Performance characteristics vary significantly by model size and complexity
5. **Compare before/after** - Always measure the impact of changes

## Optimization Principles

### What to Optimize

1. **Eliminate unnecessary work** - Deferring or skipping work entirely yields the largest gains (e.g., lazy Element3D creation)
2. **Hot loops** - Code executed millions of times (vertex/index loops in geometry building)
3. **Allocations in loops** - Reuse buffers, minimize GC pressure
4. **Cache locality** - Copy data to local arrays before tight loops

### What NOT to Optimize (Diminishing Returns)

1. **Three.js internals** - `BufferGeometry` creation, `computeBoundingBox()` are already well-optimized
2. **One-time operations** - Setup code executed once per mesh type
3. **Already-fast code** - Operations under a few milliseconds with no clear improvement path
4. **Micro-optimizations without measurement** - Always measure before and after

### Red Flags to Investigate

- **Outer timing >> inner timing** - Indicates hidden overhead between instrumented phases
- **Per-item overhead > 0.1ms** - For operations on thousands of items, even small overhead compounds
- **Unexpected allocations** - Check Chrome DevTools Performance tab for GC pauses during geometry building

## References

- **Loading Pipeline**: [CLAUDE.md -- Loading Pipeline](../../CLAUDE.md)
- **Mesh Building**: `src/vim-web/core-viewers/webgl/loader/progressive/vimMeshFactory.ts`
- **Scene Management**: `src/vim-web/core-viewers/webgl/loader/scene.ts`
- **Element3D**: `src/vim-web/core-viewers/webgl/loader/element3d.ts`
- **Color Palette**: `src/vim-web/core-viewers/webgl/loader/materials/colorPalette.ts`
Loading