@inweb/viewer-three 26.9.0 → 26.9.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.
@@ -7,9 +7,9 @@ export declare const defaultViewPositions: {
7
7
  right: Vector3;
8
8
  bottom: Vector3;
9
9
  top: Vector3;
10
- ns: Vector3;
10
+ se: Vector3;
11
11
  sw: Vector3;
12
+ ne: Vector3;
12
13
  nw: Vector3;
13
- se: Vector3;
14
14
  };
15
15
  export declare function setDefaultViewPosition(viewer: Viewer, position: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inweb/viewer-three",
3
- "version": "26.9.0",
3
+ "version": "26.9.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": "~26.9.0",
39
- "@inweb/eventemitter2": "~26.9.0",
40
- "@inweb/markup": "~26.9.0",
41
- "@inweb/viewer-core": "~26.9.0"
38
+ "@inweb/client": "~26.9.1",
39
+ "@inweb/eventemitter2": "~26.9.1",
40
+ "@inweb/markup": "~26.9.1",
41
+ "@inweb/viewer-core": "~26.9.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/three": "^0.179.0",
@@ -170,7 +170,7 @@ export class Viewer
170
170
 
171
171
  this.scene = new Scene();
172
172
  this.helpers = new Helpers();
173
- this.target = new Vector3();
173
+ this.target = new Vector3(0, 0, 0);
174
174
 
175
175
  const pixelRatio = window.devicePixelRatio;
176
176
  const rect = canvas.parentElement.getBoundingClientRect();
@@ -178,7 +178,7 @@ export class Viewer
178
178
  const height = rect.height || 1;
179
179
  const aspect = width / height;
180
180
 
181
- this.camera = new PerspectiveCamera(45, aspect, 0.01, 1000);
181
+ this.camera = new PerspectiveCamera(45, aspect, 0.001, 1000);
182
182
  this.camera.up.set(0, 1, 0);
183
183
  this.camera.position.set(0, 0, 1);
184
184
  this.camera.lookAt(this.target);
@@ -661,7 +661,7 @@ export class Viewer
661
661
  camera.left = camera.bottom * aspect;
662
662
  camera.right = camera.top * aspect;
663
663
  camera.near = 0;
664
- camera.far = extentsSize * 100;
664
+ camera.far = extentsSize * 1000;
665
665
  camera.zoom = orthogonal_camera.view_to_world_scale;
666
666
  camera.updateProjectionMatrix();
667
667
 
@@ -686,8 +686,8 @@ export class Viewer
686
686
  const camera = new PerspectiveCamera();
687
687
  camera.fov = perspective_camera.field_of_view;
688
688
  camera.aspect = aspect;
689
- camera.near = extentsSize / 100;
690
- camera.far = extentsSize * 100;
689
+ camera.near = extentsSize / 1000;
690
+ camera.far = extentsSize * 1000;
691
691
  camera.updateProjectionMatrix();
692
692
 
693
693
  camera.up.copy(getVector3FromPoint3d(perspective_camera.up_vector));
@@ -21,36 +21,40 @@
21
21
  // acknowledge and accept the above terms.
22
22
  ///////////////////////////////////////////////////////////////////////////////
23
23
 
24
- import { Sphere, Vector3 } from "three";
24
+ import { Quaternion, Sphere, Vector3 } from "three";
25
25
  import type { Viewer } from "../Viewer";
26
26
  import { zoomTo } from "./ZoomTo";
27
27
 
28
28
  export const defaultViewPositions = {
29
- front: new Vector3(0, 0, 1),
30
- back: new Vector3(0, 0, -1),
31
- left: new Vector3(-1, 0, 0),
32
- right: new Vector3(1, 0, 0),
29
+ front: new Vector3(0, 0, -1),
30
+ back: new Vector3(0, 0, 1),
31
+ left: new Vector3(1, 0, 0),
32
+ right: new Vector3(-1, 0, 0),
33
33
  bottom: new Vector3(0, -1, 0),
34
34
  top: new Vector3(0, 1, 0),
35
- ns: new Vector3(-0.5, 1.0, -0.5).normalize(),
36
- sw: new Vector3(0.5, 1.0, -0.5).normalize(),
37
- nw: new Vector3(0.5, 1.0, 0.5).normalize(),
38
- se: new Vector3(-0.5, 1.0, 0.5).normalize(),
35
+ se: new Vector3(-1, 1, -1).normalize(),
36
+ sw: new Vector3(1, 1, -1).normalize(),
37
+ ne: new Vector3(-1, 1, 1).normalize(),
38
+ nw: new Vector3(1, 1, 1).normalize(),
39
39
  };
40
40
 
41
41
  export function setDefaultViewPosition(viewer: Viewer, position: string): void {
42
- const direction = defaultViewPositions[position] || defaultViewPositions["sw"];
42
+ const extentsCenter = viewer.extents.getCenter(new Vector3());
43
+ const extentsSize = viewer.extents.getBoundingSphere(new Sphere()).radius * 2;
43
44
 
44
- const center = viewer.extents.getCenter(new Vector3());
45
- const sphere = viewer.extents.getBoundingSphere(new Sphere());
46
- const offset = direction.clone().multiplyScalar(sphere.radius);
45
+ const upY = new Vector3(0, 1, 0);
46
+ const offsetY = defaultViewPositions[position] || defaultViewPositions["sw"];
47
+
48
+ const up = new Vector3().copy(viewer.camera.up);
49
+ const quaternion = new Quaternion().setFromUnitVectors(upY, up);
50
+ const offset = new Vector3().copy(offsetY).applyQuaternion(quaternion);
47
51
 
48
52
  const camera = viewer.camera;
49
- camera.position.copy(center).add(offset);
50
- camera.lookAt(center);
53
+ camera.position.copy(offset).multiplyScalar(extentsSize).add(extentsCenter);
54
+ camera.lookAt(extentsCenter);
51
55
  camera.updateMatrixWorld();
52
56
 
53
- viewer.target.copy(center);
57
+ viewer.target.copy(extentsCenter);
54
58
 
55
59
  viewer.update();
56
60
  viewer.emit({ type: "viewposition", data: position });
@@ -27,8 +27,8 @@ import type { Viewer } from "../Viewer";
27
27
  export function zoomTo(viewer: Viewer, box: Box3): void {
28
28
  if (box.isEmpty()) return;
29
29
 
30
- const center = box.getCenter(new Vector3());
31
- const sphere = box.getBoundingSphere(new Sphere());
30
+ const boxCenter = box.getCenter(new Vector3());
31
+ const boxSize = box.getBoundingSphere(new Sphere()).radius;
32
32
 
33
33
  const rendererSize = viewer.renderer.getSize(new Vector2());
34
34
  const aspect = rendererSize.x / rendererSize.y;
@@ -36,30 +36,30 @@ export function zoomTo(viewer: Viewer, box: Box3): void {
36
36
  const camera = viewer.camera as any;
37
37
 
38
38
  if (camera.isPerspectiveCamera) {
39
- const offset = new Vector3(0, 0, 1);
40
- offset.applyQuaternion(camera.quaternion);
41
- offset.multiplyScalar(sphere.radius / Math.tan(MathUtils.DEG2RAD * camera.fov * 0.5));
39
+ const offset = new Vector3(0, 0, 1)
40
+ .applyQuaternion(camera.quaternion)
41
+ .multiplyScalar(boxSize / Math.tan(MathUtils.DEG2RAD * camera.fov * 0.5));
42
42
 
43
- camera.position.copy(center).add(offset);
43
+ camera.position.copy(offset).add(boxCenter);
44
44
  camera.updateMatrixWorld();
45
45
  }
46
46
  if (camera.isOrthographicCamera) {
47
- camera.top = sphere.radius;
48
- camera.bottom = -sphere.radius;
47
+ camera.top = boxSize;
48
+ camera.bottom = -boxSize;
49
49
  camera.left = camera.bottom * aspect;
50
50
  camera.right = camera.top * aspect;
51
51
  camera.zoom = 1;
52
52
  camera.updateProjectionMatrix();
53
53
 
54
- const offset = new Vector3(0, 0, 1);
55
- offset.applyQuaternion(camera.quaternion);
56
- offset.multiplyScalar(viewer.extents.getBoundingSphere(new Sphere()).radius * 3);
54
+ const offset = new Vector3(0, 0, 1)
55
+ .applyQuaternion(camera.quaternion)
56
+ .multiplyScalar(viewer.extents.getBoundingSphere(new Sphere()).radius * 3);
57
57
 
58
- camera.position.copy(center).add(offset);
58
+ camera.position.copy(offset).add(boxCenter);
59
59
  camera.updateMatrixWorld();
60
60
  }
61
61
 
62
- viewer.target.copy(center);
62
+ viewer.target.copy(boxCenter);
63
63
 
64
64
  viewer.update();
65
65
  viewer.emitEvent({ type: "zoom" });
@@ -21,7 +21,7 @@
21
21
  // acknowledge and accept the above terms.
22
22
  ///////////////////////////////////////////////////////////////////////////////
23
23
 
24
- import { AmbientLight, DirectionalLight, HemisphereLight, Sphere, Vector3 } from "three";
24
+ import { AmbientLight, DirectionalLight, HemisphereLight, Quaternion, Sphere, Vector3 } from "three";
25
25
 
26
26
  import { IComponent } from "@inweb/viewer-core";
27
27
  import type { Viewer } from "../Viewer";
@@ -73,13 +73,15 @@ export class LightComponent implements IComponent {
73
73
  const extentsCenter = this.viewer.extents.getCenter(new Vector3());
74
74
  const extentsSize = this.viewer.extents.getBoundingSphere(new Sphere()).radius;
75
75
 
76
- const front = new Vector3()
77
- .copy(this.viewer.camera.up)
78
- .cross(new Vector3(1, 0, 0))
79
- .negate();
76
+ const upY = new Vector3(0, 1, 0);
77
+ const frontY = new Vector3(0, 0, -1);
78
+
79
+ const up = new Vector3().copy(this.viewer.camera.up);
80
+ const quaternion = new Quaternion().setFromUnitVectors(upY, up);
81
+ const front = new Vector3().copy(frontY).applyQuaternion(quaternion).negate();
80
82
 
81
83
  this.directionalLight.position
82
- .copy(this.viewer.camera.up)
84
+ .copy(up)
83
85
  .applyAxisAngle(front, (-Math.PI * 30) / 180)
84
86
  .multiplyScalar(extentsSize * 2)
85
87
  .add(extentsCenter);
@@ -23,7 +23,7 @@ import {
23
23
  BufferAttribute,
24
24
  LineBasicMaterial,
25
25
  } from "three";
26
- import { GltfStructure, GL_CONSTANTS } from "./GltfStructure.js";
26
+ import { GL_CONSTANTS } from "./GltfStructure.js";
27
27
  import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
28
28
 
29
29
  export class DynamicGltfLoader {
@@ -94,6 +94,10 @@ export class DynamicGltfLoader {
94
94
  this.hiddenHandles = new Set();
95
95
  this.newOptimizedObjects = new Set();
96
96
  this.oldOptimizeObjects = new Set();
97
+
98
+ this.maxConcurrentChunks = 8;
99
+ this.activeChunkLoads = 0;
100
+ this.chunkQueue = [];
97
101
  }
98
102
 
99
103
  setVisibleEdges(visible) {
@@ -503,6 +507,7 @@ export class DynamicGltfLoader {
503
507
  const structureArray = Array.isArray(structures) ? structures : [structures];
504
508
 
505
509
  for (const structure of structureArray) {
510
+ await structure.initialize(this);
506
511
  this.structures.push(structure);
507
512
  }
508
513
 
@@ -894,13 +899,6 @@ export class DynamicGltfLoader {
894
899
  this.nodesToLoad = [];
895
900
  }
896
901
 
897
- async addStructure(loadController) {
898
- const structure = new GltfStructure();
899
- await structure.initialize(loadController);
900
- this.structures.push(structure);
901
- return structure;
902
- }
903
-
904
902
  removeOptimization() {
905
903
  this.originalObjects.forEach((obj) => (obj.visible = true));
906
904
 
@@ -932,7 +930,8 @@ export class DynamicGltfLoader {
932
930
  }
933
931
 
934
932
  clear() {
935
- // Clear all structures
933
+ this.chunkQueue = [];
934
+
936
935
  this.structures.forEach((structure) => {
937
936
  if (structure) {
938
937
  structure.clear();
@@ -973,7 +972,6 @@ export class DynamicGltfLoader {
973
972
  });
974
973
  this.loadedMeshes.clear();
975
974
 
976
- // Clear all structure roots and their children
977
975
  this.structureRoots.forEach((rootGroup) => {
978
976
  if (rootGroup) {
979
977
  rootGroup.traverse((child) => {
@@ -993,7 +991,6 @@ export class DynamicGltfLoader {
993
991
  });
994
992
  this.structureRoots.clear();
995
993
 
996
- // Clear all optimized objects
997
994
  this.mergedMesh.forEach((mesh) => {
998
995
  if (mesh.geometry) mesh.geometry.dispose();
999
996
  if (mesh.material) {
@@ -1746,7 +1743,6 @@ export class DynamicGltfLoader {
1746
1743
  });
1747
1744
  }
1748
1745
 
1749
- // Возвращает bounding box для конкретной структуры
1750
1746
  getStructureGeometryExtent(structureId) {
1751
1747
  const extent = new Box3();
1752
1748
  for (const [nodeId, node] of this.nodes.entries()) {
@@ -1764,4 +1760,33 @@ export class DynamicGltfLoader {
1764
1760
  }
1765
1761
  return extent;
1766
1762
  }
1763
+
1764
+ setMaxConcurrentChunks(maxChunks) {
1765
+ if (maxChunks < 1) {
1766
+ console.warn("Max concurrent chunks must be at least 1");
1767
+ return;
1768
+ }
1769
+ this.maxConcurrentChunks = maxChunks;
1770
+ }
1771
+
1772
+ waitForChunkSlot() {
1773
+ if (this.activeChunkLoads < this.maxConcurrentChunks) {
1774
+ this.activeChunkLoads++;
1775
+ return Promise.resolve();
1776
+ }
1777
+
1778
+ return new Promise((resolve) => {
1779
+ this.chunkQueue.push(resolve);
1780
+ });
1781
+ }
1782
+
1783
+ releaseChunkSlot() {
1784
+ this.activeChunkLoads--;
1785
+
1786
+ if (this.chunkQueue.length > 0) {
1787
+ const nextResolve = this.chunkQueue.shift();
1788
+ this.activeChunkLoads++;
1789
+ nextResolve();
1790
+ }
1791
+ }
1767
1792
  }
@@ -43,11 +43,12 @@ const MAX_GAP = 128 * 1024; // 128 KB
43
43
  const MAX_CHUNK = 30 * 1024 * 1024; // 100 MB
44
44
 
45
45
  export class GltfStructure {
46
- constructor(id) {
46
+ constructor(id, loadController) {
47
47
  this.id = `${id}`;
48
48
  this.json = null;
49
49
  this.baseUrl = "";
50
- this.loadController = null;
50
+ this.loadController = loadController;
51
+ this.loader = null;
51
52
  this.batchDelay = 10;
52
53
  this.maxBatchSize = 5 * 1024 * 1024;
53
54
  this.maxRangesPerRequest = 512;
@@ -59,10 +60,10 @@ export class GltfStructure {
59
60
  this.materialCache = new Map();
60
61
  }
61
62
 
62
- async initialize(loadController) {
63
- this.json = await loadController.loadJson();
64
- this.baseUrl = await loadController.baseUrl();
65
- this.loadController = loadController;
63
+ async initialize(loader) {
64
+ this.json = await this.loadController.loadJson();
65
+ this.baseUrl = await this.loadController.baseUrl();
66
+ this.loader = loader;
66
67
  }
67
68
 
68
69
  clear() {
@@ -75,10 +76,12 @@ export class GltfStructure {
75
76
  this.batchTimeout = null;
76
77
  }
77
78
 
78
- // Clear materials and textures
79
79
  this.disposeMaterials();
80
80
  this.textureCache.clear();
81
81
  this.materials.clear();
82
+
83
+ this.activeChunkLoads = 0;
84
+ this.chunkQueue = [];
82
85
  }
83
86
 
84
87
  getJson() {
@@ -157,35 +160,32 @@ export class GltfStructure {
157
160
  finalRanges.push({ start, end, requests });
158
161
  }
159
162
  }
160
- /*
161
- for (const range of finalRanges) {
162
- const length = range.end - range.start;
163
- const buffer = await this.loadController.loadBinaryData([
164
- { offset: range.start, length: length }
165
- ]);
166
- for (const req of range.requests) {
167
- const relOffset = req.offset - range.start;
168
- try {
169
- req._resolve({ buffer, relOffset, length: req.length });
170
- } catch (e) {
171
- req._reject(e);
163
+
164
+ const promises = finalRanges.map(async (range, index) => {
165
+ await this.loader.waitForChunkSlot();
166
+
167
+ try {
168
+ const length = range.end - range.start;
169
+ const buffer = await this.loadController.loadBinaryData([{ offset: range.start, length }]);
170
+
171
+ for (const req of range.requests) {
172
+ const relOffset = req.offset - range.start;
173
+ try {
174
+ req._resolve({ buffer, relOffset, length: req.length });
175
+ } catch (e) {
176
+ req._reject(e);
177
+ }
172
178
  }
173
- }
174
- }
175
- */
176
-
177
- const promises = finalRanges.map(async (range) => {
178
- const length = range.end - range.start;
179
- const buffer = await this.loadController.loadBinaryData([{ offset: range.start, length }]);
180
- for (const req of range.requests) {
181
- const relOffset = req.offset - range.start;
182
- try {
183
- req._resolve({ buffer, relOffset, length: req.length });
184
- } catch (e) {
185
- req._reject(e);
179
+ } catch (error) {
180
+ for (const req of range.requests) {
181
+ req._reject(error);
186
182
  }
183
+ console.warn(`Failed to load chunk ${index + 1}/${finalRanges.length} (${range.start}-${range.end}):`, error);
184
+ } finally {
185
+ this.loader.releaseChunkSlot();
187
186
  }
188
187
  });
188
+
189
189
  await Promise.all(promises);
190
190
 
191
191
  this.pendingRequests = [];
@@ -58,6 +58,7 @@ export class GLTFCloudDynamicLoader implements ILoader {
58
58
  this.gltfLoader = new DynamicGltfLoader(this.viewer.camera, scene, this.viewer.renderer);
59
59
  this.gltfLoader.memoryLimit = this.viewer.options.memoryLimit;
60
60
  this.gltfLoader.setVisibleEdges(this.viewer.options.edgeModel);
61
+ // this.gltfLoader.setMaxConcurrentChunks(this.viewer.options.maxConcurrentChunks);
61
62
 
62
63
  this.gltfLoader.addEventListener("databasechunk", (data) => {
63
64
  const modelImpl = new DynamicModelImpl(scene);
@@ -126,8 +127,7 @@ export class GLTFCloudDynamicLoader implements ILoader {
126
127
  baseUrl: () => Promise.resolve(`${model.httpClient.serverUrl}${model.path}/`),
127
128
  };
128
129
 
129
- const structure = new GltfStructure(model.id);
130
- await structure.initialize(loadController);
130
+ const structure = new GltfStructure(model.id, loadController);
131
131
 
132
132
  await this.gltfLoader.loadStructure(structure);
133
133
  await this.gltfLoader.loadNodes();