@inweb/viewer-three 27.1.9 → 27.2.1

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.
@@ -99,7 +99,7 @@ export declare class Viewer extends EventEmitter2<ViewerEventMap & CanvasEventMa
99
99
  *
100
100
  * @param file - File to load. Can be:
101
101
  *
102
- * - `File`, `Assembly` or `Model` instance from the Open Cloud Server
102
+ * - `File`, `Assembly`, or `Model` instance from the Open Cloud Server
103
103
  * - `URL` string
104
104
  * - {@link https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs | Data URL} string
105
105
  * - {@link https://developer.mozilla.org/docs/Web/API/File | Web API File} object
@@ -11,12 +11,14 @@ interface WalkControlsEventMap {
11
11
  export declare class WalkControls extends Controls<WalkControlsEventMap> {
12
12
  readonly EYE_HEIGHT = 1.7;
13
13
  readonly FAILING_DISTANCE = 2;
14
- readonly GROUND_FOLLOWING_SPEED = 0.05;
14
+ readonly GROUND_FOLLOWING_SKIP_FRAMES = 3;
15
+ readonly GROUND_FOLLOWING_SPEED = 0.4;
15
16
  readonly LOOK_SPEED = 0.1;
16
17
  readonly WALK_SPEED_DELIMITER = 4;
17
18
  readonly WHEEL_SPEED_DELIMITER = 15000;
18
19
  movementSpeed: number;
19
20
  multiplier: number;
21
+ private groundFollowingSkippedFrames;
20
22
  private raycaster;
21
23
  private groundObjects;
22
24
  private moveKeys;
@@ -27,6 +29,9 @@ export declare class WalkControls extends Controls<WalkControlsEventMap> {
27
29
  private mouseDragOn;
28
30
  rotateDelta: Vector2;
29
31
  private camera;
32
+ private readonly _up;
33
+ private readonly _forward;
34
+ private readonly _sideways;
30
35
  constructor(camera: Camera, canvas: HTMLElement, groundObjects: Object3D[]);
31
36
  dispose(): void;
32
37
  onPointerDown: (event: PointerEvent) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inweb/viewer-three",
3
- "version": "27.1.9",
3
+ "version": "27.2.1",
4
4
  "description": "JavaScript library for rendering CAD and BIM files in a browser using Three.js",
5
5
  "homepage": "https://cloud.opendesign.com/docs/index.html",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -35,10 +35,10 @@
35
35
  "docs": "typedoc"
36
36
  },
37
37
  "dependencies": {
38
- "@inweb/client": "~27.1.9",
39
- "@inweb/eventemitter2": "~27.1.9",
40
- "@inweb/markup": "~27.1.9",
41
- "@inweb/viewer-core": "~27.1.9"
38
+ "@inweb/client": "~27.2.1",
39
+ "@inweb/eventemitter2": "~27.2.1",
40
+ "@inweb/markup": "~27.2.1",
41
+ "@inweb/viewer-core": "~27.2.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@streamparser/json": "^0.0.22",
@@ -389,7 +389,7 @@ export class Viewer
389
389
  *
390
390
  * @param file - File to load. Can be:
391
391
  *
392
- * - `File`, `Assembly` or `Model` instance from the Open Cloud Server
392
+ * - `File`, `Assembly`, or `Model` instance from the Open Cloud Server
393
393
  * - `URL` string
394
394
  * - {@link https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs | Data URL} string
395
395
  * - {@link https://developer.mozilla.org/docs/Web/API/File | Web API File} object
@@ -134,7 +134,7 @@ export class CameraComponent implements IComponent {
134
134
  });
135
135
 
136
136
  if (camera) {
137
- camera.isDefaultCamera = true;
137
+ camera.userData.isDefaultCamera = true;
138
138
  camera.scale.set(1, 1, 1); // <- Visualize fix
139
139
 
140
140
  this.switchCamera(camera);
@@ -100,6 +100,7 @@ export class InfoComponent implements IComponent {
100
100
 
101
101
  this.viewer.info.memory.geometries = 0;
102
102
  this.viewer.info.memory.geometryBytes = 0;
103
+ this.viewer.info.memory.optimizedGeometryBytes = 0;
103
104
  this.viewer.info.memory.textures = 0;
104
105
  this.viewer.info.memory.textureBytes = 0;
105
106
  this.viewer.info.memory.materials = 0;
@@ -141,6 +142,7 @@ export class InfoComponent implements IComponent {
141
142
 
142
143
  this.viewer.info.memory.geometries += info.memory.geometries;
143
144
  this.viewer.info.memory.geometryBytes += info.memory.geometryBytes;
145
+ this.viewer.info.memory.optimizedGeometryBytes += info.memory.optimizedGeometryBytes;
144
146
  this.viewer.info.memory.textures += info.memory.textures;
145
147
  this.viewer.info.memory.textureBytes += info.memory.textureBytes;
146
148
  this.viewer.info.memory.materials += info.memory.materials;
@@ -153,7 +155,9 @@ export class InfoComponent implements IComponent {
153
155
 
154
156
  console.log("Number of objects:", info.scene.objects);
155
157
  console.log("Number of objects after optimization:", info.optimizedScene.objects);
156
- console.log("Total geometry size:", info.memory.totalEstimatedGpuBytes / (1024 * 1024), "MB");
158
+ console.log("Geometry size:", info.memory.geometryBytes / (1024 * 1024), "MB");
159
+ console.log("Optimized geometry size:", info.memory.optimizedGeometryBytes / (1024 * 1024), "MB");
160
+ console.log("Estimated GPU used:", info.memory.totalEstimatedGpuBytes / (1024 * 1024), "MB");
157
161
 
158
162
  console.log("File load time:", this.viewer.info.performance.loadTime, "ms");
159
163
  };
@@ -31,13 +31,15 @@ interface WalkControlsEventMap {
31
31
  export class WalkControls extends Controls<WalkControlsEventMap> {
32
32
  readonly EYE_HEIGHT = 1.7;
33
33
  readonly FAILING_DISTANCE = 2;
34
- readonly GROUND_FOLLOWING_SPEED = 0.05;
34
+ readonly GROUND_FOLLOWING_SKIP_FRAMES = 3;
35
+ readonly GROUND_FOLLOWING_SPEED = 0.4;
35
36
  readonly LOOK_SPEED = 0.1;
36
37
  readonly WALK_SPEED_DELIMITER = 4;
37
38
  readonly WHEEL_SPEED_DELIMITER = 15000;
38
39
 
39
40
  public movementSpeed = 0.1;
40
41
  public multiplier = 3;
42
+ private groundFollowingSkippedFrames = 0;
41
43
 
42
44
  private raycaster: Raycaster;
43
45
  private groundObjects: Object3D[];
@@ -53,6 +55,10 @@ export class WalkControls extends Controls<WalkControlsEventMap> {
53
55
 
54
56
  private camera: Camera;
55
57
 
58
+ private readonly _up = new Vector3();
59
+ private readonly _forward = new Vector3();
60
+ private readonly _sideways = new Vector3();
61
+
56
62
  constructor(camera: Camera, canvas: HTMLElement, groundObjects: Object3D[]) {
57
63
  super(camera, canvas);
58
64
  this.camera = camera;
@@ -61,6 +67,14 @@ export class WalkControls extends Controls<WalkControlsEventMap> {
61
67
  this.raycaster = new Raycaster();
62
68
  this.raycaster.near = 0;
63
69
  this.raycaster.far = this.EYE_HEIGHT + this.FAILING_DISTANCE;
70
+ this.raycaster.params = {
71
+ Mesh: {},
72
+ Line: { threshold: 0 },
73
+ Line2: { threshold: 0 },
74
+ LOD: { threshold: 0 },
75
+ Points: { threshold: 0 },
76
+ Sprite: { threshold: 0 },
77
+ };
64
78
 
65
79
  this.moveKeys = new Set();
66
80
  this.moveClock = new Clock();
@@ -165,17 +179,8 @@ export class WalkControls extends Controls<WalkControlsEventMap> {
165
179
  };
166
180
 
167
181
  private updateGroundFollowing() {
168
- const up = new Vector3().copy(this.camera.up);
169
- this.raycaster.set(this.object.position, up.negate());
170
-
171
- this.raycaster.params = this.raycaster.params = {
172
- Mesh: {},
173
- Line: { threshold: 0 },
174
- Line2: { threshold: 0 },
175
- LOD: { threshold: 0 },
176
- Points: { threshold: 0 },
177
- Sprite: { threshold: 0 },
178
- };
182
+ this._up.copy(this.camera.up).negate();
183
+ this.raycaster.set(this.object.position, this._up);
179
184
 
180
185
  const intersects = this.raycaster.intersectObjects(this.groundObjects, false);
181
186
  if (intersects.length > 0) {
@@ -190,8 +195,8 @@ export class WalkControls extends Controls<WalkControlsEventMap> {
190
195
  override update() {
191
196
  let moved = false;
192
197
  let upgradeGroundFollowing = false;
193
- const forward = new Vector3();
194
- const sideways = new Vector3();
198
+ const forward = this._forward;
199
+ const sideways = this._sideways;
195
200
 
196
201
  if (this.moveKeys.size > 0) {
197
202
  upgradeGroundFollowing = true;
@@ -252,7 +257,11 @@ export class WalkControls extends Controls<WalkControlsEventMap> {
252
257
  moved = true;
253
258
  }
254
259
 
255
- if (upgradeGroundFollowing) this.updateGroundFollowing();
260
+ this.groundFollowingSkippedFrames++;
261
+ if (upgradeGroundFollowing && this.groundFollowingSkippedFrames >= this.GROUND_FOLLOWING_SKIP_FRAMES) {
262
+ this.groundFollowingSkippedFrames = 0;
263
+ this.updateGroundFollowing();
264
+ }
256
265
 
257
266
  if (moved) {
258
267
  this.dispatchEvent({ type: "change" });
@@ -60,11 +60,18 @@ export class DynamicGltfLoader {
60
60
  this.structureRoots = new Map();
61
61
 
62
62
  this.memoryLimit = this.getAvailableMemory();
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
+ this.memoryEstimationFactor = 1.7;
63
69
  this.loadedGeometrySize = 0;
64
70
  this.geometryCache = new Map();
65
71
  this.materialCache = new Map();
66
72
  this.textureCache = new Map();
67
73
  this.currentMemoryUsage = 0;
74
+ this.pendingMemoryUsage = 0;
68
75
 
69
76
  this.updateMemoryIndicator();
70
77
 
@@ -145,7 +152,7 @@ export class DynamicGltfLoader {
145
152
  console.warn("Error detecting available memory:", error);
146
153
  }
147
154
 
148
- return memoryLimit / 3;
155
+ return memoryLimit;
149
156
  }
150
157
 
151
158
  getAbortController() {
@@ -156,9 +163,27 @@ export class DynamicGltfLoader {
156
163
  this.abortController.abort();
157
164
  }
158
165
 
166
+ getOptimizedGeometrySize() {
167
+ let total = 0;
168
+ const addSize = (obj) => {
169
+ if (obj && obj.geometry) total += this.estimateGeometrySize(obj);
170
+ };
171
+ this.mergedMesh?.forEach(addSize);
172
+ this.mergedLines?.forEach(addSize);
173
+ this.mergedLineSegments?.forEach(addSize);
174
+ this.mergedPoints?.forEach(addSize);
175
+ return total;
176
+ }
177
+
159
178
  updateMemoryIndicator() {
179
+ const optimizedUsage = this.getOptimizedGeometrySize();
180
+ const totalUsage = this.currentMemoryUsage + optimizedUsage;
181
+ const totalUsageEstimate = Math.round(totalUsage * this.memoryEstimationFactor);
160
182
  this.dispatchEvent("geometrymemory", {
161
183
  currentUsage: this.currentMemoryUsage,
184
+ optimizedUsage,
185
+ totalUsage,
186
+ totalUsageEstimate,
162
187
  limit: this.memoryLimit,
163
188
  });
164
189
  }
@@ -224,11 +249,14 @@ export class DynamicGltfLoader {
224
249
  currentMemoryUsage += geo.size;
225
250
  }
226
251
 
227
- if (currentMemoryUsage > this.memoryLimit) {
228
- console.log(`Memory usage (${Math.round(currentMemoryUsage / (1024 * 1024))}MB) exceeds limit`);
252
+ const effectiveLimitForEviction = this.memoryLimit / this.memoryEstimationFactor;
253
+ if (currentMemoryUsage > effectiveLimitForEviction) {
254
+ console.log(
255
+ `Memory usage (${Math.round((currentMemoryUsage * this.memoryEstimationFactor) / (1024 * 1024))}MB est.) exceeds limit`
256
+ );
229
257
 
230
258
  for (const geo of geometries) {
231
- if (currentMemoryUsage <= this.memoryLimit) break;
259
+ if (currentMemoryUsage <= effectiveLimitForEviction) break;
232
260
 
233
261
  if (this.abortController.signal.aborted) {
234
262
  throw new DOMException("Loading aborted", "AbortError");
@@ -325,7 +353,9 @@ export class DynamicGltfLoader {
325
353
  }
326
354
  const materialCount = uniqueMaterialIds.size;
327
355
  const textureCount = uniqueTextureIds.size;
328
- const estimatedGpuMemoryBytes = geometryMemoryBytes;
356
+ const optimizedUsageBytes = this.getOptimizedGeometrySize();
357
+ const totalUsageBytes = geometryMemoryBytes + optimizedUsageBytes;
358
+ const estimatedGpuMemoryBytes = Math.round(totalUsageBytes * this.memoryEstimationFactor);
329
359
 
330
360
  if (!this._webglInfoCache) {
331
361
  try {
@@ -366,7 +396,12 @@ export class DynamicGltfLoader {
366
396
  },
367
397
  },
368
398
  memory: {
369
- geometries: { count: geometryCount, bytes: geometryMemoryBytes },
399
+ geometries: {
400
+ count: geometryCount,
401
+ bytes: geometryMemoryBytes,
402
+ optimizedBytes: optimizedUsageBytes,
403
+ totalRawBytes: totalUsageBytes,
404
+ },
370
405
  textures: { count: textureCount },
371
406
  materials: { count: materialCount },
372
407
  totalEstimatedGpuBytes: estimatedGpuMemoryBytes,
@@ -379,9 +414,12 @@ export class DynamicGltfLoader {
379
414
  };
380
415
  }
381
416
 
382
- async loadNode(nodeId, onLoadFinishCb) {
417
+ async loadNode(nodeId, onLoadFinishCb, reservedEstimatedSize = 0) {
383
418
  const node = this.nodes.get(nodeId);
384
- if (!node || node.loaded || node.loading) return;
419
+ if (!node || node.loaded || node.loading) {
420
+ this.pendingMemoryUsage = Math.max(0, this.pendingMemoryUsage - reservedEstimatedSize);
421
+ return;
422
+ }
385
423
 
386
424
  node.loading = true;
387
425
  const meshDef = node.structure.getJson().meshes[node.meshIndex];
@@ -471,6 +509,7 @@ export class DynamicGltfLoader {
471
509
  if (bufferRequests.length === 0) {
472
510
  node.loaded = true;
473
511
  node.loading = false;
512
+ this.pendingMemoryUsage = Math.max(0, this.pendingMemoryUsage - reservedEstimatedSize);
474
513
  return;
475
514
  }
476
515
  bufferRequests.sort((a, b) => a.offset - b.offset);
@@ -597,6 +636,7 @@ export class DynamicGltfLoader {
597
636
  }
598
637
  node.loaded = true;
599
638
  node.loading = false;
639
+ this.pendingMemoryUsage = Math.max(0, this.pendingMemoryUsage - reservedEstimatedSize);
600
640
  const geometrySize = this.estimateGeometrySize(node.object);
601
641
  this.geometryCache.set(node.object.uuid, geometrySize);
602
642
  this.currentMemoryUsage += geometrySize;
@@ -605,6 +645,7 @@ export class DynamicGltfLoader {
605
645
  }
606
646
  } catch (error) {
607
647
  node.loading = false;
648
+ this.pendingMemoryUsage = Math.max(0, this.pendingMemoryUsage - reservedEstimatedSize);
608
649
 
609
650
  if (error.name === "AbortError") {
610
651
  return;
@@ -872,16 +913,19 @@ export class DynamicGltfLoader {
872
913
  let loadedCount = 0;
873
914
  let lastLoadedCount = 0;
874
915
  const totalNodes = nodesToLoad.length;
916
+ const progressTotal = { value: totalNodes };
875
917
 
876
918
  const loadProgress = async () => {
877
919
  loadedCount++;
920
+ const total = progressTotal.value;
921
+ const percentage = total > 0 ? Math.min(100, Math.round((loadedCount / total) * 100)) : 0;
878
922
  if (loadedCount - lastLoadedCount > 1000) {
879
923
  lastLoadedCount = loadedCount;
880
924
  this.updateMemoryIndicator();
881
925
  this.dispatchEvent("geometryprogress", {
882
- percentage: Math.round((loadedCount / totalNodes) * 100),
926
+ percentage,
883
927
  loaded: loadedCount,
884
- total: totalNodes,
928
+ total,
885
929
  });
886
930
 
887
931
  this.dispatchEvent("update");
@@ -894,23 +938,25 @@ export class DynamicGltfLoader {
894
938
 
895
939
  try {
896
940
  const loadOperations = [];
941
+ let memoryLimitReached = false;
897
942
  for (const nodeId of nodesToLoad) {
898
943
  if (this.abortController.signal.aborted) {
899
944
  throw new DOMException("Loading aborted", "AbortError");
900
945
  }
901
946
 
902
947
  const estimatedSize = await this.estimateNodeSize(nodeId);
948
+ const estimated = Number(estimatedSize) || 0;
949
+ const effectiveLimit = this.memoryLimit / this.optimizationMemoryMultiplier / this.memoryEstimationFactor;
903
950
 
904
- if (this.currentMemoryUsage + estimatedSize > this.memoryLimit) {
905
- console.log(`Memory limit reached after loading ${loadedCount} nodes`);
906
- this.dispatchEvent("geometryerror", {
907
- message: "Memory limit reached",
908
- });
909
- this.dispatchEvent("update");
910
- return loadedCount;
951
+ if (this.currentMemoryUsage + this.pendingMemoryUsage + estimated > effectiveLimit) {
952
+ memoryLimitReached = true;
953
+ progressTotal.value = loadOperations.length;
954
+ console.log(`Memory limit reached after scheduling ${loadOperations.length} nodes`);
955
+ break;
911
956
  }
912
957
 
913
- loadOperations.push(this.loadNode(nodeId, loadProgress));
958
+ this.pendingMemoryUsage += estimated;
959
+ loadOperations.push(this.loadNode(nodeId, loadProgress, estimated));
914
960
  }
915
961
 
916
962
  for (const structure of this.structures) {
@@ -921,7 +967,8 @@ export class DynamicGltfLoader {
921
967
 
922
968
  this.dispatchEvent("geometryend", {
923
969
  totalLoaded: loadedCount,
924
- totalNodes,
970
+ totalNodes: progressTotal.value,
971
+ memoryLimitReached,
925
972
  });
926
973
 
927
974
  return loadedCount;
@@ -1004,8 +1051,6 @@ export class DynamicGltfLoader {
1004
1051
  const transformedBox = node.geometryExtents.clone();
1005
1052
  const structureRoot = node.structure ? this.structureRoots.get(node.structure.id) : null;
1006
1053
 
1007
- // Calculate relative transformation from node.group to structureRoot
1008
- // This matches the logic used in merge methods
1009
1054
  if (node.group) {
1010
1055
  const relativeMatrix = new Matrix4();
1011
1056
  let currentObject = node.group;
@@ -1278,6 +1323,7 @@ export class DynamicGltfLoader {
1278
1323
 
1279
1324
  this.totalLoadedObjects = 0;
1280
1325
  this.currentMemoryUsage = 0;
1326
+ this.pendingMemoryUsage = 0;
1281
1327
  this.loadedGeometrySize = 0;
1282
1328
 
1283
1329
  this.abortController = new AbortController();
@@ -1527,6 +1573,7 @@ export class DynamicGltfLoader {
1527
1573
  message: `Optimization complete! ${this.maxObjectId} objects processed.`,
1528
1574
  });
1529
1575
 
1576
+ this.updateMemoryIndicator();
1530
1577
  this.dispatchEvent("update");
1531
1578
  }
1532
1579
 
@@ -49,6 +49,7 @@ export class DynamicModelImpl extends ModelImpl {
49
49
 
50
50
  info.memory.geometries = stats.memory.geometries.count;
51
51
  info.memory.geometryBytes = stats.memory.geometries.bytes;
52
+ info.memory.optimizedGeometryBytes = stats.memory.geometries.optimizedBytes;
52
53
  info.memory.textures = stats.memory.textures.count;
53
54
  info.memory.materials = stats.memory.materials.count;
54
55
  info.memory.totalEstimatedGpuBytes = stats.memory.totalEstimatedGpuBytes;
@@ -259,6 +259,7 @@ export class ModelImpl implements IModelImpl {
259
259
 
260
260
  info.memory.geometries = geometries.size;
261
261
  info.memory.geometryBytes = geometryBytes;
262
+ info.memory.optimizedGeometryBytes = 0;
262
263
  info.memory.textures = textures.size;
263
264
  info.memory.textureBytes = Math.floor(textureBytes);
264
265
  info.memory.materials = materials.size;