@jdultra/threedtiles 3.1.2 → 3.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jdultra/threedtiles",
3
- "version": "3.1.2",
3
+ "version": "3.1.3",
4
4
  "description": "An OGC 3DTiles viewer for Three.js",
5
5
  "main": "tileset.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -1,14 +1,12 @@
1
1
  import "regenerator-runtime/runtime.js";
2
2
  import * as THREE from 'three';
3
3
  import Stats from 'three/examples/jsm/libs/stats.module.js';
4
- import TilesetStats from './tileset/TilesetStats';
5
4
  import { OGC3DTile } from "./tileset/OGC3DTile";
6
5
  import { TileLoader } from "./tileset/TileLoader";
7
6
  import { MapControls, OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
8
7
 
9
8
 
10
9
  const scene = initScene();
11
- const tilesetStats = TilesetStats();
12
10
  const domContainer = initDomContainer("screen");
13
11
  const camera = initCamera();
14
12
  const ogc3DTiles = initTileset(scene);
@@ -79,14 +77,13 @@ function initTileset(scene) {
79
77
 
80
78
  const ogc3DTile = new OGC3DTile({
81
79
  url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
82
- geometricErrorMultiplier: 1,
80
+ geometricErrorMultiplier: 1.0,
83
81
  loadOutsideView: true,
84
82
  tileLoader: new TileLoader(mesh => {
85
83
  //// Insert code to be called on every newly decoded mesh e.g.:
86
84
  mesh.material.wireframe = false;
87
85
  mesh.material.side = THREE.DoubleSide;
88
- }, tilesetStats),
89
- stats: tilesetStats
86
+ }, 2000)
90
87
  });
91
88
 
92
89
 
@@ -95,7 +92,7 @@ function initTileset(scene) {
95
92
  //ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
96
93
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
97
94
  //ogc3DTile.scale.set(0.0001,0.0001,0.0001);
98
- // ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * 0.5) // Z-UP to Y-UP
95
+ //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
99
96
  // ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
100
97
  // ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
101
98
  // ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
@@ -117,7 +114,7 @@ function initTileset(scene) {
117
114
  function startInterval(){
118
115
  interval = setInterval(function () {
119
116
  ogc3DTile.update(camera);
120
- }, 25);
117
+ }, 20);
121
118
  }
122
119
  startInterval();
123
120
 
@@ -141,7 +138,6 @@ function animate() {
141
138
 
142
139
  camera.updateMatrixWorld();
143
140
  renderer.render(scene, camera);
144
- tilesetStats.update();
145
141
  stats.update();
146
142
 
147
143
  }
@@ -23,8 +23,9 @@ class OGC3DTile extends THREE.Object3D {
23
23
  * geometricErrorMultiplier: Double,
24
24
  * loadOutsideView: Boolean,
25
25
  * tileLoader : TileLoader,
26
- * stats: TilesetStats,
27
- * meshCallback: function
26
+ * meshCallback: function,
27
+ * cameraOnLoad: camera,
28
+ * parentTile: OGC3DTile
28
29
  * } properties
29
30
  */
30
31
  constructor(properties) {
@@ -42,24 +43,11 @@ class OGC3DTile extends THREE.Object3D {
42
43
  }
43
44
  // set properties general to the entire tileset
44
45
  this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
45
- if (properties.stats) {
46
- // Automatic geometric error multiplier
47
- this.stats = properties.stats;
48
-
49
- setIntervalAsync(() => {
50
- if (!!document.hidden) return;
51
- const framerate = self.stats.fps();
52
- if (framerate < 0) return;
53
- if (framerate < 58) {
54
- self.setGeometricErrorMultiplier(Math.max(0.05, self.geometricErrorMultiplier - 0.05));
55
- } else if (framerate > 58) {
56
- self.setGeometricErrorMultiplier(self.geometricErrorMultiplier + 0.05);
57
- }
58
- }, 1000);
59
- }
60
46
 
61
47
  this.meshCallback = properties.meshCallback;
62
48
  this.loadOutsideView = properties.loadOutsideView;
49
+ this.cameraOnLoad = properties.cameraOnLoad;
50
+ this.parentTile = properties.parentTile;
63
51
 
64
52
  // declare properties specific to the tile for clarity
65
53
  this.childrenTiles = [];
@@ -179,23 +167,37 @@ class OGC3DTile extends THREE.Object3D {
179
167
  });
180
168
  self.add(mesh);
181
169
  self.meshContent = mesh;
