@pascal-app/core 0.5.1 → 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 (91) hide show
  1. package/dist/events/bus.d.ts +37 -3
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/events/bus.js +1 -1
  4. package/dist/hooks/spatial-grid/spatial-grid.d.ts +2 -0
  5. package/dist/hooks/spatial-grid/spatial-grid.d.ts.map +1 -1
  6. package/dist/hooks/spatial-grid/spatial-grid.js +43 -20
  7. package/dist/index.d.ts +4 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -1
  10. package/dist/lib/polygon-geometry.d.ts +3 -0
  11. package/dist/lib/polygon-geometry.d.ts.map +1 -0
  12. package/dist/lib/polygon-geometry.js +90 -0
  13. package/dist/lib/space-detection.d.ts +10 -17
  14. package/dist/lib/space-detection.d.ts.map +1 -1
  15. package/dist/lib/space-detection.js +666 -453
  16. package/dist/material-library.d.ts +18 -0
  17. package/dist/material-library.d.ts.map +1 -0
  18. package/dist/material-library.js +603 -0
  19. package/dist/schema/index.d.ts +9 -4
  20. package/dist/schema/index.d.ts.map +1 -1
  21. package/dist/schema/index.js +5 -4
  22. package/dist/schema/material.d.ts +109 -0
  23. package/dist/schema/material.d.ts.map +1 -1
  24. package/dist/schema/material.js +52 -0
  25. package/dist/schema/nodes/ceiling.d.ts +10 -0
  26. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  27. package/dist/schema/nodes/ceiling.js +6 -0
  28. package/dist/schema/nodes/door.d.ts +1 -0
  29. package/dist/schema/nodes/door.d.ts.map +1 -1
  30. package/dist/schema/nodes/fence.d.ts +34 -0
  31. package/dist/schema/nodes/fence.d.ts.map +1 -1
  32. package/dist/schema/nodes/fence.js +5 -0
  33. package/dist/schema/nodes/item.d.ts +2 -2
  34. package/dist/schema/nodes/roof-segment.d.ts +2 -0
  35. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  36. package/dist/schema/nodes/roof-segment.js +1 -0
  37. package/dist/schema/nodes/roof.d.ts +108 -0
  38. package/dist/schema/nodes/roof.d.ts.map +1 -1
  39. package/dist/schema/nodes/roof.js +58 -2
  40. package/dist/schema/nodes/site.d.ts +1 -1
  41. package/dist/schema/nodes/slab.d.ts +10 -0
  42. package/dist/schema/nodes/slab.d.ts.map +1 -1
  43. package/dist/schema/nodes/slab.js +7 -0
  44. package/dist/schema/nodes/stair-segment.d.ts +2 -0
  45. package/dist/schema/nodes/stair-segment.d.ts.map +1 -1
  46. package/dist/schema/nodes/stair-segment.js +1 -0
  47. package/dist/schema/nodes/stair.d.ts +122 -2
  48. package/dist/schema/nodes/stair.d.ts.map +1 -1
  49. package/dist/schema/nodes/stair.js +72 -2
  50. package/dist/schema/nodes/surface-hole-metadata.d.ts +10 -0
  51. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -0
  52. package/dist/schema/nodes/surface-hole-metadata.js +5 -0
  53. package/dist/schema/nodes/wall.d.ts +87 -1
  54. package/dist/schema/nodes/wall.d.ts.map +1 -1
  55. package/dist/schema/nodes/wall.js +45 -4
  56. package/dist/schema/nodes/window.d.ts +1 -0
  57. package/dist/schema/nodes/window.d.ts.map +1 -1
  58. package/dist/schema/types.d.ts +343 -5
  59. package/dist/schema/types.d.ts.map +1 -1
  60. package/dist/store/actions/node-actions.d.ts +1 -1
  61. package/dist/store/actions/node-actions.d.ts.map +1 -1
  62. package/dist/store/actions/node-actions.js +175 -0
  63. package/dist/store/history-control.d.ts +14 -0
  64. package/dist/store/history-control.d.ts.map +1 -0
  65. package/dist/store/history-control.js +22 -0
  66. package/dist/store/use-scene.d.ts.map +1 -1
  67. package/dist/store/use-scene.js +248 -2
  68. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  69. package/dist/systems/ceiling/ceiling-system.js +7 -0
  70. package/dist/systems/fence/fence-system.d.ts.map +1 -1
  71. package/dist/systems/fence/fence-system.js +106 -39
  72. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  73. package/dist/systems/roof/roof-system.js +31 -1
  74. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  75. package/dist/systems/slab/slab-system.js +45 -8
  76. package/dist/systems/stair/stair-opening-sync.d.ts +6 -0
  77. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -0
  78. package/dist/systems/stair/stair-opening-sync.js +515 -0
  79. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  80. package/dist/systems/stair/stair-system.js +119 -2
  81. package/dist/systems/wall/wall-curve.d.ts +43 -0
  82. package/dist/systems/wall/wall-curve.d.ts.map +1 -0
  83. package/dist/systems/wall/wall-curve.js +176 -0
  84. package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
  85. package/dist/systems/wall/wall-footprint.js +16 -2
  86. package/dist/systems/wall/wall-mitering.d.ts +7 -0
  87. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  88. package/dist/systems/wall/wall-mitering.js +76 -3
  89. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  90. package/dist/systems/wall/wall-system.js +202 -2
  91. package/package.json +3 -3
