Each ThreeDShapeElement instance owns exactly one isolated Three.js world: one WebGLRenderer, one Scene, one PerspectiveCamera and one set of OrbitControls. Shapes do not share any Three.js resources.
The design is documented directly in the source header of ThreeDShapeElement.ts, and it exists to guarantee three properties:
Two other approaches were considered and rejected:
| Consequence | |
|---|---|
| Pro | Complete isolation. Deleting a shape is a single dispose() call with no entanglement. |
| Pro | Independent dirty flags mean idle shapes consume zero GPU time. |
| Pro | No z-order or stacking conflicts between shapes. |
| Con | Each shape creates its own WebGL context. Browsers enforce a hard limit, typically 8-16 active contexts. Exceeding it causes the oldest context to be silently lost. |
The bridge between the 2D world and the 3D world is the DesignElement type. Every 2D element with type === "threed-shape" stores three optional 3D fields:
| Field | Type | Purpose |
|---|---|---|
| threeDMetadata | ThreeDMetadata | Serialized scene snapshot metadata for project save/load |
| threeDGeometryType | GeometryType | The primitive type the shape was created with |
| threeDSceneState | SceneStateSnapshot | Live scene state used for persistence and restoration |