@pascal-app/editor 0.9.1 → 0.9.2
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/package.json +12 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +63 -25
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +36 -3
- package/src/components/editor/first-person/build-collider-world.ts +32 -5
- package/src/components/editor/first-person-controls.tsx +118 -7
- package/src/components/editor/floating-action-menu.tsx +410 -33
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +1804 -849
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/use-handle-drag.ts +37 -4
- package/src/components/editor/index.tsx +248 -27
- package/src/components/editor/measurement-pill.tsx +62 -22
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +366 -173
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +812 -466
- package/src/components/editor/site-edge-labels.tsx +4 -4
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +255 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +136 -43
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +3 -13
- package/src/components/editor/wall-move-side-handles.tsx +36 -21
- package/src/components/editor/wall-snap-beacon-layer.tsx +160 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +1 -1
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +169 -4
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +300 -59
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1704 -342
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +419 -60
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +80 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +31 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +761 -121
- package/src/components/tools/registry/move-registry-node-tool.tsx +561 -99
- package/src/components/tools/roof/roof-tool.tsx +320 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +88 -36
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +82 -11
- package/src/components/tools/tool-manager.tsx +86 -25
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +180 -103
- package/src/components/tools/wall/wall-snap-geometry.test.ts +44 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +67 -6
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +82 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -93
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +2 -2
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +86 -17
- package/src/components/ui/controls/material-picker.tsx +83 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +1 -1
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +227 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +22 -13
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +15 -2
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +71 -40
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +60 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +40 -17
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +42 -28
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +85 -10
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-keyboard.ts +385 -40
- package/src/index.tsx +219 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +240 -3
- package/src/lib/fresh-planar-placement.ts +68 -1
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +9 -0
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +73 -8
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +50 -13
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +111 -66
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +484 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +11 -3
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/level-name.ts +0 -11
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { emitter, type GridEvent, sceneRegistry } from '@pascal-app/core'
|
|
3
|
+
import { type AnyNodeId, emitter, type GridEvent, sceneRegistry } from '@pascal-app/core'
|
|
4
4
|
import { GRID_LAYER, getSceneTheme, useViewer } from '@pascal-app/viewer'
|
|
5
5
|
import { useFrame } from '@react-three/fiber'
|
|
6
6
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
7
|
-
import {
|
|
7
|
+
import { DoubleSide, type Mesh, PlaneGeometry, Quaternion, Vector2, Vector3 } from 'three'
|
|
8
8
|
import { color, float, fract, fwidth, mix, positionLocal, uniform } from 'three/tsl'
|
|
9
9
|
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
10
10
|
import { useCeilingEvents } from '../../hooks/use-ceiling-events'
|
|
11
11
|
import { useGridEvents } from '../../hooks/use-grid-events'
|
|
12
|
+
import { getPlacementSurface } from '../../lib/active-placement-surface'
|
|
13
|
+
import useEditor, { isGridSnapActive } from '../../store/use-editor'
|
|
14
|
+
import { getMovingNode } from '../../store/use-interaction-scope'
|
|
15
|
+
|
|
16
|
+
// Reveal radius (m) of the cursor-local grid patch shown while placing/moving in
|
|
17
|
+
// grid-snap mode — much tighter than the idle reveal so only the area you're
|
|
18
|
+
// about to snap into lights up.
|
|
19
|
+
const PLACEMENT_REVEAL_RADIUS = 12
|
|
20
|
+
|
|
21
|
+
const UP = new Vector3(0, 1, 0)
|
|
22
|
+
// PlaneGeometry faces +Z; this is the orientation that lays it flat (its normal
|
|
23
|
+
// → world +Y), equivalent to the old `rotation-x={-π/2}`.
|
|
24
|
+
const PLANE_LOCAL_NORMAL = new Vector3(0, 0, 1)
|
|
25
|
+
const HORIZONTAL_QUATERNION = new Quaternion().setFromUnitVectors(PLANE_LOCAL_NORMAL, UP)
|
|
12
26
|
|
|
13
27
|
export const Grid = ({
|
|
14
28
|
cellSize = 0.5,
|
|
@@ -38,6 +52,30 @@ export const Grid = ({
|
|
|
38
52
|
const effectiveSectionColor = isDark ? '#666677' : sectionColor
|
|
39
53
|
|
|
40
54
|
const cursorPositionRef = useRef(new Vector2(0, 0))
|
|
55
|
+
// Scratch for reading a moving node's world Y (surface elevation) each frame.
|
|
56
|
+
const worldPosRef = useRef(new Vector3())
|
|
57
|
+
// Scratch for the wall-anchored branch: invert the plane orientation to map the
|
|
58
|
+
// ghost into plane-local XY (the cursor reveal) without re-centring the mesh.
|
|
59
|
+
const invQuatRef = useRef(new Quaternion())
|
|
60
|
+
const wallCursorRef = useRef(new Vector3())
|
|
61
|
+
// Scratch for deriving a moving wall-item's host normal from its mesh.
|
|
62
|
+
const wallNormalRef = useRef(new Vector3())
|
|
63
|
+
// Last Y pushed to `gridY` state, so the per-frame surface follow only triggers
|
|
64
|
+
// a React re-render when the height actually changes (not every frame).
|
|
65
|
+
const lastGridYRef = useRef<number | null>(null)
|
|
66
|
+
|
|
67
|
+
// Reveal radius + baseline alpha are uniforms so a placement/move can shrink
|
|
68
|
+
// the grid to a tight cursor patch (and drop the always-on baseline) without
|
|
69
|
+
// rebuilding the shader. Driven each frame in `useFrame`.
|
|
70
|
+
const revealRadiusUniform = useMemo(() => uniform(revealRadius), [revealRadius])
|
|
71
|
+
const baseAlphaUniform = useMemo(() => uniform(0.4), [])
|
|
72
|
+
// Created once and driven by `.value` each frame (see `useFrame`). Keying this
|
|
73
|
+
// on `cellSize` rebuilt the uniform AND the material `useMemo` below on every
|
|
74
|
+
// `gridSnapStep` change — a full shader recompile that stalled hard whenever
|
|
75
|
+
// the grid resolution changed. The live cell size is a uniform write only.
|
|
76
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: created once on purpose; `.value` is driven each frame.
|
|
77
|
+
const cellSizeUniform = useMemo(() => uniform(cellSize), [])
|
|
78
|
+
const patchAlphaUniform = useMemo(() => uniform(1), [])
|
|
41
79
|
|
|
42
80
|
const material = useMemo(() => {
|
|
43
81
|
// Use xy since plane geometry is in XY space (before rotation)
|
|
@@ -48,7 +86,7 @@ export const Grid = ({
|
|
|
48
86
|
|
|
49
87
|
// Grid line function using fwidth for anti-aliasing
|
|
50
88
|
// Returns 1 on grid lines, 0 elsewhere
|
|
51
|
-
const getGrid = (size: number, thickness: number) => {
|
|
89
|
+
const getGrid = (size: number | typeof cellSizeUniform, thickness: number) => {
|
|
52
90
|
const r = pos.div(size)
|
|
53
91
|
const fw = fwidth(r)
|
|
54
92
|
// Distance to nearest grid line for each axis
|
|
@@ -70,7 +108,7 @@ export const Grid = ({
|
|
|
70
108
|
return lineX.max(lineY)
|
|
71
109
|
}
|
|
72
110
|
|
|
73
|
-
const g1 = getGrid(
|
|
111
|
+
const g1 = getGrid(cellSizeUniform, cellThickness)
|
|
74
112
|
const g2 = getGrid(sectionSize, sectionThickness)
|
|
75
113
|
|
|
76
114
|
// Distance fade from center
|
|
@@ -79,7 +117,9 @@ export const Grid = ({
|
|
|
79
117
|
|
|
80
118
|
// Cursor reveal effect - distance from cursor
|
|
81
119
|
const cursorDist = pos.sub(cursorPos).length()
|
|
82
|
-
const cursorFade = float(1)
|
|
120
|
+
const cursorFade = float(1)
|
|
121
|
+
.sub(cursorDist.div(revealRadiusUniform).clamp(0, 1))
|
|
122
|
+
.smoothstep(0, 1)
|
|
83
123
|
|
|
84
124
|
// Mix colors based on section grid
|
|
85
125
|
const gridColor = mix(
|
|
@@ -88,21 +128,26 @@ export const Grid = ({
|
|
|
88
128
|
float(sectionThickness).mul(g2).min(1),
|
|
89
129
|
)
|
|
90
130
|
|
|
91
|
-
// Baseline alpha: small amount of opacity everywhere the grid exists
|
|
92
|
-
const baseAlpha = float(0.4) // Subtle global visibility
|
|
93
|
-
|
|
94
131
|
// Combined alpha with cursor fade and baseline minimum
|
|
95
|
-
const alpha = g1.add(g2).mul(fade).mul(cursorFade.max(
|
|
96
|
-
const
|
|
132
|
+
const alpha = g1.add(g2).mul(fade).mul(cursorFade.max(baseAlphaUniform))
|
|
133
|
+
const boostedAlpha = alpha.mul(patchAlphaUniform).min(1)
|
|
134
|
+
const finalAlpha = mix(boostedAlpha.mul(0.75), boostedAlpha, g2)
|
|
97
135
|
|
|
98
136
|
return new MeshBasicNodeMaterial({
|
|
99
137
|
transparent: true,
|
|
100
138
|
colorNode: gridColor,
|
|
101
139
|
opacityNode: finalAlpha,
|
|
102
140
|
depthWrite: false,
|
|
141
|
+
// `depthTest` is toggled per-frame in `useFrame`: ON for the floor lattice
|
|
142
|
+
// (so the ground occludes a sub-floor grid) and OFF on a wall (so the
|
|
143
|
+
// lattice shows through the wall when the opening is handled from the far
|
|
144
|
+
// side). Default ON for the floor case.
|
|
145
|
+
depthTest: true,
|
|
146
|
+
// Wall-plane placements are handled from either side of the wall, so the
|
|
147
|
+
// lattice must render from both faces.
|
|
148
|
+
side: DoubleSide,
|
|
103
149
|
})
|
|
104
150
|
}, [
|
|
105
|
-
cellSize,
|
|
106
151
|
cellThickness,
|
|
107
152
|
effectiveCellColor,
|
|
108
153
|
sectionSize,
|
|
@@ -110,7 +155,10 @@ export const Grid = ({
|
|
|
110
155
|
effectiveSectionColor,
|
|
111
156
|
fadeDistance,
|
|
112
157
|
fadeStrength,
|
|
113
|
-
|
|
158
|
+
revealRadiusUniform,
|
|
159
|
+
baseAlphaUniform,
|
|
160
|
+
cellSizeUniform,
|
|
161
|
+
patchAlphaUniform,
|
|
114
162
|
])
|
|
115
163
|
|
|
116
164
|
const gridRef = useRef<Mesh>(null!)
|
|
@@ -124,13 +172,10 @@ export const Grid = ({
|
|
|
124
172
|
useCeilingEvents()
|
|
125
173
|
|
|
126
174
|
// Track the last world-space cursor hit. The reveal-fade shader reads
|
|
127
|
-
// `positionLocal.xy` (vertex position on the un-transformed plane), and
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
// so the local-frame cursor must be recomputed every frame from the
|
|
132
|
-
// stored world cursor — otherwise the ring drifts whenever the grid is
|
|
133
|
-
// mid-lerp (e.g. just after a building rotation commits).
|
|
175
|
+
// `positionLocal.xy` (vertex position on the un-transformed plane), and the
|
|
176
|
+
// laid-flat orientation maps `positionLocal.y` to world `-Z` relative to the
|
|
177
|
+
// mesh origin. The cursor is recomputed every frame from the stored world hit
|
|
178
|
+
// so the reveal stays put regardless of where the mesh origin sits.
|
|
134
179
|
const lastWorldCursorRef = useRef<{ x: number; z: number } | null>(null)
|
|
135
180
|
useEffect(() => {
|
|
136
181
|
const onGridMove = (event: GridEvent) => {
|
|
@@ -143,32 +188,113 @@ export const Grid = ({
|
|
|
143
188
|
}
|
|
144
189
|
}, [])
|
|
145
190
|
|
|
146
|
-
useFrame((
|
|
191
|
+
useFrame(() => {
|
|
147
192
|
const { levelId } = useViewer.getState().selection
|
|
148
|
-
|
|
149
|
-
// building. The Y origin still lerps to the active level so the grid
|
|
150
|
-
// sits at floor height when a level is open.
|
|
151
|
-
let targetY = 0
|
|
193
|
+
let levelY = 0
|
|
152
194
|
if (levelId) {
|
|
153
195
|
const levelMesh = sceneRegistry.nodes.get(levelId)
|
|
154
196
|
if (levelMesh) {
|
|
155
|
-
|
|
197
|
+
levelY = levelMesh.position.y
|
|
156
198
|
}
|
|
157
199
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
200
|
+
|
|
201
|
+
// Resolve the surface the active ghost is snapped to (contact point +
|
|
202
|
+
// normal). A fresh GLB item / drawn kind publishes via the surface module; a
|
|
203
|
+
// moving node is read straight off its mesh (treated as horizontal). Null
|
|
204
|
+
// when nothing is being placed.
|
|
205
|
+
const published = getPlacementSurface()
|
|
206
|
+
const movingForGrid = getMovingNode()
|
|
207
|
+
let surfacePoint: Vector3 | null = null
|
|
208
|
+
let surfaceNormal = UP
|
|
209
|
+
if (published) {
|
|
210
|
+
surfacePoint = published.point
|
|
211
|
+
surfaceNormal = published.normal
|
|
212
|
+
} else if (movingForGrid) {
|
|
213
|
+
const ghostMesh = sceneRegistry.nodes.get(movingForGrid.id as AnyNodeId)
|
|
214
|
+
if (ghostMesh) {
|
|
215
|
+
surfacePoint = ghostMesh.getWorldPosition(worldPosRef.current)
|
|
216
|
+
// A wall-hosted item read off its mesh gets its host's orientation
|
|
217
|
+
// immediately: its local +Z faces out of the wall (same convention the
|
|
218
|
+
// placement coordinator publishes). Without this the grid flashes
|
|
219
|
+
// horizontal on move-start until the first pointer move publishes a
|
|
220
|
+
// wall surface.
|
|
221
|
+
const attachTo = movingForGrid.type === 'item' ? movingForGrid.asset?.attachTo : undefined
|
|
222
|
+
if (attachTo === 'wall' || attachTo === 'wall-side') {
|
|
223
|
+
ghostMesh.getWorldQuaternion(invQuatRef.current)
|
|
224
|
+
const fwd = wallNormalRef.current.set(0, 0, 1).applyQuaternion(invQuatRef.current)
|
|
225
|
+
fwd.y = 0
|
|
226
|
+
if (fwd.lengthSq() > 1e-6) surfaceNormal = fwd.normalize()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
168
229
|
}
|
|
169
|
-
})
|
|
170
230
|
|
|
171
|
-
|
|
231
|
+
const gridMesh = gridRef.current
|
|
232
|
+
const onWall = surfacePoint != null && Math.abs(surfaceNormal.y) < 0.5
|
|
233
|
+
if (onWall && surfacePoint) {
|
|
234
|
+
// Wall-anchored lattice: orient the plane into the wall and pin the mesh to
|
|
235
|
+
// the plane's FOOT (the point on the wall plane closest to the world origin)
|
|
236
|
+
// — never the moving ghost. Sliding the opening along the wall then only
|
|
237
|
+
// moves the reveal patch (the cursor uniform); the snap lattice stays put.
|
|
238
|
+
// (Copying `surfacePoint` here made the grid follow the item — useless.)
|
|
239
|
+
gridMesh.quaternion.setFromUnitVectors(PLANE_LOCAL_NORMAL, surfaceNormal)
|
|
240
|
+
const planeOffset = surfacePoint.dot(surfaceNormal)
|
|
241
|
+
gridMesh.position.copy(surfaceNormal).multiplyScalar(planeOffset)
|
|
242
|
+
// Cursor → plane-local XY: rotate (ghost − anchor) by the inverse plane
|
|
243
|
+
// orientation. Both lie in the plane, so the resulting local Z is ~0.
|
|
244
|
+
invQuatRef.current.copy(gridMesh.quaternion).invert()
|
|
245
|
+
wallCursorRef.current
|
|
246
|
+
.copy(surfacePoint)
|
|
247
|
+
.sub(gridMesh.position)
|
|
248
|
+
.applyQuaternion(invQuatRef.current)
|
|
249
|
+
cursorPositionRef.current.set(wallCursorRef.current.x, wallCursorRef.current.y)
|
|
250
|
+
if (lastGridYRef.current !== surfacePoint.y) {
|
|
251
|
+
lastGridYRef.current = surfacePoint.y
|
|
252
|
+
setGridY(surfacePoint.y)
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
// Horizontal: keep the lattice anchored to world XZ (0,0); only the Y
|
|
256
|
+
// origin follows the surface height (floor / shelf top). Snap directly —
|
|
257
|
+
// the old lerp made the grid visibly drift up to a new floor height.
|
|
258
|
+
// Cursor uniform tracks the world cursor (mirrored on Z for the flat plane).
|
|
259
|
+
const targetY = surfacePoint ? surfacePoint.y : levelY
|
|
260
|
+
// Visual mesh rides 1mm above the surface: at exactly the surface Y the
|
|
261
|
+
// lattice is coplanar with the slab top and z-fights it. The event plane
|
|
262
|
+
// (`gridY` → useGridEvents) stays at the true surface height.
|
|
263
|
+
gridMesh.position.set(0, targetY + 0.001, 0)
|
|
264
|
+
gridMesh.quaternion.copy(HORIZONTAL_QUATERNION)
|
|
265
|
+
const world = lastWorldCursorRef.current
|
|
266
|
+
if (world) {
|
|
267
|
+
cursorPositionRef.current.set(world.x, -world.z)
|
|
268
|
+
}
|
|
269
|
+
if (lastGridYRef.current !== targetY) {
|
|
270
|
+
lastGridYRef.current = targetY
|
|
271
|
+
setGridY(targetY)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Floor grid depth-tests against the scene (ground occludes a sub-floor
|
|
276
|
+
// lattice); the wall grid ignores depth so it stays visible through the wall
|
|
277
|
+
// when the opening is being handled from the opposite side.
|
|
278
|
+
if (material.depthTest === onWall) {
|
|
279
|
+
material.depthTest = !onWall
|
|
280
|
+
material.needsUpdate = true
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// The grid is a placement aid: a tight cursor patch (no always-on baseline)
|
|
284
|
+
// shown whenever the active context is in grid-snap mode — ANY armed
|
|
285
|
+
// draft/build tool (wall / slab / fence / ceiling / zone / column / MEP / …),
|
|
286
|
+
// a node move, or a reshape — and hidden in select/idle, paint, and non-grid
|
|
287
|
+
// (lines/off) modes. `isGridSnapActive()` already derives the snap context
|
|
288
|
+
// from the interaction scope OR the armed build tool and is true only when
|
|
289
|
+
// that context resolves to grid, so it IS the gate. (Previously this also
|
|
290
|
+
// required a ghost in flight, so a merely-armed draft tool showed nothing.)
|
|
291
|
+
const snapPatchVisible = isGridSnapActive()
|
|
292
|
+
revealRadiusUniform.value = PLACEMENT_REVEAL_RADIUS
|
|
293
|
+
baseAlphaUniform.value = 0
|
|
294
|
+
cellSizeUniform.value = useEditor.getState().gridSnapStep
|
|
295
|
+
patchAlphaUniform.value = 1.5
|
|
296
|
+
gridRef.current.visible = snapPatchVisible
|
|
297
|
+
})
|
|
172
298
|
|
|
173
299
|
// Pass the geometry as a prop instead of a JSX child so the mesh
|
|
174
300
|
// is never reconciled with R3F's empty placeholder `BufferGeometry`.
|
|
@@ -183,13 +309,14 @@ export const Grid = ({
|
|
|
183
309
|
useEffect(() => () => geometry.dispose(), [geometry])
|
|
184
310
|
|
|
185
311
|
return (
|
|
312
|
+
// Orientation is driven imperatively in `useFrame` (horizontal by default,
|
|
313
|
+
// tilted into the wall plane while placing on a wall), so no static rotation.
|
|
186
314
|
<mesh
|
|
187
315
|
geometry={geometry}
|
|
188
316
|
layers={GRID_LAYER}
|
|
189
317
|
material={material}
|
|
190
318
|
ref={gridRef}
|
|
191
|
-
|
|
192
|
-
visible={showGrid}
|
|
319
|
+
renderOrder={1}
|
|
193
320
|
/>
|
|
194
321
|
)
|
|
195
322
|
}
|