@pascal-app/viewer 1.0.7 → 1.1.1
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/README.md +209 -19
- package/dist/components/viewer/drone-controller.d.ts.map +1 -1
- package/dist/components/viewer/drone-controller.js +1 -5
- package/dist/components/viewer/first-person/build-collider-world.d.ts +14 -0
- package/dist/components/viewer/first-person/build-collider-world.d.ts.map +1 -0
- package/dist/components/viewer/first-person/build-collider-world.js +426 -0
- package/dist/components/viewer/first-person/ground-holes.d.ts +22 -0
- package/dist/components/viewer/first-person/ground-holes.d.ts.map +1 -0
- package/dist/components/viewer/first-person/ground-holes.js +116 -0
- package/dist/components/viewer/first-person/mechanism-targets.d.ts +27 -0
- package/dist/components/viewer/first-person/mechanism-targets.d.ts.map +1 -0
- package/dist/components/viewer/first-person/mechanism-targets.js +79 -0
- package/dist/components/viewer/first-person/terrain-collider.d.ts +32 -0
- package/dist/components/viewer/first-person/terrain-collider.d.ts.map +1 -0
- package/dist/components/viewer/first-person/terrain-collider.js +175 -0
- package/dist/components/viewer/glb-replace-instances.d.ts +6 -3
- package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -1
- package/dist/components/viewer/glb-replace-instances.js +5 -4
- package/dist/components/viewer/glb-scene.d.ts +3 -1
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +2 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +3 -4
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +8 -4
- package/dist/components/viewer/live-walkthrough-controller.d.ts +17 -0
- package/dist/components/viewer/live-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/live-walkthrough-controller.js +1449 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +22 -1
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +2 -2
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -1
- package/dist/components/viewer/unsupported-gpu-fallback.js +21 -1
- package/dist/components/viewer/use-daylight-rooms.d.ts.map +1 -1
- package/dist/components/viewer/use-daylight-rooms.js +75 -33
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/lib/asset-url.d.ts +0 -1
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +3 -3
- package/dist/lib/daylight/room-context.d.ts.map +1 -1
- package/dist/lib/daylight/room-context.js +3 -2
- package/dist/lib/daylight/room-map.d.ts +149 -0
- package/dist/lib/daylight/room-map.d.ts.map +1 -0
- package/dist/lib/daylight/room-map.js +813 -0
- package/dist/lib/daylight/room-scene.d.ts +4 -2
- package/dist/lib/daylight/room-scene.d.ts.map +1 -1
- package/dist/lib/daylight/room-scene.js +81 -40
- package/dist/lib/daylight/rooms.d.ts +71 -32
- package/dist/lib/daylight/rooms.d.ts.map +1 -1
- package/dist/lib/daylight/rooms.js +371 -286
- package/dist/lib/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +2 -1
- package/dist/lib/measurement-label.d.ts +4 -0
- package/dist/lib/measurement-label.d.ts.map +1 -0
- package/dist/lib/measurement-label.js +54 -0
- package/dist/lib/measurement-presentation.d.ts +7 -0
- package/dist/lib/measurement-presentation.d.ts.map +1 -0
- package/dist/lib/measurement-presentation.js +14 -0
- package/dist/lib/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +3 -3
- package/dist/lib/texture-reference.d.ts.map +1 -1
- package/dist/lib/texture-reference.js +4 -3
- package/dist/lib/typing-target.d.ts +2 -0
- package/dist/lib/typing-target.d.ts.map +1 -0
- package/dist/lib/typing-target.js +7 -0
- package/dist/lib/viewer-config.d.ts +27 -0
- package/dist/lib/viewer-config.d.ts.map +1 -0
- package/dist/lib/viewer-config.js +23 -0
- package/dist/lib/viewer-preference-storage.d.ts +7 -0
- package/dist/lib/viewer-preference-storage.d.ts.map +1 -0
- package/dist/lib/viewer-preference-storage.js +23 -0
- package/dist/sfx/audio-settings.d.ts +81 -0
- package/dist/sfx/audio-settings.d.ts.map +1 -0
- package/dist/sfx/audio-settings.js +166 -0
- package/dist/sfx/index.d.ts +6 -0
- package/dist/sfx/index.d.ts.map +1 -0
- package/dist/sfx/index.js +5 -0
- package/dist/sfx/interaction-sounds.d.ts +7 -0
- package/dist/sfx/interaction-sounds.d.ts.map +1 -0
- package/dist/sfx/interaction-sounds.js +39 -0
- package/dist/sfx/player.d.ts +31 -0
- package/dist/sfx/player.d.ts.map +1 -0
- package/dist/sfx/player.js +255 -0
- package/dist/sfx/sounds.d.ts +60 -0
- package/dist/sfx/sounds.d.ts.map +1 -0
- package/dist/sfx/sounds.js +25 -0
- package/dist/sfx/use-viewer-ui-sounds.d.ts +8 -0
- package/dist/sfx/use-viewer-ui-sounds.d.ts.map +1 -0
- package/dist/sfx/use-viewer-ui-sounds.js +42 -0
- package/dist/store/use-first-person-hud.d.ts +13 -0
- package/dist/store/use-first-person-hud.d.ts.map +1 -0
- package/dist/store/use-first-person-hud.js +21 -0
- package/dist/store/use-viewer.d.ts +2 -56
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +3 -1
- package/dist/systems/elevator/elevator-interaction-system.d.ts +16 -2
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-interaction-system.js +41 -8
- package/dist/systems/geometry/geometry-status.d.ts +8 -0
- package/dist/systems/geometry/geometry-status.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-status.js +24 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +134 -78
- package/dist/systems/wall/wall-system.d.ts +11 -0
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +56 -10
- package/dist/ui/components/action-button.d.ts +14 -0
- package/dist/ui/components/action-button.d.ts.map +1 -0
- package/dist/ui/components/action-button.js +21 -0
- package/dist/ui/components/action-menu-placement.d.ts +7 -0
- package/dist/ui/components/action-menu-placement.d.ts.map +1 -0
- package/dist/ui/components/action-menu-placement.js +10 -0
- package/dist/ui/components/floorplan-compass-button.d.ts +10 -0
- package/dist/ui/components/floorplan-compass-button.d.ts.map +1 -0
- package/dist/ui/components/floorplan-compass-button.js +22 -0
- package/dist/ui/components/follow-pascal-toggle.d.ts +6 -0
- package/dist/ui/components/follow-pascal-toggle.d.ts.map +1 -0
- package/dist/ui/components/follow-pascal-toggle.js +21 -0
- package/dist/ui/components/localized-html.d.ts +9 -0
- package/dist/ui/components/localized-html.d.ts.map +1 -0
- package/dist/ui/components/localized-html.js +16 -0
- package/dist/ui/components/render-settings-menu.d.ts +9 -0
- package/dist/ui/components/render-settings-menu.d.ts.map +1 -0
- package/dist/ui/components/render-settings-menu.js +145 -0
- package/dist/ui/components/rooms/room-highlight.d.ts +10 -0
- package/dist/ui/components/rooms/room-highlight.d.ts.map +1 -0
- package/dist/ui/components/rooms/room-highlight.js +56 -0
- package/dist/ui/components/rooms/room-labels.d.ts +278 -0
- package/dist/ui/components/rooms/room-labels.d.ts.map +1 -0
- package/dist/ui/components/rooms/room-labels.js +1295 -0
- package/dist/ui/components/rooms/viewer-rooms.d.ts +14 -0
- package/dist/ui/components/rooms/viewer-rooms.d.ts.map +1 -0
- package/dist/ui/components/rooms/viewer-rooms.js +123 -0
- package/dist/ui/components/scene-loader.d.ts +18 -0
- package/dist/ui/components/scene-loader.d.ts.map +1 -0
- package/dist/ui/components/scene-loader.js +29 -0
- package/dist/ui/components/sky-control.d.ts +37 -0
- package/dist/ui/components/sky-control.d.ts.map +1 -0
- package/dist/ui/components/sky-control.js +382 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts +4 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts.map +1 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.js +117 -0
- package/dist/ui/components/viewer-controls-bar.d.ts +20 -0
- package/dist/ui/components/viewer-controls-bar.d.ts.map +1 -0
- package/dist/ui/components/viewer-controls-bar.js +130 -0
- package/dist/ui/components/viewer-hover-label.d.ts +14 -0
- package/dist/ui/components/viewer-hover-label.d.ts.map +1 -0
- package/dist/ui/components/viewer-hover-label.js +28 -0
- package/dist/ui/components/viewer-scene-header.d.ts +34 -0
- package/dist/ui/components/viewer-scene-header.d.ts.map +1 -0
- package/dist/ui/components/viewer-scene-header.js +92 -0
- package/dist/ui/components/viewer-selection-manager.d.ts +7 -0
- package/dist/ui/components/viewer-selection-manager.d.ts.map +1 -0
- package/dist/ui/components/viewer-selection-manager.js +116 -0
- package/dist/ui/components/viewer-stage-modes.d.ts +7 -0
- package/dist/ui/components/viewer-stage-modes.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage-modes.js +42 -0
- package/dist/ui/components/viewer-stage-switcher.d.ts +10 -0
- package/dist/ui/components/viewer-stage-switcher.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage-switcher.js +16 -0
- package/dist/ui/components/viewer-stage.d.ts +25 -0
- package/dist/ui/components/viewer-stage.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage.js +107 -0
- package/dist/ui/components/walkthrough-hud.d.ts +29 -0
- package/dist/ui/components/walkthrough-hud.d.ts.map +1 -0
- package/dist/ui/components/walkthrough-hud.js +55 -0
- package/dist/ui/floorplan/floorplan-camera-sync.d.ts +22 -0
- package/dist/ui/floorplan/floorplan-camera-sync.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-camera-sync.js +157 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts +61 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.js +280 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts +54 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.js +317 -0
- package/dist/ui/floorplan/floorplan-label-angle.d.ts +24 -0
- package/dist/ui/floorplan/floorplan-label-angle.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-label-angle.js +60 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.d.ts +18 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.js +41 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.d.ts +26 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.js +173 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.d.ts +22 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.js +41 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.d.ts +8 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.js +21 -0
- package/dist/ui/floorplan/floorplan-preview.d.ts +19 -0
- package/dist/ui/floorplan/floorplan-preview.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview.js +700 -0
- package/dist/ui/hooks/use-mobile.d.ts +2 -0
- package/dist/ui/hooks/use-mobile.d.ts.map +1 -0
- package/dist/ui/hooks/use-mobile.js +14 -0
- package/dist/ui/index.d.ts +49 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +48 -0
- package/dist/ui/lib/below-grade.d.ts +38 -0
- package/dist/ui/lib/below-grade.d.ts.map +1 -0
- package/dist/ui/lib/below-grade.js +102 -0
- package/dist/ui/lib/camera-pose.d.ts +19 -0
- package/dist/ui/lib/camera-pose.d.ts.map +1 -0
- package/dist/ui/lib/camera-pose.js +113 -0
- package/dist/ui/lib/cn.d.ts +3 -0
- package/dist/ui/lib/cn.d.ts.map +1 -0
- package/dist/ui/lib/cn.js +5 -0
- package/dist/ui/lib/level-name.d.ts +10 -0
- package/dist/ui/lib/level-name.d.ts.map +1 -0
- package/dist/ui/lib/level-name.js +33 -0
- package/dist/ui/lib/node-display-label.d.ts +8 -0
- package/dist/ui/lib/node-display-label.d.ts.map +1 -0
- package/dist/ui/lib/node-display-label.js +36 -0
- package/dist/ui/lib/room-area.d.ts +3 -0
- package/dist/ui/lib/room-area.d.ts.map +1 -0
- package/dist/ui/lib/room-area.js +8 -0
- package/dist/ui/lib/room-assembly-overlay.d.ts +46 -0
- package/dist/ui/lib/room-assembly-overlay.d.ts.map +1 -0
- package/dist/ui/lib/room-assembly-overlay.js +222 -0
- package/dist/ui/lib/room-name-messages.d.ts +28 -0
- package/dist/ui/lib/room-name-messages.d.ts.map +1 -0
- package/dist/ui/lib/room-name-messages.js +112 -0
- package/dist/ui/lib/room-selection.d.ts +58 -0
- package/dist/ui/lib/room-selection.d.ts.map +1 -0
- package/dist/ui/lib/room-selection.js +297 -0
- package/dist/ui/lib/scene-theme-name.d.ts +4 -0
- package/dist/ui/lib/scene-theme-name.d.ts.map +1 -0
- package/dist/ui/lib/scene-theme-name.js +22 -0
- package/dist/ui/lib/selection-modifiers.d.ts +20 -0
- package/dist/ui/lib/selection-modifiers.d.ts.map +1 -0
- package/dist/ui/lib/selection-modifiers.js +15 -0
- package/dist/ui/lib/viewer-selection.d.ts +132 -0
- package/dist/ui/lib/viewer-selection.d.ts.map +1 -0
- package/dist/ui/lib/viewer-selection.js +462 -0
- package/dist/ui/lib/zone-content.d.ts +6 -0
- package/dist/ui/lib/zone-content.d.ts.map +1 -0
- package/dist/ui/lib/zone-content.js +96 -0
- package/dist/ui/primitives/button.d.ts +11 -0
- package/dist/ui/primitives/button.d.ts.map +1 -0
- package/dist/ui/primitives/button.js +35 -0
- package/dist/ui/primitives/dropdown-menu.d.ts +26 -0
- package/dist/ui/primitives/dropdown-menu.d.ts.map +1 -0
- package/dist/ui/primitives/dropdown-menu.js +53 -0
- package/dist/ui/primitives/popover.d.ts +8 -0
- package/dist/ui/primitives/popover.d.ts.map +1 -0
- package/dist/ui/primitives/popover.js +17 -0
- package/dist/ui/primitives/switch.d.ts +5 -0
- package/dist/ui/primitives/switch.d.ts.map +1 -0
- package/dist/ui/primitives/switch.js +8 -0
- package/dist/ui/primitives/tooltip.d.ts +8 -0
- package/dist/ui/primitives/tooltip.d.ts.map +1 -0
- package/dist/ui/primitives/tooltip.js +17 -0
- package/dist/ui/store/camera-pose-store.d.ts +17 -0
- package/dist/ui/store/camera-pose-store.d.ts.map +1 -0
- package/dist/ui/store/camera-pose-store.js +15 -0
- package/dist/ui/store/navigation-sync-pose-store.d.ts +26 -0
- package/dist/ui/store/navigation-sync-pose-store.d.ts.map +1 -0
- package/dist/ui/store/navigation-sync-pose-store.js +16 -0
- package/dist/ui/store/use-follow-pascal.d.ts +30 -0
- package/dist/ui/store/use-follow-pascal.d.ts.map +1 -0
- package/dist/ui/store/use-follow-pascal.js +28 -0
- package/dist/ui/theme.css +339 -0
- package/package.json +34 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { openElevatorDoor, requestElevatorLevel, resolveElevatorDispatchTarget, useInteractive, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useThree } from '@react-three/fiber';
|
|
3
3
|
import { useEffect, useRef } from 'react';
|
|
4
|
-
import { Raycaster, Vector2 } from 'three';
|
|
4
|
+
import { Mesh, Raycaster, Vector2 } from 'three';
|
|
5
5
|
function getElevatorButtonData(object) {
|
|
6
6
|
let current = object;
|
|
7
7
|
while (current) {
|
|
@@ -18,6 +18,8 @@ function getElevatorButtonData(object) {
|
|
|
18
18
|
* whose meshes this capture-phase listener must not trip over.
|
|
19
19
|
*/
|
|
20
20
|
export function collectElevatorButtons(object, into = []) {
|
|
21
|
+
if (!object.visible)
|
|
22
|
+
return into;
|
|
21
23
|
if (object.userData.elevatorButton) {
|
|
22
24
|
into.push(object);
|
|
23
25
|
return into;
|
|
@@ -26,7 +28,41 @@ export function collectElevatorButtons(object, into = []) {
|
|
|
26
28
|
collectElevatorButtons(child, into);
|
|
27
29
|
return into;
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
/** Ignore malformed material groups during transient paint updates, rather than raycasting invalid faces. */
|
|
32
|
+
export function pickElevatorButton(scene, raycaster) {
|
|
33
|
+
const buttons = collectElevatorButtons(scene);
|
|
34
|
+
const faces = [];
|
|
35
|
+
for (const button of buttons)
|
|
36
|
+
button.traverseVisible((object) => {
|
|
37
|
+
if (object instanceof Mesh)
|
|
38
|
+
faces.push(object);
|
|
39
|
+
});
|
|
40
|
+
const hit = raycaster.intersectObjects(faces, false)[0];
|
|
41
|
+
if (!hit)
|
|
42
|
+
return null;
|
|
43
|
+
const blockers = [];
|
|
44
|
+
scene.traverseVisible((object) => {
|
|
45
|
+
if (!(object instanceof Mesh) ||
|
|
46
|
+
getElevatorButtonData(object) ||
|
|
47
|
+
object.userData.pascalExport === 'strip')
|
|
48
|
+
return;
|
|
49
|
+
if (Array.isArray(object.material) &&
|
|
50
|
+
object.geometry.groups.some((group) => !object.material[group.materialIndex ?? 0] || !Number.isFinite(group.count)))
|
|
51
|
+
return;
|
|
52
|
+
blockers.push(object);
|
|
53
|
+
});
|
|
54
|
+
const occluded = raycaster.intersectObjects(blockers, false).some((intersection) => {
|
|
55
|
+
if (intersection.distance >= hit.distance - 0.001)
|
|
56
|
+
return false;
|
|
57
|
+
const mesh = intersection.object;
|
|
58
|
+
const material = Array.isArray(mesh.material)
|
|
59
|
+
? mesh.material[intersection.face?.materialIndex ?? 0]
|
|
60
|
+
: mesh.material;
|
|
61
|
+
return material?.visible && (!material.transparent || material.opacity >= 0.95);
|
|
62
|
+
});
|
|
63
|
+
return occluded ? null : getElevatorButtonData(hit.object);
|
|
64
|
+
}
|
|
65
|
+
export function ElevatorInteractionSystem({ enabled = true }) {
|
|
30
66
|
const camera = useThree((state) => state.camera);
|
|
31
67
|
const gl = useThree((state) => state.gl);
|
|
32
68
|
const scene = useThree((state) => state.scene);
|
|
@@ -35,7 +71,7 @@ export function ElevatorInteractionSystem() {
|
|
|
35
71
|
useEffect(() => {
|
|
36
72
|
const canvas = gl.domElement;
|
|
37
73
|
const handlePointerDown = (event) => {
|
|
38
|
-
if (event.button !== 0)
|
|
74
|
+
if (event.button !== 0 || !enabled)
|
|
39
75
|
return;
|
|
40
76
|
const buttons = collectElevatorButtons(scene);
|
|
41
77
|
if (buttons.length === 0)
|
|
@@ -46,10 +82,7 @@ export function ElevatorInteractionSystem() {
|
|
|
46
82
|
pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
47
83
|
const raycaster = raycasterRef.current;
|
|
48
84
|
raycaster.setFromCamera(pointer, camera);
|
|
49
|
-
const button = raycaster
|
|
50
|
-
.intersectObjects(buttons, true)
|
|
51
|
-
.map((intersection) => getElevatorButtonData(intersection.object))
|
|
52
|
-
.find((data) => data !== null);
|
|
85
|
+
const button = pickElevatorButton(scene, raycaster);
|
|
53
86
|
if (!button || button.disabled)
|
|
54
87
|
return;
|
|
55
88
|
event.preventDefault();
|
|
@@ -74,6 +107,6 @@ export function ElevatorInteractionSystem() {
|
|
|
74
107
|
return () => {
|
|
75
108
|
canvas.removeEventListener('pointerdown', handlePointerDown, true);
|
|
76
109
|
};
|
|
77
|
-
}, [camera, gl, scene]);
|
|
110
|
+
}, [camera, gl, scene, enabled]);
|
|
78
111
|
return null;
|
|
79
112
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AnyNodeId } from '@pascal-app/core';
|
|
2
|
+
export type GeometryFailure = 'build-failed' | 'invalid-geometry';
|
|
3
|
+
/** Geometry failures describe the current rendering, never saved building data. */
|
|
4
|
+
export declare const useGeometryStatus: import("zustand").UseBoundStore<import("zustand").StoreApi<{
|
|
5
|
+
failures: ReadonlyMap<AnyNodeId, GeometryFailure>;
|
|
6
|
+
}>>;
|
|
7
|
+
export declare function setGeometryFailure(id: AnyNodeId, message: GeometryFailure | null): void;
|
|
8
|
+
//# sourceMappingURL=geometry-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-status.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,kBAAkB,CAAA;AAG3D,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,kBAAkB,CAAA;AAEjE,mFAAmF;AACnF,eAAO,MAAM,iBAAiB;cAAsB,WAAW,CAAC,SAAS,EAAE,eAAe,CAAC;GAI1F,CAAA;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,QAOhF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useScene } from '@pascal-app/core';
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
/** Geometry failures describe the current rendering, never saved building data. */
|
|
4
|
+
export const useGeometryStatus = create(() => ({
|
|
5
|
+
failures: new Map(),
|
|
6
|
+
}));
|
|
7
|
+
export function setGeometryFailure(id, message) {
|
|
8
|
+
const current = useGeometryStatus.getState().failures;
|
|
9
|
+
if ((current.get(id) ?? null) === message)
|
|
10
|
+
return;
|
|
11
|
+
const failures = new Map(current);
|
|
12
|
+
if (message)
|
|
13
|
+
failures.set(id, message);
|
|
14
|
+
else
|
|
15
|
+
failures.delete(id);
|
|
16
|
+
useGeometryStatus.setState({ failures });
|
|
17
|
+
}
|
|
18
|
+
useScene.subscribe((scene, previous) => {
|
|
19
|
+
const failures = useGeometryStatus.getState().failures;
|
|
20
|
+
for (const id of failures.keys()) {
|
|
21
|
+
if (scene.hydrationId !== previous.hydrationId || !scene.nodes[id])
|
|
22
|
+
setGeometryFailure(id, null);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAgCZ,KAAK,eAAe,EAUrB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ import { applyWorldScaleBoxUVs } from '../../lib/box-uv';
|
|
|
9
9
|
import { ensureRenderableGeometryAttributes, subtractCsgBrush } from '../../lib/csg-utils';
|
|
10
10
|
import useViewer from '../../store/use-viewer';
|
|
11
11
|
import { isRevealAssembling } from '../construction-reveal/construction-reveal';
|
|
12
|
+
import { setGeometryFailure, useGeometryStatus } from '../geometry/geometry-status';
|
|
12
13
|
import { ROOF_GABLE_LEFT_MATERIAL_INDEX, ROOF_GABLE_RIGHT_MATERIAL_INDEX, ROOF_HEEL_MATERIAL_INDEX, } from './roof-material-slots';
|
|
13
14
|
function csgGeometry(brush) {
|
|
14
15
|
return brush.geometry;
|
|
@@ -119,9 +120,14 @@ const MAX_ROOFS_PER_FRAME = 1;
|
|
|
119
120
|
const MAX_SEGMENTS_PER_FRAME = 3;
|
|
120
121
|
/** What each roof's shell was last merged from: its node and its segments, as the store held them. */
|
|
121
122
|
const mergedFrom = new Map();
|
|
123
|
+
/** A roof's shell is merged from the roof and its segments; its faces of other kinds draw apart. */
|
|
124
|
+
const shellInputIds = (roof, nodes) => [
|
|
125
|
+
roof.id,
|
|
126
|
+
...(roof.children ?? []).filter((id) => nodes[id]?.type === 'roof-segment'),
|
|
127
|
+
];
|
|
122
128
|
function rememberMergedInputs(roof, nodes) {
|
|
123
129
|
const inputs = new Map();
|
|
124
|
-
for (const id of
|
|
130
|
+
for (const id of shellInputIds(roof, nodes)) {
|
|
125
131
|
const node = nodes[id];
|
|
126
132
|
if (node)
|
|
127
133
|
inputs.set(id, node);
|
|
@@ -134,7 +140,7 @@ function mergedFromStillHolds(roofId, nodes) {
|
|
|
134
140
|
const roof = nodes[roofId];
|
|
135
141
|
if (!inputs || roof?.type !== 'roof')
|
|
136
142
|
return false;
|
|
137
|
-
const ids =
|
|
143
|
+
const ids = shellInputIds(roof, nodes);
|
|
138
144
|
return ids.length === inputs.size && ids.every((id) => inputs.get(id) === nodes[id]);
|
|
139
145
|
}
|
|
140
146
|
/**
|
|
@@ -372,7 +378,13 @@ export const RoofSystem = ({ geometryAdapter } = {}) => {
|
|
|
372
378
|
// Built even while hidden: a roof assembling from its segments (construction
|
|
373
379
|
// reveal) hides its shell, and must show it whole the moment it is captured.
|
|
374
380
|
rememberMergedInputs(node, nodes);
|
|
375
|
-
|
|
381
|
+
try {
|
|
382
|
+
updateMergedRoofGeometry(node, group, nodes, framing);
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
setGeometryFailure(node.id, 'build-failed');
|
|
386
|
+
console.error('Roof rebuild failed:', error);
|
|
387
|
+
}
|
|
376
388
|
roofsProcessed++;
|
|
377
389
|
pendingRoofUpdates.delete(id);
|
|
378
390
|
}
|
|
@@ -383,7 +395,19 @@ export const RoofSystem = ({ geometryAdapter } = {}) => {
|
|
|
383
395
|
// GEOMETRY GENERATION
|
|
384
396
|
// ============================================================================
|
|
385
397
|
function updateRoofSegmentGeometry(node, mesh, nodes, adapter, framing = false) {
|
|
386
|
-
|
|
398
|
+
let newGeo;
|
|
399
|
+
try {
|
|
400
|
+
newGeo = generateRoofSegmentGeometry(node, nodes, { framing });
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
setGeometryFailure(node.id, 'build-failed');
|
|
404
|
+
console.error('Roof segment rebuild failed:', error);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (useGeometryStatus.getState().failures.has(node.id)) {
|
|
408
|
+
newGeo.dispose();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
387
411
|
mesh.geometry.dispose();
|
|
388
412
|
mesh.geometry = newGeo;
|
|
389
413
|
computeGeometryBoundsTree(newGeo);
|
|
@@ -405,69 +429,70 @@ function subtractAccessoryCuts(brushes, segment, nodes) {
|
|
|
405
429
|
let workingShin = brushes.shinSlab;
|
|
406
430
|
let workingDeck = brushes.deckSlab;
|
|
407
431
|
let workingWall = brushes.wallBrush;
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
432
|
+
try {
|
|
433
|
+
for (const childElemId of segment.children ?? []) {
|
|
434
|
+
const storedChild = nodes[childElemId];
|
|
435
|
+
if (!storedChild)
|
|
436
|
+
continue;
|
|
437
|
+
const childElem = getEffectiveNode(storedChild);
|
|
438
|
+
const meta = typeof childElem.metadata === 'object' && childElem.metadata !== null
|
|
439
|
+
? childElem.metadata
|
|
440
|
+
: undefined;
|
|
441
|
+
if (meta?.isTransient)
|
|
442
|
+
continue;
|
|
443
|
+
const childDef = nodeRegistry.get(childElem.type);
|
|
444
|
+
const buildCut = childDef?.capabilities?.roofAccessory?.buildCut;
|
|
445
|
+
if (!buildCut)
|
|
446
|
+
continue;
|
|
447
|
+
const cutGeo = buildCut(childElem, segment);
|
|
448
|
+
if (!cutGeo)
|
|
449
|
+
continue;
|
|
450
|
+
// Wrap the kind-emitted geometry in a Brush. Kinds return raw
|
|
451
|
+
// shapes; the viewer welds (mandatory after rotations leave
|
|
452
|
+
// duplicated verts), attaches a single material group, and
|
|
453
|
+
// builds the bounds tree — keeping kind code free of
|
|
454
|
+
// three-bvh-csg / three-mesh-bvh imports.
|
|
455
|
+
const welded = mergeVertices(cutGeo, 1e-4);
|
|
456
|
+
cutGeo.dispose();
|
|
457
|
+
const idxCount = welded.getIndex()?.count ?? 0;
|
|
458
|
+
if (idxCount === 0) {
|
|
459
|
+
welded.dispose();
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
welded.clearGroups();
|
|
463
|
+
welded.addGroup(0, idxCount, 0);
|
|
464
|
+
welded.computeVertexNormals();
|
|
465
|
+
ensureRenderableGeometryAttributes(welded);
|
|
466
|
+
computeGeometryBoundsTree(welded);
|
|
467
|
+
const cut = new Brush(welded, dummyMats[0]);
|
|
468
|
+
cut.updateMatrixWorld();
|
|
469
|
+
const cutScope = childDef?.capabilities?.roofAccessory?.cutScope ?? 'all';
|
|
470
|
+
try {
|
|
471
|
+
if (cutScope !== 'wall') {
|
|
472
|
+
const nextShin = csgEvaluator.evaluate(workingShin, cut, SUBTRACTION);
|
|
473
|
+
workingShin.geometry.dispose();
|
|
474
|
+
prepareBrushForCSG(nextShin);
|
|
475
|
+
workingShin = nextShin;
|
|
476
|
+
const nextDeck = csgEvaluator.evaluate(workingDeck, cut, SUBTRACTION);
|
|
477
|
+
workingDeck.geometry.dispose();
|
|
478
|
+
prepareBrushForCSG(nextDeck);
|
|
479
|
+
workingDeck = nextDeck;
|
|
480
|
+
}
|
|
481
|
+
const nextWall = csgEvaluator.evaluate(workingWall, cut, SUBTRACTION);
|
|
482
|
+
workingWall.geometry.dispose();
|
|
483
|
+
prepareBrushForCSG(nextWall);
|
|
484
|
+
workingWall = nextWall;
|
|
485
|
+
}
|
|
486
|
+
finally {
|
|
487
|
+
cut.geometry.dispose();
|
|
455
488
|
}
|
|
456
|
-
const nextWall = csgEvaluator.evaluate(workingWall, cut, SUBTRACTION);
|
|
457
|
-
workingWall.geometry.dispose();
|
|
458
|
-
prepareBrushForCSG(nextWall);
|
|
459
|
-
workingWall = nextWall;
|
|
460
|
-
}
|
|
461
|
-
catch (e) {
|
|
462
|
-
console.error(`[${childElem.type}] cut CSG failed:`, e);
|
|
463
|
-
}
|
|
464
|
-
finally {
|
|
465
|
-
cut.geometry.dispose();
|
|
466
489
|
}
|
|
467
490
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
491
|
+
finally {
|
|
492
|
+
brushes.shinSlab = workingShin;
|
|
493
|
+
brushes.deckSlab = workingDeck;
|
|
494
|
+
brushes.wallBrush = workingWall;
|
|
495
|
+
}
|
|
471
496
|
}
|
|
472
497
|
function getMergedRoofSegmentBrushes(roofNode, segment, nodes, framing) {
|
|
473
498
|
const segmentId = segment.id;
|
|
@@ -478,7 +503,7 @@ function getMergedRoofSegmentBrushes(roofNode, segment, nodes, framing) {
|
|
|
478
503
|
return cloneCachedMergedRoofSegmentBrushes(cached);
|
|
479
504
|
}
|
|
480
505
|
const brushes = withSegmentUvMatrix(composeSegmentWorldMatrix(roofNode.position, roofNode.rotation ?? 0, segment.position, segment.rotation ?? 0), () => getRoofSegmentBrushes(segment, {
|
|
481
|
-
fascia: wearsFascia(segment, roofNode),
|
|
506
|
+
fascia: wearsFascia(segment, roofNode, nodes),
|
|
482
507
|
framing: framingDepth,
|
|
483
508
|
}));
|
|
484
509
|
if (!brushes) {
|
|
@@ -486,7 +511,13 @@ function getMergedRoofSegmentBrushes(roofNode, segment, nodes, framing) {
|
|
|
486
511
|
mergedRoofSegmentGeometryCache.delete(segmentId);
|
|
487
512
|
return null;
|
|
488
513
|
}
|
|
489
|
-
|
|
514
|
+
try {
|
|
515
|
+
subtractAccessoryCuts(brushes, segment, nodes);
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
disposeRoofSegmentBrushSet(brushes);
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
490
521
|
_matrix.compose(_position.set(segment.position[0], segment.position[1], segment.position[2]), _quaternion.setFromAxisAngle(_yAxis, segment.rotation), _scale);
|
|
491
522
|
const applyTransform = (brush) => {
|
|
492
523
|
csgGeometry(brush).applyMatrix4(_matrix);
|
|
@@ -536,10 +567,11 @@ function updateMergedRoofGeometry(roofNode, group, nodes, framing) {
|
|
|
536
567
|
// Merge each child through `getEffectiveNode` so an in-flight handle
|
|
537
568
|
// drag (live override on width / depth / wallHeight / pitch / rotation)
|
|
538
569
|
// is reflected in the merged shell during the drag, not only on commit.
|
|
570
|
+
// Faces of other kinds (`capabilities.roofs`) render as their own kind, outside the shell.
|
|
539
571
|
const children = (roofNode.children ?? [])
|
|
540
572
|
.map((id) => {
|
|
541
573
|
const scn = nodes[id];
|
|
542
|
-
return scn ? getEffectiveNode(scn) : undefined;
|
|
574
|
+
return scn?.type === 'roof-segment' ? getEffectiveNode(scn) : undefined;
|
|
543
575
|
})
|
|
544
576
|
.filter((n) => n !== undefined && !hasSegmentMaterialOverride(n) && !isIndependentRoofSegment(n));
|
|
545
577
|
if (children.length === 0) {
|
|
@@ -547,6 +579,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes, framing) {
|
|
|
547
579
|
// Not BoxGeometry: its 6 groups against the merged mesh's 4-material array
|
|
548
580
|
// crash GLTFExporter (materials[4] → undefined) when the roof bakes.
|
|
549
581
|
mergedMesh.geometry = createDegenerateRoofPlaceholder();
|
|
582
|
+
setGeometryFailure(roofNode.id, null);
|
|
550
583
|
return;
|
|
551
584
|
}
|
|
552
585
|
let totalShinSlab = null;
|
|
@@ -601,7 +634,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes, framing) {
|
|
|
601
634
|
else {
|
|
602
635
|
totalDeckSlab = brushes.deckSlab;
|
|
603
636
|
}
|
|
604
|
-
if (!shouldIncludeRoofSegmentWallShell(child, roofNode)) {
|
|
637
|
+
if (!shouldIncludeRoofSegmentWallShell(child, roofNode, nodes)) {
|
|
605
638
|
brushes.wallBrush.geometry.dispose();
|
|
606
639
|
brushes.innerBrush.geometry.dispose();
|
|
607
640
|
}
|
|
@@ -643,6 +676,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes, framing) {
|
|
|
643
676
|
console.warn('[RoofSystem] Skipping merged roof geometry with invalid attributes', roofNode.id);
|
|
644
677
|
warnedMergedRoofNaNIds.add(roofNode.id);
|
|
645
678
|
}
|
|
679
|
+
setGeometryFailure(roofNode.id, 'invalid-geometry');
|
|
646
680
|
resultGeo.dispose();
|
|
647
681
|
if (combined !== shinDeck)
|
|
648
682
|
shinDeck.geometry.dispose();
|
|
@@ -667,7 +701,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes, framing) {
|
|
|
667
701
|
}
|
|
668
702
|
let finalGeo = addShedInsetEndPanels(resultGeo, csgChildren, true);
|
|
669
703
|
splitRoofWallBandFaces(finalGeo, csgChildren
|
|
670
|
-
.filter((child) => shouldIncludeRoofSegmentWallShell(child, roofNode))
|
|
704
|
+
.filter((child) => shouldIncludeRoofSegmentWallShell(child, roofNode, nodes))
|
|
671
705
|
.map((child) => ({
|
|
672
706
|
node: child,
|
|
673
707
|
inverse: new THREE.Matrix4()
|
|
@@ -691,10 +725,12 @@ function updateMergedRoofGeometry(roofNode, group, nodes, framing) {
|
|
|
691
725
|
ensureRenderableGeometryAttributes(finalGeo);
|
|
692
726
|
mergedMesh.geometry.dispose();
|
|
693
727
|
mergedMesh.geometry = finalGeo;
|
|
728
|
+
setGeometryFailure(roofNode.id, null);
|
|
694
729
|
if (combined !== shinDeck)
|
|
695
730
|
shinDeck.geometry.dispose();
|
|
696
731
|
}
|
|
697
732
|
catch (e) {
|
|
733
|
+
setGeometryFailure(roofNode.id, 'build-failed');
|
|
698
734
|
console.error('Merged roof CSG failed:', e);
|
|
699
735
|
}
|
|
700
736
|
totalShinSlab.geometry.dispose();
|
|
@@ -712,6 +748,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes, framing) {
|
|
|
712
748
|
ensureRenderableGeometryAttributes(finalGeo);
|
|
713
749
|
mergedMesh.geometry.dispose();
|
|
714
750
|
mergedMesh.geometry = finalGeo;
|
|
751
|
+
setGeometryFailure(roofNode.id, null);
|
|
715
752
|
}
|
|
716
753
|
for (const geometry of directSegmentGeometries) {
|
|
717
754
|
if (geometry !== finalGeo)
|
|
@@ -823,7 +860,7 @@ function getMergedRoofSegmentCacheKey(roofNode, segment, nodes, framing) {
|
|
|
823
860
|
roofPosition: roofNode.position ?? [0, 0, 0],
|
|
824
861
|
roofRotation: roofNode.rotation ?? 0,
|
|
825
862
|
// a shed's fascia depends on whether its roof is a composite of pieces
|
|
826
|
-
composite: (roofNode
|
|
863
|
+
composite: isCompositeRoof(roofNode, nodes),
|
|
827
864
|
segment,
|
|
828
865
|
accessories: getMergedRoofAccessoryCachePayload(segment, nodes),
|
|
829
866
|
framing,
|
|
@@ -955,7 +992,15 @@ function readShedOpenEndSides(node) {
|
|
|
955
992
|
return new Set();
|
|
956
993
|
return new Set(value.filter((side) => side === 'left' || side === 'right'));
|
|
957
994
|
}
|
|
958
|
-
|
|
995
|
+
/**
|
|
996
|
+
* Whether a roof's shell is pieced from several segments. Its faces of other kinds
|
|
997
|
+
* (`capabilities.roofs`) are no pieces of it; without the scene, every child counts.
|
|
998
|
+
*/
|
|
999
|
+
function isCompositeRoof(roof, nodes) {
|
|
1000
|
+
const children = roof.children ?? [];
|
|
1001
|
+
return (nodes ? children.filter((id) => nodes[id]?.type === 'roof-segment') : children).length > 1;
|
|
1002
|
+
}
|
|
1003
|
+
function shouldIncludeRoofSegmentWallShell(node, parentRoof, nodes) {
|
|
959
1004
|
if (node.wallShell === 'include')
|
|
960
1005
|
return true;
|
|
961
1006
|
if (node.wallShell === 'omit')
|
|
@@ -965,7 +1010,7 @@ function shouldIncludeRoofSegmentWallShell(node, parentRoof) {
|
|
|
965
1010
|
// Older composite roofs use overlapping shed segments as deck pieces. Their
|
|
966
1011
|
// wall volumes were never part of the rendered shell and make CSG grow
|
|
967
1012
|
// exponentially when unioned together.
|
|
968
|
-
return !parentRoof || (parentRoof
|
|
1013
|
+
return !parentRoof || !isCompositeRoof(parentRoof, nodes);
|
|
969
1014
|
}
|
|
970
1015
|
function hasSegmentTrim(node) {
|
|
971
1016
|
const trim = normalizeRoofSegmentTrim(node);
|
|
@@ -1423,14 +1468,14 @@ function buildEaveCuts(node, reach, eave, seal) {
|
|
|
1423
1468
|
* one deck (the same pieces whose wall shells are left out), so boards would
|
|
1424
1469
|
* show between them.
|
|
1425
1470
|
*/
|
|
1426
|
-
function wearsFascia(node, parentRoof) {
|
|
1471
|
+
function wearsFascia(node, parentRoof, nodes) {
|
|
1427
1472
|
if (node.fascia !== true)
|
|
1428
1473
|
return false;
|
|
1429
1474
|
if (node.roofType !== 'shed')
|
|
1430
1475
|
return true;
|
|
1431
1476
|
if (isLeanToShedSegment(node))
|
|
1432
1477
|
return false;
|
|
1433
|
-
return !parentRoof || (parentRoof
|
|
1478
|
+
return !parentRoof || !isCompositeRoof(parentRoof, nodes);
|
|
1434
1479
|
}
|
|
1435
1480
|
/** What a framed roof's deck keeps over its open structure band at least: a 7/16 in sheet. */
|
|
1436
1481
|
const ROOF_DECK_KEPT = 0.0111;
|
|
@@ -1932,18 +1977,25 @@ export function generateRoofSegmentGeometry(node, nodes, options = {}) {
|
|
|
1932
1977
|
}
|
|
1933
1978
|
result.computeVertexNormals();
|
|
1934
1979
|
ensureRenderableGeometryAttributes(result);
|
|
1980
|
+
setGeometryFailure(node.id, null);
|
|
1935
1981
|
return result;
|
|
1936
1982
|
}
|
|
1937
1983
|
const brushes = withSegmentUvMatrix(segmentWorldMatrix, () => getRoofSegmentBrushes(node, {
|
|
1938
|
-
fascia: wearsFascia(node, parentRoof),
|
|
1984
|
+
fascia: wearsFascia(node, parentRoof, nodes),
|
|
1939
1985
|
framing: options.framing ? roofFramingDepth(node) : 0,
|
|
1940
1986
|
}));
|
|
1941
1987
|
if (!brushes) {
|
|
1942
|
-
|
|
1988
|
+
setGeometryFailure(node.id, 'build-failed');
|
|
1943
1989
|
return new THREE.BoxGeometry(node.width, node.wallHeight, node.depth);
|
|
1944
1990
|
}
|
|
1945
1991
|
if (nodes) {
|
|
1946
|
-
|
|
1992
|
+
try {
|
|
1993
|
+
subtractAccessoryCuts(brushes, node, nodes);
|
|
1994
|
+
}
|
|
1995
|
+
catch (error) {
|
|
1996
|
+
disposeRoofSegmentBrushSet(brushes);
|
|
1997
|
+
throw error;
|
|
1998
|
+
}
|
|
1947
1999
|
}
|
|
1948
2000
|
const { deckSlab, shinSlab, wallBrush, innerBrush, rakeBoards } = brushes;
|
|
1949
2001
|
let resultGeo = new THREE.BufferGeometry();
|
|
@@ -1952,7 +2004,7 @@ export function generateRoofSegmentGeometry(node, nodes, options = {}) {
|
|
|
1952
2004
|
prepareBrushForCSG(shinDeck);
|
|
1953
2005
|
let combined = shinDeck;
|
|
1954
2006
|
let hollowWall = null;
|
|
1955
|
-
if (shouldIncludeRoofSegmentWallShell(node, parentRoof)) {
|
|
2007
|
+
if (shouldIncludeRoofSegmentWallShell(node, parentRoof, nodes)) {
|
|
1956
2008
|
hollowWall = csgEvaluator.evaluate(wallBrush, innerBrush, SUBTRACTION);
|
|
1957
2009
|
prepareBrushForCSG(hollowWall);
|
|
1958
2010
|
combined = csgEvaluator.evaluate(shinDeck, hollowWall, ADDITION);
|
|
@@ -1968,8 +2020,11 @@ export function generateRoofSegmentGeometry(node, nodes, options = {}) {
|
|
|
1968
2020
|
resultGeo = csgGeometry(combined);
|
|
1969
2021
|
if (geometryHasInvalidAttributes(resultGeo)) {
|
|
1970
2022
|
resultGeo.dispose();
|
|
2023
|
+
setGeometryFailure(node.id, 'invalid-geometry');
|
|
1971
2024
|
resultGeo = csgGeometry(wallBrush).clone();
|
|
1972
2025
|
}
|
|
2026
|
+
else
|
|
2027
|
+
setGeometryFailure(node.id, null);
|
|
1973
2028
|
const resultMaterials = csgMaterials(combined);
|
|
1974
2029
|
const matToIndex = new Map([
|
|
1975
2030
|
[dummyMats[0], 0],
|
|
@@ -1989,6 +2044,7 @@ export function generateRoofSegmentGeometry(node, nodes, options = {}) {
|
|
|
1989
2044
|
shinDeck.geometry.dispose();
|
|
1990
2045
|
}
|
|
1991
2046
|
catch (e) {
|
|
2047
|
+
setGeometryFailure(node.id, 'build-failed');
|
|
1992
2048
|
console.error('Roof CSG failed:', e);
|
|
1993
2049
|
resultGeo = csgGeometry(wallBrush).clone();
|
|
1994
2050
|
}
|
|
@@ -55,6 +55,17 @@ export declare function markWallsForZoneFinishChanges(): void;
|
|
|
55
55
|
*/
|
|
56
56
|
export declare function wallGeometryChildren(node: WallNode, nodes: Readonly<Record<string, AnyNode>>): AnyNode[];
|
|
57
57
|
type WallTerrainBottomSampler = (x: number, z: number) => number | null;
|
|
58
|
+
/** The wall's top in wall-local coordinates: `height` at the start, rising `slope` per metre of x. */
|
|
59
|
+
type WallTopLine = {
|
|
60
|
+
height: number;
|
|
61
|
+
slope: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Wall-local Y of the sloped top at `x`. Mitered corners sample `x` outside
|
|
65
|
+
* [0, L], where the line can fall below the wall base; the top never drops
|
|
66
|
+
* under {@link MIN_WALL_END_HEIGHT} (or the flat height, if lower).
|
|
67
|
+
*/
|
|
68
|
+
export declare function wallTopYAt(wallTop: WallTopLine, x: number): number;
|
|
58
69
|
export declare function generateExtrudedWall(wallNode: WallNode, childrenNodes: AnyNode[], miterData: WallMiterData, slabElevation?: number, baseElevation?: number, baseSegments?: readonly WallSlabSupportSegment[], storeyHeight?: number, terrainBottomAt?: WallTerrainBottomSampler, faceBase?: {
|
|
59
70
|
a: readonly WallSlabSupportSegment[];
|
|
60
71
|
b: readonly WallSlabSupportSegment[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"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"}
|