@@ -6,10 +6,202 @@ import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
6
6
  import { spatialGridManager } from '../../hooks/spatial-grid/spatial-grid-manager';
7
7
  import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync';
8
8
  import useScene from '../../store/use-scene';
9
+ import { getWallCurveFrameAt, getWallSurfacePolygon, isCurvedWall } from './wall-curve';
9
10
  import { DEFAULT_WALL_HEIGHT, getWallPlanFootprint, getWallThickness } from './wall-footprint';
10
- import { calculateLevelMiters, getAdjacentWallIds, } from './wall-mitering';
11
+ import { calculateLevelMiters, getAdjacentWallIds, getWallMiterBoundaryPoints, pointToKey, } from './wall-mitering';
11
12
  // Reusable CSG evaluator for better performance
12
13
  const csgEvaluator = new Evaluator();
14
+ const CURVED_WALL_3D_ENDPOINT_INSET = 0.0015;
15
+ const WALL_FACE_NORMAL_Y_EPSILON = 0.6;
16
+ const WALL_FACE_EDGE_DISTANCE_EPSILON = 0.003;
17
+ function ensureUv2Attribute(geometry) {
18
+ const uv = geometry.getAttribute('uv');
19
+ if (!uv)
20
+ return;
21
+ geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2));
22
+ }
23
+ function insetCurvedWallBoundaryPointsFor3D(wall, boundaryPoints, miterData) {
24
+ if (!boundaryPoints || !isCurvedWall(wall)) {
25
+ return boundaryPoints;
26
+ }
27
+ const insetDistance = Math.min(CURVED_WALL_3D_ENDPOINT_INSET, Math.max((wall.thickness ?? 0.1) * 0.01, 0.0005));
28
+ if (insetDistance <= 0) {
29
+ return boundaryPoints;
30
+ }
31
+ const next = { ...boundaryPoints };
32
+ const startJunction = miterData.junctions.get(pointToKey({ x: wall.start[0], y: wall.start[1] }));
33
+ const endJunction = miterData.junctions.get(pointToKey({ x: wall.end[0], y: wall.end[1] }));
34
+ if (startJunction && startJunction.connectedWalls.length > 1) {
35
+ const frame = getWallCurveFrameAt(wall, 0);
36
+ next.startLeft = {
37
+ x: next.startLeft.x + frame.tangent.x * insetDistance,
38
+ y: next.startLeft.y + frame.tangent.y * insetDistance,
39
+ };
40
+ next.startRight = {
41
+ x: next.startRight.x + frame.tangent.x * insetDistance,
42
+ y: next.startRight.y + frame.tangent.y * insetDistance,
43
+ };
44
+ }
45
+ if (endJunction && endJunction.connectedWalls.length > 1) {
46
+ const frame = getWallCurveFrameAt(wall, 1);
47
+ next.endLeft = {
48
+ x: next.endLeft.x - frame.tangent.x * insetDistance,
49
+ y: next.endLeft.y - frame.tangent.y * insetDistance,
50
+ };
51
+ next.endRight = {
52
+ x: next.endRight.x - frame.tangent.x * insetDistance,
53
+ y: next.endRight.y - frame.tangent.y * insetDistance,
54
+ };
55
+ }
56
+ return next;
57
+ }
58
+ function addTaggedWallBoundaryEdge(edges, points, startIndex, endIndex, tag) {
59
+ const start = points[startIndex];
60
+ const end = points[endIndex];
61
+ if (!(start && end))
62
+ return;
63
+ if (Math.hypot(end.x - start.x, end.z - start.z) < 1e-6)
64
+ return;
65
+ edges.push({
66
+ start: new THREE.Vector2(start.x, start.z),
67
+ end: new THREE.Vector2(end.x, end.z),
68
+ tag,
69
+ });
70
+ }
71
+ function buildTaggedWallBoundaryEdges(wall, localPoints, miterData) {
72
+ if (localPoints.length < 2)
73
+ return [];
74
+ const edges = [];
75
+ if (isCurvedWall(wall)) {
76
+ const sidePointCount = Math.floor(localPoints.length / 2);
77
+ if (sidePointCount < 2)
78
+ return edges;
79
+ for (let index = 0; index < sidePointCount - 1; index += 1) {
80
+ addTaggedWallBoundaryEdge(edges, localPoints, index, index + 1, 'back');
81
+ }
82
+ addTaggedWallBoundaryEdge(edges, localPoints, sidePointCount - 1, sidePointCount, 'base');
83
+ for (let index = sidePointCount; index < localPoints.length - 1; index += 1) {
84
+ addTaggedWallBoundaryEdge(edges, localPoints, index, index + 1, 'front');
85
+ }
86
+ addTaggedWallBoundaryEdge(edges, localPoints, localPoints.length - 1, 0, 'base');
87
+ return edges;
88
+ }
89
+ const startKey = pointToKey({ x: wall.start[0], y: wall.start[1] });
90
+ const startJunction = miterData.junctionData.get(startKey)?.get(wall.id);
91
+ const startLeftIndex = startJunction ? localPoints.length - 2 : localPoints.length - 1;
92
+ const endLeftIndex = startJunction ? localPoints.length - 3 : localPoints.length - 2;
93
+ addTaggedWallBoundaryEdge(edges, localPoints, 0, 1, 'back');
94
+ for (let index = 1; index < endLeftIndex; index += 1) {
95
+ addTaggedWallBoundaryEdge(edges, localPoints, index, index + 1, 'base');
96
+ }
97
+ addTaggedWallBoundaryEdge(edges, localPoints, endLeftIndex, startLeftIndex, 'front');
98
+ for (let index = startLeftIndex; index < localPoints.length - 1; index += 1) {
99
+ addTaggedWallBoundaryEdge(edges, localPoints, index, index + 1, 'base');
100
+ }
101
+ addTaggedWallBoundaryEdge(edges, localPoints, localPoints.length - 1, 0, 'base');
102
+ return edges;
103
+ }
104
+ function distanceToWallBoundaryEdge(point, edge) {
105
+ const edgeDx = edge.end.x - edge.start.x;
106
+ const edgeDz = edge.end.y - edge.start.y;
107
+ const pointDx = point.x - edge.start.x;
108
+ const pointDz = point.y - edge.start.y;
109
+ const edgeLengthSq = edgeDx * edgeDx + edgeDz * edgeDz;
110
+ if (edgeLengthSq < 1e-12) {
111
+ return point.distanceTo(edge.start);
112
+ }
113
+ const t = THREE.MathUtils.clamp((pointDx * edgeDx + pointDz * edgeDz) / edgeLengthSq, 0, 1);
114
+ const closestX = edge.start.x + edgeDx * t;
115
+ const closestZ = edge.start.y + edgeDz * t;
116
+ return Math.hypot(point.x - closestX, point.y - closestZ);
117
+ }
118
+ function getWallFaceMaterialIndex(wall, face) {
119
+ const semantic = face === 'front' ? wall.frontSide : wall.backSide;
120
+ const fallback = face === 'front' ? 1 : 2;
121
+ if (semantic === 'interior')
122
+ return 1;
123
+ if (semantic === 'exterior')
124
+ return 2;
125
+ return fallback;
126
+ }
127
+ function assignWallMaterialGroups(geometry, wall, boundaryEdges) {
128
+ const position = geometry.getAttribute('position');
129
+ if (!position)
130
+ return;
131
+ const index = geometry.getIndex();
132
+ const triangleCount = index ? Math.floor(index.count / 3) : Math.floor(position.count / 3);
133
+ if (triangleCount === 0) {
134
+ geometry.clearGroups();
135
+ return;
136
+ }
137
+ const triangleMaterials = new Array(triangleCount).fill(0);
138
+ const a = new THREE.Vector3();
139
+ const b = new THREE.Vector3();
140
+ const c = new THREE.Vector3();
141
+ const ab = new THREE.Vector3();
142
+ const ac = new THREE.Vector3();
143
+ const normal = new THREE.Vector3();
144
+ const centroid = new THREE.Vector3();
145
+ const projectedCentroid = new THREE.Vector2();
146
+ const maxBoundaryDistance = Math.max(getWallThickness(wall) * 0.02, WALL_FACE_EDGE_DISTANCE_EPSILON);
147
+ for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
148
+ const baseIndex = triangleIndex * 3;
149
+ const ia = index ? index.getX(baseIndex) : baseIndex;
150
+ const ib = index ? index.getX(baseIndex + 1) : baseIndex + 1;
151
+ const ic = index ? index.getX(baseIndex + 2) : baseIndex + 2;
152
+ a.fromBufferAttribute(position, ia);
153
+ b.fromBufferAttribute(position, ib);
154
+ c.fromBufferAttribute(position, ic);
155
+ ab.subVectors(b, a);
156
+ ac.subVectors(c, a);
157
+ normal.crossVectors(ab, ac);
158
+ if (normal.lengthSq() < 1e-12) {
159
+ triangleMaterials[triangleIndex] = 0;
160
+ continue;
161
+ }
162
+ normal.normalize();
163
+ if (Math.abs(normal.y) >= WALL_FACE_NORMAL_Y_EPSILON) {
164
+ triangleMaterials[triangleIndex] = 0;
165
+ continue;
166
+ }
167
+ centroid
168
+ .copy(a)
169
+ .add(b)
170
+ .add(c)
171
+ .multiplyScalar(1 / 3);
172
+ projectedCentroid.set(centroid.x, centroid.z);
173
+ let nearestTag = null;
174
+ let nearestDistance = Number.POSITIVE_INFINITY;
175
+ for (const edge of boundaryEdges) {
176
+ const distance = distanceToWallBoundaryEdge(projectedCentroid, edge);
177
+ if (distance < nearestDistance) {
178
+ nearestDistance = distance;
179
+ nearestTag = edge.tag;
180
+ }
181
+ }
182
+ if (!nearestTag || nearestDistance > maxBoundaryDistance) {
183
+ triangleMaterials[triangleIndex] = 0;
184
+ continue;
185
+ }
186
+ if (nearestTag === 'base') {
187
+ triangleMaterials[triangleIndex] = 0;
188
+ continue;
189
+ }
190
+ triangleMaterials[triangleIndex] = getWallFaceMaterialIndex(wall, nearestTag);
191
+ }
192
+ geometry.clearGroups();
193
+ let currentMaterial = triangleMaterials[0] ?? 0;
194
+ let groupStart = 0;
195
+ for (let triangleIndex = 1; triangleIndex < triangleCount; triangleIndex += 1) {
196
+ const materialIndex = triangleMaterials[triangleIndex] ?? 0;
197
+ if (materialIndex === currentMaterial)
198
+ continue;
199
+ geometry.addGroup(groupStart * 3, (triangleIndex - groupStart) * 3, currentMaterial);
200
+ groupStart = triangleIndex;
201
+ currentMaterial = materialIndex;
202
+ }
203
+ geometry.addGroup(groupStart * 3, (triangleCount - groupStart) * 3, currentMaterial);
204
+ }
13
205
  // ============================================================================
