# Pascal Editor Scene Registry for Fast Three.js Object Lookups: A Deep Dive

> Discover how the Pascal Editor scene registry provides fast Three.js object lookups using a Map and type-specific Sets, eliminating slow scene traversals and boosting performance.

- Repository: [Pascal/editor](https://github.com/pascalorg/editor)
- Tags: deep-dive
- Published: 2026-03-25

---

**The Pascal Editor scene registry eliminates costly Three.js scene traversals by maintaining a constant-time Map (`nodes`) and type-specific Sets (`byType`) for instant object lookups.**

The `pascalorg/editor` repository implements a specialized scene registry that bridges the editor's node-based data model with Three.js rendering. This lightweight, in-memory indexing system replaces recursive scene-graph walks with O(1) hash lookups, enabling real-time performance even when managing thousands of architectural meshes.

## Core Architecture and Implementation

The registry implementation lives in [`packages/core/src/hooks/scene-registry/scene-registry.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/hooks/scene-registry/scene-registry.ts) and exposes a singleton object that tracks every rendered mesh by its node ID.

### The Registry Data Structure

The `sceneRegistry` object maintains two complementary indexes:

```typescript
// 📁 packages/core/src/hooks/scene-registry/scene-registry.ts
export const sceneRegistry = {
  // Master lookup: ID → Object3D
  nodes: new Map<string, THREE.Object3D>(),

  // Categorised lookups: Type → Set<ID>
  byType: {
    site: new Set<string>(),
    building: new Set<string>(),
    ceiling: new Set<string>(),
    level: new Set<string>(),
    wall: new Set<string>(),
    item: new Set<string>(),
    slab: new Set<string>(),
    zone: new Set<string>(),
    roof: new Set<string>(),
    'roof‑segment': new Set<string>(),
    scan: new Set<string>(),
    guide: new Set<string>(),
    window: new Set<string>(),
    door: new Set<string>(),
  },

  /** Remove all entries – called when a scene is unloaded. */
  clear() {
    this.nodes.clear()
    for (const set of Object.values(this.byType)) {
      set.clear()
    }
  },
}

```

The `nodes` Map provides immediate access to any `Object3D` via its string ID, while `byType` organizes IDs into typed Sets for efficient bulk operations on specific categories like walls, slabs, or roofs.

### Automatic Lifecycle with useRegistry

The `useRegistry` React hook handles registration and cleanup automatically when components mount and unmount:

```typescript
export function useRegistry(
  id: string,
  type: keyof typeof sceneRegistry.byType,
  ref: React.RefObject<THREE.Object3D>,
) {
  useLayoutEffect(() => {
    const obj = ref.current
    if (!obj) return

    // 1️⃣ add to master map
    sceneRegistry.nodes.set(id, obj)

    // 2️⃣ add to type‑specific set
    sceneRegistry.byType[type].add(id)

    // 3️⃣ cleanup on unmount
    return () => {
      sceneRegistry.nodes.delete(id)
      sceneRegistry.byType[type].delete(id)
    }
  }, [id, type, ref])
}

```

This pattern prevents stale references by ensuring every registered object removes itself from both indexes when its React component unmounts.

## Performance Benefits for Three.js Applications

Traditional Three.js selection requires traversing the entire scene graph recursively to find objects by user data or UUID. The Pascal Editor scene registry replaces this O(n) operation with direct Map access:

- **Single object retrieval**: `sceneRegistry.nodes.get(nodeId)` executes in constant time regardless of scene complexity.
- **Type-filtered iteration**: `sceneRegistry.byType.wall` contains only wall IDs, allowing systems to process subsets without filtering irrelevant meshes.
- **Memory efficiency**: The registry stores only string IDs in Sets rather than duplicate object references, minimizing overhead.

This design is essential for interactive features like ray-casting, gizmos, and real-time highlighting that must resolve node IDs to meshes within a single frame budget.

## Practical Implementation Examples

### Registering a Mesh in a Custom Renderer

Every renderer component in Pascal Editor uses `useRegistry` to publish its Three.js object:

```tsx
import { useRef } from 'react'
import { useRegistry } from '@pascal-app/core'
import type * as THREE from 'three'

export function CustomBox({ nodeId }: { nodeId: string }) {
  const meshRef = useRef<THREE.Mesh>(null!)

  // Register under the “item” bucket
  useRegistry(nodeId, 'item', meshRef)

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  )
}

```

When this component mounts, the mesh enters both `sceneRegistry.nodes` and `sceneRegistry.byType.item`. Unmounting automatically triggers cleanup to prevent memory leaks.

### Retrieving Objects for Tools and Systems

Systems throughout the editor import the registry directly to access meshes:

```ts
import { sceneRegistry } from '@pascal-app/core'

// Find the mesh for a given node id
export function getMesh(nodeId: string): THREE.Object3D | undefined {
  return sceneRegistry.nodes.get(nodeId)
}

// Example: move the mesh up by 0.5 m
const mesh = getMesh('wall‑123')
if (mesh) {
  mesh.position.y += 0.5
}

```

The selection manager in [`packages/viewer/src/components/viewer/selection-manager.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/components/viewer/selection-manager.tsx) uses this pattern to resolve hovered node IDs to their corresponding `Object3D` instances for visual feedback.

