transient nodes
This commit is contained in:
@@ -17,8 +17,8 @@ import { AppSidebar } from '../ui/sidebar/app-sidebar'
|
|||||||
import { CustomCameraControls } from './custom-camera-controls'
|
import { CustomCameraControls } from './custom-camera-controls'
|
||||||
import { SelectionManager } from './selection-manager'
|
import { SelectionManager } from './selection-manager'
|
||||||
|
|
||||||
initSpatialGridSync()
|
|
||||||
useScene.getState().loadScene()
|
useScene.getState().loadScene()
|
||||||
|
initSpatialGridSync()
|
||||||
|
|
||||||
export default function Editor() {
|
export default function Editor() {
|
||||||
useKeyboard()
|
useKeyboard()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
emitter,
|
emitter,
|
||||||
type GridEvent,
|
type GridEvent,
|
||||||
ItemNode,
|
ItemNode,
|
||||||
|
isObject,
|
||||||
sceneRegistry,
|
sceneRegistry,
|
||||||
useScene,
|
useScene,
|
||||||
useSpatialQuery,
|
useSpatialQuery,
|
||||||
@@ -29,6 +30,12 @@ function snapToGrid(position: number, dimension: number): number {
|
|||||||
return Math.round((position - offset) * 2) / 2 + offset
|
return Math.round((position - offset) * 2) / 2 + offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stripTransient = (meta: any) => {
|
||||||
|
if (!isObject(meta)) return meta
|
||||||
|
const { isTransient, ...rest } = meta as Record<string, any>
|
||||||
|
return rest
|
||||||
|
}
|
||||||
|
|
||||||
export const ItemTool: React.FC = () => {
|
export const ItemTool: React.FC = () => {
|
||||||
const cursorRef = useRef<Mesh>(null!)
|
const cursorRef = useRef<Mesh>(null!)
|
||||||
const draftItem = useRef<ItemNode | null>(null)
|
const draftItem = useRef<ItemNode | null>(null)
|
||||||
@@ -90,6 +97,9 @@ export const ItemTool: React.FC = () => {
|
|||||||
position: [gridPosition.current.x, gridPosition.current.y, gridPosition.current.z],
|
position: [gridPosition.current.x, gridPosition.current.y, gridPosition.current.z],
|
||||||
name: selectedItem.name,
|
name: selectedItem.name,
|
||||||
asset: selectedItem,
|
asset: selectedItem,
|
||||||
|
metadata: {
|
||||||
|
isTransient: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
useScene.getState().createNode(draftItem.current, currentLevelId)
|
useScene.getState().createNode(draftItem.current, currentLevelId)
|
||||||
checkCanPlace()
|
checkCanPlace()
|
||||||
@@ -124,8 +134,10 @@ export const ItemTool: React.FC = () => {
|
|||||||
if (!currentLevelId || !draftItem.current || !checkCanPlace()) return
|
if (!currentLevelId || !draftItem.current || !checkCanPlace()) return
|
||||||
|
|
||||||
useScene.temporal.getState().resume()
|
useScene.temporal.getState().resume()
|
||||||
|
|
||||||
useScene.getState().updateNode(draftItem.current.id, {
|
useScene.getState().updateNode(draftItem.current.id, {
|
||||||
position: [gridPosition.current.x, 0, gridPosition.current.z],
|
position: [gridPosition.current.x, 0, gridPosition.current.z],
|
||||||
|
metadata: stripTransient(draftItem.current.metadata),
|
||||||
})
|
})
|
||||||
draftItem.current = null
|
draftItem.current = null
|
||||||
|
|
||||||
@@ -187,6 +199,7 @@ export const ItemTool: React.FC = () => {
|
|||||||
useScene.getState().updateNode(draftItem.current.id, {
|
useScene.getState().updateNode(draftItem.current.id, {
|
||||||
position: [gridPosition.current.x, gridPosition.current.y, gridPosition.current.z],
|
position: [gridPosition.current.x, gridPosition.current.y, gridPosition.current.z],
|
||||||
parentId: event.node.id,
|
parentId: event.node.id,
|
||||||
|
metadata: stripTransient(draftItem.current.metadata),
|
||||||
})
|
})
|
||||||
useScene.getState().dirtyNodes.add(event.node.id)
|
useScene.getState().dirtyNodes.add(event.node.id)
|
||||||
draftItem.current = null
|
draftItem.current = null
|
||||||
|
|||||||
@@ -26,3 +26,5 @@ export * from './schema'
|
|||||||
export { default as useScene } from './store/use-scene'
|
export { default as useScene } from './store/use-scene'
|
||||||
// Systems
|
// Systems
|
||||||
export { WallSystem } from './systems/wall/wall-system'
|
export { WallSystem } from './systems/wall/wall-system'
|
||||||
|
|
||||||
|
export { isObject } from './utils/types'
|
||||||
@@ -6,6 +6,7 @@ import { persist } from 'zustand/middleware'
|
|||||||
import { BuildingNode, type Zone } from '../schema'
|
import { BuildingNode, type Zone } from '../schema'
|
||||||
import { LevelNode } from '../schema/nodes/level'
|
import { LevelNode } from '../schema/nodes/level'
|
||||||
import type { AnyNode, AnyNodeId } from '../schema/types'
|
import type { AnyNode, AnyNodeId } from '../schema/types'
|
||||||
|
import { isObject } from '../utils/types'
|
||||||
import * as nodeActions from './actions/node-actions'
|
import * as nodeActions from './actions/node-actions'
|
||||||
import * as zoneActions from './actions/zone-actions'
|
import * as zoneActions from './actions/zone-actions'
|
||||||
|
|
||||||
@@ -76,10 +77,10 @@ const useScene = create<SceneState>()(
|
|||||||
|
|
||||||
loadScene: () => {
|
loadScene: () => {
|
||||||
if (get().rootNodeIds.length > 0) {
|
if (get().rootNodeIds.length > 0) {
|
||||||
// Assign all nodes as dirty to force re-validation
|
// Assign all nodes as dirty to force re-validation
|
||||||
Object.values(get().nodes).forEach((node) => {
|
Object.values(get().nodes).forEach((node) => {
|
||||||
get().markDirty(node.id)
|
get().markDirty(node.id)
|
||||||
})
|
})
|
||||||
return // Scene already loaded
|
return // Scene already loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +150,14 @@ const useScene = create<SceneState>()(
|
|||||||
{
|
{
|
||||||
name: 'editor-storage',
|
name: 'editor-storage',
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
nodes: state.nodes,
|
nodes: Object.fromEntries(
|
||||||
|
Object.entries(state.nodes).filter(([_, node]) => {
|
||||||
|
const meta = node.metadata
|
||||||
|
const isTransient = isObject(meta) && 'isTransient' in meta && meta.isTransient === true
|
||||||
|
|
||||||
|
return !isTransient
|
||||||
|
}),
|
||||||
|
),
|
||||||
rootNodeIds: state.rootNodeIds,
|
rootNodeIds: state.rootNodeIds,
|
||||||
zones: state.zones,
|
zones: state.zones,
|
||||||
zoneIds: state.zoneIds,
|
zoneIds: state.zoneIds,
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Type guard to check if a value is a plain object (and not null or an array).
|
||||||
|
* Useful for narrowing down Zod's generic JSON types.
|
||||||
|
*/
|
||||||
|
export const isObject = (val: unknown): val is Record<string, any> => {
|
||||||
|
return val !== null && typeof val === 'object' && !Array.isArray(val)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user