Skip to main content

Overview

The scene state is managed by a Zustand store in @pascal-app/core that provides a centralized, reactive state container for all nodes in the 3D scene.

Store Structure

Location: packages/core/src/store/use-scene.ts:14-40

CRUD Operations

Create Node

Create a single node and optionally attach it to a parent:
The node is automatically:
  1. Added to the nodes dictionary
  2. Added to parent’s children array (if parentId provided)
  3. Marked as dirty for system processing
Location: packages/core/src/store/actions/node-actions.ts:5-52

Batch Create Nodes

Create multiple nodes efficiently:
Batch operations are more efficient than individual creates as they only trigger one state update and re-render.

Update Node

Update properties of an existing node:
The node is automatically:
  1. Updated in the nodes dictionary
  2. Marked as dirty for system processing
  3. Parent is also marked dirty (to update spatial relationships)
Location: packages/core/src/store/actions/node-actions.ts:54-109

Reparenting

Change a node’s parent by updating its parentId:
The update action handles:
  1. Removing node from old parent’s children array
  2. Adding node to new parent’s children array
  3. Marking both old and new parents as dirty

Delete Node

Delete a node and all its descendants:
The deletion:
  1. Recursively deletes all children
  2. Removes node from parent’s children array
  3. Removes node from nodes dictionary
  4. Removes node from rootNodeIds (if applicable)
  5. Triggers full scene re-validation (some deletions like slabs affect elevations)
Location: packages/core/src/store/actions/node-actions.ts:111-161
Deletion is recursive - deleting a wall will also delete all doors, windows, and items attached to it.

Dirty Nodes

The dirtyNodes set tracks which nodes need geometry updates. Systems check this set each frame and only recompute geometry for dirty nodes.

Automatic Marking

Nodes are automatically marked dirty when:
  • Created with createNode / createNodes
  • Updated with updateNode / updateNodes
  • Their parent is modified
  • Scene is loaded or imported
Location: packages/core/src/store/use-scene.ts:126-132

Manual Marking

You can manually mark nodes dirty when needed:
Systems are responsible for clearing the dirty flag after processing. If a node’s mesh isn’t registered yet, the system will keep it dirty for the next frame.

Middleware

The scene store uses two Zustand middleware layers:

Persistence Middleware

Automatically saves scene to IndexedDB for local persistence:
Location: packages/core/src/store/use-scene.ts:155-172

Transient Nodes

Nodes marked with metadata: { isTransient: true } are not persisted to IndexedDB:
Use transient nodes for preview geometry, guides, or temporary visual elements that shouldn’t be persisted.

Temporal Middleware (Undo/Redo)

Provides undo/redo functionality using Zundo:
Location: packages/core/src/store/use-scene.ts:147-153

Using Undo/Redo

Dirty Node Re-validation

After undo/redo, all nodes are marked dirty to trigger re-validation:
Location: packages/core/src/store/use-scene.ts:237-257

Loading and Clearing Scenes

Load Default Scene

Create a default scene hierarchy (Site → Building → Level):
Location: packages/core/src/store/use-scene.ts:90-124

Set Scene from External Data

Load a scene from external source (file, database, etc.):
This:
  1. Replaces current scene
  2. Applies backward compatibility patches (e.g., adding scale to old ItemNodes)
  3. Marks all nodes dirty for re-validation
Location: packages/core/src/store/use-scene.ts:70-88

Clear Scene

Reset to empty scene and reload default:
Location: packages/core/src/store/use-scene.ts:61-68

Usage Patterns

In React Components

Outside React (Systems, Utilities)

Use useScene.getState() to access state outside React components. This doesn’t create a subscription and won’t cause re-renders.