### Bulk Operations by Type

The type-specific indexes enable efficient batch processing without scene traversal:

```ts
import { sceneRegistry } from '@pascal-app/core'

export function highlightAllWalls() {
  sceneRegistry.byType.wall.forEach(wallId => {
    const mesh = sceneRegistry.nodes.get(wallId) as THREE.Mesh | undefined
    if (mesh) {
      mesh.material = new THREE.MeshBasicMaterial({ color: 'yellow' })
    }
  })
}

```

The wall cutout system in [`packages/viewer/src/systems/wall/wall-cutout.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/systems/wall/wall-cutout.tsx) iterates `sceneRegistry.byType.wall` to detect geometric changes and regenerate openings only for wall meshes.

## Key Integration Points in the Codebase

The registry serves as the authoritative source for mesh lookups across multiple packages:

- **[`packages/viewer/src/systems/wall/wall-cutout.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/systems/wall/wall-cutout.tsx)**: Iterates the wall Set to process cutout geometry updates.
- **[`packages/viewer/src/components/viewer/selection-manager.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/components/viewer/selection-manager.tsx)**: Resolves user clicks to Three.js objects via the master nodes Map.
- **[`packages/core/src/systems/wall/wall-system.tsx`](https://github.com/pascalorg/editor/blob/main/packages/core/src/systems/wall/wall-system.tsx)**: Accesses specific wall meshes directly by ID during geometry recomputation.
- **[`packages/editor/src/components/tools/window/window-tool.tsx`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/components/tools/window/window-tool.tsx)**: Reads mesh properties like position from the registry to calculate tool interactions.

## Summary

- The **scene registry** in `pascalorg/editor` maintains O(1) lookups via a master `Map` of node IDs to Three.js objects.
- **Type-specific Sets** in `sceneRegistry.byType` allow systems to iterate only relevant object categories (walls, slabs, etc.) without scene traversal.
- The **`useRegistry` hook** automatically manages object lifecycle, registering on mount and cleaning up on unmount to prevent memory leaks.
- **Direct imports** of `sceneRegistry` enable non-React systems like wall cutout processors and selection managers to access meshes instantly.
- This architecture replaces recursive Three.js scene walking with hash-based access, maintaining interactive frame rates even with thousands of architectural elements.

## Frequently Asked Questions

### How does the registry prevent memory leaks when objects are destroyed?

The `useRegistry` hook returns a cleanup function that executes automatically when the React component unmounts. This function calls `sceneRegistry.nodes.delete(id)` and `sceneRegistry.byType[type].delete(id)`, ensuring no stale references remain in the Map or Sets. Additionally, the `clear()` method removes all entries when an entire scene is unloaded.

### What node types are supported in the type registry?

The `byType` object supports architectural categories including `wall`, `slab`, `roof`, `window`, `door`, `building`, `level`, `zone`, `item`, `ceiling`, `site`, `scan`, `guide`, and `roof-segment`. Each type maintains its own `Set<string>` containing only the IDs of objects belonging to that category.

### How does this differ from Three.js's built-in scene graph traversal?

Three.js requires recursive traversal of the scene graph using `scene.traverse()` or `scene.getObjectById()`, which scales linearly (O(n)) with scene complexity. The Pascal Editor registry provides constant-time (O(1)) access via JavaScript's native `Map.get()` operation, and type-filtered access without iteration overhead by maintaining pre-sorted Sets.

### Can systems outside React components access the registry?

Yes. While `useRegistry` is designed for React components, any module can import the singleton `sceneRegistry` object directly from `@pascal-app/core` to read or modify entries. This allows imperative systems, Web Workers, or utility functions to perform fast lookups without React context.