9.2 KiB
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<number, SceneItem>.
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:
type SceneItems = Record<number, SceneItem>
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.
type GroupChild =
| { kind: "item"; id: number }
| { kind: "group"; id: number }
type SceneGroup = {
name: string
children: GroupChild[]
}
type SceneGroups = Record<number, SceneGroup>
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 |
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 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) HistorySnapshots:
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 ontopastand clearfuture. - 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()swapitems/groups/nextGroupIdbetweenpast/futureand clear the current selection state (selectedGroup,multiSelectedItems,multiSelectedGroups).- History is in-memory only — it is not part of
partializeand 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 |
— |