@pascal-app/nodes 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/dist/fence/definition.d.ts.map +1 -1
- package/dist/fence/definition.js +10 -3
- package/dist/fence/geometry.d.ts.map +1 -1
- package/dist/fence/geometry.js +10 -4
- package/dist/fence/lift.d.ts +25 -2
- package/dist/fence/lift.d.ts.map +1 -1
- package/dist/fence/lift.js +32 -4
- package/dist/fence/system.d.ts.map +1 -1
- package/dist/fence/system.js +7 -2
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/fence/definition.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/fence/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAA;AAkBzB,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAgPpC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,OAAO,SAAS,CAyH5D,CAAA"}
|
package/dist/fence/definition.js
CHANGED
|
@@ -4,7 +4,7 @@ import { buildFenceFloorplan } from './floorplan';
|
|
|
4
4
|
import { fenceControlPointAffordance, fenceCurveAffordance, fenceMoveEndpointAffordance, fenceTangentAffordance, } from './floorplan-affordances';
|
|
5
5
|
import { fenceFloorplanMoveTarget } from './floorplan-move';
|
|
6
6
|
import { buildFenceGeometry } from './geometry';
|
|
7
|
-
import { resolveFenceLiftElevation } from './lift';
|
|
7
|
+
import { resolveFenceLiftElevation, resolveFenceLiftElevationForNodes } from './lift';
|
|
8
8
|
import { fencePaint } from './paint';
|
|
9
9
|
import { fenceParametrics } from './parametrics';
|
|
10
10
|
import { FenceNode } from './schema';
|
|
@@ -16,7 +16,10 @@ const SIDE_HANDLE_MIN_HEIGHT = 0.4;
|
|
|
16
16
|
const HEIGHT_HANDLE_OFFSET = 0.45;
|
|
17
17
|
const MIN_FENCE_HEIGHT = 0.3;
|
|
18
18
|
function fenceBaseElevation(n, sceneApi) {
|
|
19
|
-
|
|
19
|
+
const nodes = sceneApi?.nodes();
|
|
20
|
+
return nodes
|
|
21
|
+
? resolveFenceLiftElevationForNodes(n, nodes)
|
|
22
|
+
: resolveFenceLiftElevation(n, () => undefined);
|
|
20
23
|
}
|
|
21
24
|
function fenceMidpointFrame(n) {
|
|
22
25
|
const dx = n.end[0] - n.start[0];
|
|
@@ -110,7 +113,11 @@ function fenceElevationHandle(currentBase) {
|
|
|
110
113
|
onDrag: (n, sceneApi) => publishStructuralElevationGuide(fenceElevationGuideSource(n), fenceBaseElevation(n, sceneApi), sceneApi.nodes()),
|
|
111
114
|
onDragEnd: (n) => clearStructuralElevationGuide(n.id),
|
|
112
115
|
apply: (initial, newBase, sceneApi) => {
|
|
113
|
-
|
|
116
|
+
// The offset is measured from the support WITHOUT the current offset —
|
|
117
|
+
// including the ground, so dragging a fence's base to an absolute height
|
|
118
|
+
// on a hillside stores the delta from that hillside and the fence keeps
|
|
119
|
+
// following it.
|
|
120
|
+
const supportBase = resolveFenceLiftElevationForNodes({ ...initial, supportOffset: undefined }, sceneApi.nodes());
|
|
114
121
|
const nextOffset = newBase - supportBase;
|
|
115
122
|
return {
|
|
116
123
|
supportOffset: Math.abs(nextOffset) < 1e-6 ? undefined : nextOffset,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../src/fence/geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,eAAe,EAA0B,MAAM,kBAAkB,CAAA;AAC/F,OAAO,EAEL,KAAK,WAAW,EAKhB,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAa,KAAK,EAAqC,MAAM,OAAO,CAAA;AAE3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAwFzC,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,SAAS,EACf,GAAG,CAAC,EAAE,eAAe,EACrB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,GAClB,KAAK,
|
|
1
|
+
{"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../src/fence/geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,eAAe,EAA0B,MAAM,kBAAkB,CAAA;AAC/F,OAAO,EAEL,KAAK,WAAW,EAKhB,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAa,KAAK,EAAqC,MAAM,OAAO,CAAA;AAE3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAwFzC,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,SAAS,EACf,GAAG,CAAC,EAAE,eAAe,EACrB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,GAClB,KAAK,CA0CP"}
|
package/dist/fence/geometry.js
CHANGED
|
@@ -51,10 +51,16 @@ function getLegacyFenceMaterial(node, shading) {
|
|
|
51
51
|
export function buildFenceGeometry(node, ctx, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme) {
|
|
52
52
|
const group = new Group();
|
|
53
53
|
const geometries = generateFenceSlotGeometries(node);
|
|
54
|
-
// A hosted railing (`supportSlabId`) stands on its slab's walking surface
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
54
|
+
// A hosted railing (`supportSlabId`) stands on its slab's walking surface;
|
|
55
|
+
// an unhosted one stands on the ground, which `ctx.levelBaseAt` resolves at
|
|
56
|
+
// the fence's own start point — the anchor its plan geometry is measured
|
|
57
|
+
// from, so the resolver and the mesh cannot disagree about where the ground
|
|
58
|
+
// is under this fence. The builder emits local-space children, so the lift
|
|
59
|
+
// lives on an inner group rather than the registered (React-transformed)
|
|
60
|
+
// root.
|
|
61
|
+
const lift = ctx
|
|
62
|
+
? resolveFenceLiftElevation(node, (id) => ctx.resolve(id), ctx.levelBaseAt?.(node.start[0], node.start[1]) ?? 0)
|
|
63
|
+
: 0;
|
|
58
64
|
const meshParent = new Group();
|
|
59
65
|
meshParent.position.y = lift;
|
|
60
66
|
group.add(meshParent);
|
package/dist/fence/lift.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AnyNode } from '@pascal-app/core';
|
|
2
2
|
import type { FenceNode } from './schema';
|
|
3
3
|
/**
|
|
4
4
|
* Elevation (meters above the level plane) a hosted fence stands at.
|
|
@@ -10,6 +10,29 @@ import type { FenceNode } from './schema';
|
|
|
10
10
|
* falls back to the level floor, mirroring the read-path rules of the
|
|
11
11
|
* other `supportSlabId` carriers. Pure so it is unit-testable and
|
|
12
12
|
* callable from the geometry builder with `ctx.resolve`.
|
|
13
|
+
*
|
|
14
|
+
* `levelBase` is what "the level floor" means here — the sculpted ground
|
|
15
|
+
* under the fence (`levelBaseElevationAt`, reached through `ctx.levelBaseAt`
|
|
16
|
+
* in the builder and {@link resolveFenceLiftElevationForNodes} everywhere
|
|
17
|
+
* else), or 0 for a level with no terrain under it. A caller-resolved scalar
|
|
18
|
+
* rather than a terrain lookup inside, so this stays pure and the sample stays
|
|
19
|
+
* at the caller's chosen point; it substitutes for the literal `0` this used to
|
|
20
|
+
* return in every unhosted branch, and nowhere else — a host slab that still
|
|
21
|
+
* exists wins outright, so a railing on a deck pad keeps the pad's elevation
|
|
22
|
+
* instead of following the hillside around it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveFenceLiftElevation(node: Pick<FenceNode, 'supportSlabId' | 'supportOffset' | 'parentId'>, resolve: (id: string) => AnyNode | undefined, levelBase?: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* {@link resolveFenceLiftElevation} against a nodes record, with the level base
|
|
27
|
+
* resolved from the terrain under the fence's start point — the same anchor the
|
|
28
|
+
* geometry builder samples, so a handle, a snap guide and the rendered rail all
|
|
29
|
+
* agree about where the ground is.
|
|
30
|
+
*
|
|
31
|
+
* Exists so the callers that already hold the whole scene (handles via
|
|
32
|
+
* `sceneApi.nodes()`, the dependency tracker, the editor's elevation guides)
|
|
33
|
+
* don't each re-derive the level walk and the sample point. The builder keeps
|
|
34
|
+
* using the pure form, since `GeometryContext` deliberately exposes the ground
|
|
35
|
+
* as a closure rather than the store.
|
|
13
36
|
*/
|
|
14
|
-
export declare function
|
|
37
|
+
export declare function resolveFenceLiftElevationForNodes(node: Pick<FenceNode, 'id' | 'start' | 'supportSlabId' | 'supportOffset' | 'parentId'>, nodes: Record<string, AnyNode>): number;
|
|
15
38
|
//# sourceMappingURL=lift.d.ts.map
|
package/dist/fence/lift.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lift.d.ts","sourceRoot":"","sources":["../../src/fence/lift.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"lift.d.ts","sourceRoot":"","sources":["../../src/fence/lift.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,eAAe,GAAG,eAAe,GAAG,UAAU,CAAC,EACrE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,EAC5C,SAAS,SAAI,GACZ,MAAM,CAQR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,OAAO,GAAG,eAAe,GAAG,eAAe,GAAG,UAAU,CAAC,EACtF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAIR"}
|
package/dist/fence/lift.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findLevelAncestorId, levelBaseElevationAt, } from '@pascal-app/core';
|
|
1
2
|
/**
|
|
2
3
|
* Elevation (meters above the level plane) a hosted fence stands at.
|
|
3
4
|
*
|
|
@@ -8,16 +9,43 @@
|
|
|
8
9
|
* falls back to the level floor, mirroring the read-path rules of the
|
|
9
10
|
* other `supportSlabId` carriers. Pure so it is unit-testable and
|
|
10
11
|
* callable from the geometry builder with `ctx.resolve`.
|
|
12
|
+
*
|
|
13
|
+
* `levelBase` is what "the level floor" means here — the sculpted ground
|
|
14
|
+
* under the fence (`levelBaseElevationAt`, reached through `ctx.levelBaseAt`
|
|
15
|
+
* in the builder and {@link resolveFenceLiftElevationForNodes} everywhere
|
|
16
|
+
* else), or 0 for a level with no terrain under it. A caller-resolved scalar
|
|
17
|
+
* rather than a terrain lookup inside, so this stays pure and the sample stays
|
|
18
|
+
* at the caller's chosen point; it substitutes for the literal `0` this used to
|
|
19
|
+
* return in every unhosted branch, and nowhere else — a host slab that still
|
|
20
|
+
* exists wins outright, so a railing on a deck pad keeps the pad's elevation
|
|
21
|
+
* instead of following the hillside around it.
|
|
11
22
|
*/
|
|
12
|
-
export function resolveFenceLiftElevation(node, resolve) {
|
|
23
|
+
export function resolveFenceLiftElevation(node, resolve, levelBase = 0) {
|
|
13
24
|
const offset = node.supportOffset ?? 0;
|
|
14
25
|
if (!node.supportSlabId)
|
|
15
|
-
return offset;
|
|
26
|
+
return levelBase + offset;
|
|
16
27
|
const host = resolve(node.supportSlabId);
|
|
17
28
|
if (host?.type !== 'slab')
|
|
18
|
-
return offset;
|
|
29
|
+
return levelBase + offset;
|
|
19
30
|
if ((host.parentId ?? null) !== (node.parentId ?? null))
|
|
20
|
-
return offset;
|
|
31
|
+
return levelBase + offset;
|
|
21
32
|
const elevation = host.elevation;
|
|
22
33
|
return (Number.isFinite(elevation) ? elevation : 0) + offset;
|
|
23
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* {@link resolveFenceLiftElevation} against a nodes record, with the level base
|
|
37
|
+
* resolved from the terrain under the fence's start point — the same anchor the
|
|
38
|
+
* geometry builder samples, so a handle, a snap guide and the rendered rail all
|
|
39
|
+
* agree about where the ground is.
|
|
40
|
+
*
|
|
41
|
+
* Exists so the callers that already hold the whole scene (handles via
|
|
42
|
+
* `sceneApi.nodes()`, the dependency tracker, the editor's elevation guides)
|
|
43
|
+
* don't each re-derive the level walk and the sample point. The builder keeps
|
|
44
|
+
* using the pure form, since `GeometryContext` deliberately exposes the ground
|
|
45
|
+
* as a closure rather than the store.
|
|
46
|
+
*/
|
|
47
|
+
export function resolveFenceLiftElevationForNodes(node, nodes) {
|
|
48
|
+
const levelId = findLevelAncestorId(node.id, nodes);
|
|
49
|
+
const levelBase = levelId ? levelBaseElevationAt(nodes, levelId, node.start[0], node.start[1]) : 0;
|
|
50
|
+
return resolveFenceLiftElevation(node, (id) => nodes[id], levelBase);
|
|
51
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/fence/system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/fence/system.tsx"],"names":[],"mappings":"AAiCA,QAAA,MAAM,YAAY,YAgBjB,CAAA;AAED,eAAe,YAAY,CAAA"}
|
package/dist/fence/system.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useScene } from '@pascal-app/core';
|
|
3
3
|
import { useEffect } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveFenceLiftElevationForNodes } from './lift';
|
|
5
5
|
/**
|
|
6
6
|
* Hosted-railing dependency tracker. A fence with `supportSlabId` renders at
|
|
7
7
|
* its host slab's elevation, but a store update only dirties the node that
|
|
@@ -17,9 +17,14 @@ function fenceLiftSignatures(nodes) {
|
|
|
17
17
|
if (node.type !== 'fence')
|
|
18
18
|
continue;
|
|
19
19
|
const fence = node;
|
|
20
|
+
// Unhosted fences are covered too, and by a different mechanism: their lift
|
|
21
|
+
// is the ground, which only a sculpt moves, and `markTerrainSupportDependents`
|
|
22
|
+
// dirties them from the terrain change itself. Tracking them here would
|
|
23
|
+
// resolve every fence in the scene on every store update to catch an event
|
|
24
|
+
// this subscription never sees.
|
|
20
25
|
if (!fence.supportSlabId)
|
|
21
26
|
continue;
|
|
22
|
-
signatures.set(fence.id,
|
|
27
|
+
signatures.set(fence.id, resolveFenceLiftElevationForNodes(fence, nodes));
|
|
23
28
|
}
|
|
24
29
|
return signatures;
|
|
25
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/nodes",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
4
|
"description": "Built-in node bundles for the Pascal 3D editor — one folder per kind, exported as `builtinPlugin`",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"prepublishOnly": "bun run build && bun test"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@pascal-app/core": "^1.0.0-beta.
|
|
27
|
-
"@pascal-app/editor": "^1.0.0-beta.
|
|
28
|
-
"@pascal-app/viewer": "^1.0.0-beta.
|
|
26
|
+
"@pascal-app/core": "^1.0.0-beta.3",
|
|
27
|
+
"@pascal-app/editor": "^1.0.0-beta.3",
|
|
28
|
+
"@pascal-app/viewer": "^1.0.0-beta.3",
|
|
29
29
|
"@react-three/drei": "^10",
|
|
30
30
|
"@react-three/fiber": "^9",
|
|
31
31
|
"lucide-react": "^1",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"zustand": "^5"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@pascal-app/core": "^1.0.0-beta.
|
|
38
|
-
"@pascal-app/editor": "^1.0.0-beta.
|
|
39
|
-
"@pascal-app/viewer": "^1.0.0-beta.
|
|
37
|
+
"@pascal-app/core": "^1.0.0-beta.3",
|
|
38
|
+
"@pascal-app/editor": "^1.0.0-beta.3",
|
|
39
|
+
"@pascal-app/viewer": "^1.0.0-beta.3",
|
|
40
40
|
"@pascal/typescript-config": "*",
|
|
41
41
|
"@types/bun": "^1.3.0",
|
|
42
42
|
"@types/node": "^22.19.12",
|