@pascal-app/viewer 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/dist/components/viewer/daylight-atmosphere.d.ts +9 -0
  2. package/dist/components/viewer/daylight-atmosphere.d.ts.map +1 -0
  3. package/dist/components/viewer/daylight-atmosphere.js +315 -0
  4. package/dist/components/viewer/drone-controller.d.ts +15 -0
  5. package/dist/components/viewer/drone-controller.d.ts.map +1 -0
  6. package/dist/components/viewer/drone-controller.js +194 -0
  7. package/dist/components/viewer/glb-interactive.d.ts +4 -18
  8. package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
  9. package/dist/components/viewer/glb-interactive.js +32 -268
  10. package/dist/components/viewer/glb-scene-motion.d.ts +8 -0
  11. package/dist/components/viewer/glb-scene-motion.d.ts.map +1 -0
  12. package/dist/components/viewer/glb-scene-motion.js +57 -0
  13. package/dist/components/viewer/glb-scene.d.ts +3 -1
  14. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  15. package/dist/components/viewer/glb-scene.js +289 -49
  16. package/dist/components/viewer/glb-selection-callout.d.ts +17 -0
  17. package/dist/components/viewer/glb-selection-callout.d.ts.map +1 -0
  18. package/dist/components/viewer/glb-selection-callout.js +54 -0
  19. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
  20. package/dist/components/viewer/glb-walkthrough-controller.js +6 -3
  21. package/dist/components/viewer/index.d.ts +5 -0
  22. package/dist/components/viewer/index.d.ts.map +1 -1
  23. package/dist/components/viewer/index.js +30 -6
  24. package/dist/components/viewer/lights.d.ts.map +1 -1
  25. package/dist/components/viewer/lights.js +192 -5
  26. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  27. package/dist/components/viewer/post-processing.js +150 -94
  28. package/dist/components/viewer/scene-environment.js +4 -5
  29. package/dist/components/viewer/selection-callout.d.ts +51 -0
  30. package/dist/components/viewer/selection-callout.d.ts.map +1 -0
  31. package/dist/components/viewer/selection-callout.js +197 -0
  32. package/dist/components/viewer/use-daylight-rooms.d.ts +13 -0
  33. package/dist/components/viewer/use-daylight-rooms.d.ts.map +1 -0
  34. package/dist/components/viewer/use-daylight-rooms.js +236 -0
  35. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  36. package/dist/components/viewer/viewer-camera.js +12 -1
  37. package/dist/index.d.ts +21 -7
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +23 -7
  40. package/dist/lib/daylight/ground.d.ts +21 -0
  41. package/dist/lib/daylight/ground.d.ts.map +1 -0
  42. package/dist/lib/daylight/ground.js +53 -0
  43. package/dist/lib/daylight/location.d.ts +32 -0
  44. package/dist/lib/daylight/location.d.ts.map +1 -0
  45. package/dist/lib/daylight/location.js +74 -0
  46. package/dist/lib/daylight/look.d.ts +33 -0
  47. package/dist/lib/daylight/look.d.ts.map +1 -0
  48. package/dist/lib/daylight/look.js +13 -0
  49. package/dist/lib/daylight/material-hooks.d.ts +2 -0
  50. package/dist/lib/daylight/material-hooks.d.ts.map +1 -0
  51. package/dist/lib/daylight/material-hooks.js +150 -0
  52. package/dist/lib/daylight/room-context.d.ts +12 -0
  53. package/dist/lib/daylight/room-context.d.ts.map +1 -0
  54. package/dist/lib/daylight/room-context.js +53 -0
  55. package/dist/lib/daylight/room-scene.d.ts +54 -0
  56. package/dist/lib/daylight/room-scene.d.ts.map +1 -0
  57. package/dist/lib/daylight/room-scene.js +356 -0
  58. package/dist/lib/daylight/rooms.d.ts +262 -0
  59. package/dist/lib/daylight/rooms.d.ts.map +1 -0
  60. package/dist/lib/daylight/rooms.js +1223 -0
  61. package/dist/lib/daylight/runtime.d.ts +33 -0
  62. package/dist/lib/daylight/runtime.d.ts.map +1 -0
  63. package/dist/lib/daylight/runtime.js +42 -0
  64. package/dist/lib/daylight/sky.d.ts +26 -0
  65. package/dist/lib/daylight/sky.d.ts.map +1 -0
  66. package/dist/lib/daylight/sky.js +217 -0
  67. package/dist/lib/daylight/solar.d.ts +46 -0
  68. package/dist/lib/daylight/solar.d.ts.map +1 -0
  69. package/dist/lib/daylight/solar.js +156 -0
  70. package/dist/lib/device-tier.d.ts +29 -0
  71. package/dist/lib/device-tier.d.ts.map +1 -0
  72. package/dist/lib/device-tier.js +71 -0
  73. package/dist/lib/dispose-object3d.d.ts.map +1 -1
  74. package/dist/lib/dispose-object3d.js +4 -2
  75. package/dist/lib/drone-flight.d.ts +44 -0
  76. package/dist/lib/drone-flight.d.ts.map +1 -0
  77. package/dist/lib/drone-flight.js +121 -0
  78. package/dist/lib/editor-highlight.d.ts +16 -0
  79. package/dist/lib/editor-highlight.d.ts.map +1 -0
  80. package/dist/lib/editor-highlight.js +33 -0
  81. package/dist/lib/foundation-materials.d.ts.map +1 -1
  82. package/dist/lib/foundation-materials.js +2 -4
  83. package/dist/lib/glb-merge.d.ts +23 -0
  84. package/dist/lib/glb-merge.d.ts.map +1 -0
  85. package/dist/lib/glb-merge.js +184 -0
  86. package/dist/lib/hardware-geometry.d.ts +21 -0
  87. package/dist/lib/hardware-geometry.d.ts.map +1 -0
  88. package/dist/lib/hardware-geometry.js +283 -0
  89. package/dist/lib/layer-pass.d.ts +2 -0
  90. package/dist/lib/layer-pass.d.ts.map +1 -1
  91. package/dist/lib/layer-pass.js +20 -0
  92. package/dist/lib/materials.d.ts.map +1 -1
  93. package/dist/lib/materials.js +50 -23
  94. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  95. package/dist/lib/merged-outline-node.js +7 -1
  96. package/dist/lib/occlusion-culler.d.ts +34 -0
  97. package/dist/lib/occlusion-culler.d.ts.map +1 -0
  98. package/dist/lib/occlusion-culler.js +698 -0
  99. package/dist/lib/post-processing-resources.d.ts +4 -0
  100. package/dist/lib/post-processing-resources.d.ts.map +1 -1
  101. package/dist/lib/post-processing-resources.js +5 -0
  102. package/dist/lib/readback-rows.d.ts +14 -0
  103. package/dist/lib/readback-rows.d.ts.map +1 -0
  104. package/dist/lib/readback-rows.js +31 -0
  105. package/dist/lib/reveal-pose.d.ts.map +1 -1
  106. package/dist/lib/reveal-pose.js +3 -0
  107. package/dist/lib/scene-matrices.d.ts +12 -0
  108. package/dist/lib/scene-matrices.d.ts.map +1 -0
  109. package/dist/lib/scene-matrices.js +287 -0
  110. package/dist/lib/scene-pass-mrt.d.ts +5 -2
  111. package/dist/lib/scene-pass-mrt.d.ts.map +1 -1
  112. package/dist/lib/scene-pass-mrt.js +7 -2
  113. package/dist/lib/scene-themes.d.ts +0 -2
  114. package/dist/lib/scene-themes.d.ts.map +1 -1
  115. package/dist/lib/scene-themes.js +0 -2
  116. package/dist/lib/scene-visibility.d.ts +2 -0
  117. package/dist/lib/scene-visibility.d.ts.map +1 -1
  118. package/dist/lib/scene-visibility.js +12 -3
  119. package/dist/lib/site-surfaces.d.ts +1 -8
  120. package/dist/lib/site-surfaces.d.ts.map +1 -1
  121. package/dist/lib/site-surfaces.js +0 -19
  122. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  123. package/dist/lib/snapshot-pipeline.js +24 -40
  124. package/dist/lib/texture-reference.d.ts.map +1 -1
  125. package/dist/lib/texture-reference.js +10 -8
  126. package/dist/lib/underground-ambiance.d.ts +0 -2
  127. package/dist/lib/underground-ambiance.d.ts.map +1 -1
  128. package/dist/lib/underground-ambiance.js +0 -2
  129. package/dist/store/use-item-light-pool.d.ts +3 -6
  130. package/dist/store/use-item-light-pool.d.ts.map +1 -1
  131. package/dist/store/use-item-light-pool.js +12 -0
  132. package/dist/store/use-viewer.d.ts +72 -15
  133. package/dist/store/use-viewer.d.ts.map +1 -1
  134. package/dist/store/use-viewer.js +103 -18
  135. package/dist/systems/ceiling/ceiling-system.d.ts +5 -0
  136. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  137. package/dist/systems/ceiling/ceiling-system.js +11 -1
  138. package/dist/systems/door/door-system.d.ts.map +1 -1
  139. package/dist/systems/door/door-system.js +501 -145
  140. package/dist/systems/door/leaf-profile.d.ts +11 -0
  141. package/dist/systems/door/leaf-profile.d.ts.map +1 -0
  142. package/dist/systems/door/leaf-profile.js +140 -0
  143. package/dist/systems/interactive/callout-actions.d.ts +72 -0
  144. package/dist/systems/interactive/callout-actions.d.ts.map +1 -0
  145. package/dist/systems/interactive/callout-actions.js +212 -0
  146. package/dist/systems/interactive/interactive-system.d.ts +5 -1
  147. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  148. package/dist/systems/interactive/interactive-system.js +44 -121
  149. package/dist/systems/interactive/opening-settled.d.ts +16 -0
  150. package/dist/systems/interactive/opening-settled.d.ts.map +1 -0
  151. package/dist/systems/interactive/opening-settled.js +53 -0
  152. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  153. package/dist/systems/item-light/item-light-system.js +22 -214
  154. package/dist/systems/item-light/pooled-lights.d.ts +70 -0
  155. package/dist/systems/item-light/pooled-lights.d.ts.map +1 -0
  156. package/dist/systems/item-light/pooled-lights.js +449 -0
  157. package/dist/systems/level/level-system.d.ts.map +1 -1
  158. package/dist/systems/level/level-system.js +19 -1
  159. package/dist/systems/roof/roof-material-slots.d.ts +14 -0
  160. package/dist/systems/roof/roof-material-slots.d.ts.map +1 -0
  161. package/dist/systems/roof/roof-material-slots.js +13 -0
  162. package/dist/systems/roof/roof-materials.d.ts +25 -2
  163. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  164. package/dist/systems/roof/roof-materials.js +85 -26
  165. package/dist/systems/roof/roof-system.d.ts +34 -1
  166. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  167. package/dist/systems/roof/roof-system.js +477 -105
  168. package/dist/systems/wall/opening-casing-geometry.d.ts +14 -0
  169. package/dist/systems/wall/opening-casing-geometry.d.ts.map +1 -0
  170. package/dist/systems/wall/opening-casing-geometry.js +102 -0
  171. package/dist/systems/wall/opening-cutout-geometry.d.ts +9 -0
  172. package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -1
  173. package/dist/systems/wall/opening-cutout-geometry.js +7 -3
  174. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  175. package/dist/systems/wall/wall-materials.js +28 -35
  176. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  177. package/dist/systems/wall/wall-system.js +34 -19
  178. package/dist/systems/wall/wall-t-junctions.d.ts +15 -0
  179. package/dist/systems/wall/wall-t-junctions.d.ts.map +1 -0
  180. package/dist/systems/wall/wall-t-junctions.js +149 -0
  181. package/dist/systems/window/window-animation-system.d.ts +3 -12
  182. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  183. package/dist/systems/window/window-animation-system.js +37 -122
  184. package/dist/systems/window/window-pose.d.ts +31 -0
  185. package/dist/systems/window/window-pose.d.ts.map +1 -0
  186. package/dist/systems/window/window-pose.js +152 -0
  187. package/dist/systems/window/window-roller-shutter.d.ts +8 -0
  188. package/dist/systems/window/window-roller-shutter.d.ts.map +1 -0
  189. package/dist/systems/window/window-roller-shutter.js +64 -0
  190. package/dist/systems/window/window-system.d.ts +2 -11
  191. package/dist/systems/window/window-system.d.ts.map +1 -1
  192. package/dist/systems/window/window-system.js +470 -303
  193. package/package.json +6 -6
  194. package/dist/systems/interactive/control-widget.d.ts +0 -10
  195. package/dist/systems/interactive/control-widget.d.ts.map +0 -1
  196. package/dist/systems/interactive/control-widget.js +0 -35
  197. package/dist/systems/interactive/procedural-controls.d.ts +0 -13
  198. package/dist/systems/interactive/procedural-controls.d.ts.map +0 -1
  199. package/dist/systems/interactive/procedural-controls.js +0 -17
