@jdultra/threedtiles 3.0.8 → 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/.vscode/settings.json +2 -0
- package/README.md +50 -4
- package/index.html +1 -43
- package/manifest.json +4 -0
- package/package.json +7 -3
- package/src/decoder/B3DMDecoder.js +3 -6
- package/src/index.js +22 -75
- package/src/tileset/OGC3DTile.js +135 -112
- package/src/tileset/TileLoader.js +276 -0
- package/src/cache/Cache.js +0 -44
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ import { OGC3DTile } from "./tileset/OGC3DTile";
|
|
|
14
14
|
...
|
|
15
15
|
|
|
16
16
|
const ogc3DTile = new OGC3DTile({
|
|
17
|
-
url: "https://
|
|
17
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json"
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
scene.add(ogc3DTile);
|
|
@@ -33,6 +33,9 @@ setInterval(function () {
|
|
|
33
33
|
- Allows tilesets transformations. Translate, scale and rotate a tilesets in real-time.
|
|
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
|
+
- Share a cache between tileset instances
|
|
37
|
+
- Automatically tune the geometric error multiplier to 60 FPS
|
|
38
|
+
- Automatic scaling of the cache
|
|
36
39
|
|
|
37
40
|
### geometric Error Multiplier
|
|
38
41
|
The geometric error multiplier allows you to multiply the geometric error by a factor.
|
|
@@ -43,20 +46,43 @@ you may also set this value at initialization:
|
|
|
43
46
|
|
|
44
47
|
```
|
|
45
48
|
const ogc3DTile = new OGC3DTile({
|
|
46
|
-
url: "https://
|
|
49
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
47
50
|
geometricErrorMultiplier: 2.0
|
|
48
51
|
});
|
|
49
52
|
```
|
|
50
53
|
A lower value will result in lower detail tiles being loaded and a higher value results in higher detail tiles being loaded.
|
|
51
54
|
A value of 1.0 is the default.
|
|
52
55
|
|
|
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
|
+
|
|
78
|
+
|
|
53
79
|
### load tiles outside of view
|
|
54
80
|
By default, only the tiles that intersect the view frustum are loaded. When the camera moves, the scene will have to load the missing tiles.
|
|
55
81
|
Instead of this behaviour, you can force the lowest possible LODs to be loaded for tiles around the view so that there are no gaps in the mesh when the camera moves or when displaying shadows.
|
|
56
82
|
|
|
57
83
|
```
|
|
58
84
|
const ogc3DTile = new OGC3DTile({
|
|
59
|
-
url: "https://
|
|
85
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
60
86
|
loadOutsideView: true
|
|
61
87
|
});
|
|
62
88
|
```
|
|
@@ -66,13 +92,33 @@ Add a callback on loaded tiles in order to set a material or do some logic on th
|
|
|
66
92
|
|
|
67
93
|
```
|
|
68
94
|
const ogc3DTile = new OGC3DTile({
|
|
69
|
-
url: "https://
|
|
95
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
70
96
|
meshCallback: mesh => {
|
|
71
97
|
mesh.material.wireframe = true;
|
|
72
98
|
}
|
|
73
99
|
});
|
|
74
100
|
```
|
|
75
101
|
|
|
102
|
+
### Cache
|
|
103
|
+
You may instanciate a cache through the TileLoader class and re-use it for several or all your tilesets.
|
|
104
|
+
The limitation is that all the tilesets using the same cache will have the same callback.
|
|
105
|
+
|
|
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.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
import { TileLoader } from "@jdultra/threedtiles/src/tileset/TileLoader";
|
|
110
|
+
|
|
111
|
+
const ogc3DTile = new OGC3DTile({
|
|
112
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
113
|
+
tileLoader: new TileLoader(mesh => {
|
|
114
|
+
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
115
|
+
mesh.material.wireframe = false;
|
|
116
|
+
mesh.material.side = THREE.DoubleSide;
|
|
117
|
+
}, tilesetStats),
|
|
118
|
+
meshCallback: mesh => { mesh.material.wireframe = true;} // This callback will not be used as the callback provided to the TileLoader takes priority
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
76
122
|
### Transformations
|
|
77
123
|
The OGC 3DTile object is a regular three.js Object3D so it can be transformed via the standard three.js API:
|
|
78
124
|
|
package/index.html
CHANGED
|
@@ -4,53 +4,11 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="utf-8" />
|
|
6
6
|
<title>Three 3DTiles viewer sample</title>
|
|
7
|
-
<
|
|
8
|
-
.slidecontainer {
|
|
9
|
-
width: 100%;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.slider {
|
|
13
|
-
-webkit-appearance: none;
|
|
14
|
-
width: 100%;
|
|
15
|
-
height: 15px;
|
|
16
|
-
border-radius: 5px;
|
|
17
|
-
background: #d3d3d3;
|
|
18
|
-
outline: none;
|
|
19
|
-
opacity: 0.7;
|
|
20
|
-
-webkit-transition: .2s;
|
|
21
|
-
transition: opacity .2s;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.slider:hover {
|
|
25
|
-
opacity: 1;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.slider::-webkit-slider-thumb {
|
|
29
|
-
-webkit-appearance: none;
|
|
30
|
-
appearance: none;
|
|
31
|
-
width: 25px;
|
|
32
|
-
height: 25px;
|
|
33
|
-
border-radius: 50%;
|
|
34
|
-
background: #0439aa;
|
|
35
|
-
cursor: pointer;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.slider::-moz-range-thumb {
|
|
39
|
-
width: 25px;
|
|
40
|
-
height: 25px;
|
|
41
|
-
border-radius: 50%;
|
|
42
|
-
background: #04AA6D;
|
|
43
|
-
cursor: pointer;
|
|
44
|
-
}
|
|
45
|
-
</style>
|
|
7
|
+
<link rel="manifest" href="manifest.json">
|
|
46
8
|
</head>
|
|
47
9
|
|
|
48
10
|
<body>
|
|
49
11
|
<div id="screen"></div>
|
|
50
|
-
<div style="position: absolute; top: 1%; z-index: 100; right:1%; ">
|
|
51
|
-
<input type="range" min="0.1" max="4" value="1.0", step="0.1" class="slider" id="lodMultiplier" >
|
|
52
|
-
<p style="color: #0439aa;">LOD multiplier: <span id="multiplierValue"></span></p>
|
|
53
|
-
</div>
|
|
54
12
|
<div style="position: absolute; bottom: 1%; z-index: 100;">
|
|
55
13
|
<a href="https://openheritage3d.org/project.php?id=taz6-n215">ORIGINAL MODEL</a>
|
|
56
14
|
</div>
|
package/manifest.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jdultra/threedtiles",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "An OGC 3DTiles viewer for Three.js",
|
|
5
5
|
"main": "tileset.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,9 +26,13 @@
|
|
|
26
26
|
"gltf-validator": ">=2.0.0-dev.3.3",
|
|
27
27
|
"js-utils-z": "1.2.1",
|
|
28
28
|
"lodash": ">=4.17.20",
|
|
29
|
+
"lru-cache": "^7.4.1",
|
|
30
|
+
"mnemonist": "^0.39.0",
|
|
29
31
|
"path-browserify": "^1.0.1",
|
|
30
32
|
"regenerator-runtime": ">=0.13.7",
|
|
31
|
-
"
|
|
33
|
+
"set-interval-async": "^2.0.3",
|
|
34
|
+
"three": "0.131.0",
|
|
35
|
+
"uuid": "^8.3.2"
|
|
32
36
|
},
|
|
33
37
|
"devDependencies": {
|
|
34
38
|
"@babel/core": "^7.12.9",
|
|
@@ -41,7 +45,7 @@
|
|
|
41
45
|
"mini-css-extract-plugin": "^1.3.1",
|
|
42
46
|
"webpack": "^5.65.0",
|
|
43
47
|
"webpack-cli": "4.9.1",
|
|
44
|
-
"webpack-dev-server": "4.7.
|
|
48
|
+
"webpack-dev-server": "^4.7.4",
|
|
45
49
|
"whatwg-fetch": "^3.5.0"
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
2
|
-
import * as THREE from 'three';
|
|
3
2
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
4
3
|
import { LegacyGLTFLoader } from './LegacyGLTFLoader.js';
|
|
5
|
-
import { Color, DoubleSide, BufferAttribute, Mesh } from "three";
|
|
6
|
-
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
7
4
|
|
|
8
5
|
const gltfLoader = new GLTFLoader();
|
|
9
6
|
const dracoLoader = new DRACOLoader();
|
|
10
|
-
dracoLoader.setDecoderPath( '/
|
|
7
|
+
dracoLoader.setDecoderPath( 'https://www.gstatic.com/draco/versioned/decoders/1.4.3/' );
|
|
11
8
|
gltfLoader.setDRACOLoader( dracoLoader );
|
|
12
9
|
const legacyGLTFLoader = new LegacyGLTFLoader();
|
|
13
10
|
const B3DMDecoder = {
|
|
@@ -106,7 +103,7 @@ const B3DMDecoder = {
|
|
|
106
103
|
* @param {*} scene
|
|
107
104
|
* @returns
|
|
108
105
|
*/
|
|
109
|
-
function mergeColoredObject(scene) {
|
|
106
|
+
/*function mergeColoredObject(scene) {
|
|
110
107
|
|
|
111
108
|
const coloredMeshes = {};
|
|
112
109
|
const texturedMeshes = {};
|
|
@@ -213,7 +210,7 @@ function mergeColoredObject(scene) {
|
|
|
213
210
|
console.log();
|
|
214
211
|
scene.matrix = new THREE.Matrix4();
|
|
215
212
|
return scene;
|
|
216
|
-
}
|
|
213
|
+
}*/
|
|
217
214
|
|
|
218
215
|
export { B3DMDecoder }
|
|
219
216
|
|
package/src/index.js
CHANGED
|
@@ -2,80 +2,25 @@ 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
4
|
import { OGC3DTile } from "./tileset/OGC3DTile";
|
|
5
|
+
import { TileLoader } from "./tileset/TileLoader";
|
|
5
6
|
import { MapControls, OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
6
|
-
import back from './images/skybox/back.png';
|
|
7
|
-
import front from './images/skybox/front.png';
|
|
8
|
-
import top from './images/skybox/top.png';
|
|
9
|
-
import bottom from './images/skybox/bottom.png';
|
|
10
|
-
import right from './images/skybox/right.png';
|
|
11
|
-
import left from './images/skybox/left.png';
|
|
12
7
|
|
|
13
8
|
|
|
14
9
|
const scene = initScene();
|
|
15
10
|
const domContainer = initDomContainer("screen");
|
|
16
11
|
const camera = initCamera();
|
|
17
12
|
const ogc3DTiles = initTileset(scene);
|
|
18
|
-
initLODMultiplierSlider(ogc3DTiles);
|
|
19
13
|
const controller = initController(camera, domContainer);
|
|
20
|
-
const skybox = initSkybox(controller, camera, scene);
|
|
21
14
|
|
|
22
15
|
const stats = initStats(domContainer);
|
|
23
16
|
const renderer = initRenderer(camera, domContainer);
|
|
24
17
|
|
|
25
18
|
animate();
|
|
26
19
|
|
|
27
|
-
function initSkybox(controller, camera, scene) {
|
|
28
|
-
const geometry = new THREE.BoxGeometry(8000, 8000, 8000);
|
|
29
|
-
const textures = [
|
|
30
|
-
loadTexture(back),
|
|
31
|
-
loadTexture(front),
|
|
32
|
-
loadTexture(top),
|
|
33
|
-
loadTexture(bottom),
|
|
34
|
-
loadTexture(right),
|
|
35
|
-
loadTexture(left),
|
|
36
|
-
];
|
|
37
|
-
function loadTexture(url) {
|
|
38
|
-
return new THREE.TextureLoader().load(url, (texture => {
|
|
39
|
-
texture.wrapS = THREE.ClampToEdgeWrapping;
|
|
40
|
-
texture.wrapT = THREE.ClampToEdgeWrapping;
|
|
41
|
-
texture.magFilter = THREE.LinearFilter;
|
|
42
|
-
texture.minFilter = THREE.LinearFilter;
|
|
43
|
-
}))
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
const materials = [];
|
|
47
|
-
textures.forEach(tex => {
|
|
48
|
-
materials.push(new THREE.MeshBasicMaterial({ map: tex, side: THREE.BackSide }));
|
|
49
|
-
})
|
|
50
|
-
const mesh = new THREE.Mesh(geometry, materials);
|
|
51
|
-
mesh.position.copy(camera.position);
|
|
52
|
-
controller.addEventListener("change", () => {
|
|
53
|
-
mesh.position.copy(camera.position);
|
|
54
|
-
});
|
|
55
|
-
scene.add(mesh);
|
|
56
|
-
return mesh;
|
|
57
|
-
}
|
|
58
|
-
function initLODMultiplierSlider(tileset) {
|
|
59
|
-
var slider = document.getElementById("lodMultiplier");
|
|
60
|
-
var output = document.getElementById("multiplierValue");
|
|
61
|
-
output.innerHTML = slider.value;
|
|
62
|
-
|
|
63
|
-
slider.oninput = () => {
|
|
64
|
-
tileset.setGeometricErrorMultiplier(slider.value)
|
|
65
|
-
output.innerHTML = slider.value;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
20
|
function initScene() {
|
|
69
21
|
const scene = new THREE.Scene();
|
|
70
|
-
scene.background = new THREE.Color(
|
|
71
|
-
scene.add(new THREE.AmbientLight(0xFFFFFF, 0
|
|
72
|
-
|
|
73
|
-
var dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
|
|
74
|
-
dirLight.position.set(-400, 500, -100);
|
|
75
|
-
dirLight.target.position.set(0, 0, 0);
|
|
76
|
-
|
|
77
|
-
scene.add(dirLight);
|
|
78
|
-
scene.add(dirLight.target);
|
|
22
|
+
scene.background = new THREE.Color(0x000000);
|
|
23
|
+
scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
|
|
79
24
|
return scene;
|
|
80
25
|
}
|
|
81
26
|
|
|
@@ -115,16 +60,15 @@ function initRenderer(camera, dom) {
|
|
|
115
60
|
}
|
|
116
61
|
|
|
117
62
|
function initStats(dom) {
|
|
118
|
-
const stats =
|
|
119
|
-
|
|
63
|
+
const stats = Stats();
|
|
64
|
+
document.body.appendChild(stats.dom);
|
|
120
65
|
return stats;
|
|
121
66
|
}
|
|
122
67
|
|
|
123
68
|
|
|
124
69
|
function initCamera() {
|
|
125
70
|
const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1, 10000);
|
|
126
|
-
camera.position.set(
|
|
127
|
-
camera.lookAt(-100, 40, 0);
|
|
71
|
+
camera.position.set(10, 10, 10);
|
|
128
72
|
|
|
129
73
|
return camera;
|
|
130
74
|
}
|
|
@@ -132,28 +76,33 @@ function initCamera() {
|
|
|
132
76
|
function initTileset(scene) {
|
|
133
77
|
|
|
134
78
|
const ogc3DTile = new OGC3DTile({
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
url: "https://storage.googleapis.com/ogc-3d-tiles/berlinSubsetTiled/tileset.json",
|
|
138
|
-
geometricErrorMultiplier: 1,
|
|
79
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
80
|
+
geometricErrorMultiplier: 1.0,
|
|
139
81
|
loadOutsideView: true,
|
|
140
|
-
|
|
82
|
+
tileLoader: new TileLoader(mesh => {
|
|
141
83
|
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
142
84
|
mesh.material.wireframe = false;
|
|
143
85
|
mesh.material.side = THREE.DoubleSide;
|
|
144
|
-
}
|
|
86
|
+
}, 2000)
|
|
145
87
|
});
|
|
88
|
+
|
|
146
89
|
|
|
147
90
|
//// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
|
|
148
91
|
//ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -10)
|
|
149
92
|
//ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
|
|
150
93
|
//ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
|
|
151
|
-
//ogc3DTile.scale.set(0.
|
|
152
|
-
//ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0),
|
|
94
|
+
//ogc3DTile.scale.set(0.0001,0.0001,0.0001);
|
|
95
|
+
//ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
|
|
96
|
+
// ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
|
|
97
|
+
// ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
|
|
98
|
+
// ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
|
|
153
99
|
//// 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
|
|
154
100
|
|
|
101
|
+
|
|
102
|
+
|
|
155
103
|
var interval ;
|
|
156
104
|
document.addEventListener('keyup', (e) => {
|
|
105
|
+
console.log(camera.position)
|
|
157
106
|
if(!e.key || e.key !== "p") return;
|
|
158
107
|
if(!!interval){
|
|
159
108
|
clearInterval(interval);
|
|
@@ -165,10 +114,9 @@ function initTileset(scene) {
|
|
|
165
114
|
function startInterval(){
|
|
166
115
|
interval = setInterval(function () {
|
|
167
116
|
ogc3DTile.update(camera);
|
|
168
|
-
},
|
|
117
|
+
}, 20);
|
|
169
118
|
}
|
|
170
119
|
startInterval();
|
|
171
|
-
|
|
172
120
|
|
|
173
121
|
scene.add(ogc3DTile)
|
|
174
122
|
return ogc3DTile;
|
|
@@ -177,9 +125,9 @@ function initTileset(scene) {
|
|
|
177
125
|
function initController(camera, dom) {
|
|
178
126
|
const controller = new OrbitControls(camera, dom);
|
|
179
127
|
|
|
180
|
-
controller.target.set(-
|
|
128
|
+
controller.target.set(-11.50895,0.058452500000001, 3.1369285);
|
|
181
129
|
controller.minDistance = 1;
|
|
182
|
-
controller.maxDistance =
|
|
130
|
+
controller.maxDistance = 5000;
|
|
183
131
|
controller.update();
|
|
184
132
|
return controller;
|
|
185
133
|
}
|
|
@@ -190,7 +138,6 @@ function animate() {
|
|
|
190
138
|
|
|
191
139
|
camera.updateMatrixWorld();
|
|
192
140
|
renderer.render(scene, camera);
|
|
193
|
-
|
|
194
141
|
stats.update();
|
|
195
142
|
|
|
196
143
|
}
|
package/src/tileset/OGC3DTile.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { OBB } from "../geometry/obb";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { TileLoader } from "./TileLoader";
|
|
4
|
+
import { v4 as uuidv4 } from "uuid";
|
|
5
|
+
import { setIntervalAsync } from 'set-interval-async/dynamic';
|
|
6
|
+
// import { clearIntervalAsync } from 'set-interval-async';
|
|
5
7
|
const path = require('path');
|
|
6
8
|
|
|
7
|
-
const tilesToLoad = [];
|
|
8
|
-
function scheduleLoadTile(tile) {
|
|
9
|
-
tilesToLoad.push(tile);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
setInterval(() => {
|
|
13
|
-
const tile = tilesToLoad.shift();
|
|
14
|
-
if (!!tile) tile.load();
|
|
15
|
-
}, 5)
|
|
16
|
-
|
|
17
9
|
const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1));
|
|
18
10
|
|
|
19
11
|
|
|
@@ -30,15 +22,32 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
30
22
|
* parentRefinement: optional,
|
|
31
23
|
* geometricErrorMultiplier: Double,
|
|
32
24
|
* loadOutsideView: Boolean,
|
|
33
|
-
*
|
|
25
|
+
* tileLoader : TileLoader,
|
|
26
|
+
* meshCallback: function,
|
|
27
|
+
* cameraOnLoad: camera,
|
|
28
|
+
* parentTile: OGC3DTile
|
|
34
29
|
* } properties
|
|
35
30
|
*/
|
|
36
31
|
constructor(properties) {
|
|
37
32
|
super();
|
|
33
|
+
const self = this;
|
|
34
|
+
this.uuid = uuidv4();
|
|
35
|
+
if (!!properties.tileLoader) {
|
|
36
|
+
this.tileLoader = properties.tileLoader;
|
|
37
|
+
} else {
|
|
38
|
+
this.tileLoader = new TileLoader(!properties.meshCallback ?
|
|
39
|
+
mesh => {
|
|
40
|
+
mesh.material.wireframe = false;
|
|
41
|
+
mesh.material.side = THREE.DoubleSide;
|
|
42
|
+
} : properties.meshCallback);
|
|
43
|
+
}
|
|
38
44
|
// set properties general to the entire tileset
|
|
39
45
|
this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
|
|
46
|
+
|
|
40
47
|
this.meshCallback = properties.meshCallback;
|
|
41
48
|
this.loadOutsideView = properties.loadOutsideView;
|
|
49
|
+
this.cameraOnLoad = properties.cameraOnLoad;
|
|
50
|
+
this.parentTile = properties.parentTile;
|
|
42
51
|
|
|
43
52
|
// declare properties specific to the tile for clarity
|
|
44
53
|
this.childrenTiles = [];
|
|
@@ -55,7 +64,6 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
55
64
|
this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
|
|
56
65
|
this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
|
|
57
66
|
|
|
58
|
-
const self = this;
|
|
59
67
|
if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
|
|
60
68
|
self.setup(properties);
|
|
61
69
|
} else if (properties.url) { // If only the url to the tileset.json is provided
|
|
@@ -95,9 +103,9 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
95
103
|
}
|
|
96
104
|
// decode transform
|
|
97
105
|
if (!!this.json.transform) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.
|
|
106
|
+
let mat = new THREE.Matrix4();
|
|
107
|
+
mat.elements = this.json.transform;
|
|
108
|
+
this.applyMatrix4(mat);
|
|
101
109
|
}
|
|
102
110
|
// decode volume
|
|
103
111
|
if (!!this.json.boundingVolume) {
|
|
@@ -124,11 +132,13 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
124
132
|
} else {
|
|
125
133
|
this.hasMeshContent = true;
|
|
126
134
|
}
|
|
127
|
-
|
|
135
|
+
this.load();
|
|
136
|
+
//scheduleLoadTile(this);
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
load() {
|
|
131
140
|
var self = this;
|
|
141
|
+
if (self.deleted) return;
|
|
132
142
|
if (!!self.json.content) {
|
|
133
143
|
let url;
|
|
134
144
|
if (!!self.json.content.uri) {
|
|
@@ -146,77 +156,78 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
if (!!url) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
if (url.includes(".b3dm")) {
|
|
160
|
+
self.contentURL = url;
|
|
161
|
+
self.tileLoader.get(this.uuid, url, mesh => {
|
|
162
|
+
if (!!self.deleted) return;
|
|
163
|
+
mesh.traverse((o) => {
|
|
164
|
+
if (o.isMesh) {
|
|
165
|
+
o.material.visible = false;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
self.add(mesh);
|
|
169
|
+
self.meshContent = mesh;
|
|
170
|
+
}, !self.cameraOnLoad ? () => 0 : () => {
|
|
171
|
+
return self.calculateDistanceToCamera(self.cameraOnLoad);
|
|
172
|
+
}, () => self.getSiblings(), self.level, self.uuid);
|
|
173
|
+
} else if (url.includes(".json")) {
|
|
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
|
+
});
|
|
164
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 => { });
|
|
165
198
|
}).catch(error => { });
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (!self.json.children) self.json.children = [];
|
|
171
|
-
json.rootPath = path.dirname(url);
|
|
172
|
-
self.json.children.push(json);
|
|
173
|
-
delete self.json.content;
|
|
174
|
-
self.hasUnloadedJSONContent = false;
|
|
175
|
-
}).catch(error => { });
|
|
176
|
-
}
|
|
177
|
-
}).catch(error => {
|
|
178
|
-
});
|
|
199
|
+
}, 0); */
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
|
|
179
203
|
}
|
|
180
204
|
}
|
|
181
205
|
}
|
|
182
206
|
|
|
183
|
-
|
|
184
|
-
var self = this;
|
|
207
|
+
dispose() {
|
|
185
208
|
|
|
186
|
-
self
|
|
209
|
+
const self = this;
|
|
210
|
+
self.deleted = true;
|
|
211
|
+
this.traverse(function (element) {
|
|
212
|
+
if (!!element.contentURL) {
|
|
213
|
+
self.tileLoader.invalidate(element.contentURL, element.uuid);
|
|
214
|
+
}
|
|
187
215
|
if (!!element.controller) { // abort tile request
|
|
188
216
|
element.controller.abort();
|
|
189
217
|
}
|
|
190
|
-
if (element.material) {
|
|
191
|
-
// dispose materials
|
|
192
|
-
if (element.material.length) {
|
|
193
|
-
for (let i = 0; i < element.material.length; ++i) {
|
|
194
|
-
element.material[i].dispose();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
element.material.dispose()
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
if (element.geometry) {
|
|
203
|
-
// dispose geometry
|
|
204
|
-
element.geometry.dispose();
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
}));
|
|
208
|
-
for (let i = 0; i < this.childrenTiles.length; i++) {
|
|
209
218
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
});
|
|
220
|
+
this.parent = null;
|
|
221
|
+
this.parentTile = null;
|
|
222
|
+
this.dispatchEvent({ type: 'removed' });
|
|
223
|
+
}
|
|
224
|
+
disposeChildren() {
|
|
225
|
+
var self = this;
|
|
215
226
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (!!
|
|
227
|
+
self.childrenTiles.forEach(tile => tile.dispose());
|
|
228
|
+
self.childrenTiles = [];
|
|
229
|
+
self.children = [];
|
|
230
|
+
if (!!self.meshContent) self.children.push(self.meshContent);
|
|
220
231
|
}
|
|
221
232
|
|
|
222
233
|
|
|
@@ -228,14 +239,14 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
228
239
|
_update(camera, frustum) {
|
|
229
240
|
const self = this;
|
|
230
241
|
|
|
242
|
+
self.childrenTiles.forEach(child => child._update(camera, frustum));
|
|
231
243
|
if (!!self.boundingVolume && !!self.geometricError) {
|
|
232
|
-
|
|
244
|
+
self.metric = self.calculateUpdateMetric(camera, frustum);
|
|
233
245
|
}
|
|
234
246
|
|
|
235
|
-
updateNodeVisibility(metric);
|
|
236
|
-
updateTree(metric);
|
|
237
|
-
self.
|
|
238
|
-
trimTree(metric);
|
|
247
|
+
updateNodeVisibility(self.metric);
|
|
248
|
+
updateTree(self.metric);
|
|
249
|
+
trimTree(self.metric);
|
|
239
250
|
|
|
240
251
|
|
|
241
252
|
function updateTree(metric) {
|
|
@@ -293,7 +304,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
293
304
|
self.changeContentVisibility(false);
|
|
294
305
|
} else {
|
|
295
306
|
//self.changeContentVisibility(true);
|
|
296
|
-
|
|
307
|
+
|
|
297
308
|
}
|
|
298
309
|
}
|
|
299
310
|
}
|
|
@@ -316,15 +327,17 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
316
327
|
function loadJsonChildren() {
|
|
317
328
|
self.json.children.forEach(childJSON => {
|
|
318
329
|
let childTile = new OGC3DTile({
|
|
330
|
+
parentTile: self,
|
|
319
331
|
parentGeometricError: self.geometricError,
|
|
320
332
|
parentBoundingVolume: self.boundingVolume,
|
|
321
333
|
parentRefinement: self.refinement,
|
|
322
334
|
json: childJSON,
|
|
323
335
|
rootPath: self.rootPath,
|
|
324
336
|
geometricErrorMultiplier: self.geometricErrorMultiplier,
|
|
325
|
-
meshCallback: self.meshCallback,
|
|
326
337
|
loadOutsideView: self.loadOutsideView,
|
|
327
|
-
level: self.level + 1
|
|
338
|
+
level: self.level + 1,
|
|
339
|
+
tileLoader: self.tileLoader,
|
|
340
|
+
cameraOnLoad: camera
|
|
328
341
|
});
|
|
329
342
|
self.childrenTiles.push(childTile);
|
|
330
343
|
self.add(childTile);
|
|
@@ -376,7 +389,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
376
389
|
return true;
|
|
377
390
|
}
|
|
378
391
|
|
|
379
|
-
return
|
|
392
|
+
return true;
|
|
380
393
|
|
|
381
394
|
}
|
|
382
395
|
|
|
@@ -447,36 +460,46 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
447
460
|
//throw Error("Region bounding volume not supported");
|
|
448
461
|
return -1;
|
|
449
462
|
}
|
|
450
|
-
|
|
451
463
|
}
|
|
452
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
|
+
}
|
|
453
500
|
setGeometricErrorMultiplier(geometricErrorMultiplier) {
|
|
454
501
|
this.geometricErrorMultiplier = geometricErrorMultiplier;
|
|
455
502
|
this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
|
|
456
503
|
}
|
|
457
504
|
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
*
|
|
461
|
-
* @param {Integer} size a number of vertices
|
|
462
|
-
*/
|
|
463
|
-
function createMeshCache(size = 5000000, meshCallback = () => { }) {
|
|
464
|
-
/* return new Cache(
|
|
465
|
-
(url, self)=>{
|
|
466
|
-
fetch(url, { signal: self.controller.signal }).then(result => {
|
|
467
|
-
if (!result.ok) {
|
|
468
|
-
throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
469
|
-
}
|
|
470
|
-
result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
|
|
471
|
-
mesh.traverse((o) => {
|
|
472
|
-
if (o.isMesh) {
|
|
473
|
-
o.material.visible = false;
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
return mesh;
|
|
477
|
-
}).catch(error => { });
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
) */
|
|
481
|
-
}
|
|
482
|
-
export { OGC3DTile, createMeshCache };
|
|
505
|
+
export { OGC3DTile };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { LinkedHashMap } from 'js-utils-z';
|
|
2
|
+
import { B3DMDecoder } from "../decoder/B3DMDecoder";
|
|
3
|
+
|
|
4
|
+
const ready = [];
|
|
5
|
+
const downloads = [];
|
|
6
|
+
const nextReady = [];
|
|
7
|
+
const nextDownloads = [];
|
|
8
|
+
|
|
9
|
+
function scheduleDownload(f) {
|
|
10
|
+
downloads.unshift(f);
|
|
11
|
+
//setTimeout(()=>download(),0);
|
|
12
|
+
}
|
|
13
|
+
function download() {
|
|
14
|
+
if (nextDownloads.length == 0) {
|
|
15
|
+
getNextDownloads();
|
|
16
|
+
if (nextDownloads.length == 0) return;
|
|
17
|
+
}
|
|
18
|
+
const nextDownload = nextDownloads.shift();
|
|
19
|
+
if (!!nextDownload && nextDownload.shouldDoDownload()) {
|
|
20
|
+
nextDownload.doDownload();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function meshReceived(cache, register, key, distanceFunction, getSiblings, level, uuid) {
|
|
24
|
+
ready.unshift([cache, register, key, distanceFunction, getSiblings, level, uuid]);
|
|
25
|
+
//setTimeout(()=>loadBatch(),1);
|
|
26
|
+
}
|
|
27
|
+
function loadBatch() {
|
|
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
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
setInterval(() => {
|
|
119
|
+
download()
|
|
120
|
+
}, 1)
|
|
121
|
+
setInterval(() => {
|
|
122
|
+
loadBatch()
|
|
123
|
+
}, 1)
|
|
124
|
+
|
|
125
|
+
class TileLoader {
|
|
126
|
+
constructor(meshCallback, maxCachedItems) {
|
|
127
|
+
this.meshCallback = meshCallback;
|
|
128
|
+
this.cache = new LinkedHashMap();
|
|
129
|
+
this.maxCachedItems = !!maxCachedItems ? maxCachedItems : 1000;
|
|
130
|
+
this.register = {};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get(tileIdentifier, path, callback, distanceFunction, getSiblings, level, uuid) {
|
|
134
|
+
const self = this;
|
|
135
|
+
const key = simplifyPath(path);
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if (!path.includes(".b3dm") && !path.includes(".json")) {
|
|
139
|
+
console.error("the 3DTiles cache can only be used to load B3DM and json data");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!self.register[key]) {
|
|
143
|
+
self.register[key] = {};
|
|
144
|
+
}
|
|
145
|
+
if (!!self.register[key][tileIdentifier]) {
|
|
146
|
+
console.error(" a tile should only be loaded once");
|
|
147
|
+
}
|
|
148
|
+
self.register[key][tileIdentifier] = callback;
|
|
149
|
+
|
|
150
|
+
const cachedObject = self.cache.get(key);
|
|
151
|
+
if (!!cachedObject) {
|
|
152
|
+
meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
|
|
153
|
+
} else if (Object.keys(self.register[key]).length == 1) {
|
|
154
|
+
let downloadFunction;
|
|
155
|
+
if (path.includes(".b3dm")) {
|
|
156
|
+
downloadFunction = () => {
|
|
157
|
+
fetch(path).then(result => {
|
|
158
|
+
if (!result.ok) {
|
|
159
|
+
console.error("could not load tile with path : " + path)
|
|
160
|
+
throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
161
|
+
}
|
|
162
|
+
result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
|
|
163
|
+
self.cache.put(key, mesh);
|
|
164
|
+
self.checkSize();
|
|
165
|
+
meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
});
|
|
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
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
invalidate(path, tileIdentifier) {
|
|
201
|
+
const key = simplifyPath(path);
|
|
202
|
+
delete this.register[key][tileIdentifier];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
checkSize() {
|
|
206
|
+
const self = this;
|
|
207
|
+
|
|
208
|
+
let i = 0;
|
|
209
|
+
|
|
210
|
+
while (self.cache.size() > self.maxCachedItems && i < self.cache.size()) {
|
|
211
|
+
console.log(self.cache.size())
|
|
212
|
+
i++;
|
|
213
|
+
const entry = self.cache.head();
|
|
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()
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (o.geometry) {
|
|
236
|
+
// dispose geometry
|
|
237
|
+
o.geometry.dispose();
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function simplifyPath(main_path) {
|
|
249
|
+
|
|
250
|
+
var parts = main_path.split('/'),
|
|
251
|
+
new_path = [],
|
|
252
|
+
length = 0;
|
|
253
|
+
for (var i = 0; i < parts.length; i++) {
|
|
254
|
+
var part = parts[i];
|
|
255
|
+
if (part === '.' || part === '' || part === '..') {
|
|
256
|
+
if (part === '..' && length > 0) {
|
|
257
|
+
length--;
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
new_path[length++] = part;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (length === 0) {
|
|
265
|
+
return '/';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
var result = '';
|
|
269
|
+
for (var i = 0; i < length; i++) {
|
|
270
|
+
result += '/' + new_path[i];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export { TileLoader };
|
package/src/cache/Cache.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { LinkedHashMap } from 'js-utils-z';
|
|
3
|
-
|
|
4
|
-
class Cache {
|
|
5
|
-
constructor(loader, counter, dispose, max) {
|
|
6
|
-
this.loader = loader;
|
|
7
|
-
this.counter = counter;
|
|
8
|
-
this.dispose = dispose;
|
|
9
|
-
this.max = max;
|
|
10
|
-
this.currentSize = 0;
|
|
11
|
-
this.objects = new LinkedHashMap();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
get(name){
|
|
15
|
-
if(this.objects.has(name)){
|
|
16
|
-
const item = this.objects.remove(name);
|
|
17
|
-
item.users++;
|
|
18
|
-
this.objects.put(name, item, false);
|
|
19
|
-
return new Promise.resolve({dispose:()=>item.users--,content:item.content});
|
|
20
|
-
}else{
|
|
21
|
-
return this.loader(name).then(content=>{
|
|
22
|
-
const item = { users: 1, content: content };
|
|
23
|
-
this.objects.put(name, item, false);
|
|
24
|
-
currentSize+=this.counter(item);
|
|
25
|
-
checkSize();
|
|
26
|
-
return {dispose:()=>item.users--,content:item.content};
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
checkSize(){
|
|
32
|
-
let object = this.objects.head();
|
|
33
|
-
while(this.currentSize > this.max && !!object){
|
|
34
|
-
if(object.value.users <=0){
|
|
35
|
-
const item = this.objects.remove(object.key);
|
|
36
|
-
this.currentSize -= this.counter(item.content);
|
|
37
|
-
this.dispose(item.content);
|
|
38
|
-
}
|
|
39
|
-
object = object.next();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export{Cache};
|