draft zone
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import type { Zone } from '../../schema'
|
||||
import type { SceneState } from '../use-scene'
|
||||
|
||||
export const createZonesAction = (
|
||||
set: (fn: (state: SceneState) => Partial<SceneState>) => void,
|
||||
_get: () => SceneState,
|
||||
zones: Zone[],
|
||||
) => {
|
||||
set((state) => {
|
||||
const nextZones = { ...state.zones }
|
||||
const nextZoneIds = [...state.zoneIds]
|
||||
|
||||
for (const zone of zones) {
|
||||
nextZones[zone.id] = zone
|
||||
|
||||
if (!nextZoneIds.includes(zone.id)) {
|
||||
nextZoneIds.push(zone.id)
|
||||
}
|
||||
}
|
||||
|
||||
return { zones: nextZones, zoneIds: nextZoneIds }
|
||||
})
|
||||
}
|
||||
|
||||
export const updateZonesAction = (
|
||||
set: (fn: (state: SceneState) => Partial<SceneState>) => void,
|
||||
_get: () => SceneState,
|
||||
updates: { id: Zone['id']; data: Partial<Zone> }[],
|
||||
) => {
|
||||
set((state) => {
|
||||
const nextZones = { ...state.zones }
|
||||
|
||||
for (const { id, data } of updates) {
|
||||
const currentZone = nextZones[id]
|
||||
if (!currentZone) continue
|
||||
|
||||
nextZones[id] = { ...currentZone, ...data }
|
||||
}
|
||||
|
||||
return { zones: nextZones }
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteZonesAction = (
|
||||
set: (fn: (state: SceneState) => Partial<SceneState>) => void,
|
||||
_get: () => SceneState,
|
||||
ids: Zone['id'][],
|
||||
) => {
|
||||
set((state) => {
|
||||
const nextZones = { ...state.zones }
|
||||
let nextZoneIds = [...state.zoneIds]
|
||||
|
||||
for (const id of ids) {
|
||||
delete nextZones[id]
|
||||
nextZoneIds = nextZoneIds.filter((zid) => zid !== id)
|
||||
}
|
||||
|
||||
return { zones: nextZones, zoneIds: nextZoneIds }
|
||||
})
|
||||
}
|
||||
@@ -1,37 +1,48 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import { temporal } from 'zundo'
|
||||
import { create } from 'zustand'
|
||||
import { BuildingNode, ItemNode } from '../schema'
|
||||
import { LevelNode } from '../schema/nodes/level'
|
||||
import { WallNode } from '../schema/nodes/wall'
|
||||
import type { AnyNode, AnyNodeId } from '../schema/types'
|
||||
import * as nodeActions from './actions/node-actions'
|
||||
import { temporal } from "zundo";
|
||||
import { create } from "zustand";
|
||||
import { BuildingNode, ItemNode, type Zone } from "../schema";
|
||||
import { LevelNode } from "../schema/nodes/level";
|
||||
import { WallNode } from "../schema/nodes/wall";
|
||||
import type { AnyNode, AnyNodeId } from "../schema/types";
|
||||
import * as nodeActions from "./actions/node-actions";
|
||||
import * as zoneActions from "./actions/zone-actions";
|
||||
|
||||
export type SceneState = {
|
||||
// 1. The Data: A flat dictionary of all nodes
|
||||
nodes: Record<AnyNodeId, AnyNode>
|
||||
nodes: Record<AnyNodeId, AnyNode>;
|
||||
zones: Record<Zone["id"], Zone>;
|
||||
|
||||
// 2. The Root: Which nodes are at the top level?
|
||||
rootNodeIds: AnyNodeId[]
|
||||
rootNodeIds: AnyNodeId[];
|
||||
zoneIds: Zone["id"][];
|
||||
|
||||
// 3. The "Dirty" Set: For the Wall/Physics systems
|
||||
dirtyNodes: Set<AnyNodeId>
|
||||
dirtyNodes: Set<AnyNodeId>;
|
||||
|
||||
// Actions
|
||||
loadScene: () => void
|
||||
markDirty: (id: AnyNodeId) => void
|
||||
clearDirty: (id: AnyNodeId) => void
|
||||
loadScene: () => void;
|
||||
markDirty: (id: AnyNodeId) => void;
|
||||
clearDirty: (id: AnyNodeId) => void;
|
||||
|
||||
createNode: (node: AnyNode, parentId?: AnyNodeId) => void
|
||||
createNodes: (ops: { node: AnyNode; parentId?: AnyNodeId }[]) => void
|
||||
createNode: (node: AnyNode, parentId?: AnyNodeId) => void;
|
||||
createNodes: (ops: { node: AnyNode; parentId?: AnyNodeId }[]) => void;
|
||||
|
||||
updateNode: (id: AnyNodeId, data: Partial<AnyNode>) => void
|
||||
updateNodes: (updates: { id: AnyNodeId; data: Partial<AnyNode> }[]) => void
|
||||
updateNode: (id: AnyNodeId, data: Partial<AnyNode>) => void;
|
||||
updateNodes: (updates: { id: AnyNodeId; data: Partial<AnyNode> }[]) => void;
|
||||
|
||||
deleteNode: (id: AnyNodeId) => void
|
||||
deleteNodes: (ids: AnyNodeId[]) => void
|
||||
}
|
||||
deleteNode: (id: AnyNodeId) => void;
|
||||
deleteNodes: (ids: AnyNodeId[]) => void;
|
||||
|
||||
// Zone actions
|
||||
createZone: (zone: Zone) => void;
|
||||
createZones: (zones: Zone[]) => void;
|
||||
updateZone: (id: Zone["id"], data: Partial<Zone>) => void;
|
||||
updateZones: (updates: { id: Zone["id"]; data: Partial<Zone> }[]) => void;
|
||||
deleteZone: (id: Zone["id"]) => void;
|
||||
deleteZones: (ids: Zone["id"][]) => void;
|
||||
};
|
||||
|
||||
// type PartializedStoreState = Pick<SceneState, 'rootNodeIds' | 'nodes'>;
|
||||
|
||||
@@ -40,9 +51,11 @@ const useScene = create<SceneState>()(
|
||||
(set, get) => ({
|
||||
// 1. Flat dictionary of all nodes
|
||||
nodes: {},
|
||||
zones: {},
|
||||
|
||||
// 2. Root node IDs
|
||||
rootNodeIds: [],
|
||||
zoneIds: [],
|
||||
|
||||
// 3. Dirty set
|
||||
dirtyNodes: new Set<AnyNodeId>(),
|
||||
@@ -50,70 +63,70 @@ const useScene = create<SceneState>()(
|
||||
loadScene: () => {
|
||||
const building = BuildingNode.parse({
|
||||
children: [],
|
||||
})
|
||||
});
|
||||
|
||||
const level0 = LevelNode.parse({
|
||||
level: 0,
|
||||
children: [],
|
||||
})
|
||||
});
|
||||
const level1 = LevelNode.parse({
|
||||
level: 1,
|
||||
children: [],
|
||||
})
|
||||
});
|
||||
const level2 = LevelNode.parse({
|
||||
level: 2,
|
||||
children: [],
|
||||
})
|
||||
});
|
||||
|
||||
const wall0 = WallNode.parse({
|
||||
start: [0, 0],
|
||||
end: [5, 0],
|
||||
children: [],
|
||||
parentId: level0.id,
|
||||
})
|
||||
});
|
||||
|
||||
const wall1 = WallNode.parse({
|
||||
start: [0, 0],
|
||||
end: [0, 5],
|
||||
children: [],
|
||||
parentId: level0.id,
|
||||
})
|
||||
});
|
||||
|
||||
const wall2 = WallNode.parse({
|
||||
start: [5, 5],
|
||||
end: [0, 5],
|
||||
children: [],
|
||||
parentId: level0.id,
|
||||
})
|
||||
});
|
||||
|
||||
const wall3 = WallNode.parse({
|
||||
start: [5, 5],
|
||||
end: [5, 0],
|
||||
children: [],
|
||||
parentId: level1.id,
|
||||
})
|
||||
});
|
||||
|
||||
const window1 = ItemNode.parse({
|
||||
type: 'item',
|
||||
name: 'Window',
|
||||
type: "item",
|
||||
name: "Window",
|
||||
position: [2.5, 0.5, 0],
|
||||
parentId: wall3.id,
|
||||
asset: {
|
||||
id: 'window-round',
|
||||
name: 'Round Window',
|
||||
thumbnail: '/items/window-small/thumbnail.png',
|
||||
category: 'windows',
|
||||
attachTo: 'wall',
|
||||
src: '/items/window-small/model.glb',
|
||||
id: "window-round",
|
||||
name: "Round Window",
|
||||
thumbnail: "/items/window-small/thumbnail.png",
|
||||
category: "windows",
|
||||
attachTo: "wall",
|
||||
src: "/items/window-small/model.glb",
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
wall3.children.push(window1.id)
|
||||
wall3.children.push(window1.id);
|
||||
|
||||
level0.children.push(wall0.id, wall1.id, wall2.id)
|
||||
level1.children.push(wall3.id)
|
||||
level0.children.push(wall0.id, wall1.id, wall2.id);
|
||||
level1.children.push(wall3.id);
|
||||
|
||||
building.children.push(level0.id, level1.id, level2.id)
|
||||
building.children.push(level0.id, level1.id, level2.id);
|
||||
|
||||
// Define all nodes flat
|
||||
const nodes: Record<AnyNodeId, AnyNode> = {
|
||||
@@ -126,60 +139,75 @@ const useScene = create<SceneState>()(
|
||||
[wall2.id]: wall2,
|
||||
[wall3.id]: wall3,
|
||||
[window1.id]: window1,
|
||||
}
|
||||
};
|
||||
|
||||
// Root nodes are the levels
|
||||
const rootNodeIds = [building.id]
|
||||
const rootNodeIds = [building.id];
|
||||
|
||||
get().dirtyNodes.add(wall0.id)
|
||||
get().dirtyNodes.add(wall1.id)
|
||||
get().dirtyNodes.add(wall2.id)
|
||||
get().dirtyNodes.add(wall3.id)
|
||||
set({ nodes, rootNodeIds })
|
||||
get().dirtyNodes.add(wall0.id);
|
||||
get().dirtyNodes.add(wall1.id);
|
||||
get().dirtyNodes.add(wall2.id);
|
||||
get().dirtyNodes.add(wall3.id);
|
||||
set({ nodes, rootNodeIds });
|
||||
},
|
||||
|
||||
markDirty: (id) => {
|
||||
get().dirtyNodes.add(id)
|
||||
get().dirtyNodes.add(id);
|
||||
},
|
||||
|
||||
clearDirty: (id) => {
|
||||
get().dirtyNodes.delete(id)
|
||||
get().dirtyNodes.delete(id);
|
||||
},
|
||||
|
||||
createNodes: (ops) => nodeActions.createNodesAction(set, get, ops),
|
||||
createNode: (node, parentId) => nodeActions.createNodesAction(set, get, [{ node, parentId }]),
|
||||
createNode: (node, parentId) =>
|
||||
nodeActions.createNodesAction(set, get, [{ node, parentId }]),
|
||||
|
||||
updateNodes: (updates) => nodeActions.updateNodesAction(set, get, updates),
|
||||
updateNode: (id, data) => nodeActions.updateNodesAction(set, get, [{ id, data }]),
|
||||
updateNodes: (updates) =>
|
||||
nodeActions.updateNodesAction(set, get, updates),
|
||||
updateNode: (id, data) =>
|
||||
nodeActions.updateNodesAction(set, get, [{ id, data }]),
|
||||
|
||||
// --- DELETE ---
|
||||
|
||||
deleteNodes: (ids) => nodeActions.deleteNodesAction(set, get, ids),
|
||||
|
||||
deleteNode: (id) => nodeActions.deleteNodesAction(set, get, [id]),
|
||||
|
||||
// --- ZONES ---
|
||||
|
||||
createZones: (zones) => zoneActions.createZonesAction(set, get, zones),
|
||||
createZone: (zone) => zoneActions.createZonesAction(set, get, [zone]),
|
||||
|
||||
updateZones: (updates) => zoneActions.updateZonesAction(set, get, updates),
|
||||
updateZone: (id, data) =>
|
||||
zoneActions.updateZonesAction(set, get, [{ id, data }]),
|
||||
|
||||
deleteZones: (ids) => zoneActions.deleteZonesAction(set, get, ids),
|
||||
deleteZone: (id) => zoneActions.deleteZonesAction(set, get, [id]),
|
||||
}),
|
||||
{
|
||||
partialize: (state) => {
|
||||
const { nodes, rootNodeIds } = state // Only track nodes and rootNodeIds in history
|
||||
return { nodes, rootNodeIds }
|
||||
const { nodes, rootNodeIds } = state; // Only track nodes and rootNodeIds in history
|
||||
return { nodes, rootNodeIds };
|
||||
},
|
||||
limit: 50, // Limit to last 50 actions
|
||||
},
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
export default useScene
|
||||
export default useScene;
|
||||
|
||||
// Subscribe to the temporal store (Undo/Redo events)
|
||||
useScene.temporal.subscribe((state, prevState) => {
|
||||
// Check if we just jumped in time (Undo/Redo)
|
||||
// If the 'nodes' object changed but it wasn't a normal 'set'
|
||||
const currentNodes = useScene.getState().nodes
|
||||
const currentNodes = useScene.getState().nodes;
|
||||
|
||||
// Trigger a full scene re-validation
|
||||
Object.values(currentNodes).forEach((node) => {
|
||||
if (node.type === 'wall') {
|
||||
useScene.getState().markDirty(node.id)
|
||||
if (node.type === "wall") {
|
||||
useScene.getState().markDirty(node.id);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user