@@ -0,0 +1,14 @@
1
+ import { type DoorNode, type OpeningCasingHost, type WindowNode } from '@pascal-app/core';
2
+ import * as THREE from 'three';
3
+ /** Name of the meshes a door or window builds its casing from. */
4
+ export declare const OPENING_CASING_MESH_NAME = "opening-casing";
5
+ /**
6
+ * A door's or window's casing as meshes in the unit's own frame, to hang on the
7
+ * mesh its frame is built on: the boards of `openingCasingBoards` on each cased
8
+ * face of the host, turned half round with a unit that faces the host's back. A
9
+ * rectangular opening is cased with butted boards. An arched or rounded one is
10
+ * cased with a band that follows the cutout's own profile over its head and down
11
+ * its sides; a window's bottom board stays straight below it.
12
+ */
13
+ export declare function buildOpeningCasingMeshes(opening: DoorNode | WindowNode, host: OpeningCasingHost, material: THREE.Material): THREE.Mesh[];
14
+ //# sourceMappingURL=opening-casing-geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opening-casing-geometry.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/opening-casing-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,iBAAiB,EAKtB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,kEAAkE;AAClE,eAAO,MAAM,wBAAwB,mBAAmB,CAAA;AAExD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,QAAQ,GAAG,UAAU,EAC9B,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ,GACvB,KAAK,CAAC,IAAI,EAAE,CAwCd"}
@@ -0,0 +1,102 @@
1
+ import { getDoorOpeningSize, OPENING_CASING_PROUD, openingCasingBoards, openingCasingFaces, openingCasingWidth, openingHostFacing, } from '@pascal-app/core';
2
+ import * as THREE from 'three';
3
+ import { applyWorldScaleBoxUVs } from '../../lib/box-uv';
4
+ import { buildOpeningCutoutShape, getRoundedOpeningRadii } from './opening-cutout-geometry';
5
+ /** Name of the meshes a door or window builds its casing from. */
6
+ export const OPENING_CASING_MESH_NAME = 'opening-casing';
7
+ /**
8
+ * A door's or window's casing as meshes in the unit's own frame, to hang on the
9
+ * mesh its frame is built on: the boards of `openingCasingBoards` on each cased
10
+ * face of the host, turned half round with a unit that faces the host's back. A
11
+ * rectangular opening is cased with butted boards. An arched or rounded one is
12
+ * cased with a band that follows the cutout's own profile over its head and down
13
+ * its sides; a window's bottom board stays straight below it.
14
+ */
15
+ export function buildOpeningCasingMeshes(opening, host, material) {
16
+ const facing = openingHostFacing(opening);
17
+ const size = getDoorOpeningSize(opening);
18
+ const shaped = size.width === opening.width &&
19
+ size.height === opening.height &&
20
+ (opening.openingShape === 'arch' || opening.openingShape === 'rounded');
21
+ const meshes = [];
22
+ const add = (geometry, face) => {
23
+ // Turned half round, the unit's x and z run against the host's.
24
+ if (facing < 0)
25
+ geometry.rotateY(Math.PI);
26
+ const mesh = new THREE.Mesh(geometry, material);
27
+ mesh.name = OPENING_CASING_MESH_NAME;
28
+ mesh.userData.slotId = `casing-${face}`;
29
+ meshes.push(mesh);
30
+ };
31
+ for (const board of openingCasingBoards(opening, host)) {
32
+ if (shaped && (board.part === 'head' || board.part === 'leg'))
33
+ continue;
34
+ const [width, height, depth] = board.size;
35
+ const geometry = new THREE.BoxGeometry(width, height, depth);
36
+ applyWorldScaleBoxUVs(geometry, width, height, depth);
37
+ geometry.translate(...board.center);
38
+ add(geometry, board.face);
39
+ }
40
+ if (shaped) {
41
+ const band = casingBandShape(opening, openingCasingWidth(opening));
42
+ for (const { face, plane, out } of openingCasingFaces(opening, host)) {
43
+ const geometry = new THREE.ExtrudeGeometry(band, {
44
+ depth: OPENING_CASING_PROUD,
45
+ bevelEnabled: false,
46
+ curveSegments: 24,
47
+ });
48
+ geometry.translate(0, 0, out > 0 ? plane : plane - OPENING_CASING_PROUD);
49
+ add(geometry, face);
50
+ }
51
+ }
52
+ return meshes;
53
+ }
54
+ /**
55
+ * The casing band of an arched or rounded opening as one outline, in the host
56
+ * frame: out from the cutout's profile by `width` over the head and down both
57
+ * sides, open along the bottom (a door's floor, a window's bottom board).
58
+ */
59
+ function casingBandShape(opening, width) {
60
+ const halfWidth = opening.width / 2;
61
+ const halfHeight = opening.height / 2;
62
+ const inner = buildOpeningCutoutShape(opening, {
63
+ left: -halfWidth,
64
+ right: halfWidth,
65
+ bottom: -halfHeight,
66
+ top: halfHeight,
67
+ }).getPoints(24);
68
+ const outer = buildOpeningCutoutShape(grownOpening(opening, width), {
69
+ left: -halfWidth - width,
70
+ right: halfWidth + width,
71
+ bottom: -halfHeight,
72
+ top: halfHeight + width,
73
+ }).getPoints(24);
74
+ // Both profiles start along the bottom, left to right, and end back at their
75
+ // first point: the band runs up and over the outer one from its bottom right,
76
+ // then back over the inner one from its bottom left.
77
+ return new THREE.Shape([...outer.slice(1), ...inner.slice(1).reverse()]);
78
+ }
79
+ /**
80
+ * The opening a casing band's outer edge follows: its arch or its top corners
81
+ * grown by `width`, so the band keeps one width all round, and square at the
82
+ * bottom, where the band stops.
83
+ */
84
+ function grownOpening(opening, width) {
85
+ if (opening.openingShape === 'arch') {
86
+ const archHeight = Math.min(Math.max(opening.archHeight ?? opening.width / 2, 0.01), opening.height);
87
+ return { ...opening, archHeight: archHeight + width };
88
+ }
89
+ const radii = getRoundedOpeningRadii(opening, opening.width, opening.height);
90
+ const grow = (radius) => (radius > 1e-6 ? radius + width : 0);
91
+ return opening.type === 'window'
92
+ ? {
93
+ ...opening,
94
+ openingRadiusMode: 'individual',
95
+ openingCornerRadii: [grow(radii.topLeft), grow(radii.topRight), 0, 0],
96
+ }
97
+ : {
98
+ ...opening,
99
+ openingRadiusMode: 'individual',
100
+ openingTopRadii: [grow(radii.topLeft), grow(radii.topRight)],
101
+ };
102
+ }
@@ -8,6 +8,12 @@ export type OpeningCutoutRect = {
8
8
  top: number;
9
9
  };
