@pascal-app/core 0.5.1 → 0.7.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 +74 -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 +2 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +2 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +164 -6
- 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 +9 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -11
- package/dist/lib/door-operation.d.ts +7 -0
- package/dist/lib/door-operation.d.ts.map +1 -0
- package/dist/lib/door-operation.js +25 -0
- 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/slab-polygon.d.ts +3 -0
- package/dist/lib/slab-polygon.d.ts.map +1 -0
- package/dist/lib/slab-polygon.js +58 -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 +20 -0
- package/dist/material-library.d.ts.map +1 -0
- package/dist/material-library.js +580 -0
- package/dist/schema/asset-url.d.ts +34 -0
- package/dist/schema/asset-url.d.ts.map +1 -0
- package/dist/schema/asset-url.js +79 -0
- package/dist/schema/asset-url.test.d.ts +2 -0
- package/dist/schema/asset-url.test.d.ts.map +1 -0
- package/dist/schema/asset-url.test.js +138 -0
- package/dist/schema/index.d.ts +14 -7
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +10 -7
- package/dist/schema/material.d.ts +112 -2
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +55 -1
- package/dist/schema/nodes/ceiling.d.ts +11 -1
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +6 -0
- package/dist/schema/nodes/column.d.ts +520 -0
- package/dist/schema/nodes/column.d.ts.map +1 -0
- package/dist/schema/nodes/column.js +385 -0
- package/dist/schema/nodes/door.d.ts +74 -1
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +39 -2
- package/dist/schema/nodes/fence.d.ts +34 -0
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +5 -0
- package/dist/schema/nodes/guide.d.ts +17 -0
- package/dist/schema/nodes/guide.d.ts.map +1 -1
- package/dist/schema/nodes/guide.js +11 -1
- package/dist/schema/nodes/item.d.ts +10 -2
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +18 -1
- 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 +6 -0
- package/dist/schema/nodes/roof-segment.d.ts +3 -1
- 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/scan.d.ts.map +1 -1
- package/dist/schema/nodes/scan.js +2 -1
- package/dist/schema/nodes/site.d.ts +2 -1
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/slab.d.ts +11 -1
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +7 -0
- package/dist/schema/nodes/spawn.d.ts +24 -0
- package/dist/schema/nodes/spawn.d.ts.map +1 -0
- package/dist/schema/nodes/spawn.js +8 -0
- package/dist/schema/nodes/stair-segment.d.ts +3 -1
- 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 +122 -2
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +72 -2
- 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 +57 -1
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +29 -0
- package/dist/schema/types.d.ts +653 -12
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +4 -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 +181 -5
- 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-interactive.d.ts +43 -0
- package/dist/store/use-interactive.d.ts.map +1 -1
- package/dist/store/use-interactive.js +66 -0
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +307 -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.map +1 -1
- package/dist/systems/fence/fence-system.js +106 -39
- 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 +576 -0
- package/dist/systems/stair/stair-opening-sync.test.d.ts +2 -0
- package/dist/systems/stair/stair-opening-sync.test.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-sync.test.js +65 -0
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +119 -2
- 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 +33 -5
package/dist/store/use-scene.js
CHANGED
|
@@ -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';
|
|
8
10
|
import * as nodeActions from './actions/node-actions';
|
|
11
|
+
import { resetSceneHistoryPauseDepth } from './history-control';
|
|
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,9 +269,75 @@ 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
|
}
|
|
293
|
+
function getNodeChildIds(node) {
|
|
294
|
+
if (!('children' in node) || !Array.isArray(node.children)) {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
return node.children
|
|
298
|
+
.map((child) => {
|
|
299
|
+
if (typeof child === 'string')
|
|
300
|
+
return child;
|
|
301
|
+
if (child && typeof child === 'object' && 'id' in child && typeof child.id === 'string') {
|
|
302
|
+
return child.id;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
})
|
|
306
|
+
.filter((id) => typeof id === 'string');
|
|
307
|
+
}
|
|
308
|
+
function normalizeRootNodeIds(nodes, rootNodeIds) {
|
|
309
|
+
const existingRootIds = rootNodeIds.filter((id) => Boolean(nodes[id]));
|
|
310
|
+
const siteRootIds = existingRootIds.filter((id) => nodes[id]?.type === 'site');
|
|
311
|
+
if (siteRootIds.length > 0) {
|
|
312
|
+
return siteRootIds;
|
|
313
|
+
}
|
|
314
|
+
return existingRootIds.filter((id) => nodes[id]?.parentId === null);
|
|
315
|
+
}
|
|
316
|
+
function collectReachableNodeIds(nodes, rootNodeIds) {
|
|
317
|
+
const reachable = new Set();
|
|
318
|
+
const stack = [...rootNodeIds];
|
|
319
|
+
const childIdsByParentId = new Map();
|
|
320
|
+
for (const node of Object.values(nodes)) {
|
|
321
|
+
if (!node.parentId)
|
|
322
|
+
continue;
|
|
323
|
+
const parentId = node.parentId;
|
|
324
|
+
const children = childIdsByParentId.get(parentId) ?? [];
|
|
325
|
+
children.push(node.id);
|
|
326
|
+
childIdsByParentId.set(parentId, children);
|
|
327
|
+
}
|
|
328
|
+
while (stack.length > 0) {
|
|
329
|
+
const id = stack.pop();
|
|
330
|
+
if (!id || reachable.has(id))
|
|
331
|
+
continue;
|
|
332
|
+
const node = nodes[id];
|
|
333
|
+
if (!node)
|
|
334
|
+
continue;
|
|
335
|
+
reachable.add(id);
|
|
336
|
+
stack.push(...getNodeChildIds(node));
|
|
337
|
+
stack.push(...(childIdsByParentId.get(id) ?? []));
|
|
338
|
+
}
|
|
339
|
+
return reachable;
|
|
340
|
+
}
|
|
49
341
|
const useScene = create()(temporal((set, get) => ({
|
|
50
342
|
// 1. Flat dictionary of all nodes
|
|
51
343
|
nodes: {},
|
|
@@ -81,9 +373,19 @@ const useScene = create()(temporal((set, get) => ({
|
|
|
81
373
|
delete cleanedNodes[node.id];
|
|
82
374
|
}
|
|
83
375
|
}
|
|
376
|
+
const normalizedRootNodeIds = normalizeRootNodeIds(cleanedNodes, rootNodeIds);
|
|
377
|
+
const reachableNodeIds = collectReachableNodeIds(cleanedNodes, normalizedRootNodeIds);
|
|
378
|
+
if (normalizedRootNodeIds.length > 0) {
|
|
379
|
+
for (const node of Object.values(cleanedNodes)) {
|
|
380
|
+
if (reachableNodeIds.has(node.id))
|
|
381
|
+
continue;
|
|
382
|
+
console.warn('[Scene] Removing unreachable node', node.id);
|
|
383
|
+
delete cleanedNodes[node.id];
|
|
384
|
+
}
|
|
385
|
+
}
|
|
84
386
|
set({
|
|
85
387
|
nodes: cleanedNodes,
|
|
86
|
-
rootNodeIds,
|
|
388
|
+
rootNodeIds: normalizedRootNodeIds,
|
|
87
389
|
dirtyNodes: new Set(),
|
|
88
390
|
collections: {},
|
|
89
391
|
});
|
|
@@ -246,6 +548,7 @@ let prevFutureLength = 0;
|
|
|
246
548
|
let prevNodesSnapshot = null;
|
|
247
549
|
export function clearSceneHistory() {
|
|
248
550
|
useScene.temporal.getState().clear();
|
|
551
|
+
resetSceneHistoryPauseDepth();
|
|
249
552
|
prevPastLength = 0;
|
|
250
553
|
prevFutureLength = 0;
|
|
251
554
|
prevNodesSnapshot = null;
|
|
@@ -261,8 +564,9 @@ useScene.temporal.subscribe((state) => {
|
|
|
261
564
|
if (didUndo || didRedo) {
|
|
262
565
|
// Capture the previous snapshot before RAF fires
|
|
263
566
|
const snapshotBefore = prevNodesSnapshot;
|
|
264
|
-
//
|
|
265
|
-
|
|
567
|
+
// Defer to a microtask so the scene store has settled before we diff,
|
|
568
|
+
// but still mark walls/items dirty before the next paint.
|
|
569
|
+
queueMicrotask(() => {
|
|
266
570
|
const currentNodes = useScene.getState().nodes;
|
|
267
571
|
const { markDirty } = useScene.getState();
|
|
268
572
|
if (snapshotBefore) {
|
|
@@ -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;
|
|
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
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"AAyQA,eAAO,MAAM,WAAW,YAiBvB,CAAA"}
|
|
@@ -3,6 +3,94 @@ import * as THREE from 'three';
|
|
|
3
3
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
4
4
|
import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
|
|
5
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
|
+
}
|
|
6
94
|
function getStyleDefaults(style) {
|
|
7
95
|
if (style === 'privacy') {
|
|
8
96
|
return { spacingFactor: 0.42, postFactor: 1.35, baseFactor: 1.2, topFactor: 1.2 };
|
|
@@ -14,7 +102,7 @@ function getStyleDefaults(style) {
|
|
|
14
102
|
}
|
|
15
103
|
function createFenceParts(fence) {
|
|
16
104
|
const parts = [];
|
|
17
|
-
const length = Math.max(
|
|
105
|
+
const length = Math.max(getWallCurveLength(fence), 0.01);
|
|
18
106
|
const panelDepth = Math.max(fence.thickness, 0.03);
|
|
19
107
|
const clearance = Math.max(fence.groundClearance, 0);
|
|
20
108
|
const styleDefaults = getStyleDefaults(fence.style);
|
|
@@ -27,61 +115,43 @@ function createFenceParts(fence) {
|
|
|
27
115
|
const isFloating = fence.baseStyle === 'floating';
|
|
28
116
|
const baseY = isFloating ? clearance : 0;
|
|
29
117
|
const effectiveBaseHeight = baseHeight;
|
|
118
|
+
const startInsetT = Math.min(0.499, edgeInset / length);
|
|
119
|
+
const endInsetT = Math.max(0.501, 1 - edgeInset / length);
|
|
30
120
|
if (!isFloating) {
|
|
31
|
-
parts.push(
|
|
32
|
-
|
|
33
|
-
scale: [length, effectiveBaseHeight, panelDepth * 1.05],
|
|
34
|
-
});
|
|
35
|
-
parts.push({
|
|
36
|
-
position: [0, baseY + effectiveBaseHeight + verticalHeight * 0.15, 0],
|
|
37
|
-
scale: [length, topRailHeight * 0.8, panelDepth * 0.35],
|
|
38
|
-
});
|
|
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));
|
|
39
123
|
}
|
|
40
124
|
const count = Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1);
|
|
41
|
-
const step = count > 1 ? (length - edgeInset * 2) / (count - 1) : 0;
|
|
42
|
-
const startX = -length / 2 + edgeInset;
|
|
43
125
|
const verticalY = baseY + effectiveBaseHeight + verticalHeight / 2;
|
|
44
126
|
for (let index = 0; index < count; index += 1) {
|
|
45
|
-
const
|
|
46
|
-
|
|
127
|
+
const t = count === 1 ? 0.5 : startInsetT + (endInsetT - startInsetT) * (index / (count - 1));
|
|
128
|
+
const frame = getFencePointAt(fence, t);
|
|
47
129
|
const isEdgePost = index === 0 || index === count - 1;
|
|
48
|
-
if (count > 1) {
|
|
49
|
-
if (index === 0)
|
|
50
|
-
posX = -length / 2 + edgeInset + postWidth / 2;
|
|
51
|
-
else if (index === count - 1)
|
|
52
|
-
posX = length / 2 - edgeInset - postWidth / 2;
|
|
53
|
-
}
|
|
54
130
|
const postHeight = isFloating && isEdgePost
|
|
55
131
|
? effectiveBaseHeight + verticalHeight + topRailHeight + clearance
|
|
56
132
|
: verticalHeight;
|
|
57
133
|
const postY = isFloating && isEdgePost ? postHeight / 2 : verticalY;
|
|
58
134
|
parts.push({
|
|
59
|
-
position: [
|
|
135
|
+
position: [frame.point.x, postY, frame.point.y],
|
|
136
|
+
rotationY: -frame.tangentAngle,
|
|
60
137
|
scale: [postWidth, postHeight, Math.max(panelDepth * 0.35, 0.012)],
|
|
61
138
|
});
|
|
62
139
|
}
|
|
63
|
-
parts.push(
|
|
64
|
-
position: [0, baseY + effectiveBaseHeight + verticalHeight + topRailHeight / 2, 0],
|
|
65
|
-
scale: [length, topRailHeight, Math.max(panelDepth * 0.55, 0.018)],
|
|
66
|
-
});
|
|
140
|
+
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
|
|
67
141
|
if (isFloating) {
|
|
68
|
-
parts.push(
|
|
69
|
-
position: [0, baseY + effectiveBaseHeight + topRailHeight / 2, 0],
|
|
70
|
-
scale: [length, topRailHeight, Math.max(panelDepth * 0.55, 0.018)],
|
|
71
|
-
});
|
|
142
|
+
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
|
|
72
143
|
}
|
|
73
144
|
return parts;
|
|
74
145
|
}
|
|
75
146
|
function generateFenceGeometry(fence) {
|
|
76
147
|
const parts = createFenceParts(fence);
|
|
77
|
-
const geometries = parts.map(
|
|
78
|
-
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
79
|
-
geometry.scale(part.scale[0], part.scale[1], part.scale[2]);
|
|
80
|
-
geometry.translate(part.position[0], part.position[1], part.position[2]);
|
|
81
|
-
return geometry;
|
|
82
|
-
});
|
|
148
|
+
const geometries = parts.map(createFencePartGeometry);
|
|
83
149
|
const merged = mergeGeometries(geometries, false) ?? new THREE.BufferGeometry();
|
|
84
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
|
+
}
|
|
85
155
|
merged.computeVertexNormals();
|
|
86
156
|
return merged;
|
|
87
157
|
}
|
|
@@ -95,11 +165,8 @@ function updateFenceGeometry(fenceId) {
|
|
|
95
165
|
const newGeometry = generateFenceGeometry(node);
|
|
96
166
|
mesh.geometry.dispose();
|
|
97
167
|
mesh.geometry = newGeometry;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const angle = Math.atan2(node.end[1] - node.start[1], node.end[0] - node.start[0]);
|
|
101
|
-
mesh.position.set(centerX, 0, centerZ);
|
|
102
|
-
mesh.rotation.set(0, -angle, 0);
|
|
168
|
+
mesh.position.set(0, 0, 0);
|
|
169
|
+
mesh.rotation.set(0, 0, 0);
|
|
103
170
|
}
|
|
104
171
|
export const FenceSystem = () => {
|
|
105
172
|
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
@@ -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"}
|