@pascal-app/viewer 0.6.0 → 0.8.0

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 (107) hide show
  1. package/dist/components/error-boundary.d.ts +7 -3
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +3 -1
  4. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  5. package/dist/components/renderers/ceiling/ceiling-renderer.js +19 -3
  6. package/dist/components/renderers/column/column-renderer.d.ts +5 -0
  7. package/dist/components/renderers/column/column-renderer.d.ts.map +1 -0
  8. package/dist/components/renderers/column/column-renderer.js +597 -0
  9. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
  10. package/dist/components/renderers/door/door-renderer.js +4 -9
  11. package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
  12. package/dist/components/renderers/fence/fence-renderer.js +2 -16
  13. package/dist/components/renderers/guide/guide-renderer.js +1 -1
  14. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
  15. package/dist/components/renderers/item/item-renderer.js +2 -1
  16. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  17. package/dist/components/renderers/node-renderer.js +3 -1
  18. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  19. package/dist/components/renderers/roof/roof-renderer.js +1 -13
  20. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
  21. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +5 -20
  22. package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
  23. package/dist/components/renderers/slab/slab-renderer.js +38 -26
  24. package/dist/components/renderers/spawn/spawn-renderer.d.ts +5 -0
  25. package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +1 -0
  26. package/dist/components/renderers/spawn/spawn-renderer.js +32 -0
  27. package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
  28. package/dist/components/renderers/stair/stair-renderer.js +7 -25
  29. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
  30. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +4 -2
  31. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  32. package/dist/components/viewer/frame-limiter.js +5 -1
  33. package/dist/components/viewer/index.d.ts +3 -0
  34. package/dist/components/viewer/index.d.ts.map +1 -1
  35. package/dist/components/viewer/index.js +89 -19
  36. package/dist/components/viewer/post-processing.d.ts +13 -1
  37. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  38. package/dist/components/viewer/post-processing.js +71 -35
  39. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  40. package/dist/components/viewer/selection-manager.js +19 -2
  41. package/dist/hooks/use-node-events.d.ts +9 -1
  42. package/dist/hooks/use-node-events.d.ts.map +1 -1
  43. package/dist/index.d.ts +6 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +5 -2
  46. package/dist/lib/materials.d.ts +3 -0
  47. package/dist/lib/materials.d.ts.map +1 -1
  48. package/dist/lib/materials.js +16 -3
  49. package/dist/lib/merged-outline-node.d.ts +2 -0
  50. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  51. package/dist/lib/merged-outline-node.js +29 -16
  52. package/dist/store/use-viewer.d.ts +4 -4
  53. package/dist/store/use-viewer.d.ts.map +1 -1
  54. package/dist/store/use-viewer.js +2 -2
  55. package/dist/systems/ceiling/ceiling-system.d.ts +8 -0
  56. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -0
  57. package/dist/systems/ceiling/ceiling-system.js +91 -0
  58. package/dist/systems/column/column-geometry.d.ts +22 -0
  59. package/dist/systems/column/column-geometry.d.ts.map +1 -0
  60. package/dist/systems/column/column-geometry.js +108 -0
  61. package/dist/systems/door/door-animation-system.d.ts +2 -0
  62. package/dist/systems/door/door-animation-system.d.ts.map +1 -0
  63. package/dist/systems/door/door-animation-system.js +53 -0
  64. package/dist/systems/door/door-system.d.ts +2 -0
  65. package/dist/systems/door/door-system.d.ts.map +1 -0
  66. package/dist/systems/door/door-system.js +867 -0
  67. package/dist/systems/fence/fence-system.d.ts +2 -0
  68. package/dist/systems/fence/fence-system.d.ts.map +1 -0
  69. package/dist/systems/fence/fence-system.js +185 -0
  70. package/dist/systems/guide/guide-system.d.ts.map +1 -1
  71. package/dist/systems/guide/guide-system.js +1 -26
  72. package/dist/systems/item/item-system.d.ts +2 -0
  73. package/dist/systems/item/item-system.d.ts.map +1 -0
  74. package/dist/systems/item/item-system.js +44 -0
  75. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  76. package/dist/systems/roof/roof-materials.js +18 -1
  77. package/dist/systems/roof/roof-system.d.ts +16 -0
  78. package/dist/systems/roof/roof-system.d.ts.map +1 -0
  79. package/dist/systems/roof/roof-system.js +800 -0
  80. package/dist/systems/scan/scan-system.d.ts.map +1 -1
  81. package/dist/systems/scan/scan-system.js +1 -26
  82. package/dist/systems/slab/slab-system.d.ts +8 -0
  83. package/dist/systems/slab/slab-system.d.ts.map +1 -0
  84. package/dist/systems/slab/slab-system.js +155 -0
  85. package/dist/systems/stair/stair-materials.d.ts.map +1 -1
  86. package/dist/systems/stair/stair-materials.js +25 -2
  87. package/dist/systems/stair/stair-system.d.ts +2 -0
  88. package/dist/systems/stair/stair-system.d.ts.map +1 -0
  89. package/dist/systems/stair/stair-system.js +770 -0
  90. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  91. package/dist/systems/wall/wall-materials.js +2 -2
  92. package/dist/systems/wall/wall-system.d.ts +11 -0
  93. package/dist/systems/wall/wall-system.d.ts.map +1 -0
  94. package/dist/systems/wall/wall-system.js +595 -0
  95. package/dist/systems/window/window-animation-system.d.ts +2 -0
  96. package/dist/systems/window/window-animation-system.d.ts.map +1 -0
  97. package/dist/systems/window/window-animation-system.js +135 -0
  98. package/dist/systems/window/window-system.d.ts +12 -0
  99. package/dist/systems/window/window-system.d.ts.map +1 -0
  100. package/dist/systems/window/window-system.js +1430 -0
  101. package/package.json +6 -4
  102. package/dist/lib/suppress-three-clock-warning.d.ts +0 -25
  103. package/dist/lib/suppress-three-clock-warning.d.ts.map +0 -1
  104. package/dist/lib/suppress-three-clock-warning.js +0 -51
  105. package/dist/systems/export/export-system.d.ts +0 -2
  106. package/dist/systems/export/export-system.d.ts.map +0 -1
  107. package/dist/systems/export/export-system.js +0 -60
