@pascal-app/editor 1.0.0-beta.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bunfig.toml +4 -0
- package/package.json +13 -7
- package/scripts/generate-print-golden-house.ts +89 -0
- package/src/components/editor/bake-exporter.tsx +1 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +41 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +10 -2
- package/src/components/editor/capture-camera-rig.tsx +59 -0
- package/src/components/editor/custom-camera-controls.tsx +20 -11
- package/src/components/editor/editor-layout-mobile.tsx +6 -6
- package/src/components/editor/editor-layout-v2.tsx +9 -2
- package/src/components/editor/export-manager.tsx +175 -33
- package/src/components/editor/first-person/build-collider-world.test.ts +30 -0
- package/src/components/editor/first-person-controls.tsx +180 -12
- package/src/components/editor/floating-action-menu.tsx +42 -51
- package/src/components/editor/floorplan-panel.tsx +76 -12
- package/src/components/editor/grid.tsx +12 -7
- package/src/components/editor/group-actions.ts +34 -50
- package/src/components/editor/group-move-3d.ts +51 -21
- package/src/components/editor/group-transform-shared.test.ts +78 -0
- package/src/components/editor/group-transform-shared.ts +74 -7
- package/src/components/editor/handles/handle-arrow-raycast.test.ts +86 -0
- package/src/components/editor/handles/handle-arrow.tsx +109 -48
- package/src/components/editor/handles/linear-resize-drag.ts +67 -0
- package/src/components/editor/handles/resize-snap.test.ts +46 -0
- package/src/components/editor/handles/resize-snap.ts +10 -1
- package/src/components/editor/handles/use-handle-drag.ts +18 -1
- package/src/components/editor/index.tsx +140 -21
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +47 -58
- package/src/components/editor/selection-manager.tsx +104 -42
- package/src/components/editor/snapshot-capture-overlay.tsx +298 -39
- package/src/components/editor/snapshot-capture.test.ts +422 -0
- package/src/components/editor/snapshot-capture.ts +145 -0
- package/src/components/editor/thumbnail-generator.tsx +284 -237
- package/src/components/editor/use-floorplan-background-placement.ts +53 -5
- package/src/components/editor/wall-move-side-handles.tsx +299 -7
- package/src/components/editor-2d/floorplan-group-move.tsx +67 -33
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +3 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +129 -38
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +14 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +16 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +2 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +52 -3
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +196 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +180 -32
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +402 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.ts +371 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +347 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +281 -305
- package/src/components/systems/roof/roof-edit-system.test.tsx +120 -0
- package/src/components/systems/roof/roof-edit-system.tsx +10 -19
- package/src/components/systems/selection-affordance-manager.tsx +59 -9
- package/src/components/systems/selection-affordance-services.ts +19 -0
- package/src/components/systems/stair/stair-edit-system.tsx +5 -2
- package/src/components/tools/fence/fence-drafting.test.ts +100 -0
- package/src/components/tools/fence/fence-drafting.ts +6 -25
- package/src/components/tools/item/draft-creation.test.ts +12 -0
- package/src/components/tools/item/draft-creation.ts +10 -0
- package/src/components/tools/item/face-host-commit.test.ts +65 -0
- package/src/components/tools/item/face-host-commit.ts +56 -0
- package/src/components/tools/item/face-host-preview.test.ts +77 -0
- package/src/components/tools/item/face-host-preview.ts +82 -0
- package/src/components/tools/item/move-tool.tsx +4 -3
- package/src/components/tools/item/placement-math.test.ts +11 -1
- package/src/components/tools/item/placement-math.ts +0 -20
- package/src/components/tools/item/placement-strategies.test.ts +331 -4
- package/src/components/tools/item/placement-strategies.ts +135 -2
- package/src/components/tools/item/placement-surface.test.ts +61 -0
- package/src/components/tools/item/placement-surface.ts +24 -0
- package/src/components/tools/item/placement-types.ts +6 -0
- package/src/components/tools/item/test-face-host.ts +121 -0
- package/src/components/tools/item/use-draft-node.test.tsx +336 -0
- package/src/components/tools/item/use-draft-node.ts +26 -4
- package/src/components/tools/item/use-placement-coordinator.tsx +428 -153
- package/src/components/tools/registered-tool-install-lifecycle.test.tsx +256 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +10 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +306 -110
- package/src/components/tools/registry-tool-context.tsx +30 -0
- package/src/components/tools/select/box-select-tool.tsx +3 -1
- package/src/components/tools/select/marquee-footprint.test.ts +53 -0
- package/src/components/tools/select/marquee-footprint.ts +29 -0
- package/src/components/tools/shared/placement-box.tsx +57 -7
- package/src/components/tools/shared/placement-dimension-guides.tsx +121 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +319 -17
- package/src/components/tools/shared/pointer-support-cap.ts +45 -8
- package/src/components/tools/site/terrain-sculpt-grid.tsx +9 -5
- package/src/components/tools/site/terrain-sculpt-tool.tsx +1 -1
- package/src/components/tools/stair/stair-tool.tsx +127 -74
- package/src/components/tools/tool-manager.tsx +25 -8
- package/src/components/tools/wall/wall-drafting.test.ts +320 -2
- package/src/components/tools/wall/wall-drafting.ts +23 -114
- package/src/components/ui/action-menu/control-modes.tsx +9 -6
- package/src/components/ui/action-menu/index.tsx +11 -1
- package/src/components/ui/command-palette/editor-commands.tsx +9 -12
- package/src/components/ui/command-palette/index.tsx +7 -2
- package/src/components/ui/controls/material-paint-panel.tsx +6 -4
- package/src/components/ui/controls/material-picker.tsx +4 -4
- package/src/components/ui/controls/scene-material-list.tsx +6 -5
- package/src/components/ui/controls/segmented-control.tsx +8 -1
- package/src/components/ui/controls/slider-control.tsx +19 -3
- package/src/components/ui/controls/toggle-control.tsx +18 -5
- package/src/components/ui/controls/tool-options-panel.tsx +126 -0
- package/src/components/ui/floating-level-selector.tsx +22 -5
- package/src/components/ui/helpers/contextual-helper-panel.tsx +9 -0
- package/src/components/ui/helpers/helper-manager.tsx +47 -11
- package/src/components/ui/helpers/item-helper.tsx +1 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +21 -0
- package/src/components/ui/panels/homogeneous-selection.test.ts +80 -0
- package/src/components/ui/panels/homogeneous-selection.ts +47 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +11 -5
- package/src/components/ui/panels/multi-field-value.test.ts +209 -0
- package/src/components/ui/panels/multi-field-value.ts +163 -0
- package/src/components/ui/panels/multi-height-mode.tsx +185 -0
- package/src/components/ui/panels/multi-parametric-inspector.tsx +263 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +48 -37
- package/src/components/ui/panels/node-display.ts +4 -0
- package/src/components/ui/panels/panel-manager.tsx +77 -3
- package/src/components/ui/panels/parametric-field-control.tsx +173 -0
- package/src/components/ui/panels/parametric-field-utils.ts +18 -0
- package/src/components/ui/panels/parametric-inspector.tsx +29 -162
- package/src/components/ui/panels/reference-panel.tsx +31 -1
- package/src/components/ui/primitives/shortcut-token.tsx +23 -1
- package/src/components/ui/scene-loader.tsx +35 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +390 -36
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +20 -23
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +140 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +109 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +19 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +116 -46
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +28 -11
- package/src/components/ui/sidebar/panels/site-panel/tree-node.test.ts +8 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +14 -6
- package/src/components/ui/sidebar/tab-bar.tsx +2 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +17 -2
- package/src/components/viewer/viewer-scene-header.tsx +2 -1
- package/src/components/viewer/viewer-stage.tsx +4 -1
- package/src/components/viewer-overlay.tsx +1 -22
- package/src/components/viewer-zone-system.tsx +6 -2
- package/src/components/walkthrough-hud.tsx +19 -10
- package/src/hooks/use-auto-save.test.ts +73 -1
- package/src/hooks/use-auto-save.ts +76 -5
- package/src/hooks/use-ceiling-events.test.ts +122 -0
- package/src/hooks/use-drag-action.ts +17 -3
- package/src/hooks/use-grid-events.test.ts +240 -0
- package/src/hooks/use-grid-events.ts +190 -11
- package/src/hooks/use-keyboard.test.ts +244 -0
- package/src/hooks/use-keyboard.ts +129 -62
- package/src/hooks/use-registered-tool-enabled.ts +20 -0
- package/src/hooks/use-save-shortcut.ts +33 -0
- package/src/index.tsx +82 -4
- package/src/lib/active-placement-surface.test.ts +16 -0
- package/src/lib/active-placement-surface.ts +14 -0
- package/src/lib/ceiling-surface-raycast.test.ts +64 -0
- package/src/lib/ceiling-surface-raycast.ts +12 -0
- package/src/lib/contextual-help-extension.ts +17 -0
- package/src/lib/continuation.test.ts +11 -0
- package/src/lib/continuation.ts +8 -1
- package/src/lib/direct-manipulation.test.ts +132 -0
- package/src/lib/direct-manipulation.ts +38 -1
- package/src/lib/editor-api.ts +6 -3
- package/src/lib/elevation-guides.test.ts +28 -1
- package/src/lib/elevation-guides.ts +22 -7
- package/src/lib/floating-menu-scale.test.ts +29 -0
- package/src/lib/floating-menu-scale.ts +14 -0
- package/src/lib/floorplan/apply-alignment.test.ts +47 -2
- package/src/lib/floorplan/apply-alignment.ts +10 -1
- package/src/lib/floorplan/floorplan-export.test.ts +397 -1
- package/src/lib/floorplan/floorplan-export.tsx +111 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +26 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +32 -24
- package/src/lib/floorplan/floorplan-readonly.ts +1 -0
- package/src/lib/floorplan-grid-event-point.test.ts +29 -0
- package/src/lib/floorplan-grid-event-point.ts +13 -0
- package/src/lib/glb-export.test.ts +1169 -21
- package/src/lib/glb-export.ts +629 -101
- package/src/lib/grid-event-presentation.ts +16 -0
- package/src/lib/history.test.ts +528 -147
- package/src/lib/history.ts +81 -7
- package/src/lib/host-tree-children.test.ts +27 -0
- package/src/lib/host-tree-children.ts +79 -0
- package/src/lib/interaction/hot-set.test.ts +10 -1
- package/src/lib/interaction/hot-set.ts +3 -1
- package/src/lib/interaction/overlay-policy.test.ts +33 -1
- package/src/lib/interaction/overlay-policy.ts +16 -0
- package/src/lib/interaction/registered-drafting.ts +36 -0
- package/src/lib/interaction/scope.ts +31 -0
- package/src/lib/level-print-export.test.ts +484 -0
- package/src/lib/level-print-export.ts +526 -0
- package/src/lib/local-project-presentation-persistence.test.ts +302 -0
- package/src/lib/local-project-presentation-persistence.ts +335 -0
- package/src/lib/material-paint.ts +2 -0
- package/src/lib/model-export.ts +29 -0
- package/src/lib/paint-preview-owner.ts +71 -0
- package/src/lib/planar-cursor-placement.test.ts +178 -1
- package/src/lib/planar-cursor-placement.ts +61 -5
- package/src/lib/plugin-panels.test.ts +92 -1
- package/src/lib/plugin-panels.ts +48 -0
- package/src/lib/portable-export.test.ts +366 -0
- package/src/lib/portable-export.ts +969 -0
- package/src/lib/print-3mf.test.ts +86 -0
- package/src/lib/print-3mf.ts +161 -0
- package/src/lib/print-content-scope.test.ts +107 -0
- package/src/lib/print-content-scope.ts +90 -0
- package/src/lib/print-export.test.ts +294 -0
- package/src/lib/print-export.ts +735 -0
- package/src/lib/print-feature-thickness.test.ts +147 -0
- package/src/lib/print-feature-thickness.ts +180 -0
- package/src/lib/print-golden-house.test-fixture.ts +352 -0
- package/src/lib/print-golden-house.test.ts +249 -0
- package/src/lib/print-roof-solids.test.ts +133 -0
- package/src/lib/print-roof-solids.ts +630 -0
- package/src/lib/print-shell-compiler-baseline.test.ts +419 -0
- package/src/lib/print-shell-compiler-baseline.ts +225 -0
- package/src/lib/print-shell-compiler-manifold-core.ts +330 -0
- package/src/lib/print-shell-compiler-manifold-worker.ts +187 -0
- package/src/lib/print-shell-compiler-manifold.worker.ts +20 -0
- package/src/lib/print-shell-compiler-mesh-data.ts +32 -0
- package/src/lib/print-shell-compiler-protocol.ts +30 -0
- package/src/lib/print-shell-compiler.ts +273 -0
- package/src/lib/print-wall-solids.test.ts +194 -0
- package/src/lib/print-wall-solids.ts +328 -0
- package/src/lib/rigid-plan-svg-transform.test.ts +35 -0
- package/src/lib/rigid-plan-svg-transform.ts +24 -0
- package/src/lib/scene.ts +22 -8
- package/src/lib/selection-routing.test.ts +105 -1
- package/src/lib/selection-routing.ts +19 -0
- package/src/lib/sfx-bus.ts +4 -0
- package/src/lib/sfx-player.ts +8 -0
- package/src/lib/snapping-mode.test.ts +32 -4
- package/src/lib/snapping-mode.ts +11 -7
- package/src/lib/usdz-export.ts +38 -0
- package/src/lib/walkthrough-pointer-lock.ts +47 -0
- package/src/store/live-draft-preview-stores.test.ts +3 -0
- package/src/store/terrain-sculpt-mode.test.ts +3 -6
- package/src/store/tool-mode.test.ts +87 -0
- package/src/store/use-camera-hint-focus.ts +48 -0
- package/src/store/use-editor.tsx +299 -188
- package/src/store/use-floorplan-draft-preview.ts +9 -0
- package/src/store/use-interaction-scope.test.ts +23 -1
- package/src/store/use-interaction-scope.ts +63 -2
- package/src/store/use-placement-preview.ts +56 -3
- package/src/components/tools/roof/roof-tool.tsx +0 -656
- package/src/components/ui/helpers/roof-helper.tsx +0 -14
|
@@ -1,656 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AlignmentAnchor,
|
|
3
|
-
type AnyNode,
|
|
4
|
-
type AnyNodeId,
|
|
5
|
-
collectAlignmentAnchors,
|
|
6
|
-
emitter,
|
|
7
|
-
type GridEvent,
|
|
8
|
-
type LevelNode,
|
|
9
|
-
RoofNode,
|
|
10
|
-
RoofSegmentNode,
|
|
11
|
-
resolveBuildingForLevel,
|
|
12
|
-
sceneRegistry,
|
|
13
|
-
useScene,
|
|
14
|
-
type WallNode,
|
|
15
|
-
wallSegmentAnchors,
|
|
16
|
-
} from '@pascal-app/core'
|
|
17
|
-
import { clearSurfacePlanSnapFeedback, resolveSurfacePlanPointSnap } from '@pascal-app/editor'
|
|
18
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
19
|
-
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
20
|
-
import * as THREE from 'three'
|
|
21
|
-
import {
|
|
22
|
-
BufferGeometry,
|
|
23
|
-
DoubleSide,
|
|
24
|
-
Float32BufferAttribute,
|
|
25
|
-
type Group,
|
|
26
|
-
type Line,
|
|
27
|
-
Vector3,
|
|
28
|
-
} from 'three'
|
|
29
|
-
import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
|
|
30
|
-
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
31
|
-
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
32
|
-
import { snapWorldXZForActiveBuilding } from '../../../lib/world-grid-snap'
|
|
33
|
-
import useEditor, { isGridSnapActive, isMagneticSnapActive } from '../../../store/use-editor'
|
|
34
|
-
import { useFloorplanDraftPreview } from '../../../store/use-floorplan-draft-preview'
|
|
35
|
-
import { CursorSphere } from '../shared/cursor-sphere'
|
|
36
|
-
|
|
37
|
-
const DEFAULT_WALL_HEIGHT = 0.5
|
|
38
|
-
const DEFAULT_PITCH_DEG = 40
|
|
39
|
-
const GRID_OFFSET = 0.02
|
|
40
|
-
|
|
41
|
-
// Walls that are direct children of a level.
|
|
42
|
-
function getLevelWalls(
|
|
43
|
-
levelId: string | null,
|
|
44
|
-
nodes: Readonly<Record<string, AnyNode>>,
|
|
45
|
-
): WallNode[] {
|
|
46
|
-
if (!levelId) return []
|
|
47
|
-
const levelNode = nodes[levelId]
|
|
48
|
-
if (levelNode?.type !== 'level') return []
|
|
49
|
-
return (levelNode as LevelNode).children
|
|
50
|
-
.map((childId) => nodes[childId])
|
|
51
|
-
.filter((node): node is WallNode => node?.type === 'wall')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Walls on the level directly beneath the active one. Levels share the same
|
|
55
|
-
// local XZ origin (they only differ in world Y), so these walls live in the
|
|
56
|
-
// identical coordinate frame and feed straight into both the alignment pool
|
|
57
|
-
// and the magnetic wall-snap pipeline — letting a roof drawn on the upper
|
|
58
|
-
// floor snap onto the wall corners of the floor below.
|
|
59
|
-
function getBelowLevelWalls(
|
|
60
|
-
currentLevelId: string | null,
|
|
61
|
-
nodes: Readonly<Record<string, AnyNode>>,
|
|
62
|
-
): WallNode[] {
|
|
63
|
-
if (!currentLevelId) return []
|
|
64
|
-
const currentLevel = nodes[currentLevelId]
|
|
65
|
-
if (currentLevel?.type !== 'level') return []
|
|
66
|
-
const buildingId = resolveBuildingForLevel(currentLevel.id, nodes)
|
|
67
|
-
if (!buildingId) return []
|
|
68
|
-
const building = nodes[buildingId]
|
|
69
|
-
if (building?.type !== 'building') return []
|
|
70
|
-
const currentIndex = (currentLevel as LevelNode).level
|
|
71
|
-
const belowLevel = (building.children ?? [])
|
|
72
|
-
.map((childId) => nodes[childId])
|
|
73
|
-
.filter((node): node is LevelNode => node?.type === 'level' && node.level < currentIndex)
|
|
74
|
-
.sort((a, b) => b.level - a.level)[0]
|
|
75
|
-
return getLevelWalls(belowLevel?.id ?? null, nodes)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Current-level + floor-below walls — the magnetic snap targets the roof draft
|
|
79
|
-
// locks onto (corners, midpoints, crossings, wall bodies), matching the wall
|
|
80
|
-
// tool. Same coordinate frame, so no transform is needed.
|
|
81
|
-
function getRoofSnapWalls(
|
|
82
|
-
currentLevelId: string | null,
|
|
83
|
-
nodes: Readonly<Record<string, AnyNode>>,
|
|
84
|
-
): WallNode[] {
|
|
85
|
-
return [...getLevelWalls(currentLevelId, nodes), ...getBelowLevelWalls(currentLevelId, nodes)]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Current-level alignment anchors plus the floor-below wall corners.
|
|
89
|
-
function collectRoofAlignmentAnchors(
|
|
90
|
-
nodes: Readonly<Record<string, AnyNode>>,
|
|
91
|
-
currentLevelId: string | null,
|
|
92
|
-
): AlignmentAnchor[] {
|
|
93
|
-
return [
|
|
94
|
-
...collectAlignmentAnchors(nodes, '', currentLevelId),
|
|
95
|
-
...getBelowLevelWalls(currentLevelId, nodes).flatMap((wall) =>
|
|
96
|
-
wallSegmentAnchors(wall.id, wall.start, wall.end, wall.thickness),
|
|
97
|
-
),
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Creates a roof group with one default gable segment
|
|
103
|
-
*/
|
|
104
|
-
const commitRoofPlacement = (
|
|
105
|
-
levelId: LevelNode['id'],
|
|
106
|
-
corner1: [number, number, number],
|
|
107
|
-
corner2: [number, number, number],
|
|
108
|
-
selectedIds: string[],
|
|
109
|
-
): AnyNode['id'] => {
|
|
110
|
-
const { createNode, createNodes, nodes } = useScene.getState()
|
|
111
|
-
|
|
112
|
-
// A placed roof preset seeds `toolDefaults.roof` with the flattened
|
|
113
|
-
// subtree params (roofType, pitch, wallHeight, overhang, materials, …)
|
|
114
|
-
// before the tool activates. The footprint (width/depth) and placement
|
|
115
|
-
// come from the drawn rectangle and always win; the segment carries the
|
|
116
|
-
// shape/material params, the roof container picks up the materials.
|
|
117
|
-
const defaults = useEditor.getState().toolDefaults.roof ?? {}
|
|
118
|
-
|
|
119
|
-
const centerX = (corner1[0] + corner2[0]) / 2
|
|
120
|
-
const centerZ = (corner1[2] + corner2[2]) / 2
|
|
121
|
-
|
|
122
|
-
const width = Math.max(Math.abs(corner2[0] - corner1[0]), 1)
|
|
123
|
-
const depth = Math.max(Math.abs(corner2[2] - corner1[2]), 1)
|
|
124
|
-
|
|
125
|
-
// Determine if there is an active roof node we should add to
|
|
126
|
-
let targetRoofId: RoofNode['id'] | null = null
|
|
127
|
-
const selectedId = selectedIds[0]
|
|
128
|
-
if (selectedIds.length === 1 && selectedId) {
|
|
129
|
-
const selectedNode = nodes[selectedId as AnyNodeId]
|
|
130
|
-
if (selectedNode?.type === 'roof') {
|
|
131
|
-
targetRoofId = selectedNode.id
|
|
132
|
-
} else if (selectedNode?.type === 'roof-segment' && selectedNode.parentId) {
|
|
133
|
-
targetRoofId = selectedNode.parentId as RoofNode['id']
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (targetRoofId) {
|
|
138
|
-
const targetRoof = nodes[targetRoofId] as RoofNode
|
|
139
|
-
let localX = centerX
|
|
140
|
-
let localZ = centerZ
|
|
141
|
-
|
|
142
|
-
// Convert world coordinates to the local space of the parent roof
|
|
143
|
-
const targetObj = sceneRegistry.nodes.get(targetRoofId)
|
|
144
|
-
if (targetObj) {
|
|
145
|
-
const worldVec = new THREE.Vector3(centerX, 0, centerZ)
|
|
146
|
-
targetObj.worldToLocal(worldVec)
|
|
147
|
-
localX = worldVec.x
|
|
148
|
-
localZ = worldVec.z
|
|
149
|
-
} else {
|
|
150
|
-
// Math fallback if mesh isn't ready
|
|
151
|
-
const dx = centerX - targetRoof.position[0]
|
|
152
|
-
const dz = centerZ - targetRoof.position[2]
|
|
153
|
-
const angle = -targetRoof.rotation
|
|
154
|
-
localX = dx * Math.cos(angle) - dz * Math.sin(angle)
|
|
155
|
-
localZ = dx * Math.sin(angle) + dz * Math.cos(angle)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const segment = RoofSegmentNode.parse({
|
|
159
|
-
wallHeight: DEFAULT_WALL_HEIGHT,
|
|
160
|
-
pitch: DEFAULT_PITCH_DEG,
|
|
161
|
-
roofType: 'gable',
|
|
162
|
-
...defaults,
|
|
163
|
-
width,
|
|
164
|
-
depth,
|
|
165
|
-
position: [localX, 0, localZ],
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
createNode(segment, targetRoofId as AnyNode['id'])
|
|
169
|
-
sfxEmitter.emit('sfx:structure-build')
|
|
170
|
-
return segment.id // Returns segment ID so it can be selected immediately
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Count existing roofs for naming
|
|
174
|
-
const roofCount = Object.values(nodes).filter((n) => n.type === 'roof').length
|
|
175
|
-
const name = `Roof ${roofCount + 1}`
|
|
176
|
-
|
|
177
|
-
// Create the segment first (centered in its new parent)
|
|
178
|
-
const segment = RoofSegmentNode.parse({
|
|
179
|
-
wallHeight: DEFAULT_WALL_HEIGHT,
|
|
180
|
-
pitch: DEFAULT_PITCH_DEG,
|
|
181
|
-
roofType: 'gable',
|
|
182
|
-
...defaults,
|
|
183
|
-
width,
|
|
184
|
-
depth,
|
|
185
|
-
position: [0, 0, 0],
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
// Create the roof container. Segment-shaped params (roofType, pitch, …) are
|
|
189
|
-
// dropped by the RoofNode schema; surface materials in `defaults` carry over.
|
|
190
|
-
const roof = RoofNode.parse({
|
|
191
|
-
...defaults,
|
|
192
|
-
name,
|
|
193
|
-
position: [centerX, 0, centerZ],
|
|
194
|
-
children: [segment.id],
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
// Create roof first (so segment can be parented to it), then segment
|
|
198
|
-
createNodes([
|
|
199
|
-
{ node: roof, parentId: levelId },
|
|
200
|
-
{ node: segment, parentId: roof.id },
|
|
201
|
-
])
|
|
202
|
-
|
|
203
|
-
sfxEmitter.emit('sfx:structure-build')
|
|
204
|
-
return roof.id
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
type PreviewState = {
|
|
208
|
-
corner1: [number, number, number] | null
|
|
209
|
-
cursorPosition: [number, number, number]
|
|
210
|
-
levelY: number
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function buildRoofGhostGeometry(
|
|
214
|
-
width: number,
|
|
215
|
-
depth: number,
|
|
216
|
-
wallHeight: number,
|
|
217
|
-
pitchDeg: number,
|
|
218
|
-
) {
|
|
219
|
-
const safeWidth = Math.max(width, 0.1)
|
|
220
|
-
const safeDepth = Math.max(depth, 0.1)
|
|
221
|
-
const halfWidth = safeWidth / 2
|
|
222
|
-
const halfDepth = safeDepth / 2
|
|
223
|
-
const ridgeHeight = wallHeight + Math.tan((pitchDeg * Math.PI) / 180) * halfDepth
|
|
224
|
-
|
|
225
|
-
const vertices = [
|
|
226
|
-
// Front slope
|
|
227
|
-
-halfWidth,
|
|
228
|
-
wallHeight,
|
|
229
|
-
-halfDepth,
|
|
230
|
-
halfWidth,
|
|
231
|
-
wallHeight,
|
|
232
|
-
-halfDepth,
|
|
233
|
-
halfWidth,
|
|
234
|
-
ridgeHeight,
|
|
235
|
-
0,
|
|
236
|
-
|
|
237
|
-
-halfWidth,
|
|
238
|
-
wallHeight,
|
|
239
|
-
-halfDepth,
|
|
240
|
-
halfWidth,
|
|
241
|
-
ridgeHeight,
|
|
242
|
-
0,
|
|
243
|
-
-halfWidth,
|
|
244
|
-
ridgeHeight,
|
|
245
|
-
0,
|
|
246
|
-
|
|
247
|
-
// Back slope
|
|
248
|
-
-halfWidth,
|
|
249
|
-
ridgeHeight,
|
|
250
|
-
0,
|
|
251
|
-
halfWidth,
|
|
252
|
-
ridgeHeight,
|
|
253
|
-
0,
|
|
254
|
-
halfWidth,
|
|
255
|
-
wallHeight,
|
|
256
|
-
halfDepth,
|
|
257
|
-
|
|
258
|
-
-halfWidth,
|
|
259
|
-
ridgeHeight,
|
|
260
|
-
0,
|
|
261
|
-
halfWidth,
|
|
262
|
-
wallHeight,
|
|
263
|
-
halfDepth,
|
|
264
|
-
-halfWidth,
|
|
265
|
-
wallHeight,
|
|
266
|
-
halfDepth,
|
|
267
|
-
|
|
268
|
-
// Left gable
|
|
269
|
-
-halfWidth,
|
|
270
|
-
wallHeight,
|
|
271
|
-
-halfDepth,
|
|
272
|
-
-halfWidth,
|
|
273
|
-
ridgeHeight,
|
|
274
|
-
0,
|
|
275
|
-
-halfWidth,
|
|
276
|
-
wallHeight,
|
|
277
|
-
halfDepth,
|
|
278
|
-
|
|
279
|
-
// Right gable
|
|
280
|
-
halfWidth,
|
|
281
|
-
wallHeight,
|
|
282
|
-
-halfDepth,
|
|
283
|
-
halfWidth,
|
|
284
|
-
wallHeight,
|
|
285
|
-
halfDepth,
|
|
286
|
-
halfWidth,
|
|
287
|
-
ridgeHeight,
|
|
288
|
-
0,
|
|
289
|
-
]
|
|
290
|
-
|
|
291
|
-
const geometry = new BufferGeometry()
|
|
292
|
-
geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3))
|
|
293
|
-
geometry.computeVertexNormals()
|
|
294
|
-
return geometry
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function buildRoofGhostEdges(width: number, depth: number, wallHeight: number, pitchDeg: number) {
|
|
298
|
-
const safeWidth = Math.max(width, 0.1)
|
|
299
|
-
const safeDepth = Math.max(depth, 0.1)
|
|
300
|
-
const halfWidth = safeWidth / 2
|
|
301
|
-
const halfDepth = safeDepth / 2
|
|
302
|
-
const ridgeHeight = wallHeight + Math.tan((pitchDeg * Math.PI) / 180) * halfDepth
|
|
303
|
-
|
|
304
|
-
const vertices = [
|
|
305
|
-
// Base rectangle
|
|
306
|
-
-halfWidth,
|
|
307
|
-
wallHeight,
|
|
308
|
-
-halfDepth,
|
|
309
|
-
halfWidth,
|
|
310
|
-
wallHeight,
|
|
311
|
-
-halfDepth,
|
|
312
|
-
halfWidth,
|
|
313
|
-
wallHeight,
|
|
314
|
-
-halfDepth,
|
|
315
|
-
halfWidth,
|
|
316
|
-
wallHeight,
|
|
317
|
-
halfDepth,
|
|
318
|
-
halfWidth,
|
|
319
|
-
wallHeight,
|
|
320
|
-
halfDepth,
|
|
321
|
-
-halfWidth,
|
|
322
|
-
wallHeight,
|
|
323
|
-
halfDepth,
|
|
324
|
-
-halfWidth,
|
|
325
|
-
wallHeight,
|
|
326
|
-
halfDepth,
|
|
327
|
-
-halfWidth,
|
|
328
|
-
wallHeight,
|
|
329
|
-
-halfDepth,
|
|
330
|
-
|
|
331
|
-
// Ridge + gable edges
|
|
332
|
-
-halfWidth,
|
|
333
|
-
ridgeHeight,
|
|
334
|
-
0,
|
|
335
|
-
halfWidth,
|
|
336
|
-
ridgeHeight,
|
|
337
|
-
0,
|
|
338
|
-
-halfWidth,
|
|
339
|
-
wallHeight,
|
|
340
|
-
-halfDepth,
|
|
341
|
-
-halfWidth,
|
|
342
|
-
ridgeHeight,
|
|
343
|
-
0,
|
|
344
|
-
-halfWidth,
|
|
345
|
-
ridgeHeight,
|
|
346
|
-
0,
|
|
347
|
-
-halfWidth,
|
|
348
|
-
wallHeight,
|
|
349
|
-
halfDepth,
|
|
350
|
-
halfWidth,
|
|
351
|
-
wallHeight,
|
|
352
|
-
-halfDepth,
|
|
353
|
-
halfWidth,
|
|
354
|
-
ridgeHeight,
|
|
355
|
-
0,
|
|
356
|
-
halfWidth,
|
|
357
|
-
ridgeHeight,
|
|
358
|
-
0,
|
|
359
|
-
halfWidth,
|
|
360
|
-
wallHeight,
|
|
361
|
-
halfDepth,
|
|
362
|
-
]
|
|
363
|
-
|
|
364
|
-
const geometry = new BufferGeometry()
|
|
365
|
-
geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3))
|
|
366
|
-
return geometry
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
export const RoofTool: React.FC = () => {
|
|
370
|
-
const cursorRef = useRef<Group>(null)
|
|
371
|
-
const outlineRef = useRef<Line>(null!)
|
|
372
|
-
const currentLevelId = useViewer((state) => state.selection.levelId)
|
|
373
|
-
const selectedIds = useViewer((state) => state.selection.selectedIds)
|
|
374
|
-
const setSelection = useViewer((state) => state.setSelection)
|
|
375
|
-
|
|
376
|
-
const selectedIdsRef = useRef(selectedIds)
|
|
377
|
-
useEffect(() => {
|
|
378
|
-
selectedIdsRef.current = selectedIds
|
|
379
|
-
}, [selectedIds])
|
|
380
|
-
|
|
381
|
-
// Clear preset-seeded defaults on deactivation so a later manual roof draw
|
|
382
|
-
// isn't built with a stale preset's parameters. Unmount-only.
|
|
383
|
-
useEffect(() => () => useEditor.getState().setToolDefaults('roof', null), [])
|
|
384
|
-
|
|
385
|
-
const corner1Ref = useRef<[number, number, number] | null>(null)
|
|
386
|
-
const previousGridPosRef = useRef<[number, number] | null>(null)
|
|
387
|
-
const [preview, setPreview] = useState<PreviewState>({
|
|
388
|
-
corner1: null,
|
|
389
|
-
cursorPosition: [0, 0, 0],
|
|
390
|
-
levelY: 0,
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
useEffect(() => {
|
|
394
|
-
if (!currentLevelId) return
|
|
395
|
-
|
|
396
|
-
outlineRef.current.geometry = new BufferGeometry()
|
|
397
|
-
|
|
398
|
-
// Alignment candidates — anchors of every alignable object on the active
|
|
399
|
-
// level plus the wall corners of the floor directly below, so a roof drawn
|
|
400
|
-
// on the upper floor aligns to the walls beneath it. Refreshed after each
|
|
401
|
-
// roof commits. Both corners of the rectangle align.
|
|
402
|
-
let alignmentCandidates = collectRoofAlignmentAnchors(useScene.getState().nodes, currentLevelId)
|
|
403
|
-
|
|
404
|
-
// Resolve a grid:move/click into the drafted corner via the shared surface
|
|
405
|
-
// snap pipeline: magnetic lock onto wall corners / midpoints / crossings /
|
|
406
|
-
// bodies on the active level + floor below (raising the green beacon),
|
|
407
|
-
// falling back to alignment guides, then to the world-grid snap. The same
|
|
408
|
-
// path the slab/ceiling tools use, so the beacon and coloring match. The
|
|
409
|
-
// pipeline reads the snapping mode itself (Shift bypass, magnetic on/off),
|
|
410
|
-
// so this tool never inspects the flags. `levelId` is intentionally omitted
|
|
411
|
-
// so the explicit floor-below `walls` aren't filtered back out.
|
|
412
|
-
const resolveDraftPoint = (event: GridEvent): [number, number] => {
|
|
413
|
-
const rawPoint: [number, number] = [event.localPosition[0], event.localPosition[2]]
|
|
414
|
-
const gridFallback: [number, number] = isGridSnapActive()
|
|
415
|
-
? snapWorldXZForActiveBuilding(
|
|
416
|
-
event.position[0],
|
|
417
|
-
event.position[2],
|
|
418
|
-
useEditor.getState().gridSnapStep,
|
|
419
|
-
).local
|
|
420
|
-
: rawPoint
|
|
421
|
-
const nodes = useScene.getState().nodes
|
|
422
|
-
return resolveSurfacePlanPointSnap({
|
|
423
|
-
rawPoint,
|
|
424
|
-
fallbackPoint: gridFallback,
|
|
425
|
-
walls: getRoofSnapWalls(currentLevelId, nodes),
|
|
426
|
-
candidates: alignmentCandidates,
|
|
427
|
-
movingId: '__roof-draft__',
|
|
428
|
-
highlightWalls: true,
|
|
429
|
-
}).point
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const updateOutline = (
|
|
433
|
-
corner1: [number, number, number],
|
|
434
|
-
corner2: [number, number, number],
|
|
435
|
-
) => {
|
|
436
|
-
const gridY = corner1[1] + GRID_OFFSET
|
|
437
|
-
|
|
438
|
-
const groundPoints = [
|
|
439
|
-
new Vector3(corner1[0], gridY, corner1[2]),
|
|
440
|
-
new Vector3(corner2[0], gridY, corner1[2]),
|
|
441
|
-
new Vector3(corner2[0], gridY, corner2[2]),
|
|
442
|
-
new Vector3(corner1[0], gridY, corner2[2]),
|
|
443
|
-
new Vector3(corner1[0], gridY, corner1[2]),
|
|
444
|
-
]
|
|
445
|
-
|
|
446
|
-
outlineRef.current.geometry.dispose()
|
|
447
|
-
outlineRef.current.geometry = new BufferGeometry().setFromPoints(groundPoints)
|
|
448
|
-
outlineRef.current.visible = true
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const onGridMove = (event: GridEvent) => {
|
|
452
|
-
if (!cursorRef.current) return
|
|
453
|
-
|
|
454
|
-
const [gridX, gridZ] = resolveDraftPoint(event)
|
|
455
|
-
const y = event.localPosition[1]
|
|
456
|
-
|
|
457
|
-
const cursorPosition: [number, number, number] = [gridX, y, gridZ]
|
|
458
|
-
const gridY = y + GRID_OFFSET
|
|
459
|
-
|
|
460
|
-
cursorRef.current.position.set(gridX, gridY, gridZ)
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
(isGridSnapActive() || isMagneticSnapActive()) &&
|
|
464
|
-
corner1Ref.current &&
|
|
465
|
-
previousGridPosRef.current &&
|
|
466
|
-
(gridX !== previousGridPosRef.current[0] || gridZ !== previousGridPosRef.current[1])
|
|
467
|
-
) {
|
|
468
|
-
sfxEmitter.emit('sfx:grid-snap')
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
previousGridPosRef.current = [gridX, gridZ]
|
|
472
|
-
|
|
473
|
-
setPreview({
|
|
474
|
-
corner1: corner1Ref.current,
|
|
475
|
-
cursorPosition,
|
|
476
|
-
levelY: y,
|
|
477
|
-
})
|
|
478
|
-
|
|
479
|
-
if (corner1Ref.current) {
|
|
480
|
-
const draftPreview = useFloorplanDraftPreview.getState()
|
|
481
|
-
draftPreview.setRoofDraftStart([corner1Ref.current[0], corner1Ref.current[2]])
|
|
482
|
-
draftPreview.setRoofDraftEnd([gridX, gridZ])
|
|
483
|
-
updateOutline(corner1Ref.current, cursorPosition)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const onGridClick = (event: GridEvent) => {
|
|
488
|
-
if (!currentLevelId) return
|
|
489
|
-
|
|
490
|
-
const [gridX, gridZ] = resolveDraftPoint(event)
|
|
491
|
-
const y = event.localPosition[1]
|
|
492
|
-
|
|
493
|
-
if (corner1Ref.current) {
|
|
494
|
-
const roofId = commitRoofPlacement(
|
|
495
|
-
currentLevelId,
|
|
496
|
-
corner1Ref.current,
|
|
497
|
-
[gridX, y, gridZ],
|
|
498
|
-
selectedIdsRef.current,
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
setSelection({ selectedIds: [roofId as AnyNode['id']] })
|
|
502
|
-
|
|
503
|
-
corner1Ref.current = null
|
|
504
|
-
const draftPreview = useFloorplanDraftPreview.getState()
|
|
505
|
-
draftPreview.setRoofDraftStart(null)
|
|
506
|
-
draftPreview.setRoofDraftEnd(null)
|
|
507
|
-
outlineRef.current.visible = false
|
|
508
|
-
alignmentCandidates = collectRoofAlignmentAnchors(useScene.getState().nodes, currentLevelId)
|
|
509
|
-
clearSurfacePlanSnapFeedback()
|
|
510
|
-
} else {
|
|
511
|
-
corner1Ref.current = [gridX, y, gridZ]
|
|
512
|
-
const draftPreview = useFloorplanDraftPreview.getState()
|
|
513
|
-
draftPreview.setRoofDraftStart([gridX, gridZ])
|
|
514
|
-
draftPreview.setRoofDraftEnd([gridX, gridZ])
|
|
515
|
-
sfxEmitter.emit('sfx:structure-build-start')
|
|
516
|
-
setPreview((prev) => ({
|
|
517
|
-
...prev,
|
|
518
|
-
corner1: corner1Ref.current,
|
|
519
|
-
}))
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const onCancel = () => {
|
|
524
|
-
if (corner1Ref.current) {
|
|
525
|
-
markToolCancelConsumed()
|
|
526
|
-
corner1Ref.current = null
|
|
527
|
-
const draftPreview = useFloorplanDraftPreview.getState()
|
|
528
|
-
draftPreview.setRoofDraftStart(null)
|
|
529
|
-
draftPreview.setRoofDraftEnd(null)
|
|
530
|
-
outlineRef.current.visible = false
|
|
531
|
-
setPreview((prev) => ({ ...prev, corner1: null }))
|
|
532
|
-
}
|
|
533
|
-
clearSurfacePlanSnapFeedback()
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
emitter.on('grid:move', onGridMove)
|
|
537
|
-
emitter.on('grid:click', onGridClick)
|
|
538
|
-
emitter.on('tool:cancel', onCancel)
|
|
539
|
-
|
|
540
|
-
return () => {
|
|
541
|
-
emitter.off('grid:move', onGridMove)
|
|
542
|
-
emitter.off('grid:click', onGridClick)
|
|
543
|
-
emitter.off('tool:cancel', onCancel)
|
|
544
|
-
clearSurfacePlanSnapFeedback()
|
|
545
|
-
|
|
546
|
-
corner1Ref.current = null
|
|
547
|
-
const draftPreview = useFloorplanDraftPreview.getState()
|
|
548
|
-
draftPreview.setRoofDraftStart(null)
|
|
549
|
-
draftPreview.setRoofDraftEnd(null)
|
|
550
|
-
}
|
|
551
|
-
}, [currentLevelId, setSelection])
|
|
552
|
-
|
|
553
|
-
const { corner1, cursorPosition, levelY } = preview
|
|
554
|
-
|
|
555
|
-
const previewDimensions = useMemo(() => {
|
|
556
|
-
if (!corner1) return null
|
|
557
|
-
const length = Math.abs(cursorPosition[0] - corner1[0])
|
|
558
|
-
const width = Math.abs(cursorPosition[2] - corner1[2])
|
|
559
|
-
const centerX = (corner1[0] + cursorPosition[0]) / 2
|
|
560
|
-
const centerZ = (corner1[2] + cursorPosition[2]) / 2
|
|
561
|
-
return { length, width, centerX, centerZ }
|
|
562
|
-
}, [corner1, cursorPosition])
|
|
563
|
-
|
|
564
|
-
const roofGhostGeometry = useMemo(() => {
|
|
565
|
-
if (!previewDimensions) return null
|
|
566
|
-
return buildRoofGhostGeometry(
|
|
567
|
-
previewDimensions.length,
|
|
568
|
-
previewDimensions.width,
|
|
569
|
-
DEFAULT_WALL_HEIGHT,
|
|
570
|
-
DEFAULT_PITCH_DEG,
|
|
571
|
-
)
|
|
572
|
-
}, [previewDimensions])
|
|
573
|
-
|
|
574
|
-
const roofGhostEdges = useMemo(() => {
|
|
575
|
-
if (!previewDimensions) return null
|
|
576
|
-
return buildRoofGhostEdges(
|
|
577
|
-
previewDimensions.length,
|
|
578
|
-
previewDimensions.width,
|
|
579
|
-
DEFAULT_WALL_HEIGHT,
|
|
580
|
-
DEFAULT_PITCH_DEG,
|
|
581
|
-
)
|
|
582
|
-
}, [previewDimensions])
|
|
583
|
-
|
|
584
|
-
useEffect(
|
|
585
|
-
() => () => {
|
|
586
|
-
roofGhostGeometry?.dispose()
|
|
587
|
-
roofGhostEdges?.dispose()
|
|
588
|
-
},
|
|
589
|
-
[roofGhostEdges, roofGhostGeometry],
|
|
590
|
-
)
|
|
591
|
-
|
|
592
|
-
return (
|
|
593
|
-
<group>
|
|
594
|
-
<CursorSphere ref={cursorRef} />
|
|
595
|
-
|
|
596
|
-
{/* @ts-ignore */}
|
|
597
|
-
<line
|
|
598
|
-
frustumCulled={false}
|
|
599
|
-
layers={EDITOR_LAYER}
|
|
600
|
-
// @ts-expect-error
|
|
601
|
-
ref={outlineRef}
|
|
602
|
-
renderOrder={1}
|
|
603
|
-
visible={false}
|
|
604
|
-
>
|
|
605
|
-
<bufferGeometry />
|
|
606
|
-
<lineBasicNodeMaterial
|
|
607
|
-
color="#818cf8"
|
|
608
|
-
depthTest={false}
|
|
609
|
-
depthWrite={false}
|
|
610
|
-
linewidth={2}
|
|
611
|
-
opacity={0.3}
|
|
612
|
-
transparent
|
|
613
|
-
/>
|
|
614
|
-
</line>
|
|
615
|
-
|
|
616
|
-
{corner1 && (
|
|
617
|
-
<CursorSphere
|
|
618
|
-
color="#818cf8"
|
|
619
|
-
position={[corner1[0], levelY + GRID_OFFSET, corner1[2]]}
|
|
620
|
-
showTooltip={false}
|
|
621
|
-
/>
|
|
622
|
-
)}
|
|
623
|
-
|
|
624
|
-
{previewDimensions && previewDimensions.length > 0.1 && previewDimensions.width > 0.1 && (
|
|
625
|
-
<group
|
|
626
|
-
layers={EDITOR_LAYER}
|
|
627
|
-
position={[previewDimensions.centerX, levelY + GRID_OFFSET, previewDimensions.centerZ]}
|
|
628
|
-
>
|
|
629
|
-
{roofGhostGeometry && (
|
|
630
|
-
<mesh geometry={roofGhostGeometry} layers={EDITOR_LAYER} renderOrder={1}>
|
|
631
|
-
<meshBasicMaterial
|
|
632
|
-
color="#818cf8"
|
|
633
|
-
depthTest={false}
|
|
634
|
-
depthWrite={false}
|
|
635
|
-
opacity={0.16}
|
|
636
|
-
side={DoubleSide}
|
|
637
|
-
transparent
|
|
638
|
-
/>
|
|
639
|
-
</mesh>
|
|
640
|
-
)}
|
|
641
|
-
{roofGhostEdges && (
|
|
642
|
-
<lineSegments geometry={roofGhostEdges} layers={EDITOR_LAYER} renderOrder={2}>
|
|
643
|
-
<lineBasicMaterial
|
|
644
|
-
color="#818cf8"
|
|
645
|
-
depthTest={false}
|
|
646
|
-
depthWrite={false}
|
|
647
|
-
opacity={0.5}
|
|
648
|
-
transparent
|
|
649
|
-
/>
|
|
650
|
-
</lineSegments>
|
|
651
|
-
)}
|
|
652
|
-
</group>
|
|
653
|
-
)}
|
|
654
|
-
</group>
|
|
655
|
-
)
|
|
656
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { SnapContext } from '../../../lib/snapping-mode'
|
|
2
|
-
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
3
|
-
|
|
4
|
-
export function RoofHelper({ snapContext }: { snapContext?: SnapContext | null }) {
|
|
5
|
-
return (
|
|
6
|
-
<ContextualHelperPanel
|
|
7
|
-
hints={[
|
|
8
|
-
{ keys: ['Left click'], label: 'Set corner' },
|
|
9
|
-
{ keys: ['Esc'], label: 'Cancel' },
|
|
10
|
-
]}
|
|
11
|
-
snapContext={snapContext}
|
|
12
|
-
/>
|
|
13
|
-
)
|
|
14
|
-
}
|