@pascal-app/core 0.5.0 → 0.6.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 (99) hide show
  1. package/dist/events/bus.d.ts +39 -4
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/events/bus.js +1 -1
  4. package/dist/hooks/scene-registry/scene-registry.d.ts +1 -0
  5. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  6. package/dist/hooks/scene-registry/scene-registry.js +1 -0
  7. package/dist/hooks/spatial-grid/spatial-grid.d.ts +2 -0
  8. package/dist/hooks/spatial-grid/spatial-grid.d.ts.map +1 -1
  9. package/dist/hooks/spatial-grid/spatial-grid.js +43 -20
  10. package/dist/index.d.ts +6 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +5 -1
  13. package/dist/lib/polygon-geometry.d.ts +3 -0
  14. package/dist/lib/polygon-geometry.d.ts.map +1 -0
  15. package/dist/lib/polygon-geometry.js +90 -0
  16. package/dist/lib/space-detection.d.ts +10 -17
  17. package/dist/lib/space-detection.d.ts.map +1 -1
  18. package/dist/lib/space-detection.js +666 -453
  19. package/dist/material-library.d.ts +18 -0
  20. package/dist/material-library.d.ts.map +1 -0
  21. package/dist/material-library.js +603 -0
  22. package/dist/schema/index.d.ts +10 -4
  23. package/dist/schema/index.d.ts.map +1 -1
  24. package/dist/schema/index.js +6 -4
  25. package/dist/schema/material.d.ts +109 -0
  26. package/dist/schema/material.d.ts.map +1 -1
  27. package/dist/schema/material.js +52 -0
  28. package/dist/schema/nodes/ceiling.d.ts +10 -0
  29. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  30. package/dist/schema/nodes/ceiling.js +6 -0
  31. package/dist/schema/nodes/door.d.ts +1 -0
  32. package/dist/schema/nodes/door.d.ts.map +1 -1
  33. package/dist/schema/nodes/fence.d.ts +85 -0
  34. package/dist/schema/nodes/fence.d.ts.map +1 -0
  35. package/dist/schema/nodes/fence.js +34 -0
  36. package/dist/schema/nodes/item.d.ts +2 -2
  37. package/dist/schema/nodes/level.d.ts +1 -1
  38. package/dist/schema/nodes/level.d.ts.map +1 -1
  39. package/dist/schema/nodes/level.js +2 -0
  40. package/dist/schema/nodes/roof-segment.d.ts +2 -0
  41. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  42. package/dist/schema/nodes/roof-segment.js +1 -0
  43. package/dist/schema/nodes/roof.d.ts +108 -0
  44. package/dist/schema/nodes/roof.d.ts.map +1 -1
  45. package/dist/schema/nodes/roof.js +58 -2
  46. package/dist/schema/nodes/site.d.ts +1 -1
  47. package/dist/schema/nodes/slab.d.ts +10 -0
  48. package/dist/schema/nodes/slab.d.ts.map +1 -1
  49. package/dist/schema/nodes/slab.js +7 -0
  50. package/dist/schema/nodes/stair-segment.d.ts +2 -0
  51. package/dist/schema/nodes/stair-segment.d.ts.map +1 -1
  52. package/dist/schema/nodes/stair-segment.js +1 -0
  53. package/dist/schema/nodes/stair.d.ts +164 -0
  54. package/dist/schema/nodes/stair.d.ts.map +1 -1
  55. package/dist/schema/nodes/stair.js +106 -5
  56. package/dist/schema/nodes/surface-hole-metadata.d.ts +10 -0
  57. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -0
  58. package/dist/schema/nodes/surface-hole-metadata.js +5 -0
  59. package/dist/schema/nodes/wall.d.ts +87 -1
  60. package/dist/schema/nodes/wall.d.ts.map +1 -1
  61. package/dist/schema/nodes/wall.js +45 -4
  62. package/dist/schema/nodes/window.d.ts +1 -0
  63. package/dist/schema/nodes/window.d.ts.map +1 -1
  64. package/dist/schema/types.d.ts +406 -4
  65. package/dist/schema/types.d.ts.map +1 -1
  66. package/dist/schema/types.js +2 -0
  67. package/dist/store/actions/node-actions.d.ts +1 -1
  68. package/dist/store/actions/node-actions.d.ts.map +1 -1
  69. package/dist/store/actions/node-actions.js +175 -0
  70. package/dist/store/history-control.d.ts +14 -0
  71. package/dist/store/history-control.d.ts.map +1 -0
  72. package/dist/store/history-control.js +22 -0
  73. package/dist/store/use-scene.d.ts.map +1 -1
  74. package/dist/store/use-scene.js +249 -3
  75. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  76. package/dist/systems/ceiling/ceiling-system.js +7 -0
  77. package/dist/systems/fence/fence-system.d.ts +2 -0
  78. package/dist/systems/fence/fence-system.d.ts.map +1 -0
  79. package/dist/systems/fence/fence-system.js +187 -0
  80. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  81. package/dist/systems/roof/roof-system.js +31 -1
  82. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  83. package/dist/systems/slab/slab-system.js +45 -8
  84. package/dist/systems/stair/stair-opening-sync.d.ts +6 -0
  85. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -0
  86. package/dist/systems/stair/stair-opening-sync.js +515 -0
  87. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  88. package/dist/systems/stair/stair-system.js +432 -10
  89. package/dist/systems/wall/wall-curve.d.ts +43 -0
  90. package/dist/systems/wall/wall-curve.d.ts.map +1 -0
  91. package/dist/systems/wall/wall-curve.js +176 -0
  92. package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
  93. package/dist/systems/wall/wall-footprint.js +16 -2
  94. package/dist/systems/wall/wall-mitering.d.ts +7 -0
  95. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  96. package/dist/systems/wall/wall-mitering.js +76 -3
  97. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  98. package/dist/systems/wall/wall-system.js +202 -2
  99. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAQ9C,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,SA4C/C,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SA+DrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SAuEjB,CAAA"}
