@jdultra/threedtiles 3.1.2 → 3.1.5

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/README.md CHANGED
@@ -4,8 +4,6 @@
4
4
 
5
5
  demo : https://ebeaufay.github.io/ThreedTilesViewer.github.io/
6
6
 
7
- Currently, the library is limmited to B3DM files.
8
-
9
7
  Adding a tileset to a scene is as easy as :
10
8
 
11
9
  ```
@@ -27,6 +25,8 @@ setInterval(function () {
27
25
  }, 200);
28
26
  ```
29
27
 
28
+ Currently, the library is limmited to B3DM files.
29
+
30
30
  ## Features
31
31
 
32
32
  - Handles nested tileset.json files which are loaded on the fly (a tileset.json may point to another tileset.json file as its child).
@@ -34,8 +34,7 @@ setInterval(function () {
34
34
  - callback on loaded geometry to assign a custom material or use the meshes for computations.
35
35
  - Optionally load low detail tiles outside of view frustum for correct shadows and basic mesh present when the camera moves quickly.
36
36
  - Share a cache between tileset instances
37
- - Automatically tune the geometric error multiplier to 60 FPS
38
- - Automatic scaling of the cache
37
+ - Optimal tile load order
39
38
 
40
39
  ### geometric Error Multiplier
41
40
  The geometric error multiplier allows you to multiply the geometric error by a factor.
@@ -53,27 +52,6 @@ const ogc3DTile = new OGC3DTile({
53
52
  A lower value will result in lower detail tiles being loaded and a higher value results in higher detail tiles being loaded.
54
53
  A value of 1.0 is the default.
55
54
 
56
- #### Automatic Geometric error multiplier
57
- In order to reach a steady 60 FPS, you can specify a TilesetStats object.
58
- This object is basically the Stats object from the Three.js samples without the UI component.
59
- It must be updated in the animate function and given to the tileset at construction.
60
- ```
61
- import TilesetStats from '@jdultra/threedtiles/src/tileset/TilesetStats';
62
-
63
- const tilesetStats = TilesetStats();
64
-
65
- const ogc3DTile = new OGC3DTile({
66
- url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
67
- stats: tilesetStats
68
- });
69
-
70
- function animate() {
71
-
72
- ...
73
-
74
- tilesetStats.update();
75
- }
76
- ```
77
55
 
78
56
 
79
57
  ### load tiles outside of view
@@ -98,12 +76,14 @@ const ogc3DTile = new OGC3DTile({
98
76
  }
99
77
  });
100
78
  ```
79
+ If using a shared cache between tilesets, check out the next section.
101
80
 
102
81
  ### Cache
103
82
  You may instanciate a cache through the TileLoader class and re-use it for several or all your tilesets.
104
83
  The limitation is that all the tilesets using the same cache will have the same callback.
105
84
 
106
- If a TilesetStats object is passed, it will be used to monitor the size of the cache when the browser allows it, otherwise, each cache is limitted to 1000 items.
85
+ The TileLoader constructor takes 2 arguments. The first is a callback for meshes (see above section) and the second is
86
+ the maximum number of items in the cache (default is 1000).
107
87
 
108
88
  ```
109
89
  import { TileLoader } from "@jdultra/threedtiles/src/tileset/TileLoader";
@@ -114,7 +94,9 @@ const ogc3DTile = new OGC3DTile({
114
94
  //// Insert code to be called on every newly decoded mesh e.g.:
115
95
  mesh.material.wireframe = false;
116
96
  mesh.material.side = THREE.DoubleSide;
117
- }, tilesetStats),
97
+ },
98
+ 2000
99
+ ),
118
100
  meshCallback: mesh => { mesh.material.wireframe = true;} // This callback will not be used as the callback provided to the TileLoader takes priority
119
101
  });
