# Creator Data Structure This document describes the internal data structures used by the XRWise Creator editor. --- ## `SceneItem` A single object in the scene. Stored in the Zustand store as `Record`. ```ts type SceneItem = { // --- always present --- type: ItemType // e.g. "Cube", "Camera", "Directional Light" category: string // e.g. "Basic", "Light", "Streaming" position: [number, number, number] // world-space XYZ // --- transform --- rotation?: number[] // Euler degrees [x, y, z] dimensions?: number[] // XYZ scale / size in metres // --- appearance --- color?: string // hex color e.g. "#ffffff" opacity?: number // 0–1 // --- lights --- intensity?: number | string // light intensity target?: number[] // look-at point [x, y, z] // --- media / streaming --- file?: string // asset path or storage key LiveKitRoomID?: string shader?: string // "unlit" | "lit" | "greenscreen" GreenScreenColor?: string // hex color GreenScreenBorderSettings?: string // numeric string GreenScreenColorNear?: string // numeric string (0–1) widthcrop?: number heightcrop?: number videoJobId?: string // in-progress conversion job ID // --- text --- text?: string fontWeight?: "light" | "regular" | "bold" fontSize?: number // --- camera --- zoom?: number // 1–10; maps to FOV = 50 / zoom // --- interactions --- grabable?: boolean locked?: boolean // blocks move gizmo + deletion in the editor } ``` ### Fields by item type | Type | Required fields | Optional fields | |---|---|---| | `Cube` / `Sphere` / `Cylinder` / `Cone` | `position` | `color`, `opacity`, `dimensions`, `rotation` | | `Floor` | `position` | `color`, `opacity`, `dimensions`, `rotation` | | `Wall` | `position` | `color`, `opacity`, `dimensions`, `rotation` | | `Directional Light` | `position`, `target` | `color`, `intensity` | | `Point Light` | `position` | `color`, `intensity` | | `Entrance` / `Exit` | `position` | — | | `Billboard` | `position`, `file` | `dimensions`, `rotation` | | `LiveKitStream` | `position`, `LiveKitRoomID` | `shader`, `GreenScreenColor`, `GreenScreenBorderSettings`, `GreenScreenColorNear`, `widthcrop`, `heightcrop`, `dimensions`, `rotation` | | `VideoWall` | `position` | `file`, `videoJobId`, `widthcrop`, `heightcrop`, `dimensions`, `rotation` | | `Presentation` | `position` | `file`, `dimensions`, `rotation` | | `Camera` | `position`, `target` | `zoom`, `rotation` | | `Text` | `position`, `text` | `color`, `fontSize`, `fontWeight`, `dimensions`, `rotation` | | `Custom` | `position`, `file` | `dimensions`, `rotation` | | `Area` | `position` | `dimensions` (X/Z footprint only — Y is fixed), `rotation` | ### Common optional field (all types) | Field | Type | Description | |---|---|---| | `grabable` | `boolean` | Whether the item is interactable in-experience | | `locked` | `boolean` | When `true`, hides the move gizmo for the item and blocks deletion until unlocked from the menubar | --- ## `SceneItems` The full scene is stored as a flat map keyed by numeric ID: ```ts type SceneItems = Record ``` IDs are assigned sequentially when items are spawned and do not change. --- ## `SceneGroups` Used by the Grouping feature to let several items (or nested groups) be selected, moved, and managed as one. ```ts type GroupChild = | { kind: "item"; id: number } | { kind: "group"; id: number } type SceneGroup = { name: string children: GroupChild[] } type SceneGroups = Record ``` Group IDs are assigned sequentially from the store's `nextGroupId` counter, independent of item IDs. A group's `children` reference item/group IDs directly — there is no `groupId` field on `SceneItem` itself. --- ## `SceneStore` (Zustand) The complete editor state. Fields marked 💾 are persisted to `localStorage` under the key `scene-storage`. | Field | Type | Default | 💾 | Description | |---|---|---|---|---| | `items` | `SceneItems` | `{}` | ✅ | All scene objects | | `selected` | `number \| null` | `null` | ✅ | Currently selected item ID | | `skyColor` | `string` | `"#ffffff"` | ✅ | Background color | | `showGrid` | `boolean` | `true` | ✅ | Grid visibility | | `customItems` | `CustomItem[]` | `[]` | ✅ | User-uploaded GLB assets | | `showLights` | `boolean` | `true` | ✅ | Light helper visibility | | `showWalls` | `boolean` | `true` | ✅ | Wall visibility | | `showAreas` | `boolean` | `true` | ✅ | Area Collider visibility (Canvas Toggles) | | `cameraTarget` | `number[]` | `[0,0,0]` | ✅ | OrbitControls look-at point | | `cameraRotation` | `number[]` | `[0,0,0]` | ✅ | Editor camera rotation | | `cameraQuaternion` | `[x,y,z,w]` | `[0,0,0,0]` | ✅ | Editor camera quaternion | | `graphicalCoding` | `{ nodes, connections } \| null` | `null` | ✅ | Legacy node-graph coding state (superseded by `blockCoding`) | | `blockCoding` | `{ scripts: unknown[] } \| null` | `null` | ✅ | Block Coding scripts — see [Block Coding data structure](./data-structure-block-coding.md) | | `backgroundSound` | `string \| null` | `null` | ✅ | Uploaded ambient sound storage path (World Settings → Sound) | | `groups` | `SceneGroups` | `{}` | ✅ | All item/group hierarchies | | `nextGroupId` | `number` | `1` | ✅ | Next group ID to assign | | `selectedGroup` | `number \| null` | `null` | — | Currently selected group ID | | `multiSelectedItems` | `number[]` | `[]` | — | Item IDs in a multi-select (shift-click) | | `multiSelectedGroups` | `number[]` | `[]` | — | Group IDs in a multi-select | | `past` / `future` | `HistorySnapshot[]` | `[]` | — | Undo/redo stacks — see [Undo/Redo](#undoredo) below | | `message` | `string \| null` | `null` | — | Transient toast message | | `hovered` | `number \| null` | `null` | — | Item ID currently hovered in canvas or panel | | `focusItem` | `number \| null` | `null` | — | Item ID the camera should focus on | | `isDragging` | `boolean` | `false` | — | Pointer drag state | | `isTyping` | `boolean` | `false` | — | Text input focus state | | `orbitControls` | `OrbitControlsImpl \| null` | `null` | — | Live OrbitControls ref | | `api` | `ScreenshotAPI \| null` | `null` | — | Screenshot renderer ref | 💾 reflects the `partialize` allowlist in `lib/SceneStore.ts` — `selectedGroup`, `multiSelectedItems`, `multiSelectedGroups`, `past`, `future`, and the transient/ref fields below them are intentionally excluded, so undo history and multi-selection do not survive a page refresh. --- ## Undo/Redo `past`/`future` hold up to `MAX_HISTORY` (50) `HistorySnapshot`s: ```ts type HistorySnapshot = { items: SceneItems groups: SceneGroups nextGroupId: number } ``` - Mutations that go through `setItems`, `updateItem`, `batchUpdateItems`, or any group action (`createGroup`, `createGroupFromSelection`, `disbandGroup`, `renameGroup`) push the *pre-change* state onto `past` and clear `future`. - Pushes are coalesced: rapid successive edits (e.g. dragging a gizmo) within 400ms of the last push are merged into one history entry, so undo steps back per gesture rather than per frame. - `undo()`/`redo()` swap `items`/`groups`/`nextGroupId` between `past`/`future` and clear the current selection state (`selectedGroup`, `multiSelectedItems`, `multiSelectedGroups`). - History is in-memory only — it is not part of `partialize` and does not round-trip through save/load or the database JSON. --- ## `ITEMS_CONFIG` spawn defaults When an item is added to the scene, it is initialized with these values from `items-config.tsx`: | Type | `dimensions` | `color` | `rotation` | `spawnY` | `customAttributes` | |---|---|---|---|---|---| | `Cube` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `0.5` | — | | `Sphere` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `0.5` | — | | `Cylinder` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `0.5` | — | | `Cone` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `0.5` | — | | `Floor` | `[10,0.1,10]` | `#ffffff` | `[0,0,0]` | `0` | — | | `Wall` | `[20,5,0.1]` | `#ffffff` | `[0,90,0]` | `2.5` | — | | `Point Light` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `5` | `intensity: 50, target: [0,0,0]` | | `Directional Light` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `5` | `intensity: 20, target: [0,0,0]` | | `Entrance` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `0.1` | — | | `Exit` | `[1,1,1]` | `#962929` | `[0,0,0]` | `0.1` | — | | `Billboard` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `4` | `file: "/img/default-img.png"` | | `LiveKitStream` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `4` | `LiveKitRoomID: "Gas Station"` | | `VideoWall` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `4` | — | | `Presentation` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `4` | — | | `Camera` | `[1,1,1]` | `#444444` | `[0,0,0]` | `4` | `zoom: 1, target: [0,0,0]` | | `Text` | `[0.5,0.5,0.5]` | `black` | `[0,0,0]` | `2` | `text: "Text", fontWeight: "regular", fontSize: 5` | | `Custom` | `[1,1,1]` | `#ffffff` | `[0,0,0]` | `1` | — | | `Area` | `[10,0.1,10]` | `#ffffff` | `[0,0,0]` | `0` | — |