14
206
  // WALL SYSTEM
15
207
  // ============================================================================
@@ -131,7 +323,10 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
131
323
  if (L < 1e-9) {
132
324
  return new THREE.BufferGeometry();
133
325
  }
134
- const polyPoints = getWallPlanFootprint(wallNode, miterData);
326
+ const boundaryPoints = getWallMiterBoundaryPoints(wallNode, miterData);
327
+ const polyPoints = isCurvedWall(wallNode)
328
+ ? getWallSurfacePolygon(wallNode, 24, insetCurvedWallBoundaryPointsFor3D(wallNode, boundaryPoints, miterData) ?? undefined)
329
+ : getWallPlanFootprint(wallNode, miterData);
135
330
  if (polyPoints.length < 3) {
136
331
  return new THREE.BufferGeometry();
137
332
  }
@@ -150,6 +345,7 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
150
345
  };
151
346
  // Convert polygon to local coordinates
152
347
  const localPoints = polyPoints.map(worldToLocal);
348
+ const boundaryEdges = buildTaggedWallBoundaryEdges(wallNode, localPoints, miterData);
153
349
  // Build THREE.js shape
154
350
  // Shape uses (x, y) where we map: shape.x = local.x, shape.y = -local.z
155
351
  // The negation is needed because after rotateX(-PI/2), shape.y becomes -geometry.z
