@inweb/viewer-three 27.2.3 → 27.2.4

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.
@@ -23,10 +23,6 @@ import {
23
23
  NormalBlending,
24
24
  BufferAttribute,
25
25
  LineBasicMaterial,
26
- DataTexture,
27
- RGBAFormat,
28
- FloatType,
29
- NearestFilter,
30
26
  } from "three";
31
27
  import { GL_CONSTANTS } from "./GltfStructure.js";
32
28
  import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
@@ -65,6 +61,10 @@ export class DynamicGltfLoader {
65
61
 
66
62
  this.memoryLimit = this.getAvailableMemory();
67
63
  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,123 +126,6 @@ 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;
246
129
  }
247
130
 
248
131
  setVisibleEdges(visible) {
@@ -1286,100 +1169,42 @@ export class DynamicGltfLoader {
1286
1169
  }
1287
1170
 
1288
1171
  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
-
1297
1172
  material.onBeforeCompile = (shader) => {
1298
- shader.uniforms.transformTexture = uniforms.transformTexture;
1299
- shader.uniforms.transformTextureSize = uniforms.transformTextureSize;
1300
-
1301
- // 1. Common Definitions
1302
1173
  shader.vertexShader = shader.vertexShader.replace(
1303
1174
  "#include <common>",
1304
1175
  `
1305
1176
  #include <common>
1306
-
1307
1177
  attribute float visibility;
1308
- attribute float objectId;
1309
1178
  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
- }
1327
1179
  `
1328
1180
  );
1329
1181
 
1330
- // 2. Inject matrix retrieval at start of main()
1331
- shader.vertexShader = shader.vertexShader.replace(
1332
- "void main() {",
1182
+ shader.fragmentShader = shader.fragmentShader.replace(
1183
+ "#include <common>",
1333
1184
  `
1334
- void main() {
1335
- mat4 batchingMatrix = getTransformMatrix(objectId);
1336
- vVisibility = visibility;
1185
+ #include <common>
1186
+ varying float vVisibility;
1337
1187
  `
1338
1188
  );
1339
1189
 
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
1353
1190
  shader.vertexShader = shader.vertexShader.replace(
1354
- "#include <begin_vertex>",
1191
+ "void main() {",
1355
1192
  `
1356
- vec3 transformed = vec3( position );
1357
- transformed = ( batchingMatrix * vec4( transformed, 1.0 ) ).xyz;
1193
+ void main() {
1194
+ vVisibility = visibility;
1358
1195
  `
1359
1196
  );
1360
1197
 
1361
- // 5. Fragment Shader
1362
- shader.fragmentShader = shader.fragmentShader
1363
- .replace(
1364
- "#include <common>",
1365
- `
1366
- #include <common>
1367
- varying float vVisibility;
1198
+ shader.fragmentShader = shader.fragmentShader.replace(
1199
+ "void main() {",
1368
1200
  `
1369
- )
1370
- .replace(
1371
- "void main() {",
1372
- `
1373
1201
  void main() {
1374
1202
  if (vVisibility < 0.5) discard;
1375
1203
  `
1376
- );
1377
-
1378
- //console.log("!vertex", shader.vertexShader);
1204
+ );
1379
1205
  };
1380
-
1381
- // Ensure the material recompiles to pick up changes
1382
1206
  material.needsUpdate = true;
1207
+
1383
1208
  return material;
1384
1209
  }
1385
1210
 
@@ -1507,8 +1332,6 @@ export class DynamicGltfLoader {
1507
1332
  this.objectIdToIndex.clear();
1508
1333
  this.maxObjectId = 0;
1509
1334
  this.objectVisibility = new Float32Array();
1510
- this.meshToNodeMap = null;
1511
- this.visibilityMaterials.clear();
1512
1335
  }
1513
1336
 
