@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/editor",
3
- "version": "1.0.0-beta.1",
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.1",
15
- "@pascal-app/viewer": "^1.0.0-beta.1",
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.1",
60
- "@pascal-app/viewer": "^1.0.0-beta.1",
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 hostElevation =
53
- host?.type === 'slab' && (host.parentId ?? null) === (node.parentId ?? null)
54
- ? (host.elevation ?? 0)
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 hostElevation + (node.supportOffset ?? 0)
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 terrainFieldOf(site) ?? createTerrainField(fieldExtentForSite(site))
100
+ return persistedTerrainFieldOf(site) ?? createTerrainField(fieldExtentForSite(site))
101
101
  }
102
102
 
103
103
  /** Whether an XZ point belongs to the editable property footprint. */