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.
- package/LICENSE +28 -0
- package/README.md +41 -0
- package/build/t3d.3dtiles.js +5418 -0
- package/build/t3d.3dtiles.min.js +2 -0
- package/build/t3d.3dtiles.module.js +6183 -0
- package/examples/jsm/CesiumIon.js +30 -0
- package/examples/jsm/Tiles3DDebugger.js +219 -0
- package/examples/jsm/Tiles3DUtils.js +32 -0
- package/examples/jsm/helpers/LineSegmentHelper.js +74 -0
- package/examples/jsm/helpers/OBBHelper.js +48 -0
- package/examples/jsm/helpers/Tiles3DHelper.js +135 -0
- package/examples/jsm/math/Intersects.js +100 -0
- package/examples/jsm/math/LineSegment.js +234 -0
- package/package.json +57 -0
- package/src/Tiles3D.js +402 -0
- package/src/libs/ImageBitmapLoader.js +56 -0
- package/src/libs/glTF/Constants.js +85 -0
- package/src/libs/glTF/GLTFLoader.js +169 -0
- package/src/libs/glTF/GLTFResource.js +28 -0
- package/src/libs/glTF/GLTFUtils.js +131 -0
- package/src/libs/glTF/extensions/EXT_meshopt_compression.js +35 -0
- package/src/libs/glTF/extensions/KHR_draco_mesh_compression.js +54 -0
- package/src/libs/glTF/extensions/KHR_lights_punctual.js +53 -0
- package/src/libs/glTF/extensions/KHR_materials_clearcoat.js +42 -0
- package/src/libs/glTF/extensions/KHR_materials_pbrSpecularGlossiness.js +53 -0
- package/src/libs/glTF/extensions/KHR_materials_unlit.js +13 -0
- package/src/libs/glTF/extensions/KHR_texture_basisu.js +14 -0
- package/src/libs/glTF/extensions/KHR_texture_transform.js +31 -0
- package/src/libs/glTF/parsers/AccessorParser.js +99 -0
- package/src/libs/glTF/parsers/AnimationParser.js +118 -0
- package/src/libs/glTF/parsers/BufferParser.js +35 -0
- package/src/libs/glTF/parsers/BufferViewParser.js +27 -0
- package/src/libs/glTF/parsers/ImageParser.js +97 -0
- package/src/libs/glTF/parsers/IndexParser.js +22 -0
- package/src/libs/glTF/parsers/MaterialParser.js +146 -0
- package/src/libs/glTF/parsers/NodeParser.js +145 -0
- package/src/libs/glTF/parsers/PrimitiveParser.js +289 -0
- package/src/libs/glTF/parsers/ReferenceParser.js +67 -0
- package/src/libs/glTF/parsers/SceneParser.js +41 -0
- package/src/libs/glTF/parsers/SkinParser.js +53 -0
- package/src/libs/glTF/parsers/TextureParser.js +71 -0
- package/src/libs/glTF/parsers/Validator.js +16 -0
- package/src/loaders/B3DMLoader.js +49 -0
- package/src/loaders/CMPTLoader.js +48 -0
- package/src/loaders/I3DMLoader.js +49 -0
- package/src/loaders/PNTSLoader.js +20 -0
- package/src/loaders/TileGLTFLoader.js +17 -0
- package/src/loaders/parsers/HeaderParser.js +104 -0
- package/src/loaders/parsers/LoadParser.js +22 -0
- package/src/loaders/parsers/TableParser.js +66 -0
- package/src/loaders/parsers/b3dm/B3DMParser.js +18 -0
- package/src/loaders/parsers/b3dm/B3DMRootParser.js +22 -0
- package/src/loaders/parsers/b3dm/MaterialParser.js +156 -0
- package/src/loaders/parsers/cmpt/CMPTParser.js +37 -0
- package/src/loaders/parsers/cmpt/CMPTRootParser.js +44 -0
- package/src/loaders/parsers/gltf/IndexParser.js +24 -0
- package/src/loaders/parsers/i3dm/I3DMParser.js +28 -0
- package/src/loaders/parsers/i3dm/I3DMRootParser.js +123 -0
- package/src/loaders/parsers/i3dm/MaterialParser.js +152 -0
- package/src/loaders/parsers/i3dm/PrimitiveParser.js +291 -0
- package/src/loaders/parsers/pnts/PNTSRootParser.js +69 -0
- package/src/main.js +14 -0
- package/src/materials/InstancedBasicMaterial.js +32 -0
- package/src/materials/InstancedPBRMaterial.js +32 -0
- package/src/materials/InstancedShaderChunks.js +23 -0
- package/src/math/Ellipsoid.js +41 -0
- package/src/math/EllipsoidRegion.js +160 -0
- package/src/math/FastFrustum.js +49 -0
- package/src/math/GeoUtils.js +31 -0
- package/src/math/OBB.js +395 -0
- package/src/math/TileBoundingVolume.js +172 -0
- package/src/math/TileOBB.js +103 -0
- package/src/utils/BatchTable.js +14 -0
- package/src/utils/CameraList.js +131 -0
- package/src/utils/FeatureTable.js +107 -0
- package/src/utils/IntersectionUtils.js +136 -0
- package/src/utils/LRUCache.js +159 -0
- package/src/utils/ModelLoader.js +150 -0
- package/src/utils/PriorityQueue.js +102 -0
- package/src/utils/RequestState.js +17 -0
- package/src/utils/TilesLoader.js +386 -0
- package/src/utils/TilesScheduler.js +375 -0
- package/src/utils/Utils.js +43 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Vector2, Vector3, Matrix4 } from 't3d';
|
|
2
|
+
import { FastFrustum } from '../math/FastFrustum.js';
|
|
3
|
+
|
|
4
|
+
export class CameraList {
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this._cameras = [];
|
|
8
|
+
this._resolution = new Vector2();
|
|
9
|
+
this._infos = [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
add(camera) {
|
|
13
|
+
const cameras = this._cameras;
|
|
14
|
+
|
|
15
|
+
if (cameras.indexOf(camera) === -1) {
|
|
16
|
+
cameras.push(camera);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
remove(camera) {
|
|
24
|
+
const cameras = this._cameras;
|
|
25
|
+
const index = cameras.indexOf(camera);
|
|
26
|
+
|
|
27
|
+
if (index !== -1) {
|
|
28
|
+
cameras.splice(index, 1);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setResolution(width, height) {
|
|
36
|
+
this._resolution.set(width, height);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
updateInfos(originMatrix) {
|
|
40
|
+
const cameras = this._cameras;
|
|
41
|
+
const cameraCount = cameras.length;
|
|
42
|
+
const infos = this._infos;
|
|
43
|
+
const resolution = this._resolution;
|
|
44
|
+
|
|
45
|
+
if (cameraCount === 0) {
|
|
46
|
+
console.warn('CameraList.updateInfos(): No camera added.');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// automatically scale the array of infos to match the cameras
|
|
51
|
+
|
|
52
|
+
while (infos.length > cameras.length) {
|
|
53
|
+
infos.pop();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
while (infos.length < cameras.length) {
|
|
57
|
+
infos.push({
|
|
58
|
+
frustum: new FastFrustum(), // in origin space
|
|
59
|
+
isOrthographic: false,
|
|
60
|
+
sseDenominator: -1, // used if isOrthographic is false
|
|
61
|
+
position: new Vector3(), // in origin space
|
|
62
|
+
invScale: -1,
|
|
63
|
+
pixelSize: 0 // used if isOrthographic is true
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// get inverse scale of origin matrix
|
|
68
|
+
|
|
69
|
+
_mat4_1.copy(originMatrix).inverse();
|
|
70
|
+
|
|
71
|
+
const invScaleX = _vec3_1.setFromMatrixColumn(_mat4_1, 0).getLength();
|
|
72
|
+
const invScaleY = _vec3_1.setFromMatrixColumn(_mat4_1, 1).getLength();
|
|
73
|
+
const invScaleZ = _vec3_1.setFromMatrixColumn(_mat4_1, 2).getLength();
|
|
74
|
+
|
|
75
|
+
if (Math.abs(Math.max(invScaleX - invScaleY, invScaleX - invScaleZ)) > 1e-6) {
|
|
76
|
+
console.warn('CameraList.updateInfos(): Non uniform scale used for tile which may cause issues when calculating screen space error.');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const invScale = invScaleX;
|
|
80
|
+
const invOriginMatrix = _mat4_1;
|
|
81
|
+
|
|
82
|
+
// update the camera infos
|
|
83
|
+
|
|
84
|
+
for (let i = 0, l = infos.length; i < l; i++) {
|
|
85
|
+
const camera = cameras[i];
|
|
86
|
+
const info = infos[i];
|
|
87
|
+
|
|
88
|
+
const cameraResolutionX = resolution.x * (camera.rect.z - camera.rect.x);
|
|
89
|
+
const cameraResolutionY = resolution.y * (camera.rect.w - camera.rect.y);
|
|
90
|
+
|
|
91
|
+
if (cameraResolutionX === 0 || cameraResolutionY === 0) {
|
|
92
|
+
console.warn('CameraList.updateInfos(): Resolution for camera error calculation is not set.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Read the calculated projection matrix directly to support custom Camera implementations
|
|
96
|
+
const projection = camera.projectionMatrix.elements;
|
|
97
|
+
|
|
98
|
+
// The last element of the projection matrix is 1 for orthographic, 0 for perspective
|
|
99
|
+
info.isOrthographic = projection[15] === 1;
|
|
100
|
+
|
|
101
|
+
if (info.isOrthographic) {
|
|
102
|
+
// the view width and height are used to populate matrix elements 0 and 5.
|
|
103
|
+
const w = 2 / projection[0];
|
|
104
|
+
const h = 2 / projection[5];
|
|
105
|
+
info.pixelSize = Math.max(h / cameraResolutionY, w / cameraResolutionX);
|
|
106
|
+
} else {
|
|
107
|
+
// the vertical FOV is used to populate matrix element 5.
|
|
108
|
+
info.sseDenominator = (2 / projection[5]) / cameraResolutionY;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
info.invScale = invScale;
|
|
112
|
+
|
|
113
|
+
// get frustum in origin space
|
|
114
|
+
_mat4_2.copy(originMatrix).premultiply(camera.projectionViewMatrix);
|
|
115
|
+
info.frustum.setFromMatrix(_mat4_2);
|
|
116
|
+
info.frustum.updateCache();
|
|
117
|
+
|
|
118
|
+
// get camera position in origin space
|
|
119
|
+
info.position.setFromMatrixPosition(camera.worldMatrix).applyMatrix4(invOriginMatrix);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getInfos() {
|
|
124
|
+
return this._infos;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const _mat4_1 = new Matrix4();
|
|
130
|
+
const _mat4_2 = new Matrix4();
|
|
131
|
+
const _vec3_1 = new Vector3();
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
|
|
2
|
+
import { GLTFUtils } from '../libs/glTF/GLTFUtils.js';
|
|
3
|
+
|
|
4
|
+
export class FeatureTable {
|
|
5
|
+
|
|
6
|
+
constructor(buffer, start, headerLength, binLength) {
|
|
7
|
+
this.buffer = buffer;
|
|
8
|
+
this.binOffset = start + headerLength;
|
|
9
|
+
this.binLength = binLength;
|
|
10
|
+
|
|
11
|
+
let header = null;
|
|
12
|
+
if (headerLength !== 0) {
|
|
13
|
+
const headerData = new Uint8Array(buffer, start, headerLength);
|
|
14
|
+
header = JSON.parse(GLTFUtils.decodeText(headerData));
|
|
15
|
+
} else {
|
|
16
|
+
header = {};
|
|
17
|
+
}
|
|
18
|
+
this.header = header;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getKeys() {
|
|
22
|
+
return Object.keys(this.header);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getData(key, count, defaultComponentType = null, defaultType = null) {
|
|
26
|
+
const header = this.header;
|
|
27
|
+
|
|
28
|
+
if (!(key in header)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const feature = header[key];
|
|
33
|
+
if (!(feature instanceof Object)) {
|
|
34
|
+
return feature;
|
|
35
|
+
} else if (Array.isArray(feature)) {
|
|
36
|
+
return feature;
|
|
37
|
+
} else {
|
|
38
|
+
const { buffer, binOffset, binLength } = this;
|
|
39
|
+
const byteOffset = feature.byteOffset || 0;
|
|
40
|
+
const featureType = feature.type || defaultType;
|
|
41
|
+
const featureComponentType = feature.componentType || defaultComponentType;
|
|
42
|
+
|
|
43
|
+
if ('type' in feature && defaultType && feature.type !== defaultType) {
|
|
44
|
+
throw new Error('FeatureTable: Specified type does not match expected type.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let stride;
|
|
48
|
+
switch (featureType) {
|
|
49
|
+
case 'SCALAR':
|
|
50
|
+
stride = 1;
|
|
51
|
+
break;
|
|
52
|
+
case 'VEC2':
|
|
53
|
+
stride = 2;
|
|
54
|
+
break;
|
|
55
|
+
case 'VEC3':
|
|
56
|
+
stride = 3;
|
|
57
|
+
break;
|
|
58
|
+
case 'VEC4':
|
|
59
|
+
stride = 4;
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
throw new Error(`FeatureTable: Feature type not provided for "${key}".`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let data;
|
|
66
|
+
const arrayStart = binOffset + byteOffset;
|
|
67
|
+
const arrayLength = count * stride;
|
|
68
|
+
|
|
69
|
+
switch (featureComponentType) {
|
|
70
|
+
case 'BYTE':
|
|
71
|
+
data = new Int8Array(buffer, arrayStart, arrayLength);
|
|
72
|
+
break;
|
|
73
|
+
case 'UNSIGNED_BYTE':
|
|
74
|
+
data = new Uint8Array(buffer, arrayStart, arrayLength);
|
|
75
|
+
break;
|
|
76
|
+
case 'SHORT':
|
|
77
|
+
data = new Int16Array(buffer, arrayStart, arrayLength);
|
|
78
|
+
break;
|
|
79
|
+
case 'UNSIGNED_SHORT':
|
|
80
|
+
data = new Uint16Array(buffer, arrayStart, arrayLength);
|
|
81
|
+
break;
|
|
82
|
+
case 'INT':
|
|
83
|
+
data = new Int32Array(buffer, arrayStart, arrayLength);
|
|
84
|
+
break;
|
|
85
|
+
case 'UNSIGNED_INT':
|
|
86
|
+
data = new Uint32Array(buffer, arrayStart, arrayLength);
|
|
87
|
+
break;
|
|
88
|
+
case 'FLOAT':
|
|
89
|
+
data = new Float32Array(buffer, arrayStart, arrayLength);
|
|
90
|
+
break;
|
|
91
|
+
case 'DOUBLE':
|
|
92
|
+
data = new Float64Array(buffer, arrayStart, arrayLength);
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
throw new Error(`FeatureTable: Feature component type not provided for "${key}".`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const dataEnd = arrayStart + arrayLength * data.BYTES_PER_ELEMENT;
|
|
99
|
+
if (dataEnd > binOffset + binLength) {
|
|
100
|
+
throw new Error('FeatureTable: Feature data read outside binary body length.');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return data;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Matrix4, Ray, Vector3 } from 't3d';
|
|
2
|
+
|
|
3
|
+
export const raycastTraverse = (tile, tiles3D, ray, intersects, localRay = null) => {
|
|
4
|
+
const { activeTiles } = tiles3D;
|
|
5
|
+
const boundingVolume = tile.cached.boundingVolume;
|
|
6
|
+
|
|
7
|
+
// reuse the ray when traversing the tree
|
|
8
|
+
if (localRay === null) {
|
|
9
|
+
localRay = _ray_1;
|
|
10
|
+
_mat4_1.copy(tiles3D.worldMatrix).inverse();
|
|
11
|
+
localRay.copy(ray).applyMatrix4(_mat4_1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!tile.__used || !boundingVolume.intersectsRay(localRay)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (activeTiles.has(tile)) {
|
|
19
|
+
_intersectTileScene(tile, ray, intersects);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const children = tile.children;
|
|
23
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
24
|
+
raycastTraverse(children[i], tiles3D, ray, intersects, localRay);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Returns the closest hit when traversing the tree
|
|
29
|
+
export const raycastTraverseFirstHit = (tile, tiles3D, ray, localRay = null) => {
|
|
30
|
+
const { activeTiles } = tiles3D;
|
|
31
|
+
|
|
32
|
+
// reuse the ray when traversing the tree
|
|
33
|
+
if (localRay === null) {
|
|
34
|
+
localRay = _ray_1;
|
|
35
|
+
_mat4_1.copy(tiles3D.worldMatrix).inverse();
|
|
36
|
+
localRay.copy(ray).applyMatrix4(_mat4_1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// get a set of intersections so we intersect the nearest one first
|
|
40
|
+
const array = [];
|
|
41
|
+
const children = tile.children;
|
|
42
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
43
|
+
const child = children[i];
|
|
44
|
+
|
|
45
|
+
if (!child.__used) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const boundingVolume = child.cached.boundingVolume;
|
|
50
|
+
|
|
51
|
+
if (boundingVolume.intersectRay(localRay, _vec3_1)) {
|
|
52
|
+
_vec3_1.applyMatrix4(tiles3D.worldMatrix);
|
|
53
|
+
array.push({
|
|
54
|
+
distance: _vec3_1.distanceToSquared(ray.origin),
|
|
55
|
+
tile: child
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// sort them by ascending distance
|
|
61
|
+
array.sort(distanceSort);
|
|
62
|
+
|
|
63
|
+
let bestHit = null;
|
|
64
|
+
let bestHitDistSq = Infinity;
|
|
65
|
+
|
|
66
|
+
// If the root is active make sure we've checked it
|
|
67
|
+
if (activeTiles.has(tile)) {
|
|
68
|
+
_intersectTileScene(tile, ray, _hitArray);
|
|
69
|
+
|
|
70
|
+
if (_hitArray.length > 0) {
|
|
71
|
+
if (_hitArray.length > 1) {
|
|
72
|
+
_hitArray.sort(distanceSort);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const hit = _hitArray[0];
|
|
76
|
+
_hitArray.length = 0;
|
|
77
|
+
|
|
78
|
+
bestHit = hit;
|
|
79
|
+
bestHitDistSq = hit.distance * hit.distance;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// traverse until we find the best hit and early out if a tile bounds
|
|
84
|
+
// couldn't possible include a best hit
|
|
85
|
+
for (let i = 0, l = array.length; i < l; i++) {
|
|
86
|
+
const data = array[i];
|
|
87
|
+
const distanceSquared = data.distance;
|
|
88
|
+
const tile = data.tile;
|
|
89
|
+
|
|
90
|
+
if (distanceSquared > bestHitDistSq) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const hit = raycastTraverseFirstHit(tile, tiles3D, ray, localRay);
|
|
95
|
+
|
|
96
|
+
if (hit) {
|
|
97
|
+
const hitDistSq = hit.distance * hit.distance;
|
|
98
|
+
if (hitDistSq < bestHitDistSq) {
|
|
99
|
+
bestHit = hit;
|
|
100
|
+
bestHitDistSq = hitDistSq;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return bestHit;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const distanceSort = (a, b) => {
|
|
109
|
+
return a.distance - b.distance;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const _intersectTileScene = (tile, ray, intersects) => {
|
|
113
|
+
const scene = tile.cached.scene;
|
|
114
|
+
|
|
115
|
+
const lengthBefore = intersects.length;
|
|
116
|
+
|
|
117
|
+
scene.traverse(c => {
|
|
118
|
+
// We set the default raycast function to empty so t3d.js doesn't automatically cast against it
|
|
119
|
+
Object.getPrototypeOf(c).raycast.call(c, ray, intersects);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const lengthAfter = intersects.length;
|
|
123
|
+
|
|
124
|
+
// add the tile to intersects
|
|
125
|
+
if (lengthAfter > lengthBefore) {
|
|
126
|
+
for (let i = lengthBefore; i < lengthAfter; i++) {
|
|
127
|
+
intersects[i].tile = tile;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const _mat4_1 = new Matrix4();
|
|
133
|
+
const _ray_1 = new Ray();
|
|
134
|
+
const _vec3_1 = new Vector3();
|
|
135
|
+
|
|
136
|
+
const _hitArray = [];
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
|
|
2
|
+
export class LRUCache {
|
|
3
|
+
|
|
4
|
+
constructor({ maxSize = 800, minSize = 600, unloadPercent = 0.05, unloadPriorityCallback = defaultPriorityCallback }) {
|
|
5
|
+
// options
|
|
6
|
+
this.maxSize = maxSize;
|
|
7
|
+
this.minSize = minSize;
|
|
8
|
+
this.unloadPercent = unloadPercent;
|
|
9
|
+
|
|
10
|
+
// "itemSet" doubles as both the list of the full set of items currently
|
|
11
|
+
// stored in the cache (keys) as well as a map to the time the item was last
|
|
12
|
+
// used so it can be sorted appropriately.
|
|
13
|
+
this.itemSet = new Map();
|
|
14
|
+
this.itemList = [];
|
|
15
|
+
this.usedSet = new Set();
|
|
16
|
+
this.callbacks = new Map();
|
|
17
|
+
|
|
18
|
+
this.scheduled = false;
|
|
19
|
+
|
|
20
|
+
this.unloadPriorityCallback = unloadPriorityCallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
isFull() {
|
|
24
|
+
return this.itemSet.size >= this.maxSize;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
add(item, removeCb) {
|
|
28
|
+
const itemSet = this.itemSet;
|
|
29
|
+
if (itemSet.has(item)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (this.isFull()) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const usedSet = this.usedSet;
|
|
38
|
+
const itemList = this.itemList;
|
|
39
|
+
const callbacks = this.callbacks;
|
|
40
|
+
itemList.push(item);
|
|
41
|
+
usedSet.add(item);
|
|
42
|
+
itemSet.set(item, Date.now());
|
|
43
|
+
callbacks.set(item, removeCb);
|
|
44
|
+
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
remove(item) {
|
|
49
|
+
const usedSet = this.usedSet;
|
|
50
|
+
const itemSet = this.itemSet;
|
|
51
|
+
const itemList = this.itemList;
|
|
52
|
+
const callbacks = this.callbacks;
|
|
53
|
+
|
|
54
|
+
if (itemSet.has(item)) {
|
|
55
|
+
callbacks.get(item)(item);
|
|
56
|
+
|
|
57
|
+
const index = itemList.indexOf(item);
|
|
58
|
+
itemList.splice(index, 1);
|
|
59
|
+
usedSet.delete(item);
|
|
60
|
+
itemSet.delete(item);
|
|
61
|
+
callbacks.delete(item);
|
|
62
|
+
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
markUsed(item) {
|
|
70
|
+
const itemSet = this.itemSet;
|
|
71
|
+
const usedSet = this.usedSet;
|
|
72
|
+
if (!itemSet.has(item) || usedSet.has(item)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
itemSet.set(item, Date.now());
|
|
77
|
+
usedSet.add(item);
|
|
78
|
+
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
markAllUnused() {
|
|
83
|
+
this.usedSet.clear();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
unloadToMinSize() {
|
|
87
|
+
const unloadPercent = this.unloadPercent;
|
|
88
|
+
const targetSize = this.minSize;
|
|
89
|
+
const itemList = this.itemList;
|
|
90
|
+
const itemSet = this.itemSet;
|
|
91
|
+
const usedSet = this.usedSet;
|
|
92
|
+
const callbacks = this.callbacks;
|
|
93
|
+
const unused = itemList.length - usedSet.size;
|
|
94
|
+
const excess = itemList.length - targetSize;
|
|
95
|
+
const unloadPriorityCallback = this.unloadPriorityCallback;
|
|
96
|
+
|
|
97
|
+
if (excess <= 0 || unused <= 0) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// used items should be at the end of the array
|
|
102
|
+
itemList.sort((a, b) => {
|
|
103
|
+
const usedA = usedSet.has(a);
|
|
104
|
+
const usedB = usedSet.has(b);
|
|
105
|
+
if (usedA && usedB) {
|
|
106
|
+
// If they're both used then don't bother moving them
|
|
107
|
+
return 0;
|
|
108
|
+
} else if (!usedA && !usedB) {
|
|
109
|
+
// Use the sort function otherwise
|
|
110
|
+
// higher priority should be further to the left
|
|
111
|
+
return unloadPriorityCallback(itemSet, b) - unloadPriorityCallback(itemSet, a);
|
|
112
|
+
} else {
|
|
113
|
+
// If one is used and the other is not move the used one towards the end of the array
|
|
114
|
+
return usedA ? 1 : -1;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// address corner cases where the minSize might be zero or smaller than maxSize - minSize,
|
|
119
|
+
// which would result in a very small or no items being unloaded.
|
|
120
|
+
const unusedExcess = Math.min(excess, unused);
|
|
121
|
+
const maxUnload = Math.max(targetSize * unloadPercent, unusedExcess * unloadPercent);
|
|
122
|
+
let nodesToUnload = Math.min(maxUnload, unused);
|
|
123
|
+
nodesToUnload = Math.ceil(nodesToUnload);
|
|
124
|
+
|
|
125
|
+
const removedItems = itemList.splice(0, nodesToUnload);
|
|
126
|
+
for (let i = 0, l = removedItems.length; i < l; i++) {
|
|
127
|
+
const item = removedItems[i];
|
|
128
|
+
callbacks.get(item)(item);
|
|
129
|
+
itemSet.delete(item);
|
|
130
|
+
callbacks.delete(item);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
scheduleUnload(markAllUnused = true) {
|
|
137
|
+
if (this.scheduled) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.scheduled = true;
|
|
142
|
+
enqueueMicrotask(() => {
|
|
143
|
+
this.scheduled = false;
|
|
144
|
+
this.unloadToMinSize();
|
|
145
|
+
if (markAllUnused) {
|
|
146
|
+
this.markAllUnused();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const defaultPriorityCallback = (map, key) => {
|
|
154
|
+
return map.get(key);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const enqueueMicrotask = callback => {
|
|
158
|
+
Promise.resolve().then(callback);
|
|
159
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
|
|
2
|
+
import { Matrix4, Vector3 } from 't3d';
|
|
3
|
+
import { B3DMLoader } from '../loaders/B3DMLoader.js';
|
|
4
|
+
import { I3DMLoader } from '../loaders/I3DMLoader.js';
|
|
5
|
+
import { PNTSLoader } from '../loaders/PNTSLoader.js';
|
|
6
|
+
import { CMPTLoader } from '../loaders/CMPTLoader.js';
|
|
7
|
+
import { TileGLTFLoader } from '../loaders/TileGLTFLoader.js';
|
|
8
|
+
|
|
9
|
+
export class ModelLoader {
|
|
10
|
+
|
|
11
|
+
constructor(manager) {
|
|
12
|
+
const b3dmLoader = new B3DMLoader(manager);
|
|
13
|
+
const i3dmLoader = new I3DMLoader(manager);
|
|
14
|
+
const pntsLoader = new PNTSLoader(manager);
|
|
15
|
+
const cmptLoader = new CMPTLoader(manager);
|
|
16
|
+
const gltfLoader = new TileGLTFLoader(manager);
|
|
17
|
+
|
|
18
|
+
this._loaders = new Map([
|
|
19
|
+
['b3dm', b3dmLoader],
|
|
20
|
+
['i3dm', i3dmLoader],
|
|
21
|
+
['pnts', pntsLoader],
|
|
22
|
+
['cmpt', cmptLoader],
|
|
23
|
+
['gltf', gltfLoader],
|
|
24
|
+
['glb', gltfLoader]
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setDRACOLoader(dracoLoader) {
|
|
29
|
+
this._loaders.get('b3dm').setDRACOLoader(dracoLoader);
|
|
30
|
+
this._loaders.get('i3dm').setDRACOLoader(dracoLoader);
|
|
31
|
+
this._loaders.get('cmpt').setDRACOLoader(dracoLoader);
|
|
32
|
+
this._loaders.get('gltf').setDRACOLoader(dracoLoader);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setKTX2Loader(ktx2Loader) {
|
|
36
|
+
this._loaders.get('b3dm').setKTX2Loader(ktx2Loader);
|
|
37
|
+
this._loaders.get('i3dm').setKTX2Loader(ktx2Loader);
|
|
38
|
+
this._loaders.get('cmpt').setKTX2Loader(ktx2Loader);
|
|
39
|
+
this._loaders.get('gltf').setKTX2Loader(ktx2Loader);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
loadTileContent(buffer, tile, extension, tiles3D) {
|
|
43
|
+
tile._loadIndex = tile._loadIndex || 0;
|
|
44
|
+
tile._loadIndex++;
|
|
45
|
+
|
|
46
|
+
const uri = tile.content.uri;
|
|
47
|
+
const uriSplits = uri.split(/[\\\/]/g); // eslint-disable-line no-useless-escape
|
|
48
|
+
uriSplits.pop();
|
|
49
|
+
const workingPath = uriSplits.join('/');
|
|
50
|
+
const fetchOptions = tiles3D.fetchOptions;
|
|
51
|
+
|
|
52
|
+
const loadIndex = tile._loadIndex;
|
|
53
|
+
let promise = null;
|
|
54
|
+
|
|
55
|
+
const upAxis = tiles3D.rootTileSet.asset && tiles3D.rootTileSet.asset.gltfUpAxis || 'y';
|
|
56
|
+
const cached = tile.cached;
|
|
57
|
+
const cachedTransform = cached.transform;
|
|
58
|
+
|
|
59
|
+
switch (upAxis.toLowerCase()) {
|
|
60
|
+
case 'x':
|
|
61
|
+
mat4_1.makeRotationAxis(Y_AXIS, -Math.PI / 2);
|
|
62
|
+
break;
|
|
63
|
+
case 'y':
|
|
64
|
+
mat4_1.makeRotationAxis(X_AXIS, Math.PI / 2);
|
|
65
|
+
break;
|
|
66
|
+
case 'z':
|
|
67
|
+
mat4_1.identity();
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const loader = this._loaders.get(extension);
|
|
72
|
+
|
|
73
|
+
if (loader) {
|
|
74
|
+
const config = {
|
|
75
|
+
fetchOptions,
|
|
76
|
+
path: workingPath,
|
|
77
|
+
buffer
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (extension === 'b3dm' || extension === 'i3dm' || extension === 'cmpt') {
|
|
81
|
+
config.adjustmentTransform = mat4_1.clone();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
promise = loader.load(uri, config);
|
|
85
|
+
} else {
|
|
86
|
+
console.warn(`TilesRenderer: Content type "${extension}" not supported.`);
|
|
87
|
+
promise = Promise.resolve(null);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return promise.then(resource => {
|
|
91
|
+
const scene = resource.root;
|
|
92
|
+
|
|
93
|
+
if (tile._loadIndex !== loadIndex || !scene) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ensure the matrix is up to date in case the scene has a transform applied
|
|
98
|
+
scene.updateMatrix();
|
|
99
|
+
|
|
100
|
+
// apply the local up-axis correction rotation
|
|
101
|
+
// GLTFLoader seems to never set a transformation on the root scene object so
|
|
102
|
+
// any transformations applied to it can be assumed to be applied after load
|
|
103
|
+
// (such as applying RTC_CENTER) meaning they should happen _after_ the z-up
|
|
104
|
+
// rotation fix which is why "multiply" happens here.
|
|
105
|
+
if (extension === 'glb' || extension === 'gltf') {
|
|
106
|
+
scene.matrix.multiply(mat4_1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
scene.matrix.premultiply(cachedTransform);
|
|
110
|
+
scene.matrix.decompose(scene.position, scene.quaternion, scene.scale);
|
|
111
|
+
|
|
112
|
+
cached.scene = scene;
|
|
113
|
+
cached.featureTable = resource.featureTable;
|
|
114
|
+
cached.batchTable = resource.batchTable;
|
|
115
|
+
|
|
116
|
+
const materials = [];
|
|
117
|
+
const geometry = [];
|
|
118
|
+
const textures = [];
|
|
119
|
+
scene.traverse(c => {
|
|
120
|
+
if (c.geometry) {
|
|
121
|
+
geometry.push(c.geometry);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (c.material) {
|
|
125
|
+
const material = c.material;
|
|
126
|
+
materials.push(c.material);
|
|
127
|
+
|
|
128
|
+
for (const key in material) {
|
|
129
|
+
const value = material[key];
|
|
130
|
+
if (value && value.isTexture) {
|
|
131
|
+
textures.push(value);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
cached.materials = materials;
|
|
138
|
+
cached.geometry = geometry;
|
|
139
|
+
cached.textures = textures;
|
|
140
|
+
|
|
141
|
+
return scene;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const mat4_1 = new Matrix4();
|
|
148
|
+
|
|
149
|
+
const X_AXIS = new Vector3(1, 0, 0);
|
|
150
|
+
const Y_AXIS = new Vector3(0, 1, 0);
|