10
10
  export declare function getOpeningCutoutProxyDepth(wallThickness: number): number;
11
+ type CornerRadii = {
12
+ topLeft: number;
13
+ topRight: number;
14
+ bottomRight: number;
15
+ bottomLeft: number;
16
+ };
11
17
  /**
12
18
  * Pure cutout profile for a shaped door / window opening. `rect` is in
13
19
  * the caller's coordinate frame — the wall CSG pipeline passes wall-local
@@ -28,4 +34,7 @@ export declare function hasFlatOpeningCutoutBottom(opening: OpeningCutoutNode):
28
34
  * subtract a face exactly coplanar with the wall base.
29
35
  */
30
36
  export declare function getOpeningCutoutBottomPadding(opening: OpeningCutoutNode, bottom: number): number;
37
+ /** The corner radii a rounded opening's cutout is cut with, clamped to fit `width` × `height`. */
38
+ export declare function getRoundedOpeningRadii(opening: OpeningCutoutNode, width: number, height: number): CornerRadii;
39
+ export {};
31
40
  //# sourceMappingURL=opening-cutout-geometry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"opening-cutout-geometry.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/opening-cutout-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,UAAU,CAAA;AAErD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAYD,wBAAgB,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAExE;AASD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,iBAAiB,GACtB,KAAK,CAAC,KAAK,CAuCb;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,KAAK,CAAC,cAAc,CAqBtB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAW9E;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhG"}
