@jdultra/threedtiles 3.0.8 → 3.1.0
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/index.html +0 -43
- package/package.json +7 -3
- package/src/decoder/B3DMDecoder.js +3 -6
- package/src/index.js +27 -73
- package/src/tileset/OGC3DTile.js +87 -104
- package/src/tileset/TileLoader.js +188 -0
- package/src/tileset/TilesetStats.js +65 -0
- package/src/cache/Cache.js +0 -44
package/index.html
CHANGED
|
@@ -4,53 +4,10 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="utf-8" />
|
|
6
6
|
<title>Three 3DTiles viewer sample</title>
|
|
7
|
-
<style>
|
|
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>
|
|
46
7
|
</head>
|
|
47
8
|
|
|
48
9
|
<body>
|
|
49
10
|
<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
11
|
<div style="position: absolute; bottom: 1%; z-index: 100;">
|
|
55
12
|
<a href="https://openheritage3d.org/project.php?id=taz6-n215">ORIGINAL MODEL</a>
|
|
56
13
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jdultra/threedtiles",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
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
|
@@ -1,81 +1,31 @@
|
|
|
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';
|
|
4
5
|
import { OGC3DTile } from "./tileset/OGC3DTile";
|
|
6
|
+
import { TileLoader } from "./tileset/TileLoader";
|
|
5
7
|
import { MapControls, OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
6
|
-
|
|
7
|
-
|
|
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';
|
|
8
|
+
|
|
9
|
+
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
const scene = initScene();
|
|
13
|
+
const tilesetStats = TilesetStats();
|
|
15
14
|
const domContainer = initDomContainer("screen");
|
|
16
15
|
const camera = initCamera();
|
|
17
16
|
const ogc3DTiles = initTileset(scene);
|
|
18
|
-
initLODMultiplierSlider(ogc3DTiles);
|
|
19
17
|
const controller = initController(camera, domContainer);
|
|
20
|
-
const skybox = initSkybox(controller, camera, scene);
|
|
21
18
|
|
|
22
19
|
const stats = initStats(domContainer);
|
|
23
20
|
const renderer = initRenderer(camera, domContainer);
|
|
24
21
|
|
|
25
22
|
animate();
|
|
26
23
|
|
|
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
24
|
|
|
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
25
|
function initScene() {
|
|
69
26
|
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);
|
|
27
|
+
scene.background = new THREE.Color(0x000000);
|
|
28
|
+
scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
|
|
79
29
|
return scene;
|
|
80
30
|
}
|
|
81
31
|
|
|
@@ -115,16 +65,15 @@ function initRenderer(camera, dom) {
|
|
|
115
65
|
}
|
|
116
66
|
|
|
117
67
|
function initStats(dom) {
|
|
118
|
-
const stats =
|
|
119
|
-
|
|
68
|
+
const stats = Stats();
|
|
69
|
+
document.body.appendChild(stats.dom);
|
|
120
70
|
return stats;
|
|
121
71
|
}
|
|
122
72
|
|
|
123
73
|
|
|
124
74
|
function initCamera() {
|
|
125
75
|
const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1, 10000);
|
|
126
|
-
camera.position.set(
|
|
127
|
-
camera.lookAt(-100, 40, 0);
|
|
76
|
+
camera.position.set(10, 10, 10);
|
|
128
77
|
|
|
129
78
|
return camera;
|
|
130
79
|
}
|
|
@@ -132,28 +81,34 @@ function initCamera() {
|
|
|
132
81
|
function initTileset(scene) {
|
|
133
82
|
|
|
134
83
|
const ogc3DTile = new OGC3DTile({
|
|
135
|
-
|
|
136
|
-
//url: "https://storage.googleapis.com/ogc-3d-tiles/castleX/tileset.json",
|
|
137
|
-
url: "https://storage.googleapis.com/ogc-3d-tiles/berlinSubsetTiled/tileset.json",
|
|
84
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
138
85
|
geometricErrorMultiplier: 1,
|
|
139
86
|
loadOutsideView: true,
|
|
140
|
-
|
|
87
|
+
tileLoader: new TileLoader(mesh => {
|
|
141
88
|
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
142
89
|
mesh.material.wireframe = false;
|
|
143
90
|
mesh.material.side = THREE.DoubleSide;
|
|
144
|
-
}
|
|
91
|
+
}, tilesetStats),
|
|
92
|
+
stats: tilesetStats
|
|
145
93
|
});
|
|
94
|
+
|
|
146
95
|
|
|
147
96
|
//// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
|
|
148
97
|
//ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -10)
|
|
149
98
|
//ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
|
|
150
99
|
//ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
|
|
151
|
-
//ogc3DTile.scale.set(0.
|
|
152
|
-
//ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0),
|
|
100
|
+
//ogc3DTile.scale.set(0.0001,0.0001,0.0001);
|
|
101
|
+
// ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * 0.5) // Z-UP to Y-UP
|
|
102
|
+
// ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
|
|
103
|
+
// ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
|
|
104
|
+
// ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
|
|
153
105
|
//// 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
106
|
|
|
107
|
+
|
|
108
|
+
|
|
155
109
|
var interval ;
|
|
156
110
|
document.addEventListener('keyup', (e) => {
|
|
111
|
+
console.log(camera.position)
|
|
157
112
|
if(!e.key || e.key !== "p") return;
|
|
158
113
|
if(!!interval){
|
|
159
114
|
clearInterval(interval);
|
|
@@ -165,10 +120,9 @@ function initTileset(scene) {
|
|
|
165
120
|
function startInterval(){
|
|
166
121
|
interval = setInterval(function () {
|
|
167
122
|
ogc3DTile.update(camera);
|
|
168
|
-
},
|
|
123
|
+
}, 25);
|
|
169
124
|
}
|
|
170
125
|
startInterval();
|
|
171
|
-
|
|
172
126
|
|
|
173
127
|
scene.add(ogc3DTile)
|
|
174
128
|
return ogc3DTile;
|
|
@@ -177,9 +131,9 @@ function initTileset(scene) {
|
|
|
177
131
|
function initController(camera, dom) {
|
|
178
132
|
const controller = new OrbitControls(camera, dom);
|
|
179
133
|
|
|
180
|
-
controller.target.set(-
|
|
134
|
+
controller.target.set(-11.50895,0.058452500000001, 3.1369285);
|
|
181
135
|
controller.minDistance = 1;
|
|
182
|
-
controller.maxDistance =
|
|
136
|
+
controller.maxDistance = 5000;
|
|
183
137
|
controller.update();
|
|
184
138
|
return controller;
|
|
185
139
|
}
|
|
@@ -190,7 +144,7 @@ function animate() {
|
|
|
190
144
|
|
|
191
145
|
camera.updateMatrixWorld();
|
|
192
146
|
renderer.render(scene, camera);
|
|
193
|
-
|
|
147
|
+
tilesetStats.update();
|
|
194
148
|
stats.update();
|
|
195
149
|
|
|
196
150
|
}
|
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,13 +22,41 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
30
22
|
* parentRefinement: optional,
|
|
31
23
|
* geometricErrorMultiplier: Double,
|
|
32
24
|
* loadOutsideView: Boolean,
|
|
33
|
-
*
|
|
25
|
+
* tileLoader : TileLoader,
|
|
26
|
+
* stats: TilesetStats,
|
|
27
|
+
* meshCallback: function
|
|
34
28
|
* } properties
|
|
35
29
|
*/
|
|
36
30
|
constructor(properties) {
|
|
37
31
|
super();
|
|
32
|
+
const self = this;
|
|
33
|
+
this.uuid = uuidv4();
|
|
34
|
+
if (!!properties.tileLoader) {
|
|
35
|
+
this.tileLoader = properties.tileLoader;
|
|
36
|
+
} else {
|
|
37
|
+
this.tileLoader = new TileLoader(!properties.meshCallback ?
|
|
38
|
+
mesh => {
|
|
39
|
+
mesh.material.wireframe = false;
|
|
40
|
+
mesh.material.side = THREE.DoubleSide;
|
|
41
|
+
} : properties.meshCallback);
|
|
42
|
+
}
|
|
38
43
|
// set properties general to the entire tileset
|
|
39
44
|
this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
|
|
45
|
+
if (properties.stats) {
|
|
46
|
+
// Automatic geometric error multiplier
|
|
47
|
+
this.stats = properties.stats;
|
|
48
|
+
setIntervalAsync(() => {
|
|
49
|
+
const framerate = self.stats.fps();
|
|
50
|
+
if (framerate < 0) return;
|
|
51
|
+
if (framerate < 58) {
|
|
52
|
+
self.setGeometricErrorMultiplier(Math.max(0.05, self.geometricErrorMultiplier - 0.05));
|
|
53
|
+
} else if (framerate > 58) {
|
|
54
|
+
self.setGeometricErrorMultiplier(self.geometricErrorMultiplier + 0.05);
|
|
55
|
+
}
|
|
56
|
+
self.setGeometricErrorMultiplier(self.geometricErrorMultiplier * (self.stats.fps() / 60));
|
|
57
|
+
}, 1000);
|
|
58
|
+
}
|
|
59
|
+
|
|
40
60
|
this.meshCallback = properties.meshCallback;
|
|
41
61
|
this.loadOutsideView = properties.loadOutsideView;
|
|
42
62
|
|
|
@@ -55,7 +75,6 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
55
75
|
this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
|
|
56
76
|
this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
|
|
57
77
|
|
|
58
|
-
const self = this;
|
|
59
78
|
if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
|
|
60
79
|
self.setup(properties);
|
|
61
80
|
} else if (properties.url) { // If only the url to the tileset.json is provided
|
|
@@ -95,9 +114,9 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
95
114
|
}
|
|
96
115
|
// decode transform
|
|
97
116
|
if (!!this.json.transform) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.
|
|
117
|
+
let mat = new THREE.Matrix4();
|
|
118
|
+
mat.elements = this.json.transform;
|
|
119
|
+
this.applyMatrix4(mat);
|
|
101
120
|
}
|
|
102
121
|
// decode volume
|
|
103
122
|
if (!!this.json.boundingVolume) {
|
|
@@ -124,11 +143,13 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
124
143
|
} else {
|
|
125
144
|
this.hasMeshContent = true;
|
|
126
145
|
}
|
|
127
|
-
|
|
146
|
+
this.load();
|
|
147
|
+
//scheduleLoadTile(this);
|
|
128
148
|
}
|
|
129
149
|
}
|
|
130
150
|
load() {
|
|
131
151
|
var self = this;
|
|
152
|
+
if (self.deleted) return;
|
|
132
153
|
if (!!self.json.content) {
|
|
133
154
|
let url;
|
|
134
155
|
if (!!self.json.content.uri) {
|
|
@@ -146,24 +167,24 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
146
167
|
}
|
|
147
168
|
|
|
148
169
|
if (!!url) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
if (url.includes(".b3dm")) {
|
|
171
|
+
self.contentURL = url;
|
|
172
|
+
self.tileLoader.get(this.uuid, url, mesh => {
|
|
173
|
+
if (!!self.deleted) return;
|
|
174
|
+
mesh.traverse((o) => {
|
|
175
|
+
if (o.isMesh) {
|
|
176
|
+
o.material.visible = false;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
self.add(mesh);
|
|
180
|
+
self.meshContent = mesh;
|
|
181
|
+
})
|
|
182
|
+
} else if (url.includes(".json")) {
|
|
183
|
+
self.controller = new AbortController();
|
|
184
|
+
fetch(url, { signal: self.controller.signal }).then(result => {
|
|
185
|
+
if (!result.ok) {
|
|
186
|
+
throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
187
|
+
}
|
|
167
188
|
result.json().then(json => {
|
|
168
189
|
// when json content is downloaded, it is inserted into this tile's original JSON as a child
|
|
169
190
|
// and the content object is deleted from the original JSON
|
|
@@ -173,50 +194,36 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
173
194
|
delete self.json.content;
|
|
174
195
|
self.hasUnloadedJSONContent = false;
|
|
175
196
|
}).catch(error => { });
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
197
|
+
}).catch(error => { });
|
|
198
|
+
}
|
|
199
|
+
|
|
179
200
|
}
|
|
180
201
|
}
|
|
181
202
|
}
|
|
182
203
|
|
|
183
|
-
|
|
184
|
-
var self = this;
|
|
204
|
+
dispose() {
|
|
185
205
|
|
|
186
|
-
self
|
|
206
|
+
const self = this;
|
|
207
|
+
self.deleted = true;
|
|
208
|
+
this.traverse(function (element) {
|
|
209
|
+
if (!!element.contentURL) {
|
|
210
|
+
self.tileLoader.invalidate(element.contentURL, element.uuid);
|
|
211
|
+
}
|
|
187
212
|
if (!!element.controller) { // abort tile request
|
|
188
213
|
element.controller.abort();
|
|
189
214
|
}
|
|
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
215
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
216
|
+
});
|
|
217
|
+
this.parent = null;
|
|
218
|
+
this.dispatchEvent({ type: 'removed' });
|
|
219
|
+
}
|
|
220
|
+
disposeChildren() {
|
|
221
|
+
var self = this;
|
|
215
222
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (!!
|
|
223
|
+
self.childrenTiles.forEach(tile => tile.dispose());
|
|
224
|
+
self.childrenTiles = [];
|
|
225
|
+
self.children = [];
|
|
226
|
+
if (!!self.meshContent) self.children.push(self.meshContent);
|
|
220
227
|
}
|
|
221
228
|
|
|
222
229
|
|
|
@@ -228,14 +235,14 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
228
235
|
_update(camera, frustum) {
|
|
229
236
|
const self = this;
|
|
230
237
|
|
|
238
|
+
self.childrenTiles.forEach(child => child._update(camera, frustum));
|
|
231
239
|
if (!!self.boundingVolume && !!self.geometricError) {
|
|
232
|
-
|
|
240
|
+
self.metric = self.calculateUpdateMetric(camera, frustum);
|
|
233
241
|
}
|
|
234
242
|
|
|
235
|
-
updateNodeVisibility(metric);
|
|
236
|
-
updateTree(metric);
|
|
237
|
-
self.
|
|
238
|
-
trimTree(metric);
|
|
243
|
+
updateNodeVisibility(self.metric);
|
|
244
|
+
updateTree(self.metric);
|
|
245
|
+
trimTree(self.metric);
|
|
239
246
|
|
|
240
247
|
|
|
241
248
|
function updateTree(metric) {
|
|
@@ -293,7 +300,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
293
300
|
self.changeContentVisibility(false);
|
|
294
301
|
} else {
|
|
295
302
|
//self.changeContentVisibility(true);
|
|
296
|
-
|
|
303
|
+
|
|
297
304
|
}
|
|
298
305
|
}
|
|
299
306
|
}
|
|
@@ -322,9 +329,9 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
322
329
|
json: childJSON,
|
|
323
330
|
rootPath: self.rootPath,
|
|
324
331
|
geometricErrorMultiplier: self.geometricErrorMultiplier,
|
|
325
|
-
meshCallback: self.meshCallback,
|
|
326
332
|
loadOutsideView: self.loadOutsideView,
|
|
327
|
-
level: self.level + 1
|
|
333
|
+
level: self.level + 1,
|
|
334
|
+
tileLoader: self.tileLoader
|
|
328
335
|
});
|
|
329
336
|
self.childrenTiles.push(childTile);
|
|
330
337
|
self.add(childTile);
|
|
@@ -376,7 +383,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
376
383
|
return true;
|
|
377
384
|
}
|
|
378
385
|
|
|
379
|
-
return
|
|
386
|
+
return true;
|
|
380
387
|
|
|
381
388
|
}
|
|
382
389
|
|
|
@@ -455,28 +462,4 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
455
462
|
this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
|
|
456
463
|
}
|
|
457
464
|
}
|
|
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 };
|
|
465
|
+
export { OGC3DTile };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { LinkedHashMap } from 'js-utils-z';
|
|
2
|
+
import { B3DMDecoder } from "../decoder/B3DMDecoder";
|
|
3
|
+
import { setIntervalAsync } from 'set-interval-async/dynamic';
|
|
4
|
+
|
|
5
|
+
const ready = [];
|
|
6
|
+
const downloads = [];
|
|
7
|
+
function scheduleDownload(f) {
|
|
8
|
+
downloads.unshift(f);
|
|
9
|
+
}
|
|
10
|
+
function download() {
|
|
11
|
+
if(downloads.length <=0) return;
|
|
12
|
+
const nextDownload = downloads.shift();
|
|
13
|
+
if (!!nextDownload && nextDownload.shouldDoDownload()) {
|
|
14
|
+
nextDownload.doDownload();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function meshReceived(cache, register, key) {
|
|
18
|
+
ready.unshift([cache, register, key]);
|
|
19
|
+
}
|
|
20
|
+
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
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
setIntervalAsync(() => {
|
|
40
|
+
loadBatch();
|
|
41
|
+
}, 10)
|
|
42
|
+
setIntervalAsync(() => {
|
|
43
|
+
download();
|
|
44
|
+
}, 10)
|
|
45
|
+
|
|
46
|
+
class TileLoader {
|
|
47
|
+
constructor(meshCallback, stats) {
|
|
48
|
+
this.meshCallback = meshCallback;
|
|
49
|
+
this.cache = new LinkedHashMap();
|
|
50
|
+
this.maxSize = 1000;
|
|
51
|
+
this.stats = stats;
|
|
52
|
+
this.register = {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get(tileIdentifier, path, callback) {
|
|
56
|
+
const self = this;
|
|
57
|
+
const key = simplifyPath(path);
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if (!path.includes(".b3dm")) {
|
|
61
|
+
console.error("the 3DTiles cache can only be used to load B3DM data");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!self.register[key]) {
|
|
65
|
+
self.register[key] = {};
|
|
66
|
+
}
|
|
67
|
+
if (!!self.register[key][tileIdentifier]) {
|
|
68
|
+
console.error(" a tile should only be loaded once");
|
|
69
|
+
}
|
|
70
|
+
self.register[key][tileIdentifier] = callback;
|
|
71
|
+
|
|
72
|
+
const cachedObject = self.cache.get(key);
|
|
73
|
+
if (!!cachedObject) {
|
|
74
|
+
meshReceived(self.cache, self.register, key);
|
|
75
|
+
} 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": () => {
|
|
81
|
+
fetch(path).then(result => {
|
|
82
|
+
if (!result.ok) {
|
|
83
|
+
console.error("could not load tile with path : " + path)
|
|
84
|
+
throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
85
|
+
}
|
|
86
|
+
result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
|
|
87
|
+
self.cache.put(key, mesh);
|
|
88
|
+
self.checkSize();
|
|
89
|
+
meshReceived(self.cache, self.register, key);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
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
|
+
}
|
|
108
|
+
|
|
109
|
+
invalidate(path, tileIdentifier) {
|
|
110
|
+
const key = simplifyPath(path);
|
|
111
|
+
delete this.register[key][tileIdentifier];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
checkSize() {
|
|
115
|
+
const self = this;
|
|
116
|
+
|
|
117
|
+
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()) {
|
|
128
|
+
i++;
|
|
129
|
+
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();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
o.material.dispose()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (o.geometry) {
|
|
150
|
+
// dispose geometry
|
|
151
|
+
o.geometry.dispose();
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function simplifyPath(main_path) {
|
|
161
|
+
|
|
162
|
+
var parts = main_path.split('/'),
|
|
163
|
+
new_path = [],
|
|
164
|
+
length = 0;
|
|
165
|
+
for (var i = 0; i < parts.length; i++) {
|
|
166
|
+
var part = parts[i];
|
|
167
|
+
if (part === '.' || part === '' || part === '..') {
|
|
168
|
+
if (part === '..' && length > 0) {
|
|
169
|
+
length--;
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
new_path[length++] = part;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (length === 0) {
|
|
177
|
+
return '/';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
var result = '';
|
|
181
|
+
for (var i = 0; i < length; i++) {
|
|
182
|
+
result += '/' + new_path[i];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { TileLoader };
|
|
@@ -0,0 +1,65 @@
|
|
|
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;
|
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};
|