@pascal-app/core 1.0.0 → 1.0.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/capture/index.d.ts +2 -2
- package/dist/capture/index.js +2 -2
- package/dist/capture/schema.d.ts.map +1 -1
- package/dist/capture/schema.js +2 -0
- package/dist/capture/source.d.ts +1 -1
- package/dist/capture/source.js +1 -1
- package/dist/events/bus.d.ts +6 -3
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +4 -4
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +7 -12
- package/dist/hooks/spatial-grid/floor-placed-footprints.d.ts +4 -0
- package/dist/hooks/spatial-grid/floor-placed-footprints.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-footprints.js +7 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +5 -8
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +23 -106
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +263 -84
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +77 -13
- package/dist/hooks/spatial-grid/support-host-patch.d.ts +1 -1
- package/dist/hooks/spatial-grid/support-host-patch.js +6 -6
- package/dist/hooks/spatial-grid/use-spatial-query.d.ts +1 -1
- package/dist/hooks/spatial-grid/use-spatial-query.js +1 -1
- package/dist/index.d.ts +77 -72
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -69
- package/dist/lib/door-operation.d.ts +1 -1
- package/dist/lib/item-polygon-overlap.d.ts +6 -0
- package/dist/lib/item-polygon-overlap.d.ts.map +1 -0
- package/dist/lib/item-polygon-overlap.js +67 -0
- package/dist/lib/level-name.d.ts +1 -1
- package/dist/lib/measurement-geometry.d.ts +4 -4
- package/dist/lib/node-visibility.d.ts +12 -0
- package/dist/lib/node-visibility.d.ts.map +1 -0
- package/dist/lib/node-visibility.js +12 -0
- package/dist/lib/plan-footprint.d.ts +41 -0
- package/dist/lib/plan-footprint.d.ts.map +1 -0
- package/dist/lib/plan-footprint.js +58 -0
- package/dist/lib/room-topology-index.d.ts +1 -1
- package/dist/lib/room-topology-index.js +1 -1
- package/dist/lib/selection-proxy.d.ts +1 -1
- package/dist/lib/slab-polygon.d.ts +2 -2
- package/dist/lib/slab-polygon.js +2 -2
- package/dist/lib/space-detection.d.ts +1 -1
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +16 -11
- package/dist/lib/terrain-brush.d.ts +1 -1
- package/dist/lib/terrain-brush.js +1 -1
- package/dist/lib/terrain-codec.d.ts +2 -2
- package/dist/lib/terrain-codec.js +1 -1
- package/dist/lib/terrain-raycast.d.ts +1 -1
- package/dist/lib/terrain-raycast.js +1 -1
- package/dist/lib/terrain-source.d.ts +3 -3
- package/dist/lib/terrain-source.js +3 -3
- package/dist/lib/terrain-support.d.ts +1 -1
- package/dist/lib/terrain-support.js +3 -3
- package/dist/lib/unit-containment.d.ts +19 -0
- package/dist/lib/unit-containment.d.ts.map +1 -0
- package/dist/lib/unit-containment.js +158 -0
- package/dist/lib/unit-report.d.ts +19 -0
- package/dist/lib/unit-report.d.ts.map +1 -0
- package/dist/lib/unit-report.js +36 -0
- package/dist/lib/wall-distance.d.ts +2 -2
- package/dist/lib/wall-distance.js +1 -1
- package/dist/lib/zone-quantities.d.ts +1 -1
- package/dist/lib/zone-quantities.js +7 -7
- package/dist/material-library.d.ts +1 -1
- package/dist/material-library.js +1 -1
- package/dist/procedural-items/fixtures.d.ts +261 -0
- package/dist/procedural-items/fixtures.d.ts.map +1 -0
- package/dist/procedural-items/fixtures.js +349 -0
- package/dist/procedural-items/index.d.ts +10 -0
- package/dist/procedural-items/index.d.ts.map +1 -0
- package/dist/procedural-items/index.js +9 -0
- package/dist/procedural-items/integration.d.ts +92 -0
- package/dist/procedural-items/integration.d.ts.map +1 -0
- package/dist/procedural-items/integration.js +19 -0
- package/dist/procedural-items/library-colors.d.ts +56 -0
- package/dist/procedural-items/library-colors.d.ts.map +1 -0
- package/dist/procedural-items/library-colors.js +84 -0
- package/dist/procedural-items/materials.d.ts +5 -0
- package/dist/procedural-items/materials.d.ts.map +1 -0
- package/dist/procedural-items/materials.js +47 -0
- package/dist/procedural-items/node.d.ts +280 -0
- package/dist/procedural-items/node.d.ts.map +1 -0
- package/dist/procedural-items/node.js +71 -0
- package/dist/procedural-items/placement.d.ts +7 -0
- package/dist/procedural-items/placement.d.ts.map +1 -0
- package/dist/procedural-items/placement.js +48 -0
- package/dist/procedural-items/query.d.ts +51 -0
- package/dist/procedural-items/query.d.ts.map +1 -0
- package/dist/procedural-items/query.js +342 -0
- package/dist/procedural-items/recipe.d.ts +144 -0
- package/dist/procedural-items/recipe.d.ts.map +1 -0
- package/dist/procedural-items/recipe.js +372 -0
- package/dist/procedural-items/spatial.d.ts +19 -0
- package/dist/procedural-items/spatial.d.ts.map +1 -0
- package/dist/procedural-items/spatial.js +39 -0
- package/dist/registry/__bench__/relations-resolver.bench.js +2 -2
- package/dist/registry/handles.d.ts +40 -5
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +6 -6
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +4 -4
- package/dist/registry/registry.d.ts +1 -1
- package/dist/registry/relations-resolver.d.ts +2 -2
- package/dist/registry/relations-resolver.js +1 -1
- package/dist/registry/scene-api.d.ts +2 -2
- package/dist/registry/scene-api.js +2 -2
- package/dist/registry/subtree.d.ts +1 -1
- package/dist/registry/subtree.d.ts.map +1 -1
- package/dist/registry/subtree.js +8 -2
- package/dist/registry/types.d.ts +32 -10
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/use-registry-version.js +1 -1
- package/dist/registry/validate-relations.d.ts +4 -0
- package/dist/registry/validate-relations.d.ts.map +1 -0
- package/dist/registry/validate-relations.js +22 -0
- package/dist/schema/__bench__/node-parsers.bench.js +3 -3
- package/dist/schema/__fixtures__/node-fixtures.d.ts +1 -1
- package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -1
- package/dist/schema/__fixtures__/node-fixtures.js +4 -2
- package/dist/schema/base.js +1 -1
- package/dist/schema/collections.d.ts +1 -1
- package/dist/schema/collections.js +1 -1
- package/dist/schema/compiled-node-parsers.d.ts +1 -1
- package/dist/schema/compiled-node-parsers.js +1 -1
- package/dist/schema/index.d.ts +74 -70
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +65 -61
- package/dist/schema/material.d.ts +1 -1
- package/dist/schema/material.js +1 -1
- package/dist/schema/nodes/automatic-downspout.d.ts +4 -4
- package/dist/schema/nodes/automatic-downspout.js +5 -5
- package/dist/schema/nodes/block.d.ts +3 -3
- package/dist/schema/nodes/block.d.ts.map +1 -1
- package/dist/schema/nodes/block.js +24 -12
- package/dist/schema/nodes/box-vent.js +2 -2
- package/dist/schema/nodes/building.d.ts +1 -1
- package/dist/schema/nodes/building.d.ts.map +1 -1
- package/dist/schema/nodes/building.js +8 -5
- package/dist/schema/nodes/cabinet.d.ts +4 -4
- package/dist/schema/nodes/cabinet.js +4 -4
- package/dist/schema/nodes/ceiling.d.ts +1 -1
- package/dist/schema/nodes/ceiling.js +5 -5
- package/dist/schema/nodes/chimney.js +2 -2
- package/dist/schema/nodes/column.d.ts +1 -0
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +3 -2
- package/dist/schema/nodes/construction-dimension.d.ts +3 -3
- package/dist/schema/nodes/construction-dimension.js +2 -2
- package/dist/schema/nodes/cupola.d.ts +1 -1
- package/dist/schema/nodes/cupola.js +2 -2
- package/dist/schema/nodes/curtain-wall.d.ts +129 -0
- package/dist/schema/nodes/curtain-wall.d.ts.map +1 -0
- package/dist/schema/nodes/curtain-wall.js +49 -0
- package/dist/schema/nodes/door.d.ts +2 -2
- package/dist/schema/nodes/door.js +2 -2
- package/dist/schema/nodes/dormer.d.ts +3 -3
- package/dist/schema/nodes/dormer.js +4 -4
- package/dist/schema/nodes/downspout.js +2 -2
- package/dist/schema/nodes/duct-fitting.js +1 -1
- package/dist/schema/nodes/duct-segment.js +2 -2
- package/dist/schema/nodes/duct-terminal.js +1 -1
- package/dist/schema/nodes/elevator.js +2 -2
- package/dist/schema/nodes/eyebrow-vent.js +2 -2
- package/dist/schema/nodes/fence.js +2 -2
- package/dist/schema/nodes/guide.js +2 -2
- package/dist/schema/nodes/gutter.d.ts +1 -1
- package/dist/schema/nodes/gutter.js +4 -4
- package/dist/schema/nodes/hvac-equipment.js +1 -1
- package/dist/schema/nodes/imported-mesh.d.ts +40 -0
- package/dist/schema/nodes/imported-mesh.d.ts.map +1 -0
- package/dist/schema/nodes/imported-mesh.js +27 -0
- package/dist/schema/nodes/item.d.ts +3 -3
- package/dist/schema/nodes/item.js +3 -3
- package/dist/schema/nodes/lean-to-extension.js +4 -4
- package/dist/schema/nodes/level.d.ts +1 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +1 -1
- package/dist/schema/nodes/lineset.js +1 -1
- package/dist/schema/nodes/liquid-line.js +1 -1
- package/dist/schema/nodes/measurement.js +2 -2
- package/dist/schema/nodes/pipe-fitting.d.ts +1 -1
- package/dist/schema/nodes/pipe-fitting.js +1 -1
- package/dist/schema/nodes/pipe-segment.d.ts +1 -1
- package/dist/schema/nodes/pipe-segment.js +2 -2
- package/dist/schema/nodes/pipe-trap.d.ts +1 -1
- package/dist/schema/nodes/pipe-trap.js +1 -1
- package/dist/schema/nodes/ridge-vent.d.ts +1 -1
- package/dist/schema/nodes/ridge-vent.js +3 -3
- package/dist/schema/nodes/roof-segment-shape.d.ts +1 -1
- package/dist/schema/nodes/roof-segment-shape.js +1 -1
- package/dist/schema/nodes/roof-segment-walls.d.ts +1 -1
- package/dist/schema/nodes/roof-segment-walls.js +1 -1
- package/dist/schema/nodes/roof-segment.d.ts +2 -2
- package/dist/schema/nodes/roof-segment.js +2 -2
- package/dist/schema/nodes/roof.d.ts +1 -1
- package/dist/schema/nodes/roof.js +3 -3
- package/dist/schema/nodes/scan.js +3 -3
- package/dist/schema/nodes/shelf.d.ts +1 -1
- package/dist/schema/nodes/shelf.d.ts.map +1 -1
- package/dist/schema/nodes/shelf.js +3 -4
- package/dist/schema/nodes/site.js +2 -2
- package/dist/schema/nodes/skylight.d.ts +4 -4
- package/dist/schema/nodes/skylight.js +2 -2
- package/dist/schema/nodes/slab.js +3 -3
- package/dist/schema/nodes/solar-panel.js +3 -3
- package/dist/schema/nodes/spawn.js +1 -1
- package/dist/schema/nodes/stair-segment.js +2 -2
- package/dist/schema/nodes/stair.d.ts +1 -1
- package/dist/schema/nodes/stair.js +3 -3
- package/dist/schema/nodes/structural-grid.js +1 -1
- package/dist/schema/nodes/turbine-vent.d.ts +1 -1
- package/dist/schema/nodes/turbine-vent.js +2 -2
- package/dist/schema/nodes/unit.d.ts +33 -0
- package/dist/schema/nodes/unit.d.ts.map +1 -0
- package/dist/schema/nodes/unit.js +13 -0
- package/dist/schema/nodes/wall.d.ts +75 -2
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +10 -6
- package/dist/schema/nodes/window.js +2 -2
- package/dist/schema/nodes/zone.js +1 -1
- package/dist/schema/scene-material.js +2 -2
- package/dist/schema/types.d.ts +417 -22
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +56 -49
- package/dist/services/__fixtures__/hosting.d.ts +3 -0
- package/dist/services/__fixtures__/hosting.d.ts.map +1 -0
- package/dist/services/__fixtures__/hosting.js +9 -0
- package/dist/services/__fixtures__/shelf-surface-rows.d.ts +4 -0
- package/dist/services/__fixtures__/shelf-surface-rows.d.ts.map +1 -0
- package/dist/services/__fixtures__/shelf-surface-rows.js +18 -0
- package/dist/services/alignment-anchors.d.ts +6 -11
- package/dist/services/alignment-anchors.d.ts.map +1 -1
- package/dist/services/alignment-anchors.js +7 -34
- package/dist/services/drag-session.d.ts +4 -4
- package/dist/services/drag-session.js +1 -1
- package/dist/services/hosting.d.ts +5 -8
- package/dist/services/hosting.d.ts.map +1 -1
- package/dist/services/hosting.js +25 -18
- package/dist/services/index.d.ts +16 -13
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +16 -13
- package/dist/services/level-height.d.ts +2 -2
- package/dist/services/level-height.js +3 -3
- package/dist/services/movement.d.ts +3 -3
- package/dist/services/movement.js +2 -2
- package/dist/services/port-connectivity.d.ts +1 -1
- package/dist/services/port-connectivity.js +1 -1
- package/dist/services/riser-diagram.d.ts +1 -1
- package/dist/services/shelf-board.d.ts +5 -0
- package/dist/services/shelf-board.d.ts.map +1 -0
- package/dist/services/shelf-board.js +15 -0
- package/dist/services/snap.d.ts +3 -0
- package/dist/services/snap.d.ts.map +1 -1
- package/dist/services/snap.js +11 -0
- package/dist/services/storey.d.ts +2 -2
- package/dist/services/storey.js +2 -2
- package/dist/services/surface-hosting-procedural-parity.fixtures.d.ts +13 -0
- package/dist/services/surface-hosting-procedural-parity.fixtures.d.ts.map +1 -0
- package/dist/services/surface-hosting-procedural-parity.fixtures.js +4691 -0
- package/dist/services/surface-hosting-shelf-parity.fixtures.d.ts +1060 -0
- package/dist/services/surface-hosting-shelf-parity.fixtures.d.ts.map +1 -0
- package/dist/services/surface-hosting-shelf-parity.fixtures.js +3127 -0
- package/dist/services/surface-hosting.d.ts +92 -0
- package/dist/services/surface-hosting.d.ts.map +1 -0
- package/dist/services/surface-hosting.js +305 -0
- package/dist/services/surface-region.d.ts +8 -0
- package/dist/services/surface-region.d.ts.map +1 -0
- package/dist/services/surface-region.js +44 -0
- package/dist/services/system-graph.d.ts +2 -2
- package/dist/services/system-graph.js +2 -2
- package/dist/services/validate-dwv.d.ts +1 -1
- package/dist/services/validate-dwv.js +1 -1
- package/dist/store/actions/node-actions.d.ts +2 -2
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +50 -104
- package/dist/store/history-control.d.ts +3 -3
- package/dist/store/history-invalidation.d.ts +1 -1
- package/dist/store/history-invalidation.js +1 -1
- package/dist/store/use-interactive.d.ts +2 -2
- package/dist/store/use-live-terrain.d.ts +1 -1
- package/dist/store/use-scene.d.ts +4 -4
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +50 -32
- package/dist/systems/elevator/elevator-dispatch.d.ts +2 -2
- package/dist/systems/elevator/elevator-dispatch.js +1 -1
- package/dist/systems/elevator/elevator-geometry.d.ts +1 -1
- package/dist/systems/elevator/elevator-opening-sync.d.ts +1 -1
- package/dist/systems/elevator/elevator-opening-sync.js +2 -2
- package/dist/systems/elevator/elevator-opening-system.js +3 -3
- package/dist/systems/elevator/elevator-runtime.d.ts +3 -3
- package/dist/systems/elevator/elevator-runtime.js +3 -3
- package/dist/systems/elevator/elevator-service.d.ts +1 -1
- package/dist/systems/elevator/elevator-service.js +1 -1
- package/dist/systems/fence/fence-centerline.d.ts +2 -2
- package/dist/systems/fence/fence-centerline.js +2 -2
- package/dist/systems/fence/fence-spline.d.ts +2 -2
- package/dist/systems/roof/roof-elevation-system.js +3 -3
- package/dist/systems/roof/roof-elevation.d.ts +2 -2
- package/dist/systems/roof/roof-elevation.js +6 -6
- package/dist/systems/roof/roof-footprint.d.ts +1 -1
- package/dist/systems/roof/roof-footprint.js +3 -3
- package/dist/systems/slab/slab-placement.d.ts +1 -1
- package/dist/systems/slab/slab-support.d.ts +1 -1
- package/dist/systems/slab/slab-support.js +4 -4
- package/dist/systems/stair/stair-flight.d.ts +2 -2
- package/dist/systems/stair/stair-flight.js +2 -2
- package/dist/systems/stair/stair-footprint.d.ts +1 -1
- package/dist/systems/stair/stair-opening-preview.d.ts +248 -87
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-preview.js +2 -2
- package/dist/systems/stair/stair-opening-sync.d.ts +1 -1
- package/dist/systems/stair/stair-opening-sync.js +6 -6
- package/dist/systems/stair/stair-opening-system.js +8 -8
- package/dist/systems/stair/stair-rise.d.ts +1 -1
- package/dist/systems/stair/stair-rise.js +3 -3
- package/dist/systems/wall/wall-curve.d.ts +2 -2
- package/dist/systems/wall/wall-footprint.d.ts +3 -3
- package/dist/systems/wall/wall-footprint.js +3 -3
- package/dist/systems/wall/wall-merge.d.ts +38 -0
- package/dist/systems/wall/wall-merge.d.ts.map +1 -0
- package/dist/systems/wall/wall-merge.js +240 -0
- package/dist/systems/wall/wall-mitering.d.ts +1 -1
- package/dist/systems/wall/wall-mitering.js +1 -1
- package/dist/systems/wall/wall-move.d.ts +1 -1
- package/dist/systems/wall/wall-operations.d.ts +28 -0
- package/dist/systems/wall/wall-operations.d.ts.map +1 -0
- package/dist/systems/wall/wall-operations.js +145 -0
- package/dist/systems/wall/wall-top.d.ts +1 -1
- package/dist/systems/wall/wall-topology.d.ts +9 -2
- package/dist/systems/wall/wall-topology.d.ts.map +1 -1
- package/dist/systems/wall/wall-topology.js +57 -9
- package/dist/utils/clone-scene-graph.d.ts +3 -3
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +9 -3
- package/dist/utils/scene-migrations.d.ts +3 -3
- package/dist/utils/scene-migrations.js +3 -3
- package/dist/utils/vertical-scene-migration.js +5 -5
- package/dist/validation/validate-build-json.d.ts +5 -2
- package/dist/validation/validate-build-json.d.ts.map +1 -1
- package/dist/validation/validate-build-json.js +57 -4
- package/package.json +15 -4
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { AnyNodeDefinition, SceneApi } from '../registry/types.js';
|
|
2
|
+
import type { AnyNode } from '../schema/types.js';
|
|
3
|
+
export type SurfaceId = string;
|
|
4
|
+
export interface SurfaceRegion {
|
|
5
|
+
kind: 'rect' | 'polygon';
|
|
6
|
+
/** Rectangle half-extents in the surface frame's XZ plane. */
|
|
7
|
+
size?: readonly [number, number];
|
|
8
|
+
center?: readonly [number, number];
|
|
9
|
+
/** Counterclockwise boundary in the surface frame's XZ plane. */
|
|
10
|
+
points?: readonly (readonly [number, number])[];
|
|
11
|
+
holes?: readonly (readonly (readonly [number, number])[])[];
|
|
12
|
+
}
|
|
13
|
+
interface SurfaceFrame {
|
|
14
|
+
label?: string;
|
|
15
|
+
position: readonly [number, number, number];
|
|
16
|
+
rotation?: readonly [number, number, number];
|
|
17
|
+
normal: readonly [number, number, number];
|
|
18
|
+
/** Defaults to true; the caller's snap function still controls whether the grid is active. */
|
|
19
|
+
gridSnap?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type DeclaredHostSurface = SurfaceFrame & {
|
|
22
|
+
id: SurfaceId;
|
|
23
|
+
region: SurfaceRegion;
|
|
24
|
+
};
|
|
25
|
+
export type HostSurface = DeclaredHostSurface | (SurfaceFrame & {
|
|
26
|
+
id: null;
|
|
27
|
+
region?: never;
|
|
28
|
+
});
|
|
29
|
+
export interface SurfaceHit {
|
|
30
|
+
point: readonly [number, number, number];
|
|
31
|
+
normalWorldY: number;
|
|
32
|
+
meshName?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface SurfaceContext {
|
|
35
|
+
scene: Pick<SceneApi, 'get' | 'nodes'>;
|
|
36
|
+
}
|
|
37
|
+
export interface SurfaceProvider {
|
|
38
|
+
childFrame: 'host-local' | 'surface-local';
|
|
39
|
+
surfaces?(host: AnyNode, ctx: SurfaceContext): readonly DeclaredHostSurface[];
|
|
40
|
+
resolveHit(host: AnyNode, hit: SurfaceHit, ctx: SurfaceContext): HostSurface | null;
|
|
41
|
+
accepts?(host: AnyNode, childKind: string, surface: HostSurface, ctx: SurfaceContext): boolean;
|
|
42
|
+
}
|
|
43
|
+
export type SurfaceRejectReason = 'host-not-eligible' | 'invalid-hit' | 'no-surface' | 'child-not-accepted' | 'footprint-outside-surface' | 'footprint-exceeds-host' | 'surface-cutout' | 'surface-occupied';
|
|
44
|
+
export type SurfacePlacement = {
|
|
45
|
+
position: readonly [number, number, number];
|
|
46
|
+
rotationY: number;
|
|
47
|
+
surfaceId: SurfaceId | null;
|
|
48
|
+
/** Movers write surfaceLocal for surface-local providers, otherwise the host-local pose. */
|
|
49
|
+
childFrame: SurfaceProvider['childFrame'];
|
|
50
|
+
/** Null exactly for hit-derived surfaces. Full XYZ rotation is needed on tilted surfaces. */
|
|
51
|
+
surfaceLocal: {
|
|
52
|
+
position: readonly [number, number, number];
|
|
53
|
+
rotationY: number;
|
|
54
|
+
rotation: readonly [number, number, number];
|
|
55
|
+
} | null;
|
|
56
|
+
};
|
|
57
|
+
export declare const NON_PHYSICAL_HOST_KINDS: readonly string[];
|
|
58
|
+
export declare const hitDerivedSurfaceProvider: SurfaceProvider;
|
|
59
|
+
export declare const itemSurfaceProvider: SurfaceProvider;
|
|
60
|
+
export declare const shelfSurfaceProvider: SurfaceProvider;
|
|
61
|
+
export declare const proceduralItemSurfaceProvider: SurfaceProvider;
|
|
62
|
+
export declare function getSurfaceProvider(host: AnyNode): SurfaceProvider;
|
|
63
|
+
export declare function rendersHostedChildren(def: AnyNodeDefinition): boolean;
|
|
64
|
+
export declare function canHostSurfaceChild(host: AnyNode, childKind: string, childId?: string): boolean;
|
|
65
|
+
/** Hit and child rotation are host-local; bounds and dimensions are scaled child-local values. */
|
|
66
|
+
export declare function resolveSurfacePlacement(args: {
|
|
67
|
+
host: AnyNode;
|
|
68
|
+
surface?: HostSurface;
|
|
69
|
+
childKind: string;
|
|
70
|
+
childId?: string;
|
|
71
|
+
childFootprint: {
|
|
72
|
+
size: readonly [number, number, number];
|
|
73
|
+
rotationY: number;
|
|
74
|
+
/** Full host-local XYZ Euler rotation, when available; otherwise rotationY is used. */
|
|
75
|
+
rotation?: readonly [number, number, number];
|
|
76
|
+
localBounds?: {
|
|
77
|
+
min: readonly [number, number, number];
|
|
78
|
+
max: readonly [number, number, number];
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
hit: SurfaceHit;
|
|
82
|
+
/** Child origin after grab-offset correction; the hit still elects the actual support. */
|
|
83
|
+
origin?: readonly [number, number, number];
|
|
84
|
+
scene: SceneApi;
|
|
85
|
+
/** Pure child-centered grid function; omitted means snapping is off. */
|
|
86
|
+
snapScalar?: (position: number, dimension: number) => number;
|
|
87
|
+
/** Defaults to true; drag previews may skip fit while still enforcing host and child eligibility. */
|
|
88
|
+
checkFootprint?: boolean;
|
|
89
|
+
onReject?: (reason: SurfaceRejectReason) => void;
|
|
90
|
+
}): SurfacePlacement | null;
|
|
91
|
+
export {};
|
|
92
|
+
//# sourceMappingURL=surface-hosting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-hosting.d.ts","sourceRoot":"","sources":["../../src/services/surface-hosting.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { attachmentBounds, attachmentRegionsOverlap, isProceduralItem, } from '../procedural-items/query.js';
|
|
2
|
+
import { evaluateRecipe } from '../procedural-items/recipe.js';
|
|
3
|
+
import { boundsOf, boxCorners, frame, transformPoint } from '../procedural-items/spatial.js';
|
|
4
|
+
import { nodeRegistry } from '../registry/registry.js';
|
|
5
|
+
import { getScaledDimensions, isLowProfileItemSurface } from '../schema/nodes/item.js';
|
|
6
|
+
import { canHostOnTop, wouldCreateHostingCycle } from './hosting.js';
|
|
7
|
+
import { shelfRowBoardDimensions } from './shelf-board.js';
|
|
8
|
+
import { surfaceRegionContainsFootprint, surfaceRegionContainsPoint } from './surface-region.js';
|
|
9
|
+
export const NON_PHYSICAL_HOST_KINDS = [
|
|
10
|
+
'guide',
|
|
11
|
+
'measurement',
|
|
12
|
+
'scan',
|
|
13
|
+
'construction-dimension',
|
|
14
|
+
'lineset',
|
|
15
|
+
'structural-grid',
|
|
16
|
+
'spawn',
|
|
17
|
+
'zone',
|
|
18
|
+
'unit',
|
|
19
|
+
'site',
|
|
20
|
+
'building',
|
|
21
|
+
'level',
|
|
22
|
+
];
|
|
23
|
+
const UPWARD_SURFACE_NORMAL_MIN_Y = 0.75;
|
|
24
|
+
export const hitDerivedSurfaceProvider = {
|
|
25
|
+
childFrame: 'host-local',
|
|
26
|
+
resolveHit(_host, hit) {
|
|
27
|
+
if (!(hit.normalWorldY >= UPWARD_SURFACE_NORMAL_MIN_Y) || !hit.point.every(Number.isFinite))
|
|
28
|
+
return null;
|
|
29
|
+
return { id: null, position: hit.point, normal: [0, 1, 0], gridSnap: true };
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export const itemSurfaceProvider = {
|
|
33
|
+
childFrame: 'host-local',
|
|
34
|
+
resolveHit(host, hit) {
|
|
35
|
+
if (host.type !== 'item' || !canHostOnTop(host) || isLowProfileItemSurface(host))
|
|
36
|
+
return null;
|
|
37
|
+
if (!(hit.normalWorldY >= UPWARD_SURFACE_NORMAL_MIN_Y) || !hit.point.every(Number.isFinite))
|
|
38
|
+
return null;
|
|
39
|
+
const height = host.asset.surface ? host.asset.surface.height * host.scale[1] : hit.point[1];
|
|
40
|
+
return {
|
|
41
|
+
id: null,
|
|
42
|
+
position: [hit.point[0], height, hit.point[2]],
|
|
43
|
+
normal: [0, 1, 0],
|
|
44
|
+
gridSnap: true,
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
function nearestSurface(surfaces, localY) {
|
|
49
|
+
let best = surfaces[0] ?? null;
|
|
50
|
+
for (const surface of surfaces.slice(1)) {
|
|
51
|
+
if (best && Math.abs(surface.position[1] - localY) < Math.abs(best.position[1] - localY))
|
|
52
|
+
best = surface;
|
|
53
|
+
}
|
|
54
|
+
return best;
|
|
55
|
+
}
|
|
56
|
+
function shelfSurfaces(host) {
|
|
57
|
+
if (host.type !== 'shelf')
|
|
58
|
+
return [];
|
|
59
|
+
return (nodeRegistry.get(host.type)?.capabilities.surfaces?.custom?.(host) ?? []).map((surface, index) => {
|
|
60
|
+
const [width, , depth] = shelfRowBoardDimensions(host, surface.position[1]);
|
|
61
|
+
return {
|
|
62
|
+
...surface,
|
|
63
|
+
id: `row:${index}`,
|
|
64
|
+
gridSnap: true,
|
|
65
|
+
region: { kind: 'rect', size: [width / 2, depth / 2] },
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export const shelfSurfaceProvider = {
|
|
70
|
+
childFrame: 'host-local',
|
|
71
|
+
surfaces: shelfSurfaces,
|
|
72
|
+
resolveHit(host, hit) {
|
|
73
|
+
if (!(hit.normalWorldY >= UPWARD_SURFACE_NORMAL_MIN_Y) || !hit.point.every(Number.isFinite))
|
|
74
|
+
return null;
|
|
75
|
+
return nearestSurface(shelfSurfaces(host), hit.point[1]);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const proceduralSurfaceCache = new WeakMap();
|
|
79
|
+
function proceduralSurfaces(host) {
|
|
80
|
+
if (!isProceduralItem(host))
|
|
81
|
+
return [];
|
|
82
|
+
// Include contents as well as identity so in-place parameter/recipe edits cannot leave stale surfaces.
|
|
83
|
+
const key = JSON.stringify([host.recipe, host.parameters]);
|
|
84
|
+
const cached = proceduralSurfaceCache.get(host.recipe);
|
|
85
|
+
if (cached?.key === key)
|
|
86
|
+
return cached.surfaces;
|
|
87
|
+
const surfaces = evaluateRecipe(host.recipe, host.parameters).surfaces.map((surface) => ({
|
|
88
|
+
id: surface.id,
|
|
89
|
+
label: surface.label,
|
|
90
|
+
position: surface.position,
|
|
91
|
+
rotation: surface.rotation,
|
|
92
|
+
normal: surface.normal,
|
|
93
|
+
gridSnap: true,
|
|
94
|
+
region: { kind: 'rect', size: [surface.size[0] / 2, surface.size[1] / 2] },
|
|
95
|
+
}));
|
|
96
|
+
proceduralSurfaceCache.set(host.recipe, { key, surfaces });
|
|
97
|
+
return surfaces;
|
|
98
|
+
}
|
|
99
|
+
function surfaceLocalPoint(surface, point) {
|
|
100
|
+
const delta = point.map((v, i) => v - surface.position[i]);
|
|
101
|
+
return frame([0, 0, 0], [...(surface.rotation ?? [0, 0, 0])]).axes.map((axis) => axis.reduce((sum, v, i) => sum + v * delta[i], 0));
|
|
102
|
+
}
|
|
103
|
+
function surfaceLocalRotation(surface, rotation) {
|
|
104
|
+
const surfaceRotation = surface.rotation ?? [0, 0, 0];
|
|
105
|
+
// Preserve unwrapped yaw exactly for the common horizontal, yaw-only case.
|
|
106
|
+
if (!surfaceRotation[0] && !surfaceRotation[2] && !rotation[0] && !rotation[2])
|
|
107
|
+
return [0, rotation[1] - surfaceRotation[1], 0];
|
|
108
|
+
const axes = frame([0, 0, 0], rotation).axes.map((axis) => surfaceLocalPoint({ ...surface, position: [0, 0, 0] }, axis));
|
|
109
|
+
const y = Math.asin(Math.max(-1, Math.min(1, axes[2][0])));
|
|
110
|
+
return Math.abs(axes[2][0]) < 0.9999999
|
|
111
|
+
? [Math.atan2(-axes[2][1], axes[2][2]), y, Math.atan2(-axes[1][0], axes[0][0])]
|
|
112
|
+
: [Math.atan2(axes[1][2], axes[1][1]), y, 0];
|
|
113
|
+
}
|
|
114
|
+
export const proceduralItemSurfaceProvider = {
|
|
115
|
+
childFrame: 'surface-local',
|
|
116
|
+
surfaces: proceduralSurfaces,
|
|
117
|
+
resolveHit(host, hit) {
|
|
118
|
+
if (!(hit.normalWorldY >= UPWARD_SURFACE_NORMAL_MIN_Y) || !hit.point.every(Number.isFinite))
|
|
119
|
+
return null;
|
|
120
|
+
const candidates = proceduralSurfaces(host).filter((surface) => {
|
|
121
|
+
const point = surfaceLocalPoint(surface, hit.point);
|
|
122
|
+
return (surface.normal[1] >= UPWARD_SURFACE_NORMAL_MIN_Y &&
|
|
123
|
+
surfaceRegionContainsPoint(surface.region, [point[0], point[2]]));
|
|
124
|
+
});
|
|
125
|
+
return nearestSurface(candidates, hit.point[1]);
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
const adapters = new Map([
|
|
129
|
+
['item', itemSurfaceProvider],
|
|
130
|
+
['shelf', shelfSurfaceProvider],
|
|
131
|
+
['procedural-item', proceduralItemSurfaceProvider],
|
|
132
|
+
]);
|
|
133
|
+
export function getSurfaceProvider(host) {
|
|
134
|
+
const declaration = nodeRegistry.get(host.type)?.capabilities.surfaces;
|
|
135
|
+
return declaration?.hosting || adapters.get(host.type) || hitDerivedSurfaceProvider;
|
|
136
|
+
}
|
|
137
|
+
export function rendersHostedChildren(def) {
|
|
138
|
+
return def.renderer
|
|
139
|
+
? def.renderer.kind === 'parametric' && def.rendersChildren !== false
|
|
140
|
+
: !!def.geometry;
|
|
141
|
+
}
|
|
142
|
+
export function canHostSurfaceChild(host, childKind, childId) {
|
|
143
|
+
const def = nodeRegistry.get(host.type);
|
|
144
|
+
if (!def ||
|
|
145
|
+
NON_PHYSICAL_HOST_KINDS.includes(host.type) ||
|
|
146
|
+
def.capabilities.surfaces?.hosting === false ||
|
|
147
|
+
nodeRegistry.get(childKind)?.capabilities.surfacePlacement === 'floor-only' ||
|
|
148
|
+
!rendersHostedChildren(def))
|
|
149
|
+
return false;
|
|
150
|
+
const generated = childId
|
|
151
|
+
? undefined
|
|
152
|
+
: nodeRegistry.get(childKind)?.schema.shape.id?.safeParse(undefined);
|
|
153
|
+
const id = childId ?? (generated?.success ? generated.data : undefined);
|
|
154
|
+
if (typeof id !== 'string')
|
|
155
|
+
return false;
|
|
156
|
+
const parsed = def.schema.shape.children?.safeParse([id]);
|
|
157
|
+
return parsed?.success === true && Array.isArray(parsed.data) && parsed.data.includes(id);
|
|
158
|
+
}
|
|
159
|
+
function hostRegion(host, ctx) {
|
|
160
|
+
const capabilities = nodeRegistry.get(host.type)?.capabilities;
|
|
161
|
+
const evaluated = isProceduralItem(host) ? evaluateRecipe(host.recipe, host.parameters) : null;
|
|
162
|
+
const bounds = capabilities?.dragBounds?.(host, ctx.scene.nodes()) ??
|
|
163
|
+
(evaluated
|
|
164
|
+
? {
|
|
165
|
+
size: evaluated.dimensions,
|
|
166
|
+
center: evaluated.min.map((v, i) => (v + evaluated.max[i]) / 2),
|
|
167
|
+
}
|
|
168
|
+
: undefined);
|
|
169
|
+
const size = host.type === 'item'
|
|
170
|
+
? getScaledDimensions(host)
|
|
171
|
+
: (bounds?.size ??
|
|
172
|
+
capabilities?.floorPlaced?.footprint?.(host, { nodes: ctx.scene.nodes() }).dimensions);
|
|
173
|
+
if (!size)
|
|
174
|
+
return undefined;
|
|
175
|
+
return {
|
|
176
|
+
kind: 'rect',
|
|
177
|
+
size: [size[0] / 2, size[2] / 2],
|
|
178
|
+
center: bounds?.center ? [bounds.center[0], bounds.center[2]] : [0, 0],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Hit and child rotation are host-local; bounds and dimensions are scaled child-local values. */
|
|
182
|
+
export function resolveSurfacePlacement(args) {
|
|
183
|
+
const { host, hit, childKind, childFootprint } = args;
|
|
184
|
+
if (args.childId && wouldCreateHostingCycle(args.childId, host, args.scene))
|
|
185
|
+
return null;
|
|
186
|
+
// Defer without a refusal so the paired grid event can place on floor support.
|
|
187
|
+
if (!canHostOnTop(host) || !canHostSurfaceChild(host, childKind, args.childId))
|
|
188
|
+
return null;
|
|
189
|
+
const reject = (reason) => {
|
|
190
|
+
args.onReject?.(reason);
|
|
191
|
+
return null;
|
|
192
|
+
};
|
|
193
|
+
if (!hit.point.every(Number.isFinite))
|
|
194
|
+
return reject('invalid-hit');
|
|
195
|
+
const ctx = { scene: args.scene };
|
|
196
|
+
const provider = getSurfaceProvider(host);
|
|
197
|
+
const poseFor = (surface) => {
|
|
198
|
+
const declaredId = surface.id;
|
|
199
|
+
if (declaredId !== null && !surface.region) {
|
|
200
|
+
throw new Error(`Declared surface ${host.type}:${declaredId} must publish a region`);
|
|
201
|
+
}
|
|
202
|
+
const snap = (surface.gridSnap ?? true) ? args.snapScalar : undefined;
|
|
203
|
+
const origin = args.origin ?? hit.point;
|
|
204
|
+
const position = [
|
|
205
|
+
snap?.(origin[0], childFootprint.size[0]) ?? origin[0],
|
|
206
|
+
surface.position[1],
|
|
207
|
+
snap?.(origin[2], childFootprint.size[2]) ?? origin[2],
|
|
208
|
+
];
|
|
209
|
+
const rotation = surfaceLocalRotation(surface, [
|
|
210
|
+
...(childFootprint.rotation ?? [0, childFootprint.rotationY, 0]),
|
|
211
|
+
]);
|
|
212
|
+
const localPosition = surfaceLocalPoint(surface, position);
|
|
213
|
+
if (surface.id !== null) {
|
|
214
|
+
const bounds = childFootprint.localBounds ?? {
|
|
215
|
+
min: [-childFootprint.size[0] / 2, 0, -childFootprint.size[2] / 2],
|
|
216
|
+
max: [
|
|
217
|
+
childFootprint.size[0] / 2,
|
|
218
|
+
childFootprint.size[1],
|
|
219
|
+
childFootprint.size[2] / 2,
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
localPosition[1] = -Math.min(...boxCorners([...bounds.min], [...bounds.max]).map((p) => transformPoint(frame([0, 0, 0], rotation), p)[1]));
|
|
223
|
+
// Intersect the host-local vertical cursor line with the support plane, then lift the child's bottom.
|
|
224
|
+
const normal = frame([0, 0, 0], [...(surface.rotation ?? [0, 0, 0])]).axes[1];
|
|
225
|
+
position[1] =
|
|
226
|
+
surface.position[1] +
|
|
227
|
+
(localPosition[1] -
|
|
228
|
+
normal[0] * (position[0] - surface.position[0]) -
|
|
229
|
+
normal[2] * (position[2] - surface.position[2])) /
|
|
230
|
+
normal[1];
|
|
231
|
+
const projected = surfaceLocalPoint(surface, position);
|
|
232
|
+
localPosition[0] = projected[0];
|
|
233
|
+
localPosition[2] = projected[2];
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
position,
|
|
237
|
+
rotationY: childFootprint.rotationY,
|
|
238
|
+
surfaceId: surface.id,
|
|
239
|
+
childFrame: surface.id === null ? 'host-local' : provider.childFrame,
|
|
240
|
+
surfaceLocal: surface.id === null ? null : { position: localPosition, rotationY: rotation[1], rotation },
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
let surface = args.surface ?? provider.resolveHit(host, hit, ctx);
|
|
244
|
+
if (!surface && hit.normalWorldY >= UPWARD_SURFACE_NORMAL_MIN_Y) {
|
|
245
|
+
// Grab offsets and snapping can leave the pointer over a hole while the child's centre is supported.
|
|
246
|
+
surface = nearestSurface((provider.surfaces?.(host, ctx) ?? []).filter((candidate) => {
|
|
247
|
+
if (candidate.normal[1] < UPWARD_SURFACE_NORMAL_MIN_Y)
|
|
248
|
+
return false;
|
|
249
|
+
const proposal = poseFor(candidate).surfaceLocal;
|
|
250
|
+
return surfaceRegionContainsFootprint(candidate.region, proposal.position, childFootprint.size, proposal.rotation, childFootprint.localBounds);
|
|
251
|
+
}), hit.point[1]);
|
|
252
|
+
}
|
|
253
|
+
if (!surface && isProceduralItem(host)) {
|
|
254
|
+
const bounds = evaluateRecipe(host.recipe, host.parameters);
|
|
255
|
+
if (hit.point.every((value, axis) => value >= bounds.min[axis] - 1e-6 && value <= bounds.max[axis] + 1e-6))
|
|
256
|
+
surface = hitDerivedSurfaceProvider.resolveHit(host, hit, ctx);
|
|
257
|
+
}
|
|
258
|
+
if (!surface) {
|
|
259
|
+
const overCutout = hit.normalWorldY >= UPWARD_SURFACE_NORMAL_MIN_Y &&
|
|
260
|
+
provider.surfaces?.(host, ctx).some((candidate) => {
|
|
261
|
+
if (!candidate.region?.holes?.length)
|
|
262
|
+
return false;
|
|
263
|
+
const local = surfaceLocalPoint(candidate, hit.point);
|
|
264
|
+
const point = [local[0], local[2]];
|
|
265
|
+
return (surfaceRegionContainsPoint({ ...candidate.region, holes: [] }, point) &&
|
|
266
|
+
!surfaceRegionContainsPoint(candidate.region, point));
|
|
267
|
+
});
|
|
268
|
+
return reject(overCutout ? 'surface-cutout' : 'no-surface');
|
|
269
|
+
}
|
|
270
|
+
const pose = poseFor(surface);
|
|
271
|
+
const localPosition = pose.surfaceLocal?.position ?? [0, 0, 0];
|
|
272
|
+
const rotation = pose.surfaceLocal?.rotation ?? [0, childFootprint.rotationY, 0];
|
|
273
|
+
if (provider.accepts && !provider.accepts(host, childKind, surface, ctx)) {
|
|
274
|
+
return reject('child-not-accepted');
|
|
275
|
+
}
|
|
276
|
+
if (args.checkFootprint === false)
|
|
277
|
+
return pose;
|
|
278
|
+
if (surface.region) {
|
|
279
|
+
if (!surfaceRegionContainsFootprint(surface.region, localPosition, childFootprint.size, rotation, childFootprint.localBounds)) {
|
|
280
|
+
const insideOutline = surfaceRegionContainsFootprint({ ...surface.region, holes: [] }, localPosition, childFootprint.size, rotation, childFootprint.localBounds);
|
|
281
|
+
return reject(insideOutline ? 'surface-cutout' : 'footprint-outside-surface');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
if (!surfaceRegionContainsFootprint(hostRegion(host, ctx), pose.position, childFootprint.size, childFootprint.rotation ?? childFootprint.rotationY, childFootprint.localBounds))
|
|
286
|
+
return reject('footprint-exceeds-host');
|
|
287
|
+
}
|
|
288
|
+
if (isProceduralItem(host) && pose.surfaceId !== null && pose.surfaceLocal) {
|
|
289
|
+
const bounds = childFootprint.localBounds ?? {
|
|
290
|
+
min: [-childFootprint.size[0] / 2, 0, -childFootprint.size[2] / 2],
|
|
291
|
+
max: [childFootprint.size[0] / 2, childFootprint.size[1], childFootprint.size[2] / 2],
|
|
292
|
+
};
|
|
293
|
+
const proposal = boundsOf(boxCorners([...bounds.min], [...bounds.max]).map((point) => transformPoint(frame([...pose.surfaceLocal.position], [...pose.surfaceLocal.rotation]), point)));
|
|
294
|
+
for (const id of host.children) {
|
|
295
|
+
if (id === args.childId || host.attachments[id] !== pose.surfaceId)
|
|
296
|
+
continue;
|
|
297
|
+
const child = args.scene.get(id);
|
|
298
|
+
if (child &&
|
|
299
|
+
(isProceduralItem(child) || child.type === 'item') &&
|
|
300
|
+
attachmentRegionsOverlap(attachmentBounds(child), proposal))
|
|
301
|
+
return reject('surface-occupied');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return pose;
|
|
305
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SurfaceRegion } from './surface-hosting.js';
|
|
2
|
+
export declare function surfaceRegionContainsPoint(region: SurfaceRegion | undefined, point: readonly [number, number]): boolean;
|
|
3
|
+
/** Position and XYZ rotation are surface-local; bounds and size are scaled child-local values. */
|
|
4
|
+
export declare function surfaceRegionContainsFootprint(region: SurfaceRegion | undefined, position: readonly [number, number, number], size: readonly [number, number, number], rotation: number | readonly [number, number, number], localBounds?: {
|
|
5
|
+
min: readonly [number, number, number];
|
|
6
|
+
max: readonly [number, number, number];
|
|
7
|
+
}): boolean;
|
|
8
|
+
//# sourceMappingURL=surface-region.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-region.d.ts","sourceRoot":"","sources":["../../src/services/surface-region.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAiBtD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAeT;AAED,kGAAkG;AAClG,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,QAAQ,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAC3C,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACvC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACpD,WAAW,CAAC,EAAE;IAAE,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC/F,OAAO,CAiBT"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { pointInPolygon } from '../lib/polygon-relations.js';
|
|
2
|
+
import { frame, transformPoint } from '../procedural-items/spatial.js';
|
|
3
|
+
const EPSILON = 1e-6;
|
|
4
|
+
function outline(region) {
|
|
5
|
+
if (region.kind === 'polygon')
|
|
6
|
+
return (region.points ?? []).map(([x, z]) => [x, z]);
|
|
7
|
+
if (!region.size)
|
|
8
|
+
return [];
|
|
9
|
+
const [x, z] = region.center ?? [0, 0];
|
|
10
|
+
const [w, d] = region.size;
|
|
11
|
+
return [
|
|
12
|
+
[x - w, z - d],
|
|
13
|
+
[x + w, z - d],
|
|
14
|
+
[x + w, z + d],
|
|
15
|
+
[x - w, z + d],
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
export function surfaceRegionContainsPoint(region, point) {
|
|
19
|
+
if (!region)
|
|
20
|
+
return true;
|
|
21
|
+
return ((region.kind === 'rect'
|
|
22
|
+
? !!region.size &&
|
|
23
|
+
Math.abs(point[0] - (region.center?.[0] ?? 0)) <= region.size[0] + EPSILON &&
|
|
24
|
+
Math.abs(point[1] - (region.center?.[1] ?? 0)) <= region.size[1] + EPSILON
|
|
25
|
+
: pointInPolygon([...point], outline(region))) &&
|
|
26
|
+
!(region.holes ?? []).some((hole) => pointInPolygon([...point], hole.map(([x, z]) => [x, z]))));
|
|
27
|
+
}
|
|
28
|
+
/** Position and XYZ rotation are surface-local; bounds and size are scaled child-local values. */
|
|
29
|
+
export function surfaceRegionContainsFootprint(region, position, size, rotation, localBounds) {
|
|
30
|
+
if (!region)
|
|
31
|
+
return true;
|
|
32
|
+
const bounds = localBounds ?? {
|
|
33
|
+
min: [-size[0] / 2, 0, -size[2] / 2],
|
|
34
|
+
max: [size[0] / 2, size[1], size[2] / 2],
|
|
35
|
+
};
|
|
36
|
+
const childFrame = frame([...position], typeof rotation === 'number' ? [0, rotation, 0] : [...rotation]);
|
|
37
|
+
// A rotated box and its XZ projection are centrally symmetric, so their centre is the transformed bounds midpoint.
|
|
38
|
+
const center = transformPoint(childFrame, [
|
|
39
|
+
(bounds.min[0] + bounds.max[0]) / 2,
|
|
40
|
+
(bounds.min[1] + bounds.max[1]) / 2,
|
|
41
|
+
(bounds.min[2] + bounds.max[2]) / 2,
|
|
42
|
+
]);
|
|
43
|
+
return surfaceRegionContainsPoint(region, [center[0], center[2]]);
|
|
44
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type NodePort } from '../registry';
|
|
2
|
-
import type { AnyNode, AnyNodeId } from '../schema';
|
|
1
|
+
import { type NodePort } from '../registry/index.js';
|
|
2
|
+
import type { AnyNode, AnyNodeId } from '../schema/index.js';
|
|
3
3
|
export type SystemSummary = {
|
|
4
4
|
/** Every node in this connected component. */
|
|
5
5
|
nodeIds: AnyNodeId[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { nodeRegistry } from '../registry';
|
|
2
|
-
import { getLevelElevations } from './storey';
|
|
1
|
+
import { nodeRegistry } from '../registry/index.js';
|
|
2
|
+
import { getLevelElevations } from './storey.js';
|
|
3
3
|
/**
|
|
4
4
|
* The "System" primitive: connected components over the port graph.
|
|
5
5
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildPortComponents } from './system-graph';
|
|
1
|
+
import { buildPortComponents } from './system-graph.js';
|
|
2
2
|
/** IPC 704.1 minimum drainage slope (rise/run, dimensionless) by
|
|
3
3
|
* nominal pipe size: ¼"/ft (1:48) under 3", ⅛"/ft (1:96) for 3–6",
|
|
4
4
|
* 1/16"/ft (1:192) at 8"+. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type AnyNode, type AnyNodeId } from '../../schema';
|
|
2
|
-
import type { SceneState } from '../use-scene';
|
|
1
|
+
import { type AnyNode, type AnyNodeId } from '../../schema/index.js';
|
|
2
|
+
import type { SceneState } from '../use-scene.js';
|
|
3
3
|
type NodeCreateOp = {
|
|
4
4
|
node: AnyNode;
|
|
5
5
|
parentId?: AnyNodeId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"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"}
|