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
packages/core/src/store/use-scene.ts:14-40
CRUD Operations
Create Node
Create a single node and optionally attach it to a parent:- Added to the
nodesdictionary - Added to parent’s
childrenarray (ifparentIdprovided) - Marked as dirty for system processing
packages/core/src/store/actions/node-actions.ts:5-52
Batch Create Nodes
Create multiple nodes efficiently:Update Node
Update properties of an existing node:- Updated in the
nodesdictionary - Marked as dirty for system processing
- Parent is also marked dirty (to update spatial relationships)
packages/core/src/store/actions/node-actions.ts:54-109
Reparenting
Change a node’s parent by updating itsparentId:
- Removing node from old parent’s
childrenarray - Adding node to new parent’s
childrenarray - Marking both old and new parents as dirty
Delete Node
Delete a node and all its descendants:- Recursively deletes all children
- Removes node from parent’s
childrenarray - Removes node from
nodesdictionary - Removes node from
rootNodeIds(if applicable) - Triggers full scene re-validation (some deletions like slabs affect elevations)
packages/core/src/store/actions/node-actions.ts:111-161
Dirty Nodes
ThedirtyNodes 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
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:packages/core/src/store/use-scene.ts:155-172
Transient Nodes
Nodes marked withmetadata: { isTransient: true } are not persisted to IndexedDB:
Temporal Middleware (Undo/Redo)
Provides undo/redo functionality using Zundo: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:packages/core/src/store/use-scene.ts:237-257
Loading and Clearing Scenes
Load Default Scene
Create a default scene hierarchy (Site → Building → Level):packages/core/src/store/use-scene.ts:90-124
Set Scene from External Data
Load a scene from external source (file, database, etc.):- Replaces current scene
- Applies backward compatibility patches (e.g., adding
scaleto old ItemNodes) - Marks all nodes dirty for re-validation
packages/core/src/store/use-scene.ts:70-88
Clear Scene
Reset to empty scene and reload default: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.