SceneObserver Class
The SceneObserver class tracks changes to a Three.js scene graph and provides snapshot functionality for scene inspection. It's the core mechanism behind 3Lens scene tracking.
Overview
import { SceneObserver } from '@3lens/core';
const observer = new SceneObserver(scene, {
onSceneChange: () => console.log('Scene changed'),
onResourceEvent: (event) => console.log('Resource event:', event),
});
// Get a snapshot of the scene tree
const rootNode = observer.createSceneNode(scene);2
3
4
5
6
7
8
9
Internal API
SceneObserver is used internally by DevtoolProbe. In most cases, you should use probe.observeScene() instead of creating observers directly.
API Reference
Constructor
new SceneObserver(scene: THREE.Scene, options?: SceneObserverOptions)Parameters:
| Parameter | Type | Description |
|---|---|---|
scene | THREE.Scene | The Three.js scene to observe |
options | SceneObserverOptions | Optional configuration |
SceneObserverOptions:
interface SceneObserverOptions {
/** Called when the scene graph changes (objects added/removed) */
onSceneChange?: () => void;
/** Called when resource lifecycle events occur */
onResourceEvent?: (event: ResourceLifecycleEvent) => void;
}2
3
4
5
6
7
Methods
createSceneNode
Creates a snapshot of the scene graph starting from a given object.
createSceneNode(obj: THREE.Object3D): SceneNodeReturns: SceneNode - Complete tree structure with transform, visibility, and type-specific data.
getObjectRef
Gets the tracked reference for a Three.js object.
getObjectRef(obj: THREE.Object3D): TrackedObjectRef | nullfindObjectByDebugId
Finds a Three.js object by its stable debug ID.
findObjectByDebugId(debugId: string): THREE.Object3D | nullcollectMaterials
Collects all materials from the scene with usage information.
collectMaterials(): {
materials: MaterialData[];
summary: MaterialsSummary
}2
3
4
collectGeometries
Collects all geometries from the scene with memory estimates.
collectGeometries(): {
geometries: GeometryData[];
summary: GeometrySummary
}2
3
4
collectTextures
Collects all textures with material usage tracking.
collectTextures(): {
textures: TextureData[];
summary: TexturesSummary
}2
3
4
collectRenderTargets
Collects render targets (shadow maps, post-processing buffers).
collectRenderTargets(): {
renderTargets: RenderTargetData[];
summary: RenderTargetsSummary
}2
3
4
findMaterialByUuid
Finds a material by its Three.js UUID.
findMaterialByUuid(uuid: string): THREE.Material | nullfindGeometryByUuid
Finds a geometry by its Three.js UUID.
findGeometryByUuid(uuid: string): THREE.BufferGeometry | nullfindTextureByUuid
Finds a texture by its Three.js UUID.
findTextureByUuid(uuid: string): THREE.Texture | nullgetLifecycleTracker
Gets the resource lifecycle tracker for this observer.
getLifecycleTracker(): ResourceLifecycleTrackerdispose
Cleans up the observer and removes scene patches.
dispose(): voidHow It Works
Scene Patching
The observer patches the scene's add() and remove() methods to track changes:
┌─────────────────────────────────────────────────────────────┐
│ Scene Method Patching │
├─────────────────────────────────────────────────────────────┤
│ │
│ scene.add(object) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ SceneObserver │ │
│ │ intercepts call │ │
│ └────────┬────────┘ │
│ │ │
│ ├──────> Track new object (create TrackedRef) │
│ ├──────> Traverse children (recursive tracking) │
│ ├──────> Fire onSceneChange callback │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Original add() │ │
│ │ called │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Object ID System
Each object gets two identifiers:
| ID Type | Source | Purpose |
|---|---|---|
threeUuid | object.uuid | Three.js internal ID |
debugId | Generated | Stable ID for devtools |
The debugId is stable across scene updates while threeUuid can change if objects are recreated.
Usage Examples
Basic Scene Observation
const observer = new SceneObserver(scene, {
onSceneChange: () => {
// Rebuild UI tree when scene changes
const rootNode = observer.createSceneNode(scene);
updateSceneTreeUI(rootNode);
},
});2
3
4
5
6
7
Resource Collection
// Get all materials with usage stats
const { materials, summary } = observer.collectMaterials();
console.log(`Total materials: ${summary.totalCount}`);
console.log(`Shader materials: ${summary.shaderMaterialCount}`);
console.log(`Transparent: ${summary.transparentCount}`);
// Find meshes using each material
materials.forEach((mat) => {
console.log(`${mat.name}: used by ${mat.usedByMeshIds.length} meshes`);
});2
3
4
5
6
7
8
9
10
11
Finding Objects
// Find by debug ID (from UI selection)
const debugId = 'obj_abc123xyz';
const object = observer.findObjectByDebugId(debugId);
if (object) {
// Manipulate the object
object.visible = !object.visible;
}2
3
4
5
6
7
8
Lifecycle Event Tracking
const observer = new SceneObserver(scene, {
onResourceEvent: (event) => {
switch (event.type) {
case 'created':
console.log(`${event.resourceType} created:`, event.uuid);
break;
case 'disposed':
console.log(`${event.resourceType} disposed:`, event.uuid);
break;
case 'orphaned':
console.warn(`Potential leak - ${event.resourceType}:`, event.uuid);
break;
}
},
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
Performance Considerations
Traversal Cost
Scene traversal is O(n) where n is the number of objects. For large scenes:
| Objects | Traversal Time |
|---|---|
| 100 | ~0.1ms |
| 1,000 | ~1ms |
| 10,000 | ~10ms |
| 100,000 | ~100ms |
Caching Strategy
The observer caches object references to avoid recreation:
// First call - creates new TrackedObjectRef
const ref1 = observer.getObjectRef(mesh); // ~0.01ms
// Subsequent calls - returns cached ref
const ref2 = observer.getObjectRef(mesh); // ~0.001ms
ref1 === ref2; // true (same reference)2
3
4
5
6
7
Memory Usage
| Tracking | Memory per Object |
|---|---|
| TrackedObjectRef | ~200 bytes |
| Full SceneNode | ~1-2 KB |
| With thumbnails | ~10-50 KB |
Integration with DevtoolProbe
The probe manages observer lifecycle:
// Internal probe implementation
class DevtoolProbe {
private _sceneObservers: Map<THREE.Scene, SceneObserver> = new Map();
observeScene(scene: THREE.Scene) {
if (this._sceneObservers.has(scene)) return;
const observer = new SceneObserver(scene, {
onSceneChange: () => this._notifySceneChange(scene),
onResourceEvent: (e) => this._handleResourceEvent(e),
});
this._sceneObservers.set(scene, observer);
}
unobserveScene(scene: THREE.Scene) {
const observer = this._sceneObservers.get(scene);
if (observer) {
observer.dispose();
this._sceneObservers.delete(scene);
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Related
- SceneNode Interface - Scene tree structure
- TrackedObjectRef - Object reference interface
- Scene Path Computation - How paths are calculated
- observeScene() - High-level scene observation