draft item tool
This commit is contained in:
@@ -51,10 +51,10 @@ export default function Editor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TestUndo = () => {
|
const TestUndo = () => {
|
||||||
const {undo, redo, futureStates, pastStates} = useScene.temporal.getState();
|
const { undo, redo, futureStates, pastStates } = useScene.temporal.getState();
|
||||||
|
|
||||||
|
return (
|
||||||
return ( <div className="absolute top-4 right-4 z-10 flex gap-2">
|
<div className="absolute top-4 right-4 z-10 flex gap-2">
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 rounded bg-white"
|
className="px-4 py-2 rounded bg-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -73,7 +73,7 @@ const TestUndo = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Passes = ({}) => {
|
export const Passes = ({}) => {
|
||||||
const { gl: renderer, scene, camera } = useThree();
|
const { gl: renderer, scene, camera } = useThree();
|
||||||
@@ -169,13 +169,13 @@ const Grid = ({
|
|||||||
grid.x
|
grid.x
|
||||||
.div(fw.x)
|
.div(fw.x)
|
||||||
.add(1 - thickness)
|
.add(1 - thickness)
|
||||||
.min(1)
|
.min(1),
|
||||||
);
|
);
|
||||||
const lineY = float(1).sub(
|
const lineY = float(1).sub(
|
||||||
grid.y
|
grid.y
|
||||||
.div(fw.y)
|
.div(fw.y)
|
||||||
.add(1 - thickness)
|
.add(1 - thickness)
|
||||||
.min(1)
|
.min(1),
|
||||||
);
|
);
|
||||||
// Combine both axes - max gives us lines in both directions
|
// Combine both axes - max gives us lines in both directions
|
||||||
return lineX.max(lineY);
|
return lineX.max(lineY);
|
||||||
@@ -192,7 +192,7 @@ const Grid = ({
|
|||||||
const gridColor = mix(
|
const gridColor = mix(
|
||||||
color(cellColor),
|
color(cellColor),
|
||||||
color(sectionColor),
|
color(sectionColor),
|
||||||
float(sectionThickness).mul(g2).min(1)
|
float(sectionThickness).mul(g2).min(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Combined alpha
|
// Combined alpha
|
||||||
@@ -229,37 +229,36 @@ const DraftSelector = () => {
|
|||||||
const selectedItemId = useRef<ItemNode["id"] | WallNode["id"]>(null);
|
const selectedItemId = useRef<ItemNode["id"] | WallNode["id"]>(null);
|
||||||
const itemSelectedAt = useRef<number>(0);
|
const itemSelectedAt = useRef<number>(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
emitter.on('building:enter', (event) => {
|
emitter.on("building:enter", (event) => {
|
||||||
console.log('Entered building:', event.node.id);
|
console.log("Entered building:", event.node.id);
|
||||||
const itemMesh = sceneRegistry.nodes.get(event.node.id);
|
const itemMesh = sceneRegistry.nodes.get(event.node.id);
|
||||||
selectedObjects.length = 0;
|
selectedObjects.length = 0;
|
||||||
selectedObjects.push(itemMesh);
|
selectedObjects.push(itemMesh);
|
||||||
});
|
});
|
||||||
emitter.on('building:leave', (event) => {
|
emitter.on("building:leave", (event) => {
|
||||||
console.log('Leaving building:', event.node.id);
|
console.log("Leaving building:", event.node.id);
|
||||||
selectedObjects.length = 0;
|
selectedObjects.length = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// emitter.on("item:click", (event) => {
|
||||||
|
// event.stopPropagation();
|
||||||
|
// if (Date.now() - itemSelectedAt.current < 50) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// itemSelectedAt.current = Date.now();
|
||||||
|
// if (selectedItemId.current === event.node.id) {
|
||||||
|
// selectedItemId.current = null;
|
||||||
|
// console.log("Deselected item:", event.node.id);
|
||||||
|
// selectedObjects.length = 0;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// selectedItemId.current = event.node.id;
|
||||||
|
// const itemMesh = sceneRegistry.nodes.get(event.node.id);
|
||||||
|
// if (!itemMesh) return;
|
||||||
|
// selectedObjects.push(itemMesh);
|
||||||
|
|
||||||
emitter.on("item:click", (event) => {
|
// console.log("Selected item:", event.node.id);
|
||||||
event.stopPropagation();
|
// });
|
||||||
if (Date.now() - itemSelectedAt.current < 50) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
itemSelectedAt.current = Date.now();
|
|
||||||
if (selectedItemId.current === event.node.id) {
|
|
||||||
selectedItemId.current = null;
|
|
||||||
console.log("Deselected item:", event.node.id);
|
|
||||||
selectedObjects.length = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedItemId.current = event.node.id;
|
|
||||||
const itemMesh = sceneRegistry.nodes.get(event.node.id);
|
|
||||||
if (!itemMesh) return;
|
|
||||||
selectedObjects.push(itemMesh);
|
|
||||||
|
|
||||||
console.log("Selected item:", event.node.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
emitter.on("wall:click", (event) => {
|
emitter.on("wall:click", (event) => {
|
||||||
if (Date.now() - itemSelectedAt.current < 50) {
|
if (Date.now() - itemSelectedAt.current < 50) {
|
||||||
@@ -294,14 +293,14 @@ const DraftSelector = () => {
|
|||||||
itemMesh.position.set(
|
itemMesh.position.set(
|
||||||
event.position[0],
|
event.position[0],
|
||||||
event.position[1],
|
event.position[1],
|
||||||
event.position[2]
|
event.position[2],
|
||||||
);
|
);
|
||||||
useScene.getState().dirtyNodes.add(wallNode.id);
|
useScene.getState().dirtyNodes.add(wallNode.id);
|
||||||
console.log(
|
console.log(
|
||||||
"Wall move event:",
|
"Wall move event:",
|
||||||
wallNode.id,
|
wallNode.id,
|
||||||
"Point position:",
|
"Point position:",
|
||||||
event.position
|
event.position,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import {
|
||||||
|
emitter,
|
||||||
|
GridEvent,
|
||||||
|
ItemNode,
|
||||||
|
sceneRegistry,
|
||||||
|
useRegistry,
|
||||||
|
useScene,
|
||||||
|
WallNode,
|
||||||
|
} from "@pascal-app/core";
|
||||||
|
import { useViewer } from "@pascal-app/viewer";
|
||||||
|
import { useFrame } from "@react-three/fiber";
|
||||||
|
import { use, useEffect, useRef } from "react";
|
||||||
|
import { Line, Mesh, Vector3 } from "three";
|
||||||
|
import { randInt } from "three/src/math/MathUtils.js";
|
||||||
|
|
||||||
|
export const ItemTool: React.FC = () => {
|
||||||
|
const cursorRef = useRef<Mesh>(null);
|
||||||
|
const draftItem = useRef<ItemNode | null>(null);
|
||||||
|
const gridPosition = useRef(new Vector3(0, 0, 0));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const createDraftItem = () => {
|
||||||
|
const { currentLevelId } = useViewer.getState();
|
||||||
|
if (!currentLevelId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
useScene.temporal.getState().pause();
|
||||||
|
draftItem.current = ItemNode.parse({
|
||||||
|
position: [randInt(-10, 10), 0, randInt(-10, 10)],
|
||||||
|
name: "Draft Item",
|
||||||
|
asset: {
|
||||||
|
category: "furniture",
|
||||||
|
src: "/items/couch-small/model.glb",
|
||||||
|
dimensions: [2, 1, 3],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
useScene.getState().createNode(draftItem.current, currentLevelId);
|
||||||
|
};
|
||||||
|
createDraftItem();
|
||||||
|
|
||||||
|
const onGridMove = (event: GridEvent) => {
|
||||||
|
if (!cursorRef.current) return;
|
||||||
|
|
||||||
|
gridPosition.current.set(
|
||||||
|
Math.round(event.position[0] * 2) / 2,
|
||||||
|
0,
|
||||||
|
Math.round(event.position[1] * 2) / 2,
|
||||||
|
);
|
||||||
|
cursorRef.current.position.set(
|
||||||
|
gridPosition.current.x,
|
||||||
|
0.1,
|
||||||
|
gridPosition.current.z,
|
||||||
|
);
|
||||||
|
if (draftItem.current) {
|
||||||
|
draftItem.current.position = [
|
||||||
|
gridPosition.current.x,
|
||||||
|
0,
|
||||||
|
gridPosition.current.z,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onGridClick = (event: GridEvent) => {
|
||||||
|
const { currentLevelId } = useViewer.getState();
|
||||||
|
|
||||||
|
console.log("oh", currentLevelId, draftItem.current);
|
||||||
|
if (!currentLevelId || !draftItem.current) return;
|
||||||
|
|
||||||
|
console.log("oh");
|
||||||
|
useScene.temporal.getState().resume();
|
||||||
|
useScene.getState().updateNode(draftItem.current.id, {
|
||||||
|
position: [gridPosition.current.x, 0, gridPosition.current.z],
|
||||||
|
});
|
||||||
|
draftItem.current = null;
|
||||||
|
|
||||||
|
useScene.temporal.getState().pause();
|
||||||
|
createDraftItem();
|
||||||
|
};
|
||||||
|
|
||||||
|
emitter.on("grid:move", onGridMove);
|
||||||
|
emitter.on("grid:click", onGridClick);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (draftItem.current) {
|
||||||
|
useScene.getState().deleteNode(draftItem.current.id);
|
||||||
|
}
|
||||||
|
emitter.off("grid:move", onGridMove);
|
||||||
|
emitter.off("grid:click", onGridClick);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useFrame((_, delta) => {
|
||||||
|
if (draftItem.current) {
|
||||||
|
const draftItemMesh = sceneRegistry.nodes.get(draftItem.current.id);
|
||||||
|
if (draftItemMesh) {
|
||||||
|
draftItemMesh.position.lerp(gridPosition.current, delta * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group>
|
||||||
|
<mesh ref={cursorRef}>
|
||||||
|
<boxGeometry args={[0.2, 0.2, 0.2]} />
|
||||||
|
<meshStandardMaterial color="red" />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import useEditor, { Phase, Tool } from "@/store/use-editor";
|
import useEditor, { Phase, Tool } from "@/store/use-editor";
|
||||||
import { WallTool } from "./wall/wall-tool";
|
import { WallTool } from "./wall/wall-tool";
|
||||||
|
import { ItemTool } from "./item/item-tool";
|
||||||
|
|
||||||
const tools: Record<Phase, Partial<Record<Tool, React.FC>>> = {
|
const tools: Record<Phase, Partial<Record<Tool, React.FC>>> = {
|
||||||
site: {
|
site: {
|
||||||
},
|
},
|
||||||
structure: {
|
structure: {
|
||||||
wall: WallTool,
|
wall: WallTool,
|
||||||
|
item: ItemTool,
|
||||||
},
|
},
|
||||||
furnish: {
|
furnish: {
|
||||||
|
item: ItemTool
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,18 @@ import { useViewer } from "@pascal-app/viewer";
|
|||||||
import { useEffect, useRef } from "react"
|
import { useEffect, useRef } from "react"
|
||||||
import { Line, Mesh, Vector3 } from "three";
|
import { Line, Mesh, Vector3 } from "three";
|
||||||
|
|
||||||
|
|
||||||
|
const commitWallDrawing = (start: [number, number], end: [number, number]) => {
|
||||||
|
const { currentLevelId } = useViewer.getState();
|
||||||
|
const { createNode } = useScene.getState();
|
||||||
|
|
||||||
|
if (!currentLevelId) return;
|
||||||
|
|
||||||
|
const wall = WallNode.parse({ start, end });
|
||||||
|
|
||||||
|
createNode(wall, currentLevelId);
|
||||||
|
};
|
||||||
|
|
||||||
export const WallTool: React.FC = () => {
|
export const WallTool: React.FC = () => {
|
||||||
|
|
||||||
const cursorRef = useRef<Mesh>(null);
|
const cursorRef = useRef<Mesh>(null);
|
||||||
@@ -42,15 +54,7 @@ export const WallTool: React.FC = () => {
|
|||||||
drawingLineRef.current.visible = true;
|
drawingLineRef.current.visible = true;
|
||||||
|
|
||||||
} else if (buildingState === 1) {
|
} else if (buildingState === 1) {
|
||||||
const currentLevelId = useViewer.getState().currentLevelId;
|
commitWallDrawing([startingPoint.x, startingPoint.z], [gridPosition[0], gridPosition[1]]);
|
||||||
console.log('currentLevelId:', currentLevelId);
|
|
||||||
if (currentLevelId) {
|
|
||||||
const wallNode = WallNode.parse({
|
|
||||||
start: [startingPoint.x, startingPoint.z],
|
|
||||||
end: [gridPosition[0], gridPosition[1]],
|
|
||||||
})
|
|
||||||
useScene.getState().createNode(wallNode, currentLevelId);
|
|
||||||
}
|
|
||||||
drawingLineRef.current.visible = false;
|
drawingLineRef.current.visible = false;
|
||||||
buildingState = 0;
|
buildingState = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,19 @@ type SceneState = {
|
|||||||
|
|
||||||
createNode: (node: AnyNode, parentId?: AnyNodeId) => void;
|
createNode: (node: AnyNode, parentId?: AnyNodeId) => void;
|
||||||
createNodes: (ops: { 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;
|
||||||
|
|
||||||
|
deleteNode: (id: AnyNodeId) => void;
|
||||||
|
deleteNodes: (ids: AnyNodeId[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PartializedStoreState = Pick<SceneState, 'rootNodeIds' | 'nodes'>;
|
// type PartializedStoreState = Pick<SceneState, 'rootNodeIds' | 'nodes'>;
|
||||||
|
|
||||||
const useScene = create<SceneState>()(temporal((set, get) => ({
|
|
||||||
|
|
||||||
|
const useScene = create<SceneState>()(
|
||||||
|
temporal(
|
||||||
|
(set, get) => ({
|
||||||
// 1. Flat dictionary of all nodes
|
// 1. Flat dictionary of all nodes
|
||||||
nodes: {},
|
nodes: {},
|
||||||
|
|
||||||
@@ -40,12 +47,10 @@ const useScene = create<SceneState>()(temporal((set, get) => ({
|
|||||||
dirtyNodes: new Set<AnyNodeId>(),
|
dirtyNodes: new Set<AnyNodeId>(),
|
||||||
|
|
||||||
loadScene: () => {
|
loadScene: () => {
|
||||||
|
|
||||||
const building = BuildingNode.parse({
|
const building = BuildingNode.parse({
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const level0 = LevelNode.parse({
|
const level0 = LevelNode.parse({
|
||||||
level: 0,
|
level: 0,
|
||||||
children: [],
|
children: [],
|
||||||
@@ -140,7 +145,7 @@ const useScene = create<SceneState>()(temporal((set, get) => ({
|
|||||||
// 1. Assign parentId to the child (Safe because BaseNode has parentId)
|
// 1. Assign parentId to the child (Safe because BaseNode has parentId)
|
||||||
const newNode = {
|
const newNode = {
|
||||||
...node,
|
...node,
|
||||||
parentId: parentId ?? null
|
parentId: parentId ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
nextNodes[newNode.id] = newNode;
|
nextNodes[newNode.id] = newNode;
|
||||||
@@ -154,7 +159,9 @@ const useScene = create<SceneState>()(temporal((set, get) => ({
|
|||||||
nextNodes[parentId] = {
|
nextNodes[parentId] = {
|
||||||
...parent,
|
...parent,
|
||||||
// Use Set to prevent duplicate IDs if createNode is called twice
|
// Use Set to prevent duplicate IDs if createNode is called twice
|
||||||
children: Array.from(new Set([...parent.children, newNode.id])) as any, // We don't verify child types here
|
children: Array.from(
|
||||||
|
new Set([...parent.children, newNode.id]),
|
||||||
|
) as any, // We don't verify child types here
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (!parentId) {
|
} else if (!parentId) {
|
||||||
@@ -173,24 +180,122 @@ const useScene = create<SceneState>()(temporal((set, get) => ({
|
|||||||
get().markDirty(node.id);
|
get().markDirty(node.id);
|
||||||
if (parentId) get().markDirty(parentId);
|
if (parentId) get().markDirty(parentId);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 3. The CONVENIENCE (Singular)
|
// 3. The CONVENIENCE (Singular)
|
||||||
createNode: (node, parentId) => get().createNodes([{ node, parentId }]),
|
createNode: (node, parentId) => get().createNodes([{ node, parentId }]),
|
||||||
|
|
||||||
|
updateNodes: (updates) => {
|
||||||
|
const parentsToUpdate = new Set<string>();
|
||||||
|
|
||||||
}), {
|
set((state) => {
|
||||||
|
const nextNodes = { ...state.nodes };
|
||||||
|
|
||||||
|
for (const { id, data } of updates) {
|
||||||
|
const currentNode = nextNodes[id];
|
||||||
|
if (!currentNode) continue;
|
||||||
|
|
||||||
|
// Handle Reparenting Logic
|
||||||
|
if (
|
||||||
|
data.parentId !== undefined &&
|
||||||
|
data.parentId !== currentNode.parentId
|
||||||
|
) {
|
||||||
|
// 1. Remove from old parent
|
||||||
|
if (currentNode.parentId && nextNodes[currentNode.parentId]) {
|
||||||
|
const oldParent = nextNodes[
|
||||||
|
currentNode.parentId
|
||||||
|
] as AnyContainerNode;
|
||||||
|
nextNodes[oldParent.id] = {
|
||||||
|
...oldParent,
|
||||||
|
children: oldParent.children.filter(
|
||||||
|
(childId) => childId !== id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
parentsToUpdate.add(oldParent.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Add to new parent
|
||||||
|
if (data.parentId && nextNodes[data.parentId]) {
|
||||||
|
const newParent = nextNodes[data.parentId] as AnyContainerNode;
|
||||||
|
nextNodes[newParent.id] = {
|
||||||
|
...newParent,
|
||||||
|
children: Array.from(new Set([...newParent.children, id])),
|
||||||
|
};
|
||||||
|
parentsToUpdate.add(newParent.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the update
|
||||||
|
nextNodes[id] = { ...nextNodes[id], ...data };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { nodes: nextNodes };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark dirty
|
||||||
|
updates.forEach((u) => get().markDirty(u.id));
|
||||||
|
parentsToUpdate.forEach((pId) => get().markDirty(pId));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateNode: (id, data) => get().updateNodes([{ id, data }]),
|
||||||
|
|
||||||
|
// --- DELETE ---
|
||||||
|
|
||||||
|
deleteNodes: (ids) => {
|
||||||
|
const parentsToMarkDirty = new Set<string>();
|
||||||
|
|
||||||
|
set((state) => {
|
||||||
|
const nextNodes = { ...state.nodes };
|
||||||
|
let nextRootIds = [...state.rootNodeIds];
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const node = nextNodes[id];
|
||||||
|
if (!node) continue;
|
||||||
|
|
||||||
|
// 1. Remove reference from Parent
|
||||||
|
if (node.parentId && nextNodes[node.parentId]) {
|
||||||
|
const parent = nextNodes[node.parentId] as AnyContainerNode;
|
||||||
|
if (parent.children) {
|
||||||
|
nextNodes[parent.id] = {
|
||||||
|
...parent,
|
||||||
|
children: parent.children.filter((cid) => cid !== id),
|
||||||
|
};
|
||||||
|
parentsToMarkDirty.add(parent.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Remove from Root list
|
||||||
|
nextRootIds = nextRootIds.filter((rid) => rid !== id);
|
||||||
|
|
||||||
|
// 3. Delete the node itself
|
||||||
|
delete nextNodes[id];
|
||||||
|
|
||||||
|
// Note: If you want "Recursive Delete" (deleting a level deletes its walls),
|
||||||
|
// you would call deleteNodes recursively here for node.children.
|
||||||
|
}
|
||||||
|
|
||||||
|
return { nodes: nextNodes, rootNodeIds: nextRootIds };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notify systems that the parent has changed (e.g. Wall needs to fill a window hole)
|
||||||
|
parentsToMarkDirty.forEach((pId) => get().markDirty(pId));
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteNode: (id) => get().deleteNodes([id]),
|
||||||
|
}),
|
||||||
|
{
|
||||||
partialize: (state) => {
|
partialize: (state) => {
|
||||||
const { nodes, rootNodeIds } = state; // Only track nodes and rootNodeIds in history
|
const { nodes, rootNodeIds } = state; // Only track nodes and rootNodeIds in history
|
||||||
return { nodes, rootNodeIds}
|
return { nodes, rootNodeIds };
|
||||||
},
|
},
|
||||||
limit: 50, // Limit to last 50 actions
|
limit: 50, // Limit to last 50 actions
|
||||||
}));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
useScene.getState().loadScene();
|
useScene.getState().loadScene();
|
||||||
export default useScene;
|
export default useScene;
|
||||||
|
|
||||||
|
|
||||||
// Subscribe to the temporal store (Undo/Redo events)
|
// Subscribe to the temporal store (Undo/Redo events)
|
||||||
useScene.temporal.subscribe((state, prevState) => {
|
useScene.temporal.subscribe((state, prevState) => {
|
||||||
// Check if we just jumped in time (Undo/Redo)
|
// Check if we just jumped in time (Undo/Redo)
|
||||||
@@ -198,8 +303,8 @@ useScene.temporal.subscribe((state, prevState) => {
|
|||||||
const currentNodes = useScene.getState().nodes;
|
const currentNodes = useScene.getState().nodes;
|
||||||
|
|
||||||
// Trigger a full scene re-validation
|
// Trigger a full scene re-validation
|
||||||
Object.values(currentNodes).forEach(node => {
|
Object.values(currentNodes).forEach((node) => {
|
||||||
if (node.type === 'wall') {
|
if (node.type === "wall") {
|
||||||
useScene.getState().markDirty(node.id);
|
useScene.getState().markDirty(node.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export const LevelRenderer = ({ node }: { node: LevelNode }) => {
|
|||||||
|
|
||||||
useRegistry(node.id, node.type, ref);
|
useRegistry(node.id, node.type, ref);
|
||||||
|
|
||||||
console.log('rendering level:', node.id, node.children);
|
|
||||||
return (
|
return (
|
||||||
<group ref={ref}>
|
<group ref={ref}>
|
||||||
{/* <mesh receiveShadow>
|
{/* <mesh receiveShadow>
|
||||||
|
|||||||
Reference in New Issue
Block a user