@@ -0,0 +1,2 @@
1
+ export declare const FenceSystem: () => null;
2
+ //# sourceMappingURL=fence-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"AA+QA,eAAO,MAAM,WAAW,YAiBvB,CAAA"}
@@ -0,0 +1,185 @@
1
+ import { getWallCurveFrameAt, getWallCurveLength, sceneRegistry, useScene, } from '@pascal-app/core';
2
+ import { useFrame } from '@react-three/fiber';
3
+ import * as THREE from 'three';
4
+ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
5
+ const MIN_CURVE_SEGMENT_LENGTH = 0.18;
6
+ function createFencePartGeometry(part) {
7
+ const geometry = new THREE.BoxGeometry(1, 1, 1);
8
+ geometry.scale(part.scale[0], part.scale[1], part.scale[2]);
9
+ if (part.rotationY) {
10
+ geometry.rotateY(part.rotationY);
11
+ }
12
+ geometry.translate(part.position[0], part.position[1], part.position[2]);
13
+ applyFenceUVs(geometry);
14
+ return geometry;
15
+ }
16
+ function getFencePointAt(fence, t) {
17
+ const frame = getWallCurveFrameAt(fence, t);
18
+ return {
19
+ point: frame.point,
20
+ tangentAngle: Math.atan2(frame.tangent.y, frame.tangent.x),
21
+ };
22
+ }
23
+ function createStraightFenceSpanPart(start, end, centerY, height, depth) {
24
+ const dx = end[0] - start[0];
25
+ const dz = end[1] - start[1];
26
+ const length = Math.hypot(dx, dz);
27
+ if (length <= 1e-4) {
28
+ return null;
29
+ }
30
+ return {
31
+ position: [(start[0] + end[0]) / 2, centerY, (start[1] + end[1]) / 2],
32
+ rotationY: -Math.atan2(dz, dx),
33
+ scale: [length, height, depth],
34
+ };
35
+ }
36
+ function createFenceCurveSpanParts(fence, startT, endT, centerY, height, depth) {
37
+ const parts = [];
38
+ const frameCount = Math.max(1, Math.ceil((getWallCurveLength(fence) * Math.max(1e-4, endT - startT)) / MIN_CURVE_SEGMENT_LENGTH));
39
+ let previous = getFencePointAt(fence, startT);
40
+ for (let index = 1; index <= frameCount; index += 1) {
41
+ const t = startT + (endT - startT) * (index / frameCount);
42
+ const current = getFencePointAt(fence, t);
43
+ const segment = createStraightFenceSpanPart([previous.point.x, previous.point.y], [current.point.x, current.point.y], centerY, height, depth);
44
+ if (segment) {
45
+ parts.push(segment);
46
+ }
47
+ previous = current;
48
+ }
49
+ return parts;
50
+ }
51
+ function applyFenceUVs(geometry) {
52
+ const position = geometry.getAttribute('position');
53
+ const normal = geometry.getAttribute('normal');
54
+ if (!(position && normal))
55
+ return;
56
+ const uvs = new Float32Array(position.count * 2);
57
+ let minX = Number.POSITIVE_INFINITY;
58
+ let minY = Number.POSITIVE_INFINITY;
59
+ let minZ = Number.POSITIVE_INFINITY;
60
+ for (let index = 0; index < position.count; index += 1) {
61
+ minX = Math.min(minX, position.getX(index));
62
+ minY = Math.min(minY, position.getY(index));
63
+ minZ = Math.min(minZ, position.getZ(index));
64
+ }
65
+ for (let index = 0; index < position.count; index += 1) {
66
+ const px = position.getX(index);
67
+ const py = position.getY(index);
68
+ const pz = position.getZ(index);
69
+ const nx = Math.abs(normal.getX(index));
70
+ const ny = Math.abs(normal.getY(index));
71
+ const nz = Math.abs(normal.getZ(index));
72
+ let u = 0;
73
+ let v = 0;
74
+ if (ny >= nx && ny >= nz) {
75
+ u = px - minX;
76
+ v = pz - minZ;
77
+ }
78
+ else if (nx >= nz) {
79
+ u = pz - minZ;
80
+ v = py - minY;
81
+ }
82
+ else {
83
+ u = px - minX;
84
+ v = py - minY;
85
+ }
86
+ uvs[index * 2] = u;
87
+ uvs[index * 2 + 1] = v;
88
+ }
89
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
90
+ geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(uvs.slice(), 2));
91
+ }
92
+ function getStyleDefaults(style) {
93
+ if (style === 'privacy') {
94
+ return { spacingFactor: 0.42, postFactor: 1.35, baseFactor: 1.2, topFactor: 1.2 };
95
+ }
96
+ if (style === 'rail') {
97
+ return { spacingFactor: 0.68, postFactor: 0.8, baseFactor: 0.85, topFactor: 0.85 };
98
+ }
99
+ return { spacingFactor: 0.3, postFactor: 0.55, baseFactor: 1, topFactor: 0.75 };
100
+ }
101
+ function createFenceParts(fence) {
102
+ const parts = [];
103
+ const length = Math.max(getWallCurveLength(fence), 0.01);
104
+ const panelDepth = Math.max(fence.thickness, 0.03);
105
+ const clearance = Math.max(fence.groundClearance, 0);
106
+ const styleDefaults = getStyleDefaults(fence.style);
107
+ const baseHeight = Math.max(fence.baseHeight * styleDefaults.baseFactor, 0.04);
108
+ const topRailHeight = Math.max(fence.topRailHeight * styleDefaults.topFactor, 0.01);
109
+ const verticalHeight = Math.max(fence.height - baseHeight - topRailHeight, 0.08);
110
+ const postWidth = Math.max(fence.postSize * styleDefaults.postFactor, 0.01);
111
+ const spacing = Math.max(fence.postSpacing * styleDefaults.spacingFactor, postWidth * 1.2);
112
+ const edgeInset = Math.max(fence.edgeInset ?? 0.015, 0.005);
113
+ const isFloating = fence.baseStyle === 'floating';
114
+ const baseY = isFloating ? clearance : 0;
115
+ const effectiveBaseHeight = baseHeight;
116
+ const startInsetT = Math.min(0.499, edgeInset / length);
117
+ const endInsetT = Math.max(0.501, 1 - edgeInset / length);
118
+ if (!isFloating) {
119
+ parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight / 2, effectiveBaseHeight, panelDepth * 1.05));
120
+ parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight * 0.15, topRailHeight * 0.8, panelDepth * 0.35));
121
+ }
122
+ const count = Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1);
123
+ const verticalY = baseY + effectiveBaseHeight + verticalHeight / 2;
124
+ for (let index = 0; index < count; index += 1) {
125
+ const t = count === 1 ? 0.5 : startInsetT + (endInsetT - startInsetT) * (index / (count - 1));
126
+ const frame = getFencePointAt(fence, t);
127
+ const isEdgePost = index === 0 || index === count - 1;
128
+ const postHeight = isFloating && isEdgePost
129
+ ? effectiveBaseHeight + verticalHeight + topRailHeight + clearance
130
+ : verticalHeight;
131
+ const postY = isFloating && isEdgePost ? postHeight / 2 : verticalY;
132
+ parts.push({
133
+ position: [frame.point.x, postY, frame.point.y],
134
+ rotationY: -frame.tangentAngle,
135
+ scale: [postWidth, postHeight, Math.max(panelDepth * 0.35, 0.012)],
136
+ });
137
+ }
138
+ parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
139
+ if (isFloating) {
140
+ parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
141
+ }
142
+ return parts;
143
+ }
144
+ function generateFenceGeometry(fence) {
145
+ const parts = createFenceParts(fence);
146
+ const geometries = parts.map(createFencePartGeometry);
147
+ const merged = mergeGeometries(geometries, false) ?? new THREE.BufferGeometry();
148
+ geometries.forEach((geometry) => geometry.dispose());
149
+ const mergedUv = merged.getAttribute('uv');
150
+ if (mergedUv) {
151
+ merged.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(mergedUv.array), 2));
152
+ }
153
+ merged.computeVertexNormals();
154
+ return merged;
155
+ }
156
+ function updateFenceGeometry(fenceId) {
157
+ const node = useScene.getState().nodes[fenceId];
158
+ if (!node || node.type !== 'fence')
159
+ return;
160
+ const mesh = sceneRegistry.nodes.get(fenceId);
161
+ if (!mesh)
162
+ return;
163
+ const newGeometry = generateFenceGeometry(node);
164
+ mesh.geometry.dispose();
165
+ mesh.geometry = newGeometry;
166
+ mesh.position.set(0, 0, 0);
167
+ mesh.rotation.set(0, 0, 0);
168
+ }
169
+ export const FenceSystem = () => {
170
+ const dirtyNodes = useScene((state) => state.dirtyNodes);
171
+ const clearDirty = useScene((state) => state.clearDirty);
172
+ useFrame(() => {
173
+ if (dirtyNodes.size === 0)
174
+ return;
175
+ const nodes = useScene.getState().nodes;
176
+ dirtyNodes.forEach((id) => {
177
+ const node = nodes[id];
178
+ if (!node || node.type !== 'fence')
179
+ return;
180
+ updateFenceGeometry(id);
181
+ clearDirty(id);
182
+ });
183
+ }, 4);
184
+ return null;
185
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"guide-system.d.ts","sourceRoot":"","sources":["../../../src/systems/guide/guide-system.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,YAsCvB,CAAA"}
1
+ {"version":3,"file":"guide-system.d.ts","sourceRoot":"","sources":["../../../src/systems/guide/guide-system.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,YAcvB,CAAA"}
@@ -1,4 +1,4 @@
1
- import { emitter, sceneRegistry } from '@pascal-app/core';
1
+ import { sceneRegistry } from '@pascal-app/core';
2
2
  import { useEffect } from 'react';
3
3
  import useViewer from '../../store/use-viewer';
4
4
  export const GuideSystem = () => {
@@ -12,30 +12,5 @@ export const GuideSystem = () => {
12
12
  }
13
13
  });
14
14
  }, [showGuides]);