182
- })
170
+ }, !self.cameraOnLoad ? () => 0 : () => {
171
+ return self.calculateDistanceToCamera(self.cameraOnLoad);
172
+ }, () => self.getSiblings(), self.level, self.uuid);
183
173
  } else if (url.includes(".json")) {
184
- self.controller = new AbortController();
185
- fetch(url, { signal: self.controller.signal }).then(result => {
186
- if (!result.ok) {
187
- throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
188
- }
189
- result.json().then(json => {
190
- // when json content is downloaded, it is inserted into this tile's original JSON as a child
191
- // and the content object is deleted from the original JSON
192
- if (!self.json.children) self.json.children = [];
193
- json.rootPath = path.dirname(url);
194
- self.json.children.push(json);
195
- delete self.json.content;
196
- self.hasUnloadedJSONContent = false;
174
+ self.tileLoader.get(this.uuid, url, json => {
175
+ if (!!self.deleted) return;
176
+ if (!self.json.children) self.json.children = [];
177
+ json.rootPath = path.dirname(url);
178
+ self.json.children.push(json);
179
+ delete self.json.content;
180
+ self.hasUnloadedJSONContent = false;
181
+ });
182
+
183
+ /* self.controller = new AbortController();
184
+ setTimeout(() => {
185
+ fetch(url, { signal: self.controller.signal }).then(result => {
186
+ if (!result.ok) {
187
+ throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
188
+ }
189
+ result.json().then(json => {
190
+ // when json content is downloaded, it is inserted into this tile's original JSON as a child
191
+ // and the content object is deleted from the original JSON
192
+ if (!self.json.children) self.json.children = [];
193
+ json.rootPath = path.dirname(url);
194
+ self.json.children.push(json);
195
+ delete self.json.content;
196
+ self.hasUnloadedJSONContent = false;
197
+ }).catch(error => { });
197
198
  }).catch(error => { });
198
- }).catch(error => { });
199
+ }, 0); */
200
+
199
201
  }
200
202
 
201
203
  }
@@ -216,6 +218,7 @@ class OGC3DTile extends THREE.Object3D {
216
218
 
217
219
  });
218
220
  this.parent = null;
221
+ this.parentTile = null;
219
222
  this.dispatchEvent({ type: 'removed' });
220
223
  }
221
224
  disposeChildren() {
@@ -324,6 +327,7 @@ class OGC3DTile extends THREE.Object3D {
324
327
  function loadJsonChildren() {
325
328
  self.json.children.forEach(childJSON => {
326
329
  let childTile = new OGC3DTile({
330
+ parentTile: self,
327
331
  parentGeometricError: self.geometricError,
328
332
  parentBoundingVolume: self.boundingVolume,
329
333
  parentRefinement: self.refinement,
@@ -332,7 +336,8 @@ class OGC3DTile extends THREE.Object3D {
332
336
  geometricErrorMultiplier: self.geometricErrorMultiplier,
333
337
  loadOutsideView: self.loadOutsideView,
334
338
  level: self.level + 1,
335
- tileLoader: self.tileLoader
339
+ tileLoader: self.tileLoader,
340
+ cameraOnLoad: camera
336
341
  });
337
342
  self.childrenTiles.push(childTile);
338
343
  self.add(childTile);
@@ -455,9 +460,43 @@ class OGC3DTile extends THREE.Object3D {
455
460
  //throw Error("Region bounding volume not supported");
456
461
  return -1;
457
462
  }
458
-
459
463
  }
460
464
 
465
+ getSiblings() {
466
+ const self = this;
467
+ const tiles = [];
468
+ if (!self.parentTile) return tiles;
469
+ let p = self.parentTile;
470
+ while (!p.hasMeshContent && !!p.parentTile) {
471
+ p = p.parentTile;
472
+ }
473
+ p.childrenTiles.forEach(child => {
474
+ if (!!child && child != self) {
475
+ while (!child.hasMeshContent && !!child.childrenTiles[0]) {
476
+ child = child.childrenTiles[0];
477
+ }
478
+ tiles.push(child);
479
+ }
480
+ });
481
+ return tiles;
482
+ }
483
+ calculateDistanceToCamera(camera) {
484
+ if (this.boundingVolume instanceof OBB) {
485
+ // box
486
+ tempSphere.copy(this.boundingVolume.sphere);
487
+ tempSphere.applyMatrix4(this.matrixWorld);
488
+ //if (!frustum.intersectsSphere(tempSphere)) return -1;
489
+ } else if (this.boundingVolume instanceof THREE.Sphere) {
490
+ //sphere
491
+ tempSphere.copy(this.boundingVolume);
492
+ tempSphere.applyMatrix4(this.matrixWorld);
493
+ //if (!frustum.intersectsSphere(tempSphere)) return -1;
494
+ }
495
+ if (this.boundingVolume instanceof THREE.Box3) {
496
+ return -1; // region not supported
497
+ }
498
+ return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
499
+ }
461
500
  setGeometricErrorMultiplier(geometricErrorMultiplier) {
462
501
  this.geometricErrorMultiplier = geometricErrorMultiplier;
463
502
  this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
@@ -1,64 +1,142 @@
1
1
  import { LinkedHashMap } from 'js-utils-z';
2
2
  import { B3DMDecoder } from "../decoder/B3DMDecoder";
3
- import { setIntervalAsync } from 'set-interval-async/dynamic';
4
3
 
5
4
  const ready = [];
6
5
  const downloads = [];
6
+ const nextReady = [];
7
+ const nextDownloads = [];
8
+
7
9
  function scheduleDownload(f) {
8
10
  downloads.unshift(f);
11
+ //setTimeout(()=>download(),0);
9
12
  }
10
13
  function download() {
11
- if(downloads.length <=0) return;
12
- const nextDownload = downloads.shift();
14
+ if (nextDownloads.length == 0) {
15
+ getNextDownloads();
16
+ if (nextDownloads.length == 0) return;
17
+ }
18
+ const nextDownload = nextDownloads.shift();
13
19
  if (!!nextDownload && nextDownload.shouldDoDownload()) {
14
20
  nextDownload.doDownload();
15
21
  }
16
22
  }
17
- function meshReceived(cache, register, key) {
18
- ready.unshift([cache, register, key]);
23
+ function meshReceived(cache, register, key, distanceFunction, getSiblings, level, uuid) {
24
+ ready.unshift([cache, register, key, distanceFunction, getSiblings, level, uuid]);
25
+ //setTimeout(()=>loadBatch(),1);
19
26
  }
20
27
  function loadBatch() {
21
- for (let i = 0; i < 1; i++) {
22
- const data = ready.shift();
23
- if (!data) return;
24
- const cache = data[0];
25
- const register = data[1];
26
- const key = data[2];
27
- const mesh = cache.get(key);
28
- if (!!mesh) {
29
- Object.keys(register[key]).forEach(tile => {
30
- const callback = register[key][tile];
31
- if (!!callback) {
32
- callback(mesh);
33
- register[key][tile] = null;
34
- }
35
- });
28
+ if (nextReady.length == 0) {
29
+ getNextReady();
30
+ if (nextReady.length == 0) return;
31
+ }
32
+ const data = nextReady.shift();
33
+ if (!data) return;
34
+ const cache = data[0];
35
+ const register = data[1];
36
+ const key = data[2];
37
+ const mesh = cache.get(key);
38
+ if (!!mesh && !!register[key]) {
39
+ Object.keys(register[key]).forEach(tile => {
40
+ const callback = register[key][tile];
41
+ if (!!callback) {
42
+ callback(mesh);
43
+ register[key][tile] = null;
44
+ }
45
+ });
46
+ }
47
+ }
48
+
49
+ function getNextDownloads() {
50
+ let smallestLevel = Number.MAX_VALUE;
51
+ let smallestDistance = Number.MAX_VALUE;
52
+ let closest = -1;
53
+ for (let i = downloads.length - 1; i >= 0; i--) {
54
+ if (!downloads[i].shouldDoDownload()) {
55
+ downloads.splice(i, 1);
56
+ continue;
57
+ }
58
+ if(!downloads[i].distanceFunction){ // if no distance function, must be a json, give absolute priority!
59
+ nextDownloads.push(downloads.splice(i, 1)[0]);
60
+ }
61
+ }
62
+ if(nextDownloads.length>0) return;
63
+ for (let i = downloads.length - 1; i >= 0; i--) {
64
+ const dist = downloads[i].distanceFunction();
65
+ if (dist < smallestDistance) {
66
+ smallestDistance = dist;
67
+ closest = i;
68
+ } else if (dist == smallestDistance && downloads[i].level < smallestLevel) {
69
+ smallestLevel = downloads[i].level;
70
+ closest = i
71
+ }
72
+ }
73
+ if (closest >= 0) {
74
+ const closestItem = downloads.splice(closest, 1).pop();
75
+ nextDownloads.push(closestItem);
76
+ const siblings = closestItem.getSiblings();
77
+ for (let i = downloads.length - 1; i >= 0; i--) {
78
+ if (siblings.includes(downloads[i].uuid)) {
79
+ nextDownloads.push(downloads.splice(i, 1).pop());
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ function getNextReady() {
86
+ let smallestLevel = Number.MAX_VALUE;
87
+ let smallestDistance = Number.MAX_VALUE;
88
+ let closest = -1;
89
+ for (let i = ready.length - 1; i >= 0; i--) {
90
+
91
+ if(!ready[i][3]){// if no distance function, must be a json, give absolute priority!
92
+ nextReady.push(ready.splice(i,1)[0]);
93
+ }
94
+ }
95
+ if(nextReady.length>0) return;
96
+ for (let i = ready.length - 1; i >= 0; i--) {
97
+ const dist = ready[i][3]();
98
+ if (dist < smallestDistance) {
99
+ smallestDistance = dist;
100
+ smallestLevel = ready[i][5]
101
+ closest = i
102
+ } else if (dist == smallestDistance && ready[i][5] < smallestLevel) {
103
+ smallestLevel = ready[i][5]
104
+ closest = i
105
+ }
106
+ }
107
+ if (closest >= 0) {
108
+ const closestItem = ready.splice(closest, 1).pop();
109
+ nextReady.push(closestItem);
110
+ const siblings = closestItem[4]();
111
+ for (let i = ready.length - 1; i >= 0; i--) {
112
+ if (siblings.includes(ready[i][6])) {
113
+ nextready.push(ready.splice(i, 1).pop());
114
+ }
36
115
  }
37
116
  }
38
117
  }
39
- setIntervalAsync(() => {
40
- loadBatch();
41
- }, 10)
42
- setIntervalAsync(() => {
43
- download();
44
- }, 10)
118
+ setInterval(() => {
119
+ download()
120
+ }, 1)
121
+ setInterval(() => {
122
+ loadBatch()
123
+ }, 1)
45
124
 
46
125
  class TileLoader {
47
- constructor(meshCallback, stats) {
126
+ constructor(meshCallback, maxCachedItems) {
48
127
  this.meshCallback = meshCallback;
49
128
  this.cache = new LinkedHashMap();
50
- this.maxSize = 1000;
51
- this.stats = stats;
129
+ this.maxCachedItems = !!maxCachedItems ? maxCachedItems : 1000;
52
130
  this.register = {};
53
131
  }
54
132
 
55
- get(tileIdentifier, path, callback) {
133
+ get(tileIdentifier, path, callback, distanceFunction, getSiblings, level, uuid) {
56
134
  const self = this;
57
135
  const key = simplifyPath(path);
58
136
 
59
137
 
60
- if (!path.includes(".b3dm")) {
61
- console.error("the 3DTiles cache can only be used to load B3DM data");
138
+ if (!path.includes(".b3dm") && !path.includes(".json")) {
139
+ console.error("the 3DTiles cache can only be used to load B3DM and json data");
62
140
  return;
63
141
  }
64
142
  if (!self.register[key]) {
@@ -71,13 +149,11 @@ class TileLoader {
71
149
 
72
150
  const cachedObject = self.cache.get(key);
73
151
  if (!!cachedObject) {
74
- meshReceived(self.cache, self.register, key);
152
+ meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
75
153
  } else if (Object.keys(self.register[key]).length == 1) {
76
- scheduleDownload({
77
- "shouldDoDownload":()=>{
78
- return Object.keys(self.register[key]).length > 0;
79
- },
80
- "doDownload": () => {
154
+ let downloadFunction;
155
+ if (path.includes(".b3dm")) {
156
+ downloadFunction = () => {
81
157
  fetch(path).then(result => {
82
158
  if (!result.ok) {
83
159
  console.error("could not load tile with path : " + path)
@@ -86,25 +162,40 @@ class TileLoader {
86
162
  result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
87
163
  self.cache.put(key, mesh);
88
164
  self.checkSize();
89
- meshReceived(self.cache, self.register, key);
165
+ meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
90
166
  });
91
167
 
92
168
  });
93
169
  }
170
+ }else if (path.includes(".json")) {
171
+ downloadFunction = () => {
172
+ fetch(path).then(result => {
173
+ if (!result.ok) {
174
+ console.error("could not load tile with path : " + path)
175
+ throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
176
+ }
177
+ result.json().then(json => {
178
+ self.cache.put(key, json);
179
+ self.checkSize();
180
+ meshReceived(self.cache, self.register, key);
181
+ });
182
+ });
183
+ }
184
+ }
185
+ scheduleDownload({
186
+ "shouldDoDownload": () => {
187
+ return !!self.register[key] && Object.keys(self.register[key]).length > 0;
188
+ },
189
+ "doDownload": downloadFunction,
190
+ "distanceFunction": distanceFunction,
191
+ "getSiblings": getSiblings,
192
+ "level": level,
193
+ "uuid": uuid
94
194
  })
95
195
  }
96
196
  }
97
197
 
98
- meshReceived(key, mesh) {
99
- const self = this;
100
- Object.keys(self.register[key]).forEach(tile => {
101
- const callback = self.register[key][tile];
102
- if (!!callback) {
103
- callback(mesh);
104
- self.register[key][tile] = null;
105
- }
106
- });
107
- }
198
+
108
199
 
109
200
  invalidate(path, tileIdentifier) {
110
201
  const key = simplifyPath(path);
@@ -113,46 +204,43 @@ class TileLoader {
113
204
 
114
205
  checkSize() {
115
206
  const self = this;
116
-
207
+
117
208
  let i = 0;
118
- function memOverflowCheck(){
119
- if(!!self.stats && self.stats.memory()>0){
120
- if(self.stats.memory()/self.stats.maxMemory()<0.25){
121
- return false;
122
- }
123
- return true;
124
- }
125
- return self.cache.size() > self.maxSize;
126
- }
127
- while (memOverflowCheck() && i < self.cache.size()) {
209
+
210
+ while (self.cache.size() > self.maxCachedItems && i < self.cache.size()) {
211
+ console.log(self.cache.size())
128
212
  i++;
129
213
  const entry = self.cache.head();
130
- if (Object.keys(self.register[entry.key]).length > 0) {
131
- self.cache.remove(entry.key);
132
- self.cache.put(entry.key, entry.value);
133
- } else {
134
- self.cache.remove(entry.key);
135
- delete self.register[entry.key];
136
- entry.value.traverse((o) => {
137
-
138
- if (o.material) {
139
- // dispose materials
140
- if (o.material.length) {
141
- for (let i = 0; i < o.material.length; ++i) {
142
- o.material[i].dispose();
214
+ const reg = self.register[entry.key];
215
+ if (!!reg) {
216
+ if (Object.keys(reg).length > 0) {
217
+ self.cache.remove(entry.key);
218
+ self.cache.put(entry.key, entry.value);
219
+ } else {
220
+ self.cache.remove(entry.key);
221
+ delete self.register[entry.key];
222
+ entry.value.traverse((o) => {
223
+
224
+ if (o.material) {
225
+ // dispose materials
226
+ if (o.material.length) {
227
+ for (let i = 0; i < o.material.length; ++i) {
228
+ o.material[i].dispose();
229
+ }
230
+ }
231
+ else {
232
+ o.material.dispose()
143
233
  }
144
234
  }
145
- else {
146
- o.material.dispose()
147
- }
148
- }
149
- if (o.geometry) {
150
- // dispose geometry
151
- o.geometry.dispose();
235
+ if (o.geometry) {
236
+ // dispose geometry
237
+ o.geometry.dispose();
152
238
 
153
- }
154
- });
239
+ }
240
+ });
241
+ }
155
242
  }
243
+
156
244
  }
157
245
  }
158
246
  }
@@ -1,65 +0,0 @@
1
- var TilesetStats = function () {
2
-
3
-
4
- var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;
5
-
6
- var fps = -1;
7
-
8
- if ( self.performance && self.performance.memory ) {
9
-
10
- var mem = -1;
11
- var maxMem = -1;
12
-
13
- }
14
-
15
-
16
- return {
17
-
18
- begin: function () {
19
-
20
- beginTime = ( performance || Date ).now();
21
-
22
- },
23
-
24
- end: function () {
25
-
26
- frames ++;
27
-
28
- var time = ( performance || Date ).now();
29
-
30
- if ( time >= prevTime + 1000 ) {
31
-
32
- fps = ( frames * 1000 ) / ( time - prevTime );
33
-
34
- prevTime = time;
35
- frames = 0;
36
-
37
- if ( !!mem ) {
38
-
39
- var memory = performance.memory;
40
- mem = memory.usedJSHeapSize;
41
- maxMem = memory.jsHeapSizeLimit;
42
-
43
- }
44
-
45
- }
46
-
47
- return time;
48
-
49
- },
50
-
51
- update: function () {
52
-
53
- beginTime = this.end();
54
-
55
- },
56
-
57
- fps: ()=>fps,
58
- memory: ()=>mem,
59
- maxMemory: ()=>maxMem
60
-
61
- };
62
-
63
- };
64
-
65
- export default TilesetStats;