@pascal-app/viewer 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/renderers/node-renderer.d.ts +1 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +38 -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 +34 -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/ground-occluder.d.ts +1 -1
- package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
- package/dist/components/viewer/ground-occluder.js +12 -24
- package/dist/components/viewer/index.d.ts +37 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +186 -63
- 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 +170 -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 +99 -11
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +231 -54
- 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 +47 -0
- package/dist/components/viewer/scene-bvh.d.ts +17 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.js +94 -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 +20 -5
- 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 +1 -1
- package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-node-events.d.ts +5 -73
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +19 -7
- package/dist/index.d.ts +36 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -3
- package/dist/lib/csg-utils.d.ts +16 -0
- package/dist/lib/csg-utils.d.ts.map +1 -0
- package/dist/lib/csg-utils.js +33 -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 +3 -0
- package/dist/lib/edge-style.d.ts.map +1 -0
- package/dist/lib/edge-style.js +11 -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 +43 -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/layers.d.ts +18 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +18 -0
- package/dist/lib/materials.d.ts +25 -15
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +277 -62
- 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 +29 -0
- package/dist/lib/scene-themes.d.ts.map +1 -0
- package/dist/lib/scene-themes.js +193 -0
- package/dist/store/use-viewer.d.ts +42 -4
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +26 -3
- 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 +62 -12
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +373 -22
- 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 +3 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +9 -5
- 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 +34 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.js +279 -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/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 +8 -11
- 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/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 +38 -14
- package/dist/systems/roof/roof-system.d.ts +27 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +396 -59
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +167 -49
- 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 +62 -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 +50 -61
- 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/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +18 -2
- package/dist/systems/wall/wall-materials.d.ts +4 -3
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +52 -28
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +171 -26
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +71 -7
- package/package.json +3 -4
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAsCA,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAsCA,eAAO,MAAM,UAAU,YAwItB,CAAA"}
|
|
@@ -33,10 +33,18 @@ export const WallCutout = () => {
|
|
|
33
33
|
const lastCameraTarget = useRef(new Vector3());
|
|
34
34
|
const lastUpdateTime = useRef(0);
|
|
35
35
|
const lastWallMode = useRef(useViewer.getState().wallMode);
|
|
36
|
+
const lastShading = useRef(useViewer.getState().shading);
|
|
36
37
|
const lastNumberOfWalls = useRef(0);
|
|
37
38
|
const lastHighlightKey = useRef('');
|
|
39
|
+
const lastTextures = useRef(useViewer.getState().textures);
|
|
40
|
+
const lastColorPreset = useRef(useViewer.getState().colorPreset);
|
|
41
|
+
const lastSceneTheme = useRef(useViewer.getState().sceneTheme);
|
|
38
42
|
useFrame(({ camera, clock }) => {
|
|
39
43
|
const wallMode = useViewer.getState().wallMode;
|
|
44
|
+
const shading = useViewer.getState().shading;
|
|
45
|
+
const textures = useViewer.getState().textures;
|
|
46
|
+
const colorPreset = useViewer.getState().colorPreset;
|
|
47
|
+
const sceneTheme = useViewer.getState().sceneTheme;
|
|
40
48
|
const selectedIds = useViewer.getState().selection.selectedIds;
|
|
41
49
|
const previewSelectedIds = useViewer.getState().previewSelectedIds;
|
|
42
50
|
const hoveredId = useViewer.getState().hoveredId;
|
|
@@ -57,6 +65,10 @@ export const WallCutout = () => {
|
|
|
57
65
|
const timeSinceUpdate = currentTime - lastUpdateTime.current;
|
|
58
66
|
if (((distanceMoved > 0.5 || directionChanged > 0.3) && timeSinceUpdate > 0.1) ||
|
|
59
67
|
lastWallMode.current !== wallMode ||
|
|
68
|
+
lastShading.current !== shading ||
|
|
69
|
+
lastTextures.current !== textures ||
|
|
70
|
+
lastColorPreset.current !== colorPreset ||
|
|
71
|
+
lastSceneTheme.current !== sceneTheme ||
|
|
60
72
|
sceneRegistry.byType.wall.size !== lastNumberOfWalls.current ||
|
|
61
73
|
lastHighlightKey.current !== highlightKey) {
|
|
62
74
|
lastCameraPosition.current.copy(currentCameraPosition);
|
|
@@ -74,7 +86,7 @@ export const WallCutout = () => {
|
|
|
74
86
|
const hideWall = getWallHideState(wallNode, wallMesh, wallMode, u);
|
|
75
87
|
const isDeleteHighlighted = deleteHoveredWallId === wallId;
|
|
76
88
|
const isSelectionHighlighted = !isDeleteHighlighted && highlightedWallIds.has(wallId);
|
|
77
|
-
const materials = getMaterialsForWall(wallNode);
|
|
89
|
+
const materials = getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme);
|
|
78
90
|
if (hideWall) {
|
|
79
91
|
;
|
|
80
92
|
wallMesh.material = isDeleteHighlighted
|
|
@@ -93,6 +105,10 @@ export const WallCutout = () => {
|
|
|
93
105
|
}
|
|
94
106
|
});
|
|
95
107
|
lastWallMode.current = wallMode;
|
|
108
|
+
lastShading.current = shading;
|
|
109
|
+
lastTextures.current = textures;
|
|
110
|
+
lastColorPreset.current = colorPreset;
|
|
111
|
+
lastSceneTheme.current = sceneTheme;
|
|
96
112
|
lastNumberOfWalls.current = sceneRegistry.byType.wall.size;
|
|
97
113
|
lastHighlightKey.current = highlightKey;
|
|
98
114
|
}
|
|
@@ -107,7 +123,7 @@ export const WallCutout = () => {
|
|
|
107
123
|
const wallNode = useScene.getState().nodes[wallId];
|
|
108
124
|
if (!wallNode || wallNode.type !== 'wall')
|
|
109
125
|
return;
|
|
110
|
-
const mats = getMaterialsForWall(wallNode);
|
|
126
|
+
const mats = getMaterialsForWall(wallNode, useViewer.getState().shading, useViewer.getState().textures, useViewer.getState().colorPreset, useViewer.getState().sceneTheme);
|
|
111
127
|
const current = wallMesh.material;
|
|
112
128
|
snapshot.set(wallMesh, current);
|
|
113
129
|
if (current === mats.highlightedVisible || current === mats.deleteVisible) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type WallNode } from '@pascal-app/core';
|
|
2
2
|
import { type Material } from 'three';
|
|
3
|
+
import { type ColorPreset, type RenderShading } from '../../lib/materials';
|
|
3
4
|
export type WallMaterialArray = [Material, Material, Material];
|
|
4
5
|
export interface WallMaterials {
|
|
5
6
|
visible: WallMaterialArray;
|
|
@@ -10,7 +11,7 @@ export interface WallMaterials {
|
|
|
10
11
|
highlightedInvisible: WallMaterialArray;
|
|
11
12
|
materialHash: string;
|
|
12
13
|
}
|
|
13
|
-
export declare function getWallMaterialHash(wallNode: WallNode): string;
|
|
14
|
-
export declare function getMaterialsForWall(wallNode: WallNode): WallMaterials;
|
|
15
|
-
export declare function getVisibleWallMaterials(wallNode: WallNode): WallMaterialArray;
|
|
14
|
+
export declare function getWallMaterialHash(wallNode: WallNode, shading: RenderShading): string;
|
|
15
|
+
export declare function getMaterialsForWall(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string): WallMaterials;
|
|
16
|
+
export declare function getVisibleWallMaterials(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string): WallMaterialArray;
|
|
16
17
|
//# 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,EAKL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAG5C,OAAO,EAEL,KAAK,WAAW,EAIhB,KAAK,aAAa,EAEnB,MAAM,qBAAqB,CAAA;AAqB5B,MAAM,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;AAE9D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,aAAa,EAAE,iBAAiB,CAAA;IAChC,eAAe,EAAE,iBAAiB,CAAA;IAClC,kBAAkB,EAAE,iBAAiB,CAAA;IACrC,oBAAoB,EAAE,iBAAiB,CAAA;IACvC,YAAY,EAAE,MAAM,CAAA;CACrB;AA6HD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAUtF;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,GAClB,aAAa,CA6Ef;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,GAClB,iBAAiB,CAEnB"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, resolveMaterial, } 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';
|
|
4
|
+
import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
5
|
+
import { baseMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, resolveSurfaceColor, } from '../../lib/materials';
|
|
6
6
|
const DEFAULT_WALL_COLOR = '#f2f0ed';
|
|
7
7
|
const WALL_HIGHLIGHT_PROFILES = {
|
|
8
8
|
delete: {
|
|
@@ -30,14 +30,17 @@ const dotPattern = Fn(() => {
|
|
|
30
30
|
const yFade = float(1).sub(smoothstep(float(0), fadeHeight, positionLocal.y));
|
|
31
31
|
return dots.mul(yFade);
|
|
32
32
|
});
|
|
33
|
-
function getSurfaceVisibleMaterial(spec) {
|
|
33
|
+
function getSurfaceVisibleMaterial(spec, shading) {
|
|
34
34
|
if (spec.materialPreset) {
|
|
35
|
-
return createMaterialFromPresetRef(spec.materialPreset) ?? baseMaterial;
|
|
35
|
+
return createMaterialFromPresetRef(spec.materialPreset, shading) ?? baseMaterial(shading);
|
|
36
36
|
}
|
|
37
37
|
if (spec.material) {
|
|
38
|
-
return createMaterial(spec.material);
|
|
38
|
+
return createMaterial(spec.material, shading);
|
|
39
39
|
}
|
|
40
|
-
return baseMaterial;
|
|
40
|
+
return baseMaterial(shading);
|
|
41
|
+
}
|
|
42
|
+
function hasExplicitMaterial(spec) {
|
|
43
|
+
return Boolean(spec.materialPreset || spec.material);
|
|
41
44
|
}
|
|
42
45
|
function getSurfaceColor(spec, fallback = DEFAULT_WALL_COLOR) {
|
|
43
46
|
const preset = getMaterialPresetByRef(spec.materialPreset);
|
|
@@ -70,14 +73,22 @@ function createHighlightedWallMaterial(material, kind) {
|
|
|
70
73
|
highlightedMaterial.needsUpdate = true;
|
|
71
74
|
return highlightedMaterial;
|
|
72
75
|
}
|
|
73
|
-
function createInvisibleWallMaterial(color) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
function createInvisibleWallMaterial(color, shading) {
|
|
77
|
+
const material = shading === 'solid'
|
|
78
|
+
? new MeshLambertNodeMaterial({
|
|
79
|
+
transparent: true,
|
|
80
|
+
color,
|
|
81
|
+
depthWrite: false,
|
|
82
|
+
emissive: color,
|
|
83
|
+
})
|
|
84
|
+
: new MeshStandardNodeMaterial({
|
|
85
|
+
transparent: true,
|
|
86
|
+
color,
|
|
87
|
+
depthWrite: false,
|
|
88
|
+
emissive: color,
|
|
89
|
+
});
|
|
90
|
+
material.opacityNode = mix(float(0.0), float(0.24), dotPattern());
|
|
91
|
+
return material;
|
|
81
92
|
}
|
|
82
93
|
function mapWallMaterialArray(materials, iteratee) {
|
|
83
94
|
return materials.map(iteratee);
|
|
@@ -93,15 +104,18 @@ function disposeOwnedMaterials(materials) {
|
|
|
93
104
|
material.dispose();
|
|
94
105
|
});
|
|
95
106
|
}
|
|
96
|
-
export function getWallMaterialHash(wallNode) {
|
|
107
|
+
export function getWallMaterialHash(wallNode, shading) {
|
|
97
108
|
return JSON.stringify({
|
|
109
|
+
shading,
|
|
98
110
|
interior: getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, 'interior')),
|
|
99
111
|
exterior: getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, 'exterior')),
|
|
100
112
|
});
|
|
101
113
|
}
|
|
102
|
-
export function getMaterialsForWall(wallNode) {
|
|
103
|
-
const cacheKey = wallNode.id
|
|
104
|
-
const materialHash =
|
|
114
|
+
export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme) {
|
|
115
|
+
const cacheKey = `${wallNode.id}-${shading}-${textures}-${colorPreset}-${sceneTheme ?? 'base'}`;
|
|
116
|
+
const materialHash = textures
|
|
117
|
+
? getWallMaterialHash(wallNode, shading)
|
|
118
|
+
: JSON.stringify({ textures, colorPreset, sceneTheme });
|
|
105
119
|
const existing = wallMaterialCache.get(cacheKey);
|
|
106
120
|
if (existing && existing.materialHash === materialHash) {
|
|
107
121
|
return existing;
|
|
@@ -117,15 +131,25 @@ export function getMaterialsForWall(wallNode) {
|
|
|
117
131
|
}
|
|
118
132
|
const interiorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'interior');
|
|
119
133
|
const exteriorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'exterior');
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
const wallRoleMaterial = createSurfaceRoleMaterial('wall', colorPreset, undefined, sceneTheme);
|
|
135
|
+
// Untextured surfaces take the themed wall role colour even with textures on;
|
|
136
|
+
// only surfaces with an explicit preset/material keep their texture.
|
|
137
|
+
const visible = textures
|
|
138
|
+
? [
|
|
139
|
+
wallRoleMaterial,
|
|
140
|
+
hasExplicitMaterial(interiorSpec)
|
|
141
|
+
? getSurfaceVisibleMaterial(interiorSpec, shading)
|
|
142
|
+
: wallRoleMaterial,
|
|
143
|
+
hasExplicitMaterial(exteriorSpec)
|
|
144
|
+
? getSurfaceVisibleMaterial(exteriorSpec, shading)
|
|
145
|
+
: wallRoleMaterial,
|
|
146
|
+
]
|
|
147
|
+
: [wallRoleMaterial, wallRoleMaterial, wallRoleMaterial];
|
|
148
|
+
const wallRoleColor = resolveSurfaceColor('wall', colorPreset, sceneTheme);
|
|
125
149
|
const invisible = [
|
|
126
|
-
createInvisibleWallMaterial(
|
|
127
|
-
createInvisibleWallMaterial(getSurfaceColor(interiorSpec,
|
|
128
|
-
createInvisibleWallMaterial(getSurfaceColor(exteriorSpec,
|
|
150
|
+
createInvisibleWallMaterial(wallRoleColor, textures ? shading : 'solid'),
|
|
151
|
+
createInvisibleWallMaterial(textures ? getSurfaceColor(interiorSpec, wallRoleColor) : wallRoleColor, textures ? shading : 'solid'),
|
|
152
|
+
createInvisibleWallMaterial(textures ? getSurfaceColor(exteriorSpec, wallRoleColor) : wallRoleColor, textures ? shading : 'solid'),
|
|
129
153
|
];
|
|
130
154
|
const highlightedVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'selection'));
|
|
131
155
|
const highlightedInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'selection'));
|
|
@@ -143,6 +167,6 @@ export function getMaterialsForWall(wallNode) {
|
|
|
143
167
|
wallMaterialCache.set(cacheKey, result);
|
|
144
168
|
return result;
|
|
145
169
|
}
|
|
146
|
-
export function getVisibleWallMaterials(wallNode) {
|
|
147
|
-
return getMaterialsForWall(wallNode).visible;
|
|
170
|
+
export function getVisibleWallMaterials(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme) {
|
|
171
|
+
return getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme).visible;
|
|
148
172
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAqBZ,KAAK,aAAa,EAClB,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA8T9B,eAAO,MAAM,UAAU,YAwJtB,CAAA;AAkGD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,GAChB,KAAK,CAAC,cAAc,CAyGtB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { calculateLevelMiters, DEFAULT_WALL_HEIGHT, getAdjacentWallIds, getWallCurveFrameAt, getWallMiterBoundaryPoints, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, pointToKey, resolveLevelId, sceneRegistry, spatialGridManager, useScene, } from '@pascal-app/core';
|
|
1
|
+
import { calculateLevelMiters, DEFAULT_WALL_HEIGHT, getAdjacentWallIds, getEffectiveNode, getWallCurveFrameAt, getWallMiterBoundaryPoints, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, pointToKey, resolveLevelId, sceneRegistry, spatialGridManager, useLiveNodeOverrides, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg';
|
|
@@ -208,49 +208,158 @@ function assignWallMaterialGroups(geometry, wall, boundaryEdges) {
|
|
|
208
208
|
// WALL SYSTEM
|
|
209
209
|
// ============================================================================
|
|
210
210
|
let useFrameNb = 0;
|
|
211
|
+
// ─── Drag-throttle state (singleton — one WallSystem mounted globally) ──
|
|
212
|
+
//
|
|
213
|
+
// Endpoint drags fire `markDirty(wallId)` on every pointermove tick. Without
|
|
214
|
+
// throttling, each tick rebuilds the dragged wall (~1 CSG + miter pass) AND
|
|
215
|
+
// every adjacent wall sharing a corner (3–4× in a t-junction or room).
|
|
216
|
+
// Visible as drag lag, especially on walls with door/window cutouts.
|
|
217
|
+
//
|
|
218
|
+
// Strategy: rebuild the dragged wall every tick (so the drag follows the
|
|
219
|
+
// cursor with full fidelity), but defer adjacent rebuilds to a trailing-
|
|
220
|
+
// edge flush DRAG_FLUSH_MS after the dirty stream stops. Visually, neighbor
|
|
221
|
+
// corners stay at their pre-drag miter until release, then snap into place
|
|
222
|
+
// within ~80ms. Standard CAD-app behavior. Speeds up t-junction drags ~3×,
|
|
223
|
+
// 4-corner-room drags ~4×.
|
|
224
|
+
const DRAG_FLUSH_MS = 80;
|
|
225
|
+
const MAX_WALL_REBUILDS_PER_FRAME = 8;
|
|
226
|
+
const WALL_PROGRESSIVE_DIRTY_THRESHOLD = MAX_WALL_REBUILDS_PER_FRAME;
|
|
227
|
+
const WALL_PROGRESSIVE_TIME_BUDGET_MS = 8;
|
|
228
|
+
let lastWallDirtyAtMs = 0;
|
|
229
|
+
const pendingAdjacentByLevel = new Map();
|
|
230
|
+
function getPendingAdjacentCount() {
|
|
231
|
+
let count = 0;
|
|
232
|
+
for (const ids of pendingAdjacentByLevel.values()) {
|
|
233
|
+
count += ids.size;
|
|
234
|
+
}
|
|
235
|
+
return count;
|
|
236
|
+
}
|
|
211
237
|
export const WallSystem = () => {
|
|
212
238
|
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
213
239
|
const clearDirty = useScene((state) => state.clearDirty);
|
|
240
|
+
// Subscribe so override-only changes (no scene write) still re-run
|
|
241
|
+
// this component, which lets the gate below pick up the latest
|
|
242
|
+
// `dirtyNodes` set from the same render pass that received the
|
|
243
|
+
// override-publishing `markDirty` call. Without this, very fast
|
|
244
|
+
// drags could land an override and a markDirty in the same React
|
|
245
|
+
// tick and the next `useFrame` would still see the stale closure.
|
|
246
|
+
useLiveNodeOverrides((s) => s.overrides);
|
|
214
247
|
useFrame(() => {
|
|
215
|
-
|
|
248
|
+
const hasDirty = dirtyNodes.size > 0;
|
|
249
|
+
const hasPending = pendingAdjacentByLevel.size > 0;
|
|
250
|
+
if (!hasDirty && !hasPending)
|
|
216
251
|
return;
|
|
217
252
|
const nodes = useScene.getState().nodes;
|
|
253
|
+
const now = performance.now();
|
|
218
254
|
// Collect dirty walls and their levels
|
|
219
255
|
const dirtyWallsByLevel = new Map();
|
|
256
|
+
let dirtyWallCount = 0;
|
|
220
257
|
useFrameNb += 1;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
dirtyWallsByLevel.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
258
|
+
if (hasDirty) {
|
|
259
|
+
dirtyNodes.forEach((id) => {
|
|
260
|
+
const node = nodes[id];
|
|
261
|
+
if (!node || node.type !== 'wall')
|
|
262
|
+
return;
|
|
263
|
+
const levelId = node.parentId;
|
|
264
|
+
if (!levelId)
|
|
265
|
+
return;
|
|
266
|
+
if (!dirtyWallsByLevel.has(levelId)) {
|
|
267
|
+
dirtyWallsByLevel.set(levelId, new Set());
|
|
268
|
+
}
|
|
269
|
+
dirtyWallsByLevel.get(levelId)?.add(id);
|
|
270
|
+
dirtyWallCount += 1;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const hasDirtyWalls = dirtyWallsByLevel.size > 0;
|
|
274
|
+
if (hasDirtyWalls) {
|
|
275
|
+
lastWallDirtyAtMs = now;
|
|
276
|
+
}
|
|
277
|
+
const useProgressiveWallRebuilds = dirtyWallCount > WALL_PROGRESSIVE_DIRTY_THRESHOLD;
|
|
278
|
+
let rebuiltWallsThisFrame = 0;
|
|
279
|
+
const rebuildFrameStartedAt = now;
|
|
233
280
|
// Process each level that has dirty walls
|
|
234
281
|
for (const [levelId, dirtyWallIds] of dirtyWallsByLevel) {
|
|
282
|
+
if (useProgressiveWallRebuilds && rebuiltWallsThisFrame >= MAX_WALL_REBUILDS_PER_FRAME) {
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
235
285
|
const levelWalls = getLevelWalls(levelId);
|
|
236
286
|
const miterData = calculateLevelMiters(levelWalls);
|
|
237
|
-
|
|
287
|
+
const rebuiltWallIds = new Set();
|
|
288
|
+
// Update dirty walls — always, no throttling. The dragged wall must
|
|
289
|
+
// follow the cursor with full fidelity (cutouts and all). Large imports
|
|
290
|
+
// enter the progressive path so initial load can't lock the tab.
|
|
238
291
|
for (const wallId of dirtyWallIds) {
|
|
292
|
+
if (useProgressiveWallRebuilds) {
|
|
293
|
+
if (rebuiltWallsThisFrame >= MAX_WALL_REBUILDS_PER_FRAME) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
if (rebuiltWallsThisFrame > 0 &&
|
|
297
|
+
performance.now() - rebuildFrameStartedAt >= WALL_PROGRESSIVE_TIME_BUDGET_MS) {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
239
301
|
const mesh = sceneRegistry.nodes.get(wallId);
|
|
240
302
|
if (mesh) {
|
|
241
303
|
updateWallGeometry(wallId, miterData);
|
|
242
304
|
clearDirty(wallId);
|
|
305
|
+
rebuiltWallIds.add(wallId);
|
|
306
|
+
rebuiltWallsThisFrame += 1;
|
|
243
307
|
}
|
|
244
308
|
// If mesh not found, keep it dirty for next frame
|
|
245
309
|
}
|
|
246
|
-
|
|
247
|
-
|
|
310
|
+
if (rebuiltWallIds.size === 0) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
// Adjacent walls sharing junctions — *defer* during active drag
|
|
314
|
+
// (dirty arrived this frame), flush on the trailing edge.
|
|
315
|
+
const adjacentWallIds = getAdjacentWallIds(levelWalls, rebuiltWallIds);
|
|
316
|
+
let pending = pendingAdjacentByLevel.get(levelId);
|
|
317
|
+
if (!pending) {
|
|
318
|
+
pending = new Set();
|
|
319
|
+
pendingAdjacentByLevel.set(levelId, pending);
|
|
320
|
+
}
|
|
248
321
|
for (const wallId of adjacentWallIds) {
|
|
249
322
|
if (!dirtyWallIds.has(wallId)) {
|
|
323
|
+
pending.add(wallId);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Trailing-edge flush: if no new dirty marks for DRAG_FLUSH_MS, the
|
|
328
|
+
// drag has ended — rebuild the queued neighbors so corners snap into
|
|
329
|
+
// their correct miter joins.
|
|
330
|
+
const quiet = !hasDirtyWalls && now - lastWallDirtyAtMs >= DRAG_FLUSH_MS;
|
|
331
|
+
if (quiet && pendingAdjacentByLevel.size > 0) {
|
|
332
|
+
const pendingCount = getPendingAdjacentCount();
|
|
333
|
+
const useProgressiveAdjacentRebuilds = pendingCount > WALL_PROGRESSIVE_DIRTY_THRESHOLD;
|
|
334
|
+
let rebuiltAdjacentThisFrame = 0;
|
|
335
|
+
const adjacentFrameStartedAt = performance.now();
|
|
336
|
+
for (const [levelId, pendingIds] of pendingAdjacentByLevel) {
|
|
337
|
+
if (pendingIds.size === 0)
|
|
338
|
+
continue;
|
|
339
|
+
const levelWalls = getLevelWalls(levelId);
|
|
340
|
+
const miterData = calculateLevelMiters(levelWalls);
|
|
341
|
+
for (const wallId of Array.from(pendingIds)) {
|
|
342
|
+
if (useProgressiveAdjacentRebuilds) {
|
|
343
|
+
if (rebuiltAdjacentThisFrame >= MAX_WALL_REBUILDS_PER_FRAME) {
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
if (rebuiltAdjacentThisFrame > 0 &&
|
|
347
|
+
performance.now() - adjacentFrameStartedAt >= WALL_PROGRESSIVE_TIME_BUDGET_MS) {
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
250
351
|
const mesh = sceneRegistry.nodes.get(wallId);
|
|
251
|
-
if (mesh)
|
|
352
|
+
if (mesh)
|
|
252
353
|
updateWallGeometry(wallId, miterData);
|
|
253
|
-
|
|
354
|
+
pendingIds.delete(wallId);
|
|
355
|
+
rebuiltAdjacentThisFrame += 1;
|
|
356
|
+
}
|
|
357
|
+
if (pendingIds.size === 0) {
|
|
358
|
+
pendingAdjacentByLevel.delete(levelId);
|
|
359
|
+
}
|
|
360
|
+
if (useProgressiveAdjacentRebuilds &&
|
|
361
|
+
rebuiltAdjacentThisFrame >= MAX_WALL_REBUILDS_PER_FRAME) {
|
|
362
|
+
break;
|
|
254
363
|
}
|
|
255
364
|
}
|
|
256
365
|
}
|
|
@@ -258,7 +367,22 @@ export const WallSystem = () => {
|
|
|
258
367
|
return null;
|
|
259
368
|
};
|
|
260
369
|
/**
|
|
261
|
-
*
|
|
370
|
+
* Merge any live override for a wall into the scene record. Lets the
|
|
371
|
+
* 2D move handler publish `{ start, end, curveOffset }` to
|
|
372
|
+
* `useLiveNodeOverrides` and have the geometry / miter pipeline use
|
|
373
|
+
* those values without zustand churn during the drag. When no
|
|
374
|
+
* override is set, the wall is returned unchanged.
|
|
375
|
+
*/
|
|
376
|
+
function getEffectiveWall(wall) {
|
|
377
|
+
const override = useLiveNodeOverrides.getState().get(wall.id);
|
|
378
|
+
if (!override || Object.keys(override).length === 0)
|
|
379
|
+
return wall;
|
|
380
|
+
return { ...wall, ...override };
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Gets all walls that belong to a level, with any live overrides
|
|
384
|
+
* merged in so miters compute against the cursor-driven positions
|
|
385
|
+
* (not the pre-drag scene state).
|
|
262
386
|
*/
|
|
263
387
|
function getLevelWalls(levelId) {
|
|
264
388
|
const { nodes } = useScene.getState();
|
|
@@ -269,28 +393,49 @@ function getLevelWalls(levelId) {
|
|
|
269
393
|
for (const childId of level.children) {
|
|
270
394
|
const child = nodes[childId];
|
|
271
395
|
if (child?.type === 'wall') {
|
|
272
|
-
walls.push(child);
|
|
396
|
+
walls.push(getEffectiveWall(child));
|
|
273
397
|
}
|
|
274
398
|
}
|
|
275
399
|
return walls;
|
|
276
400
|
}
|
|
277
401
|
/**
|
|
278
|
-
* Updates the geometry for a single wall
|
|
402
|
+
* Updates the geometry for a single wall. Reads the effective node
|
|
403
|
+
* (override-merged) so a 2D drag visibly moves the 3D mesh without
|
|
404
|
+
* having touched `useScene` mid-drag.
|
|
279
405
|
*/
|
|
280
406
|
function updateWallGeometry(wallId, miterData) {
|
|
281
407
|
const nodes = useScene.getState().nodes;
|
|
282
|
-
const
|
|
283
|
-
if (!
|
|
408
|
+
const sceneNode = nodes[wallId];
|
|
409
|
+
if (!sceneNode || sceneNode.type !== 'wall')
|
|
284
410
|
return;
|
|
411
|
+
const node = getEffectiveWall(sceneNode);
|
|
285
412
|
const mesh = sceneRegistry.nodes.get(wallId);
|
|
286
413
|
if (!mesh)
|
|
287
414
|
return;
|
|
288
415
|
const levelId = resolveLevelId(node, nodes);
|
|
289
|
-
const slabElevation = spatialGridManager.getSlabElevationForWall(levelId, node.start, node.end);
|
|
416
|
+
const slabElevation = spatialGridManager.getSlabElevationForWall(levelId, node.start, node.end, node.curveOffset ?? 0, node.thickness);
|
|
290
417
|
const childrenIds = node.children || [];
|
|
418
|
+
// Merge live overrides into door / window children so cutouts track an
|
|
419
|
+
// in-flight resize drag (door width arrow, window height arrow, etc.)
|
|
420
|
+
// without waiting on the scene store. Non-cutout children pass through
|
|
421
|
+
// unchanged.
|
|
291
422
|
const childrenNodes = childrenIds
|
|
292
423
|
.map((childId) => nodes[childId])
|
|
293
|
-
.filter((n) => n !== undefined)
|
|
424
|
+
.filter((n) => n !== undefined)
|
|
425
|
+
.map((child) => {
|
|
426
|
+
if (child.type !== 'door' && child.type !== 'window')
|
|
427
|
+
return child;
|
|
428
|
+
// `getEffectiveNode` folds in resize overrides (width/height arrows).
|
|
429
|
+
// Position moves publish to `useLiveTransforms` instead, so fold that
|
|
430
|
+
// in too — otherwise shaped openings (arch/rounded/`opening`), whose
|
|
431
|
+
// cutout brush is rebuilt from `node.position`, lag the live move
|
|
432
|
+
// (rectangular cutouts already track via the live mesh matrixWorld).
|
|
433
|
+
const effective = getEffectiveNode(child);
|
|
434
|
+
const live = useLiveTransforms.getState().get(child.id);
|
|
435
|
+
if (!live?.position)
|
|
436
|
+
return effective;
|
|
437
|
+
return { ...effective, position: live.position };
|
|
438
|
+
});
|
|
294
439
|
const newGeo = generateExtrudedWall(node, childrenNodes, miterData, slabElevation);
|
|
295
440
|
mesh.geometry.dispose();
|
|
296
441
|
mesh.geometry = newGeo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"AAuBA,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,8BAA8B,8BAA8B,CAAA;AACzE,eAAO,MAAM,+BAA+B,+BAA+B,CAAA;AAC3E,eAAO,MAAM,gCAAgC,gCAAgC,CAAA;AAC7E,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,0BAA0B,0BAA0B,CAAA;AACjE,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAC3D,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAM3D,eAAO,MAAM,YAAY,YA0FxB,CAAA"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { sceneRegistry, useInteractive, useScene, } from '@pascal-app/core';
|
|
1
|
+
import { getEffectiveNode, sceneRegistry, useInteractive, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
3
4
|
import * as THREE from 'three';
|
|
4
|
-
import {
|
|
5
|
+
import { createSurfaceRoleMaterial, glassMaterial as defaultGlassMaterial, baseMaterial as getBaseMaterial, } from '../../lib/materials';
|
|
6
|
+
import useViewer from '../../store/use-viewer';
|
|
5
7
|
// Invisible material for root mesh — used as selection hitbox only
|
|
6
8
|
const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false });
|
|
9
|
+
let baseMaterial = getBaseMaterial();
|
|
10
|
+
let glassMaterial = defaultGlassMaterial;
|
|
7
11
|
export const CASEMENT_WINDOW_SASH_NAME = 'casement-window-sash';
|
|
8
12
|
export const FRENCH_CASEMENT_LEFT_SASH_NAME = 'french-casement-left-sash';
|
|
9
13
|
export const FRENCH_CASEMENT_RIGHT_SASH_NAME = 'french-casement-right-sash';
|
|
@@ -14,27 +18,87 @@ export const DOUBLE_HUNG_BOTTOM_SASH_NAME = 'double-hung-bottom-sash';
|
|
|
14
18
|
export const LOUVERED_WINDOW_SLATS_NAME = 'louvered-window-slats';
|
|
15
19
|
export const AWNING_WINDOW_SASH_NAME = 'awning-window-sash';
|
|
16
20
|
export const HOPPER_WINDOW_SASH_NAME = 'hopper-window-sash';
|
|
21
|
+
const MAX_WINDOW_REBUILDS_PER_FRAME = 16;
|
|
22
|
+
const WINDOW_PROGRESSIVE_DIRTY_THRESHOLD = MAX_WINDOW_REBUILDS_PER_FRAME;
|
|
23
|
+
const WINDOW_PROGRESSIVE_TIME_BUDGET_MS = 8;
|
|
17
24
|
export const WindowSystem = () => {
|
|
18
25
|
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
19
26
|
const clearDirty = useScene((state) => state.clearDirty);
|
|
27
|
+
const shading = useViewer((state) => state.shading);
|
|
28
|
+
const textures = useViewer((state) => state.textures);
|
|
29
|
+
const colorPreset = useViewer((state) => state.colorPreset);
|
|
30
|
+
const materialRevisionRef = useRef(null);
|
|
31
|
+
// Subscribe so override-only updates re-run this component. Mirrors
|
|
32
|
+
// WallSystem + DoorSystem.
|
|
33
|
+
useLiveNodeOverrides((s) => s.overrides);
|
|
34
|
+
baseMaterial = textures
|
|
35
|
+
? getBaseMaterial(shading)
|
|
36
|
+
: createSurfaceRoleMaterial('joinery', colorPreset);
|
|
37
|
+
glassMaterial = textures
|
|
38
|
+
? defaultGlassMaterial
|
|
39
|
+
: createSurfaceRoleMaterial('glazing', colorPreset);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const materialRevision = `${shading}:${textures ? 'textures' : 'solid'}:${colorPreset}`;
|
|
42
|
+
if (materialRevisionRef.current === materialRevision)
|
|
43
|
+
return;
|
|
44
|
+
materialRevisionRef.current = materialRevision;
|
|
45
|
+
const nodes = useScene.getState().nodes;
|
|
46
|
+
for (const node of Object.values(nodes)) {
|
|
47
|
+
if (node?.type === 'window') {
|
|
48
|
+
useScene.getState().dirtyNodes.add(node.id);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
20
52
|
useFrame(() => {
|
|
21
53
|
if (dirtyNodes.size === 0)
|
|
22
54
|
return;
|
|
55
|
+
baseMaterial = textures
|
|
56
|
+
? getBaseMaterial(shading)
|
|
57
|
+
: createSurfaceRoleMaterial('joinery', colorPreset);
|
|
58
|
+
glassMaterial = textures
|
|
59
|
+
? defaultGlassMaterial
|
|
60
|
+
: createSurfaceRoleMaterial('glazing', colorPreset);
|
|
23
61
|
const nodes = useScene.getState().nodes;
|
|
62
|
+
const dirtyWindowIds = [];
|
|
24
63
|
dirtyNodes.forEach((id) => {
|
|
25
64
|
const node = nodes[id];
|
|
26
65
|
if (!node || node.type !== 'window')
|
|
27
66
|
return;
|
|
67
|
+
dirtyWindowIds.push(id);
|
|
68
|
+
});
|
|
69
|
+
const useProgressiveWindowRebuilds = dirtyWindowIds.length > WINDOW_PROGRESSIVE_DIRTY_THRESHOLD;
|
|
70
|
+
const frameStartedAt = performance.now();
|
|
71
|
+
let rebuiltWindowsThisFrame = 0;
|
|
72
|
+
for (const id of dirtyWindowIds) {
|
|
73
|
+
if (useProgressiveWindowRebuilds) {
|
|
74
|
+
if (rebuiltWindowsThisFrame >= MAX_WINDOW_REBUILDS_PER_FRAME) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
if (rebuiltWindowsThisFrame > 0 &&
|
|
78
|
+
performance.now() - frameStartedAt >= WINDOW_PROGRESSIVE_TIME_BUDGET_MS) {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const node = nodes[id];
|
|
83
|
+
if (!node || node.type !== 'window')
|
|
84
|
+
continue;
|
|
28
85
|
const mesh = sceneRegistry.nodes.get(id);
|
|
29
86
|
if (!mesh)
|
|
30
|
-
|
|
31
|
-
|
|
87
|
+
continue; // Keep dirty until mesh mounts
|
|
88
|
+
// Merge any live override (width / height / position) so the mesh
|
|
89
|
+
// rebuild reflects the in-flight drag without zustand churn.
|
|
90
|
+
const effectiveNode = getEffectiveNode(node);
|
|
91
|
+
updateWindowMesh(effectiveNode, mesh);
|
|
32
92
|
clearDirty(id);
|
|
93
|
+
rebuiltWindowsThisFrame += 1;
|
|
33
94
|
// Rebuild the parent wall so its cutout reflects the updated window geometry
|
|
34
|
-
|
|
35
|
-
|
|
95
|
+
// Avoid triggering expensive wall CSG rebuilds while the window is being interactively moved/duplicated.
|
|
96
|
+
// The editor tools will request a final wall rebuild on commit.
|
|
97
|
+
const isTransient = !!node.metadata?.isTransient;
|
|
98
|
+
if (!isTransient && effectiveNode.parentId) {
|
|
99
|
+
useScene.getState().dirtyNodes.add(effectiveNode.parentId);
|
|
36
100
|
}
|
|
37
|
-
}
|
|
101
|
+
}
|
|
38
102
|
}, 3);
|
|
39
103
|
return null;
|
|
40
104
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "3D viewer component for Pascal building editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,14 +22,13 @@
|
|
|
22
22
|
"prepublishOnly": "npm run build"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@pascal-app/core": "^0.
|
|
25
|
+
"@pascal-app/core": "^0.9.1",
|
|
26
26
|
"@react-three/drei": "^10",
|
|
27
27
|
"@react-three/fiber": "^9",
|
|
28
28
|
"react": "^18 || ^19",
|
|
29
29
|
"three": "^0.184"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"polygon-clipping": "^0.15.7",
|
|
33
32
|
"three-bvh-csg": "^0.0.18",
|
|
34
33
|
"three-mesh-bvh": "^0.9.8",
|
|
35
34
|
"zustand": "^5"
|
|
@@ -39,7 +38,7 @@
|
|
|
39
38
|
"@types/node": "^22",
|
|
40
39
|
"@types/react": "^19.2.2",
|
|
41
40
|
"@types/three": "^0.184.0",
|
|
42
|
-
"typescript": "6.0.
|
|
41
|
+
"typescript": "6.0.3"
|
|
43
42
|
},
|
|
44
43
|
"keywords": [
|
|
45
44
|
"3d",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"building-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/building/building-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,kBAAkB,CAAA;AAMjE,eAAO,MAAM,gBAAgB,GAAI,UAAU;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,4CAkBhE,CAAA"}
|