@pascal-app/viewer 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/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-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 +3 -1
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +289 -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 +30 -6
- 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/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/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 +236 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +12 -1
- package/dist/index.d.ts +21 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -7
- 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 +53 -0
- package/dist/lib/daylight/room-scene.d.ts +54 -0
- package/dist/lib/daylight/room-scene.d.ts.map +1 -0
- package/dist/lib/daylight/room-scene.js +356 -0
- package/dist/lib/daylight/rooms.d.ts +262 -0
- package/dist/lib/daylight/rooms.d.ts.map +1 -0
- package/dist/lib/daylight/rooms.js +1223 -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/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/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/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 +10 -8
- 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/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 +72 -15
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +103 -18
- 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/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 +477 -105
- 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.map +1 -1
- package/dist/systems/wall/wall-system.js +34 -19
- 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'
|
|
@@ -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, containsPoint, cornerPair, DEFAULT_LEVEL_HEIGHT, getAdjacentWallIds, getDoorOpeningSize, getEffectiveNode, getOpeningWallCut, getWallBodyCenterOffset, getWallCurveFrameAt, getWallCurveLength, getWallFaceOffsets, getWallLevelZones, getWallMiterBoundaryPoints, getWallPlaneTop, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, isCutterName, 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) {
|
|
@@ -1415,10 +1434,7 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
1415
1434
|
...collectCutoutBrushes(wallNode, childrenNodes, thickness, faceDatum, slabElevation, sceneNodes, baseSegments),
|
|
1416
1435
|
];
|
|
1417
1436
|
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);
|
|
1437
|
+
const splitGeometry = finishWallFaces(splitGeometryAtPlanes(geometry, finishPlanes.y, finishPlanes.x), wallNode, boundaryEdges, finish);
|
|
1422
1438
|
return withWallFinishData(mergeWallTerrainFill(splitGeometry, belowBaseFills, wallNode, boundaryEdges, finish), finish);
|
|
1423
1439
|
}
|
|
1424
1440
|
// Create wall brush from geometry
|
|
@@ -1462,10 +1478,7 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
|
|
|
1462
1478
|
csgGeometry(brush).dispose();
|
|
1463
1479
|
}
|
|
1464
1480
|
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);
|
|
1481
|
+
const splitResultGeometry = finishWallFaces(splitGeometryAtPlanes(resultGeometry, finishPlanes.y, finishPlanes.x), wallNode, boundaryEdges, finish);
|
|
1469
1482
|
return withWallFinishData(mergeWallTerrainFill(splitResultGeometry, belowBaseFills, wallNode, boundaryEdges, finish), finish);
|
|
1470
1483
|
}
|
|
1471
1484
|
function withWallFinishData(geometry, finish) {
|
|
@@ -1669,15 +1682,17 @@ function collectCutoutBrushes(wallNode, childrenNodes, wallThickness, faceBase,
|
|
|
1669
1682
|
*/
|
|
1670
1683
|
const CORNER_CUT_REACH = 6;
|
|
1671
1684
|
function createOpeningCutoutBrush(opening, wallThickness, centerOffset, cutBottom, cornerEnd) {
|
|
1672
|
-
const
|
|
1685
|
+
const size = getDoorOpeningSize(opening);
|
|
1686
|
+
const centerX = opening.position[0] - size.leafOffsetX * openingHostFacing(opening);
|
|
1687
|
+
const halfWidth = size.width / 2;
|
|
1673
1688
|
const bottom = cutBottom ?? opening.position[1] - opening.height / 2;
|
|
1674
1689
|
const bottomPadding = getOpeningCutoutBottomPadding(opening, bottom);
|
|
1675
1690
|
const reach = CORNER_CUT_REACH * wallThickness;
|
|
1676
1691
|
const geometry = buildOpeningCutoutGeometry(opening, {
|
|
1677
|
-
left:
|
|
1678
|
-
right:
|
|
1692
|
+
left: centerX - halfWidth - (cornerEnd === 'start' ? reach : 0),
|
|
1693
|
+
right: centerX + halfWidth + (cornerEnd === 'end' ? reach : 0),
|
|
1679
1694
|
bottom: bottom - bottomPadding,
|
|
1680
|
-
top: opening.position[1]
|
|
1695
|
+
top: opening.position[1] - opening.height / 2 + size.height,
|
|
1681
1696
|
}, wallThickness * 2, wallThickness);
|
|
1682
1697
|
if (centerOffset !== 0)
|
|
1683
1698
|
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"}
|