@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.
- package/dist/events/bus.d.ts +39 -4
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +1 -1
- package/dist/hooks/scene-registry/scene-registry.d.ts +1 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +1 -0
- package/dist/hooks/spatial-grid/spatial-grid.d.ts +2 -0
- package/dist/hooks/spatial-grid/spatial-grid.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid.js +43 -20
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/lib/polygon-geometry.d.ts +3 -0
- package/dist/lib/polygon-geometry.d.ts.map +1 -0
- package/dist/lib/polygon-geometry.js +90 -0
- package/dist/lib/space-detection.d.ts +10 -17
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +666 -453
- package/dist/material-library.d.ts +18 -0
- package/dist/material-library.d.ts.map +1 -0
- package/dist/material-library.js +603 -0
- package/dist/schema/index.d.ts +10 -4
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +6 -4
- package/dist/schema/material.d.ts +109 -0
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +52 -0
- package/dist/schema/nodes/ceiling.d.ts +10 -0
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +6 -0
- package/dist/schema/nodes/door.d.ts +1 -0
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/fence.d.ts +85 -0
- package/dist/schema/nodes/fence.d.ts.map +1 -0
- package/dist/schema/nodes/fence.js +34 -0
- package/dist/schema/nodes/item.d.ts +2 -2
- package/dist/schema/nodes/level.d.ts +1 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +2 -0
- package/dist/schema/nodes/roof-segment.d.ts +2 -0
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +1 -0
- package/dist/schema/nodes/roof.d.ts +108 -0
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +58 -2
- package/dist/schema/nodes/site.d.ts +1 -1
- package/dist/schema/nodes/slab.d.ts +10 -0
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +7 -0
- package/dist/schema/nodes/stair-segment.d.ts +2 -0
- package/dist/schema/nodes/stair-segment.d.ts.map +1 -1
- package/dist/schema/nodes/stair-segment.js +1 -0
- package/dist/schema/nodes/stair.d.ts +164 -0
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +106 -5
- package/dist/schema/nodes/surface-hole-metadata.d.ts +10 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -0
- package/dist/schema/nodes/surface-hole-metadata.js +5 -0
- package/dist/schema/nodes/wall.d.ts +87 -1
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +45 -4
- package/dist/schema/nodes/window.d.ts +1 -0
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/types.d.ts +406 -4
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +2 -0
- package/dist/store/actions/node-actions.d.ts +1 -1
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +175 -0
- package/dist/store/history-control.d.ts +14 -0
- package/dist/store/history-control.d.ts.map +1 -0
- package/dist/store/history-control.js +22 -0
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +249 -3
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +7 -0
- package/dist/systems/fence/fence-system.d.ts +2 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -0
- package/dist/systems/fence/fence-system.js +187 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +31 -1
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +45 -8
- package/dist/systems/stair/stair-opening-sync.d.ts +6 -0
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-sync.js +515 -0
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +432 -10
- package/dist/systems/wall/wall-curve.d.ts +43 -0
- package/dist/systems/wall/wall-curve.d.ts.map +1 -0
- package/dist/systems/wall/wall-curve.js +176 -0
- package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
- package/dist/systems/wall/wall-footprint.js +16 -2
- package/dist/systems/wall/wall-mitering.d.ts +7 -0
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +76 -3
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +202 -2
- 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;
|
|
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;
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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); // v1 — floor 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"}
|