Skip to content

Commit 2af70bd

Browse files
committed
Create roof segments when drawing roofs
1 parent 91178ad commit 2af70bd

2 files changed

Lines changed: 54 additions & 20 deletions

File tree

packages/editor/src/components/tools/roof/roof-tool.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@ import {
44
type GridEvent,
55
type LevelNode,
66
RoofNode,
7+
RoofSegmentNode,
78
useScene,
89
} from '@pascal-app/core'
910
import { useViewer } from '@pascal-app/viewer'
1011
import { useEffect, useMemo, useRef, useState } from 'react'
1112
import { BufferGeometry, DoubleSide, type Group, type Line, Vector3 } from 'three'
1213
import { EDITOR_LAYER } from '../../../lib/constants'
1314
import { sfxEmitter } from '../../../lib/sfx-bus'
14-
import useEditor from '../../../store/use-editor'
1515
import { CursorSphere } from '../shared/cursor-sphere'
1616

1717
// Default roof dimensions
1818
const DEFAULT_HEIGHT = 1.5
19-
const CEILING_HEIGHT = 2.52
2019
const GRID_OFFSET = 0.02
2120

2221
/**
@@ -27,7 +26,7 @@ const commitRoofPlacement = (
2726
corner1: [number, number, number],
2827
corner2: [number, number, number],
2928
): RoofNode['id'] => {
30-
const { createNode, nodes } = useScene.getState()
29+
const { createNodes, nodes } = useScene.getState()
3130

3231
// Calculate center position and dimensions from corners
3332
const centerX = (corner1[0] + corner2[0]) / 2
@@ -36,23 +35,28 @@ const commitRoofPlacement = (
3635
const length = Math.abs(corner2[0] - corner1[0])
3736
const width = Math.abs(corner2[2] - corner1[2])
3837

39-
// Split width evenly between left and right slopes
40-
const slopeWidth = Math.max(width / 2, 0.5)
41-
4238
// Count existing roofs for naming
4339
const roofCount = Object.values(nodes).filter((n) => n.type === 'roof').length
4440
const name = `Roof ${roofCount + 1}`
4541

4642
const roof = RoofNode.parse({
4743
name,
4844
position: [centerX, 0, centerZ], // Y is always 0
49-
length: Math.max(length, 0.5),
50-
height: DEFAULT_HEIGHT,
51-
leftWidth: slopeWidth,
52-
rightWidth: slopeWidth,
5345
})
5446

55-
createNode(roof, levelId)
47+
const segment = RoofSegmentNode.parse({
48+
position: [0, 0, 0],
49+
roofType: 'gable',
50+
width: Math.max(length, 0.5),
51+
depth: Math.max(width, 0.5),
52+
wallHeight: 0,
53+
roofHeight: DEFAULT_HEIGHT,
54+
})
55+
56+
createNodes([
57+
{ node: roof, parentId: levelId },
58+
{ node: segment, parentId: roof.id },
59+
])
5660
sfxEmitter.emit('sfx:structure-build')
5761
return roof.id
5862
}
@@ -68,8 +72,6 @@ export const RoofTool: React.FC = () => {
6872
const outlineRef = useRef<Line>(null!)
6973
const currentLevelId = useViewer((state) => state.selection.levelId)
7074
const setSelection = useViewer((state) => state.setSelection)
71-
const setTool = useEditor((state) => state.setTool)
72-
const setMode = useEditor((state) => state.setMode)
7375

7476
const corner1Ref = useRef<[number, number, number] | null>(null)
7577
const previousGridPosRef = useRef<[number, number] | null>(null)
@@ -190,7 +192,7 @@ export const RoofTool: React.FC = () => {
190192
// Reset state on unmount
191193
corner1Ref.current = null
192194
}
193-
}, [currentLevelId, setTool, setSelection, setMode])
195+
}, [currentLevelId, setSelection])
194196

195197
const { corner1, cursorPosition, levelY } = preview
196198

packages/editor/src/components/ui/panels/roof-panel.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
'use client'
22

3-
import { type AnyNode, type RoofNode, useScene } from '@pascal-app/core'
3+
import { type AnyNode, type AnyNodeId, type RoofNode, useScene } from '@pascal-app/core'
44
import { useViewer } from '@pascal-app/viewer'
55
import { useCallback } from 'react'
66
import { formatLength } from '../../../lib/measurements'
77
import { getRoofDimensions } from '../../../lib/roof-dimensions'
88
import { ActionButton } from '../controls/action-button'
9-
import { MetricControl } from '../controls/metric-control'
109
import { PanelSection } from '../controls/panel-section'
1110
import { SliderControl } from '../controls/slider-control'
1211
import { PanelWrapper } from './panel-wrapper'
@@ -16,17 +15,50 @@ export function RoofPanel() {
1615
const unitSystem = useViewer((s) => s.unitSystem)
1716
const setSelection = useViewer((s) => s.setSelection)
1817
const nodes = useScene((s) => s.nodes)
19-
const updateNode = useScene((s) => s.updateNode)
18+
const updateNodes = useScene((s) => s.updateNodes)
2019

2120
const selectedId = selectedIds[0]
2221
const node = selectedId ? (nodes[selectedId as AnyNode['id']] as RoofNode | undefined) : undefined
2322

2423
const handleUpdate = useCallback(
25-
(updates: Partial<RoofNode>) => {
24+
(
25+
updates: Partial<RoofNode> & {
26+
length?: number
27+
height?: number
28+
leftWidth?: number
29+
rightWidth?: number
30+
},
31+
) => {
2632
if (!selectedId) return
27-
updateNode(selectedId as AnyNode['id'], updates)
33+
34+
const roof = nodes[selectedId as AnyNode['id']] as RoofNode | undefined
35+
if (!roof) return
36+
37+
const dimensions = getRoofDimensions(roof, nodes)
38+
const nextLeftWidth = updates.leftWidth ?? dimensions.leftWidth
39+
const nextRightWidth = updates.rightWidth ?? dimensions.rightWidth
40+
41+
const batchedUpdates: Array<{ id: AnyNodeId; data: Partial<AnyNode> }> = [
42+
{
43+
id: selectedId as AnyNodeId,
44+
data: updates as Partial<AnyNode>,
45+
},
46+
]
47+
48+
if (dimensions.primarySegment) {
49+
batchedUpdates.push({
50+
id: dimensions.primarySegment.id as AnyNodeId,
51+
data: {
52+
width: updates.length ?? dimensions.primarySegment.width,
53+
depth: nextLeftWidth + nextRightWidth,
54+
roofHeight: updates.height ?? dimensions.primarySegment.roofHeight,
55+
} as Partial<AnyNode>,
56+
})
57+
}
58+
59+
updateNodes(batchedUpdates)
2860
},
29-
[selectedId, updateNode],
61+
[nodes, selectedId, updateNodes],
3062
)
3163

3264
const handleClose = useCallback(() => {

0 commit comments

Comments
 (0)