@jdultra/threedtiles 7.0.2 → 8.0.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.
@@ -4,12 +4,14 @@ import { TileLoader } from "./TileLoader";
4
4
  import { v4 as uuidv4 } from "uuid";
5
5
  import * as path from "path-browserify"
6
6
  import { clamp } from "three/src/math/MathUtils";
7
+ //import { Octree } from 'three/addons/math/Octree.js';
8
+ //import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
7
9
 
8
10
  const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 1);
9
11
  const tempVec1 = new THREE.Vector3(0, 0, 0);
10
12
  const tempVec2 = new THREE.Vector3(0, 0, 0);
11
13
  const upVector = new THREE.Vector3(0, 1, 0);
12
- const rendererSize = new THREE.Vector2(1000,1000);
14
+ const rendererSize = new THREE.Vector2(1000, 1000);
13
15
  const tempQuaternion = new THREE.Quaternion();
14
16
 
15
17
 
@@ -46,16 +48,16 @@ class OGC3DTile extends THREE.Object3D {
46
48
  if (!!properties.tileLoader) {
47
49
  this.tileLoader = properties.tileLoader;
48
50
  } else {
49
- this.tileLoader = new TileLoader(
50
- 200,
51
- !properties.meshCallback ? mesh => {
52
- mesh.material.wireframe = false;
53
- mesh.material.side = THREE.DoubleSide;
54
- } : properties.meshCallback,
55
- !properties.pointsCallback ? points => {
56
- points.material.size = 0.1;
57
- points.material.sizeAttenuation = true;
58
- } : properties.pointsCallback);
51
+ const tileLoaderOptions = {};
52
+ tileLoaderOptions.meshCallback = !properties.meshCallback ? mesh => {
53
+ mesh.material.wireframe = false;
54
+ mesh.material.side = THREE.DoubleSide;
55
+ } : properties.meshCallback;
56
+ tileLoaderOptions.pointsCallback = !properties.pointsCallback ? points => {
57
+ points.material.size = 0.1;
58
+ points.material.sizeAttenuation = true;
59
+ } : properties.pointsCallback;
60
+ this.tileLoader = new TileLoader(tileLoaderOptions);
59
61
  }
60
62
  // set properties general to the entire tileset
61
63
  this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
@@ -93,6 +95,7 @@ class OGC3DTile extends THREE.Object3D {
93
95
  this.centerModel = properties.centerModel;
94
96
  this.abortController = new AbortController();
95
97
  this.layers.disable(0);
98
+ //this.octree = new Octree();
96
99
 
97
100
  if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
98
101
  self.setup(properties);
@@ -123,11 +126,11 @@ class OGC3DTile extends THREE.Object3D {
123
126
  (this.json.boundingVolume.region[1] + this.json.boundingVolume.region[3]) * 0.5,
124
127
  (this.json.boundingVolume.region[4] + this.json.boundingVolume.region[5]) * 0.5,
125
128
  tempVec1);
126
-
129
+
127
130
  tempQuaternion.setFromUnitVectors(tempVec1.normalize(), upVector.normalize());
128
131
  self.applyQuaternion(tempQuaternion);
129
132
  }
130
-
133
+
131
134
  self.translateX(-tempSphere.center.x * self.scale.x);
132
135
  self.translateY(-tempSphere.center.y * self.scale.y);
133
136
  self.translateZ(-tempSphere.center.z * self.scale.z);
@@ -169,7 +172,7 @@ class OGC3DTile extends THREE.Object3D {
169
172
  mat.elements = this.json.transform;
170
173
  this.applyMatrix4(mat);
171
174
  }
172
-
175
+
173
176
  // decode volume
174
177
  if (!!this.json.boundingVolume) {
175
178
  if (!!this.json.boundingVolume.box) {
@@ -190,7 +193,7 @@ class OGC3DTile extends THREE.Object3D {
190
193
  this.boundingVolume = properties.parentBoundingVolume;
191
194
  }
192
195
 
193
-
196
+
194
197
 
195
198
  if (!!this.json.content) { //if there is a content, json or otherwise, schedule it to be loaded
196
199
  if (!!this.json.content.uri && this.json.content.uri.includes("json")) {
@@ -215,7 +218,7 @@ class OGC3DTile extends THREE.Object3D {
215
218
 
216
219
  return absoluteURL || absolutePath;
217
220
  }
218
-
221
+
219
222
  load() {
220
223
  var self = this;
221
224
  if (self.deleted) return;
@@ -238,12 +241,12 @@ class OGC3DTile extends THREE.Object3D {
238
241
  if (!!url) {
239
242
  if (url.includes(".b3dm") || url.includes(".glb") || url.includes(".gltf")) {
240
243
  self.contentURL = url;
241
-
244
+
242
245
  self.tileLoader.get(self.abortController, this.uuid, url, mesh => {
243
246
  if (!!self.deleted) return;
244
247
  mesh.traverse((o) => {
245
248
  if (o.isMesh) {
246
-
249
+
247
250
  o.layers.disable(0);
248
251
  if (self.occlusionCullingService) {
249
252
  const position = o.geometry.attributes.position;
@@ -259,17 +262,19 @@ class OGC3DTile extends THREE.Object3D {
259
262
  //o.material.visible = false;
260
263
  }
261
264
  });
262
-
265
+ //let s = Date.now();
266
+ //self.octree.fromGraphNode( mesh );
267
+ //console.log(Date.now()-s);
263
268
  self.add(mesh);
264
269
  self.updateWorldMatrix(false, true);
265
270
  // mesh.layers.disable(0);
266
271
  self.meshContent = mesh;
267
272
  }, !self.cameraOnLoad ? () => 0 : () => {
268
273
  return self.calculateDistanceToCamera(self.cameraOnLoad);
269
- }, () => self.getSiblings(),
270
- self.level,
271
- !!self.json.boundingVolume.region,
272
- self.geometricError
274
+ }, () => self.getSiblings(),
275
+ self.level,
276
+ !!self.json.boundingVolume.region,
277
+ self.geometricError
273
278
  );
274
279
  } else if (url.includes(".json")) {
275
280
  self.tileLoader.get(self.abortController, this.uuid, url, json => {
@@ -549,7 +554,7 @@ class OGC3DTile extends THREE.Object3D {
549
554
  return;
550
555
  }
551
556
  self.materialVisibility = visibility
552
-
557
+
553
558
  self.meshDisplayed = true;
554
559
  if (!!self.meshContent.traverse) {
555
560
  self.meshContent.traverse(function (element) {
@@ -597,7 +602,7 @@ class OGC3DTile extends THREE.Object3D {
597
602
  return 0;
598
603
  }
599
604
  const scale = this.matrixWorld.getMaxScaleOnAxis();
600
- if(!!this.renderer){
605
+ if (!!this.renderer) {
601
606
  this.renderer.getDrawingBufferSize(rendererSize);
602
607
  }
603
608
  let s = rendererSize.y;
@@ -5,6 +5,7 @@ import { initial } from 'lodash';
5
5
  import * as THREE from 'three';
6
6
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
7
7
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
8
+ import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader";
8
9
 
9
10
  let concurentDownloads = 0;
10
11
  const zUpToYUpMatrix = new THREE.Matrix4();
@@ -12,17 +13,49 @@ zUpToYUpMatrix.set(1, 0, 0, 0,
12
13
  0, 0, -1, 0,
13
14
  0, 1, 0, 0,
14
15
  0, 0, 0, 1);
15
- const gltfLoader = new GLTFLoader();
16
- const dracoLoader = new DRACOLoader();
17
- dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.4.3/');
18
- gltfLoader.setDRACOLoader(dracoLoader);
19
16
 
17
+
18
+
19
+
20
+ /**
21
+ * A Tile loader that manages caching and load order.
22
+ * The cache is an LRU cache and is defined by the number of items it can hold.
23
+ * The actual number of cached items might grow beyond max if all items are in use.
24
+ *
25
+ * The load order is designed for optimal perceived loading speed (nearby tiles are refined first).
26
+ *
27
+ * @param {Object} [options] - Optional configuration object.
28
+ * @param {number} [options.maxCachedItems=100] - the cache size.
29
+ * @param {function} [options.meshCallback] - A callback to call on newly decoded meshes.
30
+ * @param {function} [options.pointsCallback] - A callback to call on newly decoded points.
31
+ * @param {renderer} [options.renderer] - The renderer, this is required for KTX2 support.
32
+ */
20
33
  class TileLoader {
21
- constructor(maxCachedItems, meshCallback, pointsCallback) {
22
- this.meshCallback = meshCallback;
23
- this.pointsCallback = pointsCallback;
34
+ constructor(options) {
35
+ this.maxCachedItems = 100;
36
+ if (!!options) {
37
+ this.meshCallback = options.meshCallback;
38
+ this.pointsCallback = options.pointsCallback;
39
+ if (options.maxCachedItems) this.maxCachedItems = options.maxCachedItems;
40
+
41
+ }
42
+
43
+ this.gltfLoader = new GLTFLoader();
44
+ const dracoLoader = new DRACOLoader();
45
+ dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.4.3/');
46
+ this.gltfLoader.setDRACOLoader(dracoLoader);
47
+
48
+ if(!!options && !!options.renderer){
49
+ const ktx2Loader = new KTX2Loader();
50
+ ktx2Loader.setTranscoderPath('https://storage.googleapis.com/ogc-3d-tiles/basis/').detectSupport(options.renderer);
51
+ this.gltfLoader.setKTX2Loader(ktx2Loader);
52
+
53
+ this.b3dmDecoder=new B3DMDecoder(options.renderer);
54
+ }else{
55
+ this.b3dmDecoder=new B3DMDecoder(null);
56
+ }
57
+
24
58
  this.cache = new LinkedHashMap();
25
- this.maxCachedItems = !!maxCachedItems ? maxCachedItems : 100;
26
59
  this.register = {};
27
60
 
28
61
 
@@ -33,6 +66,7 @@ class TileLoader {
33
66
  this.init();
34
67
  }
35
68
 
69
+
36
70
  init() {
37
71
 
38
72
  const self = this;
@@ -195,7 +229,7 @@ class TileLoader {
195
229
 
196
230
  })
197
231
  .then(resultArrayBuffer => {
198
- return B3DMDecoder.parseB3DM(resultArrayBuffer, self.meshCallback, geometricError, zUpToYUp);
232
+ return this.b3dmDecoder.parseB3DM(resultArrayBuffer, self.meshCallback, geometricError, zUpToYUp);
199
233
  })
200
234
  .then(mesh => {
201
235
  self.cache.put(key, mesh);
@@ -207,7 +241,7 @@ class TileLoader {
207
241
  } else if (path.includes(".glb") || path.includes(".gltf")) {
208
242
  downloadFunction = () => {
209
243
  concurentDownloads++;
210
- gltfLoader.load(path, gltf => {
244
+ this.gltfLoader.load(path, gltf => {
211
245
  gltf.scene.traverse((o) => {
212
246
  o.geometricError = geometricError;
213
247
  if (o.isMesh) {
@@ -367,7 +367,7 @@ class InstancedTile extends THREE.Object3D {
367
367
  }
368
368
  if (metric >= self.master.geometricErrorMultiplier * self.geometricError) {
369
369
  self.disposeChildren();
370
- updateNodeVisibility();
370
+ updateNodeVisibility(metric);
371
371
  return;
372
372
  }
373
373
 
@@ -6,25 +6,58 @@ import { MeshTile } from './MeshTile';
6
6
  import { JsonTile } from './JsonTile';
7
7
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
8
8
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
9
-
9
+ import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader";
10
10
 
11
11
  let concurentDownloads = 0;
12
- const gltfLoader = new GLTFLoader();
13
- const dracoLoader = new DRACOLoader();
14
- dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.4.3/');
15
- gltfLoader.setDRACOLoader(dracoLoader);
12
+
16
13
  const zUpToYUpMatrix = new THREE.Matrix4();
17
14
  zUpToYUpMatrix.set(1, 0, 0, 0,
18
15
  0, 0, -1, 0,
19
16
  0, 1, 0, 0,
20
17
  0, 0, 0, 1);
21
18
 
19
+ /**
20
+ * A Tile loader that manages caching and load order for instanced tiles.
21
+ * The cache is an LRU cache and is defined by the number of items it can hold.
22
+ * The actual number of cached items might grow beyond max if all items are in use.
23
+ *
24
+ * The load order is designed for optimal perceived loading speed (nearby tiles are refined first).
25
+ *
26
+ * @param {scene} [scene] - a threejs scene.
27
+ * @param {Object} [options] - Optional configuration object.
28
+ * @param {number} [options.maxCachedItems=100] - the cache size.
29
+ * @param {number} [options.maxInstances=1] - the cache size.
30
+ * @param {function} [options.meshCallback] - A callback to call on newly decoded meshes.
31
+ * @param {function} [options.pointsCallback] - A callback to call on newly decoded points.
32
+ * @param {renderer} [options.renderer] - The renderer, this is required for KTX2 support.
33
+ */
22
34
  class InstancedTileLoader {
23
- constructor(scene, maxCachedItems, maxInstances, meshCallback) {
24
- this.meshCallback = meshCallback;
25
- this.maxInstances = maxInstances;
35
+ constructor(scene, options) {
36
+ this.maxCachedItems = 100;
37
+ this.maxInstances = 1;
38
+ if (!!options) {
39
+ this.meshCallback = options.meshCallback;
40
+ this.pointsCallback = options.pointsCallback;
41
+ if (options.maxCachedItems) this.maxCachedItems = options.maxCachedItems;
42
+ if (options.maxInstances) this.maxInstances = options.maxInstances;
43
+
44
+ }
45
+ this.gltfLoader = new GLTFLoader();
46
+ const dracoLoader = new DRACOLoader();
47
+ dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.4.3/');
48
+ this.gltfLoader.setDRACOLoader(dracoLoader);
49
+
50
+ if(!!options && !!options.renderer){
51
+ const ktx2Loader = new KTX2Loader();
52
+ ktx2Loader.setTranscoderPath('https://storage.googleapis.com/ogc-3d-tiles/basis/').detectSupport(options.renderer);
53
+ this.gltfLoader.setKTX2Loader(ktx2Loader);
54
+
55
+ this.b3dmDecoder=new B3DMDecoder(options.renderer);
56
+ }else{
57
+ this.b3dmDecoder=new B3DMDecoder(null);
58
+ }
59
+
26
60
  this.cache = new LinkedHashMap();
27
- this.maxCachedItems = !!maxCachedItems ? maxCachedItems : 100;
28
61
  this.scene = scene;
29
62
 
30
63
  this.ready = [];
@@ -34,16 +67,17 @@ class InstancedTileLoader {
34
67
  this.init();
35
68
  }
36
69
 
37
- update(){
70
+
71
+ update() {
38
72
  const self = this;
39
-
40
- self.cache._data.forEach(v=>{
73
+
74
+ self.cache._data.forEach(v => {
41
75
  v.update();
42
76
  })
43
-
77
+
44
78
  }
45
- init(){
46
-
79
+ init() {
80
+
47
81
  const self = this;
48
82
  setIntervalAsync(() => {
49
83
  self.download();
@@ -54,7 +88,7 @@ class InstancedTileLoader {
54
88
  do {
55
89
  loaded = self.loadBatch();
56
90
  } while (loaded > 0 && (Date.now() - start) <= 0)
57
-
91
+
58
92
  }, 10);
59
93
  }
60
94
 
@@ -69,8 +103,8 @@ class InstancedTileLoader {
69
103
  if (!!nextDownload && nextDownload.shouldDoDownload()) {
70
104
  //nextDownload.doDownload();
71
105
  concurentDownloads++;
72
- if(nextDownload.path.includes(".b3dm")){
73
- fetch(nextDownload.path, {signal: nextDownload.abortController.signal}).then(result => {
106
+ if (nextDownload.path.includes(".b3dm")) {
107
+ fetch(nextDownload.path, { signal: nextDownload.abortController.signal }).then(result => {
74
108
  concurentDownloads--;
75
109
  if (!result.ok) {
76
110
  console.error("could not load tile with path : " + path)
@@ -79,17 +113,17 @@ class InstancedTileLoader {
79
113
  return result.arrayBuffer();
80
114
 
81
115
  })
82
- .then(resultArrayBuffer=>{
83
- return B3DMDecoder.parseB3DMInstanced(resultArrayBuffer, self.meshCallback, self.maxInstances, nextDownload.zUpToYUp);
84
- })
85
- .then(mesh=>{
86
- nextDownload.tile.setObject(mesh);
87
- self.ready.unshift(nextDownload);
88
-
89
- })
90
- .catch(e=>console.error(e));
91
- }if(nextDownload.path.includes(".glb") || (nextDownload.path.includes(".gltf"))){
92
- gltfLoader.load(nextDownload.path, gltf => {
116
+ .then(resultArrayBuffer => {
117
+ return this.b3dmDecoder.parseB3DMInstanced(resultArrayBuffer, self.meshCallback, self.maxInstances, nextDownload.zUpToYUp);
118
+ })
119
+ .then(mesh => {
120
+ nextDownload.tile.setObject(mesh);
121
+ self.ready.unshift(nextDownload);
122
+
123
+ })
124
+ .catch(e => console.error(e));
125
+ } if (nextDownload.path.includes(".glb") || (nextDownload.path.includes(".gltf"))) {
126
+ this.gltfLoader.load(nextDownload.path, gltf => {
93
127
  gltf.scene.traverse((o) => {
94
128
  o.geometricError = nextDownload.geometricError;
95
129
  if (o.isMesh) {
@@ -112,34 +146,34 @@ class InstancedTileLoader {
112
146
  instancedMesh = new THREE.InstancedMesh(child.geometry, child.material, self.maxInstances);
113
147
  instancedMesh.baseMatrix = child.matrixWorld;
114
148
  }
115
-
149
+
116
150
  });
117
151
  self.ready.unshift(nextDownload);
118
- if(!instancedMesh){
119
- gltf.scene.traverse(c=>{
120
- if(c.dispose) c.dispose();
121
- if(c.material) c.material.dispose();
152
+ if (!instancedMesh) {
153
+ gltf.scene.traverse(c => {
154
+ if (c.dispose) c.dispose();
155
+ if (c.material) c.material.dispose();
122
156
  });
123
- }else{
157
+ } else {
124
158
  nextDownload.tile.setObject(instancedMesh);
125
159
  }
126
160
  });
127
-
128
- }else if(nextDownload.path.includes(".json")){
161
+
162
+ } else if (nextDownload.path.includes(".json")) {
129
163
  concurentDownloads++;
130
- fetch(nextDownload.path, {signal: nextDownload.abortController.signal}).then(result => {
164
+ fetch(nextDownload.path, { signal: nextDownload.abortController.signal }).then(result => {
131
165
  concurentDownloads--;
132
166
  if (!result.ok) {
133
167
  console.error("could not load tile with path : " + path)
134
168
  throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
135
169
  }
136
170
  return result.json();
137
-
171
+
138
172
  }).then(json => {
139
173
  nextDownload.tile.setObject(json, nextDownload.path);
140
174
  self.ready.unshift(nextDownload);
141
175
  })
142
- .catch(e=>console.error(e))
176
+ .catch(e => console.error(e))
143
177
  }
144
178
  }
145
179
  }
@@ -153,8 +187,8 @@ class InstancedTileLoader {
153
187
  }
154
188
  const download = this.nextReady.shift();
155
189
  if (!download) return 0;
156
-
157
- if(!!download.tile.addToScene)download.tile.addToScene();
190
+
191
+ if (!!download.tile.addToScene) download.tile.addToScene();
158
192
  return 1;
159
193
  }
160
194
 
@@ -162,7 +196,7 @@ class InstancedTileLoader {
162
196
  let smallestDistance = Number.MAX_VALUE;
163
197
  let closest = -1;
164
198
  for (let i = this.ready.length - 1; i >= 0; i--) {
165
-
199
+
166
200
  if (!this.ready[i].distanceFunction) {// if no distance function, must be a json, give absolute priority!
167
201
  this.nextReady.push(this.ready.splice(i, 1)[0]);
168
202
  }
@@ -205,7 +239,7 @@ class InstancedTileLoader {
205
239
  if (path.includes(".b3dm") || path.includes(".glb") || path.includes(".gltf")) {
206
240
  const tile = new MeshTile(self.scene);
207
241
  tile.addInstance(instancedOGC3DTile);
208
-
242
+
209
243
  self.cache.put(key, tile);
210
244
 
211
245
  const realAbortController = new AbortController();
@@ -229,7 +263,7 @@ class InstancedTileLoader {
229
263
  return true;
230
264
  },
231
265
  })
232
- }else if(path.includes(".json")){
266
+ } else if (path.includes(".json")) {
233
267
  const tile = new JsonTile();
234
268
  tile.addInstance(instancedOGC3DTile);
235
269
  self.cache.put(key, tile);
@@ -258,7 +292,7 @@ class InstancedTileLoader {
258
292
  }
259
293
 
260
294
 
261
-
295
+
262
296
  getNextDownloads() {
263
297
  let smallestDistance = Number.MAX_VALUE;
264
298
  let closest = -1;
@@ -267,7 +301,7 @@ class InstancedTileLoader {
267
301
  if (!download.shouldDoDownload()) {
268
302
  this.downloads.splice(i, 1);
269
303
  continue;
270
- }
304
+ }
271
305
  if (!download.distanceFunction) { // if no distance function, must be a json, give absolute priority!
272
306
  this.nextDownloads.push(this.downloads.splice(i, 1)[0]);
273
307
  }
@@ -275,7 +309,7 @@ class InstancedTileLoader {
275
309
  if (this.nextDownloads.length > 0) return;
276
310
  for (let i = this.downloads.length - 1; i >= 0; i--) {
277
311
  const download = this.downloads[i];
278
- const dist = download.distanceFunction()*download.level;
312
+ const dist = download.distanceFunction() * download.level;
279
313
  if (dist < smallestDistance) {
280
314
  smallestDistance = dist;
281
315
  closest = i;
@@ -301,12 +335,12 @@ class InstancedTileLoader {
301
335
  while (self.cache.size() > self.maxCachedItems && i < self.cache.size()) {
302
336
  i++;
303
337
  const entry = self.cache.head();
304
- if(entry.value.getCount()>0){
338
+ if (entry.value.getCount() > 0) {
305
339
  self.cache.remove(entry.key);
306
340
  self.cache.put(entry.key, entry.value);
307
- }else{
341
+ } else {
308
342
  self.cache.remove(entry.key);
309
- if(entry.value.instancedMesh){
343
+ if (entry.value.instancedMesh) {
310
344
  entry.value.instancedMesh.traverse((o) => {
311
345
  if (o.material) {
312
346
  // dispose materials