1514
1337
  setStructureTransform(structureId, matrix) {
@@ -1643,10 +1466,6 @@ export class DynamicGltfLoader {
1643
1466
 
1644
1467
  this.originalObjects.clear();
1645
1468
  this.originalObjectsToSelection.clear();
1646
- // Clear previous optimization data
1647
- this.objectIdToIndex.clear();
1648
- this.maxObjectId = 0;
1649
-
1650
1469
  const structureGroups = new Map();
1651
1470
 
1652
1471
  this.dispatchEvent("optimizationprogress", {
@@ -1655,8 +1474,6 @@ export class DynamicGltfLoader {
1655
1474
  message: "Collecting scene objects...",
1656
1475
  });
1657
1476
 
1658
- let totalObjectsToMerge = 0;
1659
-
1660
1477
  this.scene.traverse((object) => {
1661
1478
  if (object.userData.structureId) {
1662
1479
  const structureId = object.userData.structureId;
@@ -1676,41 +1493,19 @@ export class DynamicGltfLoader {
1676
1493
  }
1677
1494
 
1678
1495
  const group = structureGroups.get(structureId);
1679
- let added = false;
1680
1496
 
1681
1497
  if (object instanceof Mesh) {
1682
1498
  this.addToMaterialGroup(object, group.mapMeshes, group.meshes);
1683
- added = true;
1684
1499
  } else if (object instanceof LineSegments) {
1685
1500
  this.addToMaterialGroup(object, group.mapLineSegments, group.lineSegments);
1686
- added = true;
1687
1501
  } else if (object instanceof Line) {
1688
1502
  this.addToMaterialGroup(object, group.mapLines, group.lines);
1689
- added = true;
1690
1503
  } else if (object instanceof Points) {
1691
1504
  this.addToMaterialGroup(object, group.mapPoints, group.points);
1692
- added = true;
1693
- }
1694
-
1695
- if (added) {
1696
- totalObjectsToMerge++;
1697
1505
  }
1698
1506
  }
1699
1507
  });
1700
1508
 
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
-
1714
1509
  let processedGroups = 0;
1715
1510
  const totalGroups = structureGroups.size;
1716
1511
 
@@ -1768,7 +1563,7 @@ export class DynamicGltfLoader {
1768
1563
  }
1769
1564
  });
1770
1565
 
1771
- // Texture and visibility initialized at start of optimization
1566
+ this.initializeObjectVisibility();
1772
1567
 
1773
1568
  console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
1774
1569
 
@@ -1863,7 +1658,6 @@ export class DynamicGltfLoader {
1863
1658
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
1864
1659
 
1865
1660
  const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
1866
- mergedMesh.frustumCulled = false; // Disable culling because vertex shader moves objects
1867
1661
  mergedMesh.userData.isOptimized = true;
1868
1662
  rootGroup.add(mergedMesh);
1869
1663
 
@@ -2010,7 +1804,6 @@ export class DynamicGltfLoader {
2010
1804
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
2011
1805
 
2012
1806
  const mergedLine = new LineSegments(geometry, visibilityMaterial);
2013
- mergedLine.frustumCulled = false;
2014
1807
  mergedLine.userData.isEdge = isEdge;
2015
1808
  mergedLine.userData.isOptimized = true;
2016
1809
 
@@ -2122,7 +1915,6 @@ export class DynamicGltfLoader {
2122
1915
  const visibilityMaterial = this.createVisibilityMaterial(group.material);
2123
1916
 
2124
1917
  const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
2125
- mergedLine.frustumCulled = false;
2126
1918
  mergedLine.userData.isEdge = isEdge;
2127
1919
  mergedLine.userData.isOptimized = true;
2128
1920
 
@@ -2209,33 +2001,7 @@ export class DynamicGltfLoader {
2209
2001
 
2210
2002
  if (geometries.length > 0) {
2211
2003
  const mergedGeometry = mergeGeometries(geometries, false);
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;
2004
+ const mergedPoints = new Points(mergedGeometry, group.material);
2239
2005
  mergedPoints.userData.isOptimized = true;
2240
2006
 
2241
2007
  if (this.useVAO) {
@@ -2319,41 +2085,15 @@ export class DynamicGltfLoader {
2319
2085
  });
2320
2086
 
2321
2087
  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
-
2346
2088
  const material = new LineBasicMaterial({
2347
2089
  vertexColors: true,
2348
2090
  });
2349
- const visibilityMaterial = this.createVisibilityMaterial(material);
2350
2091
 
2351
2092
  if (this.useVAO) {
2352
2093
  this.createVAO(finalGeometry);
2353
2094
  }
2354
2095
 
2355
- const mergedLine = new LineSegments(finalGeometry, visibilityMaterial);
2356
- mergedLine.frustumCulled = false;
2096
+ const mergedLine = new LineSegments(finalGeometry, material);
2357
2097
  mergedLine.userData.structureId = structureId;
2358
2098
  mergedLine.userData.isOptimized = true;
2359
2099
  rootGroup.add(mergedLine);
@@ -2498,69 +2238,21 @@ export class DynamicGltfLoader {
2498
2238
  return;
2499
2239
  }
2500
2240
 
2501
- if (!this.transformData) {
2502
- console.warn("Transform texture not initialized");
2503
- return;
2504
- }
2505
-
2506
2241
  // Store transform map directly
2507
- this.objectTransforms = objectTransformMap;
2508
-
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;
2242
+ this.objectTransforms = new Map(objectTransformMap);
2529
2243
 
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
- }
2244
+ // Apply transforms to all merged meshes
2245
+ for (const mesh of this.mergedMesh) {
2246
+ this._applyTransformToMergedObject(mesh);
2554
2247
  }
2555
-
2556
- if (textureNeedsUpdate) {
2557
- this.updateTransformTexture();
2558
- if (
2559
- this._lastTransformTexture !== this.transformTexture ||
2560
- this._lastTransformTextureSize !== this.transformTextureSize
2561
- ) {
2562
- this.updateMaterialUniforms();
2563
- }
2248
+ for (const line of this.mergedLines) {
2249
+ this._applyTransformToMergedObject(line);
2250
+ }
2251
+ for (const lineSegment of this.mergedLineSegments) {
2252
+ this._applyTransformToMergedObject(lineSegment);
2253
+ }
2254
+ for (const point of this.mergedPoints) {
2255
+ this._applyTransformToMergedObject(point);
2564
2256
  }
2565
2257
  }
2566
2258
 
@@ -2581,90 +2273,28 @@ export class DynamicGltfLoader {
2581
2273
  : Array.from(objects)
2582
2274
  : Array.from(this.originalObjects);
2583
2275
 
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
-
2597
2276
  for (const obj of objectsArray) {
2598
2277
  if (!obj.geometry || !obj.geometry.attributes.position) continue;
2599
2278
 
2600
- // OPTIMIZATION: Use cached node extent if available
2601
- if (!obj.userData.explodeVector) {
2602
- let center = null;
2279
+ const boundingBox = new Box3().setFromBufferAttribute(obj.geometry.attributes.position);
2603
2280
 
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
- }
2281
+ if (obj.matrixWorld) {
2282
+ boundingBox.applyMatrix4(obj.matrixWorld);
2283
+ }
2612
2284
 
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
- }
2285
+ if (boundingBox.isEmpty()) continue;
2621
2286
 
2622
- // Calculate vector from explode center to object center
2623
- const explodeVector = center.sub(explodeCenter);
2287
+ const objectCenter = new Vector3();
2288
+ boundingBox.getCenter(objectCenter);
2624
2289
 
2625
- // Cache it
2626
- obj.userData.explodeVector = explodeVector;
2627
- }
2628
- const explodeVector = obj.userData.explodeVector;
2629
- const distance = explodeVector.length();
2290
+ const direction = objectCenter.clone().sub(explodeCenter);
2291
+ const distance = direction.length();
2630
2292
 
2631
2293
  if (distance > 0) {
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
- }
2294
+ direction.normalize();
2295
+ const offset = direction.multiplyScalar(distance * (explodeFactor - 1.0));
2661
2296
 
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);
2297
+ const matrix = new Matrix4().makeTranslation(offset.x, offset.y, offset.z);
2668
2298
  transformMap.set(obj, matrix);
2669
2299
  }
2670
2300
  }
@@ -2674,13 +2304,159 @@ export class DynamicGltfLoader {
2674
2304
 
2675
2305
  clearTransforms() {
2676
2306
  this.objectTransforms.clear();
2677
- this._resetTransformData(true);
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
+ }
2678
2320
  }
2679
2321
 
2680
2322
  clearHandleTransforms() {
2681
2323
  this.clearTransforms();
2682
2324
  }
2683
2325
 
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
+
2684
2460
  syncHiddenObjects() {
2685
2461
  if (this.mergedObjectMap.size === 0) {
2686
2462
  console.log("No merged objects to sync");