@pascal-app/core 0.8.0 → 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/dist/events/bus.d.ts +67 -13
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
- package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
- package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts +14 -23
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +59 -26
- package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
- package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
- package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
- package/dist/index.d.ts +36 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -6
- package/dist/lib/level-name.d.ts +4 -0
- package/dist/lib/level-name.d.ts.map +1 -0
- package/dist/lib/level-name.js +10 -0
- package/dist/lib/measurement-geometry.d.ts +19 -0
- package/dist/lib/measurement-geometry.d.ts.map +1 -0
- package/dist/lib/measurement-geometry.js +232 -0
- package/dist/lib/measurement-geometry.test.d.ts +2 -0
- package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
- package/dist/lib/measurement-geometry.test.js +102 -0
- package/dist/lib/polygon-geometry.d.ts +0 -1
- package/dist/lib/polygon-geometry.d.ts.map +1 -1
- package/dist/lib/polygon-geometry.js +0 -18
- package/dist/lib/polygon-relations.d.ts +10 -0
- package/dist/lib/polygon-relations.d.ts.map +1 -0
- package/dist/lib/polygon-relations.js +73 -0
- package/dist/lib/selection-proxy.d.ts +4 -0
- package/dist/lib/selection-proxy.d.ts.map +1 -0
- package/dist/lib/selection-proxy.js +12 -0
- package/dist/lib/slab-polygon.d.ts +31 -2
- package/dist/lib/slab-polygon.d.ts.map +1 -1
- package/dist/lib/slab-polygon.js +525 -31
- package/dist/lib/slab-polygon.test.d.ts +2 -0
- package/dist/lib/slab-polygon.test.d.ts.map +1 -0
- package/dist/lib/slab-polygon.test.js +635 -0
- package/dist/lib/slots.d.ts +14 -0
- package/dist/lib/slots.d.ts.map +1 -0
- package/dist/lib/slots.js +26 -0
- package/dist/lib/space-detection-pause.test.d.ts +2 -0
- package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
- package/dist/lib/space-detection-pause.test.js +42 -0
- package/dist/lib/space-detection.d.ts +54 -2
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +599 -124
- package/dist/lib/space-detection.test.d.ts +2 -0
- package/dist/lib/space-detection.test.d.ts.map +1 -0
- package/dist/lib/space-detection.test.js +376 -0
- package/dist/lib/wall-distance.d.ts +60 -0
- package/dist/lib/wall-distance.d.ts.map +1 -0
- package/dist/lib/wall-distance.js +85 -0
- package/dist/lib/zone-quantities.d.ts +21 -0
- package/dist/lib/zone-quantities.d.ts.map +1 -0
- package/dist/lib/zone-quantities.js +183 -0
- package/dist/lib/zone-quantities.test.d.ts +2 -0
- package/dist/lib/zone-quantities.test.d.ts.map +1 -0
- package/dist/lib/zone-quantities.test.js +82 -0
- package/dist/material-library.d.ts +20 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +3737 -200
- package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
- package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
- package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
- package/dist/registry/handles.d.ts +394 -0
- package/dist/registry/handles.d.ts.map +1 -0
- package/dist/registry/handles.js +24 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/registry.d.ts +136 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +282 -0
- package/dist/registry/registry.test.d.ts +2 -0
- package/dist/registry/registry.test.d.ts.map +1 -0
- package/dist/registry/registry.test.js +196 -0
- package/dist/registry/relations-resolver.d.ts +42 -0
- package/dist/registry/relations-resolver.d.ts.map +1 -0
- package/dist/registry/relations-resolver.js +77 -0
- package/dist/registry/relations-resolver.test.d.ts +2 -0
- package/dist/registry/relations-resolver.test.d.ts.map +1 -0
- package/dist/registry/relations-resolver.test.js +183 -0
- package/dist/registry/scene-api.d.ts +44 -0
- package/dist/registry/scene-api.d.ts.map +1 -0
- package/dist/registry/scene-api.js +111 -0
- package/dist/registry/scene-api.test.d.ts +2 -0
- package/dist/registry/scene-api.test.d.ts.map +1 -0
- package/dist/registry/scene-api.test.js +183 -0
- package/dist/registry/subtree.d.ts +67 -0
- package/dist/registry/subtree.d.ts.map +1 -0
- package/dist/registry/subtree.js +123 -0
- package/dist/registry/subtree.test.d.ts +2 -0
- package/dist/registry/subtree.test.d.ts.map +1 -0
- package/dist/registry/subtree.test.js +163 -0
- package/dist/registry/types.d.ts +2099 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +1 -0
- package/dist/schema/index.d.ts +35 -4
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +32 -4
- package/dist/schema/material.d.ts +13 -2
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +17 -5
- package/dist/schema/material.test.d.ts +2 -0
- package/dist/schema/material.test.d.ts.map +1 -0
- package/dist/schema/material.test.js +43 -0
- package/dist/schema/nodes/box-vent.d.ts +74 -0
- package/dist/schema/nodes/box-vent.d.ts.map +1 -0
- package/dist/schema/nodes/box-vent.js +63 -0
- 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 +3 -2
- package/dist/schema/nodes/cabinet.d.ts +534 -0
- package/dist/schema/nodes/cabinet.d.ts.map +1 -0
- package/dist/schema/nodes/cabinet.js +137 -0
- package/dist/schema/nodes/ceiling.d.ts +5 -2
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +5 -1
- package/dist/schema/nodes/chimney.d.ts +158 -0
- package/dist/schema/nodes/chimney.d.ts.map +1 -0
- package/dist/schema/nodes/chimney.js +67 -0
- package/dist/schema/nodes/column.d.ts +600 -7
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +589 -6
- package/dist/schema/nodes/cupola.d.ts +66 -0
- package/dist/schema/nodes/cupola.d.ts.map +1 -0
- package/dist/schema/nodes/cupola.js +28 -0
- package/dist/schema/nodes/door.d.ts +13 -5
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +12 -0
- package/dist/schema/nodes/dormer.d.ts +228 -0
- package/dist/schema/nodes/dormer.d.ts.map +1 -0
- package/dist/schema/nodes/dormer.js +119 -0
- package/dist/schema/nodes/downspout.d.ts +75 -0
- package/dist/schema/nodes/downspout.d.ts.map +1 -0
- package/dist/schema/nodes/downspout.js +63 -0
- package/dist/schema/nodes/duct-fitting.d.ts +91 -0
- package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
- package/dist/schema/nodes/duct-fitting.js +92 -0
- package/dist/schema/nodes/duct-segment.d.ts +61 -0
- package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
- package/dist/schema/nodes/duct-segment.js +73 -0
- package/dist/schema/nodes/duct-terminal.d.ts +57 -0
- package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
- package/dist/schema/nodes/duct-terminal.js +51 -0
- package/dist/schema/nodes/elevator.d.ts +106 -0
- package/dist/schema/nodes/elevator.d.ts.map +1 -0
- package/dist/schema/nodes/elevator.js +57 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
- package/dist/schema/nodes/eyebrow-vent.js +40 -0
- package/dist/schema/nodes/fence.d.ts +19 -2
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +27 -2
- package/dist/schema/nodes/gutter.d.ts +84 -0
- package/dist/schema/nodes/gutter.d.ts.map +1 -0
- package/dist/schema/nodes/gutter.js +76 -0
- package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
- package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
- package/dist/schema/nodes/hvac-equipment.js +55 -0
- package/dist/schema/nodes/item.d.ts +12 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +20 -0
- 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 +4 -0
- package/dist/schema/nodes/lineset.d.ts +42 -0
- package/dist/schema/nodes/lineset.d.ts.map +1 -0
- package/dist/schema/nodes/lineset.js +38 -0
- package/dist/schema/nodes/liquid-line.d.ts +34 -0
- package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
- package/dist/schema/nodes/liquid-line.js +24 -0
- package/dist/schema/nodes/measurement.d.ts +306 -0
- package/dist/schema/nodes/measurement.d.ts.map +1 -0
- package/dist/schema/nodes/measurement.js +87 -0
- package/dist/schema/nodes/measurement.test.d.ts +2 -0
- package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
- package/dist/schema/nodes/measurement.test.js +139 -0
- package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
- package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-fitting.js +45 -0
- package/dist/schema/nodes/pipe-segment.d.ts +48 -0
- package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-segment.js +36 -0
- package/dist/schema/nodes/pipe-trap.d.ts +43 -0
- package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-trap.js +36 -0
- package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
- package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
- package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
- package/dist/schema/nodes/ridge-vent.d.ts +82 -0
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
- package/dist/schema/nodes/ridge-vent.js +248 -0
- package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
- package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-shape.js +318 -0
- package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
- package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
- package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
- package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
- package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-walls.js +330 -0
- package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
- package/dist/schema/nodes/roof-segment.d.ts +273 -3
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +539 -3
- package/dist/schema/nodes/roof.d.ts +8 -8
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +4 -16
- package/dist/schema/nodes/shelf.d.ts +106 -0
- package/dist/schema/nodes/shelf.d.ts.map +1 -0
- package/dist/schema/nodes/shelf.js +90 -0
- package/dist/schema/nodes/site.d.ts +1 -122
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +2 -6
- package/dist/schema/nodes/skylight.d.ts +279 -0
- package/dist/schema/nodes/skylight.d.ts.map +1 -0
- package/dist/schema/nodes/skylight.js +152 -0
- package/dist/schema/nodes/slab.d.ts +5 -2
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +5 -1
- package/dist/schema/nodes/solar-panel.d.ts +117 -0
- package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
- package/dist/schema/nodes/solar-panel.js +47 -0
- package/dist/schema/nodes/stair-segment.d.ts +4 -4
- package/dist/schema/nodes/stair.d.ts +13 -12
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +3 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
- package/dist/schema/nodes/surface-hole-metadata.js +3 -1
- package/dist/schema/nodes/turbine-vent.d.ts +68 -0
- package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
- package/dist/schema/nodes/turbine-vent.js +41 -0
- package/dist/schema/nodes/wall.d.ts +245 -6
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +257 -0
- package/dist/schema/nodes/wall.test.d.ts +2 -0
- package/dist/schema/nodes/wall.test.d.ts.map +1 -0
- package/dist/schema/nodes/wall.test.js +208 -0
- package/dist/schema/nodes/window.d.ts +11 -3
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +12 -0
- package/dist/schema/nodes/zone.d.ts +2 -0
- package/dist/schema/nodes/zone.d.ts.map +1 -1
- package/dist/schema/nodes/zone.js +6 -0
- package/dist/schema/scene-material.d.ts +41 -0
- package/dist/schema/scene-material.d.ts.map +1 -0
- package/dist/schema/scene-material.js +9 -0
- package/dist/schema/types.d.ts +2850 -655
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +49 -0
- package/dist/services/alignment-anchors.d.ts +100 -0
- package/dist/services/alignment-anchors.d.ts.map +1 -0
- package/dist/services/alignment-anchors.js +312 -0
- package/dist/services/alignment-anchors.test.d.ts +2 -0
- package/dist/services/alignment-anchors.test.d.ts.map +1 -0
- package/dist/services/alignment-anchors.test.js +383 -0
- package/dist/services/alignment.d.ts +131 -0
- package/dist/services/alignment.d.ts.map +1 -0
- package/dist/services/alignment.js +171 -0
- package/dist/services/alignment.test.d.ts +2 -0
- package/dist/services/alignment.test.d.ts.map +1 -0
- package/dist/services/alignment.test.js +92 -0
- package/dist/services/drag-session.d.ts +47 -0
- package/dist/services/drag-session.d.ts.map +1 -0
- package/dist/services/drag-session.js +100 -0
- package/dist/services/drag-session.test.d.ts +2 -0
- package/dist/services/drag-session.test.d.ts.map +1 -0
- package/dist/services/drag-session.test.js +219 -0
- package/dist/services/hosting.d.ts +88 -0
- package/dist/services/hosting.d.ts.map +1 -0
- package/dist/services/hosting.js +131 -0
- package/dist/services/hosting.test.d.ts +2 -0
- package/dist/services/hosting.test.d.ts.map +1 -0
- package/dist/services/hosting.test.js +230 -0
- package/dist/services/index.d.ts +13 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +12 -0
- package/dist/services/level-height.d.ts +28 -0
- package/dist/services/level-height.d.ts.map +1 -0
- package/dist/services/level-height.js +62 -0
- package/dist/services/movement.d.ts +48 -0
- package/dist/services/movement.d.ts.map +1 -0
- package/dist/services/movement.js +65 -0
- package/dist/services/movement.test.d.ts +2 -0
- package/dist/services/movement.test.d.ts.map +1 -0
- package/dist/services/movement.test.js +133 -0
- package/dist/services/opening-guides.d.ts +141 -0
- package/dist/services/opening-guides.d.ts.map +1 -0
- package/dist/services/opening-guides.js +267 -0
- package/dist/services/opening-guides.test.d.ts +2 -0
- package/dist/services/opening-guides.test.d.ts.map +1 -0
- package/dist/services/opening-guides.test.js +208 -0
- package/dist/services/port-connectivity.d.ts +115 -0
- package/dist/services/port-connectivity.d.ts.map +1 -0
- package/dist/services/port-connectivity.js +300 -0
- package/dist/services/port-connectivity.test.d.ts +2 -0
- package/dist/services/port-connectivity.test.d.ts.map +1 -0
- package/dist/services/port-connectivity.test.js +297 -0
- package/dist/services/riser-diagram.d.ts +40 -0
- package/dist/services/riser-diagram.d.ts.map +1 -0
- package/dist/services/riser-diagram.js +110 -0
- package/dist/services/riser-diagram.test.d.ts +2 -0
- package/dist/services/riser-diagram.test.d.ts.map +1 -0
- package/dist/services/riser-diagram.test.js +66 -0
- package/dist/services/single-undo-dance.test.d.ts +2 -0
- package/dist/services/single-undo-dance.test.d.ts.map +1 -0
- package/dist/services/single-undo-dance.test.js +210 -0
- package/dist/services/snap.d.ts +80 -0
- package/dist/services/snap.d.ts.map +1 -0
- package/dist/services/snap.js +140 -0
- package/dist/services/snap.test.d.ts +2 -0
- package/dist/services/snap.test.d.ts.map +1 -0
- package/dist/services/snap.test.js +137 -0
- package/dist/services/system-graph.d.ts +30 -0
- package/dist/services/system-graph.d.ts.map +1 -0
- package/dist/services/system-graph.js +164 -0
- package/dist/services/system-graph.test.d.ts +2 -0
- package/dist/services/system-graph.test.d.ts.map +1 -0
- package/dist/services/system-graph.test.js +138 -0
- package/dist/services/validate-dwv.d.ts +32 -0
- package/dist/services/validate-dwv.d.ts.map +1 -0
- package/dist/services/validate-dwv.js +118 -0
- package/dist/services/validate-dwv.test.d.ts +2 -0
- package/dist/services/validate-dwv.test.d.ts.map +1 -0
- package/dist/services/validate-dwv.test.js +109 -0
- package/dist/solar-panel-presets.d.ts +17 -0
- package/dist/solar-panel-presets.d.ts.map +1 -0
- package/dist/solar-panel-presets.js +43 -0
- package/dist/store/actions/node-actions.d.ts +14 -2
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +585 -15
- package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
- package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
- package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
- package/dist/store/actions/reparent.test.d.ts +2 -0
- package/dist/store/actions/reparent.test.d.ts.map +1 -0
- package/dist/store/actions/reparent.test.js +212 -0
- package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
- package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
- package/dist/store/actions/ridge-vent-update.test.js +264 -0
- package/dist/store/history-control.d.ts +11 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +33 -0
- package/dist/store/use-interactive.d.ts +39 -0
- package/dist/store/use-interactive.d.ts.map +1 -1
- package/dist/store/use-interactive.js +75 -0
- package/dist/store/use-live-node-overrides.d.ts +22 -0
- package/dist/store/use-live-node-overrides.d.ts.map +1 -0
- package/dist/store/use-live-node-overrides.js +59 -0
- package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
- package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
- package/dist/store/use-scene-dirty-tracking.test.js +68 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-elevator-migration.test.js +109 -0
- package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
- package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
- package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
- package/dist/store/use-scene-plugins.test.d.ts +2 -0
- package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
- package/dist/store/use-scene-plugins.test.js +51 -0
- package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
- package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
- package/dist/store/use-scene-window-migration.test.d.ts +2 -0
- package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-window-migration.test.js +85 -0
- package/dist/store/use-scene.d.ts +28 -2
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +546 -49
- package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
- package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-dispatch.js +57 -0
- package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
- package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-geometry.js +50 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.js +218 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-system.js +48 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime-system.js +8 -0
- package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
- package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.js +225 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.test.js +64 -0
- package/dist/systems/elevator/elevator-service.d.ts +19 -0
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-service.js +111 -0
- package/dist/systems/fence/fence-centerline.d.ts +12 -0
- package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
- package/dist/systems/fence/fence-centerline.js +37 -0
- package/dist/systems/fence/fence-spline.d.ts +39 -0
- package/dist/systems/fence/fence-spline.d.ts.map +1 -0
- package/dist/systems/fence/fence-spline.js +196 -0
- package/dist/systems/fence/fence-spline.test.d.ts +2 -0
- package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
- package/dist/systems/fence/fence-spline.test.js +136 -0
- package/dist/systems/stair/stair-footprint.d.ts +44 -0
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
- package/dist/systems/stair/stair-footprint.js +178 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.js +93 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.test.js +80 -0
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +155 -147
- package/dist/systems/stair/stair-opening-sync.test.js +424 -2
- package/dist/systems/stair/stair-opening-system.d.ts +2 -0
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-system.js +104 -0
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +38 -2
- package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
- package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-mitering.test.js +63 -0
- package/dist/systems/wall/wall-move.d.ts +48 -0
- package/dist/systems/wall/wall-move.d.ts.map +1 -0
- package/dist/systems/wall/wall-move.js +193 -0
- package/dist/utils/clone-scene-graph.d.ts +7 -4
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +29 -7
- package/dist/utils/clone-scene-graph.test.d.ts +2 -0
- package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
- package/dist/utils/clone-scene-graph.test.js +62 -0
- package/dist/utils/heal-scene-graph.d.ts +18 -0
- package/dist/utils/heal-scene-graph.d.ts.map +1 -0
- package/dist/utils/heal-scene-graph.js +90 -0
- package/dist/utils/heal-scene-graph.test.d.ts +2 -0
- package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
- package/dist/utils/heal-scene-graph.test.js +88 -0
- package/dist/validation/validate-build-json.d.ts +44 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -0
- package/dist/validation/validate-build-json.js +256 -0
- package/package.json +11 -4
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure 2D spline sampling for fences whose centerline is defined by a `path`
|
|
3
|
+
* of control points (the "flying path" curved fence).
|
|
4
|
+
*
|
|
5
|
+
* Each control point carries an OUT-handle offset vector. When the user has
|
|
6
|
+
* not adjusted it, the handle defaults to a distance-aware Catmull-Rom-style
|
|
7
|
+
* tangent: direction comes from neighbouring points, while length is capped by
|
|
8
|
+
* the shorter adjacent span. When the user drags a tangent handle (stored in
|
|
9
|
+
* `tangents[i]`), that point's handle becomes the stored vector and the IN
|
|
10
|
+
* handle is its mirror, so the curve stays smooth (C1) through the point but
|
|
11
|
+
* bends to taste. Each span is then a cubic Bézier between consecutive points
|
|
12
|
+
* using their handles.
|
|
13
|
+
*
|
|
14
|
+
* Lives in `@pascal-app/core` and imports NO Three.js — the same `CurveFrame`
|
|
15
|
+
* shape that `wall-curve.ts` returns (point / tangent / normal) is produced
|
|
16
|
+
* here so the spline branch is a drop-in for the arc branch in every consumer.
|
|
17
|
+
*/
|
|
18
|
+
const EPSILON = 1e-6;
|
|
19
|
+
const DEFAULT_SEGMENTS_PER_SPAN = 32;
|
|
20
|
+
const TWO_POINT_CURVE_SAGITTA_RATIO = 0.18;
|
|
21
|
+
const TWO_POINT_CURVE_MIN_SAGITTA = 0.18;
|
|
22
|
+
const TWO_POINT_CURVE_MAX_SAGITTA = 1.2;
|
|
23
|
+
export function isSplineFence(fence) {
|
|
24
|
+
return Array.isArray(fence.path) && fence.path.length >= 2;
|
|
25
|
+
}
|
|
26
|
+
function toPoints(path) {
|
|
27
|
+
return path.map(([x, y]) => ({ x, y }));
|
|
28
|
+
}
|
|
29
|
+
function distance(a, b) {
|
|
30
|
+
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
31
|
+
}
|
|
32
|
+
function clamp01(value) {
|
|
33
|
+
return Math.max(0, Math.min(1, value));
|
|
34
|
+
}
|
|
35
|
+
function clamp(value, min, max) {
|
|
36
|
+
return Math.max(min, Math.min(max, value));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* OUT-handle offset vector for control point `index` — the stored tangent if
|
|
40
|
+
* the user has adjusted it, otherwise the automatic distance-aware tangent
|
|
41
|
+
* (endpoints duplicate the neighbour so the ends stay tangent to their single
|
|
42
|
+
* span). The IN handle is the negation of this.
|
|
43
|
+
*
|
|
44
|
+
* Exported so the editing UI can draw the tangent line / handle dots at the
|
|
45
|
+
* right place even before the user has dragged them.
|
|
46
|
+
*/
|
|
47
|
+
export function getFenceControlHandle(path, tangents, index) {
|
|
48
|
+
const stored = tangents?.[index];
|
|
49
|
+
if (stored) {
|
|
50
|
+
return { x: stored[0], y: stored[1] };
|
|
51
|
+
}
|
|
52
|
+
const prev = path[index - 1] ?? path[index];
|
|
53
|
+
const next = path[index + 1] ?? path[index];
|
|
54
|
+
const prevDistance = distance({ x: path[index][0], y: path[index][1] }, {
|
|
55
|
+
x: prev[0],
|
|
56
|
+
y: prev[1],
|
|
57
|
+
});
|
|
58
|
+
const nextDistance = distance({ x: path[index][0], y: path[index][1] }, {
|
|
59
|
+
x: next[0],
|
|
60
|
+
y: next[1],
|
|
61
|
+
});
|
|
62
|
+
const handleLength = Math.min(prevDistance || nextDistance, nextDistance || prevDistance) / 3;
|
|
63
|
+
const vx = next[0] - prev[0];
|
|
64
|
+
const vy = next[1] - prev[1];
|
|
65
|
+
const len = Math.hypot(vx, vy);
|
|
66
|
+
if (len < EPSILON || handleLength < EPSILON) {
|
|
67
|
+
return { x: 0, y: 0 };
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
x: (vx / len) * handleLength,
|
|
71
|
+
y: (vy / len) * handleLength,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function getTwoPointFenceCurveTangents(path) {
|
|
75
|
+
if (path.length !== 2)
|
|
76
|
+
return undefined;
|
|
77
|
+
const start = path[0];
|
|
78
|
+
const end = path[1];
|
|
79
|
+
const dx = end[0] - start[0];
|
|
80
|
+
const dy = end[1] - start[1];
|
|
81
|
+
const chordLength = Math.hypot(dx, dy);
|
|
82
|
+
if (chordLength < EPSILON)
|
|
83
|
+
return undefined;
|
|
84
|
+
const tangentX = dx / 3;
|
|
85
|
+
const tangentY = dy / 3;
|
|
86
|
+
const normalX = -dy / chordLength;
|
|
87
|
+
const normalY = dx / chordLength;
|
|
88
|
+
const sagitta = clamp(chordLength * TWO_POINT_CURVE_SAGITTA_RATIO, TWO_POINT_CURVE_MIN_SAGITTA, TWO_POINT_CURVE_MAX_SAGITTA);
|
|
89
|
+
const bendX = normalX * sagitta * (4 / 3);
|
|
90
|
+
const bendY = normalY * sagitta * (4 / 3);
|
|
91
|
+
return [
|
|
92
|
+
[tangentX + bendX, tangentY + bendY],
|
|
93
|
+
[tangentX - bendX, tangentY - bendY],
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
function cubicBezier(p0, p1, p2, p3, u) {
|
|
97
|
+
const mu = 1 - u;
|
|
98
|
+
const a = mu * mu * mu;
|
|
99
|
+
const b = 3 * mu * mu * u;
|
|
100
|
+
const c = 3 * mu * u * u;
|
|
101
|
+
const d = u * u * u;
|
|
102
|
+
return {
|
|
103
|
+
x: a * p0.x + b * p1.x + c * p2.x + d * p3.x,
|
|
104
|
+
y: a * p0.y + b * p1.y + c * p2.y + d * p3.y,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function hasAnyTangent(tangents) {
|
|
108
|
+
return Array.isArray(tangents) && tangents.some((t) => t != null);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Sample the spline centerline into a polyline. Control points are honored as
|
|
112
|
+
* on-curve anchors; `segmentsPerSpan` controls smoothness between them.
|
|
113
|
+
* Returns `(segmentsPerSpan * spanCount) + 1` points, first == path[0],
|
|
114
|
+
* last == path[-1].
|
|
115
|
+
*/
|
|
116
|
+
export function sampleFenceSpline(path, tangents, segmentsPerSpan = DEFAULT_SEGMENTS_PER_SPAN) {
|
|
117
|
+
const pts = toPoints(path);
|
|
118
|
+
if (pts.length === 0)
|
|
119
|
+
return [];
|
|
120
|
+
if (pts.length === 1)
|
|
121
|
+
return [pts[0]];
|
|
122
|
+
// Two points with no adjusted tangents is a straight segment.
|
|
123
|
+
if (pts.length === 2 && !hasAnyTangent(tangents))
|
|
124
|
+
return [pts[0], pts[1]];
|
|
125
|
+
const steps = Math.max(1, Math.floor(segmentsPerSpan));
|
|
126
|
+
const result = [pts[0]];
|
|
127
|
+
for (let i = 0; i < pts.length - 1; i += 1) {
|
|
128
|
+
const p1 = pts[i];
|
|
129
|
+
const p2 = pts[i + 1];
|
|
130
|
+
const outHandle = getFenceControlHandle(path, tangents, i);
|
|
131
|
+
const nextHandle = getFenceControlHandle(path, tangents, i + 1);
|
|
132
|
+
// Bézier controls: leave p1 along its OUT handle, arrive at p2 along its
|
|
133
|
+
// IN handle (= negated OUT handle).
|
|
134
|
+
const c1 = { x: p1.x + outHandle.x, y: p1.y + outHandle.y };
|
|
135
|
+
const c2 = { x: p2.x - nextHandle.x, y: p2.y - nextHandle.y };
|
|
136
|
+
for (let s = 1; s <= steps; s += 1) {
|
|
137
|
+
result.push(cubicBezier(p1, c1, c2, p2, s / steps));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
function frameFromPolyline(points, t) {
|
|
143
|
+
if (points.length === 0) {
|
|
144
|
+
return {
|
|
145
|
+
point: { x: 0, y: 0 },
|
|
146
|
+
tangent: { x: 1, y: 0 },
|
|
147
|
+
normal: { x: 0, y: 1 },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (points.length === 1) {
|
|
151
|
+
return {
|
|
152
|
+
point: points[0],
|
|
153
|
+
tangent: { x: 1, y: 0 },
|
|
154
|
+
normal: { x: 0, y: 1 },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const clamped = clamp01(t);
|
|
158
|
+
const lastIndex = points.length - 1;
|
|
159
|
+
const scaled = clamped * lastIndex;
|
|
160
|
+
const lower = Math.min(lastIndex - 1, Math.floor(scaled));
|
|
161
|
+
const upper = lower + 1;
|
|
162
|
+
const localU = scaled - lower;
|
|
163
|
+
const a = points[lower];
|
|
164
|
+
const b = points[upper];
|
|
165
|
+
const point = {
|
|
166
|
+
x: a.x + (b.x - a.x) * localU,
|
|
167
|
+
y: a.y + (b.y - a.y) * localU,
|
|
168
|
+
};
|
|
169
|
+
const dx = b.x - a.x;
|
|
170
|
+
const dy = b.y - a.y;
|
|
171
|
+
const len = Math.hypot(dx, dy);
|
|
172
|
+
const tangent = len < EPSILON ? { x: 1, y: 0 } : { x: dx / len, y: dy / len };
|
|
173
|
+
return {
|
|
174
|
+
point,
|
|
175
|
+
tangent,
|
|
176
|
+
normal: { x: -tangent.y, y: tangent.x },
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Frame (point + tangent + normal) at parameter `t` in [0, 1] along the spline
|
|
181
|
+
* centerline. Same return shape as `getWallCurveFrameAt` so it is a drop-in for
|
|
182
|
+
* the arc branch. `t` is uniform over the sampled polyline (arc length is not
|
|
183
|
+
* reparameterised — adequate for marching posts / rails and far cheaper).
|
|
184
|
+
*/
|
|
185
|
+
export function getFenceSplineFrameAt(path, t, tangents, segmentsPerSpan = DEFAULT_SEGMENTS_PER_SPAN) {
|
|
186
|
+
return frameFromPolyline(sampleFenceSpline(path, tangents, segmentsPerSpan), t);
|
|
187
|
+
}
|
|
188
|
+
/** Total polyline length of the sampled spline centerline. */
|
|
189
|
+
export function getFenceSplineLength(path, tangents, segmentsPerSpan = DEFAULT_SEGMENTS_PER_SPAN) {
|
|
190
|
+
const points = sampleFenceSpline(path, tangents, segmentsPerSpan);
|
|
191
|
+
let total = 0;
|
|
192
|
+
for (let i = 1; i < points.length; i += 1) {
|
|
193
|
+
total += distance(points[i - 1], points[i]);
|
|
194
|
+
}
|
|
195
|
+
return total;
|
|
196
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fence-spline.test.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-spline.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { getFenceControlHandle, getFenceSplineFrameAt, getFenceSplineLength, getTwoPointFenceCurveTangents, isSplineFence, sampleFenceSpline, } from './fence-spline';
|
|
3
|
+
describe('isSplineFence', () => {
|
|
4
|
+
test('false without a path or with < 2 points', () => {
|
|
5
|
+
expect(isSplineFence({ path: undefined })).toBe(false);
|
|
6
|
+
expect(isSplineFence({ path: [[0, 0]] })).toBe(false);
|
|
7
|
+
});
|
|
8
|
+
test('true with >= 2 points', () => {
|
|
9
|
+
expect(isSplineFence({
|
|
10
|
+
path: [
|
|
11
|
+
[0, 0],
|
|
12
|
+
[1, 0],
|
|
13
|
+
],
|
|
14
|
+
})).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe('sampleFenceSpline', () => {
|
|
18
|
+
test('honors the control points as on-curve anchors', () => {
|
|
19
|
+
const path = [
|
|
20
|
+
[0, 0],
|
|
21
|
+
[2, 2],
|
|
22
|
+
[4, 0],
|
|
23
|
+
];
|
|
24
|
+
const sampled = sampleFenceSpline(path, undefined, 8);
|
|
25
|
+
// First and last sample equal the path endpoints exactly.
|
|
26
|
+
expect(sampled[0]).toEqual({ x: 0, y: 0 });
|
|
27
|
+
expect(sampled[sampled.length - 1]).toEqual({ x: 4, y: 0 });
|
|
28
|
+
// The interior control point is interpolated: some sample lands on (2, 2).
|
|
29
|
+
const hitsMiddle = sampled.some((p) => Math.hypot(p.x - 2, p.y - 2) < 1e-6);
|
|
30
|
+
expect(hitsMiddle).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
test('two-point path with no tangents is a straight segment', () => {
|
|
33
|
+
expect(sampleFenceSpline([
|
|
34
|
+
[0, 0],
|
|
35
|
+
[3, 0],
|
|
36
|
+
], undefined, 8)).toEqual([
|
|
37
|
+
{ x: 0, y: 0 },
|
|
38
|
+
{ x: 3, y: 0 },
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
test('a stored tangent bends an otherwise straight two-point span', () => {
|
|
42
|
+
const path = [
|
|
43
|
+
[0, 0],
|
|
44
|
+
[4, 0],
|
|
45
|
+
];
|
|
46
|
+
// Pull the first point's handle up — the span should bow off the X axis.
|
|
47
|
+
const sampled = sampleFenceSpline(path, [[0, 2], null], 12);
|
|
48
|
+
expect(sampled[0]).toEqual({ x: 0, y: 0 });
|
|
49
|
+
expect(sampled[sampled.length - 1]).toEqual({ x: 4, y: 0 });
|
|
50
|
+
const maxY = Math.max(...sampled.map((p) => Math.abs(p.y)));
|
|
51
|
+
expect(maxY).toBeGreaterThan(0.1);
|
|
52
|
+
});
|
|
53
|
+
test('generated two-point curve tangents create a gentle arc', () => {
|
|
54
|
+
const path = [
|
|
55
|
+
[0, 0],
|
|
56
|
+
[4, 0],
|
|
57
|
+
];
|
|
58
|
+
const sampled = sampleFenceSpline(path, getTwoPointFenceCurveTangents(path), 16);
|
|
59
|
+
expect(sampled[0]).toEqual({ x: 0, y: 0 });
|
|
60
|
+
expect(sampled[sampled.length - 1]).toEqual({ x: 4, y: 0 });
|
|
61
|
+
const maxY = Math.max(...sampled.map((p) => p.y));
|
|
62
|
+
expect(maxY).toBeGreaterThan(0.4);
|
|
63
|
+
});
|
|
64
|
+
test('produces a smooth (no-cusp) curve on uneven spacing', () => {
|
|
65
|
+
const path = [
|
|
66
|
+
[0, 0],
|
|
67
|
+
[1, 0.2],
|
|
68
|
+
[5, 0.3],
|
|
69
|
+
[6, 0],
|
|
70
|
+
];
|
|
71
|
+
const sampled = sampleFenceSpline(path, undefined, 16);
|
|
72
|
+
let maxTurn = 0;
|
|
73
|
+
for (let i = 2; i < sampled.length; i += 1) {
|
|
74
|
+
const a = sampled[i - 2];
|
|
75
|
+
const b = sampled[i - 1];
|
|
76
|
+
const c = sampled[i];
|
|
77
|
+
const t1 = Math.atan2(b.y - a.y, b.x - a.x);
|
|
78
|
+
const t2 = Math.atan2(c.y - b.y, c.x - b.x);
|
|
79
|
+
let d = Math.abs(t2 - t1);
|
|
80
|
+
if (d > Math.PI)
|
|
81
|
+
d = 2 * Math.PI - d;
|
|
82
|
+
maxTurn = Math.max(maxTurn, d);
|
|
83
|
+
}
|
|
84
|
+
expect(maxTurn).toBeLessThan(Math.PI / 2);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('getFenceControlHandle', () => {
|
|
88
|
+
test('returns the stored tangent when present', () => {
|
|
89
|
+
expect(getFenceControlHandle([
|
|
90
|
+
[0, 0],
|
|
91
|
+
[4, 0],
|
|
92
|
+
], [[1, 2], null], 0)).toEqual({ x: 1, y: 2 });
|
|
93
|
+
});
|
|
94
|
+
test('falls back to the automatic distance-aware tangent', () => {
|
|
95
|
+
const path = [
|
|
96
|
+
[0, 0],
|
|
97
|
+
[3, 0],
|
|
98
|
+
[6, 0],
|
|
99
|
+
];
|
|
100
|
+
expect(getFenceControlHandle(path, undefined, 1)).toEqual({ x: 1, y: 0 });
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('getFenceSplineFrameAt', () => {
|
|
104
|
+
const path = [
|
|
105
|
+
[0, 0],
|
|
106
|
+
[2, 0],
|
|
107
|
+
[4, 0],
|
|
108
|
+
];
|
|
109
|
+
test('t=0 / t=1 land on the endpoints', () => {
|
|
110
|
+
expect(getFenceSplineFrameAt(path, 0).point).toEqual({ x: 0, y: 0 });
|
|
111
|
+
expect(getFenceSplineFrameAt(path, 1).point).toEqual({ x: 4, y: 0 });
|
|
112
|
+
});
|
|
113
|
+
test('returns a unit tangent and perpendicular normal', () => {
|
|
114
|
+
const frame = getFenceSplineFrameAt(path, 0.5);
|
|
115
|
+
expect(Math.hypot(frame.tangent.x, frame.tangent.y)).toBeCloseTo(1, 5);
|
|
116
|
+
const dot = frame.tangent.x * frame.normal.x + frame.tangent.y * frame.normal.y;
|
|
117
|
+
expect(dot).toBeCloseTo(0, 5);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('getFenceSplineLength', () => {
|
|
121
|
+
test('roughly matches the straight distance for a straight path', () => {
|
|
122
|
+
expect(getFenceSplineLength([
|
|
123
|
+
[0, 0],
|
|
124
|
+
[3, 4],
|
|
125
|
+
], undefined, 8)).toBeCloseTo(5, 5);
|
|
126
|
+
});
|
|
127
|
+
test('a curved path is longer than its endpoint chord', () => {
|
|
128
|
+
const path = [
|
|
129
|
+
[0, 0],
|
|
130
|
+
[2, 2],
|
|
131
|
+
[4, 0],
|
|
132
|
+
];
|
|
133
|
+
const chord = Math.hypot(4, 0);
|
|
134
|
+
expect(getFenceSplineLength(path, undefined, 16)).toBeGreaterThan(chord);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AnyNode, StairNode, StairSegmentNode } from '../../schema';
|
|
2
|
+
/**
|
|
3
|
+
* Stair footprint geometry shared by the slab-opening sync and the
|
|
4
|
+
* alignment-anchor adapters. A stair has no single box footprint: straight
|
|
5
|
+
* stairs are a cumulative chain of `stair-segment` children, curved / spiral
|
|
6
|
+
* stairs are an annular sector stored entirely on the parent. Both reduce to
|
|
7
|
+
* an XZ bounding box here so callers that only need "where does the stair sit
|
|
8
|
+
* in plan" (alignment guides) don't have to re-walk the geometry.
|
|
9
|
+
*
|
|
10
|
+
* All math is in the building-local XZ frame, matching `node.position`.
|
|
11
|
+
*/
|
|
12
|
+
export type StairFootprintAABB = {
|
|
13
|
+
minX: number;
|
|
14
|
+
minZ: number;
|
|
15
|
+
maxX: number;
|
|
16
|
+
maxZ: number;
|
|
17
|
+
};
|
|
18
|
+
type SegmentTransform = {
|
|
19
|
+
position: [number, number, number];
|
|
20
|
+
rotation: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* XZ rotation in the stair geometry convention (equivalent to rotating by
|
|
24
|
+
* `-angle` in standard math): positive `angle` turns local +Z toward +X. Every
|
|
25
|
+
* stair helper — slab openings, floor-plan emitter, this footprint — shares it,
|
|
26
|
+
* so anchors line up with the rendered stair.
|
|
27
|
+
*/
|
|
28
|
+
export declare function rotateXZ(x: number, z: number, angle: number): [number, number];
|
|
29
|
+
/**
|
|
30
|
+
* Cumulative per-segment transforms for a straight (segment-chained) stair.
|
|
31
|
+
* Each flight attaches to the previous segment's end; `attachmentSide` rotates
|
|
32
|
+
* the chain ±90° (left / right) or continues straight (front). Positions are in
|
|
33
|
+
* the stair's local frame (before the stair's own `position` / `rotation`).
|
|
34
|
+
*/
|
|
35
|
+
export declare function computeSegmentTransforms(segments: StairSegmentNode[]): SegmentTransform[];
|
|
36
|
+
/**
|
|
37
|
+
* XZ bounding box of a stair's plan footprint, or null when it can't be
|
|
38
|
+
* determined (a straight stair whose segment children aren't in `nodes`).
|
|
39
|
+
* Straight stairs need the children to walk the flight chain; curved / spiral
|
|
40
|
+
* stairs are derived from the parent alone, so `nodes` is optional for them.
|
|
41
|
+
*/
|
|
42
|
+
export declare function stairFootprintAABB(stair: StairNode, nodes?: Readonly<Record<string, AnyNode>>): StairFootprintAABB | null;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=stair-footprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stair-footprint.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-footprint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAa,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEnF;;;;;;;;;GASG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE3F,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAI9E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CAkDzF;AAwHD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,EAChB,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,kBAAkB,GAAG,IAAI,CAK3B"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XZ rotation in the stair geometry convention (equivalent to rotating by
|
|
3
|
+
* `-angle` in standard math): positive `angle` turns local +Z toward +X. Every
|
|
4
|
+
* stair helper — slab openings, floor-plan emitter, this footprint — shares it,
|
|
5
|
+
* so anchors line up with the rendered stair.
|
|
6
|
+
*/
|
|
7
|
+
export function rotateXZ(x, z, angle) {
|
|
8
|
+
const cos = Math.cos(angle);
|
|
9
|
+
const sin = Math.sin(angle);
|
|
10
|
+
return [x * cos + z * sin, -x * sin + z * cos];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Cumulative per-segment transforms for a straight (segment-chained) stair.
|
|
14
|
+
* Each flight attaches to the previous segment's end; `attachmentSide` rotates
|
|
15
|
+
* the chain ±90° (left / right) or continues straight (front). Positions are in
|
|
16
|
+
* the stair's local frame (before the stair's own `position` / `rotation`).
|
|
17
|
+
*/
|
|
18
|
+
export function computeSegmentTransforms(segments) {
|
|
19
|
+
const transforms = [];
|
|
20
|
+
let currentX = 0;
|
|
21
|
+
let currentY = 0;
|
|
22
|
+
let currentZ = 0;
|
|
23
|
+
let currentRot = 0;
|
|
24
|
+
for (let index = 0; index < segments.length; index++) {
|
|
25
|
+
const segment = segments[index];
|
|
26
|
+
if (!segment)
|
|
27
|
+
continue;
|
|
28
|
+
if (index === 0) {
|
|
29
|
+
transforms.push({ position: [currentX, currentY, currentZ], rotation: currentRot });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const previous = segments[index - 1];
|
|
33
|
+
if (!previous)
|
|
34
|
+
continue;
|
|
35
|
+
let attachX = 0;
|
|
36
|
+
let attachZ = 0;
|
|
37
|
+
let rotationDelta = 0;
|
|
38
|
+
switch (segment.attachmentSide) {
|
|
39
|
+
case 'front':
|
|
40
|
+
attachX = 0;
|
|
41
|
+
attachZ = previous.length;
|
|
42
|
+
break;
|
|
43
|
+
case 'left':
|
|
44
|
+
attachX = previous.width / 2;
|
|
45
|
+
attachZ = previous.length / 2;
|
|
46
|
+
rotationDelta = Math.PI / 2;
|
|
47
|
+
break;
|
|
48
|
+
case 'right':
|
|
49
|
+
attachX = -previous.width / 2;
|
|
50
|
+
attachZ = previous.length / 2;
|
|
51
|
+
rotationDelta = -Math.PI / 2;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
const [deltaX, deltaZ] = rotateXZ(attachX, attachZ, currentRot);
|
|
55
|
+
currentX += deltaX;
|
|
56
|
+
currentY += previous.height;
|
|
57
|
+
currentZ += deltaZ;
|
|
58
|
+
currentRot += rotationDelta;
|
|
59
|
+
transforms.push({ position: [currentX, currentY, currentZ], rotation: currentRot });
|
|
60
|
+
}
|
|
61
|
+
return transforms;
|
|
62
|
+
}
|
|
63
|
+
function emptyBox() {
|
|
64
|
+
return {
|
|
65
|
+
minX: Number.POSITIVE_INFINITY,
|
|
66
|
+
minZ: Number.POSITIVE_INFINITY,
|
|
67
|
+
maxX: Number.NEGATIVE_INFINITY,
|
|
68
|
+
maxZ: Number.NEGATIVE_INFINITY,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Grow `box` to include the world-plan point produced by rotating the
|
|
72
|
+
* stair-local point by the stair's rotation and offsetting by its position. */
|
|
73
|
+
function extendByLocal(box, stair, localX, localZ) {
|
|
74
|
+
const [wx, wz] = rotateXZ(localX, localZ, stair.rotation ?? 0);
|
|
75
|
+
const x = stair.position[0] + wx;
|
|
76
|
+
const z = stair.position[2] + wz;
|
|
77
|
+
if (x < box.minX)
|
|
78
|
+
box.minX = x;
|
|
79
|
+
if (x > box.maxX)
|
|
80
|
+
box.maxX = x;
|
|
81
|
+
if (z < box.minZ)
|
|
82
|
+
box.minZ = z;
|
|
83
|
+
if (z > box.maxZ)
|
|
84
|
+
box.maxZ = z;
|
|
85
|
+
}
|
|
86
|
+
function finiteBox(box) {
|
|
87
|
+
return Number.isFinite(box.minX) && Number.isFinite(box.minZ) ? box : null;
|
|
88
|
+
}
|
|
89
|
+
/** Bounding box of a straight stair's segment chain, walking the children. */
|
|
90
|
+
function straightStairAABB(stair, nodes) {
|
|
91
|
+
const segments = (stair.children ?? [])
|
|
92
|
+
.map((childId) => nodes[childId])
|
|
93
|
+
.filter((segment) => segment?.type === 'stair-segment' && segment.visible !== false);
|
|
94
|
+
if (segments.length === 0)
|
|
95
|
+
return null;
|
|
96
|
+
const transforms = computeSegmentTransforms(segments);
|
|
97
|
+
const box = emptyBox();
|
|
98
|
+
segments.forEach((segment, index) => {
|
|
99
|
+
const transform = transforms[index];
|
|
100
|
+
if (!transform)
|
|
101
|
+
return;
|
|
102
|
+
const halfWidth = segment.width / 2;
|
|
103
|
+
// Segment-local footprint: X across the flight, Z along the run from the
|
|
104
|
+
// attachment edge (0) to the far edge (length).
|
|
105
|
+
for (const [cornerX, cornerZ] of [
|
|
106
|
+
[-halfWidth, 0],
|
|
107
|
+
[halfWidth, 0],
|
|
108
|
+
[halfWidth, segment.length],
|
|
109
|
+
[-halfWidth, segment.length],
|
|
110
|
+
]) {
|
|
111
|
+
const [offsetX, offsetZ] = rotateXZ(cornerX, cornerZ, transform.rotation);
|
|
112
|
+
extendByLocal(box, stair, transform.position[0] + offsetX, transform.position[2] + offsetZ);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return finiteBox(box);
|
|
116
|
+
}
|
|
117
|
+
const ARC_SAMPLES = 48;
|
|
118
|
+
function getSpiralLandingSweep(stair, sweepAngle) {
|
|
119
|
+
if ((stair.topLandingMode ?? 'none') !== 'integrated')
|
|
120
|
+
return 0;
|
|
121
|
+
const innerRadius = Math.max(0.05, stair.innerRadius ?? 0.9);
|
|
122
|
+
const width = Math.max(stair.width ?? 1, 0.4);
|
|
123
|
+
const landingDepth = Math.max(0.3, stair.topLandingDepth ?? Math.max(width * 0.9, 0.8));
|
|
124
|
+
return (Math.min(Math.PI * 0.75, landingDepth / Math.max(innerRadius + width / 2, 0.1)) *
|
|
125
|
+
Math.sign(sweepAngle || 1));
|
|
126
|
+
}
|
|
127
|
+
/** Bounding box of a curved / spiral stair's annular sector (plus the
|
|
128
|
+
* integrated spiral top landing when present). */
|
|
129
|
+
function arcStairAABB(stair) {
|
|
130
|
+
const isSpiral = stair.stairType === 'spiral';
|
|
131
|
+
const minInnerRadius = isSpiral ? 0.05 : 0.2;
|
|
132
|
+
const innerRadius = Math.max(minInnerRadius, stair.innerRadius ?? (isSpiral ? 0.2 : 0.9));
|
|
133
|
+
const width = Math.max(stair.width ?? 1, 0.4);
|
|
134
|
+
const outerRadius = innerRadius + width;
|
|
135
|
+
const rawSweep = stair.sweepAngle ?? (isSpiral ? Math.PI * 2 : Math.PI / 2);
|
|
136
|
+
let sweep = rawSweep;
|
|
137
|
+
// A full revolution would make the arc degenerate; clamp just under 2π the
|
|
138
|
+
// same way the floor-plan emitter does so the sampled box stays correct.
|
|
139
|
+
if (Math.abs(sweep) >= Math.PI * 2)
|
|
140
|
+
sweep = Math.sign(sweep || 1) * (Math.PI * 2 - 0.001);
|
|
141
|
+
const half = sweep / 2;
|
|
142
|
+
const box = emptyBox();
|
|
143
|
+
// Sample both rims across the sweep — the extremes can fall on either the
|
|
144
|
+
// arc ends or an axis crossing in between, so we need the full sweep.
|
|
145
|
+
for (let step = 0; step <= ARC_SAMPLES; step += 1) {
|
|
146
|
+
const angle = -half + (sweep * step) / ARC_SAMPLES;
|
|
147
|
+
const cos = Math.cos(angle);
|
|
148
|
+
const sin = Math.sin(angle);
|
|
149
|
+
extendByLocal(box, stair, cos * innerRadius, sin * innerRadius);
|
|
150
|
+
extendByLocal(box, stair, cos * outerRadius, sin * outerRadius);
|
|
151
|
+
}
|
|
152
|
+
// Integrated spiral top landing renders as an angular extension of the
|
|
153
|
+
// annular stair body, not as a rectangular box outside the outer rim.
|
|
154
|
+
if (isSpiral && stair.topLandingMode === 'integrated') {
|
|
155
|
+
const landingSweep = getSpiralLandingSweep(stair, rawSweep);
|
|
156
|
+
const landingSteps = Math.max(1, Math.ceil(Math.abs(landingSweep) / (Math.PI / 24)));
|
|
157
|
+
for (let step = 0; step <= landingSteps; step += 1) {
|
|
158
|
+
const angle = rawSweep / 2 + (landingSweep * step) / landingSteps;
|
|
159
|
+
const cos = Math.cos(angle);
|
|
160
|
+
const sin = Math.sin(angle);
|
|
161
|
+
extendByLocal(box, stair, cos * innerRadius, sin * innerRadius);
|
|
162
|
+
extendByLocal(box, stair, cos * outerRadius, sin * outerRadius);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return finiteBox(box);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* XZ bounding box of a stair's plan footprint, or null when it can't be
|
|
169
|
+
* determined (a straight stair whose segment children aren't in `nodes`).
|
|
170
|
+
* Straight stairs need the children to walk the flight chain; curved / spiral
|
|
171
|
+
* stairs are derived from the parent alone, so `nodes` is optional for them.
|
|
172
|
+
*/
|
|
173
|
+
export function stairFootprintAABB(stair, nodes) {
|
|
174
|
+
if ((stair.stairType ?? 'straight') === 'straight') {
|
|
175
|
+
return nodes ? straightStairAABB(stair, nodes) : null;
|
|
176
|
+
}
|
|
177
|
+
return arcStairAABB(stair);
|
|
178
|
+
}
|