@pascal-app/viewer 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/components/error-boundary.d.ts +6 -0
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +6 -0
- package/dist/components/renderers/node-renderer.d.ts +6 -2
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +41 -22
- package/dist/components/renderers/parametric-node-renderer.d.ts +5 -0
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -0
- package/dist/components/renderers/parametric-node-renderer.js +37 -0
- package/dist/components/renderers/scene-renderer.d.ts +1 -1
- package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
- package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
- package/dist/components/viewer/bvh-ecctrl.js +552 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +39 -4
- package/dist/components/viewer/glb-interactive.d.ts +41 -0
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
- package/dist/components/viewer/glb-interactive.js +394 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
- package/dist/components/viewer/glb-reference-nodes.js +80 -0
- package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
- package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
- package/dist/components/viewer/glb-replace-instances.js +61 -0
- package/dist/components/viewer/glb-scene.d.ts +54 -0
- package/dist/components/viewer/glb-scene.d.ts.map +1 -0
- package/dist/components/viewer/glb-scene.js +920 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +12 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/glb-walkthrough-controller.js +357 -0
- package/dist/components/viewer/index.d.ts +56 -3
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +280 -65
- package/dist/components/viewer/lights.d.ts +1 -1
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +201 -43
- package/dist/components/viewer/perf-monitor.d.ts +1 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +115 -12
- package/dist/components/viewer/post-processing.d.ts +7 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +395 -63
- package/dist/components/viewer/registered-systems.d.ts +12 -0
- package/dist/components/viewer/registered-systems.d.ts.map +1 -0
- package/dist/components/viewer/registered-systems.js +49 -0
- package/dist/components/viewer/scene-bvh.d.ts +18 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.js +97 -0
- package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
- package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.test.js +13 -0
- package/dist/components/viewer/scene-environment.d.ts +2 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -0
- package/dist/components/viewer/scene-environment.js +79 -0
- package/dist/components/viewer/selection-manager.d.ts +1 -1
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +37 -14
- package/dist/components/viewer/viewer-camera.d.ts +1 -1
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.d.ts +2 -1
- package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.js +20 -0
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +3 -22
- package/dist/hooks/use-node-events.d.ts +6 -74
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +28 -8
- package/dist/index.d.ts +48 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -4
- package/dist/lib/backdrop.d.ts +33 -0
- package/dist/lib/backdrop.d.ts.map +1 -0
- package/dist/lib/backdrop.js +92 -0
- package/dist/lib/box-uv.d.ts +13 -0
- package/dist/lib/box-uv.d.ts.map +1 -0
- package/dist/lib/box-uv.js +32 -0
- package/dist/lib/csg-utils.d.ts +10 -0
- package/dist/lib/csg-utils.d.ts.map +1 -0
- package/dist/lib/csg-utils.js +93 -0
- package/dist/lib/csg-utils.test.d.ts +2 -0
- package/dist/lib/csg-utils.test.d.ts.map +1 -0
- package/dist/lib/csg-utils.test.js +45 -0
- package/dist/lib/drawable-geometry.d.ts +21 -0
- package/dist/lib/drawable-geometry.d.ts.map +1 -0
- package/dist/lib/drawable-geometry.js +22 -0
- package/dist/lib/edge-style.d.ts +4 -0
- package/dist/lib/edge-style.d.ts.map +1 -0
- package/dist/lib/edge-style.js +31 -0
- package/dist/lib/gpu-perf.d.ts +4 -0
- package/dist/lib/gpu-perf.d.ts.map +1 -0
- package/dist/lib/gpu-perf.js +23 -0
- package/dist/lib/ink-edges.d.ts +9 -0
- package/dist/lib/ink-edges.d.ts.map +1 -0
- package/dist/lib/ink-edges.js +49 -0
- package/dist/lib/isolation.d.ts +39 -0
- package/dist/lib/isolation.d.ts.map +1 -0
- package/dist/lib/isolation.js +103 -0
- package/dist/lib/ktx2-loader.d.ts +43 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -0
- package/dist/lib/ktx2-loader.js +136 -0
- package/dist/lib/layers.d.ts +28 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +28 -0
- package/dist/lib/materials.d.ts +38 -15
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +364 -72
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +7 -0
- package/dist/lib/polygon-union.d.ts +4 -0
- package/dist/lib/polygon-union.d.ts.map +1 -0
- package/dist/lib/polygon-union.js +289 -0
- package/dist/lib/polygon-union.test.d.ts +2 -0
- package/dist/lib/polygon-union.test.d.ts.map +1 -0
- package/dist/lib/polygon-union.test.js +105 -0
- package/dist/lib/scene-themes.d.ts +30 -0
- package/dist/lib/scene-themes.d.ts.map +1 -0
- package/dist/lib/scene-themes.js +202 -0
- package/dist/lib/shadow-only.d.ts +4 -0
- package/dist/lib/shadow-only.d.ts.map +1 -0
- package/dist/lib/shadow-only.js +36 -0
- package/dist/lib/tsl-compat.d.ts +12 -0
- package/dist/lib/tsl-compat.d.ts.map +1 -0
- package/dist/lib/tsl-compat.js +13 -0
- package/dist/store/use-viewer.d.ts +92 -12
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +201 -6
- package/dist/store/use-viewer.test.d.ts +2 -0
- package/dist/store/use-viewer.test.d.ts.map +1 -0
- package/dist/store/use-viewer.test.js +42 -0
- package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +66 -13
- package/dist/systems/door/door-animation.test.d.ts +2 -0
- package/dist/systems/door/door-animation.test.d.ts.map +1 -0
- package/dist/systems/door/door-animation.test.js +51 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
- package/dist/systems/door/door-floor-alignment.test.js +50 -0
- package/dist/systems/door/door-system.d.ts +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +864 -180
- package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-interaction-system.js +62 -0
- package/dist/systems/fence/fence-system.d.ts +20 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +229 -57
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
- package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
- package/dist/systems/geometry/geometry-system.d.ts +41 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.js +348 -0
- package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
- package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.test.js +30 -0
- package/dist/systems/interactive/control-widget.d.ts +10 -0
- package/dist/systems/interactive/control-widget.d.ts.map +1 -0
- package/dist/systems/interactive/control-widget.js +35 -0
- package/dist/systems/interactive/interactive-system.d.ts +1 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +55 -56
- package/dist/systems/item/item-system.d.ts +7 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +28 -17
- package/dist/systems/item-light/item-light-system.d.ts +1 -1
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +18 -5
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +28 -4
- package/dist/systems/level/level-utils.d.ts +0 -3
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +2 -41
- package/dist/systems/roof/roof-materials.d.ts +3 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +59 -14
- package/dist/systems/roof/roof-system.d.ts +46 -6
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1365 -170
- package/dist/systems/slab/slab-system.d.ts +5 -3
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +204 -68
- package/dist/systems/slab/slab-system.test.d.ts +2 -0
- package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
- package/dist/systems/slab/slab-system.test.js +63 -0
- package/dist/systems/stair/stair-materials.d.ts +4 -3
- package/dist/systems/stair/stair-materials.d.ts.map +1 -1
- package/dist/systems/stair/stair-materials.js +30 -15
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +55 -63
- package/dist/systems/surface-hole-geometry.d.ts +3 -0
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
- package/dist/systems/surface-hole-geometry.js +4 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.js +167 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +62 -17
- package/dist/systems/wall/wall-materials.d.ts +12 -7
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +279 -46
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-support-extension.test.js +48 -0
- package/dist/systems/wall/wall-system.d.ts +2 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +493 -193
- package/dist/systems/window/window-animation-system.d.ts +12 -0
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +16 -5
- package/dist/systems/window/window-system.d.ts +7 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +308 -18
- package/package.json +5 -6
- package/dist/components/renderers/building/building-renderer.d.ts +0 -5
- package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
- package/dist/components/renderers/building/building-renderer.js +0 -11
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
- package/dist/components/renderers/column/column-renderer.d.ts +0 -5
- package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
- package/dist/components/renderers/column/column-renderer.js +0 -597
- package/dist/components/renderers/door/door-renderer.d.ts +0 -5
- package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
- package/dist/components/renderers/door/door-renderer.js +0 -16
- package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
- package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
- package/dist/components/renderers/fence/fence-renderer.js +0 -15
- package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
- package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
- package/dist/components/renderers/guide/guide-renderer.js +0 -38
- package/dist/components/renderers/item/item-renderer.d.ts +0 -5
- package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
- package/dist/components/renderers/item/item-renderer.js +0 -172
- package/dist/components/renderers/level/level-renderer.d.ts +0 -5
- package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
- package/dist/components/renderers/level/level-renderer.js +0 -11
- package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
- package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-materials.js +0 -16
- package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-renderer.js +0 -32
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
- package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
- package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
- package/dist/components/renderers/scan/scan-renderer.js +0 -54
- package/dist/components/renderers/site/site-renderer.d.ts +0 -5
- package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
- package/dist/components/renderers/site/site-renderer.js +0 -106
- package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
- package/dist/components/renderers/slab/slab-renderer.js +0 -58
- package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
- package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
- package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
- package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
- package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair/stair-renderer.js +0 -603
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -51
- package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
- package/dist/components/renderers/wall/wall-renderer.js +0 -16
- package/dist/components/renderers/window/window-renderer.d.ts +0 -5
- package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
- package/dist/components/renderers/window/window-renderer.js +0 -21
- package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
- package/dist/components/renderers/zone/zone-renderer.js +0 -181
- package/dist/components/viewer/ground-occluder.d.ts +0 -2
- package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
- package/dist/components/viewer/ground-occluder.js +0 -79
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elevator-interaction-system.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-interaction-system.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,yBAAyB,SA0DxC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { openElevatorDoor, requestElevatorLevel, resolveElevatorDispatchTarget, useInteractive, useScene, } from '@pascal-app/core';
|
|
2
|
+
import { useThree } from '@react-three/fiber';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { Raycaster, Vector2 } from 'three';
|
|
5
|
+
function getElevatorButtonData(object) {
|
|
6
|
+
let current = object;
|
|
7
|
+
while (current) {
|
|
8
|
+
const data = current.userData.elevatorButton;
|
|
9
|
+
if (data)
|
|
10
|
+
return data;
|
|
11
|
+
current = current.parent;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
export function ElevatorInteractionSystem() {
|
|
16
|
+
const camera = useThree((state) => state.camera);
|
|
17
|
+
const gl = useThree((state) => state.gl);
|
|
18
|
+
const scene = useThree((state) => state.scene);
|
|
19
|
+
const raycasterRef = useRef(new Raycaster());
|
|
20
|
+
const pointerRef = useRef(new Vector2());
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const canvas = gl.domElement;
|
|
23
|
+
const handlePointerDown = (event) => {
|
|
24
|
+
if (event.button !== 0)
|
|
25
|
+
return;
|
|
26
|
+
const rect = canvas.getBoundingClientRect();
|
|
27
|
+
const pointer = pointerRef.current;
|
|
28
|
+
pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
29
|
+
pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
30
|
+
const raycaster = raycasterRef.current;
|
|
31
|
+
raycaster.setFromCamera(pointer, camera);
|
|
32
|
+
const button = raycaster
|
|
33
|
+
.intersectObjects(scene.children, true)
|
|
34
|
+
.map((intersection) => getElevatorButtonData(intersection.object))
|
|
35
|
+
.find((data) => data !== null);
|
|
36
|
+
if (!button || button.disabled)
|
|
37
|
+
return;
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
event.stopPropagation();
|
|
40
|
+
if (button.action === 'open-door') {
|
|
41
|
+
openElevatorDoor(button.elevatorId);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!button.levelId)
|
|
45
|
+
return;
|
|
46
|
+
const targetElevatorId = button.kind === 'landing'
|
|
47
|
+
? resolveElevatorDispatchTarget({
|
|
48
|
+
elevators: useInteractive.getState().elevators,
|
|
49
|
+
levelId: button.levelId,
|
|
50
|
+
nodes: useScene.getState().nodes,
|
|
51
|
+
requestedElevatorId: button.elevatorId,
|
|
52
|
+
})
|
|
53
|
+
: button.elevatorId;
|
|
54
|
+
requestElevatorLevel(targetElevatorId, button.levelId);
|
|
55
|
+
};
|
|
56
|
+
canvas.addEventListener('pointerdown', handlePointerDown, true);
|
|
57
|
+
return () => {
|
|
58
|
+
canvas.removeEventListener('pointerdown', handlePointerDown, true);
|
|
59
|
+
};
|
|
60
|
+
}, [camera, gl, scene]);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
@@ -1,2 +1,22 @@
|
|
|
1
|
+
import { type FenceNode } from '@pascal-app/core';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
type FencePart = {
|
|
4
|
+
geometry?: THREE.BufferGeometry;
|
|
5
|
+
position: [number, number, number];
|
|
6
|
+
rotationY?: number;
|
|
7
|
+
scale: [number, number, number];
|
|
8
|
+
shape?: 'box' | 'pyramid';
|
|
9
|
+
};
|
|
10
|
+
export type FenceSlotId = 'posts' | 'infill' | 'base' | 'rail';
|
|
11
|
+
export type FenceSlotParts = Record<FenceSlotId, FencePart[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Geometry split by paint slot — posts, infill, base, rail — each a separate
|
|
14
|
+
* merged BufferGeometry (empty ones included) so the fence renderer can give
|
|
15
|
+
* each its own material + `userData.slotId`. Slots match the panel's build
|
|
16
|
+
* options 1:1.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateFenceSlotGeometries(fence: FenceNode): Record<FenceSlotId, THREE.BufferGeometry>;
|
|
19
|
+
export declare function generateFenceGeometry(fence: FenceNode): THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>;
|
|
1
20
|
export declare const FenceSystem: () => null;
|
|
21
|
+
export {};
|
|
2
22
|
//# sourceMappingURL=fence-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAKf,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,KAAK,SAAS,GAAG;IACf,QAAQ,CAAC,EAAE,KAAK,CAAC,cAAc,CAAA;IAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAG/B,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAA;CAC1B,CAAA;AAqPD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAA;AAE9D,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW,EAAE,SAAS,EAAE,CAAC,CAAA;AAsQ7D;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,SAAS,GACf,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,cAAc,CAAC,CAQ3C;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,oFAGrD;AAgBD,eAAO,MAAM,WAAW,YAiBvB,CAAA"}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getFenceCenterlineFrameAt, getFenceCenterlineLength, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
5
5
|
const MIN_CURVE_SEGMENT_LENGTH = 0.18;
|
|
6
|
+
const HORIZONTAL_FENCE_CURVE_SEGMENT_LENGTH = 0.2;
|
|
6
7
|
function createFencePartGeometry(part) {
|
|
7
|
-
|
|
8
|
+
if (part.geometry) {
|
|
9
|
+
return part.geometry;
|
|
10
|
+
}
|
|
11
|
+
const geometry = part.shape === 'pyramid'
|
|
12
|
+
? new THREE.ConeGeometry(0.5, 1, 4, 1, false, Math.PI / 4)
|
|
13
|
+
: new THREE.BoxGeometry(1, 1, 1);
|
|
8
14
|
geometry.scale(part.scale[0], part.scale[1], part.scale[2]);
|
|
9
15
|
if (part.rotationY) {
|
|
10
16
|
geometry.rotateY(part.rotationY);
|
|
@@ -14,37 +20,79 @@ function createFencePartGeometry(part) {
|
|
|
14
20
|
return geometry;
|
|
15
21
|
}
|
|
16
22
|
function getFencePointAt(fence, t) {
|
|
17
|
-
const frame =
|
|
23
|
+
const frame = getFenceCenterlineFrameAt(fence, t);
|
|
18
24
|
return {
|
|
19
25
|
point: frame.point,
|
|
20
26
|
tangentAngle: Math.atan2(frame.tangent.y, frame.tangent.x),
|
|
21
27
|
};
|
|
22
28
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
const dz = end[1] - start[1];
|
|
26
|
-
const length = Math.hypot(dx, dz);
|
|
27
|
-
if (length <= 1e-4) {
|
|
29
|
+
function createFenceCurveBlockPart(fence, startT, endT, centerY, height, depth) {
|
|
30
|
+
if (endT - startT <= 1e-5)
|
|
28
31
|
return null;
|
|
32
|
+
const halfHeight = height / 2;
|
|
33
|
+
const halfDepth = depth / 2;
|
|
34
|
+
const centerlineLength = getFenceCenterlineLength(fence);
|
|
35
|
+
const startDistance = startT * centerlineLength;
|
|
36
|
+
const endDistance = endT * centerlineLength;
|
|
37
|
+
const bottomY = centerY - halfHeight;
|
|
38
|
+
const topY = centerY + halfHeight;
|
|
39
|
+
const corners = [];
|
|
40
|
+
for (const t of [startT, endT]) {
|
|
41
|
+
const frame = getFencePointAt(fence, t);
|
|
42
|
+
const normalX = -Math.sin(frame.tangentAngle);
|
|
43
|
+
const normalZ = Math.cos(frame.tangentAngle);
|
|
44
|
+
const outerX = frame.point.x + normalX * halfDepth;
|
|
45
|
+
const outerZ = frame.point.y + normalZ * halfDepth;
|
|
46
|
+
const innerX = frame.point.x - normalX * halfDepth;
|
|
47
|
+
const innerZ = frame.point.y - normalZ * halfDepth;
|
|
48
|
+
corners.push([outerX, bottomY, outerZ], [innerX, bottomY, innerZ], [outerX, topY, outerZ], [innerX, topY, innerZ]);
|
|
29
49
|
}
|
|
50
|
+
const positions = [];
|
|
51
|
+
const uvs = [];
|
|
52
|
+
const pushVertex = (index, uv) => {
|
|
53
|
+
positions.push(...corners[index]);
|
|
54
|
+
uvs.push(...uv);
|
|
55
|
+
};
|
|
56
|
+
const pushQuad = (a, b, c, d, uvA, uvB, uvC, uvD) => {
|
|
57
|
+
pushVertex(a, uvA);
|
|
58
|
+
pushVertex(b, uvB);
|
|
59
|
+
pushVertex(c, uvC);
|
|
60
|
+
pushVertex(a, uvA);
|
|
61
|
+
pushVertex(c, uvC);
|
|
62
|
+
pushVertex(d, uvD);
|
|
63
|
+
};
|
|
64
|
+
const topOuterV = topY;
|
|
65
|
+
const topInnerV = topY + depth;
|
|
66
|
+
const innerTopV = topInnerV;
|
|
67
|
+
const innerBottomV = topInnerV + height;
|
|
68
|
+
const bottomInnerV = bottomY - depth;
|
|
69
|
+
pushQuad(0, 4, 6, 2, [startDistance, bottomY], [endDistance, bottomY], [endDistance, topY], [startDistance, topY]);
|
|
70
|
+
pushQuad(1, 3, 7, 5, [startDistance, innerBottomV], [startDistance, innerTopV], [endDistance, innerTopV], [endDistance, innerBottomV]);
|
|
71
|
+
pushQuad(2, 6, 7, 3, [startDistance, topOuterV], [endDistance, topOuterV], [endDistance, topInnerV], [startDistance, topInnerV]);
|
|
72
|
+
pushQuad(0, 1, 5, 4, [startDistance, bottomY], [startDistance, bottomInnerV], [endDistance, bottomInnerV], [endDistance, bottomY]);
|
|
73
|
+
pushQuad(0, 2, 3, 1, [0, bottomY], [0, topY], [depth, innerTopV], [depth, innerBottomV]);
|
|
74
|
+
pushQuad(4, 5, 7, 6, [0, bottomY], [depth, innerBottomV], [depth, innerTopV], [0, topY]);
|
|
75
|
+
const geometry = new THREE.BufferGeometry();
|
|
76
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(positions), 3));
|
|
77
|
+
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(new Float32Array(uvs), 2));
|
|
78
|
+
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(new Float32Array(uvs), 2));
|
|
79
|
+
geometry.computeVertexNormals();
|
|
30
80
|
return {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
scale: [
|
|
81
|
+
geometry,
|
|
82
|
+
position: [0, 0, 0],
|
|
83
|
+
scale: [1, 1, 1],
|
|
34
84
|
};
|
|
35
85
|
}
|
|
36
|
-
function
|
|
86
|
+
function createFenceCurveBlockParts(fence, startT, endT, centerY, height, depth, maxSegmentLength = MIN_CURVE_SEGMENT_LENGTH) {
|
|
87
|
+
const length = getFenceCenterlineLength(fence) * Math.max(1e-4, endT - startT);
|
|
88
|
+
const segmentCount = Math.max(1, Math.ceil(length / Math.max(1e-4, maxSegmentLength)));
|
|
37
89
|
const parts = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (segment) {
|
|
45
|
-
parts.push(segment);
|
|
46
|
-
}
|
|
47
|
-
previous = current;
|
|
90
|
+
for (let index = 0; index < segmentCount; index += 1) {
|
|
91
|
+
const segmentStartT = startT + (endT - startT) * (index / segmentCount);
|
|
92
|
+
const segmentEndT = startT + (endT - startT) * ((index + 1) / segmentCount);
|
|
93
|
+
const part = createFenceCurveBlockPart(fence, segmentStartT, segmentEndT, centerY, height, depth);
|
|
94
|
+
if (part)
|
|
95
|
+
parts.push(part);
|
|
48
96
|
}
|
|
49
97
|
return parts;
|
|
50
98
|
}
|
|
@@ -53,15 +101,13 @@ function applyFenceUVs(geometry) {
|
|
|
53
101
|
const normal = geometry.getAttribute('normal');
|
|
54
102
|
if (!(position && normal))
|
|
55
103
|
return;
|
|
104
|
+
// World-scale triplanar UVs: 1 UV unit = 1 metre, sampled from the part's
|
|
105
|
+
// local-space (already translated into fence space) coordinates with NO
|
|
106
|
+
// per-part origin shift. A shared origin keeps a tiled finish continuous
|
|
107
|
+
// across posts, rails, and infill instead of restarting the tile at each
|
|
108
|
+
// part's own min corner (the previous behaviour, which broke the 1 m
|
|
109
|
+
// contract and made adjacent parts mistile).
|
|
56
110
|
const uvs = new Float32Array(position.count * 2);
|
|
57
|
-
let minX = Number.POSITIVE_INFINITY;
|
|
58
|
-
let minY = Number.POSITIVE_INFINITY;
|
|
59
|
-
let minZ = Number.POSITIVE_INFINITY;
|
|
60
|
-
for (let index = 0; index < position.count; index += 1) {
|
|
61
|
-
minX = Math.min(minX, position.getX(index));
|
|
62
|
-
minY = Math.min(minY, position.getY(index));
|
|
63
|
-
minZ = Math.min(minZ, position.getZ(index));
|
|
64
|
-
}
|
|
65
111
|
for (let index = 0; index < position.count; index += 1) {
|
|
66
112
|
const px = position.getX(index);
|
|
67
113
|
const py = position.getY(index);
|
|
@@ -72,16 +118,16 @@ function applyFenceUVs(geometry) {
|
|
|
72
118
|
let u = 0;
|
|
73
119
|
let v = 0;
|
|
74
120
|
if (ny >= nx && ny >= nz) {
|
|
75
|
-
u = px
|
|
76
|
-
v = pz
|
|
121
|
+
u = px;
|
|
122
|
+
v = pz;
|
|
77
123
|
}
|
|
78
124
|
else if (nx >= nz) {
|
|
79
|
-
u = pz
|
|
80
|
-
v = py
|
|
125
|
+
u = pz;
|
|
126
|
+
v = py;
|
|
81
127
|
}
|
|
82
128
|
else {
|
|
83
|
-
u = px
|
|
84
|
-
v = py
|
|
129
|
+
u = px;
|
|
130
|
+
v = py;
|
|
85
131
|
}
|
|
86
132
|
uvs[index * 2] = u;
|
|
87
133
|
uvs[index * 2 + 1] = v;
|
|
@@ -98,9 +144,104 @@ function getStyleDefaults(style) {
|
|
|
98
144
|
}
|
|
99
145
|
return { spacingFactor: 0.3, postFactor: 0.55, baseFactor: 1, topFactor: 0.75 };
|
|
100
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Horizontal-board fence — composite cladding boards stacked between square
|
|
149
|
+
* intermediate posts (each capped), instead of the vertical pickets the other
|
|
150
|
+
* styles draw. Posts march along the whole span at `postSpacing` (not just the
|
|
151
|
+
* two ends), the boards run full-length so they curve with the fence, and a
|
|
152
|
+
* thin reveal between boards leaves the groove shadow that reads as cladding.
|
|
153
|
+
*/
|
|
154
|
+
function createHorizontalFenceParts(fence) {
|
|
155
|
+
const posts = [];
|
|
156
|
+
const infill = [];
|
|
157
|
+
const base = [];
|
|
158
|
+
const rail = [];
|
|
159
|
+
const length = Math.max(getFenceCenterlineLength(fence), 0.01);
|
|
160
|
+
const panelDepth = Math.max(fence.thickness, 0.03);
|
|
161
|
+
const clearance = Math.max(fence.groundClearance, 0);
|
|
162
|
+
const isFloating = fence.baseStyle === 'floating';
|
|
163
|
+
const showInfill = fence.showInfill ?? true;
|
|
164
|
+
const baseHeight = Math.max(fence.baseHeight, 0.04);
|
|
165
|
+
const topRailHeight = Math.max(fence.topRailHeight, 0.01);
|
|
166
|
+
const verticalHeight = Math.max(fence.height - baseHeight - topRailHeight, 0.08);
|
|
167
|
+
const baseY = isFloating ? clearance : 0;
|
|
168
|
+
// Square posts stand proud of the recessed boards on both faces.
|
|
169
|
+
const postWidth = Math.max(fence.postSize * 1.4, 0.04);
|
|
170
|
+
const postDepth = postWidth;
|
|
171
|
+
const boardDepth = Math.min(panelDepth, postDepth - 0.012);
|
|
172
|
+
// Stop the horizontal boards / base / rail at the inner faces of the
|
|
173
|
+
// end posts. Letting curved spans run all the way to t=0/1 makes them
|
|
174
|
+
// overlap the terminal post mesh and creates the broken seam/notch seen
|
|
175
|
+
// at curve ends.
|
|
176
|
+
const edgeInset = Math.max(fence.edgeInset ?? 0.015, postWidth * 0.5);
|
|
177
|
+
const startInsetT = Math.min(0.499, edgeInset / length);
|
|
178
|
+
const endInsetT = Math.max(0.501, 1 - edgeInset / length);
|
|
179
|
+
// Grounded fences get a kickboard along the bottom; floating ones don't.
|
|
180
|
+
if (!isFloating) {
|
|
181
|
+
base.push(...createFenceCurveBlockParts(fence, startInsetT, endInsetT, baseY + baseHeight / 2, baseHeight, postDepth * 0.92, HORIZONTAL_FENCE_CURVE_SEGMENT_LENGTH));
|
|
182
|
+
}
|
|
183
|
+
// Stack full-length boards between the kickboard and the top rail. The board
|
|
184
|
+
// height is derived to evenly fill the panel around a ~0.145 m target, with a
|
|
185
|
+
// constant reveal between each so the count adapts to any fence height.
|
|
186
|
+
if (showInfill) {
|
|
187
|
+
const reveal = Math.max(fence.slatGap ?? 0.01, 0);
|
|
188
|
+
const infillBottom = baseY + baseHeight;
|
|
189
|
+
if (reveal < 0.002) {
|
|
190
|
+
// No reveal → one flush panel, so the stacked-board edge seams don't
|
|
191
|
+
// read as faint lines where the user asked for a smooth surface.
|
|
192
|
+
infill.push(...createFenceCurveBlockParts(fence, startInsetT, endInsetT, infillBottom + verticalHeight / 2, verticalHeight, boardDepth, HORIZONTAL_FENCE_CURVE_SEGMENT_LENGTH));
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const boardCount = Math.max(1, Math.round(verticalHeight / (0.145 + reveal)));
|
|
196
|
+
const slabHeight = Math.max((verticalHeight - reveal * (boardCount - 1)) / boardCount, 0.02);
|
|
197
|
+
for (let index = 0; index < boardCount; index += 1) {
|
|
198
|
+
const centerY = infillBottom + slabHeight / 2 + index * (slabHeight + reveal);
|
|
199
|
+
infill.push(...createFenceCurveBlockParts(fence, startInsetT, endInsetT, centerY, slabHeight, boardDepth, HORIZONTAL_FENCE_CURVE_SEGMENT_LENGTH));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Top rail caps the boards.
|
|
204
|
+
rail.push(...createFenceCurveBlockParts(fence, startInsetT, endInsetT, baseY + baseHeight + verticalHeight + topRailHeight / 2, topRailHeight, Math.max(postDepth * 0.78, 0.02), HORIZONTAL_FENCE_CURVE_SEGMENT_LENGTH));
|
|
205
|
+
// Posts at every `postSpacing`, anchored at both ends, each with a flat cap.
|
|
206
|
+
const spacing = Math.max(fence.postSpacing, postWidth * 1.4);
|
|
207
|
+
const postCount = Math.max(2, Math.floor(length / spacing) + 1);
|
|
208
|
+
const postHeight = baseHeight + verticalHeight + topRailHeight + clearance;
|
|
209
|
+
const capHeight = Math.max(postWidth * 0.32, 0.03);
|
|
210
|
+
const cap = fence.postCap ?? 'pyramid';
|
|
211
|
+
for (let index = 0; index < postCount; index += 1) {
|
|
212
|
+
const t = postCount === 1 ? 0.5 : index / (postCount - 1);
|
|
213
|
+
const frame = getFencePointAt(fence, t);
|
|
214
|
+
posts.push({
|
|
215
|
+
position: [frame.point.x, postHeight / 2, frame.point.y],
|
|
216
|
+
rotationY: -frame.tangentAngle,
|
|
217
|
+
scale: [postWidth, postHeight, postDepth],
|
|
218
|
+
});
|
|
219
|
+
if (cap === 'flat') {
|
|
220
|
+
posts.push({
|
|
221
|
+
position: [frame.point.x, postHeight + capHeight / 2, frame.point.y],
|
|
222
|
+
rotationY: -frame.tangentAngle,
|
|
223
|
+
scale: [postWidth * 1.22, capHeight, postDepth * 1.22],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else if (cap === 'pyramid') {
|
|
227
|
+
posts.push({
|
|
228
|
+
position: [frame.point.x, postHeight + capHeight * 0.9, frame.point.y],
|
|
229
|
+
rotationY: -frame.tangentAngle,
|
|
230
|
+
scale: [postWidth * 1.18, capHeight * 1.8, postDepth * 1.18],
|
|
231
|
+
shape: 'pyramid',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { posts, infill, base, rail };
|
|
236
|
+
}
|
|
101
237
|
function createFenceParts(fence) {
|
|
102
|
-
|
|
103
|
-
|
|
238
|
+
if (fence.style === 'horizontal')
|
|
239
|
+
return createHorizontalFenceParts(fence);
|
|
240
|
+
const posts = [];
|
|
241
|
+
const infill = [];
|
|
242
|
+
const base = [];
|
|
243
|
+
const rail = [];
|
|
244
|
+
const length = Math.max(getFenceCenterlineLength(fence), 0.01);
|
|
104
245
|
const panelDepth = Math.max(fence.thickness, 0.03);
|
|
105
246
|
const clearance = Math.max(fence.groundClearance, 0);
|
|
106
247
|
const styleDefaults = getStyleDefaults(fence.style);
|
|
@@ -111,41 +252,53 @@ function createFenceParts(fence) {
|
|
|
111
252
|
const spacing = Math.max(fence.postSpacing * styleDefaults.spacingFactor, postWidth * 1.2);
|
|
112
253
|
const edgeInset = Math.max(fence.edgeInset ?? 0.015, 0.005);
|
|
113
254
|
const isFloating = fence.baseStyle === 'floating';
|
|
255
|
+
const showInfill = fence.showInfill ?? true;
|
|
114
256
|
const baseY = isFloating ? clearance : 0;
|
|
115
257
|
const effectiveBaseHeight = baseHeight;
|
|
116
258
|
const startInsetT = Math.min(0.499, edgeInset / length);
|
|
117
259
|
const endInsetT = Math.max(0.501, 1 - edgeInset / length);
|
|
118
260
|
if (!isFloating) {
|
|
119
|
-
|
|
120
|
-
|
|
261
|
+
base.push(...createFenceCurveBlockParts(fence, 0, 1, baseY + effectiveBaseHeight / 2, effectiveBaseHeight, panelDepth * 1.05));
|
|
262
|
+
base.push(...createFenceCurveBlockParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight * 0.15, topRailHeight * 0.8, panelDepth * 0.35));
|
|
121
263
|
}
|
|
122
|
-
const count = Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1);
|
|
264
|
+
const count = showInfill ? Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1) : 2;
|
|
123
265
|
const verticalY = baseY + effectiveBaseHeight + verticalHeight / 2;
|
|
124
266
|
for (let index = 0; index < count; index += 1) {
|
|
125
267
|
const t = count === 1 ? 0.5 : startInsetT + (endInsetT - startInsetT) * (index / (count - 1));
|
|
126
|
-
const frame = getFencePointAt(fence, t);
|
|
127
268
|
const isEdgePost = index === 0 || index === count - 1;
|
|
128
|
-
const
|
|
269
|
+
const fullHeightPost = !showInfill || (isFloating && isEdgePost);
|
|
270
|
+
const postHeight = fullHeightPost
|
|
129
271
|
? effectiveBaseHeight + verticalHeight + topRailHeight + clearance
|
|
130
272
|
: verticalHeight;
|
|
131
|
-
const postY =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
273
|
+
const postY = fullHeightPost ? postHeight / 2 : verticalY;
|
|
274
|
+
// End posts are the structural `posts` slot; the intermediate verticals are
|
|
275
|
+
// the `infill` slats (only present when showInfill adds them).
|
|
276
|
+
const slatHalfT = Math.max(0.0005, postWidth / (2 * length));
|
|
277
|
+
const slatStartT = Math.max(0, t - slatHalfT);
|
|
278
|
+
const slatEndT = Math.min(1, t + slatHalfT);
|
|
279
|
+
const slat = createFenceCurveBlockPart(fence, slatStartT, slatEndT, postY, postHeight, Math.max(panelDepth * 0.35 - 0.001, 0.011));
|
|
280
|
+
if (slat) {
|
|
281
|
+
;
|
|
282
|
+
(isEdgePost ? posts : infill).push(slat);
|
|
283
|
+
}
|
|
137
284
|
}
|
|
138
|
-
|
|
285
|
+
rail.push(...createFenceCurveBlockParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
|
|
139
286
|
if (isFloating) {
|
|
140
|
-
|
|
287
|
+
rail.push(...createFenceCurveBlockParts(fence, 0, 1, baseY + effectiveBaseHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
|
|
141
288
|
}
|
|
142
|
-
return
|
|
289
|
+
return { posts, infill, base, rail };
|
|
143
290
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
291
|
+
function mergeFenceParts(parts) {
|
|
292
|
+
// An empty slot group (e.g. infill with showInfill off, or base on a floating
|
|
293
|
+
// fence) must not reach mergeGeometries — it throws on an empty array. The
|
|
294
|
+
// empty geometry has no position attribute, so the renderer skips its mesh.
|
|
295
|
+
if (parts.length === 0)
|
|
296
|
+
return new THREE.BufferGeometry();
|
|
146
297
|
const geometries = parts.map(createFencePartGeometry);
|
|
147
298
|
const merged = mergeGeometries(geometries, false) ?? new THREE.BufferGeometry();
|
|
148
|
-
geometries.forEach((geometry) =>
|
|
299
|
+
geometries.forEach((geometry) => {
|
|
300
|
+
geometry.dispose();
|
|
301
|
+
});
|
|
149
302
|
const mergedUv = merged.getAttribute('uv');
|
|
150
303
|
if (mergedUv) {
|
|
151
304
|
merged.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(mergedUv.array), 2));
|
|
@@ -153,9 +306,28 @@ function generateFenceGeometry(fence) {
|
|
|
153
306
|
merged.computeVertexNormals();
|
|
154
307
|
return merged;
|
|
155
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Geometry split by paint slot — posts, infill, base, rail — each a separate
|
|
311
|
+
* merged BufferGeometry (empty ones included) so the fence renderer can give
|
|
312
|
+
* each its own material + `userData.slotId`. Slots match the panel's build
|
|
313
|
+
* options 1:1.
|
|
314
|
+
*/
|
|
315
|
+
export function generateFenceSlotGeometries(fence) {
|
|
316
|
+
const parts = createFenceParts(fence);
|
|
317
|
+
return {
|
|
318
|
+
posts: mergeFenceParts(parts.posts),
|
|
319
|
+
infill: mergeFenceParts(parts.infill),
|
|
320
|
+
base: mergeFenceParts(parts.base),
|
|
321
|
+
rail: mergeFenceParts(parts.rail),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
export function generateFenceGeometry(fence) {
|
|
325
|
+
const { posts, infill, base, rail } = createFenceParts(fence);
|
|
326
|
+
return mergeFenceParts([...posts, ...infill, ...base, ...rail]);
|
|
327
|
+
}
|
|
156
328
|
function updateFenceGeometry(fenceId) {
|
|
157
329
|
const node = useScene.getState().nodes[fenceId];
|
|
158
|
-
if (
|
|
330
|
+
if (node?.type !== 'fence')
|
|
159
331
|
return;
|
|
160
332
|
const mesh = sceneRegistry.nodes.get(fenceId);
|
|
161
333
|
if (!mesh)
|
|
@@ -175,7 +347,7 @@ export const FenceSystem = () => {
|
|
|
175
347
|
const nodes = useScene.getState().nodes;
|
|
176
348
|
dirtyNodes.forEach((id) => {
|
|
177
349
|
const node = nodes[id];
|
|
178
|
-
if (
|
|
350
|
+
if (node?.type !== 'fence')
|
|
179
351
|
return;
|
|
180
352
|
updateFenceGeometry(id);
|
|
181
353
|
clearDirty(id);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic floor-elevation system.
|
|
3
|
+
*
|
|
4
|
+
* Walks `dirtyNodes` and, for any kind that declares
|
|
5
|
+
* `capabilities.floorPlaced`, lifts the registered mesh's Y by whatever
|
|
6
|
+
* slab the footprint overlaps. Items / shelves / etc. that sit directly
|
|
7
|
+
* on a level pick this up automatically — no per-kind elevation logic.
|
|
8
|
+
*
|
|
9
|
+
* Skips nodes whose parent is not a level (items hosted on shelves /
|
|
10
|
+
* tables inherit Y from the parent group), and respects
|
|
11
|
+
* `floorPlaced.applies` so items with `asset.attachTo` (wall / ceiling
|
|
12
|
+
* mounted) are left alone.
|
|
13
|
+
*
|
|
14
|
+
* Runs at priority 1 — before the priority-2 systems (`GeometrySystem`,
|
|
15
|
+
* `ItemSystem`) so the dirty mark survives long enough for those to do
|
|
16
|
+
* their own work. Kinds with no geometry/system have no downstream dirty
|
|
17
|
+
* consumer, so this system clears their dirty mark after applying the lift.
|
|
18
|
+
*/
|
|
19
|
+
export declare const FloorElevationSystem: () => null;
|
|
20
|
+
//# sourceMappingURL=floor-elevation-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floor-elevation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/floor-elevation/floor-elevation-system.tsx"],"names":[],"mappings":"AAwCA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,YA6ChC,CAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getEffectiveNode, getFloorStackedPosition, nodeRegistry, sceneRegistry, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
2
|
+
import { useFrame } from '@react-three/fiber';
|
|
3
|
+
function withLiveTransform(node, id) {
|
|
4
|
+
const liveTransform = useLiveTransforms.getState().get(id);
|
|
5
|
+
if (!liveTransform)
|
|
6
|
+
return node;
|
|
7
|
+
const currentRotation = node.rotation;
|
|
8
|
+
const rotation = Array.isArray(currentRotation)
|
|
9
|
+
? [currentRotation[0] ?? 0, liveTransform.rotation, currentRotation[2] ?? 0]
|
|
10
|
+
: typeof currentRotation === 'number'
|
|
11
|
+
? liveTransform.rotation
|
|
12
|
+
: currentRotation;
|
|
13
|
+
return {
|
|
14
|
+
...node,
|
|
15
|
+
position: liveTransform.position,
|
|
16
|
+
...(rotation !== undefined ? { rotation } : {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generic floor-elevation system.
|
|
21
|
+
*
|
|
22
|
+
* Walks `dirtyNodes` and, for any kind that declares
|
|
23
|
+
* `capabilities.floorPlaced`, lifts the registered mesh's Y by whatever
|
|
24
|
+
* slab the footprint overlaps. Items / shelves / etc. that sit directly
|
|
25
|
+
* on a level pick this up automatically — no per-kind elevation logic.
|
|
26
|
+
*
|
|
27
|
+
* Skips nodes whose parent is not a level (items hosted on shelves /
|
|
28
|
+
* tables inherit Y from the parent group), and respects
|
|
29
|
+
* `floorPlaced.applies` so items with `asset.attachTo` (wall / ceiling
|
|
30
|
+
* mounted) are left alone.
|
|
31
|
+
*
|
|
32
|
+
* Runs at priority 1 — before the priority-2 systems (`GeometrySystem`,
|
|
33
|
+
* `ItemSystem`) so the dirty mark survives long enough for those to do
|
|
34
|
+
* their own work. Kinds with no geometry/system have no downstream dirty
|
|
35
|
+
* consumer, so this system clears their dirty mark after applying the lift.
|
|
36
|
+
*/
|
|
37
|
+
export const FloorElevationSystem = () => {
|
|
38
|
+
const dirtyNodes = useScene((s) => s.dirtyNodes);
|
|
39
|
+
const clearDirty = useScene((s) => s.clearDirty);
|
|
40
|
+
useFrame(() => {
|
|
41
|
+
if (dirtyNodes.size === 0)
|
|
42
|
+
return;
|
|
43
|
+
const nodes = useScene.getState().nodes;
|
|
44
|
+
dirtyNodes.forEach((id) => {
|
|
45
|
+
const node = nodes[id];
|
|
46
|
+
if (!node)
|
|
47
|
+
return;
|
|
48
|
+
const def = nodeRegistry.get(node.type);
|
|
49
|
+
const floorPlaced = def?.capabilities?.floorPlaced;
|
|
50
|
+
if (!floorPlaced)
|
|
51
|
+
return;
|
|
52
|
+
const mesh = sceneRegistry.nodes.get(id);
|
|
53
|
+
if (!mesh)
|
|
54
|
+
return;
|
|
55
|
+
const effectiveNode = withLiveTransform(getEffectiveNode(node), id);
|
|
56
|
+
const position = effectiveNode.position;
|
|
57
|
+
if (!position)
|
|
58
|
+
return;
|
|
59
|
+
// This system is the single drag-time authority for floor-stack mesh Y:
|
|
60
|
+
// tools publish base positions to live stores, renderers may
|
|
61
|
+
// reconcile that base Y onto the group, then this presentation system
|
|
62
|
+
// reapplies the resolver-derived visual Y before render. Because the
|
|
63
|
+
// override/store position remains base-height, the slab lift is never
|
|
64
|
+
// committed or applied twice.
|
|
65
|
+
const resolverNodes = effectiveNode === node ? nodes : { ...nodes, [effectiveNode.id]: effectiveNode };
|
|
66
|
+
const visualPosition = getFloorStackedPosition({
|
|
67
|
+
node: effectiveNode,
|
|
68
|
+
nodes: resolverNodes,
|
|
69
|
+
position,
|
|
70
|
+
});
|
|
71
|
+
mesh.position.y = visualPosition[1];
|
|
72
|
+
if (!(def.geometry || def.system)) {
|
|
73
|
+
clearDirty(id);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}, 1);
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Group, type Object3D } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Generic geometry system.
|
|
4
|
+
*
|
|
5
|
+
* For every node in `dirtyNodes` whose definition exposes `def.geometry`,
|
|
6
|
+
* this system:
|
|
7
|
+
* 1. Looks up the registered `Group` from `sceneRegistry` (mounted by the
|
|
8
|
+
* framework's `<ParametricNodeRenderer>`, or a custom renderer that
|
|
9
|
+
* opts into the same mount contract).
|
|
10
|
+
* 2. Builds a `GeometryContext` from the current scene snapshot.
|
|
11
|
+
* 3. Calls `def.geometry(node, ctx)` to get the new `Object3D`.
|
|
12
|
+
* 4. Disposes the registered group's existing children + their geometries
|
|
13
|
+
* and materials.
|
|
14
|
+
* 5. Reparents the returned object's children onto the registered group.
|
|
15
|
+
* 6. Clears the dirty flag.
|
|
16
|
+
*
|
|
17
|
+
* This is the "no per-kind system needed" path documented in
|
|
18
|
+
* `wiki/architecture/node-definitions.md`. A kind that only rebuilds on
|
|
19
|
+
* dirty (shelf, item, fence segment, etc.) ships nothing more than a pure
|
|
20
|
+
* `geometry` function — no `renderer.tsx`, no `system.tsx`.
|
|
21
|
+
*
|
|
22
|
+
* Kinds with `def.system` declared run their own systems *in addition* to
|
|
23
|
+
* this one — animation + cascade + named-mesh material poking stay
|
|
24
|
+
* kind-specific.
|
|
25
|
+
*
|
|
26
|
+
* Frame priority 2 mirrors the per-kind shelf system it replaces. Door
|
|
27
|
+
* animation systems run at priority 2 today too, marking dirty so the
|
|
28
|
+
* geometry rebuild lands at priority 3-4 next frame. Door/window/wall
|
|
29
|
+
* still have their own systems (they need cross-cutting work this system
|
|
30
|
+
* doesn't cover) — they coexist; this system only acts on kinds that
|
|
31
|
+
* declare `def.geometry`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const GeometrySystem: () => null;
|
|
34
|
+
export default GeometrySystem;
|
|
35
|
+
export type GeometryBuildCacheEntry = {
|
|
36
|
+
group: Group;
|
|
37
|
+
key: string;
|
|
38
|
+
};
|
|
39
|
+
export declare function markGeometryBuildOutput(child: Object3D): void;
|
|
40
|
+
export declare function shouldReuseGeometryBuild(cache: Map<string, GeometryBuildCacheEntry>, id: string, group: Group, key: string): boolean;
|
|
41
|
+
//# sourceMappingURL=geometry-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-system.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAa,KAAK,KAAK,EAA4B,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAQtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,cAAc,YA8L1B,CAAA;AAmJD,eAAe,cAAc,CAAA;AAE7B,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAU7D;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAC3C,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAKT"}
|