@@ -167,6 +363,8 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
167
363
  // Rotate so extrusion direction (Z) becomes height direction (Y)
168
364
  geometry.rotateX(-Math.PI / 2);
169
365
  geometry.computeVertexNormals();
366
+ assignWallMaterialGroups(geometry, wallNode, boundaryEdges);
367
+ ensureUv2Attribute(geometry);
170
368
  // Apply CSG subtraction for cutouts (doors/windows)
171
369
  const cutoutBrushes = collectCutoutBrushes(wallNode, childrenNodes, thickness);
172
370
  if (cutoutBrushes.length === 0) {
@@ -195,6 +393,8 @@ export function generateExtrudedWall(wallNode, childrenNodes, miterData, slabEle
195
393
  }
196
394
  const resultGeometry = resultBrush.geometry;
197
395
  resultGeometry.computeVertexNormals();
396
+ assignWallMaterialGroups(resultGeometry, wallNode, boundaryEdges);
397
+ ensureUv2Attribute(resultGeometry);
198
398
  return resultGeometry;
199
399
  }
200
400
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/core",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Core library for Pascal 3D building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,7 +30,7 @@
30
30
  "@react-three/drei": "^10",
31
31
  "@react-three/fiber": "^9",
32
32
  "react": "^18 || ^19",
33
- "three": "^0.182"
33
+ "three": "^0.184"
34
34
  },
35
35
  "dependencies": {
36
36
  "dedent": "^1.7.1",
@@ -47,7 +47,7 @@
47
47
  "@pascal/typescript-config": "*",
48
48
  "@types/react": "^19.2.2",
49
49
  "typescript": "5.9.3",
50
- "@types/three": "^0.183.0"
50
+ "@types/three": "^0.184.0"
51
51
  },
52
52
  "keywords": [
53
53
  "3d",