@jdultra/threedtiles 8.0.0 → 9.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 +45 -20
- package/dist/threedtiles.min.js +2 -0
- package/package.json +17 -13
- package/.babelrc +0 -8
- package/.vscode/settings.json +0 -2
- package/index.html +0 -56
- package/src/decoder/B3DMDecoder.js +0 -125
- package/src/decoder/FeatureTable.js +0 -169
- package/src/decoder/LegacyGLTFLoader.js +0 -2216
- package/src/geometry/obb.js +0 -47
- package/src/images/skybox/back.png +0 -0
- package/src/images/skybox/bottom.png +0 -0
- package/src/images/skybox/front.png +0 -0
- package/src/images/skybox/left.png +0 -0
- package/src/images/skybox/right.png +0 -0
- package/src/images/skybox/top.png +0 -0
- package/src/index.js +0 -331
- package/src/tileset/OGC3DTile.js +0 -676
- package/src/tileset/OcclusionCullingService.js +0 -79
- package/src/tileset/TileLoader.js +0 -381
- package/src/tileset/instanced/InstancedOGC3DTile.js +0 -55
- package/src/tileset/instanced/InstancedTile.js +0 -588
- package/src/tileset/instanced/InstancedTileLoader.js +0 -396
- package/src/tileset/instanced/JsonTile.js +0 -41
- package/src/tileset/instanced/MeshTile.js +0 -81
- package/threedtiles.js +0 -82894
- package/threedtiles.js.map +0 -1
- package/webpack.config.js +0 -142
package/src/tileset/OGC3DTile.js
DELETED
|
@@ -1,676 +0,0 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
2
|
-
import { OBB } from "../geometry/obb";
|
|
3
|
-
import { TileLoader } from "./TileLoader";
|
|
4
|
-
import { v4 as uuidv4 } from "uuid";
|
|
5
|
-
import * as path from "path-browserify"
|
|
6
|
-
import { clamp } from "three/src/math/MathUtils";
|
|
7
|
-
import { Octree } from 'three/addons/math/Octree.js';
|
|
8
|
-
import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
|
|
9
|
-
|
|
10
|
-
const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 1);
|
|
11
|
-
const tempVec1 = new THREE.Vector3(0, 0, 0);
|
|
12
|
-
const tempVec2 = new THREE.Vector3(0, 0, 0);
|
|
13
|
-
const upVector = new THREE.Vector3(0, 1, 0);
|
|
14
|
-
const rendererSize = new THREE.Vector2(1000, 1000);
|
|
15
|
-
const tempQuaternion = new THREE.Quaternion();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class OGC3DTile extends THREE.Object3D {
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
-
* @param {
|
|
23
|
-
* json: optional,
|
|
24
|
-
* url: optional,
|
|
25
|
-
* rootPath: optional,
|
|
26
|
-
* parentGeometricError: optional,
|
|
27
|
-
* parentBoundingVolume: optional,
|
|
28
|
-
* parentRefinement: optional,
|
|
29
|
-
* geometricErrorMultiplier: Double,
|
|
30
|
-
* loadOutsideView: Boolean,
|
|
31
|
-
* tileLoader : TileLoader,
|
|
32
|
-
* meshCallback: function,
|
|
33
|
-
* pointsCallback: function,
|
|
34
|
-
* cameraOnLoad: camera,
|
|
35
|
-
* parentTile: OGC3DTile,
|
|
36
|
-
* onLoadCallback: function,
|
|
37
|
-
* occlusionCullingService: OcclusionCullingService,
|
|
38
|
-
* static: Boolean,
|
|
39
|
-
* centerModel: Boolean
|
|
40
|
-
* renderer: Renderer
|
|
41
|
-
* } properties
|
|
42
|
-
*/
|
|
43
|
-
constructor(properties) {
|
|
44
|
-
super();
|
|
45
|
-
const self = this;
|
|
46
|
-
|
|
47
|
-
this.uuid = uuidv4();
|
|
48
|
-
if (!!properties.tileLoader) {
|
|
49
|
-
this.tileLoader = properties.tileLoader;
|
|
50
|
-
} else {
|
|
51
|
-
const tileLoaderOptions = {};
|
|
52
|
-
tileLoaderOptions.meshCallback = !properties.meshCallback ? mesh => {
|
|
53
|
-
mesh.material.wireframe = false;
|
|
54
|
-
mesh.material.side = THREE.DoubleSide;
|
|
55
|
-
} : properties.meshCallback;
|
|
56
|
-
tileLoaderOptions.pointsCallback = !properties.pointsCallback ? points => {
|
|
57
|
-
points.material.size = 0.1;
|
|
58
|
-
points.material.sizeAttenuation = true;
|
|
59
|
-
} : properties.pointsCallback;
|
|
60
|
-
this.tileLoader = new TileLoader(tileLoaderOptions);
|
|
61
|
-
}
|
|
62
|
-
// set properties general to the entire tileset
|
|
63
|
-
this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
|
|
64
|
-
|
|
65
|
-
this.renderer = properties.renderer;
|
|
66
|
-
this.meshCallback = properties.meshCallback;
|
|
67
|
-
this.loadOutsideView = properties.loadOutsideView;
|
|
68
|
-
this.cameraOnLoad = properties.cameraOnLoad;
|
|
69
|
-
this.parentTile = properties.parentTile;
|
|
70
|
-
this.occlusionCullingService = properties.occlusionCullingService;
|
|
71
|
-
this.static = properties.static;
|
|
72
|
-
if (this.occlusionCullingService) {
|
|
73
|
-
this.color = new THREE.Color();
|
|
74
|
-
this.color.setHex(Math.random() * 0xffffff);
|
|
75
|
-
this.colorID = clamp(self.color.r * 255, 0, 255) << 16 ^ clamp(self.color.g * 255, 0, 255) << 8 ^ clamp(self.color.b * 255, 0, 255) << 0;
|
|
76
|
-
}
|
|
77
|
-
if (this.static) {
|
|
78
|
-
this.matrixAutoUpdate = false;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// declare properties specific to the tile for clarity
|
|
82
|
-
this.childrenTiles = [];
|
|
83
|
-
this.meshContent;
|
|
84
|
-
this.tileContent;
|
|
85
|
-
this.refinement; // defaults to "REPLACE"
|
|
86
|
-
this.rootPath;
|
|
87
|
-
this.geometricError;
|
|
88
|
-
this.boundingVolume;
|
|
89
|
-
this.json; // the json corresponding to this tile
|
|
90
|
-
this.materialVisibility = false;
|
|
91
|
-
this.inFrustum = true;
|
|
92
|
-
this.level = properties.level ? properties.level : 0;
|
|
93
|
-
this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
|
|
94
|
-
this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
|
|
95
|
-
this.centerModel = properties.centerModel;
|
|
96
|
-
this.abortController = new AbortController();
|
|
97
|
-
this.layers.disable(0);
|
|
98
|
-
this.octree = new Octree();
|
|
99
|
-
|
|
100
|
-
if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
|
|
101
|
-
self.setup(properties);
|
|
102
|
-
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
103
|
-
|
|
104
|
-
} else if (properties.url) { // If only the url to the tileset.json is provided
|
|
105
|
-
fetch(properties.url, { signal: self.abortController.signal }).then(result => {
|
|
106
|
-
if (!result.ok) {
|
|
107
|
-
throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
108
|
-
}
|
|
109
|
-
result.json().then(json => {
|
|
110
|
-
self.setup({ rootPath: path.dirname(properties.url), json: json });
|
|
111
|
-
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
112
|
-
if (!!self.centerModel) {
|
|
113
|
-
const tempSphere = new THREE.Sphere();
|
|
114
|
-
if (self.boundingVolume instanceof OBB) {
|
|
115
|
-
// box
|
|
116
|
-
tempSphere.copy(self.boundingVolume.sphere);
|
|
117
|
-
} else if (self.boundingVolume instanceof THREE.Sphere) {
|
|
118
|
-
//sphere
|
|
119
|
-
tempSphere.copy(self.boundingVolume);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
//tempSphere.applyMatrix4(self.matrixWorld);
|
|
123
|
-
if (!!this.json.boundingVolume.region) {
|
|
124
|
-
this.transformWGS84ToCartesian(
|
|
125
|
-
(this.json.boundingVolume.region[0] + this.json.boundingVolume.region[2]) * 0.5,
|
|
126
|
-
(this.json.boundingVolume.region[1] + this.json.boundingVolume.region[3]) * 0.5,
|
|
127
|
-
(this.json.boundingVolume.region[4] + this.json.boundingVolume.region[5]) * 0.5,
|
|
128
|
-
tempVec1);
|
|
129
|
-
|
|
130
|
-
tempQuaternion.setFromUnitVectors(tempVec1.normalize(), upVector.normalize());
|
|
131
|
-
self.applyQuaternion(tempQuaternion);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
self.translateX(-tempSphere.center.x * self.scale.x);
|
|
135
|
-
self.translateY(-tempSphere.center.y * self.scale.y);
|
|
136
|
-
self.translateZ(-tempSphere.center.z * self.scale.z);
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
setup(properties) {
|
|
145
|
-
if (!!properties.json.root) {
|
|
146
|
-
this.json = properties.json.root;
|
|
147
|
-
if (!this.json.refinement) this.json.refinement = properties.json.refinement;
|
|
148
|
-
if (!this.json.geometricError) this.json.geometricError = properties.json.geometricError;
|
|
149
|
-
if (!this.json.transform) this.json.transform = properties.json.transform;
|
|
150
|
-
if (!this.json.boundingVolume) this.json.boundingVolume = properties.json.boundingVolume;
|
|
151
|
-
} else {
|
|
152
|
-
this.json = properties.json;
|
|
153
|
-
}
|
|
154
|
-
this.rootPath = !!properties.json.rootPath ? properties.json.rootPath : properties.rootPath;
|
|
155
|
-
|
|
156
|
-
// decode refinement
|
|
157
|
-
if (!!this.json.refinement) {
|
|
158
|
-
this.refinement = this.json.refinement;
|
|
159
|
-
} else {
|
|
160
|
-
this.refinement = properties.parentRefinement;
|
|
161
|
-
}
|
|
162
|
-
// decode geometric error
|
|
163
|
-
if (!!this.json.geometricError) {
|
|
164
|
-
this.geometricError = this.json.geometricError;
|
|
165
|
-
} else {
|
|
166
|
-
this.geometricError = properties.parentGeometricError;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// decode transform
|
|
170
|
-
if (!!this.json.transform && !this.centerModel) {
|
|
171
|
-
let mat = new THREE.Matrix4();
|
|
172
|
-
mat.elements = this.json.transform;
|
|
173
|
-
this.applyMatrix4(mat);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// decode volume
|
|
177
|
-
if (!!this.json.boundingVolume) {
|
|
178
|
-
if (!!this.json.boundingVolume.box) {
|
|
179
|
-
this.boundingVolume = new OBB(this.json.boundingVolume.box);
|
|
180
|
-
} else if (!!this.json.boundingVolume.region) {
|
|
181
|
-
const region = this.json.boundingVolume.region;
|
|
182
|
-
this.transformWGS84ToCartesian(region[0], region[1], region[4], tempVec1);
|
|
183
|
-
this.transformWGS84ToCartesian(region[2], region[3], region[5], tempVec2);
|
|
184
|
-
tempVec1.lerp(tempVec2, 0.5);
|
|
185
|
-
this.boundingVolume = new THREE.Sphere(new THREE.Vector3(tempVec1.x, tempVec1.y, tempVec1.z), tempVec1.distanceTo(tempVec2));
|
|
186
|
-
} else if (!!this.json.boundingVolume.sphere) {
|
|
187
|
-
const sphere = this.json.boundingVolume.sphere;
|
|
188
|
-
this.boundingVolume = new THREE.Sphere(new THREE.Vector3(sphere[0], sphere[1], sphere[2]), sphere[3]);
|
|
189
|
-
} else {
|
|
190
|
-
this.boundingVolume = properties.parentBoundingVolume;
|
|
191
|
-
}
|
|
192
|
-
} else {
|
|
193
|
-
this.boundingVolume = properties.parentBoundingVolume;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (!!this.json.content) { //if there is a content, json or otherwise, schedule it to be loaded
|
|
199
|
-
if (!!this.json.content.uri && this.json.content.uri.includes("json")) {
|
|
200
|
-
this.hasUnloadedJSONContent = true;
|
|
201
|
-
} else if (!!this.json.content.url && this.json.content.url.includes("json")) {
|
|
202
|
-
this.hasUnloadedJSONContent = true;
|
|
203
|
-
} else {
|
|
204
|
-
this.hasMeshContent = true;
|
|
205
|
-
}
|
|
206
|
-
this.load();
|
|
207
|
-
//scheduleLoadTile(this);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
isAbsolutePathOrURL(input) {
|
|
212
|
-
// Check if it's an absolute URL with various protocols
|
|
213
|
-
const urlRegex = /^(?:http|https|ftp|tcp|udp):\/\/\S+/;
|
|
214
|
-
const absoluteURL = urlRegex.test(input);
|
|
215
|
-
|
|
216
|
-
// Check if it's an absolute path
|
|
217
|
-
const absolutePath = input.startsWith('/') && !input.startsWith('//');
|
|
218
|
-
|
|
219
|
-
return absoluteURL || absolutePath;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
load() {
|
|
223
|
-
var self = this;
|
|
224
|
-
if (self.deleted) return;
|
|
225
|
-
if (!!self.json.content) {
|
|
226
|
-
let url;
|
|
227
|
-
if (!!self.json.content.uri) {
|
|
228
|
-
if (path.isAbsolute(self.json.content.uri) || self.isAbsolutePathOrURL(self.json.content.uri)) {
|
|
229
|
-
url = self.json.content.uri;
|
|
230
|
-
} else {
|
|
231
|
-
url = self.rootPath + path.sep + self.json.content.uri;
|
|
232
|
-
}
|
|
233
|
-
} else if (!!self.json.content.url) {
|
|
234
|
-
if (path.isAbsolute(self.json.content.url) || self.isAbsolutePathOrURL(self.json.content.url)) {
|
|
235
|
-
url = self.json.content.url;
|
|
236
|
-
} else {
|
|
237
|
-
url = self.rootPath + path.sep + self.json.content.url;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (!!url) {
|
|
242
|
-
if (url.includes(".b3dm") || url.includes(".glb") || url.includes(".gltf")) {
|
|
243
|
-
self.contentURL = url;
|
|
244
|
-
|
|
245
|
-
self.tileLoader.get(self.abortController, this.uuid, url, mesh => {
|
|
246
|
-
if (!!self.deleted) return;
|
|
247
|
-
mesh.traverse((o) => {
|
|
248
|
-
if (o.isMesh) {
|
|
249
|
-
|
|
250
|
-
o.layers.disable(0);
|
|
251
|
-
if (self.occlusionCullingService) {
|
|
252
|
-
const position = o.geometry.attributes.position;
|
|
253
|
-
const colors = [];
|
|
254
|
-
for (let i = 0; i < position.count; i++) {
|
|
255
|
-
colors.push(self.color.r, self.color.g, self.color.b);
|
|
256
|
-
}
|
|
257
|
-
o.geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
258
|
-
}
|
|
259
|
-
if (self.static) {
|
|
260
|
-
o.matrixAutoUpdate = false;
|
|
261
|
-
}
|
|
262
|
-
//o.material.visible = false;
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
//let s = Date.now();
|
|
266
|
-
//self.octree.fromGraphNode( mesh );
|
|
267
|
-
//console.log(Date.now()-s);
|
|
268
|
-
self.add(mesh);
|
|
269
|
-
self.updateWorldMatrix(false, true);
|
|
270
|
-
// mesh.layers.disable(0);
|
|
271
|
-
self.meshContent = mesh;
|
|
272
|
-
}, !self.cameraOnLoad ? () => 0 : () => {
|
|
273
|
-
return self.calculateDistanceToCamera(self.cameraOnLoad);
|
|
274
|
-
}, () => self.getSiblings(),
|
|
275
|
-
self.level,
|
|
276
|
-
!!self.json.boundingVolume.region,
|
|
277
|
-
self.geometricError
|
|
278
|
-
);
|
|
279
|
-
} else if (url.includes(".json")) {
|
|
280
|
-
self.tileLoader.get(self.abortController, this.uuid, url, json => {
|
|
281
|
-
if (!!self.deleted) return;
|
|
282
|
-
if (!self.json.children) self.json.children = [];
|
|
283
|
-
json.rootPath = path.dirname(url);
|
|
284
|
-
self.json.children.push(json);
|
|
285
|
-
delete self.json.content;
|
|
286
|
-
self.hasUnloadedJSONContent = false;
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
dispose() {
|
|
296
|
-
|
|
297
|
-
const self = this;
|
|
298
|
-
self.childrenTiles.forEach(tile => tile.dispose());
|
|
299
|
-
self.deleted = true;
|
|
300
|
-
this.traverse(function (element) {
|
|
301
|
-
if (!!element.contentURL) {
|
|
302
|
-
self.tileLoader.invalidate(element.contentURL, element.uuid);
|
|
303
|
-
}
|
|
304
|
-
if (!!element.abortController) { // abort tile request
|
|
305
|
-
element.abortController.abort();
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
this.parent = null;
|
|
310
|
-
this.parentTile = null;
|
|
311
|
-
this.dispatchEvent({ type: 'removed' });
|
|
312
|
-
}
|
|
313
|
-
disposeChildren() {
|
|
314
|
-
var self = this;
|
|
315
|
-
|
|
316
|
-
self.childrenTiles.forEach(tile => tile.dispose());
|
|
317
|
-
self.childrenTiles = [];
|
|
318
|
-
self.children = [];
|
|
319
|
-
if (!!self.meshContent) self.children.push(self.meshContent);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
update(camera) {
|
|
324
|
-
const frustum = new THREE.Frustum();
|
|
325
|
-
frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));
|
|
326
|
-
this._update(camera, frustum);
|
|
327
|
-
}
|
|
328
|
-
_update(camera, frustum) {
|
|
329
|
-
const self = this;
|
|
330
|
-
|
|
331
|
-
const visibilityBeforeUpdate = self.materialVisibility;
|
|
332
|
-
|
|
333
|
-
if (!!self.boundingVolume && !!self.geometricError) {
|
|
334
|
-
self.metric = self.calculateUpdateMetric(camera, frustum);
|
|
335
|
-
}
|
|
336
|
-
self.childrenTiles.forEach(child => child._update(camera, frustum));
|
|
337
|
-
|
|
338
|
-
updateNodeVisibility(self.metric);
|
|
339
|
-
updateTree(self.metric);
|
|
340
|
-
trimTree(self.metric, visibilityBeforeUpdate);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
function updateTree(metric) {
|
|
344
|
-
// If this tile does not have mesh content but it has children
|
|
345
|
-
if (metric < 0 && self.hasMeshContent) return;
|
|
346
|
-
if (self.occlusionCullingService && self.hasMeshContent && !self.occlusionCullingService.hasID(self.colorID)) {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (!self.hasMeshContent || (metric < self.geometricErrorMultiplier * self.geometricError && !!self.meshContent)) {
|
|
350
|
-
if (!!self.json && !!self.json.children && self.childrenTiles.length != self.json.children.length) {
|
|
351
|
-
loadJsonChildren();
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function updateNodeVisibility(metric) {
|
|
358
|
-
|
|
359
|
-
//doesn't have a mesh content
|
|
360
|
-
if (!self.hasMeshContent) return;
|
|
361
|
-
|
|
362
|
-
// mesh content not yet loaded
|
|
363
|
-
if (!self.meshContent) {
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// outside frustum
|
|
368
|
-
if (metric < 0) {
|
|
369
|
-
self.inFrustum = false;
|
|
370
|
-
self.changeContentVisibility(!!self.loadOutsideView);
|
|
371
|
-
return;
|
|
372
|
-
} else {
|
|
373
|
-
self.inFrustum = true;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// has no children
|
|
377
|
-
if (self.childrenTiles.length == 0) {
|
|
378
|
-
self.changeContentVisibility(true);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// has children
|
|
383
|
-
if (metric >= self.geometricErrorMultiplier * self.geometricError) { // Ideal LOD or before ideal lod
|
|
384
|
-
|
|
385
|
-
self.changeContentVisibility(true);
|
|
386
|
-
} else if (metric < self.geometricErrorMultiplier * self.geometricError) { // Ideal LOD is past this one
|
|
387
|
-
// if children are visible and have been displayed, can be hidden
|
|
388
|
-
let allChildrenReady = true;
|
|
389
|
-
self.childrenTiles.every(child => {
|
|
390
|
-
|
|
391
|
-
if (!child.isReady()) {
|
|
392
|
-
allChildrenReady = false;
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
return true;
|
|
396
|
-
});
|
|
397
|
-
if (allChildrenReady) {
|
|
398
|
-
self.changeContentVisibility(false);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function trimTree(metric, visibilityBeforeUpdate) {
|
|
404
|
-
if (!self.hasMeshContent) return;
|
|
405
|
-
if (!self.inFrustum) { // outside frustum
|
|
406
|
-
self.disposeChildren();
|
|
407
|
-
updateNodeVisibility(metric);
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
if (self.occlusionCullingService &&
|
|
411
|
-
!visibilityBeforeUpdate &&
|
|
412
|
-
self.hasMeshContent &&
|
|
413
|
-
self.meshContent &&
|
|
414
|
-
self.meshDisplayed &&
|
|
415
|
-
self.areAllChildrenLoadedAndHidden()) {
|
|
416
|
-
|
|
417
|
-
self.disposeChildren();
|
|
418
|
-
updateNodeVisibility(metric);
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
if (metric >= self.geometricErrorMultiplier * self.geometricError) {
|
|
422
|
-
self.disposeChildren();
|
|
423
|
-
updateNodeVisibility();
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function loadJsonChildren() {
|
|
430
|
-
self.json.children.forEach(childJSON => {
|
|
431
|
-
let childTile = new OGC3DTile({
|
|
432
|
-
parentTile: self,
|
|
433
|
-
parentGeometricError: self.geometricError,
|
|
434
|
-
parentBoundingVolume: self.boundingVolume,
|
|
435
|
-
parentRefinement: self.refinement,
|
|
436
|
-
json: childJSON,
|
|
437
|
-
rootPath: self.rootPath,
|
|
438
|
-
geometricErrorMultiplier: self.geometricErrorMultiplier,
|
|
439
|
-
loadOutsideView: self.loadOutsideView,
|
|
440
|
-
level: self.level + 1,
|
|
441
|
-
tileLoader: self.tileLoader,
|
|
442
|
-
cameraOnLoad: camera,
|
|
443
|
-
occlusionCullingService: self.occlusionCullingService,
|
|
444
|
-
renderer: self.renderer,
|
|
445
|
-
static: self.static,
|
|
446
|
-
centerModel: false
|
|
447
|
-
});
|
|
448
|
-
self.childrenTiles.push(childTile);
|
|
449
|
-
self.add(childTile);
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
areAllChildrenLoadedAndHidden() {
|
|
456
|
-
let allLoadedAndHidden = true;
|
|
457
|
-
const self = this;
|
|
458
|
-
this.childrenTiles.every(child => {
|
|
459
|
-
if (child.hasMeshContent) {
|
|
460
|
-
if (child.childrenTiles.length > 0) {
|
|
461
|
-
allLoadedAndHidden = false;
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
if (!child.inFrustum) {
|
|
465
|
-
return true;
|
|
466
|
-
};
|
|
467
|
-
if (!child.materialVisibility || child.meshDisplayed) {
|
|
468
|
-
allLoadedAndHidden = false;
|
|
469
|
-
return false;
|
|
470
|
-
} else if (self.occlusionCullingService.hasID(child.colorID)) {
|
|
471
|
-
allLoadedAndHidden = false;
|
|
472
|
-
return false;
|
|
473
|
-
}
|
|
474
|
-
} else {
|
|
475
|
-
if (!child.areAllChildrenLoadedAndHidden()) {
|
|
476
|
-
allLoadedAndHidden = false;
|
|
477
|
-
return false;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
return true;
|
|
481
|
-
});
|
|
482
|
-
return allLoadedAndHidden;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Node is ready if it is outside frustum, if it was drawn at least once or if all it's children are ready
|
|
487
|
-
* @returns true if ready
|
|
488
|
-
*/
|
|
489
|
-
isReady() {
|
|
490
|
-
// if outside frustum
|
|
491
|
-
if (!this.inFrustum) return true;
|
|
492
|
-
|
|
493
|
-
// if json is not done loading
|
|
494
|
-
if (this.hasUnloadedJSONContent) return false;
|
|
495
|
-
|
|
496
|
-
// if this tile has no mesh content or if it's marked as visible false, look at children
|
|
497
|
-
if ((!this.hasMeshContent || !this.meshContent || !this.materialVisibility) && this.childrenTiles.length > 0) {
|
|
498
|
-
var allChildrenReady = true;
|
|
499
|
-
this.childrenTiles.every(child => {
|
|
500
|
-
if (!child.isReady()) {
|
|
501
|
-
allChildrenReady = false;
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
return true;
|
|
505
|
-
});
|
|
506
|
-
return allChildrenReady;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// if this tile has no mesh content
|
|
510
|
-
if (!this.hasMeshContent) {
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
// if mesh content not yet loaded
|
|
514
|
-
if (!this.meshContent) {
|
|
515
|
-
return false;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// if this tile has been marked to hide it's content
|
|
519
|
-
if (!this.materialVisibility) {
|
|
520
|
-
return false;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// if all meshes have been displayed once
|
|
524
|
-
if (this.meshDisplayed) {
|
|
525
|
-
return true;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return false;
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
changeContentVisibility(visibility) {
|
|
536
|
-
const self = this;
|
|
537
|
-
if (self.hasMeshContent && self.meshContent) {
|
|
538
|
-
if (visibility) {
|
|
539
|
-
|
|
540
|
-
self.meshContent.traverse((o) => {
|
|
541
|
-
if (o.isMesh) {
|
|
542
|
-
o.layers.enable(0);
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
} else {
|
|
546
|
-
self.meshContent.traverse((o) => {
|
|
547
|
-
if (o.isMesh) {
|
|
548
|
-
o.layers.disable(0);
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
if (self.materialVisibility == visibility) {
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
self.materialVisibility = visibility
|
|
557
|
-
|
|
558
|
-
self.meshDisplayed = true;
|
|
559
|
-
if (!!self.meshContent.traverse) {
|
|
560
|
-
self.meshContent.traverse(function (element) {
|
|
561
|
-
if (element.material) setMeshVisibility(element, visibility);
|
|
562
|
-
});
|
|
563
|
-
} else if (!!self.meshContent.scenes) {
|
|
564
|
-
self.meshContent.scenes.forEach(scene => scene.traverse(function (element) {
|
|
565
|
-
if (element.material) setMeshVisibility(element, visibility);
|
|
566
|
-
}));
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function setMeshVisibility(mesh, visibility) {
|
|
570
|
-
mesh.material.visible = visibility;
|
|
571
|
-
if (!!visibility) {
|
|
572
|
-
mesh.onAfterRender = () => {
|
|
573
|
-
delete mesh.onAfterRender;
|
|
574
|
-
self.meshDisplayed = true;
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
calculateUpdateMetric(camera, frustum) {
|
|
581
|
-
////// return -1 if not in frustum
|
|
582
|
-
if (this.boundingVolume instanceof OBB) {
|
|
583
|
-
// box
|
|
584
|
-
tempSphere.copy(this.boundingVolume.sphere);
|
|
585
|
-
tempSphere.applyMatrix4(this.matrixWorld);
|
|
586
|
-
if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
587
|
-
} else if (this.boundingVolume instanceof THREE.Sphere) {
|
|
588
|
-
//sphere
|
|
589
|
-
tempSphere.copy(this.boundingVolume);
|
|
590
|
-
tempSphere.applyMatrix4(this.matrixWorld);
|
|
591
|
-
if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
592
|
-
} else {
|
|
593
|
-
console.error("unsupported shape");
|
|
594
|
-
return -1
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/////// return metric based on geometric error and distance
|
|
599
|
-
|
|
600
|
-
const distance = Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
|
|
601
|
-
if (distance == 0) {
|
|
602
|
-
return 0;
|
|
603
|
-
}
|
|
604
|
-
const scale = this.matrixWorld.getMaxScaleOnAxis();
|
|
605
|
-
if (!!this.renderer) {
|
|
606
|
-
this.renderer.getDrawingBufferSize(rendererSize);
|
|
607
|
-
}
|
|
608
|
-
let s = rendererSize.y;
|
|
609
|
-
let fov = camera.fov;
|
|
610
|
-
if (camera.aspect < 1) {
|
|
611
|
-
fov *= camera.aspect;
|
|
612
|
-
s = rendererSize.x;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
let lambda = 2.0 * Math.tan(0.5 * fov * 0.01745329251994329576923690768489) * distance;
|
|
616
|
-
|
|
617
|
-
return (window.devicePixelRatio * 16 * lambda) / (s * scale);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
getSiblings() {
|
|
621
|
-
const self = this;
|
|
622
|
-
const tiles = [];
|
|
623
|
-
if (!self.parentTile) return tiles;
|
|
624
|
-
let p = self.parentTile;
|
|
625
|
-
while (!p.hasMeshContent && !!p.parentTile) {
|
|
626
|
-
p = p.parentTile;
|
|
627
|
-
}
|
|
628
|
-
p.childrenTiles.forEach(child => {
|
|
629
|
-
if (!!child && child != self) {
|
|
630
|
-
while (!child.hasMeshContent && !!child.childrenTiles[0]) {
|
|
631
|
-
child = child.childrenTiles[0];
|
|
632
|
-
}
|
|
633
|
-
tiles.push(child);
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
return tiles;
|
|
637
|
-
}
|
|
638
|
-
calculateDistanceToCamera(camera) {
|
|
639
|
-
if (this.boundingVolume instanceof OBB) {
|
|
640
|
-
// box
|
|
641
|
-
tempSphere.copy(this.boundingVolume.sphere);
|
|
642
|
-
tempSphere.applyMatrix4(this.matrixWorld);
|
|
643
|
-
//if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
644
|
-
} else if (this.boundingVolume instanceof THREE.Sphere) {
|
|
645
|
-
//sphere
|
|
646
|
-
tempSphere.copy(this.boundingVolume);
|
|
647
|
-
tempSphere.applyMatrix4(this.matrixWorld);
|
|
648
|
-
//if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
649
|
-
}
|
|
650
|
-
else {
|
|
651
|
-
console.error("unsupported shape")
|
|
652
|
-
}
|
|
653
|
-
return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
|
|
654
|
-
}
|
|
655
|
-
setGeometricErrorMultiplier(geometricErrorMultiplier) {
|
|
656
|
-
this.geometricErrorMultiplier = geometricErrorMultiplier;
|
|
657
|
-
this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
transformWGS84ToCartesian(lon, lat, h, sfct) {
|
|
661
|
-
const a = 6378137.0;
|
|
662
|
-
const e = 0.006694384442042;
|
|
663
|
-
const N = a / (Math.sqrt(1.0 - (e * Math.pow(Math.sin(lat), 2))));
|
|
664
|
-
const cosLat = Math.cos(lat);
|
|
665
|
-
const cosLon = Math.cos(lon);
|
|
666
|
-
const sinLat = Math.sin(lat);
|
|
667
|
-
const sinLon = Math.sin(lon);
|
|
668
|
-
const nPh = (N + h);
|
|
669
|
-
const x = nPh * cosLat * cosLon;
|
|
670
|
-
const y = nPh * cosLat * sinLon;
|
|
671
|
-
const z = (0.993305615557957 * N + h) * sinLat;
|
|
672
|
-
|
|
673
|
-
sfct.set(x, y, z);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
export { OGC3DTile };
|