@pascal-app/editor 1.0.0-beta.1 → 1.0.0-beta.3
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 +5 -5
- package/src/index.tsx +1 -0
- package/src/lib/elevation-guides.ts +33 -4
- package/src/lib/terrain-sculpt.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/editor",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
4
|
"description": "Pascal building editor component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"check-types": "tsgo --noEmit"
|
|
12
12
|
},
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"@pascal-app/core": "^1.0.0-beta.
|
|
15
|
-
"@pascal-app/viewer": "^1.0.0-beta.
|
|
14
|
+
"@pascal-app/core": "^1.0.0-beta.3",
|
|
15
|
+
"@pascal-app/viewer": "^1.0.0-beta.3",
|
|
16
16
|
"@react-three/drei": "^10",
|
|
17
17
|
"@react-three/fiber": "^9",
|
|
18
18
|
"next": ">=15",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"zustand": "^5.0.11"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@pascal-app/core": "^1.0.0-beta.
|
|
60
|
-
"@pascal-app/viewer": "^1.0.0-beta.
|
|
59
|
+
"@pascal-app/core": "^1.0.0-beta.3",
|
|
60
|
+
"@pascal-app/viewer": "^1.0.0-beta.3",
|
|
61
61
|
"@pascal/typescript-config": "*",
|
|
62
62
|
"@types/blob-stream": "^0.1.33",
|
|
63
63
|
"@types/bun": "^1.3.0",
|
package/src/index.tsx
CHANGED
|
@@ -518,6 +518,7 @@ export {
|
|
|
518
518
|
snapBuildingLocalToWorldGrid,
|
|
519
519
|
snapWorldXZForActiveBuilding,
|
|
520
520
|
} from './lib/world-grid-snap'
|
|
521
|
+
export { subscribeCameraPose } from './store/camera-pose-store'
|
|
521
522
|
export { default as useAlignmentGuides } from './store/use-alignment-guides'
|
|
522
523
|
export { default as useAudio } from './store/use-audio'
|
|
523
524
|
export { type CommandAction, useCommandRegistry } from './store/use-command-registry'
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AnyNode,
|
|
3
3
|
type AnyNodeId,
|
|
4
|
+
findLevelAncestorId,
|
|
4
5
|
getWallBaseElevationForNodes,
|
|
5
6
|
getWallEffectiveHeightForNodes,
|
|
7
|
+
levelBaseElevationAt,
|
|
6
8
|
resolveCeilingHeight,
|
|
7
9
|
} from '@pascal-app/core'
|
|
8
10
|
import useElevationGuides from '../store/use-elevation-guides'
|
|
@@ -46,14 +48,22 @@ function segmentCenter(
|
|
|
46
48
|
return [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
// Mirrors `resolveFenceLiftElevationForNodes` in `@pascal-app/nodes`, which this
|
|
52
|
+
// package cannot import (the fence definition imports the guides from here).
|
|
53
|
+
// A stale host resolves to the level base — the sculpted ground under the
|
|
54
|
+
// fence's start point, sampled where the builder samples it, so the guide line
|
|
55
|
+
// lands on the rail it claims to describe.
|
|
49
56
|
function fenceBaseElevation(node: AnyNode, nodes: Record<string, AnyNode>): number {
|
|
50
57
|
if (node.type !== 'fence') return 0
|
|
51
58
|
const host = node.supportSlabId ? nodes[node.supportSlabId as AnyNodeId] : undefined
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
const hosted = host?.type === 'slab' && (host.parentId ?? null) === (node.parentId ?? null)
|
|
60
|
+
const levelId = findLevelAncestorId(node.id as AnyNodeId, nodes)
|
|
61
|
+
const support = hosted
|
|
62
|
+
? (host.elevation ?? 0)
|
|
63
|
+
: levelId
|
|
64
|
+
? levelBaseElevationAt(nodes, levelId, node.start[0], node.start[1])
|
|
55
65
|
: 0
|
|
56
|
-
return
|
|
66
|
+
return support + (node.supportOffset ?? 0)
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
/**
|
|
@@ -75,6 +85,25 @@ export function collectElevationSnapTargets(
|
|
|
75
85
|
label: 'Level',
|
|
76
86
|
},
|
|
77
87
|
]
|
|
88
|
+
// Sculpted ground under the thing being dragged. A separate target rather than
|
|
89
|
+
// a redefinition of `Level`: the storey plane is still a real datum a user may
|
|
90
|
+
// want (a fence sunk to the building's floor line), and on a hillside the
|
|
91
|
+
// ground is a second, different one. Emitted only when they actually differ,
|
|
92
|
+
// so a flat scene keeps exactly one target at 0.
|
|
93
|
+
const groundElevation = levelBaseElevationAt(
|
|
94
|
+
nodes,
|
|
95
|
+
source.levelId,
|
|
96
|
+
source.anchor[0],
|
|
97
|
+
source.anchor[1],
|
|
98
|
+
)
|
|
99
|
+
if (Math.abs(groundElevation) > GUIDE_MATCH_EPSILON_M) {
|
|
100
|
+
targets.push({
|
|
101
|
+
id: `${source.levelId}:ground`,
|
|
102
|
+
elevation: groundElevation,
|
|
103
|
+
anchor: source.anchor,
|
|
104
|
+
label: 'Ground',
|
|
105
|
+
})
|
|
106
|
+
}
|
|
78
107
|
const level = nodes[source.levelId as AnyNodeId]
|
|
79
108
|
if (level?.type !== 'level') return targets
|
|
80
109
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type HeightPatch,
|
|
15
15
|
isDatumField,
|
|
16
16
|
minBrushRadius,
|
|
17
|
+
persistedTerrainFieldOf,
|
|
17
18
|
pointInPolygon2D,
|
|
18
19
|
quantize,
|
|
19
20
|
runAsSingleSceneHistoryStep,
|
|
@@ -21,7 +22,6 @@ import {
|
|
|
21
22
|
sampleTarget,
|
|
22
23
|
type TerrainField,
|
|
23
24
|
type TerrainVerb,
|
|
24
|
-
terrainFieldOf,
|
|
25
25
|
useLiveTerrain,
|
|
26
26
|
useScene,
|
|
27
27
|
} from '@pascal-app/core'
|
|
@@ -97,7 +97,7 @@ export function fieldExtentForSite(site: Pick<SiteNode, 'polygon'> | null | unde
|
|
|
97
97
|
* here is that the tool must not have to know how to size a grid.
|
|
98
98
|
*/
|
|
99
99
|
export function sculptFieldForSite(site: SiteNode): TerrainField {
|
|
100
|
-
return
|
|
100
|
+
return persistedTerrainFieldOf(site) ?? createTerrainField(fieldExtentForSite(site))
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/** Whether an XZ point belongs to the editable property footprint. */
|