@pascal-app/viewer 0.9.1 → 0.9.2

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