15
- useEffect(() => {
16
- const hideForCapture = () => {
17
- const guides = sceneRegistry.byType.guide || new Set();
18
- guides.forEach((guideId) => {
19
- const node = sceneRegistry.nodes.get(guideId);
20
- if (node)
21
- node.visible = false;
22
- });
23
- };
24
- const restoreAfterCapture = () => {
25
- const showGuidesNow = useViewer.getState().showGuides;
26
- const guides = sceneRegistry.byType.guide || new Set();
27
- guides.forEach((guideId) => {
28
- const node = sceneRegistry.nodes.get(guideId);
29
- if (node)
30
- node.visible = showGuidesNow;
31
- });
32
- };
33
- emitter.on('thumbnail:before-capture', hideForCapture);
34
- emitter.on('thumbnail:after-capture', restoreAfterCapture);
35
- return () => {
36
- emitter.off('thumbnail:before-capture', hideForCapture);
37
- emitter.off('thumbnail:after-capture', restoreAfterCapture);
38
- };
39
- }, []);
40
15
  return null;
41
16
  };
@@ -0,0 +1,2 @@
1
+ export declare const ItemSystem: () => null;
2
+ //# sourceMappingURL=item-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item/item-system.tsx"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,YA6CtB,CAAA"}
@@ -0,0 +1,44 @@
1
+ import { getScaledDimensions, resolveLevelId, sceneRegistry, spatialGridManager, useScene, } from '@pascal-app/core';
2
+ import { useFrame } from '@react-three/fiber';
3
+ // ============================================================================
4
+ // ITEM SYSTEM
5
+ // ============================================================================
6
+ export const ItemSystem = () => {
7
+ const dirtyNodes = useScene((state) => state.dirtyNodes);
8
+ const clearDirty = useScene((state) => state.clearDirty);
9
+ useFrame(() => {
10
+ if (dirtyNodes.size === 0)
11
+ return;
12
+ const nodes = useScene.getState().nodes;
13
+ dirtyNodes.forEach((id) => {
14
+ const node = nodes[id];
15
+ if (!node || node.type !== 'item')
16
+ return;
17
+ const item = node;
18
+ const mesh = sceneRegistry.nodes.get(id);
19
+ if (!mesh)
20
+ return;
21
+ if (item.asset.attachTo === 'wall-side') {
22
+ // Wall-attached item: offset Z by half the parent wall's thickness
23
+ const parentWall = item.parentId ? nodes[item.parentId] : undefined;
24
+ if (parentWall && parentWall.type === 'wall') {
25
+ const wallThickness = parentWall.thickness ?? 0.1;
26
+ const side = item.side === 'front' ? 1 : -1;
27
+ mesh.position.z = (wallThickness / 2) * side;
28
+ }
29
+ }
30
+ else if (!item.asset.attachTo) {
31
+ // If parented to another item (surface placement), R3F handles positioning via the hierarchy
32
+ const parentNode = item.parentId ? nodes[item.parentId] : undefined;
33
+ if (parentNode?.type !== 'item') {
34
+ // Floor item: elevate by slab height (using full footprint overlap)
35
+ const levelId = resolveLevelId(item, nodes);
36
+ const slabElevation = spatialGridManager.getSlabElevationForItem(levelId, item.position, getScaledDimensions(item), item.rotation);
37
+ mesh.position.y = slabElevation + item.position[1];
38
+ }
39
+ }
40
+ clearDirty(id);
41
+ });
42
+ }, 2);
43
+ return null;
44
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AAiBhG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,GAAG,IAAI,CAmB7E"}
1
+ {"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AA4BhG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,GAAG,IAAI,CA8B7E"}
@@ -1,6 +1,13 @@
1
1
  import { getEffectiveRoofSurfaceMaterial, } from '@pascal-app/core';
2
2
  import * as THREE from 'three';
3
3
  import { createMaterial, createMaterialFromPresetRef } from '../../lib/materials';
4
+ const roofMaterialArrayCache = new Map();
5
+ function getSurfaceMaterialSignature(spec) {
6
+ return JSON.stringify({
7
+ material: spec.material ?? null,
8
+ materialPreset: spec.materialPreset ?? null,
9
+ });
10
+ }
4
11
  function createResolvedMaterial(material, materialPreset) {
5
12
  if (materialPreset) {
6
13
  return createMaterialFromPresetRef(materialPreset);
@@ -14,16 +21,26 @@ export function getRoofMaterialArray(node) {
14
21
  const top = getEffectiveRoofSurfaceMaterial(node, 'top');
15
22
  const edge = getEffectiveRoofSurfaceMaterial(node, 'edge');
16
23
  const wall = getEffectiveRoofSurfaceMaterial(node, 'wall');
24
+ const cacheKey = JSON.stringify({
25
+ top: getSurfaceMaterialSignature(top),
26
+ edge: getSurfaceMaterialSignature(edge),
27
+ wall: getSurfaceMaterialSignature(wall),
28
+ });
29
+ const cached = roofMaterialArrayCache.get(cacheKey);
30
+ if (cached)
31
+ return cached;
17
32
  const topMaterial = createResolvedMaterial(top.material, top.materialPreset);
18
33
  const edgeMaterial = createResolvedMaterial(edge.material, edge.materialPreset);
19
34
  const wallMaterial = createResolvedMaterial(wall.material, wall.materialPreset);
20
35
  if (!(topMaterial || edgeMaterial || wallMaterial)) {
21
36
  return null;
22
37
  }
23
- return [
38
+ const materialArray = [
24
39
  edgeMaterial ?? wallMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
25
40
  wallMaterial ?? edgeMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
26
41
  wallMaterial ?? edgeMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
27
42
  topMaterial ?? wallMaterial ?? edgeMaterial ?? new THREE.MeshStandardMaterial(),
28
43
  ];
44
+ roofMaterialArrayCache.set(cacheKey, materialArray);
45
+ return materialArray;
29
46
  }
@@ -0,0 +1,16 @@
1
+ import { type RoofSegmentNode } from '@pascal-app/core';
2
+ import * as THREE from 'three';
3
+ import { Brush } from 'three-bvh-csg';
4
+ export declare const RoofSystem: () => null;
5
+ /**
6
+ * Generate complete hollow-shell geometry for a roof segment.
7
+ * Ports the prototype's CSG approach using three-bvh-csg.
8
+ */
9
+ export declare function getRoofSegmentBrushes(node: RoofSegmentNode): {
10
+ deckSlab: Brush;
11
+ shinSlab: Brush;
12
+ wallBrush: Brush;
13
+ innerBrush: Brush;
14
+ } | null;
15
+ export declare function generateRoofSegmentGeometry(node: RoofSegmentNode): THREE.BufferGeometry;
16
+ //# sourceMappingURL=roof-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EAIrB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AA4CvE,eAAO,MAAM,UAAU,YAsFtB,CAAA;AAqLD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,eAAe,GACpB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,KAAK,CAAA;CAAE,GAAG,IAAI,CAqRlF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CAAC,cAAc,CAmDvF"}