@pascal-app/viewer 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/error-boundary.d.ts +6 -0
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +6 -0
- package/dist/components/renderers/node-renderer.d.ts +5 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +5 -2
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/parametric-node-renderer.js +7 -4
- package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
- package/dist/components/viewer/bvh-ecctrl.js +552 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +39 -4
- package/dist/components/viewer/glb-interactive.d.ts +41 -0
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
- package/dist/components/viewer/glb-interactive.js +394 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
- package/dist/components/viewer/glb-reference-nodes.js +80 -0
- package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
- package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
- package/dist/components/viewer/glb-replace-instances.js +61 -0
- package/dist/components/viewer/glb-scene.d.ts +54 -0
- package/dist/components/viewer/glb-scene.d.ts.map +1 -0
- package/dist/components/viewer/glb-scene.js +920 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +12 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/glb-walkthrough-controller.js +357 -0
- package/dist/components/viewer/index.d.ts +19 -2
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +104 -12
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +36 -5
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +17 -2
- package/dist/components/viewer/post-processing.d.ts +7 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +179 -24
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +6 -4
- package/dist/components/viewer/scene-bvh.d.ts +2 -1
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
- package/dist/components/viewer/scene-bvh.js +3 -0
- package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
- package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.test.js +13 -0
- package/dist/components/viewer/scene-environment.d.ts +2 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -0
- package/dist/components/viewer/scene-environment.js +79 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +18 -10
- package/dist/components/viewer/walkthrough-controls.d.ts +1 -0
- package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.js +20 -0
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +3 -22
- package/dist/hooks/use-node-events.d.ts +1 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +22 -14
- package/dist/index.d.ts +19 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -8
- package/dist/lib/backdrop.d.ts +33 -0
- package/dist/lib/backdrop.d.ts.map +1 -0
- package/dist/lib/backdrop.js +92 -0
- package/dist/lib/box-uv.d.ts +13 -0
- package/dist/lib/box-uv.d.ts.map +1 -0
- package/dist/lib/box-uv.js +32 -0
- package/dist/lib/csg-utils.d.ts +3 -9
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +65 -5
- package/dist/lib/csg-utils.test.d.ts +2 -0
- package/dist/lib/csg-utils.test.d.ts.map +1 -0
- package/dist/lib/csg-utils.test.js +45 -0
- package/dist/lib/edge-style.d.ts +1 -0
- package/dist/lib/edge-style.d.ts.map +1 -1
- package/dist/lib/edge-style.js +26 -6
- package/dist/lib/ink-edges.d.ts +1 -1
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ink-edges.js +13 -7
- package/dist/lib/ktx2-loader.d.ts +43 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -0
- package/dist/lib/ktx2-loader.js +136 -0
- package/dist/lib/layers.d.ts +10 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +10 -0
- package/dist/lib/materials.d.ts +14 -1
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +103 -26
- package/dist/lib/scene-themes.d.ts +1 -0
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +25 -16
- package/dist/lib/shadow-only.d.ts +4 -0
- package/dist/lib/shadow-only.d.ts.map +1 -0
- package/dist/lib/shadow-only.js +36 -0
- package/dist/lib/tsl-compat.d.ts +12 -0
- package/dist/lib/tsl-compat.d.ts.map +1 -0
- package/dist/lib/tsl-compat.js +13 -0
- package/dist/store/use-viewer.d.ts +60 -18
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +175 -3
- package/dist/store/use-viewer.test.d.ts +2 -0
- package/dist/store/use-viewer.test.d.ts.map +1 -0
- package/dist/store/use-viewer.test.js +42 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +4 -1
- package/dist/systems/door/door-animation.test.d.ts +2 -0
- package/dist/systems/door/door-animation.test.d.ts.map +1 -0
- package/dist/systems/door/door-animation.test.js +51 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
- package/dist/systems/door/door-floor-alignment.test.js +50 -0
- package/dist/systems/door/door-system.d.ts +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +494 -161
- package/dist/systems/fence/fence-system.d.ts +17 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +221 -53
- package/dist/systems/geometry/geometry-system.d.ts +7 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +95 -26
- package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
- package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.test.js +30 -0
- package/dist/systems/interactive/control-widget.d.ts +10 -0
- package/dist/systems/interactive/control-widget.d.ts.map +1 -0
- package/dist/systems/interactive/control-widget.js +35 -0
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +55 -56
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +20 -6
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +28 -4
- package/dist/systems/level/level-utils.d.ts +0 -3
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +2 -41
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +29 -8
- package/dist/systems/roof/roof-system.d.ts +21 -8
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1079 -221
- package/dist/systems/slab/slab-system.d.ts +5 -3
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +41 -23
- package/dist/systems/slab/slab-system.test.js +3 -2
- package/dist/systems/stair/stair-system.js +6 -3
- package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.js +167 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +46 -17
- package/dist/systems/wall/wall-materials.d.ts +11 -7
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +244 -35
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-support-extension.test.js +48 -0
- package/dist/systems/wall/wall-system.d.ts +2 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +329 -174
- package/dist/systems/window/window-animation-system.d.ts +12 -0
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +16 -5
- package/dist/systems/window/window-system.d.ts +7 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +240 -14
- package/package.json +4 -4
- package/dist/components/viewer/ground-occluder.d.ts +0 -2
- package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
- package/dist/components/viewer/ground-occluder.js +0 -67
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { emitter, sceneRegistry, useScene } from '@pascal-app/core';
|
|
1
|
+
import { emitter, getWallFaceBandConfig, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
import { useEffect, useRef } from 'react';
|
|
4
4
|
import { Vector3 } from 'three/webgpu';
|
|
5
5
|
import useViewer from '../../store/use-viewer';
|
|
6
|
-
import { getMaterialsForWall } from './wall-materials';
|
|
6
|
+
import { getMaterialsForWall, getSelectionHighlightMaterials, getWallMaterialHash, } from './wall-materials';
|
|
7
7
|
const tmpVec = new Vector3();
|
|
8
8
|
const u = new Vector3();
|
|
9
9
|
const v = new Vector3();
|
|
@@ -28,6 +28,9 @@ function getWallHideState(wallNode, wallMesh, wallMode, cameraDir) {
|
|
|
28
28
|
}
|
|
29
29
|
return hideWall;
|
|
30
30
|
}
|
|
31
|
+
function sameMaterialArray(a, b) {
|
|
32
|
+
return Array.isArray(a) && a.length === b.length && a.every((material, i) => material === b[i]);
|
|
33
|
+
}
|
|
31
34
|
export const WallCutout = () => {
|
|
32
35
|
const lastCameraPosition = useRef(new Vector3());
|
|
33
36
|
const lastCameraTarget = useRef(new Vector3());
|
|
@@ -36,6 +39,7 @@ export const WallCutout = () => {
|
|
|
36
39
|
const lastShading = useRef(useViewer.getState().shading);
|
|
37
40
|
const lastNumberOfWalls = useRef(0);
|
|
38
41
|
const lastHighlightKey = useRef('');
|
|
42
|
+
const lastWallAppearanceKey = useRef('');
|
|
39
43
|
const lastTextures = useRef(useViewer.getState().textures);
|
|
40
44
|
const lastColorPreset = useRef(useViewer.getState().colorPreset);
|
|
41
45
|
const lastSceneTheme = useRef(useViewer.getState().sceneTheme);
|
|
@@ -49,17 +53,27 @@ export const WallCutout = () => {
|
|
|
49
53
|
const previewSelectedIds = useViewer.getState().previewSelectedIds;
|
|
50
54
|
const hoveredId = useViewer.getState().hoveredId;
|
|
51
55
|
const hoverHighlightMode = useViewer.getState().hoverHighlightMode;
|
|
56
|
+
const sceneState = useScene.getState();
|
|
52
57
|
const currentTime = clock.elapsedTime;
|
|
53
58
|
const currentCameraPosition = camera.position;
|
|
54
59
|
camera.getWorldDirection(tmpVec);
|
|
55
60
|
tmpVec.add(currentCameraPosition);
|
|
56
|
-
const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) =>
|
|
61
|
+
const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) => sceneState.nodes[id]?.type === 'wall'));
|
|
57
62
|
const deleteHoveredWallId = hoverHighlightMode === 'delete' &&
|
|
58
63
|
hoveredId &&
|
|
59
|
-
|
|
64
|
+
sceneState.nodes[hoveredId]?.type === 'wall'
|
|
60
65
|
? hoveredId
|
|
61
66
|
: null;
|
|
62
67
|
const highlightKey = `${Array.from(highlightedWallIds).sort().join('|')}::${deleteHoveredWallId ?? ''}`;
|
|
68
|
+
const wallAppearanceKey = Array.from(sceneRegistry.byType.wall)
|
|
69
|
+
.sort()
|
|
70
|
+
.map((wallId) => {
|
|
71
|
+
const wallNode = sceneState.nodes[wallId];
|
|
72
|
+
if (wallNode?.type !== 'wall')
|
|
73
|
+
return `${wallId}:missing`;
|
|
74
|
+
return `${wallId}:${getWallMaterialHash(wallNode, shading, sceneState.materials)}:${JSON.stringify(wallNode.faceBands ?? null)}`;
|
|
75
|
+
})
|
|
76
|
+
.join('|');
|
|
63
77
|
const distanceMoved = currentCameraPosition.distanceTo(lastCameraPosition.current);
|
|
64
78
|
const directionChanged = tmpVec.distanceTo(lastCameraTarget.current);
|
|
65
79
|
const timeSinceUpdate = currentTime - lastUpdateTime.current;
|
|
@@ -70,7 +84,8 @@ export const WallCutout = () => {
|
|
|
70
84
|
lastColorPreset.current !== colorPreset ||
|
|
71
85
|
lastSceneTheme.current !== sceneTheme ||
|
|
72
86
|
sceneRegistry.byType.wall.size !== lastNumberOfWalls.current ||
|
|
73
|
-
lastHighlightKey.current !== highlightKey
|
|
87
|
+
lastHighlightKey.current !== highlightKey ||
|
|
88
|
+
lastWallAppearanceKey.current !== wallAppearanceKey) {
|
|
74
89
|
lastCameraPosition.current.copy(currentCameraPosition);
|
|
75
90
|
lastCameraTarget.current.copy(tmpVec);
|
|
76
91
|
lastUpdateTime.current = currentTime;
|
|
@@ -80,27 +95,36 @@ export const WallCutout = () => {
|
|
|
80
95
|
const wallMesh = sceneRegistry.nodes.get(wallId);
|
|
81
96
|
if (!wallMesh)
|
|
82
97
|
return;
|
|
83
|
-
const wallNode =
|
|
84
|
-
if (
|
|
98
|
+
const wallNode = sceneState.nodes[wallId];
|
|
99
|
+
if (wallNode?.type !== 'wall')
|
|
85
100
|
return;
|
|
86
101
|
const hideWall = getWallHideState(wallNode, wallMesh, wallMode, u);
|
|
87
102
|
const isDeleteHighlighted = deleteHoveredWallId === wallId;
|
|
88
103
|
const isSelectionHighlighted = !isDeleteHighlighted && highlightedWallIds.has(wallId);
|
|
89
|
-
const
|
|
90
|
-
|
|
104
|
+
const shouldSelectionHighlight = isSelectionHighlighted && !getWallFaceBandConfig(wallNode).enabled;
|
|
105
|
+
const materials = getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme, sceneState.materials);
|
|
106
|
+
if (wallMode === 'translucent') {
|
|
107
|
+
;
|
|
108
|
+
wallMesh.material = isDeleteHighlighted
|
|
109
|
+
? materials.deleteTranslucent
|
|
110
|
+
: shouldSelectionHighlight
|
|
111
|
+
? getSelectionHighlightMaterials(materials.translucent)
|
|
112
|
+
: materials.translucent;
|
|
113
|
+
}
|
|
114
|
+
else if (hideWall) {
|
|
91
115
|
;
|
|
92
116
|
wallMesh.material = isDeleteHighlighted
|
|
93
117
|
? materials.deleteInvisible
|
|
94
|
-
:
|
|
95
|
-
? materials.
|
|
118
|
+
: shouldSelectionHighlight
|
|
119
|
+
? getSelectionHighlightMaterials(materials.invisible)
|
|
96
120
|
: materials.invisible;
|
|
97
121
|
}
|
|
98
122
|
else {
|
|
99
123
|
;
|
|
100
124
|
wallMesh.material = isDeleteHighlighted
|
|
101
125
|
? materials.deleteVisible
|
|
102
|
-
:
|
|
103
|
-
? materials.
|
|
126
|
+
: shouldSelectionHighlight
|
|
127
|
+
? getSelectionHighlightMaterials(materials.visible)
|
|
104
128
|
: materials.visible;
|
|
105
129
|
}
|
|
106
130
|
});
|
|
@@ -111,6 +135,7 @@ export const WallCutout = () => {
|
|
|
111
135
|
lastSceneTheme.current = sceneTheme;
|
|
112
136
|
lastNumberOfWalls.current = sceneRegistry.byType.wall.size;
|
|
113
137
|
lastHighlightKey.current = highlightKey;
|
|
138
|
+
lastWallAppearanceKey.current = wallAppearanceKey;
|
|
114
139
|
}
|
|
115
140
|
});
|
|
116
141
|
useEffect(() => {
|
|
@@ -121,17 +146,21 @@ export const WallCutout = () => {
|
|
|
121
146
|
if (!wallMesh)
|
|
122
147
|
return;
|
|
123
148
|
const wallNode = useScene.getState().nodes[wallId];
|
|
124
|
-
if (
|
|
149
|
+
if (wallNode?.type !== 'wall')
|
|
125
150
|
return;
|
|
126
|
-
const mats = getMaterialsForWall(wallNode, useViewer.getState().shading, useViewer.getState().textures, useViewer.getState().colorPreset, useViewer.getState().sceneTheme);
|
|
151
|
+
const mats = getMaterialsForWall(wallNode, useViewer.getState().shading, useViewer.getState().textures, useViewer.getState().colorPreset, useViewer.getState().sceneTheme, useScene.getState().materials);
|
|
127
152
|
const current = wallMesh.material;
|
|
128
153
|
snapshot.set(wallMesh, current);
|
|
129
|
-
if (current === mats.
|
|
154
|
+
if (current === mats.deleteVisible) {
|
|
130
155
|
wallMesh.material = mats.visible;
|
|
131
156
|
}
|
|
132
|
-
else if (current === mats.
|
|
157
|
+
else if (current === mats.deleteInvisible) {
|
|
133
158
|
wallMesh.material = mats.invisible;
|
|
134
159
|
}
|
|
160
|
+
else if (current === mats.deleteTranslucent ||
|
|
161
|
+
sameMaterialArray(current, getSelectionHighlightMaterials(mats.translucent))) {
|
|
162
|
+
wallMesh.material = mats.translucent;
|
|
163
|
+
}
|
|
135
164
|
});
|
|
136
165
|
};
|
|
137
166
|
const reapplyAfterCapture = () => {
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import { type WallNode } from '@pascal-app/core';
|
|
1
|
+
import { type SceneMaterial, type SceneMaterialId, type WallNode } from '@pascal-app/core';
|
|
2
2
|
import { type Material } from 'three';
|
|
3
3
|
import { type ColorPreset, type RenderShading } from '../../lib/materials';
|
|
4
|
-
|
|
4
|
+
type SceneMaterials = Record<SceneMaterialId, SceneMaterial> | undefined;
|
|
5
|
+
export type WallMaterialArray = Material[];
|
|
5
6
|
export interface WallMaterials {
|
|
6
7
|
visible: WallMaterialArray;
|
|
7
8
|
invisible: WallMaterialArray;
|
|
9
|
+
translucent: WallMaterialArray;
|
|
8
10
|
deleteVisible: WallMaterialArray;
|
|
9
11
|
deleteInvisible: WallMaterialArray;
|
|
10
|
-
|
|
11
|
-
highlightedInvisible: WallMaterialArray;
|
|
12
|
+
deleteTranslucent: WallMaterialArray;
|
|
12
13
|
materialHash: string;
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
export declare function
|
|
16
|
-
export declare function
|
|
15
|
+
/** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
|
|
16
|
+
export declare function getSelectionHighlightMaterials(materials: WallMaterialArray): WallMaterialArray;
|
|
17
|
+
export declare function getWallMaterialHash(wallNode: WallNode, shading: RenderShading, sceneMaterials?: SceneMaterials): string;
|
|
18
|
+
export declare function getMaterialsForWall(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials): WallMaterials;
|
|
19
|
+
export declare function getVisibleWallMaterials(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials): WallMaterialArray;
|
|
20
|
+
export {};
|
|
17
21
|
//# sourceMappingURL=wall-materials.d.ts.map
|
|
@@ -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,EAOL,KAAK,aAAa,EAClB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAG5C,OAAO,EAEL,KAAK,WAAW,EAKhB,KAAK,aAAa,EAGnB,MAAM,qBAAqB,CAAA;AAE5B,KAAK,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;AAexE,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;CACrB;AAiSD,wFAAwF;AACxF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAE9F;AA4DD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,MAAM,CAcR;AAED,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,GAC9B,aAAa,CAgIf;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,GAC9B,iBAAiB,CAGnB"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, resolveMaterial, } from '@pascal-app/core';
|
|
1
|
+
import { getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, getWallSurfaceSideFromBandSlot, parseMaterialRef, resolveMaterial, WALL_SLOT_DEFAULT, WALL_SURFACE_SLOT_DEFAULTS, } from '@pascal-app/core';
|
|
2
2
|
import { Color } from 'three';
|
|
3
3
|
import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl';
|
|
4
4
|
import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
5
|
-
import { baseMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, resolveSurfaceColor, } from '../../lib/materials';
|
|
6
|
-
const DEFAULT_WALL_COLOR = '#
|
|
5
|
+
import { baseMaterial, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, resolveMaterialRef, resolveSurfaceColor, } from '../../lib/materials';
|
|
6
|
+
const DEFAULT_WALL_COLOR = '#e9e7e3';
|
|
7
7
|
const WALL_HIGHLIGHT_PROFILES = {
|
|
8
8
|
delete: {
|
|
9
9
|
color: new Color('#dc2626'),
|
|
@@ -11,12 +11,6 @@ const WALL_HIGHLIGHT_PROFILES = {
|
|
|
11
11
|
emissiveBlend: 0.92,
|
|
12
12
|
emissiveIntensity: 0.46,
|
|
13
13
|
},
|
|
14
|
-
selection: {
|
|
15
|
-
color: new Color('#818cf8'),
|
|
16
|
-
blend: 0.32,
|
|
17
|
-
emissiveBlend: 0.7,
|
|
18
|
-
emissiveIntensity: 0.42,
|
|
19
|
-
},
|
|
20
14
|
};
|
|
21
15
|
const wallMaterialCache = new Map();
|
|
22
16
|
const dotPattern = Fn(() => {
|
|
@@ -42,6 +36,108 @@ function getSurfaceVisibleMaterial(spec, shading) {
|
|
|
42
36
|
function hasExplicitMaterial(spec) {
|
|
43
37
|
return Boolean(spec.materialPreset || spec.material);
|
|
44
38
|
}
|
|
39
|
+
// Resolve a wall face's declared default — a catalog `library:` finish or a
|
|
40
|
+
// flat colour — to a renderable material.
|
|
41
|
+
function resolveWallSlotDefault(slotDefault, shading) {
|
|
42
|
+
if (parseMaterialRef(slotDefault)?.kind === 'library') {
|
|
43
|
+
return createMaterialFromPresetRef(slotDefault, shading) ?? baseMaterial(shading);
|
|
44
|
+
}
|
|
45
|
+
return createDefaultMaterial(slotDefault, 0.9, shading);
|
|
46
|
+
}
|
|
47
|
+
// Slot-first resolution for one wall face, matching every other paintable kind:
|
|
48
|
+
// node.slots[side] ref → legacy inline fields → declared slot default.
|
|
49
|
+
// A dangling `scene:` ref (material deleted / copied across scenes) falls back
|
|
50
|
+
// to the declared default — it never blocks rendering (the dangling-ref rule).
|
|
51
|
+
function resolveWallFaceMaterial(wallNode, side, shading, sceneMaterials) {
|
|
52
|
+
const ref = wallNode.slots?.[side];
|
|
53
|
+
if (ref) {
|
|
54
|
+
return (resolveMaterialRef(ref, sceneMaterials, shading) ??
|
|
55
|
+
resolveWallSlotDefault(WALL_SLOT_DEFAULT[side], shading));
|
|
56
|
+
}
|
|
57
|
+
const spec = getEffectiveWallSurfaceMaterial(wallNode, side);
|
|
58
|
+
if (hasExplicitMaterial(spec)) {
|
|
59
|
+
return getSurfaceVisibleMaterial(spec, shading);
|
|
60
|
+
}
|
|
61
|
+
return resolveWallSlotDefault(WALL_SLOT_DEFAULT[side], shading);
|
|
62
|
+
}
|
|
63
|
+
function resolveWallSlotMaterial(wallNode, slotId, shading, sceneMaterials) {
|
|
64
|
+
const ref = wallNode.slots?.[slotId];
|
|
65
|
+
if (ref) {
|
|
66
|
+
return (resolveMaterialRef(ref, sceneMaterials, shading) ??
|
|
67
|
+
resolveWallSlotDefault(WALL_SURFACE_SLOT_DEFAULTS[slotId], shading));
|
|
68
|
+
}
|
|
69
|
+
const side = getWallSurfaceSideFromBandSlot(slotId);
|
|
70
|
+
if (side) {
|
|
71
|
+
return resolveWallFaceMaterial(wallNode, side, shading, sceneMaterials);
|
|
72
|
+
}
|
|
73
|
+
return resolveWallSlotDefault(WALL_SURFACE_SLOT_DEFAULTS[slotId], shading);
|
|
74
|
+
}
|
|
75
|
+
// Cache-key fragment for one face: the slot ref plus, for a `scene:` ref, the
|
|
76
|
+
// referenced material's *content* — so editing a scene material assigned to a
|
|
77
|
+
// wall invalidates the cache (a `library:` ref is static catalog content, so
|
|
78
|
+
// its id alone is enough). Falls back to the legacy signature when unmigrated.
|
|
79
|
+
function wallFaceMaterialSignature(wallNode, side, sceneMaterials) {
|
|
80
|
+
const ref = wallNode.slots?.[side];
|
|
81
|
+
if (ref) {
|
|
82
|
+
const parsed = parseMaterialRef(ref);
|
|
83
|
+
if (parsed?.kind === 'scene') {
|
|
84
|
+
return JSON.stringify({
|
|
85
|
+
ref,
|
|
86
|
+
material: sceneMaterials?.[parsed.id]?.material ?? null,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return JSON.stringify({ ref });
|
|
90
|
+
}
|
|
91
|
+
return getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, side));
|
|
92
|
+
}
|
|
93
|
+
function wallSlotMaterialSignature(wallNode, slotId, sceneMaterials) {
|
|
94
|
+
const ref = wallNode.slots?.[slotId];
|
|
95
|
+
if (ref) {
|
|
96
|
+
const parsed = parseMaterialRef(ref);
|
|
97
|
+
if (parsed?.kind === 'scene') {
|
|
98
|
+
return JSON.stringify({
|
|
99
|
+
ref,
|
|
100
|
+
material: sceneMaterials?.[parsed.id]?.material ?? null,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return JSON.stringify({ ref });
|
|
104
|
+
}
|
|
105
|
+
const side = getWallSurfaceSideFromBandSlot(slotId);
|
|
106
|
+
if (side)
|
|
107
|
+
return wallFaceMaterialSignature(wallNode, side, sceneMaterials);
|
|
108
|
+
return JSON.stringify({ default: WALL_SURFACE_SLOT_DEFAULTS[slotId] });
|
|
109
|
+
}
|
|
110
|
+
// Slot-first tint for the cutaway/invisible wall variant.
|
|
111
|
+
function resolveWallFaceColor(wallNode, side, sceneMaterials, fallback) {
|
|
112
|
+
const ref = wallNode.slots?.[side];
|
|
113
|
+
if (ref) {
|
|
114
|
+
const parsed = parseMaterialRef(ref);
|
|
115
|
+
if (parsed?.kind === 'library') {
|
|
116
|
+
return getMaterialPresetByRef(ref)?.mapProperties?.color ?? fallback;
|
|
117
|
+
}
|
|
118
|
+
if (parsed?.kind === 'scene') {
|
|
119
|
+
const sceneMaterial = sceneMaterials?.[parsed.id];
|
|
120
|
+
return sceneMaterial ? resolveMaterial(sceneMaterial.material).color : fallback;
|
|
121
|
+
}
|
|
122
|
+
return fallback;
|
|
123
|
+
}
|
|
124
|
+
return getSurfaceColor(getEffectiveWallSurfaceMaterial(wallNode, side), fallback);
|
|
125
|
+
}
|
|
126
|
+
function resolveWallSlotColor(wallNode, slotId, sceneMaterials, fallback) {
|
|
127
|
+
const ref = wallNode.slots?.[slotId];
|
|
128
|
+
if (ref) {
|
|
129
|
+
const parsed = parseMaterialRef(ref);
|
|
130
|
+
if (parsed?.kind === 'library') {
|
|
131
|
+
return getMaterialPresetByRef(ref)?.mapProperties?.color ?? fallback;
|
|
132
|
+
}
|
|
133
|
+
if (parsed?.kind === 'scene') {
|
|
134
|
+
const sceneMaterial = sceneMaterials?.[parsed.id];
|
|
135
|
+
return sceneMaterial ? resolveMaterial(sceneMaterial.material).color : fallback;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const side = getWallSurfaceSideFromBandSlot(slotId);
|
|
139
|
+
return side ? resolveWallFaceColor(wallNode, side, sceneMaterials, fallback) : fallback;
|
|
140
|
+
}
|
|
45
141
|
function getSurfaceColor(spec, fallback = DEFAULT_WALL_COLOR) {
|
|
46
142
|
const preset = getMaterialPresetByRef(spec.materialPreset);
|
|
47
143
|
if (preset?.mapProperties?.color) {
|
|
@@ -73,6 +169,60 @@ function createHighlightedWallMaterial(material, kind) {
|
|
|
73
169
|
highlightedMaterial.needsUpdate = true;
|
|
74
170
|
return highlightedMaterial;
|
|
75
171
|
}
|
|
172
|
+
// Light selection highlight for walls (walls are excluded from the generic
|
|
173
|
+
// editor selection highlight, so they need their own). Adds a gentle indigo
|
|
174
|
+
// emissive (no albedo tint) so the real material/texture stays readable with a
|
|
175
|
+
// soft "selected" glow. Two NodeMaterial-clone gotchas are handled:
|
|
176
|
+
// 1. `clone()` on the WebGPU backend drops the texture-map nodes → re-attach
|
|
177
|
+
// them from the source (shared by reference).
|
|
178
|
+
// 2. The wall's finish texture loads async, so an early clone has no map yet →
|
|
179
|
+
// cache keyed by the source `.map` and rebuild when it changes (self-heals
|
|
180
|
+
// once the texture lands).
|
|
181
|
+
const SELECTION_HIGHLIGHT_COLOR = new Color('#818cf8');
|
|
182
|
+
const SELECTION_EMISSIVE_BLEND = 0.4;
|
|
183
|
+
const SELECTION_EMISSIVE_INTENSITY = 0.12;
|
|
184
|
+
const SELECTION_TEXTURE_MAP_KEYS = [
|
|
185
|
+
'map',
|
|
186
|
+
'normalMap',
|
|
187
|
+
'roughnessMap',
|
|
188
|
+
'metalnessMap',
|
|
189
|
+
'aoMap',
|
|
190
|
+
'emissiveMap',
|
|
191
|
+
'bumpMap',
|
|
192
|
+
'displacementMap',
|
|
193
|
+
'alphaMap',
|
|
194
|
+
'lightMap',
|
|
195
|
+
];
|
|
196
|
+
const selectionHighlightCache = new WeakMap();
|
|
197
|
+
function getSelectionHighlightMaterial(base) {
|
|
198
|
+
const baseMap = base.map ?? null;
|
|
199
|
+
const cached = selectionHighlightCache.get(base);
|
|
200
|
+
if (cached && cached.map === baseMap)
|
|
201
|
+
return cached.clone;
|
|
202
|
+
const clone = base.clone();
|
|
203
|
+
// Re-attach texture maps the WebGPU NodeMaterial clone drops.
|
|
204
|
+
const src = base;
|
|
205
|
+
const dst = clone;
|
|
206
|
+
for (const key of SELECTION_TEXTURE_MAP_KEYS) {
|
|
207
|
+
if (src[key])
|
|
208
|
+
dst[key] = src[key];
|
|
209
|
+
}
|
|
210
|
+
if ('emissive' in clone && clone.emissive) {
|
|
211
|
+
clone.emissive = clone.emissive
|
|
212
|
+
.clone()
|
|
213
|
+
.lerp(SELECTION_HIGHLIGHT_COLOR, SELECTION_EMISSIVE_BLEND);
|
|
214
|
+
}
|
|
215
|
+
if ('emissiveIntensity' in clone) {
|
|
216
|
+
clone.emissiveIntensity = Math.max(clone.emissiveIntensity ?? 0, SELECTION_EMISSIVE_INTENSITY);
|
|
217
|
+
}
|
|
218
|
+
clone.needsUpdate = true;
|
|
219
|
+
selectionHighlightCache.set(base, { clone, map: baseMap });
|
|
220
|
+
return clone;
|
|
221
|
+
}
|
|
222
|
+
/** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
|
|
223
|
+
export function getSelectionHighlightMaterials(materials) {
|
|
224
|
+
return materials.map(getSelectionHighlightMaterial);
|
|
225
|
+
}
|
|
76
226
|
function createInvisibleWallMaterial(color, shading) {
|
|
77
227
|
const material = shading === 'solid'
|
|
78
228
|
? new MeshLambertNodeMaterial({
|
|
@@ -90,6 +240,22 @@ function createInvisibleWallMaterial(color, shading) {
|
|
|
90
240
|
material.opacityNode = mix(float(0.0), float(0.24), dotPattern());
|
|
91
241
|
return material;
|
|
92
242
|
}
|
|
243
|
+
function createTranslucentWallMaterial(color, shading) {
|
|
244
|
+
const material = shading === 'solid'
|
|
245
|
+
? new MeshLambertNodeMaterial({
|
|
246
|
+
transparent: true,
|
|
247
|
+
color,
|
|
248
|
+
opacity: 0.35,
|
|
249
|
+
depthWrite: false,
|
|
250
|
+
})
|
|
251
|
+
: new MeshStandardNodeMaterial({
|
|
252
|
+
transparent: true,
|
|
253
|
+
color,
|
|
254
|
+
opacity: 0.35,
|
|
255
|
+
depthWrite: false,
|
|
256
|
+
});
|
|
257
|
+
return material;
|
|
258
|
+
}
|
|
93
259
|
function mapWallMaterialArray(materials, iteratee) {
|
|
94
260
|
return materials.map(iteratee);
|
|
95
261
|
}
|
|
@@ -104,17 +270,25 @@ function disposeOwnedMaterials(materials) {
|
|
|
104
270
|
material.dispose();
|
|
105
271
|
});
|
|
106
272
|
}
|
|
107
|
-
export function getWallMaterialHash(wallNode, shading) {
|
|
273
|
+
export function getWallMaterialHash(wallNode, shading, sceneMaterials) {
|
|
108
274
|
return JSON.stringify({
|
|
109
275
|
shading,
|
|
110
|
-
interior:
|
|
111
|
-
exterior:
|
|
276
|
+
interior: wallFaceMaterialSignature(wallNode, 'interior', sceneMaterials),
|
|
277
|
+
exterior: wallFaceMaterialSignature(wallNode, 'exterior', sceneMaterials),
|
|
278
|
+
lowerInterior: wallSlotMaterialSignature(wallNode, 'lowerInterior', sceneMaterials),
|
|
279
|
+
middleInterior: wallSlotMaterialSignature(wallNode, 'middleInterior', sceneMaterials),
|
|
280
|
+
upperInterior: wallSlotMaterialSignature(wallNode, 'upperInterior', sceneMaterials),
|
|
281
|
+
topInterior: wallSlotMaterialSignature(wallNode, 'topInterior', sceneMaterials),
|
|
282
|
+
lowerExterior: wallSlotMaterialSignature(wallNode, 'lowerExterior', sceneMaterials),
|
|
283
|
+
middleExterior: wallSlotMaterialSignature(wallNode, 'middleExterior', sceneMaterials),
|
|
284
|
+
upperExterior: wallSlotMaterialSignature(wallNode, 'upperExterior', sceneMaterials),
|
|
285
|
+
topExterior: wallSlotMaterialSignature(wallNode, 'topExterior', sceneMaterials),
|
|
112
286
|
});
|
|
113
287
|
}
|
|
114
|
-
export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme) {
|
|
288
|
+
export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
|
|
115
289
|
const cacheKey = `${wallNode.id}-${shading}-${textures}-${colorPreset}-${sceneTheme ?? 'base'}`;
|
|
116
290
|
const materialHash = textures
|
|
117
|
-
? getWallMaterialHash(wallNode, shading)
|
|
291
|
+
? getWallMaterialHash(wallNode, shading, sceneMaterials)
|
|
118
292
|
: JSON.stringify({ textures, colorPreset, sceneTheme });
|
|
119
293
|
const existing = wallMaterialCache.get(cacheKey);
|
|
120
294
|
if (existing && existing.materialHash === materialHash) {
|
|
@@ -123,50 +297,85 @@ export function getMaterialsForWall(wallNode, shading = 'rendered', textures = t
|
|
|
123
297
|
if (existing) {
|
|
124
298
|
disposeOwnedMaterials([
|
|
125
299
|
existing.invisible,
|
|
300
|
+
existing.translucent,
|
|
126
301
|
existing.deleteVisible,
|
|
127
302
|
existing.deleteInvisible,
|
|
128
|
-
existing.
|
|
129
|
-
existing.highlightedInvisible,
|
|
303
|
+
existing.deleteTranslucent,
|
|
130
304
|
]);
|
|
131
305
|
}
|
|
132
|
-
const interiorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'interior');
|
|
133
|
-
const exteriorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'exterior');
|
|
134
306
|
const wallRoleMaterial = createSurfaceRoleMaterial('wall', colorPreset, undefined, sceneTheme);
|
|
135
|
-
//
|
|
136
|
-
//
|
|
307
|
+
// Colored mode: each face resolves slot-first (node.slots ref → legacy inline
|
|
308
|
+
// fields → declared slot default, parity with the retired DEFAULT_WALL_MATERIAL).
|
|
309
|
+
// Textures-off collapses every face to the themed wall role (the guaranteed
|
|
310
|
+
// escape hatch). The edge/cap slot (index 0) stays role-based.
|
|
137
311
|
const visible = textures
|
|
138
312
|
? [
|
|
139
313
|
wallRoleMaterial,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
314
|
+
resolveWallFaceMaterial(wallNode, 'interior', shading, sceneMaterials),
|
|
315
|
+
resolveWallFaceMaterial(wallNode, 'exterior', shading, sceneMaterials),
|
|
316
|
+
resolveWallSlotMaterial(wallNode, 'lowerInterior', shading, sceneMaterials),
|
|
317
|
+
resolveWallSlotMaterial(wallNode, 'middleInterior', shading, sceneMaterials),
|
|
318
|
+
resolveWallSlotMaterial(wallNode, 'upperInterior', shading, sceneMaterials),
|
|
319
|
+
resolveWallSlotMaterial(wallNode, 'topInterior', shading, sceneMaterials),
|
|
320
|
+
resolveWallSlotMaterial(wallNode, 'lowerExterior', shading, sceneMaterials),
|
|
321
|
+
resolveWallSlotMaterial(wallNode, 'middleExterior', shading, sceneMaterials),
|
|
322
|
+
resolveWallSlotMaterial(wallNode, 'upperExterior', shading, sceneMaterials),
|
|
323
|
+
resolveWallSlotMaterial(wallNode, 'topExterior', shading, sceneMaterials),
|
|
146
324
|
]
|
|
147
|
-
:
|
|
325
|
+
: Array.from({ length: 11 }, () => wallRoleMaterial);
|
|
148
326
|
const wallRoleColor = resolveSurfaceColor('wall', colorPreset, sceneTheme);
|
|
149
327
|
const invisible = [
|
|
150
328
|
createInvisibleWallMaterial(wallRoleColor, textures ? shading : 'solid'),
|
|
151
|
-
|
|
152
|
-
|
|
329
|
+
...['interior', 'exterior'].map((side) => createInvisibleWallMaterial(textures
|
|
330
|
+
? resolveWallFaceColor(wallNode, side, sceneMaterials, wallRoleColor)
|
|
331
|
+
: wallRoleColor, textures ? shading : 'solid')),
|
|
332
|
+
...[
|
|
333
|
+
'lowerInterior',
|
|
334
|
+
'middleInterior',
|
|
335
|
+
'upperInterior',
|
|
336
|
+
'topInterior',
|
|
337
|
+
'lowerExterior',
|
|
338
|
+
'middleExterior',
|
|
339
|
+
'upperExterior',
|
|
340
|
+
'topExterior',
|
|
341
|
+
].map((slotId) => createInvisibleWallMaterial(textures
|
|
342
|
+
? resolveWallSlotColor(wallNode, slotId, sceneMaterials, wallRoleColor)
|
|
343
|
+
: wallRoleColor, textures ? shading : 'solid')),
|
|
344
|
+
];
|
|
345
|
+
const translucent = [
|
|
346
|
+
createTranslucentWallMaterial(wallRoleColor, textures ? shading : 'solid'),
|
|
347
|
+
...['interior', 'exterior'].map((side) => createTranslucentWallMaterial(textures
|
|
348
|
+
? resolveWallFaceColor(wallNode, side, sceneMaterials, wallRoleColor)
|
|
349
|
+
: wallRoleColor, textures ? shading : 'solid')),
|
|
350
|
+
...[
|
|
351
|
+
'lowerInterior',
|
|
352
|
+
'middleInterior',
|
|
353
|
+
'upperInterior',
|
|
354
|
+
'topInterior',
|
|
355
|
+
'lowerExterior',
|
|
356
|
+
'middleExterior',
|
|
357
|
+
'upperExterior',
|
|
358
|
+
'topExterior',
|
|
359
|
+
].map((slotId) => createTranslucentWallMaterial(textures
|
|
360
|
+
? resolveWallSlotColor(wallNode, slotId, sceneMaterials, wallRoleColor)
|
|
361
|
+
: wallRoleColor, textures ? shading : 'solid')),
|
|
153
362
|
];
|
|
154
|
-
const highlightedVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'selection'));
|
|
155
|
-
const highlightedInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'selection'));
|
|
156
363
|
const deleteVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
157
364
|
const deleteInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
365
|
+
const deleteTranslucent = mapWallMaterialArray(translucent, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
158
366
|
const result = {
|
|
159
367
|
visible,
|
|
160
368
|
invisible,
|
|
369
|
+
translucent,
|
|
161
370
|
deleteVisible,
|
|
162
371
|
deleteInvisible,
|
|
163
|
-
|
|
164
|
-
highlightedInvisible,
|
|
372
|
+
deleteTranslucent,
|
|
165
373
|
materialHash,
|
|
166
374
|
};
|
|
167
375
|
wallMaterialCache.set(cacheKey, result);
|
|
168
376
|
return result;
|
|
169
377
|
}
|
|
170
|
-
export function getVisibleWallMaterials(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme) {
|
|
171
|
-
return getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme)
|
|
378
|
+
export function getVisibleWallMaterials(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
|
|
379
|
+
return getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme, sceneMaterials)
|
|
380
|
+
.visible;
|
|
172
381
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-opening-cutout.test.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-opening-cutout.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
+
// depend on @types/bun so the import type is unresolved at compile time.
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import { calculateLevelMiters, DoorNode, sceneRegistry, WallNode } from '@pascal-app/core';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { generateExtrudedWall } from './wall-system';
|
|
7
|
+
describe('wall opening cutout', () => {
|
|
8
|
+
test('cuts a floor-level door directly from node geometry without a proxy mesh', () => {
|
|
9
|
+
const wall = WallNode.parse({
|
|
10
|
+
id: 'wall_floor-opening-cutout',
|
|
11
|
+
start: [0, 0],
|
|
12
|
+
end: [2, 0],
|
|
13
|
+
height: 2.5,
|
|
14
|
+
thickness: 0.1,
|
|
15
|
+
});
|
|
16
|
+
const door = DoorNode.parse({
|
|
17
|
+
id: 'door_floor-opening-cutout',
|
|
18
|
+
wallId: wall.id,
|
|
19
|
+
position: [1, 1.05, 0],
|
|
20
|
+
width: 0.9,
|
|
21
|
+
height: 2.1,
|
|
22
|
+
});
|
|
23
|
+
const wallMesh = new THREE.Mesh();
|
|
24
|
+
sceneRegistry.nodes.set(wall.id, wallMesh);
|
|
25
|
+
try {
|
|
26
|
+
const geometry = generateExtrudedWall(wall, [door], calculateLevelMiters([wall]));
|
|
27
|
+
const position = geometry.getAttribute('position');
|
|
28
|
+
const index = geometry.index;
|
|
29
|
+
const openingLeft = door.position[0] - door.width / 2;
|
|
30
|
+
const openingRight = door.position[0] + door.width / 2;
|
|
31
|
+
let wallFaceTrianglesInsideOpening = 0;
|
|
32
|
+
let baseTrianglesInsideOpening = 0;
|
|
33
|
+
for (let offset = 0; offset < (index?.count ?? position.count); offset += 3) {
|
|
34
|
+
const indices = [0, 1, 2].map((corner) => index ? index.getX(offset + corner) : offset + corner);
|
|
35
|
+
const vertices = indices.map((vertexIndex) => new THREE.Vector3(position.getX(vertexIndex), position.getY(vertexIndex), position.getZ(vertexIndex)));
|
|
36
|
+
const centroid = vertices
|
|
37
|
+
.reduce((sum, vertex) => sum.add(vertex), new THREE.Vector3())
|
|
38
|
+
.multiplyScalar(1 / 3);
|
|
39
|
+
const insideOpeningX = centroid.x > openingLeft + 1e-4 && centroid.x < openingRight - 1e-4;
|
|
40
|
+
if (!insideOpeningX)
|
|
41
|
+
continue;
|
|
42
|
+
const onWallFace = vertices.every((vertex) => Math.abs(Math.abs(vertex.z) - (wall.thickness ?? 0.1) / 2) < 1e-5);
|
|
43
|
+
if (onWallFace && centroid.y > 1e-4 && centroid.y < door.height - 1e-4) {
|
|
44
|
+
wallFaceTrianglesInsideOpening += 1;
|
|
45
|
+
}
|
|
46
|
+
if (vertices.every((vertex) => Math.abs(vertex.y) < 1e-5)) {
|
|
47
|
+
baseTrianglesInsideOpening += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
expect(wallFaceTrianglesInsideOpening).toBe(0);
|
|
51
|
+
expect(baseTrianglesInsideOpening).toBe(0);
|
|
52
|
+
geometry.dispose();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
sceneRegistry.nodes.delete(wall.id);
|
|
56
|
+
wallMesh.geometry.dispose();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-support-extension.test.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-support-extension.test.ts"],"names":[],"mappings":""}
|