1
+ {"version":3,"file":"opening-cutout-geometry.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/opening-cutout-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,UAAU,CAAA;AAErD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAYD,wBAAgB,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,iBAAiB,GACtB,KAAK,CAAC,KAAK,CAyCb;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,KAAK,CAAC,cAAc,CAqBtB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAW9E;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhG;AAED,kGAAkG;AAClG,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,WAAW,CAyCb"}
@@ -1,3 +1,4 @@
1
+ import { getDoorOpeningSize } from '@pascal-app/core';
1
2
  import * as THREE from 'three';
2
3
  // The cutout proxy doubles as the invisible raycast hit target for an opening:
3
4
  // centered on the wall and extending past both faces so it wins the scene
@@ -22,7 +23,9 @@ export function buildOpeningCutoutShape(opening, rect) {
22
23
  const width = Math.max(right - left, 1e-6);
23
24
  const height = Math.max(top - bottom, 1e-6);
24
25
  const shape = new THREE.Shape();
25
- if (opening.openingShape === 'arch') {
26
+ const size = getDoorOpeningSize(opening);
27
+ const fixedLights = size.width !== opening.width || size.height !== opening.height;
28
+ if (!fixedLights && opening.openingShape === 'arch') {
26
29
  const halfWidth = width / 2;
27
30
  const centerX = (left + right) / 2;
28
31
  const archHeight = Math.min(Math.max(opening.archHeight ?? width / 2, 0.01), height);
@@ -41,7 +44,7 @@ export function buildOpeningCutoutShape(opening, rect) {
41
44
  shape.closePath();
42
45
  return shape;
43
46
  }
44
- if (opening.openingShape === 'rounded') {
47
+ if (!fixedLights && opening.openingShape === 'rounded') {
45
48
  const radii = getRoundedOpeningRadii(opening, width, height);
46
49
  applyRoundedOpeningShape(shape, left, right, bottom, top, radii);
47
50
  return shape;
@@ -93,7 +96,8 @@ export function hasFlatOpeningCutoutBottom(opening) {
93
96
  export function getOpeningCutoutBottomPadding(opening, bottom) {
94
97
  return bottom < 0.005 && hasFlatOpeningCutoutBottom(opening) ? OPENING_CUTOUT_BOTTOM_PADDING : 0;
95
98
  }
96
- function getRoundedOpeningRadii(opening, width, height) {
99
+ /** The corner radii a rounded opening's cutout is cut with, clamped to fit `width` × `height`. */
100
+ export function getRoundedOpeningRadii(opening, width, height) {
97
101
  if (opening.type !== 'window') {
98
102
  if (opening.openingRadiusMode === 'individual') {
99
103
  const [topLeft = 0, topRight = 0] = opening.openingTopRadii ?? [0.15, 0.15];
@@ -1 +1 @@
1
- {"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,aAAa,EAClB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAS5C,OAAO,EAEL,KAAK,WAAW,EAMhB,KAAK,aAAa,EAInB,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;IACpB,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAA;CAC1B;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM;QAAE,OAAO,EAAE,iBAAiB,CAAC;QAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAA;CACjE,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,aAAa,EACvB,QAAQ,CAAC,EAAE,OAAO,EAClB,WAAW,CAAC,EAAE,WAAW,EACzB,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,KAC3B,aAAa,CAAA;AA4SlB,wFAAwF;AACxF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAS9F;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAS1F;AA4DD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,EAClC,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAQR;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,EAClC,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,aAAa,CAmFf;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,GACjC,iBAAiB,CAUnB"}
1
+ {"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAWL,KAAK,aAAa,EAClB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAU5C,OAAO,EAEL,KAAK,WAAW,EAMhB,KAAK,aAAa,EAInB,MAAM,qBAAqB,CAAA;AAE5B,KAAK,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;AAcxE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,EAAE,CAAA;AAE1C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,aAAa,EAAE,iBAAiB,CAAA;IAChC,eAAe,EAAE,iBAAiB,CAAA;IAClC,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAA;CAC1B;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM;QAAE,OAAO,EAAE,iBAAiB,CAAC;QAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAA;CACjE,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,aAAa,EACvB,QAAQ,CAAC,EAAE,OAAO,EAClB,WAAW,CAAC,EAAE,WAAW,EACzB,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,KAC3B,aAAa,CAAA;AAiSlB,wFAAwF;AACxF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAI9F;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAI1F;AA4DD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,EAClC,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAQR;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,EAClC,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,aAAa,CAmFf;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,GAAE,SAAS,MAAM,EAAO,GACjC,iBAAiB,CAUnB"}
@@ -1,7 +1,8 @@
1
- import { getEffectiveWallFaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, parseMaterialColor, parseMaterialRef, resolveMaterial, resolveWallExteriorSide, resolveWallFaceChain, WALL_SLOT_DEFAULT, wallAssemblyExposedStructure, wallAssemblyFinishRef, } from '@pascal-app/core';
1
+ import { getEffectiveWallFaceMaterial, getMaterialPresetByRef, getWallFaceDefault, getWallSurfaceMaterialSignature, parseMaterialColor, parseMaterialRef, resolveMaterial, resolveWallExteriorSide, resolveWallFaceChain, WALL_SLOT_DEFAULT, wallAssemblyExposedStructure, wallAssemblyFaceFinish, } from '@pascal-app/core';
2
2
  import { Color } from 'three';
3
3
  import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, uv, vec2 } from 'three/tsl';
4
4
  import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
5
+ import { markHighlight, tintEmissive } from '../../lib/editor-highlight';
5
6
  import { BLOCK_MASONRY, concreteMaterial, masonryMaterial, STRUCTURE_COLORS, } from '../../lib/foundation-materials';
6
7
  import { baseMaterial, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, materialPresetRefSignature, resolveMaterialRef, resolveSlotDefaultRef, resolveSurfaceColor, } from '../../lib/materials';
7
8
  const DEFAULT_WALL_COLOR = '#e9e7e3';
@@ -9,8 +10,7 @@ const WALL_HIGHLIGHT_PROFILES = {
9
10
  delete: {
10
11
  color: new Color('#dc2626'),
11
12
  blend: 0.76,
12
- emissiveBlend: 0.92,
13
- emissiveIntensity: 0.46,
13
+ emissive: 0.92,
14
14
  },
15
15
  };
16
16
  const wallMaterialCache = new Map();
@@ -43,11 +43,11 @@ function resolveWallSlotDefault(declaredDefault, shading) {
43
43
  }
44
44
  return createDefaultMaterial(slotDefault, 0.9, shading);
45
45
  }
46
- // The assembly cladding's ref when `face` is the wall's exterior face (a is the
47
- // +normal face; an unresolved side keeps the cladding on b, as legacy did).
46
+ // The assembly's finish for `face`: its cladding when `face` is the wall's exterior face (a is the
47
+ // +normal face; an unresolved side keeps the cladding on b, as legacy did), and the paint picked
48
+ // for a lining while drafting on a lined face.
48
49
  function wallAssemblyFaceFinishRef(wallNode, face) {
49
- const exteriorFace = resolveWallExteriorSide(wallNode) === 1 ? 'a' : 'b';
50
- return face === exteriorFace ? wallAssemblyFinishRef(wallNode) : null;
50
+ return wallAssemblyFaceFinish(wallNode, face);
51
51
  }
52
52
  // What `face` shows when it is the exterior face of a wall with no cladding over a bare masonry or
53
53
  // concrete core: the structure itself (null otherwise, and for any face with a cladding).
@@ -57,7 +57,7 @@ function wallFaceExposedStructure(wallNode, face) {
57
57
  const exteriorFace = resolveWallExteriorSide(wallNode) === 1 ? 'a' : 'b';
58
58
  return face === exteriorFace ? wallAssemblyExposedStructure(wallNode) : null;
59
59
  }
60
- // The structure as the generic foundation draws it (same courses, joints and colours), made once:
60
+ // The bare structure (courses, joints and colours), made once:
61
61
  // the block's bond runs along the face's world-planar U, the wall's own metres. Solid shading
62
62
  // keeps to a flat colour, like every other wall finish.
63
63
  const structureMaterials = new Map();
@@ -73,20 +73,22 @@ function exposedStructureMaterial(structure, shading) {
73
73
  return material;
74
74
  }
75
75
  // Slot-first resolution for one wall face, matching every other paintable kind:
76
- // node.slots[face] ref → legacy inline finish → declared slot default.
76
+ // node.slots[face] ref → legacy inline finish → the face's default.
77
77
  // A dangling `scene:` ref (material deleted / copied across scenes) falls back
78
- // to the declared default — it never blocks rendering (the dangling-ref rule).
78
+ // to the face's default — it never blocks rendering (the dangling-ref rule).
79
79
  function resolveWallFaceMaterial(wallNode, face, shading, sceneMaterials) {
80
80
  const finish = resolveWallFaceChain(wallNode, face);
81
81
  if (finish.kind === 'ref') {
82
82
  return (resolveMaterialRef(finish.ref, sceneMaterials, shading) ??
83
- resolveWallSlotDefault(WALL_SLOT_DEFAULT[face], shading));
83
+ resolveWallSlotDefault(getWallFaceDefault(wallNode, face), shading));
84
84
  }
85
85
  if (finish.kind === 'legacy')
86
86
  return getSurfaceVisibleMaterial(finish.spec, shading);
87
87
  // No paint on this face: the wall ASSEMBLY's cladding (WS5) skins the
88
88
  // exterior — a "2x6 lap siding" wall reads as lap siding without the user
89
- // painting it. Painting the face still wins.
89
+ // painting it. Painting the face still wins. Any other unpainted face wears
90
+ // its default (`getWallFaceDefault`): the interior paint
91
+ // (`DEFAULT_INTERIOR_WALL_PAINT`) inside, raw drywall on the exterior side.
90
92
  const assemblyRef = wallAssemblyFaceFinishRef(wallNode, face);
91
93
  const exposed = assemblyRef ? null : wallFaceExposedStructure(wallNode, face);
92
94
  if (exposed)
@@ -116,11 +118,15 @@ function refMaterialSignature(ref, sceneMaterials) {
116
118
  // Falls back to the legacy signature when the face has no slot ref.
117
119
  function wallFaceMaterialSignature(wallNode, face, sceneMaterials) {
118
120
  const ref = wallNode.slots?.[face];
121
+ // The face's default follows its side, so a room detected outside it re-skins
122
+ // an unpainted face (and the fallback of a dangling ref).
123
+ const fallback = getWallFaceDefault(wallNode, face);
119
124
  if (ref)
120
- return refMaterialSignature(ref, sceneMaterials);
125
+ return JSON.stringify({ ref: refMaterialSignature(ref, sceneMaterials), fallback });
121
126
  const spec = getEffectiveWallFaceMaterial(wallNode, face);
122
127
  const assemblyRef = wallAssemblyFaceFinishRef(wallNode, face);
123
128
  return JSON.stringify({
129
+ fallback,
124
130
  legacy: getWallSurfaceMaterialSignature(spec),
125
131
  preset: spec.materialPreset ? materialPresetRefSignature(spec.materialPreset) : null,
126
132
  // Changing the assembly's cladding must re-skin the face.
@@ -178,16 +184,9 @@ function createHighlightedWallMaterial(material, kind) {
178
184
  if ('color' in highlightedMaterial && highlightedMaterial.color) {
179
185
  highlightedMaterial.color = getHighlightedColor(highlightedMaterial.color, kind);
180
186
  }
181
- if ('emissive' in highlightedMaterial && highlightedMaterial.emissive) {
182
- highlightedMaterial.emissive = highlightedMaterial.emissive
183
- .clone()
184
- .lerp(profile.color, profile.emissiveBlend);
185
- }
186
- if ('emissiveIntensity' in highlightedMaterial) {
187
- highlightedMaterial.emissiveIntensity = Math.max(highlightedMaterial.emissiveIntensity ?? 0, profile.emissiveIntensity);
188
- }
187
+ tintEmissive(highlightedMaterial, profile.color, profile.emissive);
189
188
  highlightedMaterial.needsUpdate = true;
190
- return highlightedMaterial;
189
+ return markHighlight(highlightedMaterial);
191
190
  }
192
191
  // Light selection highlight for walls (walls are excluded from the generic
193
192
  // editor selection highlight, so they need their own). Adds a gentle indigo
@@ -199,15 +198,13 @@ function createHighlightedWallMaterial(material, kind) {
199
198
  // cache keyed by the source `.map` and rebuild when it changes (self-heals
200
199
  // once the texture lands).
201
200
  const SELECTION_HIGHLIGHT_COLOR = new Color('#818cf8');
202
- const SELECTION_EMISSIVE_BLEND = 0.4;
203
- const SELECTION_EMISSIVE_INTENSITY = 0.12;
201
+ const SELECTION_EMISSIVE = 0.12;
204
202
  // Softer sibling of the selection glow, for HOVERING a hidden wall (the
205
203
  // X-ray nearest-first selection made hidden walls hover targets; without a
206
204
  // material affordance the only thing lighting up was the furniture behind
207
205
  // them). Same indigo so hover reads as "this will select", weaker so a
208
206
  // hovered-then-selected wall still steps up on click.
209
- const HOVER_EMISSIVE_BLEND = 0.4;
210
- const HOVER_EMISSIVE_INTENSITY = 0.2;
207
+ const HOVER_EMISSIVE = 0.08;
211
208
  const SELECTION_TEXTURE_MAP_KEYS = [
212
209
  'map',
213
210
  'normalMap',
@@ -222,7 +219,7 @@ const SELECTION_TEXTURE_MAP_KEYS = [
222
219
  ];
223
220
  const selectionHighlightCache = new WeakMap();
224
221
  const hoverHighlightCache = new WeakMap();
225
- function getEmissiveHighlightMaterial(base, cache, emissiveBlend, emissiveIntensity) {
222
+ function getEmissiveHighlightMaterial(base, cache, emissive) {
226
223
  const baseMap = base.map ?? null;
227
224
  const cached = cache.get(base);
228
225
  if (cached && cached.map === baseMap)
@@ -235,19 +232,15 @@ function getEmissiveHighlightMaterial(base, cache, emissiveBlend, emissiveIntens
235
232
  if (src[key])
236
233
  dst[key] = src[key];
237
234
  }
238
- if ('emissive' in clone && clone.emissive) {
239
- clone.emissive = clone.emissive.clone().lerp(SELECTION_HIGHLIGHT_COLOR, emissiveBlend);
240
- }
241
- if ('emissiveIntensity' in clone) {
242
- clone.emissiveIntensity = Math.max(clone.emissiveIntensity ?? 0, emissiveIntensity);
243
- }
235
+ tintEmissive(clone, SELECTION_HIGHLIGHT_COLOR, emissive);
244
236
  clone.needsUpdate = true;
237
+ markHighlight(clone);
245
238
  cache.set(base, { clone, map: baseMap });
246
239
  return clone;
247
240
  }
248
241
  /** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
249
242
  export function getSelectionHighlightMaterials(materials) {
250
- return materials.map((material) => getEmissiveHighlightMaterial(material, selectionHighlightCache, SELECTION_EMISSIVE_BLEND, SELECTION_EMISSIVE_INTENSITY));
243
+ return materials.map((material) => getEmissiveHighlightMaterial(material, selectionHighlightCache, SELECTION_EMISSIVE));
251
244
  }
252
245
  /**
253
246
  * Softer hover sibling of the selection variant — the affordance for a
@@ -255,7 +248,7 @@ export function getSelectionHighlightMaterials(materials) {
255
248
  * film so the wall the click would select reads under the cursor).
256
249
  */
257
250
  export function getHoverHighlightMaterials(materials) {
258
- return materials.map((material) => getEmissiveHighlightMaterial(material, hoverHighlightCache, HOVER_EMISSIVE_BLEND, HOVER_EMISSIVE_INTENSITY));
251
+ return materials.map((material) => getEmissiveHighlightMaterial(material, hoverHighlightCache, HOVER_EMISSIVE));
259
252
  }
260
253
  function createInvisibleWallMaterial(color, shading) {
261
254
  const material = shading === 'solid'
@@ -1 +1 @@
1
- {"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAqCd,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,QAAQ,EAEb,KAAK,sBAAsB,EAK5B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAAwC,MAAM,eAAe,CAAA;AAqBrF,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,oBAAoB,CAAA;AAK3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAyChE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG;IACjE,MAAM,EAAE,KAAK,GAAG,IAAI,CAAA;IACpB,eAAe,EAAE,KAAK,EAAE,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CA4FA;AAiJD;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,gBAAgB,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAC,GAAG,GAAG,GAAG,EAAE,eAAe,EAAE,CAAC,GAAG,IAAI,CAAA;IACrD,6FAA6F;IAC7F,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,wFAAwF;IACxF,UAAU,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAClD,CAAA;AAoUD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAID,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,EAAE,0BAA0B,KAChC;QAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;QAAC,cAAc,EAAE,OAAO,EAAE,CAAA;KAAE,CAAA;IAC/D,aAAa,CAAC,EAAE,CACd,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,KAAK,CAAC,cAAc,EAC9B,QAAQ,EAAE,SAAS,OAAO,EAAE,KACzB,KAAK,CAAC,cAAc,CAAA;IACzB,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,cAAc,KAAK,IAAI,CAAA;CACnG,CAAA;AAED,eAAO,MAAM,UAAU,yBAAyB;IAAE,eAAe,CAAC,EAAE,mBAAmB,CAAA;CAAE,SAexF,CAAA;AAED,wBAAgB,iBAAiB,CAAC,eAAe,CAAC,EAAE,mBAAmB,QAatE;AAyCD;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAoCpD;AAmZD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,OAAO,EAAE,CAUX;AA8ED,KAAK,wBAAwB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;AA4OvE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,EACjB,aAAa,SAAgB,EAC7B,YAAY,GAAE,SAAS,sBAAsB,EAE5C,EACD,YAAY,SAAuB,EACnC,eAAe,CAAC,EAAE,wBAAwB,EAC1C,QAAQ,CAAC,EAAE;IAAE,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAAC,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAA;CAAE,EACzF,YAAY,GAAE,gBAAsD,EACpE,UAAU,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAA6B,GACxE,KAAK,CAAC,cAAc,CAkUtB"}
@@ -1,4 +1,4 @@
1
- import { buildWallFinishLayout, containsPoint, cornerPair, DEFAULT_LEVEL_HEIGHT, getAdjacentWallIds, getEffectiveNode, getOpeningWallCut, getWallBodyCenterOffset, getWallCurveFrameAt, getWallCurveLength, getWallFaceOffsets, getWallLevelZones, getWallMiterBoundaryPoints, getWallPlaneTop, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, isCutterName, pointToKey, resolveCutterHost, resolveLevelId, resolveWallFaceBottom, resolveWallFinish, resolveWallTop, sceneRegistry, spatialGridManager, terrainSupportLift, useLiveNodeOverrides, useLiveTransforms, useScene, WALL_SURFACE_SLOT_DEFAULTS, wallFinishMaterialIndex, zoneHasWallFinish, } from '@pascal-app/core';
1
+ import { buildWallFinishLayout, containsPoint, cornerPair, DEFAULT_LEVEL_HEIGHT, getAdjacentWallIds, getDoorOpeningSize, getEffectiveNode, getOpeningWallCut, getWallBodyCenterOffset, getWallCurveFrameAt, getWallCurveLength, getWallFaceOffsets, getWallLevelZones, getWallMiterBoundaryPoints, getWallPlaneTop, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, isCutterName, openingHostFacing, pointToKey, resolveCutterHost, resolveLevelId, resolveWallFaceBottom, resolveWallFinish, resolveWallTop, sceneRegistry, spatialGridManager, terrainSupportLift, useLiveNodeOverrides, useLiveTransforms, useScene, WALL_SURFACE_SLOT_DEFAULTS, wallFinishMaterialIndex, zoneHasWallFinish, } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  import { useEffect } from 'react';
4
4
  import * as THREE from 'three';
@@ -17,6 +17,7 @@ import { drainStats, endInitialBuild, initiallyBuiltWalls, isWallInitialBuildAct
17
17
  import { getWallFaceBaseAt, } from './wall-finish-data';
18
18
  import { sweepUnbuiltWalls, WALL_PLACEHOLDER_SWEEP_INTERVAL } from './wall-placeholder-sweep';
19
19
  import { notifyWallRebuilt } from './wall-rebuild-notifications';
20
+ import { repairWallTJunctions } from './wall-t-junctions';
20
21
  export { isWallInitialBuildActive } from './wall-build-lifecycle';
21
22
  export { drainRebuiltWalls } from './wall-rebuild-notifications';
22
23
  // Reusable CSG evaluator for better performance
@@ -332,6 +333,9 @@ function interpolateSplitVertex(a, b, t) {
332
333
  z: a.z + (b.z - a.z) * t,
333
334
  };
334
335
  }
336
+ function compareSplitVertices(a, b) {
337
+ return a.x - b.x || a.y - b.y || a.z - b.z;
338
+ }
335
339
  function clipPolygonByPlane(polygon, axis, plane, keepBelow) {
336
340
  const out = [];
337
341
  if (polygon.length === 0)
@@ -345,9 +349,12 @@ function clipPolygonByPlane(polygon, axis, plane, keepBelow) {
345
349
  const currentInside = isInside(current);
346
350
  const previousInside = isInside(previous);
347
351
  if (currentInside !== previousInside) {
348
- const denom = current[axis] - previous[axis];
352
+ // Interpolate from the same endpoint whichever way the edge is walked: the
353
+ // two triangles sharing it then get a bit-identical seam vertex.
354
+ const [from, to] = compareSplitVertices(previous, current) <= 0 ? [previous, current] : [current, previous];
355
+ const denom = to[axis] - from[axis];
349
356
  if (Math.abs(denom) > WALL_BAND_SPLIT_EPSILON) {
350
- out.push(interpolateSplitVertex(previous, current, (plane - previous[axis]) / denom));
357
+ out.push(interpolateSplitVertex(from, to, (plane - from[axis]) / denom));
351
358
  }
352
359
  }
353
360
  if (currentInside)
@@ -1174,10 +1181,22 @@ function mergeWallTerrainFill(body, fills, wall, boundaryEdges, finish) {
1174
1181
  bodyGeometry.dispose();
1175
1182
  for (const fill of splitFills)
1176
1183
  fill.dispose();
1177
- merged.computeVertexNormals();
1178
- assignWallMaterialGroups(merged, wall, boundaryEdges, finish);
1179
- ensureRenderableGeometryAttributes(merged);
1180
- return merged;
1184
+ return finishWallFaces(merged, wall, boundaryEdges, finish);
1185
+ }
1186
+ /**
1187
+ * Classifies the faces into material groups, then removes the T-junctions that
1188
+ * CSG cuts, finish-band splits and fill seams leave behind (they crack open
1189
+ * once a bake quantizes positions). Classification runs first, on the source
1190
+ * triangles, so the repair's sliver pieces inherit their material.
1191
+ */
1192
+ function finishWallFaces(geometry, wall, boundaryEdges, finish) {
1193
+ geometry.computeVertexNormals();
1194
+ assignWallMaterialGroups(geometry, wall, boundaryEdges, finish);
1195
+ const repaired = repairWallTJunctions(geometry);
1196
+ if (repaired !== geometry)
1197
+ repaired.computeVertexNormals();
1198
+ ensureRenderableGeometryAttributes(repaired);
1199
+ return repaired;
1181
1200
  }
1182
1201
  /** Wall-local x of a support-segment parameter (curve parameter on arcs). */
1183
1202
  function wallStationX(wall, t, length) {
@@ -1415,10 +1434,7 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
1415
1434
  ...collectCutoutBrushes(wallNode, childrenNodes, thickness, faceDatum, slabElevation, sceneNodes, baseSegments),
1416
1435
  ];
1417
1436
  if (cutoutBrushes.length === 0) {
1418
- const splitGeometry = splitGeometryAtPlanes(geometry, finishPlanes.y, finishPlanes.x);
1419
- splitGeometry.computeVertexNormals();
1420
- assignWallMaterialGroups(splitGeometry, wallNode, boundaryEdges, finish);
1421
- ensureRenderableGeometryAttributes(splitGeometry);
1437
+ const splitGeometry = finishWallFaces(splitGeometryAtPlanes(geometry, finishPlanes.y, finishPlanes.x), wallNode, boundaryEdges, finish);
1422
1438
  return withWallFinishData(mergeWallTerrainFill(splitGeometry, belowBaseFills, wallNode, boundaryEdges, finish), finish);
1423
1439
  }
1424
1440
  // Create wall brush from geometry
@@ -1462,10 +1478,7 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
1462
1478
  csgGeometry(brush).dispose();
1463
1479
  }
1464
1480
  const resultGeometry = csgGeometry(resultBrush);
1465
- const splitResultGeometry = splitGeometryAtPlanes(resultGeometry, finishPlanes.y, finishPlanes.x);
1466
- splitResultGeometry.computeVertexNormals();
1467
- assignWallMaterialGroups(splitResultGeometry, wallNode, boundaryEdges, finish);
1468
- ensureRenderableGeometryAttributes(splitResultGeometry);
1481
+ const splitResultGeometry = finishWallFaces(splitGeometryAtPlanes(resultGeometry, finishPlanes.y, finishPlanes.x), wallNode, boundaryEdges, finish);
1469
1482
  return withWallFinishData(mergeWallTerrainFill(splitResultGeometry, belowBaseFills, wallNode, boundaryEdges, finish), finish);
1470
1483
  }
1471
1484
  function withWallFinishData(geometry, finish) {
@@ -1669,15 +1682,17 @@ function collectCutoutBrushes(wallNode, childrenNodes, wallThickness, faceBase,
1669
1682
  */
1670
1683
  const CORNER_CUT_REACH = 6;
1671
1684
  function createOpeningCutoutBrush(opening, wallThickness, centerOffset, cutBottom, cornerEnd) {
1672
- const halfWidth = opening.width / 2;
1685
+ const size = getDoorOpeningSize(opening);
1686
+ const centerX = opening.position[0] - size.leafOffsetX * openingHostFacing(opening);
1687
+ const halfWidth = size.width / 2;
1673
1688
  const bottom = cutBottom ?? opening.position[1] - opening.height / 2;
1674
1689
  const bottomPadding = getOpeningCutoutBottomPadding(opening, bottom);
1675
1690
  const reach = CORNER_CUT_REACH * wallThickness;
1676
1691
  const geometry = buildOpeningCutoutGeometry(opening, {
1677
- left: opening.position[0] - halfWidth - (cornerEnd === 'start' ? reach : 0),
1678
- right: opening.position[0] + halfWidth + (cornerEnd === 'end' ? reach : 0),
1692
+ left: centerX - halfWidth - (cornerEnd === 'start' ? reach : 0),
1693
+ right: centerX + halfWidth + (cornerEnd === 'end' ? reach : 0),
1679
1694
  bottom: bottom - bottomPadding,
1680
- top: opening.position[1] + opening.height / 2,
1695
+ top: opening.position[1] - opening.height / 2 + size.height,
1681
1696
  }, wallThickness * 2, wallThickness);
1682
1697
  if (centerOffset !== 0)
1683
1698
  geometry.translate(0, 0, centerOffset);
@@ -0,0 +1,15 @@
1
+ import * as THREE from 'three';
2
+ /**
3
+ * Splits every triangle whose edge carries another triangle's vertex, so the
4
+ * neighbours share that vertex instead of meeting at a T-junction. CSG cuts and
5
+ * finish-band splits leave T-junctions on wall faces: they look closed at full
6
+ * float precision, but crack open into dotted light leaks once a GLB bake
7
+ * quantizes positions.
8
+ *
9
+ * Each piece keeps its source triangle's material group, so the repair never
10
+ * reclassifies a face. Geometry without T-junctions is returned untouched;
11
+ * otherwise the result is non-indexed and position-only (callers recompute
12
+ * normals) and the input is disposed.
13
+ */
14
+ export declare function repairWallTJunctions(geometry: THREE.BufferGeometry): THREE.BufferGeometry;
15
+ //# sourceMappingURL=wall-t-junctions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-t-junctions.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-t-junctions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAW9B;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,CAiHzF"}