t3d-3dtiles 0.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.
Files changed (83) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +41 -0
  3. package/build/t3d.3dtiles.js +5418 -0
  4. package/build/t3d.3dtiles.min.js +2 -0
  5. package/build/t3d.3dtiles.module.js +6183 -0
  6. package/examples/jsm/CesiumIon.js +30 -0
  7. package/examples/jsm/Tiles3DDebugger.js +219 -0
  8. package/examples/jsm/Tiles3DUtils.js +32 -0
  9. package/examples/jsm/helpers/LineSegmentHelper.js +74 -0
  10. package/examples/jsm/helpers/OBBHelper.js +48 -0
  11. package/examples/jsm/helpers/Tiles3DHelper.js +135 -0
  12. package/examples/jsm/math/Intersects.js +100 -0
  13. package/examples/jsm/math/LineSegment.js +234 -0
  14. package/package.json +57 -0
  15. package/src/Tiles3D.js +402 -0
  16. package/src/libs/ImageBitmapLoader.js +56 -0
  17. package/src/libs/glTF/Constants.js +85 -0
  18. package/src/libs/glTF/GLTFLoader.js +169 -0
  19. package/src/libs/glTF/GLTFResource.js +28 -0
  20. package/src/libs/glTF/GLTFUtils.js +131 -0
  21. package/src/libs/glTF/extensions/EXT_meshopt_compression.js +35 -0
  22. package/src/libs/glTF/extensions/KHR_draco_mesh_compression.js +54 -0
  23. package/src/libs/glTF/extensions/KHR_lights_punctual.js +53 -0
  24. package/src/libs/glTF/extensions/KHR_materials_clearcoat.js +42 -0
  25. package/src/libs/glTF/extensions/KHR_materials_pbrSpecularGlossiness.js +53 -0
  26. package/src/libs/glTF/extensions/KHR_materials_unlit.js +13 -0
  27. package/src/libs/glTF/extensions/KHR_texture_basisu.js +14 -0
  28. package/src/libs/glTF/extensions/KHR_texture_transform.js +31 -0
  29. package/src/libs/glTF/parsers/AccessorParser.js +99 -0
  30. package/src/libs/glTF/parsers/AnimationParser.js +118 -0
  31. package/src/libs/glTF/parsers/BufferParser.js +35 -0
  32. package/src/libs/glTF/parsers/BufferViewParser.js +27 -0
  33. package/src/libs/glTF/parsers/ImageParser.js +97 -0
  34. package/src/libs/glTF/parsers/IndexParser.js +22 -0
  35. package/src/libs/glTF/parsers/MaterialParser.js +146 -0
  36. package/src/libs/glTF/parsers/NodeParser.js +145 -0
  37. package/src/libs/glTF/parsers/PrimitiveParser.js +289 -0
  38. package/src/libs/glTF/parsers/ReferenceParser.js +67 -0
  39. package/src/libs/glTF/parsers/SceneParser.js +41 -0
  40. package/src/libs/glTF/parsers/SkinParser.js +53 -0
  41. package/src/libs/glTF/parsers/TextureParser.js +71 -0
  42. package/src/libs/glTF/parsers/Validator.js +16 -0
  43. package/src/loaders/B3DMLoader.js +49 -0
  44. package/src/loaders/CMPTLoader.js +48 -0
  45. package/src/loaders/I3DMLoader.js +49 -0
  46. package/src/loaders/PNTSLoader.js +20 -0
  47. package/src/loaders/TileGLTFLoader.js +17 -0
  48. package/src/loaders/parsers/HeaderParser.js +104 -0
  49. package/src/loaders/parsers/LoadParser.js +22 -0
  50. package/src/loaders/parsers/TableParser.js +66 -0
  51. package/src/loaders/parsers/b3dm/B3DMParser.js +18 -0
  52. package/src/loaders/parsers/b3dm/B3DMRootParser.js +22 -0
  53. package/src/loaders/parsers/b3dm/MaterialParser.js +156 -0
  54. package/src/loaders/parsers/cmpt/CMPTParser.js +37 -0
  55. package/src/loaders/parsers/cmpt/CMPTRootParser.js +44 -0
  56. package/src/loaders/parsers/gltf/IndexParser.js +24 -0
  57. package/src/loaders/parsers/i3dm/I3DMParser.js +28 -0
  58. package/src/loaders/parsers/i3dm/I3DMRootParser.js +123 -0
  59. package/src/loaders/parsers/i3dm/MaterialParser.js +152 -0
  60. package/src/loaders/parsers/i3dm/PrimitiveParser.js +291 -0
  61. package/src/loaders/parsers/pnts/PNTSRootParser.js +69 -0
  62. package/src/main.js +14 -0
  63. package/src/materials/InstancedBasicMaterial.js +32 -0
  64. package/src/materials/InstancedPBRMaterial.js +32 -0
  65. package/src/materials/InstancedShaderChunks.js +23 -0
  66. package/src/math/Ellipsoid.js +41 -0
  67. package/src/math/EllipsoidRegion.js +160 -0
  68. package/src/math/FastFrustum.js +49 -0
  69. package/src/math/GeoUtils.js +31 -0
  70. package/src/math/OBB.js +395 -0
  71. package/src/math/TileBoundingVolume.js +172 -0
  72. package/src/math/TileOBB.js +103 -0
  73. package/src/utils/BatchTable.js +14 -0
  74. package/src/utils/CameraList.js +131 -0
  75. package/src/utils/FeatureTable.js +107 -0
  76. package/src/utils/IntersectionUtils.js +136 -0
  77. package/src/utils/LRUCache.js +159 -0
  78. package/src/utils/ModelLoader.js +150 -0
  79. package/src/utils/PriorityQueue.js +102 -0
  80. package/src/utils/RequestState.js +17 -0
  81. package/src/utils/TilesLoader.js +386 -0
  82. package/src/utils/TilesScheduler.js +375 -0
  83. package/src/utils/Utils.js +43 -0
