scene starting with site and not with building anymore
This commit is contained in:
@@ -49,8 +49,10 @@ function SitePhaseView() {
|
|||||||
const selectedBuildingId = useViewer((state) => state.selection.buildingId);
|
const selectedBuildingId = useViewer((state) => state.selection.buildingId);
|
||||||
const setSelection = useViewer((state) => state.setSelection);
|
const setSelection = useViewer((state) => state.setSelection);
|
||||||
|
|
||||||
const buildings = rootNodeIds
|
// Get site node and its building children
|
||||||
.map((id) => nodes[id])
|
const siteNode = rootNodeIds[0] ? nodes[rootNodeIds[0]] : null;
|
||||||
|
const buildings = (siteNode?.type === 'site' ? siteNode.children : [])
|
||||||
|
.map((child) => typeof child === 'string' ? nodes[child] : child)
|
||||||
.filter((node): node is BuildingNode => node?.type === "building");
|
.filter((node): node is BuildingNode => node?.type === "building");
|
||||||
|
|
||||||
if (buildings.length === 0) {
|
if (buildings.length === 0) {
|
||||||
@@ -92,8 +94,10 @@ function BuildingSelector() {
|
|||||||
const selectedBuildingId = useViewer((state) => state.selection.buildingId);
|
const selectedBuildingId = useViewer((state) => state.selection.buildingId);
|
||||||
const setSelection = useViewer((state) => state.setSelection);
|
const setSelection = useViewer((state) => state.setSelection);
|
||||||
|
|
||||||
const buildings = rootNodeIds
|
// Get site node and its building children
|
||||||
.map((id) => nodes[id])
|
const siteNode = rootNodeIds[0] ? nodes[rootNodeIds[0]] : null;
|
||||||
|
const buildings = (siteNode?.type === 'site' ? siteNode.children : [])
|
||||||
|
.map((child) => typeof child === 'string' ? nodes[child] : child)
|
||||||
.filter((node): node is BuildingNode => node?.type === "building");
|
.filter((node): node is BuildingNode => node?.type === "building");
|
||||||
|
|
||||||
const selectedBuilding = selectedBuildingId
|
const selectedBuilding = selectedBuildingId
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ThreeEvent } from '@react-three/fiber'
|
import type { ThreeEvent } from '@react-three/fiber'
|
||||||
import mitt from 'mitt'
|
import mitt from 'mitt'
|
||||||
import type { BuildingNode, CeilingNode, ItemNode, LevelNode, RoofNode, SlabNode, WallNode, ZoneNode } from '../schema'
|
import type { BuildingNode, CeilingNode, ItemNode, LevelNode, RoofNode, SiteNode, SlabNode, WallNode, ZoneNode } from '../schema'
|
||||||
import type { AnyNode } from '../schema/types'
|
import type { AnyNode } from '../schema/types'
|
||||||
|
|
||||||
// Base event interfaces
|
// Base event interfaces
|
||||||
@@ -20,6 +20,7 @@ export interface NodeEvent<T extends AnyNode = AnyNode> {
|
|||||||
|
|
||||||
export type WallEvent = NodeEvent<WallNode>
|
export type WallEvent = NodeEvent<WallNode>
|
||||||
export type ItemEvent = NodeEvent<ItemNode>
|
export type ItemEvent = NodeEvent<ItemNode>
|
||||||
|
export type SiteEvent = NodeEvent<SiteNode>
|
||||||
export type BuildingEvent = NodeEvent<BuildingNode>
|
export type BuildingEvent = NodeEvent<BuildingNode>
|
||||||
export type LevelEvent = NodeEvent<LevelNode>
|
export type LevelEvent = NodeEvent<LevelNode>
|
||||||
export type ZoneEvent = NodeEvent<ZoneNode>
|
export type ZoneEvent = NodeEvent<ZoneNode>
|
||||||
@@ -64,6 +65,7 @@ type CameraControlEvents = {
|
|||||||
type EditorEvents = GridEvents &
|
type EditorEvents = GridEvents &
|
||||||
NodeEvents<'wall', WallEvent> &
|
NodeEvents<'wall', WallEvent> &
|
||||||
NodeEvents<'item', ItemEvent> &
|
NodeEvents<'item', ItemEvent> &
|
||||||
|
NodeEvents<'site', SiteEvent> &
|
||||||
NodeEvents<'building', BuildingEvent> &
|
NodeEvents<'building', BuildingEvent> &
|
||||||
NodeEvents<'level', LevelEvent> &
|
NodeEvents<'level', LevelEvent> &
|
||||||
NodeEvents<'zone', ZoneEvent> &
|
NodeEvents<'zone', ZoneEvent> &
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export const sceneRegistry = {
|
|||||||
// Categorized lookups: Type -> Set of IDs
|
// Categorized lookups: Type -> Set of IDs
|
||||||
// Using a Set is faster for adding/deleting than an Array
|
// Using a Set is faster for adding/deleting than an Array
|
||||||
byType: {
|
byType: {
|
||||||
|
site: new Set<string>(),
|
||||||
building: new Set<string>(),
|
building: new Set<string>(),
|
||||||
ceiling: new Set<string>(),
|
ceiling: new Set<string>(),
|
||||||
level: new Set<string>(),
|
level: new Set<string>(),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type {
|
|||||||
ItemEvent,
|
ItemEvent,
|
||||||
LevelEvent,
|
LevelEvent,
|
||||||
NodeEvent,
|
NodeEvent,
|
||||||
|
SiteEvent,
|
||||||
SlabEvent,
|
SlabEvent,
|
||||||
WallEvent,
|
WallEvent,
|
||||||
ZoneEvent,
|
ZoneEvent,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { create, type StoreApi, type UseBoundStore } from 'zustand'
|
|||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
import { BuildingNode } from '../schema'
|
import { BuildingNode } from '../schema'
|
||||||
import { LevelNode } from '../schema/nodes/level'
|
import { LevelNode } from '../schema/nodes/level'
|
||||||
|
import { SiteNode } from '../schema/nodes/site'
|
||||||
import type { AnyNode, AnyNodeId } from '../schema/types'
|
import type { AnyNode, AnyNodeId } from '../schema/types'
|
||||||
import { isObject } from '../utils/types'
|
import { isObject } from '../utils/types'
|
||||||
import * as nodeActions from './actions/node-actions'
|
import * as nodeActions from './actions/node-actions'
|
||||||
@@ -87,25 +88,29 @@ const useScene: UseSceneStore = create<SceneState>()(
|
|||||||
return // Scene already loaded
|
return // Scene already loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
const building = BuildingNode.parse({
|
// Create hierarchy: Site → Building → Level
|
||||||
children: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const level0 = LevelNode.parse({
|
const level0 = LevelNode.parse({
|
||||||
level: 0,
|
level: 0,
|
||||||
children: [],
|
children: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
building.children.push(level0.id)
|
const building = BuildingNode.parse({
|
||||||
|
children: [level0.id],
|
||||||
|
})
|
||||||
|
|
||||||
|
const site = SiteNode.parse({
|
||||||
|
children: [building],
|
||||||
|
})
|
||||||
|
|
||||||
// Define all nodes flat
|
// Define all nodes flat
|
||||||
const nodes: Record<AnyNodeId, AnyNode> = {
|
const nodes: Record<AnyNodeId, AnyNode> = {
|
||||||
|
[site.id]: site,
|
||||||
[building.id]: building,
|
[building.id]: building,
|
||||||
[level0.id]: level0,
|
[level0.id]: level0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root nodes are the levels
|
// Site is the root
|
||||||
const rootNodeIds = [building.id]
|
const rootNodeIds = [site.id]
|
||||||
|
|
||||||
set({ nodes, rootNodeIds })
|
set({ nodes, rootNodeIds })
|
||||||
},
|
},
|
||||||
@@ -155,13 +160,44 @@ const useScene: UseSceneStore = create<SceneState>()(
|
|||||||
onRehydrateStorage: (state) => {
|
onRehydrateStorage: (state) => {
|
||||||
console.log('hydrating...')
|
console.log('hydrating...')
|
||||||
|
|
||||||
// optional
|
|
||||||
return (state, error) => {
|
return (state, error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log('an error happened during hydration', error)
|
console.log('an error happened during hydration', error)
|
||||||
} else {
|
return
|
||||||
console.log('hydration finished')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
console.log('hydration finished - no state')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration: Wrap old scenes (where root is not a SiteNode) in a SiteNode
|
||||||
|
const rootId = state.rootNodeIds?.[0]
|
||||||
|
const rootNode = rootId ? state.nodes[rootId] : null
|
||||||
|
|
||||||
|
if (rootNode && rootNode.type !== 'site') {
|
||||||
|
console.log('Migrating old scene: wrapping in SiteNode')
|
||||||
|
|
||||||
|
// Collect existing root nodes (should be BuildingNode or ItemNode)
|
||||||
|
const existingRoots = (state.rootNodeIds || [])
|
||||||
|
.map(id => state.nodes[id])
|
||||||
|
.filter(node => node?.type === 'building' || node?.type === 'item')
|
||||||
|
|
||||||
|
// Create a new SiteNode with existing roots as children
|
||||||
|
const site = SiteNode.parse({
|
||||||
|
children: existingRoots,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add site to nodes
|
||||||
|
state.nodes[site.id] = site
|
||||||
|
|
||||||
|
// Update root to be the site
|
||||||
|
state.rootNodeIds = [site.id]
|
||||||
|
|
||||||
|
console.log('Migration complete: scene now has SiteNode as root')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('hydration finished')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { ItemRenderer } from './item/item-renderer'
|
|||||||
import { LevelRenderer } from './level/level-renderer'
|
import { LevelRenderer } from './level/level-renderer'
|
||||||
import { RoofRenderer } from './roof/roof-renderer'
|
import { RoofRenderer } from './roof/roof-renderer'
|
||||||
import { ScanRenderer } from './scan/scan-renderer'
|
import { ScanRenderer } from './scan/scan-renderer'
|
||||||
|
import { SiteRenderer } from './site/site-renderer'
|
||||||
import { SlabRenderer } from './slab/slab-renderer'
|
import { SlabRenderer } from './slab/slab-renderer'
|
||||||
import { WallRenderer } from './wall/wall-renderer'
|
import { WallRenderer } from './wall/wall-renderer'
|
||||||
import { ZoneRenderer } from './zone/zone-renderer'
|
import { ZoneRenderer } from './zone/zone-renderer'
|
||||||
@@ -19,6 +20,7 @@ export const NodeRenderer = ({ nodeId }: { nodeId: AnyNode['id'] }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{node.type === 'site' && <SiteRenderer node={node} />}
|
||||||
{node.type === 'building' && <BuildingRenderer node={node} />}
|
{node.type === 'building' && <BuildingRenderer node={node} />}
|
||||||
{node.type === 'ceiling' && <CeilingRenderer node={node} />}
|
{node.type === 'ceiling' && <CeilingRenderer node={node} />}
|
||||||
{node.type === 'level' && <LevelRenderer node={node} />}
|
{node.type === 'level' && <LevelRenderer node={node} />}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { type SiteNode, useRegistry } from '@pascal-app/core'
|
||||||
|
import { useRef } from 'react'
|
||||||
|
import type { Group } from 'three'
|
||||||
|
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||||
|
import { NodeRenderer } from '../node-renderer'
|
||||||
|
|
||||||
|
export const SiteRenderer = ({ node }: { node: SiteNode }) => {
|
||||||
|
const ref = useRef<Group>(null!)
|
||||||
|
|
||||||
|
useRegistry(node.id, node.type, ref)
|
||||||
|
const handlers = useNodeEvents(node, 'site')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={ref} {...handlers}>
|
||||||
|
{node.children.map((child) => (
|
||||||
|
<NodeRenderer key={child.id} nodeId={child.id} />
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
type LevelNode,
|
type LevelNode,
|
||||||
type RoofEvent,
|
type RoofEvent,
|
||||||
type RoofNode,
|
type RoofNode,
|
||||||
|
type SiteEvent,
|
||||||
|
type SiteNode,
|
||||||
type SlabEvent,
|
type SlabEvent,
|
||||||
type SlabNode,
|
type SlabNode,
|
||||||
type WallEvent,
|
type WallEvent,
|
||||||
@@ -21,6 +23,7 @@ import {
|
|||||||
import type { ThreeEvent } from '@react-three/fiber'
|
import type { ThreeEvent } from '@react-three/fiber'
|
||||||
|
|
||||||
type NodeConfig = {
|
type NodeConfig = {
|
||||||
|
site: { node: SiteNode; event: SiteEvent }
|
||||||
item: { node: ItemNode; event: ItemEvent }
|
item: { node: ItemNode; event: ItemEvent }
|
||||||
wall: { node: WallNode; event: WallEvent }
|
wall: { node: WallNode; event: WallEvent }
|
||||||
building: { node: BuildingNode; event: BuildingEvent }
|
building: { node: BuildingNode; event: BuildingEvent }
|
||||||
|
|||||||
Reference in New Issue
Block a user