@inweb/viewer-three 27.2.1 → 27.2.3
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/viewer-three.js +704 -339
- package/dist/viewer-three.js.map +1 -1
- package/dist/viewer-three.min.js +3 -3
- package/dist/viewer-three.module.js +703 -338
- package/dist/viewer-three.module.js.map +1 -1
- package/lib/Viewer/controls/WalkControls.d.ts +8 -0
- package/lib/Viewer/draggers/CuttingPlaneDragger.d.ts +30 -8
- package/lib/Viewer/helpers/PlaneHelper2.d.ts +12 -0
- package/lib/Viewer/measurement/Snapper.d.ts +1 -1
- package/package.json +5 -5
- package/src/Viewer/Viewer.ts +4 -0
- package/src/Viewer/components/SelectionComponent.ts +1 -1
- package/src/Viewer/controls/WalkControls.ts +50 -4
- package/src/Viewer/draggers/CuttingPlaneDragger.ts +243 -42
- package/src/Viewer/draggers/CuttingPlaneXAxis.ts +2 -3
- package/src/Viewer/draggers/CuttingPlaneYAxis.ts +2 -3
- package/src/Viewer/draggers/CuttingPlaneZAxis.ts +2 -3
- package/src/Viewer/draggers/index.ts +2 -0
- package/src/Viewer/helpers/{PlaneHelper.ts → PlaneHelper2.ts} +27 -31
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +413 -189
- package/src/Viewer/measurement/Snapper.ts +13 -5
- package/lib/Viewer/helpers/PlaneHelper.d.ts +0 -11
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
NormalBlending,
|
|
24
24
|
BufferAttribute,
|
|
25
25
|
LineBasicMaterial,
|
|
26
|
+
DataTexture,
|
|
27
|
+
RGBAFormat,
|
|
28
|
+
FloatType,
|
|
29
|
+
NearestFilter,
|
|
26
30
|
} from "three";
|
|
27
31
|
import { GL_CONSTANTS } from "./GltfStructure.js";
|
|
28
32
|
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
|
@@ -61,10 +65,6 @@ export class DynamicGltfLoader {
|
|
|
61
65
|
|
|
62
66
|
this.memoryLimit = this.getAvailableMemory();
|
|
63
67
|
this.optimizationMemoryMultiplier = 5;
|
|
64
|
-
/**
|
|
65
|
-
* Real memory ~1.7x raw geometry (Three.js objects, Map/Set, buffers overhead). Used for limit
|
|
66
|
-
* checks and display.
|
|
67
|
-
*/
|
|
68
68
|
this.memoryEstimationFactor = 1.7;
|
|
69
69
|
this.loadedGeometrySize = 0;
|
|
70
70
|
this.geometryCache = new Map();
|
|
@@ -126,6 +126,123 @@ export class DynamicGltfLoader {
|
|
|
126
126
|
this.mergedGeometryVisibility = new Map(); // mergedObject -> visibility array
|
|
127
127
|
|
|
128
128
|
this._webglInfoCache = null;
|
|
129
|
+
|
|
130
|
+
// Transform texture support
|
|
131
|
+
this.transformTextureSize = 1024;
|
|
132
|
+
this.transformTexture = this.createDummyTexture();
|
|
133
|
+
this.transformData = null;
|
|
134
|
+
this.identityTransformData = null;
|
|
135
|
+
this.visibilityMaterials = new Set(); // Keep track of materials to update uniforms
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// layout (1 matrix = 4 pixels)
|
|
139
|
+
// RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
|
|
140
|
+
// with 8x8 pixel texture max 16 matrices * 4 pixels = (8 * 8)
|
|
141
|
+
// 16x16 pixel texture max 64 matrices * 4 pixels = (16 * 16)
|
|
142
|
+
// 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32)
|
|
143
|
+
// 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64)
|
|
144
|
+
|
|
145
|
+
createDummyTexture() {
|
|
146
|
+
// Create 1x1 dummy texture with identity matrix to prevent shader crash/black screen
|
|
147
|
+
const data = new Float32Array(16); // 4x4 matrix
|
|
148
|
+
const identity = new Matrix4();
|
|
149
|
+
identity.toArray(data);
|
|
150
|
+
|
|
151
|
+
// Correct dummy size: 4x1 to hold at least one matrix
|
|
152
|
+
const dummyData = new Float32Array(16);
|
|
153
|
+
identity.toArray(dummyData);
|
|
154
|
+
const dummyTexture = new DataTexture(dummyData, 4, 1, RGBAFormat, FloatType);
|
|
155
|
+
|
|
156
|
+
dummyTexture.minFilter = NearestFilter;
|
|
157
|
+
dummyTexture.magFilter = NearestFilter;
|
|
158
|
+
dummyTexture.needsUpdate = true;
|
|
159
|
+
return dummyTexture;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
initTransformTexture() {
|
|
163
|
+
if (this.transformTexture) {
|
|
164
|
+
this.transformTexture.dispose();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Logic from BatchedMesh.js _initMatricesTexture
|
|
168
|
+
// layout (1 matrix = 4 pixels)
|
|
169
|
+
// RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
|
|
170
|
+
const maxInstanceCount = this.maxObjectId + 1;
|
|
171
|
+
let size = Math.sqrt(maxInstanceCount * 4); // 4 pixels needed for 1 matrix
|
|
172
|
+
size = Math.ceil(size / 4) * 4;
|
|
173
|
+
size = Math.max(size, 4);
|
|
174
|
+
|
|
175
|
+
this.transformTextureSize = size;
|
|
176
|
+
const arraySize = size * size * 4; // 4 floats per RGBA pixel
|
|
177
|
+
this.transformData = new Float32Array(arraySize);
|
|
178
|
+
|
|
179
|
+
// Create and cache identity matrices for fast reset
|
|
180
|
+
this.identityTransformData = new Float32Array(arraySize);
|
|
181
|
+
for (let i = 0; i <= this.maxObjectId; i++) {
|
|
182
|
+
const base = i * 16;
|
|
183
|
+
if (base + 15 < arraySize) {
|
|
184
|
+
this.identityTransformData[base + 0] = 1; // m00
|
|
185
|
+
this.identityTransformData[base + 5] = 1; // m11
|
|
186
|
+
this.identityTransformData[base + 10] = 1; // m22
|
|
187
|
+
this.identityTransformData[base + 15] = 1; // m33
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Initialize with identity matrices
|
|
192
|
+
this._resetTransformData(false);
|
|
193
|
+
|
|
194
|
+
this.transformTexture = new DataTexture(this.transformData, size, size, RGBAFormat, FloatType);
|
|
195
|
+
|
|
196
|
+
this.transformTexture.needsUpdate = true;
|
|
197
|
+
this.transformTexture.generateMipmaps = false;
|
|
198
|
+
|
|
199
|
+
console.log(`Initialized transform texture: ${size}x${size} for ${maxInstanceCount} objects`);
|
|
200
|
+
|
|
201
|
+
this.updateMaterialUniforms();
|
|
202
|
+
|
|
203
|
+
// Force all visibility materials to update
|
|
204
|
+
this.visibilityMaterials.forEach((material) => {
|
|
205
|
+
material.needsUpdate = true;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Fast reset to Identity matrices without creating Matrix4 objects
|
|
210
|
+
_resetTransformData(updateTexture = true) {
|
|
211
|
+
if (!this.transformData || !this.identityTransformData) return;
|
|
212
|
+
|
|
213
|
+
// Fast copy from cached identity array
|
|
214
|
+
this.transformData.set(this.identityTransformData);
|
|
215
|
+
|
|
216
|
+
if (updateTexture) {
|
|
217
|
+
this.updateTransformTexture();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
updateMaterialUniforms() {
|
|
222
|
+
// Only update if the texture actually changed
|
|
223
|
+
// In three.js, setting `.value = this.transformTexture` triggers uniformity checks
|
|
224
|
+
// We can avoid looping over all materials if the texture reference hasn't changed
|
|
225
|
+
if (
|
|
226
|
+
this._lastTransformTexture === this.transformTexture &&
|
|
227
|
+
this._lastTransformTextureSize === this.transformTextureSize
|
|
228
|
+
) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this._lastTransformTexture = this.transformTexture;
|
|
233
|
+
this._lastTransformTextureSize = this.transformTextureSize;
|
|
234
|
+
|
|
235
|
+
this.visibilityMaterials.forEach((material) => {
|
|
236
|
+
if (material.userData && material.userData.visibilityUniforms) {
|
|
237
|
+
material.userData.visibilityUniforms.transformTexture.value = this.transformTexture;
|
|
238
|
+
material.userData.visibilityUniforms.transformTextureSize.value = this.transformTextureSize;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
updateTransformTexture() {
|
|
244
|
+
if (!this.transformTexture) return;
|
|
245
|
+
this.transformTexture.needsUpdate = true;
|
|
129
246
|
}
|
|
130
247
|
|
|
131
248
|
setVisibleEdges(visible) {
|
|
@@ -1169,42 +1286,100 @@ export class DynamicGltfLoader {
|
|
|
1169
1286
|
}
|
|
1170
1287
|
|
|
1171
1288
|
createVisibilityMaterial(material) {
|
|
1289
|
+
this.visibilityMaterials.add(material);
|
|
1290
|
+
|
|
1291
|
+
const uniforms = {
|
|
1292
|
+
transformTexture: { value: this.transformTexture },
|
|
1293
|
+
transformTextureSize: { value: this.transformTextureSize },
|
|
1294
|
+
};
|
|
1295
|
+
material.userData.visibilityUniforms = uniforms;
|
|
1296
|
+
|
|
1172
1297
|
material.onBeforeCompile = (shader) => {
|
|
1298
|
+
shader.uniforms.transformTexture = uniforms.transformTexture;
|
|
1299
|
+
shader.uniforms.transformTextureSize = uniforms.transformTextureSize;
|
|
1300
|
+
|
|
1301
|
+
// 1. Common Definitions
|
|
1173
1302
|
shader.vertexShader = shader.vertexShader.replace(
|
|
1174
1303
|
"#include <common>",
|
|
1175
1304
|
`
|
|
1176
1305
|
#include <common>
|
|
1306
|
+
|
|
1177
1307
|
attribute float visibility;
|
|
1308
|
+
attribute float objectId;
|
|
1178
1309
|
varying float vVisibility;
|
|
1310
|
+
uniform highp sampler2D transformTexture;
|
|
1311
|
+
uniform float transformTextureSize;
|
|
1312
|
+
|
|
1313
|
+
mat4 getTransformMatrix(float instanceId) {
|
|
1314
|
+
int size = int(transformTextureSize);
|
|
1315
|
+
int index = int(instanceId) * 4;
|
|
1316
|
+
|
|
1317
|
+
int x0 = index % size;
|
|
1318
|
+
int y0 = index / size;
|
|
1319
|
+
|
|
1320
|
+
vec4 row0 = texelFetch(transformTexture, ivec2(x0, y0), 0);
|
|
1321
|
+
vec4 row1 = texelFetch(transformTexture, ivec2(x0 + 1, y0), 0);
|
|
1322
|
+
vec4 row2 = texelFetch(transformTexture, ivec2(x0 + 2, y0), 0);
|
|
1323
|
+
vec4 row3 = texelFetch(transformTexture, ivec2(x0 + 3, y0), 0);
|
|
1324
|
+
|
|
1325
|
+
return mat4(row0, row1, row2, row3);
|
|
1326
|
+
}
|
|
1179
1327
|
`
|
|
1180
1328
|
);
|
|
1181
1329
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1330
|
+
// 2. Inject matrix retrieval at start of main()
|
|
1331
|
+
shader.vertexShader = shader.vertexShader.replace(
|
|
1332
|
+
"void main() {",
|
|
1184
1333
|
`
|
|
1185
|
-
|
|
1186
|
-
|
|
1334
|
+
void main() {
|
|
1335
|
+
mat4 batchingMatrix = getTransformMatrix(objectId);
|
|
1336
|
+
vVisibility = visibility;
|
|
1187
1337
|
`
|
|
1188
1338
|
);
|
|
1189
1339
|
|
|
1340
|
+
// 3. Transform Normal
|
|
1341
|
+
if (shader.vertexShader.includes("#include <beginnormal_vertex>")) {
|
|
1342
|
+
shader.vertexShader = shader.vertexShader.replace(
|
|
1343
|
+
"#include <beginnormal_vertex>",
|
|
1344
|
+
`
|
|
1345
|
+
vec3 objectNormal = vec3( normal );
|
|
1346
|
+
mat3 bm = mat3( batchingMatrix );
|
|
1347
|
+
objectNormal = bm * objectNormal;
|
|
1348
|
+
`
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// 4. Transform Position
|
|
1190
1353
|
shader.vertexShader = shader.vertexShader.replace(
|
|
1191
|
-
"
|
|
1354
|
+
"#include <begin_vertex>",
|
|
1192
1355
|
`
|
|
1193
|
-
|
|
1194
|
-
|
|
1356
|
+
vec3 transformed = vec3( position );
|
|
1357
|
+
transformed = ( batchingMatrix * vec4( transformed, 1.0 ) ).xyz;
|
|
1195
1358
|
`
|
|
1196
1359
|
);
|
|
1197
1360
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1361
|
+
// 5. Fragment Shader
|
|
1362
|
+
shader.fragmentShader = shader.fragmentShader
|
|
1363
|
+
.replace(
|
|
1364
|
+
"#include <common>",
|
|
1365
|
+
`
|
|
1366
|
+
#include <common>
|
|
1367
|
+
varying float vVisibility;
|
|
1200
1368
|
`
|
|
1369
|
+
)
|
|
1370
|
+
.replace(
|
|
1371
|
+
"void main() {",
|
|
1372
|
+
`
|
|
1201
1373
|
void main() {
|
|
1202
1374
|
if (vVisibility < 0.5) discard;
|
|
1203
1375
|
`
|
|
1204
|
-
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
//console.log("!vertex", shader.vertexShader);
|
|
1205
1379
|
};
|
|
1206
|
-
material.needsUpdate = true;
|
|
1207
1380
|
|
|
1381
|
+
// Ensure the material recompiles to pick up changes
|
|
1382
|
+
material.needsUpdate = true;
|
|
1208
1383
|
return material;
|
|
1209
1384
|
}
|
|
1210
1385
|
|
|
@@ -1332,6 +1507,8 @@ export class DynamicGltfLoader {
|
|
|
1332
1507
|
this.objectIdToIndex.clear();
|
|
1333
1508
|
this.maxObjectId = 0;
|
|
1334
1509
|
this.objectVisibility = new Float32Array();
|
|
1510
|
+
this.meshToNodeMap = null;
|
|
1511
|
+
this.visibilityMaterials.clear();
|
|
1335
1512
|
}
|
|
1336
1513
|
|
|
1337
1514
|
setStructureTransform(structureId, matrix) {
|
|
@@ -1466,6 +1643,10 @@ export class DynamicGltfLoader {
|
|
|
1466
1643
|
|
|
1467
1644
|
this.originalObjects.clear();
|
|
1468
1645
|
this.originalObjectsToSelection.clear();
|
|
1646
|
+
// Clear previous optimization data
|
|
1647
|
+
this.objectIdToIndex.clear();
|
|
1648
|
+
this.maxObjectId = 0;
|
|
1649
|
+
|
|
1469
1650
|
const structureGroups = new Map();
|
|
1470
1651
|
|
|
1471
1652
|
this.dispatchEvent("optimizationprogress", {
|
|
@@ -1474,6 +1655,8 @@ export class DynamicGltfLoader {
|
|
|
1474
1655
|
message: "Collecting scene objects...",
|
|
1475
1656
|
});
|
|
1476
1657
|
|
|
1658
|
+
let totalObjectsToMerge = 0;
|
|
1659
|
+
|
|
1477
1660
|
this.scene.traverse((object) => {
|
|
1478
1661
|
if (object.userData.structureId) {
|
|
1479
1662
|
const structureId = object.userData.structureId;
|
|
@@ -1493,19 +1676,41 @@ export class DynamicGltfLoader {
|
|
|
1493
1676
|
}
|
|
1494
1677
|
|
|
1495
1678
|
const group = structureGroups.get(structureId);
|
|
1679
|
+
let added = false;
|
|
1496
1680
|
|
|
1497
1681
|
if (object instanceof Mesh) {
|
|
1498
1682
|
this.addToMaterialGroup(object, group.mapMeshes, group.meshes);
|
|
1683
|
+
added = true;
|
|
1499
1684
|
} else if (object instanceof LineSegments) {
|
|
1500
1685
|
this.addToMaterialGroup(object, group.mapLineSegments, group.lineSegments);
|
|
1686
|
+
added = true;
|
|
1501
1687
|
} else if (object instanceof Line) {
|
|
1502
1688
|
this.addToMaterialGroup(object, group.mapLines, group.lines);
|
|
1689
|
+
added = true;
|
|
1503
1690
|
} else if (object instanceof Points) {
|
|
1504
1691
|
this.addToMaterialGroup(object, group.mapPoints, group.points);
|
|
1692
|
+
added = true;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
if (added) {
|
|
1696
|
+
totalObjectsToMerge++;
|
|
1505
1697
|
}
|
|
1506
1698
|
}
|
|
1507
1699
|
});
|
|
1508
1700
|
|
|
1701
|
+
// Initialize transform texture and visibility arrays BEFORE merging
|
|
1702
|
+
// This ensures that as we create merged objects, the texture is large enough
|
|
1703
|
+
// and populated with identity matrices, so objects don't disappear (scale 0).
|
|
1704
|
+
if (totalObjectsToMerge > 0) {
|
|
1705
|
+
console.log(`Pre-allocating transform texture for ${totalObjectsToMerge} objects`);
|
|
1706
|
+
this.maxObjectId = totalObjectsToMerge;
|
|
1707
|
+
this.initTransformTexture();
|
|
1708
|
+
this.initializeObjectVisibility();
|
|
1709
|
+
|
|
1710
|
+
// Reset counter so IDs are assigned from 0 during merge
|
|
1711
|
+
this.maxObjectId = 0;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1509
1714
|
let processedGroups = 0;
|
|
1510
1715
|
const totalGroups = structureGroups.size;
|
|
1511
1716
|
|
|
@@ -1563,7 +1768,7 @@ export class DynamicGltfLoader {
|
|
|
1563
1768
|
}
|
|
1564
1769
|
});
|
|
1565
1770
|
|
|
1566
|
-
|
|
1771
|
+
// Texture and visibility initialized at start of optimization
|
|
1567
1772
|
|
|
1568
1773
|
console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
|
|
1569
1774
|
|
|
@@ -1658,6 +1863,7 @@ export class DynamicGltfLoader {
|
|
|
1658
1863
|
const visibilityMaterial = this.createVisibilityMaterial(group.material);
|
|
1659
1864
|
|
|
1660
1865
|
const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
|
|
1866
|
+
mergedMesh.frustumCulled = false; // Disable culling because vertex shader moves objects
|
|
1661
1867
|
mergedMesh.userData.isOptimized = true;
|
|
1662
1868
|
rootGroup.add(mergedMesh);
|
|
1663
1869
|
|
|
@@ -1804,6 +2010,7 @@ export class DynamicGltfLoader {
|
|
|
1804
2010
|
const visibilityMaterial = this.createVisibilityMaterial(group.material);
|
|
1805
2011
|
|
|
1806
2012
|
const mergedLine = new LineSegments(geometry, visibilityMaterial);
|
|
2013
|
+
mergedLine.frustumCulled = false;
|
|
1807
2014
|
mergedLine.userData.isEdge = isEdge;
|
|
1808
2015
|
mergedLine.userData.isOptimized = true;
|
|
1809
2016
|
|
|
@@ -1915,6 +2122,7 @@ export class DynamicGltfLoader {
|
|
|
1915
2122
|
const visibilityMaterial = this.createVisibilityMaterial(group.material);
|
|
1916
2123
|
|
|
1917
2124
|
const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
|
|
2125
|
+
mergedLine.frustumCulled = false;
|
|
1918
2126
|
mergedLine.userData.isEdge = isEdge;
|
|
1919
2127
|
mergedLine.userData.isOptimized = true;
|
|
1920
2128
|
|
|
@@ -2001,7 +2209,33 @@ export class DynamicGltfLoader {
|
|
|
2001
2209
|
|
|
2002
2210
|
if (geometries.length > 0) {
|
|
2003
2211
|
const mergedGeometry = mergeGeometries(geometries, false);
|
|
2004
|
-
|
|
2212
|
+
|
|
2213
|
+
// Add objectId attribute
|
|
2214
|
+
const totalVertices = mergedGeometry.attributes.position.count;
|
|
2215
|
+
const objectIds = new Float32Array(totalVertices);
|
|
2216
|
+
let vertexOffset = 0;
|
|
2217
|
+
|
|
2218
|
+
group.objects.forEach((points) => {
|
|
2219
|
+
const handle = points.userData.handle;
|
|
2220
|
+
if (!this.objectIdToIndex.has(handle)) {
|
|
2221
|
+
this.objectIdToIndex.set(handle, this.maxObjectId++);
|
|
2222
|
+
}
|
|
2223
|
+
const objectId = this.objectIdToIndex.get(handle);
|
|
2224
|
+
const count = points.geometry.attributes.position.count;
|
|
2225
|
+
for (let i = 0; i < count; i++) {
|
|
2226
|
+
objectIds[vertexOffset++] = objectId;
|
|
2227
|
+
}
|
|
2228
|
+
});
|
|
2229
|
+
mergedGeometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
|
|
2230
|
+
|
|
2231
|
+
// Add visibility attribute
|
|
2232
|
+
const visibilityArray = new Float32Array(totalVertices);
|
|
2233
|
+
visibilityArray.fill(1.0);
|
|
2234
|
+
mergedGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
|
|
2235
|
+
|
|
2236
|
+
const visibilityMaterial = this.createVisibilityMaterial(group.material);
|
|
2237
|
+
const mergedPoints = new Points(mergedGeometry, visibilityMaterial);
|
|
2238
|
+
mergedPoints.frustumCulled = false;
|
|
2005
2239
|
mergedPoints.userData.isOptimized = true;
|
|
2006
2240
|
|
|
2007
2241
|
if (this.useVAO) {
|
|
@@ -2085,15 +2319,41 @@ export class DynamicGltfLoader {
|
|
|
2085
2319
|
});
|
|
2086
2320
|
|
|
2087
2321
|
const finalGeometry = mergeGeometries(geometriesWithIndex, false);
|
|
2322
|
+
|
|
2323
|
+
// Add objectId attribute
|
|
2324
|
+
const totalVertices = finalGeometry.attributes.position.count;
|
|
2325
|
+
const objectIds = new Float32Array(totalVertices);
|
|
2326
|
+
let vertexOffset = 0;
|
|
2327
|
+
|
|
2328
|
+
lineSegmentsArray.forEach((segment) => {
|
|
2329
|
+
const handle = segment.userData.handle;
|
|
2330
|
+
if (!this.objectIdToIndex.has(handle)) {
|
|
2331
|
+
this.objectIdToIndex.set(handle, this.maxObjectId++);
|
|
2332
|
+
}
|
|
2333
|
+
const objectId = this.objectIdToIndex.get(handle);
|
|
2334
|
+
const count = segment.geometry.attributes.position.count;
|
|
2335
|
+
for (let i = 0; i < count; i++) {
|
|
2336
|
+
objectIds[vertexOffset++] = objectId;
|
|
2337
|
+
}
|
|
2338
|
+
});
|
|
2339
|
+
finalGeometry.setAttribute("objectId", new BufferAttribute(objectIds, 1));
|
|
2340
|
+
|
|
2341
|
+
// Add visibility attribute
|
|
2342
|
+
const visibilityArray = new Float32Array(totalVertices);
|
|
2343
|
+
visibilityArray.fill(1.0);
|
|
2344
|
+
finalGeometry.setAttribute("visibility", new BufferAttribute(visibilityArray, 1));
|
|
2345
|
+
|
|
2088
2346
|
const material = new LineBasicMaterial({
|
|
2089
2347
|
vertexColors: true,
|
|
2090
2348
|
});
|
|
2349
|
+
const visibilityMaterial = this.createVisibilityMaterial(material);
|
|
2091
2350
|
|
|
2092
2351
|
if (this.useVAO) {
|
|
2093
2352
|
this.createVAO(finalGeometry);
|
|
2094
2353
|
}
|
|
2095
2354
|
|
|
2096
|
-
const mergedLine = new LineSegments(finalGeometry,
|
|
2355
|
+
const mergedLine = new LineSegments(finalGeometry, visibilityMaterial);
|
|
2356
|
+
mergedLine.frustumCulled = false;
|
|
2097
2357
|
mergedLine.userData.structureId = structureId;
|
|
2098
2358
|
mergedLine.userData.isOptimized = true;
|
|
2099
2359
|
rootGroup.add(mergedLine);
|
|
@@ -2238,21 +2498,69 @@ export class DynamicGltfLoader {
|
|
|
2238
2498
|
return;
|
|
2239
2499
|
}
|
|
2240
2500
|
|
|
2501
|
+
if (!this.transformData) {
|
|
2502
|
+
console.warn("Transform texture not initialized");
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2241
2506
|
// Store transform map directly
|
|
2242
|
-
this.objectTransforms =
|
|
2507
|
+
this.objectTransforms = objectTransformMap;
|
|
2243
2508
|
|
|
2244
|
-
//
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2509
|
+
// Reset to identity first to ensure clean state
|
|
2510
|
+
this._resetTransformData(false);
|
|
2511
|
+
|
|
2512
|
+
// Cache references for tight loop
|
|
2513
|
+
const transformData = this.transformData;
|
|
2514
|
+
const objectIdToIndex = this.objectIdToIndex;
|
|
2515
|
+
|
|
2516
|
+
// Fast track map iteration using an array of values if we can
|
|
2517
|
+
// While .entries() is fast, sometimes direct properties check is faster
|
|
2518
|
+
let textureNeedsUpdate = false;
|
|
2519
|
+
|
|
2520
|
+
// Process matrices directly into texture array
|
|
2521
|
+
if (objectTransformMap instanceof Map) {
|
|
2522
|
+
// Modern V8 engines optimize for...of on Maps better than Array.from or destructuring iterators
|
|
2523
|
+
for (const [object, matrix] of objectTransformMap.entries()) {
|
|
2524
|
+
const userData = object.userData;
|
|
2525
|
+
if (!userData) continue;
|
|
2526
|
+
|
|
2527
|
+
const handle = userData.handle;
|
|
2528
|
+
if (handle === undefined) continue;
|
|
2529
|
+
|
|
2530
|
+
const objectId = objectIdToIndex.get(handle);
|
|
2531
|
+
if (objectId !== undefined) {
|
|
2532
|
+
// TypedArray.set is highly optimized in modern JS engines
|
|
2533
|
+
transformData.set(matrix.elements, objectId * 16);
|
|
2534
|
+
textureNeedsUpdate = true;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
} else {
|
|
2538
|
+
// Fallback for arrays of [object, matrix] pairs
|
|
2539
|
+
const len = objectTransformMap.length;
|
|
2540
|
+
for (let i = 0; i < len; i++) {
|
|
2541
|
+
const pair = objectTransformMap[i];
|
|
2542
|
+
const userData = pair[0].userData;
|
|
2543
|
+
if (!userData) continue;
|
|
2544
|
+
|
|
2545
|
+
const handle = userData.handle;
|
|
2546
|
+
if (handle === undefined) continue;
|
|
2547
|
+
|
|
2548
|
+
const objectId = objectIdToIndex.get(handle);
|
|
2549
|
+
if (objectId !== undefined) {
|
|
2550
|
+
transformData.set(pair[1].elements, objectId * 16);
|
|
2551
|
+
textureNeedsUpdate = true;
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2253
2554
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2555
|
+
|
|
2556
|
+
if (textureNeedsUpdate) {
|
|
2557
|
+
this.updateTransformTexture();
|
|
2558
|
+
if (
|
|
2559
|
+
this._lastTransformTexture !== this.transformTexture ||
|
|
2560
|
+
this._lastTransformTextureSize !== this.transformTextureSize
|
|
2561
|
+
) {
|
|
2562
|
+
this.updateMaterialUniforms();
|
|
2563
|
+
}
|
|
2256
2564
|
}
|
|
2257
2565
|
}
|
|
2258
2566
|
|
|
@@ -2273,28 +2581,90 @@ export class DynamicGltfLoader {
|
|
|
2273
2581
|
: Array.from(objects)
|
|
2274
2582
|
: Array.from(this.originalObjects);
|
|
2275
2583
|
|
|
2584
|
+
// Cache inverse matrices for structures
|
|
2585
|
+
const structureInverseMatrices = new Map();
|
|
2586
|
+
|
|
2587
|
+
// Map mesh -> node to access cached geometryExtents
|
|
2588
|
+
if (!this.meshToNodeMap) {
|
|
2589
|
+
this.meshToNodeMap = new Map();
|
|
2590
|
+
for (const node of this.nodes.values()) {
|
|
2591
|
+
if (node.object) {
|
|
2592
|
+
this.meshToNodeMap.set(node.object, node);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2276
2597
|
for (const obj of objectsArray) {
|
|
2277
2598
|
if (!obj.geometry || !obj.geometry.attributes.position) continue;
|
|
2278
2599
|
|
|
2279
|
-
|
|
2600
|
+
// OPTIMIZATION: Use cached node extent if available
|
|
2601
|
+
if (!obj.userData.explodeVector) {
|
|
2602
|
+
let center = null;
|
|
2280
2603
|
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2604
|
+
// 1. Try to get extent from node (fastest, pre-calculated)
|
|
2605
|
+
const node = this.meshToNodeMap.get(obj);
|
|
2606
|
+
if (node && node.geometryExtents) {
|
|
2607
|
+
const box = node.geometryExtents.clone();
|
|
2608
|
+
box.applyMatrix4(obj.matrixWorld);
|
|
2609
|
+
center = new Vector3();
|
|
2610
|
+
box.getCenter(center);
|
|
2611
|
+
}
|
|
2284
2612
|
|
|
2285
|
-
|
|
2613
|
+
// 2. Fallback to geometry bounding box
|
|
2614
|
+
if (!center) {
|
|
2615
|
+
if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox();
|
|
2616
|
+
const box = obj.geometry.boundingBox.clone();
|
|
2617
|
+
box.applyMatrix4(obj.matrixWorld);
|
|
2618
|
+
center = new Vector3();
|
|
2619
|
+
box.getCenter(center);
|
|
2620
|
+
}
|
|
2286
2621
|
|
|
2287
|
-
|
|
2288
|
-
|
|
2622
|
+
// Calculate vector from explode center to object center
|
|
2623
|
+
const explodeVector = center.sub(explodeCenter);
|
|
2289
2624
|
|
|
2290
|
-
|
|
2291
|
-
|
|
2625
|
+
// Cache it
|
|
2626
|
+
obj.userData.explodeVector = explodeVector;
|
|
2627
|
+
}
|
|
2628
|
+
const explodeVector = obj.userData.explodeVector;
|
|
2629
|
+
const distance = explodeVector.length();
|
|
2292
2630
|
|
|
2293
2631
|
if (distance > 0) {
|
|
2294
|
-
|
|
2295
|
-
const offset =
|
|
2632
|
+
// Calculate offset in World Space
|
|
2633
|
+
const offset = explodeVector.clone().multiplyScalar(explodeFactor - 1.0);
|
|
2634
|
+
|
|
2635
|
+
// Convert offset from World Space to Local Space of the merged mesh
|
|
2636
|
+
const localOffset = offset.clone();
|
|
2637
|
+
|
|
2638
|
+
if (obj.userData.structureId) {
|
|
2639
|
+
const structureId = obj.userData.structureId;
|
|
2640
|
+
let inverseMatrix = structureInverseMatrices.get(structureId);
|
|
2641
|
+
|
|
2642
|
+
if (!inverseMatrix) {
|
|
2643
|
+
const rootGroup = this.structureRoots.get(structureId);
|
|
2644
|
+
if (rootGroup) {
|
|
2645
|
+
// Reuse cached inverse matrix if possible
|
|
2646
|
+
if (!rootGroup.userData.inverseWorldMatrix) {
|
|
2647
|
+
// rootGroup.updateMatrixWorld(true); // Trust current state
|
|
2648
|
+
rootGroup.userData.inverseWorldMatrix = new Matrix4().copy(rootGroup.matrixWorld).invert();
|
|
2649
|
+
}
|
|
2650
|
+
inverseMatrix = rootGroup.userData.inverseWorldMatrix;
|
|
2651
|
+
structureInverseMatrices.set(structureId, inverseMatrix);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
if (inverseMatrix) {
|
|
2656
|
+
const zero = new Vector3(0, 0, 0).applyMatrix4(inverseMatrix);
|
|
2657
|
+
const vec = offset.clone().applyMatrix4(inverseMatrix).sub(zero);
|
|
2658
|
+
localOffset.copy(vec);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2296
2661
|
|
|
2297
|
-
|
|
2662
|
+
let matrix = obj.userData.explodeMatrix;
|
|
2663
|
+
if (!matrix) {
|
|
2664
|
+
matrix = new Matrix4();
|
|
2665
|
+
obj.userData.explodeMatrix = matrix;
|
|
2666
|
+
}
|
|
2667
|
+
matrix.makeTranslation(localOffset.x, localOffset.y, localOffset.z);
|
|
2298
2668
|
transformMap.set(obj, matrix);
|
|
2299
2669
|
}
|
|
2300
2670
|
}
|
|
@@ -2304,159 +2674,13 @@ export class DynamicGltfLoader {
|
|
|
2304
2674
|
|
|
2305
2675
|
clearTransforms() {
|
|
2306
2676
|
this.objectTransforms.clear();
|
|
2307
|
-
|
|
2308
|
-
for (const mesh of this.mergedMesh) {
|
|
2309
|
-
this._restoreOriginalGeometry(mesh);
|
|
2310
|
-
}
|
|
2311
|
-
for (const line of this.mergedLines) {
|
|
2312
|
-
this._restoreOriginalGeometry(line);
|
|
2313
|
-
}
|
|
2314
|
-
for (const lineSegment of this.mergedLineSegments) {
|
|
2315
|
-
this._restoreOriginalGeometry(lineSegment);
|
|
2316
|
-
}
|
|
2317
|
-
for (const point of this.mergedPoints) {
|
|
2318
|
-
this._restoreOriginalGeometry(point);
|
|
2319
|
-
}
|
|
2677
|
+
this._resetTransformData(true);
|
|
2320
2678
|
}
|
|
2321
2679
|
|
|
2322
2680
|
clearHandleTransforms() {
|
|
2323
2681
|
this.clearTransforms();
|
|
2324
2682
|
}
|
|
2325
2683
|
|
|
2326
|
-
_applyTransformToMergedObject(mergedObject) {
|
|
2327
|
-
const objectData = this.mergedObjectMap.get(mergedObject.uuid);
|
|
2328
|
-
if (!objectData || !objectData.objectMapping) return;
|
|
2329
|
-
|
|
2330
|
-
const geometry = mergedObject.geometry;
|
|
2331
|
-
if (!geometry || !geometry.attributes.position) return;
|
|
2332
|
-
|
|
2333
|
-
const positionAttr = geometry.attributes.position;
|
|
2334
|
-
const positions = positionAttr.array;
|
|
2335
|
-
|
|
2336
|
-
if (!this.transformedGeometries.has(mergedObject.uuid)) {
|
|
2337
|
-
this.transformedGeometries.set(mergedObject.uuid, new Float32Array(positions));
|
|
2338
|
-
}
|
|
2339
|
-
|
|
2340
|
-
const originalPositions = this.transformedGeometries.get(mergedObject.uuid);
|
|
2341
|
-
const tempVector = new Vector3();
|
|
2342
|
-
|
|
2343
|
-
for (const [originalMesh, mappingData] of objectData.objectMapping) {
|
|
2344
|
-
const transform = this.objectTransforms.get(originalMesh);
|
|
2345
|
-
|
|
2346
|
-
if (!transform) {
|
|
2347
|
-
const startIdx = mappingData.startVertexIndex * 3;
|
|
2348
|
-
const endIdx = (mappingData.startVertexIndex + mappingData.vertexCount) * 3;
|
|
2349
|
-
for (let i = startIdx; i < endIdx; i++) {
|
|
2350
|
-
positions[i] = originalPositions[i];
|
|
2351
|
-
}
|
|
2352
|
-
continue;
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
const startVertex = mappingData.startVertexIndex;
|
|
2356
|
-
const vertexCount = mappingData.vertexCount;
|
|
2357
|
-
|
|
2358
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
2359
|
-
const idx = (startVertex + i) * 3;
|
|
2360
|
-
|
|
2361
|
-
tempVector.set(originalPositions[idx], originalPositions[idx + 1], originalPositions[idx + 2]);
|
|
2362
|
-
|
|
2363
|
-
tempVector.applyMatrix4(transform);
|
|
2364
|
-
|
|
2365
|
-
positions[idx] = tempVector.x;
|
|
2366
|
-
positions[idx + 1] = tempVector.y;
|
|
2367
|
-
positions[idx + 2] = tempVector.z;
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
if (geometry.attributes.normal) {
|
|
2372
|
-
this._updateNormalsForTransform(geometry, objectData, originalPositions);
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
positionAttr.needsUpdate = true;
|
|
2376
|
-
geometry.computeBoundingSphere();
|
|
2377
|
-
geometry.computeBoundingBox();
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
_updateNormalsForTransform(geometry, objectData, originalPositions) {
|
|
2381
|
-
const normalAttr = geometry.attributes.normal;
|
|
2382
|
-
if (!normalAttr) return;
|
|
2383
|
-
|
|
2384
|
-
const normals = normalAttr.array;
|
|
2385
|
-
const tempVector = new Vector3();
|
|
2386
|
-
const normalMatrix = new Matrix4();
|
|
2387
|
-
|
|
2388
|
-
// Store original normals if not already stored
|
|
2389
|
-
const normalsKey = `${geometry.uuid}_normals`;
|
|
2390
|
-
if (!this.transformedGeometries.has(normalsKey)) {
|
|
2391
|
-
this.transformedGeometries.set(normalsKey, new Float32Array(normals));
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
const originalNormals = this.transformedGeometries.get(normalsKey);
|
|
2395
|
-
|
|
2396
|
-
for (const [originalMesh, mappingData] of objectData.objectMapping) {
|
|
2397
|
-
// Direct lookup by object reference - NO HANDLE LOOKUP!
|
|
2398
|
-
const transform = this.objectTransforms.get(originalMesh);
|
|
2399
|
-
|
|
2400
|
-
if (!transform) {
|
|
2401
|
-
// Restore original normals
|
|
2402
|
-
const startIdx = mappingData.startVertexIndex * 3;
|
|
2403
|
-
const endIdx = (mappingData.startVertexIndex + mappingData.vertexCount) * 3;
|
|
2404
|
-
for (let i = startIdx; i < endIdx; i++) {
|
|
2405
|
-
normals[i] = originalNormals[i];
|
|
2406
|
-
}
|
|
2407
|
-
continue;
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
// Create normal matrix (inverse transpose of transform)
|
|
2411
|
-
normalMatrix.copy(transform).invert().transpose();
|
|
2412
|
-
|
|
2413
|
-
const startVertex = mappingData.startVertexIndex;
|
|
2414
|
-
const vertexCount = mappingData.vertexCount;
|
|
2415
|
-
|
|
2416
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
2417
|
-
const idx = (startVertex + i) * 3;
|
|
2418
|
-
|
|
2419
|
-
// Get original normal
|
|
2420
|
-
tempVector.set(originalNormals[idx], originalNormals[idx + 1], originalNormals[idx + 2]);
|
|
2421
|
-
|
|
2422
|
-
// Apply normal transformation
|
|
2423
|
-
tempVector.applyMatrix4(normalMatrix).normalize();
|
|
2424
|
-
|
|
2425
|
-
// Write back transformed normal
|
|
2426
|
-
normals[idx] = tempVector.x;
|
|
2427
|
-
normals[idx + 1] = tempVector.y;
|
|
2428
|
-
normals[idx + 2] = tempVector.z;
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
normalAttr.needsUpdate = true;
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
_restoreOriginalGeometry(mergedObject) {
|
|
2436
|
-
const geometry = mergedObject.geometry;
|
|
2437
|
-
if (!geometry || !geometry.attributes.position) return;
|
|
2438
|
-
|
|
2439
|
-
// Restore original positions
|
|
2440
|
-
const originalPositions = this.transformedGeometries.get(mergedObject.uuid);
|
|
2441
|
-
if (originalPositions) {
|
|
2442
|
-
const positions = geometry.attributes.position.array;
|
|
2443
|
-
positions.set(originalPositions);
|
|
2444
|
-
geometry.attributes.position.needsUpdate = true;
|
|
2445
|
-
}
|
|
2446
|
-
|
|
2447
|
-
// Restore original normals
|
|
2448
|
-
const normalsKey = `${geometry.uuid}_normals`;
|
|
2449
|
-
const originalNormals = this.transformedGeometries.get(normalsKey);
|
|
2450
|
-
if (originalNormals && geometry.attributes.normal) {
|
|
2451
|
-
const normals = geometry.attributes.normal.array;
|
|
2452
|
-
normals.set(originalNormals);
|
|
2453
|
-
geometry.attributes.normal.needsUpdate = true;
|
|
2454
|
-
}
|
|
2455
|
-
|
|
2456
|
-
geometry.computeBoundingSphere();
|
|
2457
|
-
geometry.computeBoundingBox();
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
2684
|
syncHiddenObjects() {
|
|
2461
2685
|
if (this.mergedObjectMap.size === 0) {
|
|
2462
2686
|
console.log("No merged objects to sync");
|