1
+ {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAIf,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AA6N9C,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,SA4C/C,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SA+DrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SAsGjB,CAAA"}
@@ -1,6 +1,151 @@
1
+ import { getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, } from '../../schema';
1
2
  // Track pending RAF for updateNodesAction to prevent multiple queued callbacks
2
3
  let pendingRafId = null;
3
4
  let pendingUpdates = new Set();
5
+ function pointsEqual(a, b, tolerance = 1e-6) {
6
+ const dx = a[0] - b[0];
7
+ const dz = a[1] - b[1];
8
+ return dx * dx + dz * dz <= tolerance * tolerance;
9
+ }
10
+ function wallLength(wall) {
11
+ return Math.hypot(wall.end[0] - wall.start[0], wall.end[1] - wall.start[1]);
12
+ }
13
+ function getWallEndpointAtPoint(wall, point) {
14
+ if (pointsEqual(wall.start, point))
15
+ return 'start';
16
+ if (pointsEqual(wall.end, point))
17
+ return 'end';
18
+ return null;
19
+ }
20
+ function getWallFreeEndpoint(wall, sharedPoint) {
21
+ return pointsEqual(wall.start, sharedPoint) ? wall.end : wall.start;
22
+ }
23
+ function areWallStylesCompatible(a, b) {
24
+ const aInterior = getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(a, 'interior'));
25
+ const bInterior = getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(b, 'interior'));
26
+ const aExterior = getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(a, 'exterior'));
27
+ const bExterior = getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(b, 'exterior'));
28
+ return ((a.parentId ?? null) === (b.parentId ?? null) &&
29
+ Math.abs((a.curveOffset ?? 0) - (b.curveOffset ?? 0)) <= 1e-6 &&
30
+ Math.abs((a.thickness ?? 0.2) - (b.thickness ?? 0.2)) <= 1e-6 &&
31
+ Math.abs((a.height ?? 2.5) - (b.height ?? 2.5)) <= 1e-6 &&
32
+ aInterior === bInterior &&
33
+ aExterior === bExterior &&
34
+ a.frontSide === b.frontSide &&
35
+ a.backSide === b.backSide &&
36
+ a.visible === b.visible);
37
+ }
38
+ function areWallsCollinearAcrossPoint(a, b, sharedPoint) {
39
+ const freeA = getWallFreeEndpoint(a, sharedPoint);
40
+ const freeB = getWallFreeEndpoint(b, sharedPoint);
41
+ const ax = freeA[0] - sharedPoint[0];
42
+ const az = freeA[1] - sharedPoint[1];
43
+ const bx = freeB[0] - sharedPoint[0];
44
+ const bz = freeB[1] - sharedPoint[1];
45
+ const lenA = Math.hypot(ax, az);
46
+ const lenB = Math.hypot(bx, bz);
47
+ if (lenA < 1e-6 || lenB < 1e-6)
48
+ return false;
49
+ const cross = (ax * bz - az * bx) / (lenA * lenB);
50
+ const dot = (ax * bx + az * bz) / (lenA * lenB);
51
+ return Math.abs(cross) <= 1e-4 && dot < -0.999;
52
+ }
53
+ function resolveMergedWallEndpoints(primary, secondary, sharedPoint) {
54
+ const primaryEndpoint = getWallEndpointAtPoint(primary, sharedPoint);
55
+ const secondaryEndpoint = getWallEndpointAtPoint(secondary, sharedPoint);
56
+ if (primaryEndpoint === 'end' && secondaryEndpoint === 'start') {
57
+ return { start: primary.start, end: secondary.end };
58
+ }
59
+ if (primaryEndpoint === 'start' && secondaryEndpoint === 'end') {
60
+ return { start: secondary.start, end: primary.end };
61
+ }
62
+ if (primaryEndpoint === 'start' && secondaryEndpoint === 'start') {
63
+ return { start: primary.end, end: secondary.end };
64
+ }
65
+ return { start: primary.start, end: secondary.start };
66
+ }
67
+ function buildMergedWallAttachmentUpdates(primary, secondary, mergedWallId, mergedStart, mergedEnd, nodes) {
68
+ const mergedLength = Math.max(Math.hypot(mergedEnd[0] - mergedStart[0], mergedEnd[1] - mergedStart[1]), 1e-6);
69
+ const tangentX = (mergedEnd[0] - mergedStart[0]) / mergedLength;
70
+ const tangentZ = (mergedEnd[1] - mergedStart[1]) / mergedLength;
71
+ const updates = [];
72
+ const wallChildren = [...(primary.children ?? []), ...(secondary.children ?? [])];
73
+ for (const childId of wallChildren) {
74
+ const child = nodes[childId];
75
+ if (!child || !('position' in child) || !Array.isArray(child.position)) {
76
+ continue;
77
+ }
78
+ const sourceWall = child.parentId === secondary.id ? secondary : primary;
79
+ const sourceLength = Math.max(wallLength(sourceWall), 1e-6);
80
+ const localX = typeof child.position[0] === 'number' ? child.position[0] : 0;
81
+ const worldX = sourceWall.start[0] + ((sourceWall.end[0] - sourceWall.start[0]) * localX) / sourceLength;
82
+ const worldZ = sourceWall.start[1] + ((sourceWall.end[1] - sourceWall.start[1]) * localX) / sourceLength;
83
+ const nextLocalX = Math.max(0, Math.min(mergedLength, (worldX - mergedStart[0]) * tangentX + (worldZ - mergedStart[1]) * tangentZ));
84
+ updates.push({
85
+ id: childId,
86
+ data: {
87
+ parentId: mergedWallId,
88
+ wallId: mergedWallId,
89
+ position: [nextLocalX, child.position[1], child.position[2]],
90
+ ...('wallT' in child ? { wallT: nextLocalX / mergedLength } : {}),
91
+ },
92
+ });
93
+ }
94
+ return updates;
95
+ }
96
+ function buildWallMergePlans(nodes, idsToDelete) {
97
+ const deletedWalls = idsToDelete
98
+ .map((id) => nodes[id])
99
+ .filter((node) => node?.type === 'wall');
100
+ const skippedWallIds = new Set(idsToDelete);
101
+ const usedWallIds = new Set();
102
+ const mergePlans = [];
103
+ for (const deletedWall of deletedWalls) {
104
+ const junctions = [deletedWall.start, deletedWall.end];
105
+ for (const junction of junctions) {
106
+ const candidates = Object.values(nodes).filter((node) => {
107
+ if (node?.type !== 'wall')
108
+ return false;
109
+ if (skippedWallIds.has(node.id) || usedWallIds.has(node.id))
110
+ return false;
111
+ if ((node.parentId ?? null) !== (deletedWall.parentId ?? null))
112
+ return false;
113
+ return pointsEqual(node.start, junction) || pointsEqual(node.end, junction);
114
+ });
115
+ if (candidates.length !== 2) {
116
+ continue;
117
+ }
118
+ const sortedCandidates = [...candidates].sort((a, b) => {
119
+ const attachmentDiff = (b.children?.length ?? 0) - (a.children?.length ?? 0);
120
+ if (attachmentDiff !== 0) {
121
+ return attachmentDiff;
122
+ }
123
+ return a.id.localeCompare(b.id);
124
+ });
125
+ const [primary, secondary] = sortedCandidates;
126
+ if (!primary ||
127
+ !secondary ||
128
+ !areWallStylesCompatible(primary, secondary) ||
129
+ !areWallsCollinearAcrossPoint(primary, secondary, junction)) {
130
+ continue;
131
+ }
132
+ const { start, end } = resolveMergedWallEndpoints(primary, secondary, junction);
133
+ const mergedChildren = Array.from(new Set([...(primary.children ?? []), ...(secondary.children ?? [])]));
134
+ const attachmentUpdates = buildMergedWallAttachmentUpdates(primary, secondary, primary.id, start, end, nodes);
135
+ mergePlans.push({
136
+ primaryWallId: primary.id,
137
+ secondaryWallId: secondary.id,
138
+ mergedStart: start,
139
+ mergedEnd: end,
140
+ mergedChildren,
141
+ attachmentUpdates,
142
+ });
143
+ usedWallIds.add(primary.id);
144
+ usedWallIds.add(secondary.id);
145
+ }
146
+ }
147
+ return mergePlans;
148
+ }
4
149
  export const createNodesAction = (set, get, ops) => {
5
150
  if (get().readOnly)
6
151
  return;
@@ -102,6 +247,8 @@ export const deleteNodesAction = (set, get, ids) => {
102
247
  if (get().readOnly)
103
248
  return;
104
249
  const parentsToMarkDirty = new Set();
250
+ const nodesToMarkDirty = new Set();
251
+ const mergePlans = buildWallMergePlans(get().nodes, ids);
105
252
  set((state) => {
106
253
  const nextNodes = { ...state.nodes };
107
254
  const nextCollections = { ...state.collections };
@@ -121,6 +268,31 @@ export const deleteNodesAction = (set, get, ids) => {
121
268
  };
122
269
  for (const id of ids)
123
270
  collect(id);
271
+ for (const plan of mergePlans) {
272
+ allIds.add(plan.secondaryWallId);
273
+ }
274
+ for (const plan of mergePlans) {
275
+ const primaryWall = nextNodes[plan.primaryWallId];
276
+ if (!(primaryWall && primaryWall.type === 'wall') || allIds.has(plan.primaryWallId)) {
277
+ continue;
278
+ }
279
+ nextNodes[plan.primaryWallId] = {
280
+ ...primaryWall,
281
+ start: plan.mergedStart,
282
+ end: plan.mergedEnd,
283
+ children: plan.mergedChildren,
284
+ };
285
+ nodesToMarkDirty.add(plan.primaryWallId);
286
+ for (const update of plan.attachmentUpdates) {
287
+ if (allIds.has(update.id))
288
+ continue;
289
+ const child = nextNodes[update.id];
290
+ if (!child)
291
+ continue;
292
+ nextNodes[update.id] = { ...child, ...update.data };
293
+ nodesToMarkDirty.add(update.id);
294
+ }
295
+ }
124
296
  for (const id of allIds) {
125
297
  const node = nextNodes[id];
126
298
  if (!node)
@@ -164,4 +336,7 @@ export const deleteNodesAction = (set, get, ids) => {
164
336
  }
165
337
  }
166
338
  });
339
+ nodesToMarkDirty.forEach((id) => {
340
+ get().markDirty(id);
341
+ });
167
342
  };
@@ -0,0 +1,14 @@
1
+ type TemporalStoreLike = {
2
+ temporal: {
3
+ getState(): {
4
+ pause(): void;
5
+ resume(): void;
6
+ };
7
+ };
8
+ };
9
+ export declare function pauseSceneHistory(sceneStore: TemporalStoreLike): void;
10
+ export declare function resumeSceneHistory(sceneStore: TemporalStoreLike): void;
11
+ export declare function getSceneHistoryPauseDepth(): number;
12
+ export declare function resetSceneHistoryPauseDepth(): void;
13
+ export {};
14
+ //# sourceMappingURL=history-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-control.d.ts","sourceRoot":"","sources":["../../src/store/history-control.ts"],"names":[],"mappings":"AAEA,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE;QACR,QAAQ,IAAI;YACV,KAAK,IAAI,IAAI,CAAA;YACb,MAAM,IAAI,IAAI,CAAA;SACf,CAAA;KACF,CAAA;CACF,CAAA;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAKrE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAStE;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
@@ -0,0 +1,22 @@
1
+ let sceneHistoryPauseDepth = 0;
2
+ export function pauseSceneHistory(sceneStore) {
3
+ if (sceneHistoryPauseDepth === 0) {
4
+ sceneStore.temporal.getState().pause();
5
+ }
6
+ sceneHistoryPauseDepth += 1;
7
+ }
8
+ export function resumeSceneHistory(sceneStore) {
9
+ if (sceneHistoryPauseDepth === 0) {
10
+ return;
11
+ }
12
+ sceneHistoryPauseDepth -= 1;
13
+ if (sceneHistoryPauseDepth === 0) {
14
+ sceneStore.temporal.getState().resume();
15
+ }
16
+ }
17
+ export function getSceneHistoryPauseDepth() {
18
+ return sceneHistoryPauseDepth;
19
+ }
20
+ export function resetSceneHistoryPauseDepth() {
21
+ sceneHistoryPauseDepth = 0;
22
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"use-scene.d.ts","sourceRoot":"","sources":["../../src/store/use-scene.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAA;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAIrE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AA8CzD,MAAM,MAAM,UAAU,GAAG;IAEvB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAGjC,WAAW,EAAE,SAAS,EAAE,CAAA;IAGxB,UAAU,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;IAG1B,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;IAG7C,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAGxC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAE/E,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAClC,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAEnC,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;IACzD,WAAW,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAErE,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IAC3D,WAAW,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAE3E,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAGvC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,YAAY,CAAA;IACvE,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAA;IACnF,eAAe,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;IAC9D,oBAAoB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACpE,CAAA;AAID,KAAK,aAAa,GAAG,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG;IACzD,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;CAC7F,CAAA;AAED,QAAA,MAAM,QAAQ,EAAE,aA8Nf,CAAA;AAED,eAAe,QAAQ,CAAA;AAOvB,wBAAgB,iBAAiB,SAKhC"}
1
+ {"version":3,"file":"use-scene.d.ts","sourceRoot":"","sources":["../../src/store/use-scene.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAA;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAMrE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAiVzD,MAAM,MAAM,UAAU,GAAG;IAEvB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAGjC,WAAW,EAAE,SAAS,EAAE,CAAA;IAGxB,UAAU,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;IAG1B,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;IAG7C,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAGxC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAE/E,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAClC,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAEnC,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;IACzD,WAAW,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAErE,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IAC3D,WAAW,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAE3E,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAGvC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,YAAY,CAAA;IACvE,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAA;IACnF,eAAe,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;IAC9D,oBAAoB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACpE,CAAA;AAID,KAAK,aAAa,GAAG,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG;IACzD,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;CAC7F,CAAA;AAED,QAAA,MAAM,QAAQ,EAAE,aA8Nf,CAAA;AAED,eAAe,QAAQ,CAAA;AAOvB,wBAAgB,iBAAiB,SAMhC"}
@@ -5,7 +5,233 @@ import { BuildingNode } from '../schema';
5
5
  import { generateCollectionId } from '../schema/collections';
6
6
  import { LevelNode } from '../schema/nodes/level';
7
7
  import { SiteNode } from '../schema/nodes/site';
8
+ import { StairNode as StairNodeSchema } from '../schema/nodes/stair';
9
+ import { StairSegmentNode as StairSegmentNodeSchema } from '../schema/nodes/stair-segment';
10
+ import { resetSceneHistoryPauseDepth } from './history-control';
8
11
  import * as nodeActions from './actions/node-actions';
12
+ function getFiniteNumber(value, fallback) {
13
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
14
+ }
15
+ function getBoolean(value, fallback) {
16
+ return typeof value === 'boolean' ? value : fallback;
17
+ }
18
+ function getEnumValue(value, allowed, fallback) {
19
+ return typeof value === 'string' && allowed.includes(value) ? value : fallback;
20
+ }
21
+ function getNullableString(value) {
22
+ return typeof value === 'string' ? value : null;
23
+ }
24
+ function getStringArray(value) {
25
+ return Array.isArray(value)
26
+ ? value.filter((entry) => typeof entry === 'string')
27
+ : [];
28
+ }
29
+ function getVector3(value, fallback) {
30
+ if (!Array.isArray(value) || value.length < 3) {
31
+ return fallback;
32
+ }
33
+ return [
34
+ getFiniteNumber(value[0], fallback[0]),
35
+ getFiniteNumber(value[1], fallback[1]),
36
+ getFiniteNumber(value[2], fallback[2]),
37
+ ];
38
+ }
39
+ function normalizeStairNode(node) {
40
+ const sanitized = {
41
+ ...node,
42
+ position: getVector3(node.position, [0, 0, 0]),
43
+ rotation: getFiniteNumber(node.rotation, 0),
44
+ stairType: getEnumValue(node.stairType, ['straight', 'curved', 'spiral'], 'straight'),
45
+ fromLevelId: getNullableString(node.fromLevelId),
46
+ toLevelId: getNullableString(node.toLevelId),
47
+ slabOpeningMode: getEnumValue(node.slabOpeningMode, ['none', 'destination'], 'none'),
48
+ openingOffset: getFiniteNumber(node.openingOffset, 0),
49
+ width: getFiniteNumber(node.width, 1),
50
+ totalRise: getFiniteNumber(node.totalRise, 2.5),
51
+ stepCount: getFiniteNumber(node.stepCount, 10),
52
+ thickness: getFiniteNumber(node.thickness, 0.25),
53
+ fillToFloor: getBoolean(node.fillToFloor, true),
54
+ innerRadius: getFiniteNumber(node.innerRadius, 0.9),
55
+ sweepAngle: getFiniteNumber(node.sweepAngle, Math.PI / 2),
56
+ topLandingMode: getEnumValue(node.topLandingMode, ['none', 'integrated'], 'none'),
57
+ topLandingDepth: getFiniteNumber(node.topLandingDepth, 0.9),
58
+ showCenterColumn: getBoolean(node.showCenterColumn, true),
59
+ showStepSupports: getBoolean(node.showStepSupports, true),
60
+ railingMode: getEnumValue(node.railingMode, ['none', 'left', 'right', 'both'], 'none'),
61
+ railingHeight: getFiniteNumber(node.railingHeight, 0.92),
62
+ children: getStringArray(node.children),
63
+ };
64
+ const parsed = StairNodeSchema.safeParse(sanitized);
65
+ return parsed.success ? parsed.data : null;
66
+ }
67
+ function normalizeStairSegmentNode(node) {
68
+ const sanitized = {
69
+ ...node,
70
+ position: getVector3(node.position, [0, 0, 0]),
71
+ rotation: getFiniteNumber(node.rotation, 0),
72
+ segmentType: getEnumValue(node.segmentType, ['stair', 'landing'], 'stair'),
73
+ width: getFiniteNumber(node.width, 1),
74
+ length: getFiniteNumber(node.length, 3),
75
+ height: getFiniteNumber(node.height, 2.5),
76
+ stepCount: getFiniteNumber(node.stepCount, 10),
77
+ attachmentSide: getEnumValue(node.attachmentSide, ['front', 'left', 'right'], 'front'),
78
+ fillToFloor: getBoolean(node.fillToFloor, true),
79
+ thickness: getFiniteNumber(node.thickness, 0.25),
80
+ };
81
+ const parsed = StairSegmentNodeSchema.safeParse(sanitized);
82
+ return parsed.success ? parsed.data : null;
83
+ }
84
+ function migrateWallSurfaceMaterials(node) {
85
+ const hasInterior = node.interiorMaterial !== undefined || typeof node.interiorMaterialPreset === 'string';
86
+ const hasExterior = node.exteriorMaterial !== undefined || typeof node.exteriorMaterialPreset === 'string';
87
+ const legacyFinish = {
88
+ material: node.material,
89
+ materialPreset: typeof node.materialPreset === 'string' ? node.materialPreset : undefined,
90
+ };
91
+ if (!hasInterior && !hasExterior) {
92
+ if (legacyFinish.material === undefined && legacyFinish.materialPreset === undefined) {
93
+ return node;
94
+ }
95
+ return {
96
+ ...node,
97
+ interiorMaterial: legacyFinish.material,
98
+ interiorMaterialPreset: legacyFinish.materialPreset,
99
+ exteriorMaterial: legacyFinish.material,
100
+ exteriorMaterialPreset: legacyFinish.materialPreset,
101
+ };
102
+ }
103
+ if (!hasInterior) {
104
+ return {
105
+ ...node,
106
+ interiorMaterial: node.exteriorMaterial,
107
+ interiorMaterialPreset: node.exteriorMaterialPreset,
108
+ };
109
+ }
110
+ if (!hasExterior) {
111
+ return {
112
+ ...node,
113
+ exteriorMaterial: node.interiorMaterial,
114
+ exteriorMaterialPreset: node.interiorMaterialPreset,
115
+ };
116
+ }
117
+ return node;
118
+ }
119
+ function migrateStairSurfaceMaterials(node) {
120
+ const hasRailing = node.railingMaterial !== undefined || typeof node.railingMaterialPreset === 'string';
121
+ const hasTread = node.treadMaterial !== undefined || typeof node.treadMaterialPreset === 'string';
122
+ const hasSide = node.sideMaterial !== undefined || typeof node.sideMaterialPreset === 'string';
123
+ const legacyFinish = {
124
+ material: node.material,
125
+ materialPreset: typeof node.materialPreset === 'string' ? node.materialPreset : undefined,
126
+ };
127
+ const resolveBodyFallback = () => {
128
+ if (node.treadMaterial !== undefined || typeof node.treadMaterialPreset === 'string') {
129
+ return {
130
+ material: node.treadMaterial,
131
+ materialPreset: typeof node.treadMaterialPreset === 'string' ? node.treadMaterialPreset : undefined,
132
+ };
133
+ }
134
+ if (node.sideMaterial !== undefined || typeof node.sideMaterialPreset === 'string') {
135
+ return {
136
+ material: node.sideMaterial,
137
+ materialPreset: typeof node.sideMaterialPreset === 'string' ? node.sideMaterialPreset : undefined,
138
+ };
139
+ }
140
+ return legacyFinish;
141
+ };
142
+ if (!hasRailing && !hasTread && !hasSide) {
143
+ if (legacyFinish.material === undefined && legacyFinish.materialPreset === undefined) {
144
+ return node;
145
+ }
146
+ return {
147
+ ...node,
148
+ railingMaterial: legacyFinish.material,
149
+ railingMaterialPreset: legacyFinish.materialPreset,
150
+ treadMaterial: legacyFinish.material,
151
+ treadMaterialPreset: legacyFinish.materialPreset,
152
+ sideMaterial: legacyFinish.material,
153
+ sideMaterialPreset: legacyFinish.materialPreset,
154
+ };
155
+ }
156
+ const next = { ...node };
157
+ if (!hasTread) {
158
+ const fallback = node.sideMaterial !== undefined || typeof node.sideMaterialPreset === 'string'
159
+ ? {
160
+ material: node.sideMaterial,
161
+ materialPreset: typeof node.sideMaterialPreset === 'string' ? node.sideMaterialPreset : undefined,
162
+ }
163
+ : resolveBodyFallback();
164
+ next.treadMaterial = fallback.material;
165
+ next.treadMaterialPreset = fallback.materialPreset;
166
+ }
167
+ if (!hasSide) {
168
+ const fallback = node.treadMaterial !== undefined || typeof node.treadMaterialPreset === 'string'
169
+ ? {
170
+ material: node.treadMaterial,
171
+ materialPreset: typeof node.treadMaterialPreset === 'string' ? node.treadMaterialPreset : undefined,
172
+ }
173
+ : resolveBodyFallback();
174
+ next.sideMaterial = fallback.material;
175
+ next.sideMaterialPreset = fallback.materialPreset;
176
+ }
177
+ if (!hasRailing) {
178
+ const fallback = resolveBodyFallback();
179
+ next.railingMaterial = fallback.material;
180
+ next.railingMaterialPreset = fallback.materialPreset;
181
+ }
182
+ return next;
183
+ }
184
+ function migrateRoofSurfaceMaterials(node) {
185
+ const hasTop = node.topMaterial !== undefined || typeof node.topMaterialPreset === 'string';
186
+ const hasEdge = node.edgeMaterial !== undefined || typeof node.edgeMaterialPreset === 'string';
187
+ const hasWall = node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string';
188
+ const legacyFinish = {
189
+ material: node.material,
190
+ materialPreset: typeof node.materialPreset === 'string' ? node.materialPreset : undefined,
191
+ };
192
+ if (!hasTop && !hasEdge && !hasWall) {
193
+ if (legacyFinish.material === undefined && legacyFinish.materialPreset === undefined) {
194
+ return node;
195
+ }
196
+ return {
197
+ ...node,
198
+ topMaterial: legacyFinish.material,
199
+ topMaterialPreset: legacyFinish.materialPreset,
200
+ edgeMaterial: legacyFinish.material,
201
+ edgeMaterialPreset: legacyFinish.materialPreset,
202
+ wallMaterial: legacyFinish.material,
203
+ wallMaterialPreset: legacyFinish.materialPreset,
204
+ };
205
+ }
206
+ const next = { ...node };
207
+ if (!hasTop) {
208
+ next.topMaterial = legacyFinish.material;
209
+ next.topMaterialPreset = legacyFinish.materialPreset;
210
+ }
211
+ if (!hasEdge) {
212
+ if (node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string') {
213
+ next.edgeMaterial = node.wallMaterial;
214
+ next.edgeMaterialPreset =
215
+ typeof node.wallMaterialPreset === 'string' ? node.wallMaterialPreset : undefined;
216
+ }
217
+ else {
218
+ next.edgeMaterial = legacyFinish.material;
219
+ next.edgeMaterialPreset = legacyFinish.materialPreset;
220
+ }
221
+ }
222
+ if (!hasWall) {
223
+ if (node.edgeMaterial !== undefined || typeof node.edgeMaterialPreset === 'string') {
224
+ next.wallMaterial = node.edgeMaterial;
225
+ next.wallMaterialPreset =
226
+ typeof node.edgeMaterialPreset === 'string' ? node.edgeMaterialPreset : undefined;
227
+ }
228
+ else {
229
+ next.wallMaterial = legacyFinish.material;
230
+ next.wallMaterialPreset = legacyFinish.materialPreset;
231
+ }
232
+ }
233
+ return next;
234
+ }
9
235
  function migrateNodes(nodes) {
10
236
  const patchedNodes = { ...nodes };
11
237
  for (const [id, node] of Object.entries(patchedNodes)) {
@@ -43,6 +269,24 @@ function migrateNodes(nodes) {
43
269
  children: [segmentId],
44
270
  };
45
271
  }
272
+ if (node.type === 'stair') {
273
+ const normalized = normalizeStairNode(migrateStairSurfaceMaterials(node));
274
+ if (normalized) {
275
+ patchedNodes[id] = normalized;
276
+ }
277
+ }
278
+ if (node.type === 'stair-segment') {
279
+ const normalized = normalizeStairSegmentNode(node);
280
+ if (normalized) {
281
+ patchedNodes[id] = normalized;
282
+ }
283
+ }
284
+ if (node.type === 'wall') {
285
+ patchedNodes[id] = migrateWallSurfaceMaterials(patchedNodes[id]);
286
+ }
287
+ if (node.type === 'roof') {
288
+ patchedNodes[id] = migrateRoofSurfaceMaterials(patchedNodes[id]);
289
+ }
46
290
  }
47
291
  return patchedNodes;
48
292
  }
@@ -246,6 +490,7 @@ let prevFutureLength = 0;
246
490
  let prevNodesSnapshot = null;
247
491
  export function clearSceneHistory() {
248
492
  useScene.temporal.getState().clear();
493
+ resetSceneHistoryPauseDepth();
249
494
  prevPastLength = 0;
250
495
  prevFutureLength = 0;
251
496
  prevNodesSnapshot = null;
@@ -261,8 +506,9 @@ useScene.temporal.subscribe((state) => {
261
506
  if (didUndo || didRedo) {
262
507
  // Capture the previous snapshot before RAF fires
263
508
  const snapshotBefore = prevNodesSnapshot;
264
- // Use RAF to ensure all middleware and store updates are complete
265
- requestAnimationFrame(() => {
509
+ // Defer to a microtask so the scene store has settled before we diff,
510
+ // but still mark walls/items dirty before the next paint.
511
+ queueMicrotask(() => {
266
512
  const currentNodes = useScene.getState().nodes;
267
513
  const { markDirty } = useScene.getState();
268
514
  if (snapshotBefore) {
@@ -284,7 +530,7 @@ useScene.temporal.subscribe((state) => {
284
530
  // Mark sibling nodes dirty so they can update their geometry
285
531
  // (e.g. adjacent walls need to recalculate miter/junction geometry)
286
532
  const parent = currentNodes[parentId];
287
- if (parent && 'children' in parent) {
533
+ if (parent && 'children' in parent && Array.isArray(parent.children)) {
288
534
  for (const childId of parent.children) {
289
535
  markDirty(childId);
290
536
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,cAAc,CAAA;AAO1D,eAAO,MAAM,aAAa,YAuBzB,CAAA;AAqBD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,KAAK,CAAC,cAAc,CA+CtF"}
1
+ {"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,cAAc,CAAA;AAc1D,eAAO,MAAM,aAAa,YAuBzB,CAAA;AAqBD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,KAAK,CAAC,cAAc,CAgDtF"}
@@ -2,6 +2,12 @@ import { useFrame } from '@react-three/fiber';
2
2
  import * as THREE from 'three';
3
3
  import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
4
4
  import useScene from '../../store/use-scene';
5
+ function ensureUv2Attribute(geometry) {
6
+ const uv = geometry.getAttribute('uv');
7
+ if (!uv)
8
+ return;
9
+ geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2));
10
+ }
5
11
  // ============================================================================
6
12
  // CEILING SYSTEM
7
13
  // ============================================================================
@@ -81,5 +87,6 @@ export function generateCeilingGeometry(ceilingNode) {
81
87
  // Rotate so the shape lies flat in X-Z plane
82
88
  geometry.rotateX(-Math.PI / 2);
83
89
  geometry.computeVertexNormals();
90
+ ensureUv2Attribute(geometry);
84
91
  return geometry;
85
92
  }
@@ -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":"AAyQA,eAAO,MAAM,WAAW,YAiBvB,CAAA"}