120
102
  ```
package/index.html CHANGED
@@ -5,10 +5,53 @@
5
5
  <meta charset="utf-8" />
6
6
  <title>Three 3DTiles viewer sample</title>
7
7
  <link rel="manifest" href="manifest.json">
8
+ <style>
9
+ .slidecontainer {
10
+ width: 100%;
11
+ }
12
+
13
+ .slider {
14
+ -webkit-appearance: none;
15
+ width: 100%;
16
+ height: 15px;
17
+ border-radius: 5px;
18
+ background: #d3d3d3;
19
+ outline: none;
20
+ opacity: 0.7;
21
+ -webkit-transition: .2s;
22
+ transition: opacity .2s;
23
+ }
24
+
25
+ .slider:hover {
26
+ opacity: 1;
27
+ }
28
+
29
+ .slider::-webkit-slider-thumb {
30
+ -webkit-appearance: none;
31
+ appearance: none;
32
+ width: 25px;
33
+ height: 25px;
34
+ border-radius: 50%;
35
+ background: #0439aa;
36
+ cursor: pointer;
37
+ }
38
+
39
+ .slider::-moz-range-thumb {
40
+ width: 25px;
41
+ height: 25px;
42
+ border-radius: 50%;
43
+ background: #04AA6D;
44
+ cursor: pointer;
45
+ }
46
+ </style>
8
47
  </head>
9
48
 
10
49
  <body>
11
50
  <div id="screen"></div>
51
+ <div style="position: absolute; top: 1%; z-index: 100; right:1%; ">
52
+ <input type="range" min="0.1" max="2" value="1.0", step="0.1" class="slider" id="lodMultiplier" >
53
+ <p style="color: #0439aa;">LOD multiplier: <span id="multiplierValue"></span></p>
54
+ </div>
12
55
  <div style="position: absolute; bottom: 1%; z-index: 100;">
13
56
  <a href="https://openheritage3d.org/project.php?id=taz6-n215">ORIGINAL MODEL</a>
14
57
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jdultra/threedtiles",
3
- "version": "3.1.2",
3
+ "version": "3.1.5",
4
4
  "description": "An OGC 3DTiles viewer for Three.js",
5
5
  "main": "tileset.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -1,17 +1,17 @@
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';
7
+ import { setIntervalAsync } from 'set-interval-async/dynamic';
8
8
 
9
9
 
10
10
  const scene = initScene();
11
- const tilesetStats = TilesetStats();
12
11
  const domContainer = initDomContainer("screen");
13
12
  const camera = initCamera();
14
13
  const ogc3DTiles = initTileset(scene);
14
+ initLODMultiplierSlider(ogc3DTiles)
15
15
  const controller = initController(camera, domContainer);
16
16
 
17
17
  const stats = initStats(domContainer);
@@ -78,46 +78,48 @@ function initCamera() {
78
78
  function initTileset(scene) {
79
79
 
80
80
  const ogc3DTile = new OGC3DTile({
81
- url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
82
- geometricErrorMultiplier: 1,
81
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tiledWithSkirts/tileset.json",
82
+ geometricErrorMultiplier: 0.8,
83
83
  loadOutsideView: true,
84
84
  tileLoader: new TileLoader(mesh => {
85
85
  //// Insert code to be called on every newly decoded mesh e.g.:
86
86
  mesh.material.wireframe = false;
87
87
  mesh.material.side = THREE.DoubleSide;
88
- }, tilesetStats),
89
- stats: tilesetStats
88
+ }, 1000),
89
+ onLoadCallback: tileset => {
90
+ console.log(tileset.json)
91
+ }
90
92
  });
91
-
93
+
92
94
 
93
95
  //// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
94
96
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -10)
95
97
  //ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
96
98
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
97
99
  //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
100
+ //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
99
101
  // ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
100
102
  // ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
101
103
  // ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
102
104
  //// It's up to the user to call updates on the tileset. You might call them whenever the camera moves or at regular time intervals like here
103
105
 
104
-
105
106
 
106
- var interval ;
107
+
108
+ var interval;
107
109
  document.addEventListener('keyup', (e) => {
108
110
  console.log(camera.position)
109
- if(!e.key || e.key !== "p") return;
110
- if(!!interval){
111
+ if (!e.key || e.key !== "p") return;
112
+ if (!!interval) {
111
113
  clearInterval(interval);
112
114
  interval = null;
113
- }else{
115
+ } else {
114
116
  startInterval();
115
117
  }
116
118
  });
117
- function startInterval(){
118
- interval = setInterval(function () {
119
+ function startInterval() {
120
+ interval = setIntervalAsync(function () {
119
121
  ogc3DTile.update(camera);
120
- }, 25);
122
+ }, 20);
121
123
  }
122
124
  startInterval();
123
125
 
@@ -125,10 +127,21 @@ function initTileset(scene) {
125
127
  return ogc3DTile;
126
128
  }
127
129
 
130
+ function initLODMultiplierSlider(tileset) {
131
+ var slider = document.getElementById("lodMultiplier");
132
+ var output = document.getElementById("multiplierValue");
133
+ output.innerHTML = slider.value;
134
+
135
+ slider.oninput = () => {
136
+ tileset.setGeometricErrorMultiplier(slider.value)
137
+ output.innerHTML = slider.value;
138
+ }
139
+ }
140
+
128
141
  function initController(camera, dom) {
129
142
  const controller = new OrbitControls(camera, dom);
130
143
 
131
- controller.target.set(-11.50895,0.058452500000001, 3.1369285);
144
+ controller.target.set(-11.50895, 0.058452500000001, 3.1369285);
132
145
  controller.minDistance = 1;
133
146
  controller.maxDistance = 5000;
134
147
  controller.update();
@@ -141,7 +154,6 @@ function animate() {
141
154
 
142
155
  camera.updateMatrixWorld();
143
156
  renderer.render(scene, camera);
144
- tilesetStats.update();
145
157
  stats.update();
146
158
 
147
159
  }
@@ -23,8 +23,10 @@ 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,
29
+ * onLoadCallback: function
28
30
  * } properties
29
31
  */
30
32
  constructor(properties) {
@@ -42,24 +44,11 @@ class OGC3DTile extends THREE.Object3D {
42
44
  }
43
45
  // set properties general to the entire tileset
44
46
  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
47
 
61
48
  this.meshCallback = properties.meshCallback;
62
49
  this.loadOutsideView = properties.loadOutsideView;
50
+ this.cameraOnLoad = properties.cameraOnLoad;
51
+ this.parentTile = properties.parentTile;
63
52
 
64
53
  // declare properties specific to the tile for clarity
65
54
  this.childrenTiles = [];
@@ -78,13 +67,17 @@ class OGC3DTile extends THREE.Object3D {
78
67
 
79
68
  if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
80
69
  self.setup(properties);
70
+ if (properties.onLoadCallback) properties.onLoadCallback(self);
81
71
  } else if (properties.url) { // If only the url to the tileset.json is provided
82
72
  self.controller = new AbortController();
83
73
  fetch(properties.url, { signal: self.controller.signal }).then(result => {
84
74
  if (!result.ok) {
85
75
  throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
86
76
  }
87
- result.json().then(json => self.setup({ rootPath: path.dirname(properties.url), json: json }))
77
+ result.json().then(json => {
78
+ self.setup({ rootPath: path.dirname(properties.url), json: json });
79
+ if (properties.onLoadCallback) properties.onLoadCallback(self);
80
+ });
88
81
  });
89
82
  }
90
83
  }
@@ -179,23 +172,37 @@ class OGC3DTile extends THREE.Object3D {
179
172
  });
180
173
  self.add(mesh);
181
174
  self.meshContent = mesh;
182
- })
175
+ }, !self.cameraOnLoad ? () => 0 : () => {
176
+ return self.calculateDistanceToCamera(self.cameraOnLoad);
177
+ }, () => self.getSiblings(), self.level, self.uuid);
183
178
  } 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;
179
+ self.tileLoader.get(this.uuid, url, json => {
180
+ if (!!self.deleted) return;
181
+ if (!self.json.children) self.json.children = [];
182
+ json.rootPath = path.dirname(url);
183
+ self.json.children.push(json);
184
+ delete self.json.content;
185
+ self.hasUnloadedJSONContent = false;
186
+ });
187
+
188
+ /* self.controller = new AbortController();
189
+ setTimeout(() => {
190
+ fetch(url, { signal: self.controller.signal }).then(result => {
191
+ if (!result.ok) {
192
+ throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
193
+ }
194
+ result.json().then(json => {
195
+ // when json content is downloaded, it is inserted into this tile's original JSON as a child
196
+ // and the content object is deleted from the original JSON
197
+ if (!self.json.children) self.json.children = [];
198
+ json.rootPath = path.dirname(url);
199
+ self.json.children.push(json);
200
+ delete self.json.content;
201
+ self.hasUnloadedJSONContent = false;
202
+ }).catch(error => { });
197
203
  }).catch(error => { });
198
- }).catch(error => { });
204
+ }, 0); */
205
+
199
206
  }
200
207
 
201
208
  }
@@ -216,6 +223,7 @@ class OGC3DTile extends THREE.Object3D {
216
223
 
217
224
  });
218
225
  this.parent = null;
226
+ this.parentTile = null;
219
227
  this.dispatchEvent({ type: 'removed' });
220
228
  }
221
229
  disposeChildren() {
@@ -324,6 +332,7 @@ class OGC3DTile extends THREE.Object3D {
324
332
  function loadJsonChildren() {
325
333
  self.json.children.forEach(childJSON => {
326
334
  let childTile = new OGC3DTile({
335
+ parentTile: self,
327
336
  parentGeometricError: self.geometricError,
328
337
  parentBoundingVolume: self.boundingVolume,
329
338
  parentRefinement: self.refinement,
@@ -332,7 +341,8 @@ class OGC3DTile extends THREE.Object3D {
332
341
  geometricErrorMultiplier: self.geometricErrorMultiplier,
333
342
  loadOutsideView: self.loadOutsideView,
334
343
  level: self.level + 1,
335
- tileLoader: self.tileLoader
344
+ tileLoader: self.tileLoader,
345
+ cameraOnLoad: camera
336
346
  });
337
347
  self.childrenTiles.push(childTile);
338
348
  self.add(childTile);
@@ -455,9 +465,43 @@ class OGC3DTile extends THREE.Object3D {
455
465
  //throw Error("Region bounding volume not supported");
456
466
  return -1;
457
467
  }
458
-
459
468
  }
460
469
 
470
+ getSiblings() {
471
+ const self = this;
472
+ const tiles = [];
473
+ if (!self.parentTile) return tiles;
474
+ let p = self.parentTile;
475
+ while (!p.hasMeshContent && !!p.parentTile) {
476
+ p = p.parentTile;
477
+ }
478
+ p.childrenTiles.forEach(child => {
479
+ if (!!child && child != self) {
480
+ while (!child.hasMeshContent && !!child.childrenTiles[0]) {
481
+ child = child.childrenTiles[0];
482
+ }
483
+ tiles.push(child);
484
+ }
485
+ });
486
+ return tiles;
487
+ }
488
+ calculateDistanceToCamera(camera) {
489
+ if (this.boundingVolume instanceof OBB) {
490
+ // box
491
+ tempSphere.copy(this.boundingVolume.sphere);
492
+ tempSphere.applyMatrix4(this.matrixWorld);
493
+ //if (!frustum.intersectsSphere(tempSphere)) return -1;
494
+ } else if (this.boundingVolume instanceof THREE.Sphere) {
495
+ //sphere
496
+ tempSphere.copy(this.boundingVolume);
497
+ tempSphere.applyMatrix4(this.matrixWorld);
498
+ //if (!frustum.intersectsSphere(tempSphere)) return -1;
499
+ }
500
+ if (this.boundingVolume instanceof THREE.Box3) {
501
+ return -1; // region not supported
502
+ }
503
+ return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
504
+ }
461
505
  setGeometricErrorMultiplier(geometricErrorMultiplier) {
462
506
  this.geometricErrorMultiplier = geometricErrorMultiplier;
463
507
  this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
@@ -4,61 +4,150 @@ import { setIntervalAsync } from 'set-interval-async/dynamic';
4
4
 
5
5
  const ready = [];
6
6
  const downloads = [];
7
+ const nextReady = [];
8
+ const nextDownloads = [];
9
+
7
10
  function scheduleDownload(f) {
8
11
  downloads.unshift(f);
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 0;
17
+ }
18
+ const nextDownload = nextDownloads.shift();
13
19
  if (!!nextDownload && nextDownload.shouldDoDownload()) {
14
20
  nextDownload.doDownload();
15
21
  }
22
+ return 1;
16
23
  }
17
- function meshReceived(cache, register, key) {
18
- ready.unshift([cache, register, key]);
24
+ function meshReceived(cache, register, key, distanceFunction, getSiblings, level, uuid) {
25
+ ready.unshift([cache, register, key, distanceFunction, getSiblings, level, uuid]);
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 0;
31
+ }
32
+ const data = nextReady.shift();
33
+ if (!data) return 0;
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
+ return 1;
48
+ }
49
+
50
+ function getNextDownloads() {
51
+ let smallestLevel = Number.MAX_VALUE;
52
+ let smallestDistance = Number.MAX_VALUE;
53
+ let closest = -1;
54
+ for (let i = downloads.length - 1; i >= 0; i--) {
55
+ if (!downloads[i].shouldDoDownload()) {
56
+ downloads.splice(i, 1);
57
+ continue;
58
+ }
59
+ if(!downloads[i].distanceFunction){ // if no distance function, must be a json, give absolute priority!
60
+ nextDownloads.push(downloads.splice(i, 1)[0]);
61
+ }
62
+ }
63
+ if(nextDownloads.length>0) return;
64
+ for (let i = downloads.length - 1; i >= 0; i--) {
65
+ const dist = downloads[i].distanceFunction();
66
+ if (dist < smallestDistance) {
67
+ smallestDistance = dist;
68
+ closest = i;
69
+ } else if (dist == smallestDistance && downloads[i].level < smallestLevel) {
70
+ smallestLevel = downloads[i].level;
71
+ closest = i
72
+ }
73
+ }
74
+ if (closest >= 0) {
75
+ const closestItem = downloads.splice(closest, 1).pop();
76
+ nextDownloads.push(closestItem);
77
+ const siblings = closestItem.getSiblings();
78
+ for (let i = downloads.length - 1; i >= 0; i--) {
79
+ if (siblings.includes(downloads[i].uuid)) {
80
+ nextDownloads.push(downloads.splice(i, 1).pop());
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ function getNextReady() {
87
+ let smallestLevel = Number.MAX_VALUE;
88
+ let smallestDistance = Number.MAX_VALUE;
89
+ let closest = -1;
90
+ for (let i = ready.length - 1; i >= 0; i--) {
91
+
92
+ if(!ready[i][3]){// if no distance function, must be a json, give absolute priority!
93
+ nextReady.push(ready.splice(i,1)[0]);
94
+ }
95
+ }
96
+ if(nextReady.length>0) return;
97
+ for (let i = ready.length - 1; i >= 0; i--) {
98
+ const dist = ready[i][3]();
99
+ if (dist < smallestDistance) {
100
+ smallestDistance = dist;
101
+ smallestLevel = ready[i][5]
102
+ closest = i
103
+ } else if (dist == smallestDistance && ready[i][5] < smallestLevel) {
104
+ smallestLevel = ready[i][5]
105
+ closest = i
106
+ }
107
+ }
108
+ if (closest >= 0) {
109
+ const closestItem = ready.splice(closest, 1).pop();
110
+ nextReady.push(closestItem);
111
+ const siblings = closestItem[4]();
112
+ for (let i = ready.length - 1; i >= 0; i--) {
113
+ if (siblings.includes(ready[i][6])) {
114
+ nextready.push(ready.splice(i, 1).pop());
115
+ }
36
116
  }
37
117
  }
38
118
  }
39
- setIntervalAsync(() => {
40
- loadBatch();
41
- }, 10)
42
- setIntervalAsync(() => {
43
- download();
44
- }, 10)
119
+ setIntervalAsync(()=>{
120
+ const start = Date.now();
121
+ let uploaded = 0;
122
+ do{
123
+ uploaded = download();
124
+ }while(uploaded > 0 && (Date.now() - start)<= 2 )
125
+
126
+ },10);
127
+ setIntervalAsync(()=>{
128
+ const start = Date.now();
129
+ let loaded = 0;
130
+ do{
131
+ loaded = loadBatch();
132
+ }while(loaded > 0 && (Date.now() - start)<= 2 )
133
+
134
+ },10);
45
135
 
46
136
  class TileLoader {
47
- constructor(meshCallback, stats) {
137
+ constructor(meshCallback, maxCachedItems) {
48
138
  this.meshCallback = meshCallback;
49
139
  this.cache = new LinkedHashMap();
50
- this.maxSize = 1000;
51
- this.stats = stats;
140
+ this.maxCachedItems = !!maxCachedItems ? maxCachedItems : 1000;
52
141
  this.register = {};
53
142
  }
54
143
 
55
- get(tileIdentifier, path, callback) {
144
+ get(tileIdentifier, path, callback, distanceFunction, getSiblings, level, uuid) {
56
145
  const self = this;
57
146
  const key = simplifyPath(path);
58
147
 
59
148
 
60
- if (!path.includes(".b3dm")) {
61
- console.error("the 3DTiles cache can only be used to load B3DM data");
149
+ if (!path.includes(".b3dm") && !path.includes(".json")) {
150
+ console.error("the 3DTiles cache can only be used to load B3DM and json data");
62
151
  return;
63
152
  }
64
153
  if (!self.register[key]) {
@@ -71,13 +160,11 @@ class TileLoader {
71
160
 
72
161
  const cachedObject = self.cache.get(key);
73
162
  if (!!cachedObject) {
74
- meshReceived(self.cache, self.register, key);
163
+ meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
75
164
  } 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": () => {
165
+ let downloadFunction;
166
+ if (path.includes(".b3dm")) {
167
+ downloadFunction = () => {
81
168
  fetch(path).then(result => {
82
169
  if (!result.ok) {
83
170
  console.error("could not load tile with path : " + path)
@@ -86,25 +173,40 @@ class TileLoader {
86
173
  result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
87
174
  self.cache.put(key, mesh);
88
175
  self.checkSize();
89
- meshReceived(self.cache, self.register, key);
176
+ meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
90
177
  });
91
178
 
92
179
  });
93
180
  }
181
+ }else if (path.includes(".json")) {
182
+ downloadFunction = () => {
183
+ fetch(path).then(result => {
184
+ if (!result.ok) {
185
+ console.error("could not load tile with path : " + path)
186
+ throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
187
+ }
188
+ result.json().then(json => {
189
+ self.cache.put(key, json);
190
+ self.checkSize();
191
+ meshReceived(self.cache, self.register, key);
192
+ });
193
+ });
194
+ }
195
+ }
196
+ scheduleDownload({
197
+ "shouldDoDownload": () => {
198
+ return !!self.register[key] && Object.keys(self.register[key]).length > 0;
199
+ },
200
+ "doDownload": downloadFunction,
201
+ "distanceFunction": distanceFunction,
202
+ "getSiblings": getSiblings,
203
+ "level": level,
204
+ "uuid": uuid
94
205
  })
95
206
  }
96
207
  }
97
208
 
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
- }
209
+
108
210
 
109
211
  invalidate(path, tileIdentifier) {
110
212
  const key = simplifyPath(path);
@@ -113,46 +215,42 @@ class TileLoader {
113
215
 
114
216
  checkSize() {
115
217
  const self = this;
116
-
218
+
117
219
  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()) {
220
+
221
+ while (self.cache.size() > self.maxCachedItems && i < self.cache.size()) {
128
222
  i++;
129
223
  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();
224
+ const reg = self.register[entry.key];
225
+ if (!!reg) {
226
+ if (Object.keys(reg).length > 0) {
227
+ self.cache.remove(entry.key);
228
+ self.cache.put(entry.key, entry.value);
229
+ } else {
230
+ self.cache.remove(entry.key);
231
+ delete self.register[entry.key];
232
+ entry.value.traverse((o) => {
233
+
234
+ if (o.material) {
235
+ // dispose materials
236
+ if (o.material.length) {
237
+ for (let i = 0; i < o.material.length; ++i) {
238
+ o.material[i].dispose();
239
+ }
240
+ }
241
+ else {
242
+ o.material.dispose()
143
243
  }
144
244
  }
145
- else {
146
- o.material.dispose()
147
- }
148
- }
149
- if (o.geometry) {
150
- // dispose geometry
151
- o.geometry.dispose();
245
+ if (o.geometry) {
246
+ // dispose geometry
247
+ o.geometry.dispose();
152
248
 
153
- }
154
- });
249
+ }
250
+ });
251
+ }
155
252
  }
253
+
156
254
  }
157
255
  }
158
256
  }
@@ -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;