@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
@@ -0,0 +1,187 @@
1
+ import { useFrame } from '@react-three/fiber';
2
+ import * as THREE from 'three';
3
+ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
4
+ import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
5
+ import useScene from '../../store/use-scene';
6
+ import { getWallCurveFrameAt, getWallCurveLength } from '../wall/wall-curve';
7
+ const MIN_CURVE_SEGMENT_LENGTH = 0.18;
8
+ function createFencePartGeometry(part) {
9
+ const geometry = new THREE.BoxGeometry(1, 1, 1);
10
+ geometry.scale(part.scale[0], part.scale[1], part.scale[2]);
11
+ if (part.rotationY) {
12
+ geometry.rotateY(part.rotationY);
13
+ }
14
+ geometry.translate(part.position[0], part.position[1], part.position[2]);
15
+ applyFenceUVs(geometry);
16
+ return geometry;
17
+ }
18
+ function getFencePointAt(fence, t) {
19
+ const frame = getWallCurveFrameAt(fence, t);
20
+ return {
21
+ point: frame.point,
22
+ tangentAngle: Math.atan2(frame.tangent.y, frame.tangent.x),
23
+ };
24
+ }
25
+ function createStraightFenceSpanPart(start, end, centerY, height, depth) {
26
+ const dx = end[0] - start[0];
27
+ const dz = end[1] - start[1];
28
+ const length = Math.hypot(dx, dz);
29
+ if (length <= 1e-4) {
30
+ return null;
31
+ }
32
+ return {
33
+ position: [(start[0] + end[0]) / 2, centerY, (start[1] + end[1]) / 2],
34
+ rotationY: -Math.atan2(dz, dx),
35
+ scale: [length, height, depth],
36
+ };
37
+ }
38
+ function createFenceCurveSpanParts(fence, startT, endT, centerY, height, depth) {
39
+ const parts = [];
40
+ const frameCount = Math.max(1, Math.ceil((getWallCurveLength(fence) * Math.max(1e-4, endT - startT)) / MIN_CURVE_SEGMENT_LENGTH));
41
+ let previous = getFencePointAt(fence, startT);
42
+ for (let index = 1; index <= frameCount; index += 1) {
43
+ const t = startT + (endT - startT) * (index / frameCount);
44
+ const current = getFencePointAt(fence, t);
45
+ const segment = createStraightFenceSpanPart([previous.point.x, previous.point.y], [current.point.x, current.point.y], centerY, height, depth);
46
+ if (segment) {
47
+ parts.push(segment);
48
+ }
49
+ previous = current;
50
+ }
51
+ return parts;
52
+ }
53
+ function applyFenceUVs(geometry) {
54
+ const position = geometry.getAttribute('position');
55
+ const normal = geometry.getAttribute('normal');
56
+ if (!(position && normal))
57
+ return;
58
+ const uvs = new Float32Array(position.count * 2);
59
+ let minX = Number.POSITIVE_INFINITY;
60
+ let minY = Number.POSITIVE_INFINITY;
61
+ let minZ = Number.POSITIVE_INFINITY;
62
+ for (let index = 0; index < position.count; index += 1) {
63
+ minX = Math.min(minX, position.getX(index));
64
+ minY = Math.min(minY, position.getY(index));
65
+ minZ = Math.min(minZ, position.getZ(index));
66
+ }
67
+ for (let index = 0; index < position.count; index += 1) {
68
+ const px = position.getX(index);
69
+ const py = position.getY(index);
70
+ const pz = position.getZ(index);
71
+ const nx = Math.abs(normal.getX(index));
72
+ const ny = Math.abs(normal.getY(index));
73
+ const nz = Math.abs(normal.getZ(index));
74
+ let u = 0;
75
+ let v = 0;
76
+ if (ny >= nx && ny >= nz) {
77
+ u = px - minX;
78
+ v = pz - minZ;
79
+ }
80
+ else if (nx >= nz) {
81
+ u = pz - minZ;
82
+ v = py - minY;
83
+ }
84
+ else {
85
+ u = px - minX;
86
+ v = py - minY;
87
+ }
88
+ uvs[index * 2] = u;
89
+ uvs[index * 2 + 1] = v;
90
+ }
91
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
92
+ geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(uvs.slice(), 2));
93
+ }
94
+ function getStyleDefaults(style) {
95
+ if (style === 'privacy') {
96
+ return { spacingFactor: 0.42, postFactor: 1.35, baseFactor: 1.2, topFactor: 1.2 };
97
+ }
98
+ if (style === 'rail') {
99
+ return { spacingFactor: 0.68, postFactor: 0.8, baseFactor: 0.85, topFactor: 0.85 };
100
+ }
101
+ return { spacingFactor: 0.3, postFactor: 0.55, baseFactor: 1, topFactor: 0.75 };
102
+ }
103
+ function createFenceParts(fence) {
104
+ const parts = [];
105
+ const length = Math.max(getWallCurveLength(fence), 0.01);
106
+ const panelDepth = Math.max(fence.thickness, 0.03);
107
+ const clearance = Math.max(fence.groundClearance, 0);
108
+ const styleDefaults = getStyleDefaults(fence.style);
109
+ const baseHeight = Math.max(fence.baseHeight * styleDefaults.baseFactor, 0.04);
110
+ const topRailHeight = Math.max(fence.topRailHeight * styleDefaults.topFactor, 0.01);
111
+ const verticalHeight = Math.max(fence.height - baseHeight - topRailHeight, 0.08);
112
+ const postWidth = Math.max(fence.postSize * styleDefaults.postFactor, 0.01);
113
+ const spacing = Math.max(fence.postSpacing * styleDefaults.spacingFactor, postWidth * 1.2);
114
+ const edgeInset = Math.max(fence.edgeInset ?? 0.015, 0.005);
115
+ const isFloating = fence.baseStyle === 'floating';
116
+ const baseY = isFloating ? clearance : 0;
117
+ const effectiveBaseHeight = baseHeight;
118
+ const startInsetT = Math.min(0.499, edgeInset / length);
119
+ const endInsetT = Math.max(0.501, 1 - edgeInset / length);
120
+ if (!isFloating) {
121
+ parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight / 2, effectiveBaseHeight, panelDepth * 1.05));
122
+ parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight * 0.15, topRailHeight * 0.8, panelDepth * 0.35));
123
+ }
124
+ const count = Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1);
125
+ const verticalY = baseY + effectiveBaseHeight + verticalHeight / 2;
126
+ for (let index = 0; index < count; index += 1) {
127
+ const t = count === 1 ? 0.5 : startInsetT + (endInsetT - startInsetT) * (index / (count - 1));
128
+ const frame = getFencePointAt(fence, t);
129
+ const isEdgePost = index === 0 || index === count - 1;
130
+ const postHeight = isFloating && isEdgePost
131
+ ? effectiveBaseHeight + verticalHeight + topRailHeight + clearance
132
+ : verticalHeight;
133
+ const postY = isFloating && isEdgePost ? postHeight / 2 : verticalY;
134
+ parts.push({
135
+ position: [frame.point.x, postY, frame.point.y],
136
+ rotationY: -frame.tangentAngle,
137
+ scale: [postWidth, postHeight, Math.max(panelDepth * 0.35, 0.012)],
138
+ });
139
+ }
140
+ parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
141
+ if (isFloating) {
142
+ parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
143
+ }
144
+ return parts;
145
+ }
146
+ function generateFenceGeometry(fence) {
147
+ const parts = createFenceParts(fence);
148
+ const geometries = parts.map(createFencePartGeometry);
149
+ const merged = mergeGeometries(geometries, false) ?? new THREE.BufferGeometry();
150
+ geometries.forEach((geometry) => geometry.dispose());
151
+ const mergedUv = merged.getAttribute('uv');
152
+ if (mergedUv) {
153
+ merged.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(mergedUv.array), 2));
154
+ }
155
+ merged.computeVertexNormals();
156
+ return merged;
157
+ }
158
+ function updateFenceGeometry(fenceId) {
159
+ const node = useScene.getState().nodes[fenceId];
160
+ if (!node || node.type !== 'fence')
161
+ return;
162
+ const mesh = sceneRegistry.nodes.get(fenceId);
163
+ if (!mesh)
164
+ return;
165
+ const newGeometry = generateFenceGeometry(node);
166
+ mesh.geometry.dispose();
167
+ mesh.geometry = newGeometry;
168
+ mesh.position.set(0, 0, 0);
169
+ mesh.rotation.set(0, 0, 0);
170
+ }
171
+ export const FenceSystem = () => {
172
+ const dirtyNodes = useScene((state) => state.dirtyNodes);
173
+ const clearDirty = useScene((state) => state.clearDirty);
174
+ useFrame(() => {
175
+ if (dirtyNodes.size === 0)
176
+ return;
177
+ const nodes = useScene.getState().nodes;
178
+ dirtyNodes.forEach((id) => {
179
+ const node = nodes[id];
180
+ if (!node || node.type !== 'fence')
181
+ return;
182
+ updateFenceGeometry(id);
183
+ clearDirty(id);
184
+ });
185
+ }, 4);
186
+ return null;
187
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAGvE,OAAO,KAAK,EAAgC,eAAe,EAAE,MAAM,cAAc,CAAA;AA+BjF,eAAO,MAAM,UAAU,YAuFtB,CAAA;AAuLD;;;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,CAsRlF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CAAC,cAAc,CAoDvF"}
1
+ {"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAGvE,OAAO,KAAK,EAAgC,eAAe,EAAE,MAAM,cAAc,CAAA;AAgCjF,eAAO,MAAM,UAAU,YAuFtB,CAAA;AAwLD;;;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,CAsRlF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CAAC,cAAc,CAqDvF"}
@@ -8,7 +8,7 @@ import useScene from '../../store/use-scene';
8
8
  const csgEvaluator = new Evaluator();
9
9
  csgEvaluator.useGroups = true;
10
10
  csgEvaluator.consolidateGroups = false; // shared dummyMats across brushes causes consolidation to misalign groupIndices vs groupOrder indices → crash
11
- csgEvaluator.attributes = ['position', 'normal'];
11
+ csgEvaluator.attributes = ['position', 'normal', 'uv'];
12
12
  function prepareBrushForCSG(brush) {
13
13
  brush.geometry.computeBoundsTree = computeBoundsTree;
14
14
  brush.geometry.computeBoundsTree({ maxLeafSize: 10 });
@@ -20,6 +20,7 @@ const _position = new THREE.Vector3();
20
20
  const _quaternion = new THREE.Quaternion();
21
21
  const _scale = new THREE.Vector3(1, 1, 1);
22
22
  const _yAxis = new THREE.Vector3(0, 1, 0);
23
+ const _uvFaceNormal = new THREE.Vector3();
23
24
  // Pending merged-roof updates carried across frames (for throttling)
24
25
  const pendingRoofUpdates = new Set();
25
26
  const MAX_ROOFS_PER_FRAME = 1;
@@ -217,6 +218,7 @@ function updateMergedRoofGeometry(roofNode, group, nodes) {
217
218
  for (const g of resultGeo.groups) {
218
219
  g.materialIndex = mapRoofGroupMaterialIndex(g.materialIndex, resultMaterials, matToIndex);
219
220
  }
221
+ ensureUv2Attribute(resultGeo);
220
222
  resultGeo.computeVertexNormals();
221
223
  mergedMesh.geometry.dispose();
222
224
  mergedMesh.geometry = resultGeo;
@@ -500,6 +502,7 @@ export function generateRoofSegmentGeometry(node) {
500
502
  shinSlab.geometry.dispose();
501
503
  wallBrush.geometry.dispose();
502
504
  innerBrush.geometry.dispose();
505
+ ensureUv2Attribute(resultGeo);
503
506
  resultGeo.computeVertexNormals();
504
507
  return resultGeo;
505
508
  }
@@ -709,6 +712,7 @@ function getModuleFaces(type, w, d, wh, rh, baseY, insets, baseW, baseD, tanThet
709
712
  function createGeometryFromFaces(faces, matRule = null) {
710
713
  const positions = [];
711
714
  const normals = [];
715
+ const uvs = [];
712
716
  const indices = [];
713
717
  const groups = [];
714
718
  let vertexCount = 0;
@@ -743,6 +747,9 @@ function createGeometryFromFaces(faces, matRule = null) {
743
747
  normals.push(normal.x, normal.y, normal.z);
744
748
  normals.push(normal.x, normal.y, normal.z);
745
749
  normals.push(normal.x, normal.y, normal.z);
750
+ pushRoofUv(uvs, p0, normal);
751
+ pushRoofUv(uvs, fi, normal);
752
+ pushRoofUv(uvs, fi1, normal);
746
753
  indices.push(vertexCount, vertexCount + 1, vertexCount + 2);
747
754
  faceVertexCount += 3;
748
755
  vertexCount += 3;
@@ -756,6 +763,7 @@ function createGeometryFromFaces(faces, matRule = null) {
756
763
  const geometry = new THREE.BufferGeometry();
757
764
  geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
758
765
  geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
766
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
759
767
  geometry.setIndex(indices);
760
768
  for (const g of groups) {
761
769
  geometry.addGroup(g.start, g.count, g.materialIndex);
@@ -763,5 +771,27 @@ function createGeometryFromFaces(faces, matRule = null) {
763
771
  // Merge identical vertices to optimize geometry for CSG and create clean topology
764
772
  const mergedGeo = mergeVertices(geometry, 1e-4);
765
773
  geometry.dispose();
774
+ ensureUv2Attribute(mergedGeo);
766
775
  return mergedGeo;
767
776
  }
777
+ function pushRoofUv(uvs, point, normal) {
778
+ _uvFaceNormal.copy(normal).normalize();
779
+ const absX = Math.abs(_uvFaceNormal.x);
780
+ const absY = Math.abs(_uvFaceNormal.y);
781
+ const absZ = Math.abs(_uvFaceNormal.z);
782
+ if (absY >= absX && absY >= absZ) {
783
+ uvs.push(point.x, point.z);
784
+ return;
785
+ }
786
+ if (absX >= absZ) {
787
+ uvs.push(point.z, point.y);
788
+ return;
789
+ }
790
+ uvs.push(point.x, point.y);
791
+ }
792
+ function ensureUv2Attribute(geometry) {
793
+ const uv = geometry.getAttribute('uv');
794
+ if (!uv)
795
+ return;
796
+ geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2));
797
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAOvD,eAAO,MAAM,UAAU,YAwBtB,CAAA;AAuED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC,cAAc,CAG7E"}
1
+ {"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,cAAc,CAAA;AAcvD,eAAO,MAAM,UAAU,YAwBtB,CAAA;AAmFD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC,cAAc,CAG7E"}
@@ -1,7 +1,14 @@
1
1
  import { useFrame } from '@react-three/fiber';
2
2
  import * as THREE from 'three';
3
3
  import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
4
+ import { insetPolygonFromCentroid, simplifyClosedPolygon } from '../../lib/polygon-geometry';
4
5
  import useScene from '../../store/use-scene';
6
+ function ensureUv2Attribute(geometry) {
7
+ const uv = geometry.getAttribute('uv');
8
+ if (!uv)
9
+ return;
10
+ geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2));
11
+ }
5
12
  // ============================================================================
6
13
  // SLAB SYSTEM
7
14
  // ============================================================================
@@ -32,6 +39,7 @@ export const SlabSystem = () => {
32
39
  */
33
40
  function updateSlabGeometry(node, mesh) {
34
41
  const newGeo = generateSlabGeometry(node);
42
+ ensureUv2Attribute(newGeo);
35
43
  mesh.geometry.dispose();
36
44
  mesh.geometry = newGeo;
37
45
  // For negative elevation, shift the mesh down so the top face sits at Y=elevation
@@ -41,6 +49,13 @@ function updateSlabGeometry(node, mesh) {
41
49
  }
42
50
  /** Half of default wall thickness — used to extend slab geometry under walls */
43
51
  const SLAB_OUTSET = 0.05;
52
+ const AUTO_SLAB_INSET = 0.02;
53
+ const AUTO_SLAB_SIMPLIFY_TOLERANCE = 0.08;
54
+ function getRenderableSlabPolygon(slabNode) {
55
+ return slabNode.autoFromWalls
56
+ ? simplifyClosedPolygon(insetPolygonFromCentroid(slabNode.polygon, AUTO_SLAB_INSET), AUTO_SLAB_SIMPLIFY_TOLERANCE)
57
+ : outsetPolygon(slabNode.polygon, SLAB_OUTSET);
58
+ }
44
59
  /**
45
60
  * Expand a polygon outward by a uniform distance.
46
61
  * Offsets each edge outward then intersects consecutive offset edges.
@@ -100,7 +115,7 @@ export function generateSlabGeometry(slabNode) {
100
115
  * Standard slab: flat extrusion upward from Y=0 by elevation thickness.
101
116
  */
102
117
  function generatePositiveSlabGeometry(slabNode) {
103
- const polygon = outsetPolygon(slabNode.polygon, SLAB_OUTSET);
118
+ const polygon = getRenderableSlabPolygon(slabNode);
104
119
  const elevation = slabNode.elevation ?? 0.05;
105
120
  if (polygon.length < 3)
106
121
  return new THREE.BufferGeometry();
@@ -134,21 +149,41 @@ function generatePositiveSlabGeometry(slabNode) {
134
149
  * - walls from Y=0 to Y=depth, inward-facing normals (visible from inside pool)
135
150
  */
136
151
  function generatePoolGeometry(slabNode) {
137
- const polygon = outsetPolygon(slabNode.polygon, SLAB_OUTSET);
152
+ const polygon = getRenderableSlabPolygon(slabNode);
138
153
  const depth = Math.abs(slabNode.elevation ?? 0.05);
139
154
  if (polygon.length < 3)
140
155
  return new THREE.BufferGeometry();
141
156
  const positions = [];
157
+ const uvs = [];
142
158
  const indices = [];
143
159
  const n = polygon.length;
160
+ const bounds = new THREE.Box2();
161
+ for (const [x, z] of polygon) {
162
+ bounds.expandByPoint(new THREE.Vector2(x, z));
163
+ }
164
+ for (const hole of slabNode.holes ?? []) {
165
+ for (const [x, z] of hole) {
166
+ bounds.expandByPoint(new THREE.Vector2(x, z));
167
+ }
168
+ }
169
+ const floorWidth = Math.max(bounds.max.x - bounds.min.x, 0.001);
170
+ const floorHeight = Math.max(bounds.max.y - bounds.min.y, 0.001);
171
+ const pushFloorVertex = (x, y, z) => {
172
+ positions.push(x, y, z);
173
+ uvs.push((x - bounds.min.x) / floorWidth, (z - bounds.min.y) / floorHeight);
174
+ };
175
+ const pushWallVertex = (x, y, z, u, v) => {
176
+ positions.push(x, y, z);
177
+ uvs.push(u, v);
178
+ };
144
179
  // --- Floor at Y=0 ---
145
180
  for (const [x, z] of polygon)
146
- positions.push(x, 0, z);
181
+ pushFloorVertex(x, 0, z);
147
182
  const pts2d = polygon.map(([x, z]) => new THREE.Vector2(x, z));
148
183
  const holesPts2d = (slabNode.holes ?? []).map((h) => h.map(([x, z]) => new THREE.Vector2(x, z)));
149
184
  for (const hole of slabNode.holes ?? []) {
150
185
  for (const [x, z] of hole)
151
- positions.push(x, 0, z);
186
+ pushFloorVertex(x, 0, z);
152
187
  }
153
188
  const floorTris = THREE.ShapeUtils.triangulateShape(pts2d, holesPts2d);
154
189
  for (const tri of floorTris) {
@@ -162,15 +197,17 @@ function generatePoolGeometry(slabNode) {
162
197
  const [x0, z0] = polygon[i];
163
198
  const [x1, z1] = polygon[j];
164
199
  const vBase = positions.length / 3;
165
- positions.push(x0, 0, z0); // v0 — floor level
166
- positions.push(x1, 0, z1); // v1 — floor level
167
- positions.push(x1, depth, z1); // v2ground level
168
- positions.push(x0, depth, z0); // v3 — ground level
200
+ const segmentLength = Math.max(Math.hypot(x1 - x0, z1 - z0), 0.001);
201
+ pushWallVertex(x0, 0, z0, 0, 0); // v0 — floor level
202
+ pushWallVertex(x1, 0, z1, segmentLength, 0); // v1floor level
203
+ pushWallVertex(x1, depth, z1, segmentLength, depth); // v2 — ground level
204
+ pushWallVertex(x0, depth, z0, 0, depth); // v3 — ground level
169
205
  indices.push(vBase, vBase + 1, vBase + 2);
170
206
  indices.push(vBase, vBase + 2, vBase + 3);
171
207
  }
172
208
  const geo = new THREE.BufferGeometry();
173
209
  geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
210
+ geo.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
174
211
  geo.setIndex(indices);
175
212
  geo.computeVertexNormals();
176
213
  return geo;
@@ -0,0 +1,6 @@
1
+ import type { AnyNode, AnyNodeId, CeilingNode, SlabNode } from '../../schema';
2
+ export declare function syncAutoStairOpenings(nodes: Record<string, AnyNode>): {
3
+ id: AnyNodeId;
4
+ data: Partial<SlabNode | CeilingNode>;
5
+ }[];
6
+ //# sourceMappingURL=stair-opening-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stair-opening-sync.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-opening-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAa,QAAQ,EAA+B,MAAM,cAAc,CAAA;AAulBrH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;QAIvC,SAAS;UAAQ,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC;IA6F5E"}