@@ -0,0 +1,102 @@
1
+
2
+ export class PriorityQueue {
3
+
4
+ constructor({ maxJobs = 6, autoUpdate = true, priorityCallback = defaultPriorityCallback }) {
5
+ // options
6
+ this.maxJobs = maxJobs;
7
+ this.autoUpdate = autoUpdate;
8
+
9
+ this.items = [];
10
+ this.callbacks = new Map();
11
+ this.currJobs = 0;
12
+ this.scheduled = false;
13
+
14
+ this.priorityCallback = priorityCallback;
15
+
16
+ this._runjobs = () => {
17
+ this.tryRunJobs();
18
+ this.scheduled = false;
19
+ };
20
+ }
21
+
22
+ // Customizable scheduling callback. Default using requestAnimationFrame()
23
+ schedulingCallback(func) {
24
+ requestAnimationFrame(func);
25
+ }
26
+
27
+ sort() {
28
+ const priorityCallback = this.priorityCallback;
29
+ const items = this.items;
30
+ items.sort(priorityCallback);
31
+ }
32
+
33
+ add(item, callback) {
34
+ return new Promise((resolve, reject) => {
35
+ const prCallback = (...args) => callback(...args).then(resolve).catch(reject);
36
+ const items = this.items;
37
+ const callbacks = this.callbacks;
38
+
39
+ items.push(item);
40
+ callbacks.set(item, prCallback);
41
+
42
+ if (this.autoUpdate) {
43
+ this.scheduleJobRun();
44
+ }
45
+ });
46
+ }
47
+
48
+ remove(item) {
49
+ const items = this.items;
50
+ const callbacks = this.callbacks;
51
+
52
+ const index = items.indexOf(item);
53
+ if (index !== -1) {
54
+ items.splice(index, 1);
55
+ callbacks.delete(item);
56
+ }
57
+ }
58
+
59
+ tryRunJobs() {
60
+ this.sort();
61
+
62
+ const items = this.items;
63
+ const callbacks = this.callbacks;
64
+ const maxJobs = this.maxJobs;
65
+ let currJobs = this.currJobs;
66
+ while (maxJobs > currJobs && items.length > 0) {
67
+ currJobs++;
68
+ const item = items.pop();
69
+ const callback = callbacks.get(item);
70
+ callbacks.delete(item);
71
+ callback(item)
72
+ .then(() => {
73
+ this.currJobs--;
74
+
75
+ if (this.autoUpdate) {
76
+ this.scheduleJobRun();
77
+ }
78
+ })
79
+ .catch(() => {
80
+ this.currJobs--;
81
+
82
+ if (this.autoUpdate) {
83
+ this.scheduleJobRun();
84
+ }
85
+ });
86
+ }
87
+ this.currJobs = currJobs;
88
+ }
89
+
90
+ scheduleJobRun() {
91
+ if (!this.scheduled) {
92
+ this.schedulingCallback(this._runjobs.bind(this));
93
+
94
+ this.scheduled = true;
95
+ }
96
+ }
97
+
98
+ }
99
+
100
+ const defaultPriorityCallback = () => {
101
+ throw new Error('PriorityQueue: PriorityCallback function not defined.');
102
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * State of the request.
3
+ *
4
+ * @enum {Number}
5
+ */
6
+ const RequestState = {
7
+ UNLOADED: 0,
8
+
9
+ LOADING: 1,
10
+
11
+ PARSING: 2,
12
+
13
+ LOADED: 3,
14
+
15
+ FAILED: 4
16
+ };
17
+ export default Object.freeze(RequestState);
@@ -0,0 +1,386 @@
1
+ import { Matrix4, Vector3 } from 't3d';
2
+ import { TileBoundingVolume } from '../math/TileBoundingVolume.js';
3
+ import RequestState from './RequestState.js';
4
+ import { LRUCache } from './LRUCache.js';
5
+ import { PriorityQueue } from './PriorityQueue.js';
6
+ import { traverseSet, getUrlExtension } from './Utils.js';
7
+
8
+ export class TilesLoader {
9
+
10
+ constructor() {
11
+ this.lruCache = new LRUCache({ unloadPriorityCallback: lruPriorityCallback });
12
+ this.downloadQueue = new PriorityQueue({ maxJobs: 4, priorityCallback });
13
+ this.parseQueue = new PriorityQueue({ maxJobs: 1, priorityCallback });
14
+ }
15
+
16
+ fetchTileSet(url, parent = null, fetchOptions = {}) {
17
+ return fetch(url, fetchOptions).then(res => {
18
+ if (res.ok) {
19
+ return res.json();
20
+ } else {
21
+ throw new Error(`TilesLoader: Failed to load tileset "${url}" with status ${res.status} : ${res.statusText}`);
22
+ }
23
+ }).then(json => {
24
+ const version = json.asset.version;
25
+ const [major, minor] = version.split('.').map(v => parseInt(v));
26
+ console.assert(
27
+ major <= 1,
28
+ 'TilesLoader: asset.version is expected to be a 1.x or a compatible version.'
29
+ );
30
+
31
+ if (major === 1 && minor > 0) {
32
+ console.warn('TilesLoader: tiles versions at 1.1 or higher have limited support. Some new extensions and features may not be supported.');
33
+ }
34
+
35
+ // remove trailing slash and last path-segment from the URL
36
+ const basePath = url.replace(/\/[^/]*\/?$/, '');
37
+
38
+ traverseSet(
39
+ json.root,
40
+ (node, parent) => preprocessTile(node, parent, basePath),
41
+ null,
42
+ parent,
43
+ parent ? parent.__depth : 0
44
+ );
45
+
46
+ return json;
47
+ });
48
+ }
49
+
50
+ requestTileContents(tile, tiles3D) {
51
+ // If the tile is already being loaded then don't
52
+ // start it again.
53
+ if (tile.__loadingState !== RequestState.UNLOADED) {
54
+ return;
55
+ }
56
+
57
+ const stats = tiles3D.stats;
58
+ const lruCache = this.lruCache;
59
+ const downloadQueue = this.downloadQueue;
60
+ const parseQueue = this.parseQueue;
61
+
62
+ const isExternalTileSet = tile.__externalTileSet;
63
+
64
+ lruCache.add(tile, t => {
65
+ if (t.__loadingState === RequestState.LOADING) {
66
+ // Stop the load if it's started
67
+ t.__loadAbort.abort();
68
+ t.__loadAbort = null;
69
+ } else if (isExternalTileSet) {
70
+ t.children.length = 0;
71
+ } else {
72
+ tiles3D.$disposeTile(t);
73
+ }
74
+
75
+ // Decrement stats
76
+ if (t.__loadingState === RequestState.LOADING) {
77
+ stats.downloading--;
78
+ } else if (t.__loadingState === RequestState.PARSING) {
79
+ stats.parsing--;
80
+ }
81
+
82
+ t.__loadingState = RequestState.UNLOADED;
83
+ t.__loadIndex++;
84
+
85
+ downloadQueue.remove(t);
86
+ parseQueue.remove(t);
87
+ });
88
+
89
+ // Track a new load index so we avoid the condition where this load is stopped and
90
+ // another begins soon after so we don't parse twice.
91
+ tile.__loadIndex++;
92
+ const loadIndex = tile.__loadIndex;
93
+ const controller = new AbortController();
94
+ const signal = controller.signal;
95
+
96
+ stats.downloading++;
97
+ tile.__loadAbort = controller;
98
+ tile.__loadingState = RequestState.LOADING;
99
+
100
+ const errorCallback = e => {
101
+ // if it has been unloaded then the tile has been disposed
102
+ if (tile.__loadIndex !== loadIndex) {
103
+ return;
104
+ }
105
+
106
+ if (e.name !== 'AbortError') {
107
+ downloadQueue.remove(tile);
108
+ parseQueue.remove(tile);
109
+
110
+ if (tile.__loadingState === RequestState.PARSING) {
111
+ stats.parsing--;
112
+ } else if (tile.__loadingState === RequestState.LOADING) {
113
+ stats.downloading--;
114
+ }
115
+
116
+ stats.failed++;
117
+ tile.__loadingState = RequestState.FAILED;
118
+
119
+ // Handle fetch bug for switching examples in index.html.
120
+ // https://stackoverflow.com/questions/12009423/what-does-status-canceled-for-a-resource-mean-in-chrome-developer-tools
121
+ if (e.message !== 'Failed to fetch') {
122
+ console.error(`TilesLoader: Failed to load tile at url "${tile.content.uri}".`);
123
+ console.error(e);
124
+ }
125
+ } else {
126
+ lruCache.remove(tile);
127
+ }
128
+ };
129
+
130
+ if (isExternalTileSet) {
131
+ downloadQueue.add(tile, tileCb => {
132
+ // if it has been unloaded then the tile has been disposed
133
+ if (tileCb.__loadIndex !== loadIndex) {
134
+ return Promise.resolve();
135
+ }
136
+
137
+ const preprocessURL = tiles3D.preprocessURL;
138
+ const fetchOptions = tiles3D.fetchOptions;
139
+ const uri = preprocessURL ? preprocessURL(tileCb.content.uri) : tileCb.content.uri;
140
+ return this.fetchTileSet(uri, tileCb, Object.assign({ signal }, fetchOptions));
141
+ }).then(json => {
142
+ // if it has been unloaded then the tile has been disposed
143
+ if (tile.__loadIndex !== loadIndex) {
144
+ return;
145
+ }
146
+
147
+ stats.downloading--;
148
+ tile.__loadAbort = null;
149
+ tile.__loadingState = RequestState.LOADED;
150
+
151
+ tile.children.push(json.root);
152
+ }).catch(errorCallback);
153
+ } else {
154
+ downloadQueue.add(tile, downloadTile => {
155
+ if (downloadTile.__loadIndex !== loadIndex) {
156
+ return Promise.resolve();
157
+ }
158
+
159
+ const preprocessURL = tiles3D.preprocessURL;
160
+ const fetchOptions = tiles3D.fetchOptions;
161
+ const uri = preprocessURL ? preprocessURL(downloadTile.content.uri) : downloadTile.content.uri;
162
+ return fetch(uri, Object.assign({ signal }, fetchOptions));
163
+ }).then(res => {
164
+ if (tile.__loadIndex !== loadIndex) {
165
+ return;
166
+ }
167
+
168
+ if (res.ok) {
169
+ const extension = getUrlExtension(res.url);
170
+ if (extension === 'gltf') {
171
+ return res.json();
172
+ } else {
173
+ return res.arrayBuffer();
174
+ }
175
+ } else {
176
+ throw new Error(`Failed to load model with error code ${res.status}`);
177
+ }
178
+ }).then(buffer => {
179
+ // if it has been unloaded then the tile has been disposed
180
+ if (tile.__loadIndex !== loadIndex) {
181
+ return;
182
+ }
183
+
184
+ stats.downloading--;
185
+ stats.parsing++;
186
+ tile.__loadAbort = null;
187
+ tile.__loadingState = RequestState.PARSING;
188
+
189
+ return parseQueue.add(tile, parseTile => {
190
+ // if it has been unloaded then the tile has been disposed
191
+ if (parseTile.__loadIndex !== loadIndex) {
192
+ return Promise.resolve();
193
+ }
194
+
195
+ const uri = parseTile.content.uri;
196
+ const extension = getUrlExtension(uri);
197
+
198
+ return tiles3D.$parseTile(buffer, parseTile, extension);
199
+ });
200
+ }).then(() => {
201
+ // if it has been unloaded then the tile has been disposed
202
+ if (tile.__loadIndex !== loadIndex) {
203
+ return;
204
+ }
205
+
206
+ stats.parsing--;
207
+ tile.__loadingState = RequestState.LOADED;
208
+
209
+ if (tile.__wasSetVisible) {
210
+ tiles3D.$setTileVisible(tile, true);
211
+ }
212
+
213
+ if (tile.__wasSetActive) {
214
+ tiles3D.$setTileActive(tile, true);
215
+ }
216
+ }).catch(errorCallback);
217
+ }
218
+ }
219
+
220
+ }
221
+
222
+ /**
223
+ * Function for sorting the evicted LRU items. We should evict the shallowest depth first.
224
+ * @param {Tile} tile
225
+ * @returns number
226
+ */
227
+ const lruPriorityCallback = (map, tile) => 1 / (tile.__depthFromRenderedParent + 1);
228
+
229
+ /**
230
+ * Function for provided to sort all tiles for prioritizing loading.
231
+ *
232
+ * @param {Tile} a
233
+ * @param {Tile} b
234
+ * @returns number
235
+ */
236
+ const priorityCallback = (a, b) => {
237
+ if (a.__depth !== b.__depth) {
238
+ // load shallower tiles first
239
+ return a.__depth > b.__depth ? -1 : 1;
240
+ } else if (a.__inFrustum !== b.__inFrustum) {
241
+ // load tiles that are in the frustum at the current depth
242
+ return a.__inFrustum ? 1 : -1;
243
+ } else if (a.__used !== b.__used) {
244
+ // load tiles that have been used
245
+ return a.__used ? 1 : -1;
246
+ } else if (a.__error !== b.__error) {
247
+ // load the tile with the higher error
248
+ return a.__error > b.__error ? 1 : -1;
249
+ } else if (a.__distanceFromCamera !== b.__distanceFromCamera) {
250
+ // and finally visible tiles which have equal error (ex: if geometricError === 0)
251
+ // should prioritize based on distance.
252
+ return a.__distanceFromCamera > b.__distanceFromCamera ? -1 : 1;
253
+ }
254
+
255
+ return 0;
256
+ };
257
+
258
+ const preprocessTile = (tile, parentTile, tileSetDir) => {
259
+ if (tile.contents) {
260
+ // TODO: multiple contents (1.1) are not supported yet
261
+ tile.content = tile.contents[0];
262
+ }
263
+
264
+ if (tile.content) {
265
+ // Fix old file formats
266
+ if (!('uri' in tile.content) && 'url' in tile.content) {
267
+ tile.content.uri = tile.content.url;
268
+ delete tile.content.url;
269
+ }
270
+
271
+ if (tile.content.uri) {
272
+ // tile content uri has to be interpreted relative to the tileset.json
273
+ // tile.content.uri = new URL( tile.content.uri, tileSetDir + '/' ).toString();
274
+ tile.content.uri = `${tileSetDir}/${tile.content.uri}`;
275
+ }
276
+
277
+ // NOTE: fix for some cases where tile provide the bounding volume
278
+ // but volumes are not present.
279
+ if (
280
+ tile.content.boundingVolume &&
281
+ !(
282
+ 'box' in tile.content.boundingVolume ||
283
+ 'sphere' in tile.content.boundingVolume ||
284
+ 'region' in tile.content.boundingVolume
285
+ )
286
+ ) {
287
+ delete tile.content.boundingVolume;
288
+ }
289
+ }
290
+
291
+ tile.parent = parentTile;
292
+ tile.children = tile.children || [];
293
+
294
+ const uri = tile.content && tile.content.uri;
295
+ if (uri) {
296
+ // "content" should only indicate loadable meshes, not external tile sets
297
+ const extension = getUrlExtension(tile.content.uri);
298
+ const isExternalTileSet = Boolean(extension && extension.toLowerCase() === 'json');
299
+ tile.__externalTileSet = isExternalTileSet;
300
+ tile.__contentEmpty = isExternalTileSet;
301
+ } else {
302
+ tile.__externalTileSet = false;
303
+ tile.__contentEmpty = true;
304
+ }
305
+
306
+ // Expected to be set during calculateError()
307
+ tile.__distanceFromCamera = Infinity;
308
+ tile.__error = Infinity;
309
+
310
+ tile.__inFrustum = false;
311
+ tile.__isLeaf = false;
312
+
313
+ tile.__usedLastFrame = false;
314
+ tile.__used = false;
315
+
316
+ tile.__wasSetVisible = false;
317
+ tile.__visible = false;
318
+ tile.__childrenWereVisible = false;
319
+ tile.__allChildrenLoaded = false;
320
+
321
+ tile.__wasSetActive = false;
322
+ tile.__active = false;
323
+
324
+ tile.__loadingState = RequestState.UNLOADED;
325
+ tile.__loadIndex = 0;
326
+
327
+ tile.__loadAbort = null;
328
+
329
+ tile.__depthFromRenderedParent = -1;
330
+ if (parentTile === null) {
331
+ tile.__depth = 0;
332
+ tile.refine = tile.refine || 'REPLACE';
333
+ } else {
334
+ tile.__depth = parentTile.__depth + 1;
335
+ tile.refine = tile.refine || parentTile.refine;
336
+ }
337
+
338
+ //
339
+
340
+ const transform = new Matrix4();
341
+ if (tile.transform) {
342
+ transform.fromArray(tile.transform);
343
+ }
344
+ if (parentTile) {
345
+ transform.premultiply(parentTile.cached.transform);
346
+ }
347
+ const transformInverse = (new Matrix4()).copy(transform).inverse();
348
+
349
+ const transformScale = _vec3_1.setFromMatrixScale(transform);
350
+ const uniformScale = Math.max(transformScale.x, transformScale.y, transformScale.z);
351
+ let geometricError = tile.geometricError * uniformScale;
352
+
353
+ const boundingVolume = new TileBoundingVolume();
354
+ if ('box' in tile.boundingVolume) {
355
+ boundingVolume.setOBBData(tile.boundingVolume.box, transform);
356
+ }
357
+ if ('sphere' in tile.boundingVolume) {
358
+ boundingVolume.setSphereData(tile.boundingVolume.sphere, transform);
359
+ }
360
+ if ('region' in tile.boundingVolume) {
361
+ boundingVolume.setRegionData(...tile.boundingVolume.region);
362
+ geometricError = tile.geometricError;
363
+ }
364
+
365
+ tile.cached = {
366
+ loadIndex: 0,
367
+ transform,
368
+ transformInverse,
369
+
370
+ geometricError, // geometric error applied tile transform scale
371
+
372
+ boundingVolume,
373
+
374
+ active: false,
375
+ inFrustum: [],
376
+
377
+ scene: null,
378
+ geometry: null,
379
+ material: null,
380
+
381
+ featureTable: null,
382
+ batchTable: null
383
+ };
384
+ };
385
+
386
+ const _vec3_1 = new Vector3();