@jdultra/threedtiles 3.3.1 → 4.0.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/README.md +39 -12
- package/index.html +1 -4
- package/package.json +14 -13
- package/src/decoder/B3DMDecoder.js +63 -64
- package/src/index.js +149 -47
- package/src/tileset/OGC3DTile.js +8 -8
- package/src/tileset/TileLoader.js +29 -29
- package/src/tileset/instanced/InstancedOGC3DTile.js +45 -0
- package/src/tileset/instanced/InstancedTile.js +510 -0
- package/src/tileset/instanced/InstancedTileLoader.js +322 -0
- package/src/tileset/instanced/JsonTile.js +41 -0
- package/src/tileset/instanced/MeshTile.js +75 -0
- package/webpack.config.js +117 -107
|
@@ -26,12 +26,6 @@ class TileLoader {
|
|
|
26
26
|
const self = this;
|
|
27
27
|
setIntervalAsync(() => {
|
|
28
28
|
self.download();
|
|
29
|
-
/* const start = Date.now();
|
|
30
|
-
let uploaded = 0;
|
|
31
|
-
do{
|
|
32
|
-
uploaded = download();
|
|
33
|
-
}while(uploaded > 0 && (Date.now() - start)<= 2 ) */
|
|
34
|
-
|
|
35
29
|
}, 10);
|
|
36
30
|
setIntervalAsync(() => {
|
|
37
31
|
const start = Date.now();
|
|
@@ -57,9 +51,6 @@ class TileLoader {
|
|
|
57
51
|
nextDownload.doDownload();
|
|
58
52
|
}
|
|
59
53
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
54
|
return;
|
|
64
55
|
}
|
|
65
56
|
meshReceived(cache, register, key, distanceFunction, getSiblings, level, uuid) {
|
|
@@ -76,11 +67,7 @@ class TileLoader {
|
|
|
76
67
|
const register = data[1];
|
|
77
68
|
const key = data[2];
|
|
78
69
|
const mesh = cache.get(key);
|
|
79
|
-
|
|
80
|
-
console.log("instanced");
|
|
81
|
-
}else{
|
|
82
|
-
console.log(" not instanced");
|
|
83
|
-
}
|
|
70
|
+
|
|
84
71
|
if (!!mesh && !!register[key]) {
|
|
85
72
|
Object.keys(register[key]).forEach(tile => {
|
|
86
73
|
const callback = register[key][tile];
|
|
@@ -164,10 +151,16 @@ class TileLoader {
|
|
|
164
151
|
}
|
|
165
152
|
|
|
166
153
|
|
|
167
|
-
get(tileIdentifier, path, callback, distanceFunction, getSiblings, level) {
|
|
154
|
+
get(abortController, tileIdentifier, path, callback, distanceFunction, getSiblings, level) {
|
|
168
155
|
const self = this;
|
|
169
156
|
const key = simplifyPath(path);
|
|
170
157
|
|
|
158
|
+
const realAbortController = new AbortController();
|
|
159
|
+
abortController.signal.addEventListener("abort", ()=>{
|
|
160
|
+
if(!self.register[key] || Object.keys(self.register[key]).length == 0){
|
|
161
|
+
realAbortController.abort();
|
|
162
|
+
}
|
|
163
|
+
})
|
|
171
164
|
|
|
172
165
|
if (!path.includes(".b3dm") && !path.includes(".json")) {
|
|
173
166
|
console.error("the 3DTiles cache can only be used to load B3DM and json data");
|
|
@@ -189,40 +182,47 @@ class TileLoader {
|
|
|
189
182
|
if (path.includes(".b3dm")) {
|
|
190
183
|
downloadFunction = () => {
|
|
191
184
|
concurentDownloads++;
|
|
192
|
-
fetch(path).then(result => {
|
|
185
|
+
fetch(path, {signal: realAbortController.signal}).then(result => {
|
|
193
186
|
concurentDownloads--;
|
|
194
187
|
if (!result.ok) {
|
|
195
188
|
console.error("could not load tile with path : " + path)
|
|
196
189
|
throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
197
190
|
}
|
|
198
|
-
result.arrayBuffer()
|
|
199
|
-
|
|
191
|
+
return result.arrayBuffer();
|
|
192
|
+
|
|
193
|
+
})
|
|
194
|
+
.then(resultArrayBuffer=>{
|
|
195
|
+
return B3DMDecoder.parseB3DM(resultArrayBuffer, self.meshCallback);
|
|
196
|
+
})
|
|
197
|
+
.then(mesh=>{
|
|
198
|
+
self.cache.put(key, mesh);
|
|
200
199
|
self.checkSize();
|
|
201
200
|
this.meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, tileIdentifier);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
201
|
+
})
|
|
202
|
+
.catch(()=>{});;
|
|
205
203
|
}
|
|
206
204
|
} else if (path.includes(".json")) {
|
|
207
205
|
downloadFunction = () => {
|
|
208
206
|
concurentDownloads++;
|
|
209
|
-
fetch(path).then(result => {
|
|
207
|
+
fetch(path, {signal: realAbortController.signal}).then(result => {
|
|
210
208
|
concurentDownloads--;
|
|
211
209
|
if (!result.ok) {
|
|
212
210
|
console.error("could not load tile with path : " + path)
|
|
213
211
|
throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
214
212
|
}
|
|
215
|
-
result.json()
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
213
|
+
return result.json();
|
|
214
|
+
|
|
215
|
+
}).then(json => {
|
|
216
|
+
self.cache.put(key, json);
|
|
217
|
+
self.checkSize();
|
|
218
|
+
this.meshReceived(self.cache, self.register, key);
|
|
219
|
+
})
|
|
220
|
+
.catch(e=>console.error("tile download aborted"));
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
this.scheduleDownload({
|
|
224
224
|
"shouldDoDownload": () => {
|
|
225
|
-
return !!self.register[key] && Object.keys(self.register[key]).length > 0;
|
|
225
|
+
return !abortController.signal.aborted && !!self.register[key] && Object.keys(self.register[key]).length > 0;
|
|
226
226
|
},
|
|
227
227
|
"doDownload": downloadFunction,
|
|
228
228
|
"distanceFunction": distanceFunction,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import {InstancedTile} from "./InstancedTile.js"
|
|
3
|
+
|
|
4
|
+
class InstancedOGC3DTile extends THREE.Object3D {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {
|
|
9
|
+
* json: optional,
|
|
10
|
+
* url: optional,
|
|
11
|
+
* rootPath: optional,
|
|
12
|
+
* parentGeometricError: optional,
|
|
13
|
+
* parentBoundingVolume: optional,
|
|
14
|
+
* parentRefinement: optional,
|
|
15
|
+
* geometricErrorMultiplier: Double,
|
|
16
|
+
* loadOutsideView: Boolean,
|
|
17
|
+
* tileLoader : InstancedTileLoader,
|
|
18
|
+
* meshCallback: function,
|
|
19
|
+
* cameraOnLoad: camera,
|
|
20
|
+
* parentTile: OGC3DTile,
|
|
21
|
+
* onLoadCallback: function,
|
|
22
|
+
* static: Boolean
|
|
23
|
+
* } properties
|
|
24
|
+
*/
|
|
25
|
+
constructor(properties) {
|
|
26
|
+
super();
|
|
27
|
+
properties.master = this;
|
|
28
|
+
this.geometricErrorMultiplier = properties.geometricErrorMultiplier? properties.geometricErrorMultiplier:1.0;
|
|
29
|
+
this.tileset = new InstancedTile(properties);
|
|
30
|
+
if (properties.static) {
|
|
31
|
+
this.matrixAutoUpdate = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
update(camera){
|
|
36
|
+
const frustum = new THREE.Frustum();
|
|
37
|
+
frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));
|
|
38
|
+
this.tileset._update(camera, frustum);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setGeometricErrorMultiplier(geometricErrorMultiplier) {
|
|
42
|
+
this.geometricErrorMultiplier = geometricErrorMultiplier?geometricErrorMultiplier:1.0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export { InstancedOGC3DTile };
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { OBB } from "../../geometry/obb";
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
import * as path from "path-browserify";
|
|
5
|
+
import * as _ from "lodash";
|
|
6
|
+
|
|
7
|
+
const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1));
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InstancedTile extends THREE.Object3D {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {
|
|
15
|
+
* json: optional,
|
|
16
|
+
* url: optional,
|
|
17
|
+
* rootPath: optional,
|
|
18
|
+
* parentGeometricError: optional,
|
|
19
|
+
* parentBoundingVolume: optional,
|
|
20
|
+
* parentRefinement: optional,
|
|
21
|
+
* loadOutsideView: Boolean,
|
|
22
|
+
* tileLoader : InstancedTileLoader,
|
|
23
|
+
* meshCallback: function,
|
|
24
|
+
* cameraOnLoad: camera,
|
|
25
|
+
* parentTile: OGC3DTile,
|
|
26
|
+
* onLoadCallback: function,
|
|
27
|
+
* static: Boolean
|
|
28
|
+
* } properties
|
|
29
|
+
*/
|
|
30
|
+
constructor(properties) {
|
|
31
|
+
super();
|
|
32
|
+
const self = this;
|
|
33
|
+
|
|
34
|
+
this.uuid = uuidv4();
|
|
35
|
+
if (!!properties.tileLoader) {
|
|
36
|
+
this.tileLoader = properties.tileLoader;
|
|
37
|
+
} else {
|
|
38
|
+
console.error("an instanced tileset must be provided an InstancedTilesetLoader");
|
|
39
|
+
}
|
|
40
|
+
// set properties general to the entire tileset
|
|
41
|
+
this.master = properties.master;
|
|
42
|
+
this.meshCallback = properties.meshCallback;
|
|
43
|
+
this.loadOutsideView = properties.loadOutsideView;
|
|
44
|
+
this.cameraOnLoad = properties.cameraOnLoad;
|
|
45
|
+
this.parentTile = properties.parentTile;
|
|
46
|
+
|
|
47
|
+
// declare properties specific to the tile for clarity
|
|
48
|
+
this.childrenTiles = [];
|
|
49
|
+
this.jsonChildren = [];
|
|
50
|
+
this.meshContent;
|
|
51
|
+
|
|
52
|
+
this.tileContent;
|
|
53
|
+
this.refinement; // defaults to "REPLACE"
|
|
54
|
+
this.rootPath;
|
|
55
|
+
this.geometricError;
|
|
56
|
+
this.boundingVolume;
|
|
57
|
+
this.json; // the json corresponding to this tile
|
|
58
|
+
this.materialVisibility = false;
|
|
59
|
+
this.inFrustum = true;
|
|
60
|
+
this.level = properties.level ? properties.level : 0;
|
|
61
|
+
this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
|
|
62
|
+
this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
|
|
63
|
+
|
|
64
|
+
this.deleted = false;
|
|
65
|
+
this.abortController = new AbortController();
|
|
66
|
+
|
|
67
|
+
if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
|
|
68
|
+
this.rootPath = !!properties.json.rootPath ? properties.json.rootPath : properties.rootPath;
|
|
69
|
+
if(properties.json.children) this.jsonChildren = properties.json.children;
|
|
70
|
+
self.setup(properties);
|
|
71
|
+
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
72
|
+
} else if (properties.url) { // If only the url to the tileset.json is provided
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
this.loadJson = (json, url)=>{
|
|
76
|
+
//json = _.cloneDeep(json)
|
|
77
|
+
//json = JSON.parse(JSON.stringify(json))
|
|
78
|
+
const p = path.dirname(url);
|
|
79
|
+
self.setup({ rootPath: p, json: json });
|
|
80
|
+
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
81
|
+
}
|
|
82
|
+
self.tileLoader.get(self.abortController, properties.url, self.uuid, self);
|
|
83
|
+
|
|
84
|
+
/* fetch(properties.url, { signal: self.abortController.signal }).then(result => {
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
87
|
+
}
|
|
88
|
+
result.json().then(json => {
|
|
89
|
+
const p = path.dirname(properties.url);
|
|
90
|
+
self.setup({ rootPath: p, json: json });
|
|
91
|
+
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
92
|
+
});
|
|
93
|
+
}); */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setup(properties) {
|
|
98
|
+
this.isSetup = true;
|
|
99
|
+
if (!!properties.json.root) {
|
|
100
|
+
this.json = properties.json.root;
|
|
101
|
+
this.jsonChildren = this.json.children;
|
|
102
|
+
if (!this.json.refinement) this.json.refinement = properties.json.refinement;
|
|
103
|
+
if (!this.json.geometricError) this.json.geometricError = properties.json.geometricError;
|
|
104
|
+
if (!this.json.transform) this.json.transform = properties.json.transform;
|
|
105
|
+
if (!this.json.boundingVolume) this.json.boundingVolume = properties.json.boundingVolume;
|
|
106
|
+
} else {
|
|
107
|
+
this.json = properties.json;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.rootPath = !!properties.json.rootPath ? properties.json.rootPath : properties.rootPath;
|
|
111
|
+
|
|
112
|
+
// decode refinement
|
|
113
|
+
if (!!this.json.refinement) {
|
|
114
|
+
this.refinement = this.json.refinement;
|
|
115
|
+
} else {
|
|
116
|
+
this.refinement = properties.parentRefinement;
|
|
117
|
+
}
|
|
118
|
+
// decode geometric error
|
|
119
|
+
if (!!this.json.geometricError) {
|
|
120
|
+
this.geometricError = this.json.geometricError;
|
|
121
|
+
} else {
|
|
122
|
+
this.geometricError = properties.parentGeometricError;
|
|
123
|
+
}
|
|
124
|
+
// decode transform
|
|
125
|
+
if (!!this.json.transform) {
|
|
126
|
+
let mat = new THREE.Matrix4();
|
|
127
|
+
mat.elements = this.json.transform;
|
|
128
|
+
this.master.applyMatrix4(mat);
|
|
129
|
+
}
|
|
130
|
+
// decode volume
|
|
131
|
+
if (!!this.json.boundingVolume) {
|
|
132
|
+
if (!!this.json.boundingVolume.box) {
|
|
133
|
+
this.boundingVolume = new OBB(this.json.boundingVolume.box);
|
|
134
|
+
} else if (!!this.json.boundingVolume.region) {
|
|
135
|
+
const region = this.json.boundingVolume.region;
|
|
136
|
+
this.boundingVolume = new THREE.Box3(new THREE.Vector3(region[0], region[2], region[4]), new THREE.Vector3(region[1], region[3], region[5]));
|
|
137
|
+
} else if (!!this.json.boundingVolume.sphere) {
|
|
138
|
+
const sphere = this.json.boundingVolume.sphere;
|
|
139
|
+
this.boundingVolume = new THREE.Sphere(new THREE.Vector3(sphere[0], sphere[2], -sphere[1]), sphere[3]);
|
|
140
|
+
} else {
|
|
141
|
+
this.boundingVolume = properties.parentBoundingVolume;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
this.boundingVolume = properties.parentBoundingVolume;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!!this.json.content) { //if there is a content, json or otherwise, schedule it to be loaded
|
|
148
|
+
if (!!this.json.content.uri && this.json.content.uri.includes("json")) {
|
|
149
|
+
this.hasUnloadedJSONContent = true;
|
|
150
|
+
} else if (!!this.json.content.url && this.json.content.url.includes("json")) {
|
|
151
|
+
this.hasUnloadedJSONContent = true;
|
|
152
|
+
} else {
|
|
153
|
+
this.hasMeshContent = true;
|
|
154
|
+
}
|
|
155
|
+
this.load();
|
|
156
|
+
//scheduleLoadTile(this);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
load() {
|
|
160
|
+
var self = this;
|
|
161
|
+
if (self.deleted) return;
|
|
162
|
+
if (!!self.json.content) {
|
|
163
|
+
let url;
|
|
164
|
+
if (!!self.json.content.uri) {
|
|
165
|
+
if (path.isAbsolute(self.json.content.uri)) {
|
|
166
|
+
url = self.json.content.uri;
|
|
167
|
+
} else {
|
|
168
|
+
url = self.rootPath + path.sep + self.json.content.uri;
|
|
169
|
+
}
|
|
170
|
+
} else if (!!self.json.content.url) {
|
|
171
|
+
if (path.isAbsolute(self.json.content.url)) {
|
|
172
|
+
url = self.json.content.url;
|
|
173
|
+
} else {
|
|
174
|
+
url = self.rootPath + path.sep + self.json.content.url;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!!url) {
|
|
179
|
+
if (url.includes(".b3dm")) {
|
|
180
|
+
self.contentURL = url;
|
|
181
|
+
|
|
182
|
+
self.tileLoader.get(self.abortController, url, self.uuid, self, !self.cameraOnLoad ? () => 0 : () => {
|
|
183
|
+
return self.calculateDistanceToCamera(self.cameraOnLoad);
|
|
184
|
+
}, () => self.getSiblings(), self.level);
|
|
185
|
+
} else if (url.includes(".json")) {
|
|
186
|
+
self.tileLoader.get(self.abortController, url, self.uuid, self);
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
loadMesh(mesh) {
|
|
195
|
+
const self = this;
|
|
196
|
+
if (self.deleted) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
//self.updateWorldMatrix(false, true);
|
|
201
|
+
self.meshContent = mesh;
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
loadJson(json, url) {
|
|
206
|
+
if (this.deleted) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if(!!this.json.children){
|
|
210
|
+
this.jsonChildren = this.json.children;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
json.rootPath = path.dirname(url);
|
|
214
|
+
this.jsonChildren.push(json);
|
|
215
|
+
this.hasUnloadedJSONContent = false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
dispose() {
|
|
219
|
+
|
|
220
|
+
const self = this;
|
|
221
|
+
self.childrenTiles.forEach(tile => tile.dispose());
|
|
222
|
+
self.deleted = true;
|
|
223
|
+
if (self.abortController) self.abortController.abort();
|
|
224
|
+
this.parent = null;
|
|
225
|
+
this.parentTile = null;
|
|
226
|
+
this.dispatchEvent({ type: 'removed' });
|
|
227
|
+
}
|
|
228
|
+
disposeChildren() {
|
|
229
|
+
var self = this;
|
|
230
|
+
|
|
231
|
+
self.childrenTiles.forEach(tile => tile.dispose());
|
|
232
|
+
self.childrenTiles = [];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
_update(camera, frustum) {
|
|
237
|
+
const self = this;
|
|
238
|
+
const visibilityBeforeUpdate = self.materialVisibility;
|
|
239
|
+
|
|
240
|
+
if (!!self.boundingVolume && !!self.geometricError) {
|
|
241
|
+
self.metric = self.calculateUpdateMetric(camera, frustum);
|
|
242
|
+
}
|
|
243
|
+
self.childrenTiles.forEach(child => child._update(camera, frustum));
|
|
244
|
+
|
|
245
|
+
updateNodeVisibility(self.metric);
|
|
246
|
+
updateTree(self.metric);
|
|
247
|
+
trimTree(self.metric, visibilityBeforeUpdate);
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
function updateTree(metric) {
|
|
251
|
+
// If this tile does not have mesh content but it has children
|
|
252
|
+
if (metric < 0 && self.hasMeshContent) return;
|
|
253
|
+
|
|
254
|
+
if ((!self.hasMeshContent && self.rootPath) || (metric < self.geometricError && !!self.meshContent)) {
|
|
255
|
+
if (!!self.json && !!self.jsonChildren && self.childrenTiles.length != self.jsonChildren.length) {
|
|
256
|
+
loadJsonChildren();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function updateNodeVisibility(metric) {
|
|
263
|
+
|
|
264
|
+
//doesn't have a mesh content
|
|
265
|
+
if (!self.hasMeshContent) return;
|
|
266
|
+
|
|
267
|
+
// mesh content not yet loaded
|
|
268
|
+
if (!self.meshContent) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// outside frustum
|
|
273
|
+
if (metric < 0) {
|
|
274
|
+
self.inFrustum = false;
|
|
275
|
+
self.changeContentVisibility(!!self.loadOutsideView);
|
|
276
|
+
return;
|
|
277
|
+
} else {
|
|
278
|
+
self.inFrustum = true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// has no children
|
|
282
|
+
if (self.childrenTiles.length == 0) {
|
|
283
|
+
self.changeContentVisibility(true);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// has children
|
|
288
|
+
if (metric >= self.geometricError) { // Ideal LOD or before ideal lod
|
|
289
|
+
|
|
290
|
+
self.changeContentVisibility(true);
|
|
291
|
+
} else if (metric < self.geometricError) { // Ideal LOD is past this one
|
|
292
|
+
// if children are visible and have been displayed, can be hidden
|
|
293
|
+
let allChildrenReady = true;
|
|
294
|
+
self.childrenTiles.every(child => {
|
|
295
|
+
|
|
296
|
+
if (!child.isReady()) {
|
|
297
|
+
allChildrenReady = false;
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
302
|
+
if (allChildrenReady) {
|
|
303
|
+
self.changeContentVisibility(false);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function trimTree(metric, visibilityBeforeUpdate) {
|
|
309
|
+
if (!self.hasMeshContent) return;
|
|
310
|
+
if (!self.inFrustum) { // outside frustum
|
|
311
|
+
self.disposeChildren();
|
|
312
|
+
updateNodeVisibility(metric);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (metric >= self.geometricError) {
|
|
316
|
+
self.disposeChildren();
|
|
317
|
+
updateNodeVisibility();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function loadJsonChildren() {
|
|
324
|
+
self.jsonChildren.forEach(childJSON => {
|
|
325
|
+
let childTile = new InstancedTile({
|
|
326
|
+
parentTile: self,
|
|
327
|
+
parentGeometricError: self.geometricError,
|
|
328
|
+
parentBoundingVolume: self.boundingVolume,
|
|
329
|
+
parentRefinement: self.refinement,
|
|
330
|
+
json: childJSON,
|
|
331
|
+
rootPath: self.rootPath,
|
|
332
|
+
loadOutsideView: self.loadOutsideView,
|
|
333
|
+
level: self.level + 1,
|
|
334
|
+
tileLoader: self.tileLoader,
|
|
335
|
+
cameraOnLoad: camera,
|
|
336
|
+
static: self.static,
|
|
337
|
+
master: self.master
|
|
338
|
+
});
|
|
339
|
+
self.childrenTiles.push(childTile);
|
|
340
|
+
//self.add(childTile);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
areAllChildrenLoadedAndHidden() {
|
|
347
|
+
let allLoadedAndHidden = true;
|
|
348
|
+
const self = this;
|
|
349
|
+
this.childrenTiles.every(child => {
|
|
350
|
+
if (child.hasMeshContent) {
|
|
351
|
+
if (child.childrenTiles.length > 0) {
|
|
352
|
+
allLoadedAndHidden = false;
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
if (!child.inFrustum) {
|
|
356
|
+
return true;
|
|
357
|
+
};
|
|
358
|
+
if (!child.materialVisibility || child.meshesToDisplay != child.meshesDisplayed) {
|
|
359
|
+
allLoadedAndHidden = false;
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
if (!child.areAllChildrenLoadedAndHidden()) {
|
|
364
|
+
allLoadedAndHidden = false;
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return true;
|
|
369
|
+
});
|
|
370
|
+
return allLoadedAndHidden;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Node is ready if it is outside frustum, if it was drawn at least once or if all it's children are ready
|
|
375
|
+
* @returns true if ready
|
|
376
|
+
*/
|
|
377
|
+
isReady() {
|
|
378
|
+
// if outside frustum
|
|
379
|
+
if (!this.inFrustum) return true;
|
|
380
|
+
|
|
381
|
+
// if json is not done loading
|
|
382
|
+
if (this.hasUnloadedJSONContent) return false;
|
|
383
|
+
|
|
384
|
+
// if this tile has no mesh content or if it's marked as visible false, look at children
|
|
385
|
+
if ((!this.hasMeshContent || !this.meshContent || !this.materialVisibility) && this.childrenTiles.length > 0) {
|
|
386
|
+
var allChildrenReady = true;
|
|
387
|
+
this.childrenTiles.every(child => {
|
|
388
|
+
if (!child.isReady()) {
|
|
389
|
+
allChildrenReady = false;
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
});
|
|
394
|
+
return allChildrenReady;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// if this tile has no mesh content
|
|
398
|
+
if (!this.hasMeshContent) {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
// if mesh content not yet loaded
|
|
402
|
+
if (!this.meshContent) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// if this tile has been marked to hide it's content
|
|
407
|
+
if (!this.materialVisibility) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// if all meshes have been displayed once
|
|
412
|
+
if (!this.meshContent.displayedOnce) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
return true;
|
|
416
|
+
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
changeContentVisibility(visibility) {
|
|
421
|
+
const self = this;
|
|
422
|
+
self.materialVisibility = visibility;
|
|
423
|
+
|
|
424
|
+
/* self.meshContent.displayedOnce = false;
|
|
425
|
+
if(visibility){
|
|
426
|
+
self.meshContent.onAfterRender = () => {
|
|
427
|
+
delete self.meshContent.onAfterRender;
|
|
428
|
+
self.meshContent.displayedOnce = true;
|
|
429
|
+
};
|
|
430
|
+
} */
|
|
431
|
+
|
|
432
|
+
}
|
|
433
|
+
calculateUpdateMetric(camera, frustum) {
|
|
434
|
+
////// return -1 if not in frustum
|
|
435
|
+
if (this.boundingVolume instanceof OBB) {
|
|
436
|
+
// box
|
|
437
|
+
tempSphere.copy(this.boundingVolume.sphere);
|
|
438
|
+
tempSphere.applyMatrix4(this.master.matrixWorld);
|
|
439
|
+
if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
440
|
+
} else if (this.boundingVolume instanceof THREE.Sphere) {
|
|
441
|
+
//sphere
|
|
442
|
+
tempSphere.copy(this.boundingVolume);
|
|
443
|
+
tempSphere.applyMatrix4(this.master.matrixWorld);
|
|
444
|
+
if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
445
|
+
} else if (this.boundingVolume instanceof THREE.Box3) {
|
|
446
|
+
// Region
|
|
447
|
+
// Region not supported
|
|
448
|
+
//throw Error("Region bounding volume not supported");
|
|
449
|
+
return -1;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/////// return metric based on geometric error and distance
|
|
453
|
+
if (this.boundingVolume instanceof OBB || this.boundingVolume instanceof THREE.Sphere) {
|
|
454
|
+
// box
|
|
455
|
+
const distance = Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
|
|
456
|
+
if (distance == 0) {
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
const scale = this.master.matrixWorld.getMaxScaleOnAxis();
|
|
460
|
+
return (((distance / Math.pow(scale, 2)) / 100) / this.master.geometricErrorMultiplier);
|
|
461
|
+
} else if (this.boundingVolume instanceof THREE.Box3) {
|
|
462
|
+
// Region
|
|
463
|
+
// Region not supported
|
|
464
|
+
//throw Error("Region bounding volume not supported");
|
|
465
|
+
return -1;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
getSiblings() {
|
|
470
|
+
const self = this;
|
|
471
|
+
const tiles = [];
|
|
472
|
+
if (!self.parentTile) return tiles;
|
|
473
|
+
let p = self.parentTile;
|
|
474
|
+
while (!p.hasMeshContent && !!p.parentTile) {
|
|
475
|
+
p = p.parentTile;
|
|
476
|
+
}
|
|
477
|
+
p.childrenTiles.forEach(child => {
|
|
478
|
+
if (!!child && child != self) {
|
|
479
|
+
while (!child.hasMeshContent && !!child.childrenTiles[0]) {
|
|
480
|
+
child = child.childrenTiles[0];
|
|
481
|
+
}
|
|
482
|
+
tiles.push(child);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
return tiles;
|
|
486
|
+
}
|
|
487
|
+
calculateDistanceToCamera(camera) {
|
|
488
|
+
if (this.boundingVolume instanceof OBB) {
|
|
489
|
+
// box
|
|
490
|
+
tempSphere.copy(this.boundingVolume.sphere);
|
|
491
|
+
tempSphere.applyMatrix4(this.master.matrixWorld);
|
|
492
|
+
//if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
493
|
+
} else if (this.boundingVolume instanceof THREE.Sphere) {
|
|
494
|
+
//sphere
|
|
495
|
+
tempSphere.copy(this.boundingVolume);
|
|
496
|
+
tempSphere.applyMatrix4(this.master.matrixWorld);
|
|
497
|
+
//if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
498
|
+
}
|
|
499
|
+
if (this.boundingVolume instanceof THREE.Box3) {
|
|
500
|
+
return -1; // region not supported
|
|
501
|
+
}
|
|
502
|
+
return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
getWorldMatrix(){
|
|
506
|
+
const self = this;
|
|
507
|
+
return self.master.matrixWorld;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
export { InstancedTile };
|