@pascal-app/viewer 0.8.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/error-boundary.d.ts +6 -0
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +6 -0
- package/dist/components/renderers/node-renderer.d.ts +6 -2
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +41 -22
- package/dist/components/renderers/parametric-node-renderer.d.ts +5 -0
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -0
- package/dist/components/renderers/parametric-node-renderer.js +37 -0
- package/dist/components/renderers/scene-renderer.d.ts +1 -1
- package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
- package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
- package/dist/components/viewer/bvh-ecctrl.js +552 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +39 -4
- package/dist/components/viewer/glb-interactive.d.ts +41 -0
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
- package/dist/components/viewer/glb-interactive.js +394 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
- package/dist/components/viewer/glb-reference-nodes.js +80 -0
- package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
- package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
- package/dist/components/viewer/glb-replace-instances.js +61 -0
- package/dist/components/viewer/glb-scene.d.ts +54 -0
- package/dist/components/viewer/glb-scene.d.ts.map +1 -0
- package/dist/components/viewer/glb-scene.js +920 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +12 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/glb-walkthrough-controller.js +357 -0
- package/dist/components/viewer/index.d.ts +56 -3
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +280 -65
- package/dist/components/viewer/lights.d.ts +1 -1
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +201 -43
- package/dist/components/viewer/perf-monitor.d.ts +1 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +115 -12
- package/dist/components/viewer/post-processing.d.ts +7 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +395 -63
- package/dist/components/viewer/registered-systems.d.ts +12 -0
- package/dist/components/viewer/registered-systems.d.ts.map +1 -0
- package/dist/components/viewer/registered-systems.js +49 -0
- package/dist/components/viewer/scene-bvh.d.ts +18 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.js +97 -0
- package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
- package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.test.js +13 -0
- package/dist/components/viewer/scene-environment.d.ts +2 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -0
- package/dist/components/viewer/scene-environment.js +79 -0
- package/dist/components/viewer/selection-manager.d.ts +1 -1
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +37 -14
- package/dist/components/viewer/viewer-camera.d.ts +1 -1
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.d.ts +2 -1
- package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.js +20 -0
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +3 -22
- package/dist/hooks/use-node-events.d.ts +6 -74
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +28 -8
- package/dist/index.d.ts +48 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -4
- package/dist/lib/backdrop.d.ts +33 -0
- package/dist/lib/backdrop.d.ts.map +1 -0
- package/dist/lib/backdrop.js +92 -0
- package/dist/lib/box-uv.d.ts +13 -0
- package/dist/lib/box-uv.d.ts.map +1 -0
- package/dist/lib/box-uv.js +32 -0
- package/dist/lib/csg-utils.d.ts +10 -0
- package/dist/lib/csg-utils.d.ts.map +1 -0
- package/dist/lib/csg-utils.js +93 -0
- package/dist/lib/csg-utils.test.d.ts +2 -0
- package/dist/lib/csg-utils.test.d.ts.map +1 -0
- package/dist/lib/csg-utils.test.js +45 -0
- package/dist/lib/drawable-geometry.d.ts +21 -0
- package/dist/lib/drawable-geometry.d.ts.map +1 -0
- package/dist/lib/drawable-geometry.js +22 -0
- package/dist/lib/edge-style.d.ts +4 -0
- package/dist/lib/edge-style.d.ts.map +1 -0
- package/dist/lib/edge-style.js +31 -0
- package/dist/lib/gpu-perf.d.ts +4 -0
- package/dist/lib/gpu-perf.d.ts.map +1 -0
- package/dist/lib/gpu-perf.js +23 -0
- package/dist/lib/ink-edges.d.ts +9 -0
- package/dist/lib/ink-edges.d.ts.map +1 -0
- package/dist/lib/ink-edges.js +49 -0
- package/dist/lib/isolation.d.ts +39 -0
- package/dist/lib/isolation.d.ts.map +1 -0
- package/dist/lib/isolation.js +103 -0
- package/dist/lib/ktx2-loader.d.ts +43 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -0
- package/dist/lib/ktx2-loader.js +136 -0
- package/dist/lib/layers.d.ts +28 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +28 -0
- package/dist/lib/materials.d.ts +38 -15
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +364 -72
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +7 -0
- package/dist/lib/polygon-union.d.ts +4 -0
- package/dist/lib/polygon-union.d.ts.map +1 -0
- package/dist/lib/polygon-union.js +289 -0
- package/dist/lib/polygon-union.test.d.ts +2 -0
- package/dist/lib/polygon-union.test.d.ts.map +1 -0
- package/dist/lib/polygon-union.test.js +105 -0
- package/dist/lib/scene-themes.d.ts +30 -0
- package/dist/lib/scene-themes.d.ts.map +1 -0
- package/dist/lib/scene-themes.js +202 -0
- package/dist/lib/shadow-only.d.ts +4 -0
- package/dist/lib/shadow-only.d.ts.map +1 -0
- package/dist/lib/shadow-only.js +36 -0
- package/dist/lib/tsl-compat.d.ts +12 -0
- package/dist/lib/tsl-compat.d.ts.map +1 -0
- package/dist/lib/tsl-compat.js +13 -0
- package/dist/store/use-viewer.d.ts +92 -12
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +201 -6
- package/dist/store/use-viewer.test.d.ts +2 -0
- package/dist/store/use-viewer.test.d.ts.map +1 -0
- package/dist/store/use-viewer.test.js +42 -0
- package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +66 -13
- package/dist/systems/door/door-animation.test.d.ts +2 -0
- package/dist/systems/door/door-animation.test.d.ts.map +1 -0
- package/dist/systems/door/door-animation.test.js +51 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
- package/dist/systems/door/door-floor-alignment.test.js +50 -0
- package/dist/systems/door/door-system.d.ts +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +864 -180
- package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-interaction-system.js +62 -0
- package/dist/systems/fence/fence-system.d.ts +20 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +229 -57
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
- package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
- package/dist/systems/geometry/geometry-system.d.ts +41 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.js +348 -0
- package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
- package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.test.js +30 -0
- package/dist/systems/interactive/control-widget.d.ts +10 -0
- package/dist/systems/interactive/control-widget.d.ts.map +1 -0
- package/dist/systems/interactive/control-widget.js +35 -0
- package/dist/systems/interactive/interactive-system.d.ts +1 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +55 -56
- package/dist/systems/item/item-system.d.ts +7 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +28 -17
- package/dist/systems/item-light/item-light-system.d.ts +1 -1
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +18 -5
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +28 -4
- package/dist/systems/level/level-utils.d.ts +0 -3
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +2 -41
- package/dist/systems/roof/roof-materials.d.ts +3 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +59 -14
- package/dist/systems/roof/roof-system.d.ts +46 -6
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1365 -170
- package/dist/systems/slab/slab-system.d.ts +5 -3
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +204 -68
- package/dist/systems/slab/slab-system.test.d.ts +2 -0
- package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
- package/dist/systems/slab/slab-system.test.js +63 -0
- package/dist/systems/stair/stair-materials.d.ts +4 -3
- package/dist/systems/stair/stair-materials.d.ts.map +1 -1
- package/dist/systems/stair/stair-materials.js +30 -15
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +55 -63
- package/dist/systems/surface-hole-geometry.d.ts +3 -0
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
- package/dist/systems/surface-hole-geometry.js +4 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.js +167 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +62 -17
- package/dist/systems/wall/wall-materials.d.ts +12 -7
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +279 -46
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-support-extension.test.js +48 -0
- package/dist/systems/wall/wall-system.d.ts +2 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +493 -193
- package/dist/systems/window/window-animation-system.d.ts +12 -0
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +16 -5
- package/dist/systems/window/window-system.d.ts +7 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +308 -18
- package/package.json +5 -6
- package/dist/components/renderers/building/building-renderer.d.ts +0 -5
- package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
- package/dist/components/renderers/building/building-renderer.js +0 -11
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
- package/dist/components/renderers/column/column-renderer.d.ts +0 -5
- package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
- package/dist/components/renderers/column/column-renderer.js +0 -597
- package/dist/components/renderers/door/door-renderer.d.ts +0 -5
- package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
- package/dist/components/renderers/door/door-renderer.js +0 -16
- package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
- package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
- package/dist/components/renderers/fence/fence-renderer.js +0 -15
- package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
- package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
- package/dist/components/renderers/guide/guide-renderer.js +0 -38
- package/dist/components/renderers/item/item-renderer.d.ts +0 -5
- package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
- package/dist/components/renderers/item/item-renderer.js +0 -172
- package/dist/components/renderers/level/level-renderer.d.ts +0 -5
- package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
- package/dist/components/renderers/level/level-renderer.js +0 -11
- package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
- package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-materials.js +0 -16
- package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-renderer.js +0 -32
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
- package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
- package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
- package/dist/components/renderers/scan/scan-renderer.js +0 -54
- package/dist/components/renderers/site/site-renderer.d.ts +0 -5
- package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
- package/dist/components/renderers/site/site-renderer.js +0 -106
- package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
- package/dist/components/renderers/slab/slab-renderer.js +0 -58
- package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
- package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
- package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
- package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
- package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair/stair-renderer.js +0 -603
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -51
- package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
- package/dist/components/renderers/wall/wall-renderer.js +0 -16
- package/dist/components/renderers/window/window-renderer.d.ts +0 -5
- package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
- package/dist/components/renderers/window/window-renderer.js +0 -21
- package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
- package/dist/components/renderers/zone/zone-renderer.js +0 -181
- package/dist/components/viewer/ground-occluder.d.ts +0 -2
- package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
- package/dist/components/viewer/ground-occluder.js +0 -79
|
@@ -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,37 +28,64 @@ 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());
|
|
34
37
|
const lastUpdateTime = useRef(0);
|
|
35
38
|
const lastWallMode = useRef(useViewer.getState().wallMode);
|
|
39
|
+
const lastShading = useRef(useViewer.getState().shading);
|
|
36
40
|
const lastNumberOfWalls = useRef(0);
|
|
37
41
|
const lastHighlightKey = useRef('');
|
|
42
|
+
const lastWallAppearanceKey = useRef('');
|
|
43
|
+
const lastTextures = useRef(useViewer.getState().textures);
|
|
44
|
+
const lastColorPreset = useRef(useViewer.getState().colorPreset);
|
|
45
|
+
const lastSceneTheme = useRef(useViewer.getState().sceneTheme);
|
|
38
46
|
useFrame(({ camera, clock }) => {
|
|
39
47
|
const wallMode = useViewer.getState().wallMode;
|
|
48
|
+
const shading = useViewer.getState().shading;
|
|
49
|
+
const textures = useViewer.getState().textures;
|
|
50
|
+
const colorPreset = useViewer.getState().colorPreset;
|
|
51
|
+
const sceneTheme = useViewer.getState().sceneTheme;
|
|
40
52
|
const selectedIds = useViewer.getState().selection.selectedIds;
|
|
41
53
|
const previewSelectedIds = useViewer.getState().previewSelectedIds;
|
|
42
54
|
const hoveredId = useViewer.getState().hoveredId;
|
|
43
55
|
const hoverHighlightMode = useViewer.getState().hoverHighlightMode;
|
|
56
|
+
const sceneState = useScene.getState();
|
|
44
57
|
const currentTime = clock.elapsedTime;
|
|
45
58
|
const currentCameraPosition = camera.position;
|
|
46
59
|
camera.getWorldDirection(tmpVec);
|
|
47
60
|
tmpVec.add(currentCameraPosition);
|
|
48
|
-
const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) =>
|
|
61
|
+
const highlightedWallIds = new Set([...selectedIds, ...previewSelectedIds].filter((id) => sceneState.nodes[id]?.type === 'wall'));
|
|
49
62
|
const deleteHoveredWallId = hoverHighlightMode === 'delete' &&
|
|
50
63
|
hoveredId &&
|
|
51
|
-
|
|
64
|
+
sceneState.nodes[hoveredId]?.type === 'wall'
|
|
52
65
|
? hoveredId
|
|
53
66
|
: null;
|
|
54
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('|');
|
|
55
77
|
const distanceMoved = currentCameraPosition.distanceTo(lastCameraPosition.current);
|
|
56
78
|
const directionChanged = tmpVec.distanceTo(lastCameraTarget.current);
|
|
57
79
|
const timeSinceUpdate = currentTime - lastUpdateTime.current;
|
|
58
80
|
if (((distanceMoved > 0.5 || directionChanged > 0.3) && timeSinceUpdate > 0.1) ||
|
|
59
81
|
lastWallMode.current !== wallMode ||
|
|
82
|
+
lastShading.current !== shading ||
|
|
83
|
+
lastTextures.current !== textures ||
|
|
84
|
+
lastColorPreset.current !== colorPreset ||
|
|
85
|
+
lastSceneTheme.current !== sceneTheme ||
|
|
60
86
|
sceneRegistry.byType.wall.size !== lastNumberOfWalls.current ||
|
|
61
|
-
lastHighlightKey.current !== highlightKey
|
|
87
|
+
lastHighlightKey.current !== highlightKey ||
|
|
88
|
+
lastWallAppearanceKey.current !== wallAppearanceKey) {
|
|
62
89
|
lastCameraPosition.current.copy(currentCameraPosition);
|
|
63
90
|
lastCameraTarget.current.copy(tmpVec);
|
|
64
91
|
lastUpdateTime.current = currentTime;
|
|
@@ -68,33 +95,47 @@ export const WallCutout = () => {
|
|
|
68
95
|
const wallMesh = sceneRegistry.nodes.get(wallId);
|
|
69
96
|
if (!wallMesh)
|
|
70
97
|
return;
|
|
71
|
-
const wallNode =
|
|
72
|
-
if (
|
|
98
|
+
const wallNode = sceneState.nodes[wallId];
|
|
99
|
+
if (wallNode?.type !== 'wall')
|
|
73
100
|
return;
|
|
74
101
|
const hideWall = getWallHideState(wallNode, wallMesh, wallMode, u);
|
|
75
102
|
const isDeleteHighlighted = deleteHoveredWallId === wallId;
|
|
76
103
|
const isSelectionHighlighted = !isDeleteHighlighted && highlightedWallIds.has(wallId);
|
|
77
|
-
const
|
|
78
|
-
|
|
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) {
|
|
79
115
|
;
|
|
80
116
|
wallMesh.material = isDeleteHighlighted
|
|
81
117
|
? materials.deleteInvisible
|
|
82
|
-
:
|
|
83
|
-
? materials.
|
|
118
|
+
: shouldSelectionHighlight
|
|
119
|
+
? getSelectionHighlightMaterials(materials.invisible)
|
|
84
120
|
: materials.invisible;
|
|
85
121
|
}
|
|
86
122
|
else {
|
|
87
123
|
;
|
|
88
124
|
wallMesh.material = isDeleteHighlighted
|
|
89
125
|
? materials.deleteVisible
|
|
90
|
-
:
|
|
91
|
-
? materials.
|
|
126
|
+
: shouldSelectionHighlight
|
|
127
|
+
? getSelectionHighlightMaterials(materials.visible)
|
|
92
128
|
: materials.visible;
|
|
93
129
|
}
|
|
94
130
|
});
|
|
95
131
|
lastWallMode.current = wallMode;
|
|
132
|
+
lastShading.current = shading;
|
|
133
|
+
lastTextures.current = textures;
|
|
134
|
+
lastColorPreset.current = colorPreset;
|
|
135
|
+
lastSceneTheme.current = sceneTheme;
|
|
96
136
|
lastNumberOfWalls.current = sceneRegistry.byType.wall.size;
|
|
97
137
|
lastHighlightKey.current = highlightKey;
|
|
138
|
+
lastWallAppearanceKey.current = wallAppearanceKey;
|
|
98
139
|
}
|
|
99
140
|
});
|
|
100
141
|
useEffect(() => {
|
|
@@ -105,17 +146,21 @@ export const WallCutout = () => {
|
|
|
105
146
|
if (!wallMesh)
|
|
106
147
|
return;
|
|
107
148
|
const wallNode = useScene.getState().nodes[wallId];
|
|
108
|
-
if (
|
|
149
|
+
if (wallNode?.type !== 'wall')
|
|
109
150
|
return;
|
|
110
|
-
const mats = getMaterialsForWall(wallNode);
|
|
151
|
+
const mats = getMaterialsForWall(wallNode, useViewer.getState().shading, useViewer.getState().textures, useViewer.getState().colorPreset, useViewer.getState().sceneTheme, useScene.getState().materials);
|
|
111
152
|
const current = wallMesh.material;
|
|
112
153
|
snapshot.set(wallMesh, current);
|
|
113
|
-
if (current === mats.
|
|
154
|
+
if (current === mats.deleteVisible) {
|
|
114
155
|
wallMesh.material = mats.visible;
|
|
115
156
|
}
|
|
116
|
-
else if (current === mats.
|
|
157
|
+
else if (current === mats.deleteInvisible) {
|
|
117
158
|
wallMesh.material = mats.invisible;
|
|
118
159
|
}
|
|
160
|
+
else if (current === mats.deleteTranslucent ||
|
|
161
|
+
sameMaterialArray(current, getSelectionHighlightMaterials(mats.translucent))) {
|
|
162
|
+
wallMesh.material = mats.translucent;
|
|
163
|
+
}
|
|
119
164
|
});
|
|
120
165
|
};
|
|
121
166
|
const reapplyAfterCapture = () => {
|
|
@@ -1,16 +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
|
+
type SceneMaterials = Record<SceneMaterialId, SceneMaterial> | undefined;
|
|
5
|
+
export type WallMaterialArray = Material[];
|
|
4
6
|
export interface WallMaterials {
|
|
5
7
|
visible: WallMaterialArray;
|
|
6
8
|
invisible: WallMaterialArray;
|
|
9
|
+
translucent: WallMaterialArray;
|
|
7
10
|
deleteVisible: WallMaterialArray;
|
|
8
11
|
deleteInvisible: WallMaterialArray;
|
|
9
|
-
|
|
10
|
-
highlightedInvisible: WallMaterialArray;
|
|
12
|
+
deleteTranslucent: WallMaterialArray;
|
|
11
13
|
materialHash: string;
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
export declare function
|
|
15
|
-
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 {};
|
|
16
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
|
-
import { MeshStandardNodeMaterial } from 'three/webgpu';
|
|
5
|
-
import { baseMaterial, createMaterial, createMaterialFromPresetRef } from '../../lib/materials';
|
|
6
|
-
const DEFAULT_WALL_COLOR = '#
|
|
4
|
+
import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
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(() => {
|
|
@@ -30,14 +24,119 @@ const dotPattern = Fn(() => {
|
|
|
30
24
|
const yFade = float(1).sub(smoothstep(float(0), fadeHeight, positionLocal.y));
|
|
31
25
|
return dots.mul(yFade);
|
|
32
26
|
});
|
|
33
|
-
function getSurfaceVisibleMaterial(spec) {
|
|
27
|
+
function getSurfaceVisibleMaterial(spec, shading) {
|
|
34
28
|
if (spec.materialPreset) {
|
|
35
|
-
return createMaterialFromPresetRef(spec.materialPreset) ?? baseMaterial;
|
|
29
|
+
return createMaterialFromPresetRef(spec.materialPreset, shading) ?? baseMaterial(shading);
|
|
36
30
|
}
|
|
37
31
|
if (spec.material) {
|
|
38
|
-
return createMaterial(spec.material);
|
|
32
|
+
return createMaterial(spec.material, shading);
|
|
33
|
+
}
|
|
34
|
+
return baseMaterial(shading);
|
|
35
|
+
}
|
|
36
|
+
function hasExplicitMaterial(spec) {
|
|
37
|
+
return Boolean(spec.materialPreset || spec.material);
|
|
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
|
+
}
|
|
39
137
|
}
|
|
40
|
-
|
|
138
|
+
const side = getWallSurfaceSideFromBandSlot(slotId);
|
|
139
|
+
return side ? resolveWallFaceColor(wallNode, side, sceneMaterials, fallback) : fallback;
|
|
41
140
|
}
|
|
42
141
|
function getSurfaceColor(spec, fallback = DEFAULT_WALL_COLOR) {
|
|
43
142
|
const preset = getMaterialPresetByRef(spec.materialPreset);
|
|
@@ -70,14 +169,92 @@ function createHighlightedWallMaterial(material, kind) {
|
|
|
70
169
|
highlightedMaterial.needsUpdate = true;
|
|
71
170
|
return highlightedMaterial;
|
|
72
171
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
}
|
|
226
|
+
function createInvisibleWallMaterial(color, shading) {
|
|
227
|
+
const material = shading === 'solid'
|
|
228
|
+
? new MeshLambertNodeMaterial({
|
|
229
|
+
transparent: true,
|
|
230
|
+
color,
|
|
231
|
+
depthWrite: false,
|
|
232
|
+
emissive: color,
|
|
233
|
+
})
|
|
234
|
+
: new MeshStandardNodeMaterial({
|
|
235
|
+
transparent: true,
|
|
236
|
+
color,
|
|
237
|
+
depthWrite: false,
|
|
238
|
+
emissive: color,
|
|
239
|
+
});
|
|
240
|
+
material.opacityNode = mix(float(0.0), float(0.24), dotPattern());
|
|
241
|
+
return material;
|
|
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;
|
|
81
258
|
}
|
|
82
259
|
function mapWallMaterialArray(materials, iteratee) {
|
|
83
260
|
return materials.map(iteratee);
|
|
@@ -93,15 +270,26 @@ function disposeOwnedMaterials(materials) {
|
|
|
93
270
|
material.dispose();
|
|
94
271
|
});
|
|
95
272
|
}
|
|
96
|
-
export function getWallMaterialHash(wallNode) {
|
|
273
|
+
export function getWallMaterialHash(wallNode, shading, sceneMaterials) {
|
|
97
274
|
return JSON.stringify({
|
|
98
|
-
|
|
99
|
-
|
|
275
|
+
shading,
|
|
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),
|
|
100
286
|
});
|
|
101
287
|
}
|
|
102
|
-
export function getMaterialsForWall(wallNode) {
|
|
103
|
-
const cacheKey = wallNode.id
|
|
104
|
-
const materialHash =
|
|
288
|
+
export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
|
|
289
|
+
const cacheKey = `${wallNode.id}-${shading}-${textures}-${colorPreset}-${sceneTheme ?? 'base'}`;
|
|
290
|
+
const materialHash = textures
|
|
291
|
+
? getWallMaterialHash(wallNode, shading, sceneMaterials)
|
|
292
|
+
: JSON.stringify({ textures, colorPreset, sceneTheme });
|
|
105
293
|
const existing = wallMaterialCache.get(cacheKey);
|
|
106
294
|
if (existing && existing.materialHash === materialHash) {
|
|
107
295
|
return existing;
|
|
@@ -109,40 +297,85 @@ export function getMaterialsForWall(wallNode) {
|
|
|
109
297
|
if (existing) {
|
|
110
298
|
disposeOwnedMaterials([
|
|
111
299
|
existing.invisible,
|
|
300
|
+
existing.translucent,
|
|
112
301
|
existing.deleteVisible,
|
|
113
302
|
existing.deleteInvisible,
|
|
114
|
-
existing.
|
|
115
|
-
existing.highlightedInvisible,
|
|
303
|
+
existing.deleteTranslucent,
|
|
116
304
|
]);
|
|
117
305
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
306
|
+
const wallRoleMaterial = createSurfaceRoleMaterial('wall', colorPreset, undefined, sceneTheme);
|
|
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.
|
|
311
|
+
const visible = textures
|
|
312
|
+
? [
|
|
313
|
+
wallRoleMaterial,
|
|
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),
|
|
324
|
+
]
|
|
325
|
+
: Array.from({ length: 11 }, () => wallRoleMaterial);
|
|
326
|
+
const wallRoleColor = resolveSurfaceColor('wall', colorPreset, sceneTheme);
|
|
125
327
|
const invisible = [
|
|
126
|
-
createInvisibleWallMaterial(
|
|
127
|
-
|
|
128
|
-
|
|
328
|
+
createInvisibleWallMaterial(wallRoleColor, textures ? shading : 'solid'),
|
|
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')),
|
|
129
362
|
];
|
|
130
|
-
const highlightedVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'selection'));
|
|
131
|
-
const highlightedInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'selection'));
|
|
132
363
|
const deleteVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
133
364
|
const deleteInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
365
|
+
const deleteTranslucent = mapWallMaterialArray(translucent, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
134
366
|
const result = {
|
|
135
367
|
visible,
|
|
136
368
|
invisible,
|
|
369
|
+
translucent,
|
|
137
370
|
deleteVisible,
|
|
138
371
|
deleteInvisible,
|
|
139
|
-
|
|
140
|
-
highlightedInvisible,
|
|
372
|
+
deleteTranslucent,
|
|
141
373
|
materialHash,
|
|
142
374
|
};
|
|
143
375
|
wallMaterialCache.set(cacheKey, result);
|
|
144
376
|
return result;
|
|
145
377
|
}
|
|
146
|
-
export function getVisibleWallMaterials(wallNode) {
|
|
147
|
-
return getMaterialsForWall(wallNode)
|
|
378
|
+
export function getVisibleWallMaterials(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
|
|
379
|
+
return getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme, sceneMaterials)
|
|
380
|
+
.visible;
|
|
148
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
|
+
});
|