@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 +9 -27
- package/index.html +43 -0
- package/package.json +1 -1
- package/src/index.js +30 -18
- package/src/tileset/OGC3DTile.js +79 -35
- package/src/tileset/TileLoader.js +179 -81
- package/src/tileset/TilesetStats.js +0 -65
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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:
|
|
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
|
-
},
|
|
89
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 =
|
|
119
|
+
function startInterval() {
|
|
120
|
+
interval = setIntervalAsync(function () {
|
|
119
121
|
ogc3DTile.update(camera);
|
|
120
|
-
},
|
|
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
|
}
|
package/src/tileset/OGC3DTile.js
CHANGED
|
@@ -23,8 +23,10 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
23
23
|
* geometricErrorMultiplier: Double,
|
|
24
24
|
* loadOutsideView: Boolean,
|
|
25
25
|
* tileLoader : TileLoader,
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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 =>
|
|
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.
|
|
185
|
-
|
|
186
|
-
if (!
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
}
|
|
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(
|
|
12
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
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,
|
|
137
|
+
constructor(meshCallback, maxCachedItems) {
|
|
48
138
|
this.meshCallback = meshCallback;
|
|
49
139
|
this.cache = new LinkedHashMap();
|
|
50
|
-
this.
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (o.material
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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;
|