@jdultra/threedtiles 8.0.1 → 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.
@@ -1,588 +0,0 @@
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
- const tempVec1 = new THREE.Vector3(0, 0, 0);
9
- const tempVec2 = new THREE.Vector3(0, 0, 0);
10
- const upVector = new THREE.Vector3(0, 1, 0);
11
- const rendererSize = new THREE.Vector2();
12
- const tempQuaternion = new THREE.Quaternion();
13
- const tempMatrix = new THREE.Matrix4();
14
-
15
- class InstancedTile extends THREE.Object3D {
16
-
17
- /**
18
- *
19
- * @param {
20
- * json: optional,
21
- * url: optional,
22
- * rootPath: optional,
23
- * parentGeometricError: optional,
24
- * parentBoundingVolume: optional,
25
- * parentRefinement: optional,
26
- * loadOutsideView: Boolean,
27
- * tileLoader : InstancedTileLoader,
28
- * meshCallback: function,
29
- * cameraOnLoad: camera,
30
- * parentTile: OGC3DTile,
31
- * onLoadCallback: function,
32
- * centerModel: Boolean
33
- * } properties
34
- */
35
- constructor(properties) {
36
- super();
37
- const self = this;
38
-
39
- this.uuid = uuidv4();
40
- if (!!properties.tileLoader) {
41
- this.tileLoader = properties.tileLoader;
42
- } else {
43
- console.error("an instanced tileset must be provided an InstancedTilesetLoader");
44
- }
45
- // set properties general to the entire tileset
46
- this.master = properties.master;
47
- this.meshCallback = properties.meshCallback;
48
- this.loadOutsideView = properties.loadOutsideView;
49
- this.cameraOnLoad = properties.cameraOnLoad;
50
- this.parentTile = properties.parentTile;
51
-
52
- // declare properties specific to the tile for clarity
53
- this.childrenTiles = [];
54
- this.jsonChildren = [];
55
- this.meshContent;
56
-
57
- this.tileContent;
58
- this.refinement; // defaults to "REPLACE"
59
- this.rootPath;
60
- this.geometricError;
61
- this.boundingVolume;
62
- this.json; // the json corresponding to this tile
63
- this.materialVisibility = false;
64
- this.inFrustum = true;
65
- this.level = properties.level ? properties.level : 0;
66
- this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
67
- this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
68
- this.centerModel = properties.centerModel;
69
-
70
- this.deleted = false;
71
- this.abortController = new AbortController();
72
-
73
- if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
74
- this.rootPath = !!properties.json.rootPath ? properties.json.rootPath : properties.rootPath;
75
- if(properties.json.children) this.jsonChildren = properties.json.children;
76
- self.setup(properties);
77
- if (properties.onLoadCallback) properties.onLoadCallback(self);
78
- } else if (properties.url) { // If only the url to the tileset.json is provided
79
-
80
-
81
- this.loadJson = (json, url)=>{
82
- //json = _.cloneDeep(json)
83
- //json = JSON.parse(JSON.stringify(json))
84
- const p = path.dirname(url);
85
- self.setup({ rootPath: p, json: json });
86
- if (!!self.centerModel) {
87
- const tempSphere = new THREE.Sphere();
88
- if (self.boundingVolume instanceof OBB) {
89
- // box
90
- tempSphere.copy(self.boundingVolume.sphere);
91
- } else if (self.boundingVolume instanceof THREE.Sphere) {
92
- //sphere
93
- tempSphere.copy(self.boundingVolume);
94
- }
95
-
96
- //tempSphere.applyMatrix4(self.matrixWorld);
97
- if (!!this.json.boundingVolume.region) {
98
- self.transformWGS84ToCartesian(
99
- (self.json.boundingVolume.region[0] + self.json.boundingVolume.region[2]) * 0.5,
100
- (self.json.boundingVolume.region[1] + self.json.boundingVolume.region[3]) * 0.5,
101
- (self.json.boundingVolume.region[4] + self.json.boundingVolume.region[5]) * 0.5,
102
- tempVec1);
103
-
104
- tempQuaternion.setFromUnitVectors(tempVec1.normalize(), upVector.normalize());
105
- self.master.applyQuaternion(tempQuaternion);
106
- self.master.updateWorldMatrix(false, false)
107
- }
108
- tempMatrix.makeTranslation(-tempSphere.center.x * self.scale.x, -tempSphere.center.y * self.scale.y, -tempSphere.center.z * self.scale.z);
109
- //self.master.applyMatrix4(tempMatrix);
110
- self.master.matrix.multiply(tempMatrix);
111
- self.master.matrix.decompose( self.master.position, self.master.quaternion, self.master.scale );
112
- }
113
- if (properties.onLoadCallback) properties.onLoadCallback(self);
114
-
115
- }
116
- self.tileLoader.get(self.abortController, properties.url, self.uuid, self);
117
-
118
- /* fetch(properties.url, { signal: self.abortController.signal }).then(result => {
119
- if (!result.ok) {
120
- throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
121
- }
122
- result.json().then(json => {
123
- const p = path.dirname(properties.url);
124
- self.setup({ rootPath: p, json: json });
125
- if (properties.onLoadCallback) properties.onLoadCallback(self);
126
- });
127
- }); */
128
- }
129
- }
130
-
131
- setup(properties) {
132
- this.isSetup = true;
133
- if (!!properties.json.root) {
134
- this.json = properties.json.root;
135
- this.jsonChildren = this.json.children;
136
- if (!this.json.refinement) this.json.refinement = properties.json.refinement;
137
- if (!this.json.geometricError) this.json.geometricError = properties.json.geometricError;
138
- if (!this.json.transform) this.json.transform = properties.json.transform;
139
- if (!this.json.boundingVolume) this.json.boundingVolume = properties.json.boundingVolume;
140
- } else {
141
- this.json = properties.json;
142
- }
143
-
144
- this.rootPath = !!properties.json.rootPath ? properties.json.rootPath : properties.rootPath;
145
-
146
- // decode refinement
147
- if (!!this.json.refinement) {
148
- this.refinement = this.json.refinement;
149
- } else {
150
- this.refinement = properties.parentRefinement;
151
- }
152
- // decode geometric error
153
- if (!!this.json.geometricError) {
154
- this.geometricError = this.json.geometricError;
155
- } else {
156
- this.geometricError = properties.parentGeometricError;
157
- }
158
- // decode transform
159
- if (!!this.json.transform && !this.centerModel) {
160
- let mat = new THREE.Matrix4();
161
- mat.elements = this.json.transform;
162
- this.master.applyMatrix4(mat);
163
- }
164
- // decode volume
165
- if (!!this.json.boundingVolume) {
166
- if (!!this.json.boundingVolume.box) {
167
- this.boundingVolume = new OBB(this.json.boundingVolume.box);
168
- } else if (!!this.json.boundingVolume.region) {
169
- const region = this.json.boundingVolume.region;
170
- this.transformWGS84ToCartesian(region[0], region[1], region[4], tempVec1);
171
- this.transformWGS84ToCartesian(region[2], region[3], region[5], tempVec2);
172
- tempVec1.lerp(tempVec2, 0.5);
173
- this.boundingVolume = new THREE.Sphere(new THREE.Vector3(tempVec1.x, tempVec1.y, tempVec1.z), tempVec1.distanceTo(tempVec2));
174
- } else if (!!this.json.boundingVolume.sphere) {
175
- const sphere = this.json.boundingVolume.sphere;
176
- this.boundingVolume = new THREE.Sphere(new THREE.Vector3(sphere[0], sphere[2], -sphere[1]), sphere[3]);
177
- } else {
178
- this.boundingVolume = properties.parentBoundingVolume;
179
- }
180
- } else {
181
- this.boundingVolume = properties.parentBoundingVolume;
182
- }
183
-
184
- if (!!this.json.content) { //if there is a content, json or otherwise, schedule it to be loaded
185
- if (!!this.json.content.uri && this.json.content.uri.includes("json")) {
186
- this.hasUnloadedJSONContent = true;
187
- } else if (!!this.json.content.url && this.json.content.url.includes("json")) {
188
- this.hasUnloadedJSONContent = true;
189
- } else {
190
- this.hasMeshContent = true;
191
- }
192
- this.load();
193
- //scheduleLoadTile(this);
194
- }
195
- }
196
-
197
- isAbsolutePathOrURL(input) {
198
- // Check if it's an absolute URL with various protocols
199
- const urlRegex = /^(?:http|https|ftp|tcp|udp):\/\/\S+/;
200
- const absoluteURL = urlRegex.test(input);
201
-
202
- // Check if it's an absolute path
203
- const absolutePath = input.startsWith('/') && !input.startsWith('//');
204
-
205
- return absoluteURL || absolutePath;
206
- }
207
- load() {
208
- var self = this;
209
- if (self.deleted) return;
210
- if (!!self.json.content) {
211
- let url;
212
- if (!!self.json.content.uri) {
213
- if (path.isAbsolute(self.json.content.uri) || self.isAbsolutePathOrURL(self.json.content.uri)) {
214
- url = self.json.content.uri;
215
- } else {
216
- url = self.rootPath + path.sep + self.json.content.uri;
217
- }
218
- } else if (!!self.json.content.url) {
219
- if (path.isAbsolute(self.json.content.url) || self.isAbsolutePathOrURL(self.json.content.url)) {
220
- url = self.json.content.url;
221
- } else {
222
- url = self.rootPath + path.sep + self.json.content.url;
223
- }
224
- }
225
-
226
- if (!!url) {
227
- if (url.includes(".b3dm")|| url.includes(".glb") || url.includes(".gltf")) {
228
- self.contentURL = url;
229
-
230
- self.tileLoader.get(self.abortController, url, self.uuid, self, !self.cameraOnLoad ? () => 0 : () => {
231
- return self.calculateDistanceToCamera(self.cameraOnLoad);
232
- }, () => self.getSiblings(),
233
- self.level,
234
- !!self.json.boundingVolume.region,
235
- self.geometricError);
236
- } else if (url.includes(".json")) {
237
- self.tileLoader.get(self.abortController, url, self.uuid, self);
238
-
239
- }
240
-
241
- }
242
- }
243
- self.matrixWorldNeedsUpdate = true;
244
- self.updateWorldMatrix(true,true)
245
- }
246
-
247
- loadMesh(mesh) {
248
- const self = this;
249
- if (self.deleted) {
250
- return;
251
- }
252
-
253
- //self.updateWorldMatrix(false, true);
254
- self.meshContent = mesh;
255
-
256
- }
257
-
258
- loadJson(json, url) {
259
- if (this.deleted) {
260
- return;
261
- }
262
- if(!!this.json.children){
263
- this.jsonChildren = this.json.children;
264
- }
265
-
266
- json.rootPath = path.dirname(url);
267
- this.jsonChildren.push(json);
268
- this.hasUnloadedJSONContent = false;
269
- }
270
-
271
- dispose() {
272
-
273
- const self = this;
274
- self.childrenTiles.forEach(tile => tile.dispose());
275
- self.deleted = true;
276
- if (self.abortController) self.abortController.abort();
277
- this.parent = null;
278
- this.parentTile = null;
279
- this.dispatchEvent({ type: 'removed' });
280
- }
281
- disposeChildren() {
282
- var self = this;
283
-
284
- self.childrenTiles.forEach(tile => tile.dispose());
285
- self.childrenTiles = [];
286
- }
287
-
288
-
289
- _update(camera, frustum) {
290
- const self = this;
291
- const visibilityBeforeUpdate = self.materialVisibility;
292
-
293
- if (!!self.boundingVolume && !!self.geometricError) {
294
- self.metric = self.calculateUpdateMetric(camera, frustum);
295
- }
296
- self.childrenTiles.forEach(child => child._update(camera, frustum));
297
-
298
- updateNodeVisibility(self.metric);
299
- updateTree(self.metric);
300
- trimTree(self.metric, visibilityBeforeUpdate);
301
-
302
-
303
- function updateTree(metric) {
304
- // If this tile does not have mesh content but it has children
305
- if (metric < 0 && self.hasMeshContent) return;
306
-
307
- if ((!self.hasMeshContent && self.rootPath) || (metric < self.master.geometricErrorMultiplier * self.geometricError && !!self.meshContent)) {
308
- if (!!self.json && !!self.jsonChildren && self.childrenTiles.length != self.jsonChildren.length) {
309
- loadJsonChildren();
310
- return;
311
- }
312
- }
313
- }
314
-
315
- function updateNodeVisibility(metric) {
316
-
317
- //doesn't have a mesh content
318
- if (!self.hasMeshContent) return;
319
-
320
- // mesh content not yet loaded
321
- if (!self.meshContent) {
322
- return;
323
- }
324
-
325
- // outside frustum
326
- if (metric < 0) {
327
- self.inFrustum = false;
328
- self.changeContentVisibility(!!self.loadOutsideView);
329
- return;
330
- } else {
331
- self.inFrustum = true;
332
- }
333
-
334
- // has no children
335
- if (self.childrenTiles.length == 0) {
336
- self.changeContentVisibility(true);
337
- return;
338
- }
339
-
340
- // has children
341
- if (metric >= self.master.geometricErrorMultiplier * self.geometricError) { // Ideal LOD or before ideal lod
342
-
343
- self.changeContentVisibility(true);
344
- } else if (metric < self.master.geometricErrorMultiplier * self.geometricError) { // Ideal LOD is past this one
345
- // if children are visible and have been displayed, can be hidden
346
- let allChildrenReady = true;
347
- self.childrenTiles.every(child => {
348
-
349
- if (!child.isReady()) {
350
- allChildrenReady = false;
351
- return false;
352
- }
353
- return true;
354
- });
355
- if (allChildrenReady) {
356
- self.changeContentVisibility(false);
357
- }
358
- }
359
- }
360
-
361
- function trimTree(metric, visibilityBeforeUpdate) {
362
- if (!self.hasMeshContent) return;
363
- if (!self.inFrustum) { // outside frustum
364
- self.disposeChildren();
365
- updateNodeVisibility(metric);
366
- return;
367
- }
368
- if (metric >= self.master.geometricErrorMultiplier * self.geometricError) {
369
- self.disposeChildren();
370
- updateNodeVisibility(metric);
371
- return;
372
- }
373
-
374
- }
375
-
376
- function loadJsonChildren() {
377
- self.jsonChildren.forEach(childJSON => {
378
- let childTile = new InstancedTile({
379
- parentTile: self,
380
- parentGeometricError: self.geometricError,
381
- parentBoundingVolume: self.boundingVolume,
382
- parentRefinement: self.refinement,
383
- json: childJSON,
384
- rootPath: self.rootPath,
385
- loadOutsideView: self.loadOutsideView,
386
- level: self.level + 1,
387
- tileLoader: self.tileLoader,
388
- cameraOnLoad: camera,
389
- master: self.master,
390
- centerModel: false
391
- });
392
- self.childrenTiles.push(childTile);
393
- //self.add(childTile);
394
- });
395
- }
396
-
397
- }
398
-
399
- areAllChildrenLoadedAndHidden() {
400
- let allLoadedAndHidden = true;
401
- const self = this;
402
- this.childrenTiles.every(child => {
403
- if (child.hasMeshContent) {
404
- if (child.childrenTiles.length > 0) {
405
- allLoadedAndHidden = false;
406
- return false;
407
- }
408
- if (!child.inFrustum) {
409
- return true;
410
- };
411
- if (!child.materialVisibility || child.meshesToDisplay != child.meshesDisplayed) {
412
- allLoadedAndHidden = false;
413
- return false;
414
- }
415
- } else {
416
- if (!child.areAllChildrenLoadedAndHidden()) {
417
- allLoadedAndHidden = false;
418
- return false;
419
- }
420
- }
421
- return true;
422
- });
423
- return allLoadedAndHidden;
424
- }
425
-
426
- /**
427
- * Node is ready if it is outside frustum, if it was drawn at least once or if all it's children are ready
428
- * @returns true if ready
429
- */
430
- isReady() {
431
- // if outside frustum
432
- if (!this.inFrustum) return true;
433
-
434
- // if json is not done loading
435
- if (this.hasUnloadedJSONContent) return false;
436
-
437
- // if this tile has no mesh content or if it's marked as visible false, look at children
438
- if ((!this.hasMeshContent || !this.meshContent || !this.materialVisibility) && this.childrenTiles.length > 0) {
439
- var allChildrenReady = true;
440
- this.childrenTiles.every(child => {
441
- if (!child.isReady()) {
442
- allChildrenReady = false;
443
- return false;
444
- }
445
- return true;
446
- });
447
- return allChildrenReady;
448
- }
449
-
450
- // if this tile has no mesh content
451
- if (!this.hasMeshContent) {
452
- return true;
453
- }
454
- // if mesh content not yet loaded
455
- if (!this.meshContent) {
456
- return false;
457
- }
458
-
459
- // if this tile has been marked to hide it's content
460
- if (!this.materialVisibility) {
461
- return false;
462
- }
463
-
464
- // if all meshes have been displayed once
465
- if (!this.meshContent.displayedOnce) {
466
- return false;
467
- }
468
- return true;
469
-
470
- }
471
-
472
-
473
- changeContentVisibility(visibility) {
474
- const self = this;
475
- self.materialVisibility = visibility;
476
-
477
- /* self.meshContent.displayedOnce = false;
478
- if(visibility){
479
- self.meshContent.onAfterRender = () => {
480
- delete self.meshContent.onAfterRender;
481
- self.meshContent.displayedOnce = true;
482
- };
483
- } */
484
-
485
- }
486
- calculateUpdateMetric(camera, frustum) {
487
- ////// return -1 if not in frustum
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
- } else {
499
- console.error("unsupported shape");
500
- return -1
501
- }
502
-
503
- /////// return metric based on geometric error and distance
504
- if (this.boundingVolume instanceof OBB || this.boundingVolume instanceof THREE.Sphere) {
505
- // box
506
- const distance = Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
507
- if (distance == 0) {
508
- return 0;
509
- }
510
- const scale = this.master.matrixWorld.getMaxScaleOnAxis();
511
-
512
- this.master.renderer.getDrawingBufferSize(rendererSize);
513
- let s = rendererSize.y;
514
- let fov = camera.fov;
515
- if(camera.aspect < 1){
516
- fov *= camera.aspect;
517
- s = rendererSize.x;
518
- }
519
-
520
- let lambda = 2.0 * Math.tan(0.5 * fov * 0.01745329251994329576923690768489) * distance;
521
-
522
- return (window.devicePixelRatio * 16 * lambda) / (s * scale);
523
- } else if (this.boundingVolume instanceof THREE.Box3) {
524
- // Region
525
- // Region not supported
526
- //throw Error("Region bounding volume not supported");
527
- return -1;
528
- }
529
- }
530
-
531
- getSiblings() {
532
- const self = this;
533
- const tiles = [];
534
- if (!self.parentTile) return tiles;
535
- let p = self.parentTile;
536
- while (!p.hasMeshContent && !!p.parentTile) {
537
- p = p.parentTile;
538
- }
539
- p.childrenTiles.forEach(child => {
540
- if (!!child && child != self) {
541
- while (!child.hasMeshContent && !!child.childrenTiles[0]) {
542
- child = child.childrenTiles[0];
543
- }
544
- tiles.push(child);
545
- }
546
- });
547
- return tiles;
548
- }
549
- calculateDistanceToCamera(camera) {
550
- if (this.boundingVolume instanceof OBB) {
551
- // box
552
- tempSphere.copy(this.boundingVolume.sphere);
553
- tempSphere.applyMatrix4(this.master.matrixWorld);
554
- //if (!frustum.intersectsSphere(tempSphere)) return -1;
555
- } else if (this.boundingVolume instanceof THREE.Sphere) {
556
- //sphere
557
- tempSphere.copy(this.boundingVolume);
558
- tempSphere.applyMatrix4(this.master.matrixWorld);
559
- //if (!frustum.intersectsSphere(tempSphere)) return -1;
560
- }
561
- else {
562
- console.error("unsupported shape")
563
- }
564
- return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
565
- }
566
-
567
- getWorldMatrix(){
568
- const self = this;
569
- return self.master.matrixWorld;
570
- }
571
-
572
- transformWGS84ToCartesian(lon, lat, h, sfct) {
573
- const a = 6378137.0;
574
- const e = 0.006694384442042;
575
- const N = a / (Math.sqrt(1.0 - (e * Math.pow(Math.sin(lat), 2))));
576
- const cosLat = Math.cos(lat);
577
- const cosLon = Math.cos(lon);
578
- const sinLat = Math.sin(lat);
579
- const sinLon = Math.sin(lon);
580
- const nPh = (N + h);
581
- const x = nPh * cosLat * cosLon;
582
- const y = nPh * cosLat * sinLon;
583
- const z = (0.993305615557957 * N + h) * sinLat;
584
-
585
- sfct.set(x, y, z);
586
- }
587
- }
588
- export { InstancedTile };