@pascal-app/viewer 1.0.6 → 1.1.0
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 +54 -21
- package/dist/components/viewer/daylight-atmosphere.d.ts +9 -0
- package/dist/components/viewer/daylight-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/daylight-atmosphere.js +315 -0
- package/dist/components/viewer/drone-controller.d.ts +15 -0
- package/dist/components/viewer/drone-controller.d.ts.map +1 -0
- package/dist/components/viewer/drone-controller.js +194 -0
- package/dist/components/viewer/glb-interactive.d.ts +4 -18
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
- package/dist/components/viewer/glb-interactive.js +32 -268
- 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-motion.d.ts +8 -0
- package/dist/components/viewer/glb-scene-motion.d.ts.map +1 -0
- package/dist/components/viewer/glb-scene-motion.js +57 -0
- package/dist/components/viewer/glb-scene.d.ts +5 -1
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +290 -49
- package/dist/components/viewer/glb-selection-callout.d.ts +17 -0
- package/dist/components/viewer/glb-selection-callout.d.ts.map +1 -0
- package/dist/components/viewer/glb-selection-callout.js +54 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +6 -3
- package/dist/components/viewer/index.d.ts +5 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +38 -10
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +192 -5
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +150 -94
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +2 -2
- package/dist/components/viewer/scene-environment.js +4 -5
- package/dist/components/viewer/selection-callout.d.ts +51 -0
- package/dist/components/viewer/selection-callout.d.ts.map +1 -0
- package/dist/components/viewer/selection-callout.js +197 -0
- 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 +13 -0
- package/dist/components/viewer/use-daylight-rooms.d.ts.map +1 -0
- package/dist/components/viewer/use-daylight-rooms.js +278 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +12 -1
- 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 +26 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -8
- 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/ground.d.ts +21 -0
- package/dist/lib/daylight/ground.d.ts.map +1 -0
- package/dist/lib/daylight/ground.js +53 -0
- package/dist/lib/daylight/location.d.ts +32 -0
- package/dist/lib/daylight/location.d.ts.map +1 -0
- package/dist/lib/daylight/location.js +74 -0
- package/dist/lib/daylight/look.d.ts +33 -0
- package/dist/lib/daylight/look.d.ts.map +1 -0
- package/dist/lib/daylight/look.js +13 -0
- package/dist/lib/daylight/material-hooks.d.ts +2 -0
- package/dist/lib/daylight/material-hooks.d.ts.map +1 -0
- package/dist/lib/daylight/material-hooks.js +150 -0
- package/dist/lib/daylight/room-context.d.ts +12 -0
- package/dist/lib/daylight/room-context.d.ts.map +1 -0
- package/dist/lib/daylight/room-context.js +54 -0
- package/dist/lib/daylight/room-map.d.ts +124 -0
- package/dist/lib/daylight/room-map.d.ts.map +1 -0
- package/dist/lib/daylight/room-map.js +630 -0
- package/dist/lib/daylight/room-scene.d.ts +56 -0
- package/dist/lib/daylight/room-scene.d.ts.map +1 -0
- package/dist/lib/daylight/room-scene.js +391 -0
- package/dist/lib/daylight/rooms.d.ts +292 -0
- package/dist/lib/daylight/rooms.d.ts.map +1 -0
- package/dist/lib/daylight/rooms.js +1268 -0
- package/dist/lib/daylight/runtime.d.ts +33 -0
- package/dist/lib/daylight/runtime.d.ts.map +1 -0
- package/dist/lib/daylight/runtime.js +42 -0
- package/dist/lib/daylight/sky.d.ts +26 -0
- package/dist/lib/daylight/sky.d.ts.map +1 -0
- package/dist/lib/daylight/sky.js +217 -0
- package/dist/lib/daylight/solar.d.ts +46 -0
- package/dist/lib/daylight/solar.d.ts.map +1 -0
- package/dist/lib/daylight/solar.js +156 -0
- package/dist/lib/device-tier.d.ts +29 -0
- package/dist/lib/device-tier.d.ts.map +1 -0
- package/dist/lib/device-tier.js +71 -0
- package/dist/lib/dispose-object3d.d.ts.map +1 -1
- package/dist/lib/dispose-object3d.js +4 -2
- package/dist/lib/drone-flight.d.ts +44 -0
- package/dist/lib/drone-flight.d.ts.map +1 -0
- package/dist/lib/drone-flight.js +121 -0
- package/dist/lib/editor-highlight.d.ts +16 -0
- package/dist/lib/editor-highlight.d.ts.map +1 -0
- package/dist/lib/editor-highlight.js +33 -0
- package/dist/lib/foundation-materials.d.ts.map +1 -1
- package/dist/lib/foundation-materials.js +2 -4
- package/dist/lib/glb-merge.d.ts +23 -0
- package/dist/lib/glb-merge.d.ts.map +1 -0
- package/dist/lib/glb-merge.js +184 -0
- package/dist/lib/hardware-geometry.d.ts +21 -0
- package/dist/lib/hardware-geometry.d.ts.map +1 -0
- package/dist/lib/hardware-geometry.js +283 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +2 -1
- package/dist/lib/layer-pass.d.ts +2 -0
- package/dist/lib/layer-pass.d.ts.map +1 -1
- package/dist/lib/layer-pass.js +20 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +50 -23
- 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 +5 -0
- package/dist/lib/measurement-presentation.d.ts.map +1 -0
- package/dist/lib/measurement-presentation.js +8 -0
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +7 -1
- package/dist/lib/occlusion-culler.d.ts +34 -0
- package/dist/lib/occlusion-culler.d.ts.map +1 -0
- package/dist/lib/occlusion-culler.js +698 -0
- package/dist/lib/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +3 -3
- package/dist/lib/post-processing-resources.d.ts +4 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -1
- package/dist/lib/post-processing-resources.js +5 -0
- package/dist/lib/readback-rows.d.ts +14 -0
- package/dist/lib/readback-rows.d.ts.map +1 -0
- package/dist/lib/readback-rows.js +31 -0
- package/dist/lib/reveal-pose.d.ts.map +1 -1
- package/dist/lib/reveal-pose.js +3 -0
- package/dist/lib/scene-matrices.d.ts +12 -0
- package/dist/lib/scene-matrices.d.ts.map +1 -0
- package/dist/lib/scene-matrices.js +287 -0
- package/dist/lib/scene-pass-mrt.d.ts +5 -2
- package/dist/lib/scene-pass-mrt.d.ts.map +1 -1
- package/dist/lib/scene-pass-mrt.js +7 -2
- package/dist/lib/scene-themes.d.ts +0 -2
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +0 -2
- package/dist/lib/scene-visibility.d.ts +2 -0
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +12 -3
- package/dist/lib/site-surfaces.d.ts +1 -8
- package/dist/lib/site-surfaces.d.ts.map +1 -1
- package/dist/lib/site-surfaces.js +0 -19
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +24 -40
- package/dist/lib/texture-reference.d.ts.map +1 -1
- package/dist/lib/texture-reference.js +13 -10
- package/dist/lib/underground-ambiance.d.ts +0 -2
- package/dist/lib/underground-ambiance.d.ts.map +1 -1
- package/dist/lib/underground-ambiance.js +0 -2
- package/dist/lib/viewer-config.d.ts +21 -0
- package/dist/lib/viewer-config.d.ts.map +1 -0
- package/dist/lib/viewer-config.js +21 -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/store/use-item-light-pool.d.ts +3 -6
- package/dist/store/use-item-light-pool.d.ts.map +1 -1
- package/dist/store/use-item-light-pool.js +12 -0
- package/dist/store/use-viewer.d.ts +62 -59
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +106 -19
- package/dist/systems/ceiling/ceiling-system.d.ts +5 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +11 -1
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +501 -145
- package/dist/systems/door/leaf-profile.d.ts +11 -0
- package/dist/systems/door/leaf-profile.d.ts.map +1 -0
- package/dist/systems/door/leaf-profile.js +140 -0
- 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/interactive/callout-actions.d.ts +72 -0
- package/dist/systems/interactive/callout-actions.d.ts.map +1 -0
- package/dist/systems/interactive/callout-actions.js +212 -0
- package/dist/systems/interactive/interactive-system.d.ts +5 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +44 -121
- package/dist/systems/interactive/opening-settled.d.ts +16 -0
- package/dist/systems/interactive/opening-settled.d.ts.map +1 -0
- package/dist/systems/interactive/opening-settled.js +53 -0
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +22 -214
- package/dist/systems/item-light/pooled-lights.d.ts +70 -0
- package/dist/systems/item-light/pooled-lights.d.ts.map +1 -0
- package/dist/systems/item-light/pooled-lights.js +449 -0
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +19 -1
- package/dist/systems/roof/roof-material-slots.d.ts +14 -0
- package/dist/systems/roof/roof-material-slots.d.ts.map +1 -0
- package/dist/systems/roof/roof-material-slots.js +13 -0
- package/dist/systems/roof/roof-materials.d.ts +25 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +85 -26
- package/dist/systems/roof/roof-system.d.ts +34 -1
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +582 -168
- package/dist/systems/wall/opening-casing-geometry.d.ts +14 -0
- package/dist/systems/wall/opening-casing-geometry.d.ts.map +1 -0
- package/dist/systems/wall/opening-casing-geometry.js +102 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts +9 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -1
- package/dist/systems/wall/opening-cutout-geometry.js +7 -3
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +28 -35
- 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 +86 -25
- package/dist/systems/wall/wall-t-junctions.d.ts +15 -0
- package/dist/systems/wall/wall-t-junctions.d.ts.map +1 -0
- package/dist/systems/wall/wall-t-junctions.js +149 -0
- package/dist/systems/window/window-animation-system.d.ts +3 -12
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +37 -122
- package/dist/systems/window/window-pose.d.ts +31 -0
- package/dist/systems/window/window-pose.d.ts.map +1 -0
- package/dist/systems/window/window-pose.js +152 -0
- package/dist/systems/window/window-roller-shutter.d.ts +8 -0
- package/dist/systems/window/window-roller-shutter.d.ts.map +1 -0
- package/dist/systems/window/window-roller-shutter.js +64 -0
- package/dist/systems/window/window-system.d.ts +2 -11
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +470 -303
- package/package.json +6 -6
- package/dist/systems/interactive/control-widget.d.ts +0 -10
- package/dist/systems/interactive/control-widget.d.ts.map +0 -1
- package/dist/systems/interactive/control-widget.js +0 -35
- package/dist/systems/interactive/procedural-controls.d.ts +0 -13
- package/dist/systems/interactive/procedural-controls.d.ts.map +0 -1
- package/dist/systems/interactive/procedural-controls.js +0 -17
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type DoorNode, type OpeningCasingHost, type WindowNode } from '@pascal-app/core';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
/** Name of the meshes a door or window builds its casing from. */
|
|
4
|
+
export declare const OPENING_CASING_MESH_NAME = "opening-casing";
|
|
5
|
+
/**
|
|
6
|
+
* A door's or window's casing as meshes in the unit's own frame, to hang on the
|
|
7
|
+
* mesh its frame is built on: the boards of `openingCasingBoards` on each cased
|
|
8
|
+
* face of the host, turned half round with a unit that faces the host's back. A
|
|
9
|
+
* rectangular opening is cased with butted boards. An arched or rounded one is
|
|
10
|
+
* cased with a band that follows the cutout's own profile over its head and down
|
|
11
|
+
* its sides; a window's bottom board stays straight below it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildOpeningCasingMeshes(opening: DoorNode | WindowNode, host: OpeningCasingHost, material: THREE.Material): THREE.Mesh[];
|
|
14
|
+
//# sourceMappingURL=opening-casing-geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opening-casing-geometry.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/opening-casing-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,iBAAiB,EAKtB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,kEAAkE;AAClE,eAAO,MAAM,wBAAwB,mBAAmB,CAAA;AAExD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,QAAQ,GAAG,UAAU,EAC9B,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ,GACvB,KAAK,CAAC,IAAI,EAAE,CAwCd"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { getDoorOpeningSize, OPENING_CASING_PROUD, openingCasingBoards, openingCasingFaces, openingCasingWidth, openingHostFacing, } from '@pascal-app/core';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { applyWorldScaleBoxUVs } from '../../lib/box-uv';
|
|
4
|
+
import { buildOpeningCutoutShape, getRoundedOpeningRadii } from './opening-cutout-geometry';
|
|
5
|
+
/** Name of the meshes a door or window builds its casing from. */
|
|
6
|
+
export const OPENING_CASING_MESH_NAME = 'opening-casing';
|
|
7
|
+
/**
|
|
8
|
+
* A door's or window's casing as meshes in the unit's own frame, to hang on the
|
|
9
|
+
* mesh its frame is built on: the boards of `openingCasingBoards` on each cased
|
|
10
|
+
* face of the host, turned half round with a unit that faces the host's back. A
|
|
11
|
+
* rectangular opening is cased with butted boards. An arched or rounded one is
|
|
12
|
+
* cased with a band that follows the cutout's own profile over its head and down
|
|
13
|
+
* its sides; a window's bottom board stays straight below it.
|
|
14
|
+
*/
|
|
15
|
+
export function buildOpeningCasingMeshes(opening, host, material) {
|
|
16
|
+
const facing = openingHostFacing(opening);
|
|
17
|
+
const size = getDoorOpeningSize(opening);
|
|
18
|
+
const shaped = size.width === opening.width &&
|
|
19
|
+
size.height === opening.height &&
|
|
20
|
+
(opening.openingShape === 'arch' || opening.openingShape === 'rounded');
|
|
21
|
+
const meshes = [];
|
|
22
|
+
const add = (geometry, face) => {
|
|
23
|
+
// Turned half round, the unit's x and z run against the host's.
|
|
24
|
+
if (facing < 0)
|
|
25
|
+
geometry.rotateY(Math.PI);
|
|
26
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
27
|
+
mesh.name = OPENING_CASING_MESH_NAME;
|
|
28
|
+
mesh.userData.slotId = `casing-${face}`;
|
|
29
|
+
meshes.push(mesh);
|
|
30
|
+
};
|
|
31
|
+
for (const board of openingCasingBoards(opening, host)) {
|
|
32
|
+
if (shaped && (board.part === 'head' || board.part === 'leg'))
|
|
33
|
+
continue;
|
|
34
|
+
const [width, height, depth] = board.size;
|
|
35
|
+
const geometry = new THREE.BoxGeometry(width, height, depth);
|
|
36
|
+
applyWorldScaleBoxUVs(geometry, width, height, depth);
|
|
37
|
+
geometry.translate(...board.center);
|
|
38
|
+
add(geometry, board.face);
|
|
39
|
+
}
|
|
40
|
+
if (shaped) {
|
|
41
|
+
const band = casingBandShape(opening, openingCasingWidth(opening));
|
|
42
|
+
for (const { face, plane, out } of openingCasingFaces(opening, host)) {
|
|
43
|
+
const geometry = new THREE.ExtrudeGeometry(band, {
|
|
44
|
+
depth: OPENING_CASING_PROUD,
|
|
45
|
+
bevelEnabled: false,
|
|
46
|
+
curveSegments: 24,
|
|
47
|
+
});
|
|
48
|
+
geometry.translate(0, 0, out > 0 ? plane : plane - OPENING_CASING_PROUD);
|
|
49
|
+
add(geometry, face);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return meshes;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The casing band of an arched or rounded opening as one outline, in the host
|
|
56
|
+
* frame: out from the cutout's profile by `width` over the head and down both
|
|
57
|
+
* sides, open along the bottom (a door's floor, a window's bottom board).
|
|
58
|
+
*/
|
|
59
|
+
function casingBandShape(opening, width) {
|
|
60
|
+
const halfWidth = opening.width / 2;
|
|
61
|
+
const halfHeight = opening.height / 2;
|
|
62
|
+
const inner = buildOpeningCutoutShape(opening, {
|
|
63
|
+
left: -halfWidth,
|
|
64
|
+
right: halfWidth,
|
|
65
|
+
bottom: -halfHeight,
|
|
66
|
+
top: halfHeight,
|
|
67
|
+
}).getPoints(24);
|
|
68
|
+
const outer = buildOpeningCutoutShape(grownOpening(opening, width), {
|
|
69
|
+
left: -halfWidth - width,
|
|
70
|
+
right: halfWidth + width,
|
|
71
|
+
bottom: -halfHeight,
|
|
72
|
+
top: halfHeight + width,
|
|
73
|
+
}).getPoints(24);
|
|
74
|
+
// Both profiles start along the bottom, left to right, and end back at their
|
|
75
|
+
// first point: the band runs up and over the outer one from its bottom right,
|
|
76
|
+
// then back over the inner one from its bottom left.
|
|
77
|
+
return new THREE.Shape([...outer.slice(1), ...inner.slice(1).reverse()]);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The opening a casing band's outer edge follows: its arch or its top corners
|
|
81
|
+
* grown by `width`, so the band keeps one width all round, and square at the
|
|
82
|
+
* bottom, where the band stops.
|
|
83
|
+
*/
|
|
84
|
+
function grownOpening(opening, width) {
|
|
85
|
+
if (opening.openingShape === 'arch') {
|
|
86
|
+
const archHeight = Math.min(Math.max(opening.archHeight ?? opening.width / 2, 0.01), opening.height);
|
|
87
|
+
return { ...opening, archHeight: archHeight + width };
|
|
88
|
+
}
|
|
89
|
+
const radii = getRoundedOpeningRadii(opening, opening.width, opening.height);
|
|
90
|
+
const grow = (radius) => (radius > 1e-6 ? radius + width : 0);
|
|
91
|
+
return opening.type === 'window'
|
|
92
|
+
? {
|
|
93
|
+
...opening,
|
|
94
|
+
openingRadiusMode: 'individual',
|
|
95
|
+
openingCornerRadii: [grow(radii.topLeft), grow(radii.topRight), 0, 0],
|
|
96
|
+
}
|
|
97
|
+
: {
|
|
98
|
+
...opening,
|
|
99
|
+
openingRadiusMode: 'individual',
|
|
100
|
+
openingTopRadii: [grow(radii.topLeft), grow(radii.topRight)],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -8,6 +8,12 @@ export type OpeningCutoutRect = {
|
|
|
8
8
|
top: number;
|
|
9
9
|
};
|
|
10
10
|
export declare function getOpeningCutoutProxyDepth(wallThickness: number): number;
|
|
11
|
+
type CornerRadii = {
|
|
12
|
+
topLeft: number;
|
|
13
|
+
topRight: number;
|
|
14
|
+
bottomRight: number;
|
|
15
|
+
bottomLeft: number;
|
|
16
|
+
};
|
|
11
17
|
/**
|
|
12
18
|
* Pure cutout profile for a shaped door / window opening. `rect` is in
|
|
13
19
|
* the caller's coordinate frame — the wall CSG pipeline passes wall-local
|
|
@@ -28,4 +34,7 @@ export declare function hasFlatOpeningCutoutBottom(opening: OpeningCutoutNode):
|
|
|
28
34
|
* subtract a face exactly coplanar with the wall base.
|
|
29
35
|
*/
|
|
30
36
|
export declare function getOpeningCutoutBottomPadding(opening: OpeningCutoutNode, bottom: number): number;
|
|
37
|
+
/** The corner radii a rounded opening's cutout is cut with, clamped to fit `width` × `height`. */
|
|
38
|
+
export declare function getRoundedOpeningRadii(opening: OpeningCutoutNode, width: number, height: number): CornerRadii;
|
|
39
|
+
export {};
|
|
31
40
|
//# sourceMappingURL=opening-cutout-geometry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opening-cutout-geometry.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/opening-cutout-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"opening-cutout-geometry.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/opening-cutout-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,UAAU,CAAA;AAErD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAYD,wBAAgB,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,iBAAiB,GACtB,KAAK,CAAC,KAAK,CAyCb;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,KAAK,CAAC,cAAc,CAqBtB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAW9E;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhG;AAED,kGAAkG;AAClG,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,WAAW,CAyCb"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getDoorOpeningSize } from '@pascal-app/core';
|
|
1
2
|
import * as THREE from 'three';
|
|
2
3
|
// The cutout proxy doubles as the invisible raycast hit target for an opening:
|
|
3
4
|
// centered on the wall and extending past both faces so it wins the scene
|
|
@@ -22,7 +23,9 @@ export function buildOpeningCutoutShape(opening, rect) {
|
|
|
22
23
|
const width = Math.max(right - left, 1e-6);
|
|
23
24
|
const height = Math.max(top - bottom, 1e-6);
|
|
24
25
|
const shape = new THREE.Shape();
|
|
25
|
-
|
|
26
|
+
const size = getDoorOpeningSize(opening);
|
|
27
|
+
const fixedLights = size.width !== opening.width || size.height !== opening.height;
|
|
28
|
+
if (!fixedLights && opening.openingShape === 'arch') {
|
|
26
29
|
const halfWidth = width / 2;
|
|
27
30
|
const centerX = (left + right) / 2;
|
|
28
31
|
const archHeight = Math.min(Math.max(opening.archHeight ?? width / 2, 0.01), height);
|
|
@@ -41,7 +44,7 @@ export function buildOpeningCutoutShape(opening, rect) {
|
|
|
41
44
|
shape.closePath();
|
|
42
45
|
return shape;
|
|
43
46
|
}
|
|
44
|
-
if (opening.openingShape === 'rounded') {
|
|
47
|
+
if (!fixedLights && opening.openingShape === 'rounded') {
|
|
45
48
|
const radii = getRoundedOpeningRadii(opening, width, height);
|
|
46
49
|
applyRoundedOpeningShape(shape, left, right, bottom, top, radii);
|
|
47
50
|
return shape;
|
|
@@ -93,7 +96,8 @@ export function hasFlatOpeningCutoutBottom(opening) {
|
|
|
93
96
|
export function getOpeningCutoutBottomPadding(opening, bottom) {
|
|
94
97
|
return bottom < 0.005 && hasFlatOpeningCutoutBottom(opening) ? OPENING_CUTOUT_BOTTOM_PADDING : 0;
|
|
95
98
|
}
|
|
96
|
-
|
|
99
|
+
/** The corner radii a rounded opening's cutout is cut with, clamped to fit `width` × `height`. */
|
|
100
|
+
export function getRoundedOpeningRadii(opening, width, height) {
|
|
97
101
|
if (opening.type !== 'window') {
|
|
98
102
|
if (opening.openingRadiusMode === 'individual') {
|
|
99
103
|
const [topLeft = 0, topRight = 0] = opening.openingTopRadii ?? [0.15, 0.15];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAWL,KAAK,aAAa,EAClB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAU5C,OAAO,EAEL,KAAK,WAAW,EAMhB,KAAK,aAAa,EAInB,MAAM,qBAAqB,CAAA;AAE5B,KAAK,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;AAcxE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,EAAE,CAAA;AAE1C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,aAAa,EAAE,iBAAiB,CAAA;IAChC,eAAe,EAAE,iBAAiB,CAAA;IAClC,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAA;CAC1B;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM;QAAE,OAAO,EAAE,iBAAiB,CAAC;QAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAA;CACjE,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,aAAa,EACvB,QAAQ,CAAC,EAAE,OAAO,EAClB,WAAW,CAAC,EAAE,WAAW,EACzB,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,KAC3B,aAAa,CAAA;AAiSlB,wFAAwF;AACxF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAI9F;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAI1F;AA4DD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,EAClC,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAQR;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,EAClC,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,aAAa,CAmFf;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,GACjC,iBAAiB,CAUnB"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { getEffectiveWallFaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, parseMaterialColor, parseMaterialRef, resolveMaterial, resolveWallExteriorSide, resolveWallFaceChain, WALL_SLOT_DEFAULT, wallAssemblyExposedStructure,
|
|
1
|
+
import { getEffectiveWallFaceMaterial, getMaterialPresetByRef, getWallFaceDefault, getWallSurfaceMaterialSignature, parseMaterialColor, parseMaterialRef, resolveMaterial, resolveWallExteriorSide, resolveWallFaceChain, WALL_SLOT_DEFAULT, wallAssemblyExposedStructure, wallAssemblyFaceFinish, } from '@pascal-app/core';
|
|
2
2
|
import { Color } from 'three';
|
|
3
3
|
import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, uv, vec2 } from 'three/tsl';
|
|
4
4
|
import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
5
|
+
import { markHighlight, tintEmissive } from '../../lib/editor-highlight';
|
|
5
6
|
import { BLOCK_MASONRY, concreteMaterial, masonryMaterial, STRUCTURE_COLORS, } from '../../lib/foundation-materials';
|
|
6
7
|
import { baseMaterial, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, materialPresetRefSignature, resolveMaterialRef, resolveSlotDefaultRef, resolveSurfaceColor, } from '../../lib/materials';
|
|
7
8
|
const DEFAULT_WALL_COLOR = '#e9e7e3';
|
|
@@ -9,8 +10,7 @@ const WALL_HIGHLIGHT_PROFILES = {
|
|
|
9
10
|
delete: {
|
|
10
11
|
color: new Color('#dc2626'),
|
|
11
12
|
blend: 0.76,
|
|
12
|
-
|
|
13
|
-
emissiveIntensity: 0.46,
|
|
13
|
+
emissive: 0.92,
|
|
14
14
|
},
|
|
15
15
|
};
|
|
16
16
|
const wallMaterialCache = new Map();
|
|
@@ -43,11 +43,11 @@ function resolveWallSlotDefault(declaredDefault, shading) {
|
|
|
43
43
|
}
|
|
44
44
|
return createDefaultMaterial(slotDefault, 0.9, shading);
|
|
45
45
|
}
|
|
46
|
-
// The assembly
|
|
47
|
-
// +normal face; an unresolved side keeps the cladding on b, as legacy did)
|
|
46
|
+
// The assembly's finish for `face`: its cladding when `face` is the wall's exterior face (a is the
|
|
47
|
+
// +normal face; an unresolved side keeps the cladding on b, as legacy did), and the paint picked
|
|
48
|
+
// for a lining while drafting on a lined face.
|
|
48
49
|
function wallAssemblyFaceFinishRef(wallNode, face) {
|
|
49
|
-
|
|
50
|
-
return face === exteriorFace ? wallAssemblyFinishRef(wallNode) : null;
|
|
50
|
+
return wallAssemblyFaceFinish(wallNode, face);
|
|
51
51
|
}
|
|
52
52
|
// What `face` shows when it is the exterior face of a wall with no cladding over a bare masonry or
|
|
53
53
|
// concrete core: the structure itself (null otherwise, and for any face with a cladding).
|
|
@@ -57,7 +57,7 @@ function wallFaceExposedStructure(wallNode, face) {
|
|
|
57
57
|
const exteriorFace = resolveWallExteriorSide(wallNode) === 1 ? 'a' : 'b';
|
|
58
58
|
return face === exteriorFace ? wallAssemblyExposedStructure(wallNode) : null;
|
|
59
59
|
}
|
|
60
|
-
// The structure
|
|
60
|
+
// The bare structure (courses, joints and colours), made once:
|
|
61
61
|
// the block's bond runs along the face's world-planar U, the wall's own metres. Solid shading
|
|
62
62
|
// keeps to a flat colour, like every other wall finish.
|
|
63
63
|
const structureMaterials = new Map();
|
|
@@ -73,20 +73,22 @@ function exposedStructureMaterial(structure, shading) {
|
|
|
73
73
|
return material;
|
|
74
74
|
}
|
|
75
75
|
// Slot-first resolution for one wall face, matching every other paintable kind:
|
|
76
|
-
// node.slots[face] ref → legacy inline finish →
|
|
76
|
+
// node.slots[face] ref → legacy inline finish → the face's default.
|
|
77
77
|
// A dangling `scene:` ref (material deleted / copied across scenes) falls back
|
|
78
|
-
// to the
|
|
78
|
+
// to the face's default — it never blocks rendering (the dangling-ref rule).
|
|
79
79
|
function resolveWallFaceMaterial(wallNode, face, shading, sceneMaterials) {
|
|
80
80
|
const finish = resolveWallFaceChain(wallNode, face);
|
|
81
81
|
if (finish.kind === 'ref') {
|
|
82
82
|
return (resolveMaterialRef(finish.ref, sceneMaterials, shading) ??
|
|
83
|
-
resolveWallSlotDefault(
|
|
83
|
+
resolveWallSlotDefault(getWallFaceDefault(wallNode, face), shading));
|
|
84
84
|
}
|
|
85
85
|
if (finish.kind === 'legacy')
|
|
86
86
|
return getSurfaceVisibleMaterial(finish.spec, shading);
|
|
87
87
|
// No paint on this face: the wall ASSEMBLY's cladding (WS5) skins the
|
|
88
88
|
// exterior — a "2x6 lap siding" wall reads as lap siding without the user
|
|
89
|
-
// painting it. Painting the face still wins.
|
|
89
|
+
// painting it. Painting the face still wins. Any other unpainted face wears
|
|
90
|
+
// its default (`getWallFaceDefault`): the interior paint
|
|
91
|
+
// (`DEFAULT_INTERIOR_WALL_PAINT`) inside, raw drywall on the exterior side.
|
|
90
92
|
const assemblyRef = wallAssemblyFaceFinishRef(wallNode, face);
|
|
91
93
|
const exposed = assemblyRef ? null : wallFaceExposedStructure(wallNode, face);
|
|
92
94
|
if (exposed)
|
|
@@ -116,11 +118,15 @@ function refMaterialSignature(ref, sceneMaterials) {
|
|
|
116
118
|
// Falls back to the legacy signature when the face has no slot ref.
|
|
117
119
|
function wallFaceMaterialSignature(wallNode, face, sceneMaterials) {
|
|
118
120
|
const ref = wallNode.slots?.[face];
|
|
121
|
+
// The face's default follows its side, so a room detected outside it re-skins
|
|
122
|
+
// an unpainted face (and the fallback of a dangling ref).
|
|
123
|
+
const fallback = getWallFaceDefault(wallNode, face);
|
|
119
124
|
if (ref)
|
|
120
|
-
return refMaterialSignature(ref, sceneMaterials);
|
|
125
|
+
return JSON.stringify({ ref: refMaterialSignature(ref, sceneMaterials), fallback });
|
|
121
126
|
const spec = getEffectiveWallFaceMaterial(wallNode, face);
|
|
122
127
|
const assemblyRef = wallAssemblyFaceFinishRef(wallNode, face);
|
|
123
128
|
return JSON.stringify({
|
|
129
|
+
fallback,
|
|
124
130
|
legacy: getWallSurfaceMaterialSignature(spec),
|
|
125
131
|
preset: spec.materialPreset ? materialPresetRefSignature(spec.materialPreset) : null,
|
|
126
132
|
// Changing the assembly's cladding must re-skin the face.
|
|
@@ -178,16 +184,9 @@ function createHighlightedWallMaterial(material, kind) {
|
|
|
178
184
|
if ('color' in highlightedMaterial && highlightedMaterial.color) {
|
|
179
185
|
highlightedMaterial.color = getHighlightedColor(highlightedMaterial.color, kind);
|
|
180
186
|
}
|
|
181
|
-
|
|
182
|
-
highlightedMaterial.emissive = highlightedMaterial.emissive
|
|
183
|
-
.clone()
|
|
184
|
-
.lerp(profile.color, profile.emissiveBlend);
|
|
185
|
-
}
|
|
186
|
-
if ('emissiveIntensity' in highlightedMaterial) {
|
|
187
|
-
highlightedMaterial.emissiveIntensity = Math.max(highlightedMaterial.emissiveIntensity ?? 0, profile.emissiveIntensity);
|
|
188
|
-
}
|
|
187
|
+
tintEmissive(highlightedMaterial, profile.color, profile.emissive);
|
|
189
188
|
highlightedMaterial.needsUpdate = true;
|
|
190
|
-
return highlightedMaterial;
|
|
189
|
+
return markHighlight(highlightedMaterial);
|
|
191
190
|
}
|
|
192
191
|
// Light selection highlight for walls (walls are excluded from the generic
|
|
193
192
|
// editor selection highlight, so they need their own). Adds a gentle indigo
|
|
@@ -199,15 +198,13 @@ function createHighlightedWallMaterial(material, kind) {
|
|
|
199
198
|
// cache keyed by the source `.map` and rebuild when it changes (self-heals
|
|
200
199
|
// once the texture lands).
|
|
201
200
|
const SELECTION_HIGHLIGHT_COLOR = new Color('#818cf8');
|
|
202
|
-
const
|
|
203
|
-
const SELECTION_EMISSIVE_INTENSITY = 0.12;
|
|
201
|
+
const SELECTION_EMISSIVE = 0.12;
|
|
204
202
|
// Softer sibling of the selection glow, for HOVERING a hidden wall (the
|
|
205
203
|
// X-ray nearest-first selection made hidden walls hover targets; without a
|
|
206
204
|
// material affordance the only thing lighting up was the furniture behind
|
|
207
205
|
// them). Same indigo so hover reads as "this will select", weaker so a
|
|
208
206
|
// hovered-then-selected wall still steps up on click.
|
|
209
|
-
const
|
|
210
|
-
const HOVER_EMISSIVE_INTENSITY = 0.2;
|
|
207
|
+
const HOVER_EMISSIVE = 0.08;
|
|
211
208
|
const SELECTION_TEXTURE_MAP_KEYS = [
|
|
212
209
|
'map',
|
|
213
210
|
'normalMap',
|
|
@@ -222,7 +219,7 @@ const SELECTION_TEXTURE_MAP_KEYS = [
|
|
|
222
219
|
];
|
|
223
220
|
const selectionHighlightCache = new WeakMap();
|
|
224
221
|
const hoverHighlightCache = new WeakMap();
|
|
225
|
-
function getEmissiveHighlightMaterial(base, cache,
|
|
222
|
+
function getEmissiveHighlightMaterial(base, cache, emissive) {
|
|
226
223
|
const baseMap = base.map ?? null;
|
|
227
224
|
const cached = cache.get(base);
|
|
228
225
|
if (cached && cached.map === baseMap)
|
|
@@ -235,19 +232,15 @@ function getEmissiveHighlightMaterial(base, cache, emissiveBlend, emissiveIntens
|
|
|
235
232
|
if (src[key])
|
|
236
233
|
dst[key] = src[key];
|
|
237
234
|
}
|
|
238
|
-
|
|
239
|
-
clone.emissive = clone.emissive.clone().lerp(SELECTION_HIGHLIGHT_COLOR, emissiveBlend);
|
|
240
|
-
}
|
|
241
|
-
if ('emissiveIntensity' in clone) {
|
|
242
|
-
clone.emissiveIntensity = Math.max(clone.emissiveIntensity ?? 0, emissiveIntensity);
|
|
243
|
-
}
|
|
235
|
+
tintEmissive(clone, SELECTION_HIGHLIGHT_COLOR, emissive);
|
|
244
236
|
clone.needsUpdate = true;
|
|
237
|
+
markHighlight(clone);
|
|
245
238
|
cache.set(base, { clone, map: baseMap });
|
|
246
239
|
return clone;
|
|
247
240
|
}
|
|
248
241
|
/** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
|
|
249
242
|
export function getSelectionHighlightMaterials(materials) {
|
|
250
|
-
return materials.map((material) => getEmissiveHighlightMaterial(material, selectionHighlightCache,
|
|
243
|
+
return materials.map((material) => getEmissiveHighlightMaterial(material, selectionHighlightCache, SELECTION_EMISSIVE));
|
|
251
244
|
}
|
|
252
245
|
/**
|
|
253
246
|
* Softer hover sibling of the selection variant — the affordance for a
|
|
@@ -255,7 +248,7 @@ export function getSelectionHighlightMaterials(materials) {
|
|
|
255
248
|
* film so the wall the click would select reads under the cursor).
|
|
256
249
|
*/
|
|
257
250
|
export function getHoverHighlightMaterials(materials) {
|
|
258
|
-
return materials.map((material) => getEmissiveHighlightMaterial(material, hoverHighlightCache,
|
|
251
|
+
return materials.map((material) => getEmissiveHighlightMaterial(material, hoverHighlightCache, HOVER_EMISSIVE));
|
|
259
252
|
}
|
|
260
253
|
function createInvisibleWallMaterial(color, shading) {
|
|
261
254
|
const material = shading === 'solid'
|
|
@@ -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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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildWallFinishLayout, containsPoint, cornerPair, DEFAULT_LEVEL_HEIGHT, getAdjacentWallIds, getEffectiveNode, getOpeningWallCut, getWallBodyCenterOffset, getWallCurveFrameAt, getWallCurveLength, getWallFaceOffsets, getWallLevelZones, getWallMiterBoundaryPoints, getWallPlaneTop, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, isCutterName, pointToKey, resolveCutterHost, resolveLevelId, resolveWallFaceBottom, resolveWallFinish, resolveWallTop, sceneRegistry, spatialGridManager, terrainSupportLift, useLiveNodeOverrides, useLiveTransforms, useScene, WALL_SURFACE_SLOT_DEFAULTS, wallFinishMaterialIndex, zoneHasWallFinish, } from '@pascal-app/core';
|
|
1
|
+
import { buildWallFinishLayout, clampWallEndHeightOffset, containsPoint, cornerPair, DEFAULT_LEVEL_HEIGHT, getAdjacentWallIds, getDoorOpeningSize, getEffectiveNode, getOpeningWallCut, getWallBodyCenterOffset, getWallCurveFrameAt, getWallCurveLength, getWallFaceOffsets, getWallLevelZones, getWallMiterBoundaryPoints, getWallPlaneTop, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, isCutterName, MIN_WALL_END_HEIGHT, openingHostFacing, pointToKey, resolveCutterHost, resolveLevelId, resolveWallFaceBottom, resolveWallFinish, resolveWallTop, sceneRegistry, spatialGridManager, terrainSupportLift, useLiveNodeOverrides, useLiveTransforms, useScene, WALL_SURFACE_SLOT_DEFAULTS, wallFinishMaterialIndex, zoneHasWallFinish, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
import { useEffect } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
@@ -17,6 +17,7 @@ import { drainStats, endInitialBuild, initiallyBuiltWalls, isWallInitialBuildAct
|
|
|
17
17
|
import { getWallFaceBaseAt, } from './wall-finish-data';
|
|
18
18
|
import { sweepUnbuiltWalls, WALL_PLACEHOLDER_SWEEP_INTERVAL } from './wall-placeholder-sweep';
|
|
19
19
|
import { notifyWallRebuilt } from './wall-rebuild-notifications';
|
|
20
|
+
import { repairWallTJunctions } from './wall-t-junctions';
|
|
20
21
|
export { isWallInitialBuildActive } from './wall-build-lifecycle';
|
|
21
22
|
export { drainRebuiltWalls } from './wall-rebuild-notifications';
|
|
22
23
|
// Reusable CSG evaluator for better performance
|
|
@@ -332,6 +333,9 @@ function interpolateSplitVertex(a, b, t) {
|
|
|
332
333
|
z: a.z + (b.z - a.z) * t,
|
|
333
334
|
};
|
|
334
335
|
}
|
|
336
|
+
function compareSplitVertices(a, b) {
|
|
337
|
+
return a.x - b.x || a.y - b.y || a.z - b.z;
|
|
338
|
+
}
|
|
335
339
|
function clipPolygonByPlane(polygon, axis, plane, keepBelow) {
|
|
336
340
|
const out = [];
|
|
337
341
|
if (polygon.length === 0)
|
|
@@ -345,9 +349,12 @@ function clipPolygonByPlane(polygon, axis, plane, keepBelow) {
|
|
|
345
349
|
const currentInside = isInside(current);
|
|
346
350
|
const previousInside = isInside(previous);
|
|
347
351
|
if (currentInside !== previousInside) {
|
|
348
|
-
|
|
352
|
+
// Interpolate from the same endpoint whichever way the edge is walked: the
|
|
353
|
+
// two triangles sharing it then get a bit-identical seam vertex.
|
|
354
|
+
const [from, to] = compareSplitVertices(previous, current) <= 0 ? [previous, current] : [current, previous];
|
|
355
|
+
const denom = to[axis] - from[axis];
|
|
349
356
|
if (Math.abs(denom) > WALL_BAND_SPLIT_EPSILON) {
|
|
350
|
-
out.push(interpolateSplitVertex(
|
|
357
|
+
out.push(interpolateSplitVertex(from, to, (plane - from[axis]) / denom));
|
|
351
358
|
}
|
|
352
359
|
}
|
|
353
360
|
if (currentInside)
|
|
@@ -1174,10 +1181,22 @@ function mergeWallTerrainFill(body, fills, wall, boundaryEdges, finish) {
|
|
|
1174
1181
|
bodyGeometry.dispose();
|
|
1175
1182
|
for (const fill of splitFills)
|
|
1176
1183
|
fill.dispose();
|
|
1177
|
-
merged
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1184
|
+
return finishWallFaces(merged, wall, boundaryEdges, finish);
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Classifies the faces into material groups, then removes the T-junctions that
|
|
1188
|
+
* CSG cuts, finish-band splits and fill seams leave behind (they crack open
|
|
1189
|
+
* once a bake quantizes positions). Classification runs first, on the source
|
|
1190
|
+
* triangles, so the repair's sliver pieces inherit their material.
|
|
1191
|
+
*/
|
|
1192
|
+
function finishWallFaces(geometry, wall, boundaryEdges, finish) {
|
|
1193
|
+
geometry.computeVertexNormals();
|
|
1194
|
+
assignWallMaterialGroups(geometry, wall, boundaryEdges, finish);
|
|
1195
|
+
const repaired = repairWallTJunctions(geometry);
|
|
1196
|
+
if (repaired !== geometry)
|
|
1197
|
+
repaired.computeVertexNormals();
|
|
1198
|
+
ensureRenderableGeometryAttributes(repaired);
|
|
1199
|
+
return repaired;
|
|
1181
1200
|
}
|
|
1182
1201
|
/** Wall-local x of a support-segment parameter (curve parameter on arcs). */
|
|
1183
1202
|
function wallStationX(wall, t, length) {
|
|
@@ -1208,6 +1227,41 @@ function buildWallFinishContext(wall, layout, faceBase, slabElevation) {
|
|
|
1208
1227
|
.sort((left, right) => left.start - right.start);
|
|
1209
1228
|
return { layout, faceBase: { a: runs(faceBase.a), b: runs(faceBase.b) }, refs, foundation };
|
|
1210
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Wall-local Y of the sloped top at `x`. Mitered corners sample `x` outside
|
|
1232
|
+
* [0, L], where the line can fall below the wall base; the top never drops
|
|
1233
|
+
* under {@link MIN_WALL_END_HEIGHT} (or the flat height, if lower).
|
|
1234
|
+
*/
|
|
1235
|
+
export function wallTopYAt(wallTop, x) {
|
|
1236
|
+
const { height, slope } = wallTop;
|
|
1237
|
+
if (slope === 0)
|
|
1238
|
+
return height;
|
|
1239
|
+
return Math.max(height + slope * x, Math.min(height, MIN_WALL_END_HEIGHT));
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Tilts a wall's top edge along its length so the `end` side sits taller (or
|
|
1243
|
+
* shorter) than the `start` side — e.g. a knee wall following a single-pitch
|
|
1244
|
+
* roof slope — instead of requiring a non-rectangular footprint. Only
|
|
1245
|
+
* vertices sitting exactly at the flat extruded top (`topY`) move.
|
|
1246
|
+
*
|
|
1247
|
+
* Evaluates the linear plane equation `slope * localX` continuously across
|
|
1248
|
+
* all top vertices (including mitered corner vertices extending beyond [0, L])
|
|
1249
|
+
* so the extruded top face remains a single coplanar surface without corner
|
|
1250
|
+
* creases or triangulation folds — except where {@link wallTopYAt} holds an
|
|
1251
|
+
* extended miter vertex above the base.
|
|
1252
|
+
*/
|
|
1253
|
+
function applyWallEndHeightSlope(geometry, wallTop) {
|
|
1254
|
+
const { height: topY, slope } = wallTop;
|
|
1255
|
+
if (slope === 0)
|
|
1256
|
+
return;
|
|
1257
|
+
const position = geometry.getAttribute('position');
|
|
1258
|
+
for (let i = 0; i < position.count; i++) {
|
|
1259
|
+
if (Math.abs(position.getY(i) - topY) > 1e-4)
|
|
1260
|
+
continue;
|
|
1261
|
+
position.setY(i, wallTopYAt(wallTop, position.getX(i)));
|
|
1262
|
+
}
|
|
1263
|
+
position.needsUpdate = true;
|
|
1264
|
+
}
|
|
1211
1265
|
export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabElevation = 0, baseElevation = slabElevation, baseSegments = [
|
|
1212
1266
|
{ start: 0, end: 1, elevation: baseElevation },
|
|
1213
1267
|
], storeyHeight = DEFAULT_LEVEL_HEIGHT, terrainBottomAt, faceBase, finishLayout = buildWallFinishLayout(wallNode, []), sceneNodes = useScene.getState().nodes) {
|
|
@@ -1217,6 +1271,7 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
1217
1271
|
const level = sceneNodes[levelId];
|
|
1218
1272
|
const authoredStoreyHeight = level?.type === 'level' ? (level.height ?? null) : null;
|
|
1219
1273
|
const topElevation = resolveWallTop(wallNode, storeyHeight, slabElevation, authoredStoreyHeight);
|
|
1274
|
+
const effectiveWallHeight = topElevation - slabElevation;
|
|
1220
1275
|
const faceDatum = faceBase;
|
|
1221
1276
|
if (faceBase)
|
|
1222
1277
|
faceBase = {
|
|
@@ -1247,6 +1302,10 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
1247
1302
|
if (L < 1e-9) {
|
|
1248
1303
|
return new THREE.BufferGeometry();
|
|
1249
1304
|
}
|
|
1305
|
+
const wallTop = {
|
|
1306
|
+
height: effectiveWallHeight,
|
|
1307
|
+
slope: clampWallEndHeightOffset(wallNode.endHeightOffset, effectiveWallHeight) / L,
|
|
1308
|
+
};
|
|
1250
1309
|
const boundaryPoints = getWallMiterBoundaryPoints(wallNode, miterData);
|
|
1251
1310
|
const polyPoints = isCurvedWall(wallNode)
|
|
1252
1311
|
? getWallSurfacePolygon(wallNode, 24, insetCurvedWallBoundaryPointsFor3D(wallNode, boundaryPoints, miterData) ?? undefined)
|
|
@@ -1293,10 +1352,10 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
1293
1352
|
depth: height,
|
|
1294
1353
|
bevelEnabled: false,
|
|
1295
1354
|
});
|
|
1296
|
-
// Rotate so extrusion direction (Z) becomes height direction (Y)
|
|
1297
1355
|
geometry.rotateX(-Math.PI / 2);
|
|
1298
1356
|
if (Math.abs(localBottom) > 1e-9)
|
|
1299
1357
|
geometry.translate(0, localBottom, 0);
|
|
1358
|
+
applyWallEndHeightSlope(geometry, wallTop);
|
|
1300
1359
|
geometry.computeVertexNormals();
|
|
1301
1360
|
assignWallMaterialGroups(geometry, wallNode, boundaryEdges, finish);
|
|
1302
1361
|
ensureRenderableGeometryAttributes(geometry);
|
|
@@ -1412,13 +1471,10 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
1412
1471
|
}
|
|
1413
1472
|
const cutoutBrushes = [
|
|
1414
1473
|
...baseProfileCutouts,
|
|
1415
|
-
...collectCutoutBrushes(wallNode, childrenNodes, thickness, faceDatum, slabElevation, sceneNodes, baseSegments),
|
|
1474
|
+
...collectCutoutBrushes(wallNode, childrenNodes, thickness, wallTop, faceDatum, slabElevation, sceneNodes, baseSegments),
|
|
1416
1475
|
];
|
|
1417
1476
|
if (cutoutBrushes.length === 0) {
|
|
1418
|
-
const splitGeometry = splitGeometryAtPlanes(geometry, finishPlanes.y, finishPlanes.x);
|
|
1419
|
-
splitGeometry.computeVertexNormals();
|
|
1420
|
-
assignWallMaterialGroups(splitGeometry, wallNode, boundaryEdges, finish);
|
|
1421
|
-
ensureRenderableGeometryAttributes(splitGeometry);
|
|
1477
|
+
const splitGeometry = finishWallFaces(splitGeometryAtPlanes(geometry, finishPlanes.y, finishPlanes.x), wallNode, boundaryEdges, finish);
|
|
1422
1478
|
return withWallFinishData(mergeWallTerrainFill(splitGeometry, belowBaseFills, wallNode, boundaryEdges, finish), finish);
|
|
1423
1479
|
}
|
|
1424
1480
|
// Create wall brush from geometry
|
|
@@ -1462,10 +1518,7 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
1462
1518
|
csgGeometry(brush).dispose();
|
|
1463
1519
|
}
|
|
1464
1520
|
const resultGeometry = csgGeometry(resultBrush);
|
|
1465
|
-
const splitResultGeometry = splitGeometryAtPlanes(resultGeometry, finishPlanes.y, finishPlanes.x);
|
|
1466
|
-
splitResultGeometry.computeVertexNormals();
|
|
1467
|
-
assignWallMaterialGroups(splitResultGeometry, wallNode, boundaryEdges, finish);
|
|
1468
|
-
ensureRenderableGeometryAttributes(splitResultGeometry);
|
|
1521
|
+
const splitResultGeometry = finishWallFaces(splitGeometryAtPlanes(resultGeometry, finishPlanes.y, finishPlanes.x), wallNode, boundaryEdges, finish);
|
|
1469
1522
|
return withWallFinishData(mergeWallTerrainFill(splitResultGeometry, belowBaseFills, wallNode, boundaryEdges, finish), finish);
|
|
1470
1523
|
}
|
|
1471
1524
|
function withWallFinishData(geometry, finish) {
|
|
@@ -1514,7 +1567,7 @@ function authoredCutoutBrush(cutoutMesh, wallMatrixInverse, wallThickness, wallN
|
|
|
1514
1567
|
computeGeometryBoundsTree(geometry);
|
|
1515
1568
|
return new Brush(geometry);
|
|
1516
1569
|
}
|
|
1517
|
-
function collectCutoutBrushes(wallNode, childrenNodes, wallThickness, faceBase, elevation = 0, sceneNodes = useScene.getState().nodes, baseSegments = [{ start: 0, end: 1, elevation }]) {
|
|
1570
|
+
function collectCutoutBrushes(wallNode, childrenNodes, wallThickness, wallTop, faceBase, elevation = 0, sceneNodes = useScene.getState().nodes, baseSegments = [{ start: 0, end: 1, elevation }]) {
|
|
1518
1571
|
const brushes = [];
|
|
1519
1572
|
const wallMesh = sceneRegistry.nodes.get(wallNode.id);
|
|
1520
1573
|
if (!wallMesh)
|
|
@@ -1571,7 +1624,7 @@ function collectCutoutBrushes(wallNode, childrenNodes, wallThickness, faceBase,
|
|
|
1571
1624
|
child.position[1] + cut.datum - elevation,
|
|
1572
1625
|
child.position[2],
|
|
1573
1626
|
],
|
|
1574
|
-
}, wallThickness, getWallBodyCenterOffset(wallNode), cut.bottom - elevation, child.type === 'window' ? cornerPair(sceneNodes, child)?.end : undefined));
|
|
1627
|
+
}, wallThickness, getWallBodyCenterOffset(wallNode), cut.bottom - elevation, child.type === 'window' ? cornerPair(sceneNodes, child)?.end : undefined, wallTop));
|
|
1575
1628
|
continue;
|
|
1576
1629
|
}
|
|
1577
1630
|
if (cut.band.length < 3)
|
|
@@ -1582,8 +1635,9 @@ function collectCutoutBrushes(wallNode, childrenNodes, wallThickness, faceBase,
|
|
|
1582
1635
|
return new THREE.Vector2(dx * Math.cos(angle) + dz * Math.sin(angle), dx * Math.sin(angle) - dz * Math.cos(angle));
|
|
1583
1636
|
});
|
|
1584
1637
|
const padding = getOpeningCutoutBottomPadding(child, cut.bottom - elevation);
|
|
1638
|
+
const top = Math.min(cut.top - elevation, Math.min(...points.map((point) => wallTopYAt(wallTop, point.x))));
|
|
1585
1639
|
const geometry = new THREE.ExtrudeGeometry(new THREE.Shape(points), {
|
|
1586
|
-
depth:
|
|
1640
|
+
depth: top - (cut.bottom - elevation) + padding,
|
|
1587
1641
|
bevelEnabled: false,
|
|
1588
1642
|
});
|
|
1589
1643
|
geometry.rotateX(-Math.PI / 2);
|
|
@@ -1668,16 +1722,23 @@ function collectCutoutBrushes(wallNode, childrenNodes, wallThickness, faceBase,
|
|
|
1668
1722
|
* cut only takes this wall's own geometry, so running past it touches nothing else.
|
|
1669
1723
|
*/
|
|
1670
1724
|
const CORNER_CUT_REACH = 6;
|
|
1671
|
-
function createOpeningCutoutBrush(opening, wallThickness, centerOffset, cutBottom, cornerEnd) {
|
|
1672
|
-
const
|
|
1725
|
+
function createOpeningCutoutBrush(opening, wallThickness, centerOffset, cutBottom, cornerEnd, wallTop) {
|
|
1726
|
+
const size = getDoorOpeningSize(opening);
|
|
1727
|
+
const centerX = opening.position[0] - size.leafOffsetX * openingHostFacing(opening);
|
|
1728
|
+
const halfWidth = size.width / 2;
|
|
1673
1729
|
const bottom = cutBottom ?? opening.position[1] - opening.height / 2;
|
|
1674
1730
|
const bottomPadding = getOpeningCutoutBottomPadding(opening, bottom);
|
|
1675
1731
|
const reach = CORNER_CUT_REACH * wallThickness;
|
|
1732
|
+
const left = centerX - halfWidth;
|
|
1733
|
+
const right = centerX + halfWidth;
|
|
1734
|
+
const openingTop = opening.position[1] - opening.height / 2 + size.height;
|
|
1676
1735
|
const geometry = buildOpeningCutoutGeometry(opening, {
|
|
1677
|
-
left:
|
|
1678
|
-
right:
|
|
1736
|
+
left: left - (cornerEnd === 'start' ? reach : 0),
|
|
1737
|
+
right: right + (cornerEnd === 'end' ? reach : 0),
|
|
1679
1738
|
bottom: bottom - bottomPadding,
|
|
1680
|
-
top:
|
|
1739
|
+
top: wallTop
|
|
1740
|
+
? Math.min(openingTop, wallTopYAt(wallTop, wallTop.slope > 0 ? left : right))
|
|
1741
|
+
: openingTop,
|
|
1681
1742
|
}, wallThickness * 2, wallThickness);
|
|
1682
1743
|
if (centerOffset !== 0)
|
|
1683
1744
|
geometry.translate(0, 0, centerOffset);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Splits every triangle whose edge carries another triangle's vertex, so the
|
|
4
|
+
* neighbours share that vertex instead of meeting at a T-junction. CSG cuts and
|
|
5
|
+
* finish-band splits leave T-junctions on wall faces: they look closed at full
|
|
6
|
+
* float precision, but crack open into dotted light leaks once a GLB bake
|
|
7
|
+
* quantizes positions.
|
|
8
|
+
*
|
|
9
|
+
* Each piece keeps its source triangle's material group, so the repair never
|
|
10
|
+
* reclassifies a face. Geometry without T-junctions is returned untouched;
|
|
11
|
+
* otherwise the result is non-indexed and position-only (callers recompute
|
|
12
|
+
* normals) and the input is disposed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function repairWallTJunctions(geometry: THREE.BufferGeometry): THREE.BufferGeometry;
|
|
15
|
+
//# sourceMappingURL=wall-t-junctions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-t-junctions.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-t-junctions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAW9B;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,CAiHzF"}
|