@jdultra/threedtiles 4.0.4 → 5.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/README.md +17 -7
- package/package.json +4 -4
- package/src/decoder/B3DMDecoder.js +16 -4
- package/src/decoder/FeatureTable.js +169 -0
- package/src/index.js +72 -118
- package/src/tileset/OGC3DTile.js +103 -45
- package/src/tileset/instanced/InstancedOGC3DTile.js +2 -0
- package/src/tileset/instanced/InstancedTile.js +81 -17
- package/src/tileset/instanced/MeshTile.js +7 -1
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ import { OGC3DTile } from "./tileset/OGC3DTile";
|
|
|
20
20
|
...
|
|
21
21
|
|
|
22
22
|
const ogc3DTile = new OGC3DTile({
|
|
23
|
-
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json"
|
|
23
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
24
|
+
renderer: renderer
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
scene.add(ogc3DTile);
|
|
@@ -56,6 +57,7 @@ you may also set this value at initialization:
|
|
|
56
57
|
```
|
|
57
58
|
const ogc3DTile = new OGC3DTile({
|
|
58
59
|
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
60
|
+
renderer: renderer,
|
|
59
61
|
geometricErrorMultiplier: 2.0
|
|
60
62
|
});
|
|
61
63
|
```
|
|
@@ -66,11 +68,12 @@ A value of 1.0 is the default.
|
|
|
66
68
|
|
|
67
69
|
### load tiles outside of view
|
|
68
70
|
By default, only the tiles that intersect the view frustum are loaded. When the camera moves, the scene will have to load the missing tiles and the user might see some holes in the model.
|
|
69
|
-
Instead of this behaviour, you can force the lowest possible LODs to be loaded for tiles outside the view so that there are no gaps in the mesh when the camera moves. This also allows displaying shadows
|
|
71
|
+
Instead of this behaviour, you can force the lowest possible LODs to be loaded for tiles outside the view so that there are no gaps in the mesh when the camera moves. This also allows displaying shadows from parts of the scene that are not in the view.
|
|
70
72
|
|
|
71
73
|
```
|
|
72
74
|
const ogc3DTile = new OGC3DTile({
|
|
73
75
|
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
76
|
+
renderer: renderer,
|
|
74
77
|
loadOutsideView: true
|
|
75
78
|
});
|
|
76
79
|
```
|
|
@@ -84,6 +87,7 @@ This can be useful to position the tileset at a specific location when it is not
|
|
|
84
87
|
```
|
|
85
88
|
const ogc3DTile = new OGC3DTile({
|
|
86
89
|
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
90
|
+
renderer: renderer,
|
|
87
91
|
onLoadCallback: tilese => {
|
|
88
92
|
console.log(tileset.boundingVolume);
|
|
89
93
|
}
|
|
@@ -96,6 +100,7 @@ Add a callback on loaded tiles in order to set a material or do some logic on th
|
|
|
96
100
|
```
|
|
97
101
|
const ogc3DTile = new OGC3DTile({
|
|
98
102
|
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
103
|
+
renderer: renderer,
|
|
99
104
|
meshCallback: mesh => {
|
|
100
105
|
mesh.material.wireframe = true;
|
|
101
106
|
mesh.material.side = THREE.DoubleSide;
|
|
@@ -116,6 +121,7 @@ import { TileLoader } from "@jdultra/threedtiles/src/tileset/TileLoader";
|
|
|
116
121
|
|
|
117
122
|
const ogc3DTile = new OGC3DTile({
|
|
118
123
|
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
124
|
+
renderer: renderer,
|
|
119
125
|
tileLoader: new TileLoader(mesh => {
|
|
120
126
|
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
121
127
|
mesh.material.wireframe = false;
|
|
@@ -133,6 +139,7 @@ The OGC 3DTile object is a regular three.js Object3D so it can be transformed vi
|
|
|
133
139
|
```
|
|
134
140
|
const ogc3DTile = new OGC3DTile({
|
|
135
141
|
url: "https://ebeaufay.github.io/ThreedTilesViewer.github.io/momoyama/tileset.json"
|
|
142
|
+
renderer: renderer,
|
|
136
143
|
});
|
|
137
144
|
|
|
138
145
|
ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -450);
|
|
@@ -156,6 +163,7 @@ This service must be passed to every OGC3DTiles object like so:
|
|
|
156
163
|
```
|
|
157
164
|
const ogc3DTile = new OGC3DTile({
|
|
158
165
|
url: "path/to/tileset.json",
|
|
166
|
+
renderer: renderer,
|
|
159
167
|
occlusionCullingService: occlusionCullingService
|
|
160
168
|
});
|
|
161
169
|
```
|
|
@@ -201,11 +209,12 @@ const instancedTileLoader = new InstancedTileLoader(scene, mesh => {
|
|
|
201
209
|
const instancedTilesets = [];
|
|
202
210
|
for (let i = 0; i < 100; i++) {
|
|
203
211
|
const tileset = new InstancedOGC3DTile({
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/droneship/tileset.json",
|
|
213
|
+
renderer: renderer,
|
|
214
|
+
geometricErrorMultiplier: 1.0,
|
|
215
|
+
loadOutsideView: false,
|
|
216
|
+
tileLoader: instancedTileLoader,
|
|
217
|
+
static: true // when static is set to true, don't forget to call InstancedOGC3DTile#updateMatrix manually
|
|
209
218
|
});
|
|
210
219
|
|
|
211
220
|
tileset.translateOnAxis(new THREE.Vector3(1, 0, 0), 50 * i);
|
|
@@ -238,6 +247,7 @@ This will skip recalculating the transformation matrix of every tile each frame
|
|
|
238
247
|
```
|
|
239
248
|
const ogc3DTile = new OGC3DTile({
|
|
240
249
|
url: "path/to/tileset.json",
|
|
250
|
+
renderer: renderer,
|
|
241
251
|
static: true
|
|
242
252
|
});
|
|
243
253
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jdultra/threedtiles",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "An OGC 3DTiles viewer for Three.js",
|
|
5
5
|
"main": "tileset.js",
|
|
6
6
|
"scripts": {
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
"path-browserify": "^1.0.1",
|
|
32
32
|
"regenerator-runtime": "^0.13.11",
|
|
33
33
|
"set-interval-async": "^2.0.3",
|
|
34
|
-
"three": "0.
|
|
34
|
+
"three": "0.148.0",
|
|
35
35
|
"uuid": "^8.3.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@babel/core": "^7.20.
|
|
38
|
+
"@babel/core": "^7.20.12",
|
|
39
39
|
"@babel/preset-env": "^7.20.2",
|
|
40
40
|
"babel-loader": "^8.3.0",
|
|
41
41
|
"copy-webpack-plugin": "^6.3.2",
|
|
42
|
-
"core-js": "^3.
|
|
42
|
+
"core-js": "^3.27.1",
|
|
43
43
|
"html-loader": "^1.3.2",
|
|
44
44
|
"html-webpack-plugin": "^4.5.0",
|
|
45
45
|
"mini-css-extract-plugin": "^1.6.2",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
2
2
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
|
+
import {FeatureTable, BatchTable} from './FeatureTable';
|
|
4
5
|
|
|
5
6
|
const gltfLoader = new GLTFLoader();
|
|
6
7
|
const dracoLoader = new DRACOLoader();
|
|
8
|
+
const tempMatrix = new THREE.Matrix4();
|
|
7
9
|
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.4.3/');
|
|
8
10
|
gltfLoader.setDRACOLoader(dracoLoader);
|
|
9
11
|
const dummy = new THREE.Object3D();
|
|
@@ -31,10 +33,10 @@ function parseB3DM(arrayBuffer, meshCallback) {
|
|
|
31
33
|
const batchTableBinaryByteLength = dataView.getUint32(24, true);
|
|
32
34
|
|
|
33
35
|
const featureTableStart = 28;
|
|
34
|
-
|
|
36
|
+
const featureTable = new FeatureTable(arrayBuffer, featureTableStart, featureTableJSONByteLength, featureTableBinaryByteLength);
|
|
35
37
|
|
|
36
38
|
const batchTableStart = featureTableStart + featureTableJSONByteLength + featureTableBinaryByteLength;
|
|
37
|
-
|
|
39
|
+
const batchTable = new BatchTable(arrayBuffer, featureTable.getData('BATCH_LENGTH'), batchTableStart, batchTableJSONByteLength, batchTableBinaryByteLength);
|
|
38
40
|
|
|
39
41
|
const glbStart = batchTableStart + batchTableJSONByteLength + batchTableBinaryByteLength;
|
|
40
42
|
const glbBytes = new Uint8Array(arrayBuffer, glbStart, byteLength - glbStart);
|
|
@@ -48,6 +50,7 @@ function parseB3DM(arrayBuffer, meshCallback) {
|
|
|
48
50
|
gltfLoader.parse(gltfBuffer, null, model => {
|
|
49
51
|
|
|
50
52
|
////TODO
|
|
53
|
+
|
|
51
54
|
//model.batchTable = b3dm.batchTable;
|
|
52
55
|
//model.featureTable = b3dm.featureTable;
|
|
53
56
|
|
|
@@ -55,6 +58,15 @@ function parseB3DM(arrayBuffer, meshCallback) {
|
|
|
55
58
|
//model.scene.featureTable = b3dm.featureTable;
|
|
56
59
|
|
|
57
60
|
//const scene = mergeColoredObject(model.scene);
|
|
61
|
+
|
|
62
|
+
//model.scene.applyMatrix4(ytozUpMatrix);
|
|
63
|
+
|
|
64
|
+
const rtcCenter = featureTable.getData('RTC_CENTER');
|
|
65
|
+
if (rtcCenter) {
|
|
66
|
+
tempMatrix.makeTranslation(rtcCenter[0], rtcCenter[2], -rtcCenter[1])
|
|
67
|
+
model.scene.applyMatrix4(tempMatrix);
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
model.scene.traverse((o) => {
|
|
59
71
|
if (o.isMesh) {
|
|
60
72
|
if (!!meshCallback) {
|
|
@@ -76,11 +88,11 @@ const B3DMDecoder = {
|
|
|
76
88
|
|
|
77
89
|
return parseB3DM(arrayBuffer, meshCallback).then(mesh => {
|
|
78
90
|
let instancedMesh;
|
|
91
|
+
mesh.updateWorldMatrix(false, true)
|
|
79
92
|
mesh.traverse(child => {
|
|
80
93
|
if (child.isMesh) {
|
|
81
94
|
instancedMesh = new THREE.InstancedMesh(child.geometry, child.material, maxCount);
|
|
82
|
-
instancedMesh.baseMatrix = child.
|
|
83
|
-
//console.log(child.matrix.elements[12])
|
|
95
|
+
instancedMesh.baseMatrix = child.matrixWorld;
|
|
84
96
|
}
|
|
85
97
|
});
|
|
86
98
|
return instancedMesh;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This class is taken straight from NASA-AMMOS library.
|
|
3
|
+
* https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/src/utilities/FeatureTable.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const utf8decoder = new TextDecoder();
|
|
7
|
+
export class FeatureTable {
|
|
8
|
+
|
|
9
|
+
constructor(buffer, start, headerLength, binLength) {
|
|
10
|
+
|
|
11
|
+
this.buffer = buffer;
|
|
12
|
+
this.binOffset = start + headerLength;
|
|
13
|
+
this.binLength = binLength;
|
|
14
|
+
|
|
15
|
+
let header = null;
|
|
16
|
+
if (headerLength !== 0) {
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const headerData = new Uint8Array(buffer, start, headerLength);
|
|
20
|
+
header = JSON.parse(utf8decoder.decode(headerData));
|
|
21
|
+
} catch (e) {
|
|
22
|
+
header = {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
} else {
|
|
26
|
+
|
|
27
|
+
header = {};
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
this.header = header;
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getKeys() {
|
|
35
|
+
|
|
36
|
+
return Object.keys(this.header);
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getData(key, count, defaultComponentType = null, defaultType = null) {
|
|
41
|
+
|
|
42
|
+
const header = this.header;
|
|
43
|
+
|
|
44
|
+
if (!(key in header)) {
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const feature = header[key];
|
|
51
|
+
if (!(feature instanceof Object)) {
|
|
52
|
+
|
|
53
|
+
return feature;
|
|
54
|
+
|
|
55
|
+
} else if (Array.isArray(feature)) {
|
|
56
|
+
|
|
57
|
+
return feature;
|
|
58
|
+
|
|
59
|
+
} else {
|
|
60
|
+
|
|
61
|
+
const { buffer, binOffset, binLength } = this;
|
|
62
|
+
const byteOffset = feature.byteOffset || 0;
|
|
63
|
+
const featureType = feature.type || defaultType;
|
|
64
|
+
const featureComponentType = feature.componentType || defaultComponentType;
|
|
65
|
+
|
|
66
|
+
if ('type' in feature && defaultType && feature.type !== defaultType) {
|
|
67
|
+
|
|
68
|
+
throw new Error('FeatureTable: Specified type does not match expected type.');
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let stride;
|
|
73
|
+
switch (featureType) {
|
|
74
|
+
|
|
75
|
+
case 'SCALAR':
|
|
76
|
+
stride = 1;
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case 'VEC2':
|
|
80
|
+
stride = 2;
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case 'VEC3':
|
|
84
|
+
stride = 3;
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case 'VEC4':
|
|
88
|
+
stride = 4;
|
|
89
|
+
break;
|
|
90
|
+
|
|
91
|
+
default:
|
|
92
|
+
throw new Error(`FeatureTable : Feature type not provided for "${key}".`);
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let data;
|
|
97
|
+
const arrayStart = binOffset + byteOffset;
|
|
98
|
+
const arrayLength = count * stride;
|
|
99
|
+
|
|
100
|
+
switch (featureComponentType) {
|
|
101
|
+
|
|
102
|
+
case 'BYTE':
|
|
103
|
+
data = new Int8Array(buffer, arrayStart, arrayLength);
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case 'UNSIGNED_BYTE':
|
|
107
|
+
data = new Uint8Array(buffer, arrayStart, arrayLength);
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case 'SHORT':
|
|
111
|
+
data = new Int16Array(buffer, arrayStart, arrayLength);
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'UNSIGNED_SHORT':
|
|
115
|
+
data = new Uint16Array(buffer, arrayStart, arrayLength);
|
|
116
|
+
break;
|
|
117
|
+
|
|
118
|
+
case 'INT':
|
|
119
|
+
data = new Int32Array(buffer, arrayStart, arrayLength);
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case 'UNSIGNED_INT':
|
|
123
|
+
data = new Uint32Array(buffer, arrayStart, arrayLength);
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case 'FLOAT':
|
|
127
|
+
data = new Float32Array(buffer, arrayStart, arrayLength);
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'DOUBLE':
|
|
131
|
+
data = new Float64Array(buffer, arrayStart, arrayLength);
|
|
132
|
+
break;
|
|
133
|
+
|
|
134
|
+
default:
|
|
135
|
+
throw new Error(`FeatureTable : Feature component type not provided for "${key}".`);
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const dataEnd = arrayStart + arrayLength * data.BYTES_PER_ELEMENT;
|
|
140
|
+
if (dataEnd > binOffset + binLength) {
|
|
141
|
+
|
|
142
|
+
throw new Error('FeatureTable: Feature data read outside binary body length.');
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return data;
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class BatchTable extends FeatureTable {
|
|
155
|
+
|
|
156
|
+
constructor(buffer, batchSize, start, headerLength, binLength) {
|
|
157
|
+
|
|
158
|
+
super(buffer, start, headerLength, binLength);
|
|
159
|
+
this.batchSize = batchSize;
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getData(key, componentType = null, type = null) {
|
|
164
|
+
|
|
165
|
+
return super.getData(key, this.batchSize, componentType, type);
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
}
|
package/src/index.js
CHANGED
|
@@ -14,43 +14,23 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
|
|
14
14
|
import { InstancedOGC3DTile } from "./tileset/instanced/InstancedOGC3DTile.js"
|
|
15
15
|
import { InstancedTileLoader } from "./tileset/instanced/InstancedTileLoader.js"
|
|
16
16
|
|
|
17
|
-
import { B3DMDecoder } from "./decoder/B3DMDecoder";
|
|
18
17
|
|
|
19
18
|
const occlusionCullingService = new OcclusionCullingService();
|
|
20
19
|
occlusionCullingService.setSide(THREE.DoubleSide);
|
|
21
20
|
const scene = initScene();
|
|
22
21
|
|
|
23
22
|
const domContainer = initDomContainer("screen");
|
|
24
|
-
const camera = initCamera();
|
|
25
|
-
//const ogc3DTiles = initTileset(scene);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const instancedTileLoader = createInstancedTileLoader(scene);
|
|
29
|
-
initInstancedTilesets(instancedTileLoader);
|
|
30
|
-
|
|
31
|
-
const controller = initController(camera, domContainer);
|
|
32
|
-
|
|
23
|
+
const camera = initCamera(domContainer.offsetWidth, domContainer.offsetHeight);
|
|
33
24
|
const stats = initStats(domContainer);
|
|
34
25
|
const renderer = initRenderer(camera, domContainer);
|
|
35
|
-
const
|
|
26
|
+
const ogc3DTiles = initTileset(scene, 1.0);
|
|
36
27
|
|
|
28
|
+
//const instancedTileLoader = createInstancedTileLoader(scene);
|
|
29
|
+
//initInstancedTilesets(instancedTileLoader);
|
|
37
30
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!result.ok) {
|
|
41
|
-
console.error("could not load tile with path : " + path)
|
|
42
|
-
throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
43
|
-
}
|
|
44
|
-
return result.arrayBuffer();
|
|
31
|
+
const controller = initController(camera, domContainer);
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
.then(resultArrayBuffer=>{
|
|
48
|
-
return B3DMDecoder.parseB3DMInstanced(resultArrayBuffer, self.meshCallback, 1);
|
|
49
|
-
})
|
|
50
|
-
.then(mesh=>{
|
|
51
|
-
scene.add(mesh)
|
|
52
|
-
|
|
53
|
-
}) */
|
|
33
|
+
const composer = initComposer(scene, camera, renderer);
|
|
54
34
|
|
|
55
35
|
|
|
56
36
|
animate();
|
|
@@ -69,11 +49,11 @@ function initComposer(scene, camera, renderer) {
|
|
|
69
49
|
function initScene() {
|
|
70
50
|
const scene = new THREE.Scene();
|
|
71
51
|
scene.matrixAutoUpdate = false;
|
|
72
|
-
scene.matrixWorldAutoUpdate = false;
|
|
73
|
-
scene.background = new THREE.Color(
|
|
52
|
+
//scene.matrixWorldAutoUpdate = false;
|
|
53
|
+
scene.background = new THREE.Color(0xffffff);
|
|
74
54
|
scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
|
|
75
55
|
|
|
76
|
-
/*const light = new THREE.PointLight(0xbbbbff, 2, 5000);
|
|
56
|
+
/* const light = new THREE.PointLight(0xbbbbff, 2, 5000);
|
|
77
57
|
const sphere = new THREE.SphereGeometry(2, 16, 8);
|
|
78
58
|
light.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0xbbbbff })));
|
|
79
59
|
scene.add(light);
|
|
@@ -84,9 +64,9 @@ function initScene() {
|
|
|
84
64
|
const sphere2 = new THREE.SphereGeometry(2, 16, 8);
|
|
85
65
|
light2.add(new THREE.Mesh(sphere2, new THREE.MeshBasicMaterial({ color: 0xffbbbb })));
|
|
86
66
|
scene.add(light2);
|
|
87
|
-
light2.position.set(200, 100, -100)
|
|
67
|
+
light2.position.set(200, 100, -100); */
|
|
68
|
+
|
|
88
69
|
|
|
89
|
-
|
|
90
70
|
return scene;
|
|
91
71
|
}
|
|
92
72
|
|
|
@@ -132,114 +112,85 @@ function initStats(dom) {
|
|
|
132
112
|
}
|
|
133
113
|
|
|
134
114
|
|
|
135
|
-
function initCamera() {
|
|
136
|
-
const camera = new THREE.PerspectiveCamera(
|
|
137
|
-
camera.position.set(
|
|
115
|
+
function initCamera(width, height) {
|
|
116
|
+
const camera = new THREE.PerspectiveCamera(60, width / height, 1, 100000);
|
|
117
|
+
camera.position.set(10000,0,0);
|
|
118
|
+
camera.lookAt(0,0,0);
|
|
119
|
+
|
|
138
120
|
camera.matrixAutoUpdate = true;
|
|
139
121
|
return camera;
|
|
140
122
|
}
|
|
141
123
|
|
|
142
|
-
function initTileset(scene) {
|
|
124
|
+
function initTileset(scene, gem) {
|
|
143
125
|
|
|
144
126
|
const tileLoader = new TileLoader(mesh => {
|
|
145
127
|
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
146
128
|
mesh.material.wireframe = false;
|
|
147
129
|
mesh.material.side = THREE.DoubleSide;
|
|
148
|
-
|
|
130
|
+
mesh.material.metalness = 0.0
|
|
131
|
+
}, 100)
|
|
149
132
|
const ogc3DTile = new OGC3DTile({
|
|
150
|
-
//url: "
|
|
151
|
-
|
|
152
|
-
url: "https://storage.googleapis.com/ogc-3d-tiles/
|
|
153
|
-
//url: "
|
|
154
|
-
geometricErrorMultiplier:
|
|
133
|
+
//url: "https://sampledata.luciad.com/data/ogc3dtiles/LucerneAirborneMesh/tileset.json",
|
|
134
|
+
url: "https://sampleservices.luciad.com/ogc/3dtiles/marseille-mesh/tileset.json",
|
|
135
|
+
//url: "https://storage.googleapis.com/ogc-3d-tiles/baltimore/tileset.json",
|
|
136
|
+
//url: "http://localhost:8082/tileset.json",
|
|
137
|
+
geometricErrorMultiplier: gem,
|
|
155
138
|
loadOutsideView: false,
|
|
156
139
|
tileLoader: tileLoader,
|
|
157
140
|
//occlusionCullingService: occlusionCullingService,
|
|
158
141
|
static: false,
|
|
142
|
+
centerModel:true,
|
|
143
|
+
renderer: renderer
|
|
159
144
|
|
|
160
145
|
});
|
|
146
|
+
setIntervalAsync(function () {
|
|
147
|
+
ogc3DTile.update(camera);
|
|
148
|
+
}, 20);
|
|
149
|
+
//ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
|
|
150
|
+
//ogc3DTile.translateOnAxis(new THREE.Vector3(0, 0, 1), 1)
|
|
151
|
+
/*
|
|
152
|
+
ogc3DTile.translateOnAxis(new THREE.Vector3(0, 0, 1), 10) // Z-UP to Y-UP
|
|
153
|
+
ogc3DTile.translateOnAxis(new THREE.Vector3(0, 1, 0), 18.5) // Z-UP to Y-UP */
|
|
154
|
+
scene.add(ogc3DTile);
|
|
161
155
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
//// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
|
|
166
|
-
ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
|
|
167
|
-
//ogc3DTile.scale.set(10.0,10.0,10.0)
|
|
168
|
-
//// If the OGC3DTile object is marked as "static" (constructorParameter), these operations will not work.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
//// It's up to the user to call updates on the tileset. You might call them whenever the camera moves or at regular time intervals like here
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
var interval;
|
|
177
|
-
document.addEventListener('keyup', (e) => {
|
|
178
|
-
console.log(camera.position)
|
|
179
|
-
if (!!e.key && e.key !== "p") {
|
|
180
|
-
|
|
181
|
-
if (!!interval) {
|
|
182
|
-
clearInterval(interval);
|
|
183
|
-
interval = null;
|
|
184
|
-
} else {
|
|
185
|
-
startInterval();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (!!e.key && e.key !== "l") {
|
|
189
|
-
|
|
190
|
-
console.log("new THREE.Vector3(" + camera.position.x + "," + camera.position.y + "," + camera.position.z + ")");
|
|
191
|
-
console.log("new THREE.Quaternion(" + camera.quaternion.x + "," + camera.quaternion.y + "," + camera.quaternion.z + "," + camera.quaternion.w + ")");
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
});
|
|
196
|
-
function startInterval() {
|
|
197
|
-
interval = setIntervalAsync(function () {
|
|
198
|
-
ogc3DTile.update(camera);
|
|
199
|
-
|
|
200
|
-
}, 20);
|
|
201
|
-
}
|
|
202
|
-
startInterval();
|
|
203
|
-
|
|
204
|
-
scene.add(ogc3DTile)
|
|
205
156
|
return ogc3DTile;
|
|
206
157
|
}
|
|
207
158
|
|
|
159
|
+
|
|
208
160
|
function createInstancedTileLoader(scene) {
|
|
209
161
|
return new InstancedTileLoader(scene, mesh => {
|
|
210
162
|
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
211
163
|
mesh.material.wireframe = false;
|
|
212
164
|
mesh.material.side = THREE.DoubleSide;
|
|
213
|
-
|
|
165
|
+
mesh.material.metalness = 0.0;
|
|
166
|
+
}, 0, 1);
|
|
214
167
|
}
|
|
215
168
|
function initInstancedTilesets(instancedTileLoader) {
|
|
216
169
|
|
|
170
|
+
/*new GLTFLoader().load('http://localhost:8080/test.glb', function ( gltf ) {
|
|
171
|
+
scene.add(gltf.scene);
|
|
172
|
+
} );*/
|
|
173
|
+
|
|
217
174
|
const instancedTilesets = [];
|
|
218
175
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
instancedTilesets.push(tileset);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
176
|
+
|
|
177
|
+
const tileset = new InstancedOGC3DTile({
|
|
178
|
+
//url: "https://storage.googleapis.com/ogc-3d-tiles/berlinTileset/tileset.json",
|
|
179
|
+
//url: "https://sampleservices.luciad.com/ogc/3dtiles/marseille-mesh/tileset.json",
|
|
180
|
+
//url: "https://s3.eu-central-2.wasabisys.com/construkted-assets-eu/ab13lasdc9i/tileset.json",
|
|
181
|
+
url: "http://localhost:8081/tileset.json",
|
|
182
|
+
geometricErrorMultiplier: 1.0,
|
|
183
|
+
loadOutsideView: true,
|
|
184
|
+
tileLoader: instancedTileLoader,
|
|
185
|
+
static: false,
|
|
186
|
+
centerModel:true,
|
|
187
|
+
renderer: renderer
|
|
188
|
+
});
|
|
189
|
+
//tileset.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
|
|
190
|
+
|
|
191
|
+
tileset.updateMatrix()
|
|
192
|
+
scene.add(tileset);
|
|
193
|
+
instancedTilesets.push(tileset);
|
|
243
194
|
|
|
244
195
|
scene.updateMatrixWorld(true)
|
|
245
196
|
function now() {
|
|
@@ -248,14 +199,14 @@ function initInstancedTilesets(instancedTileLoader) {
|
|
|
248
199
|
let updateIndex = 0;
|
|
249
200
|
setInterval(() => {
|
|
250
201
|
let startTime = now();
|
|
251
|
-
do{
|
|
202
|
+
do {
|
|
252
203
|
const frustum = new THREE.Frustum();
|
|
253
204
|
frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));
|
|
254
205
|
instancedTilesets[updateIndex].update(camera, frustum);
|
|
255
|
-
updateIndex= (updateIndex+1)%instancedTilesets.length;
|
|
256
|
-
}while(updateIndex < instancedTilesets.length && now()-startTime<4);
|
|
257
|
-
},40);
|
|
258
|
-
|
|
206
|
+
updateIndex = (updateIndex + 1) % instancedTilesets.length;
|
|
207
|
+
} while (updateIndex < instancedTilesets.length && now() - startTime < 4);
|
|
208
|
+
}, 40);
|
|
209
|
+
|
|
259
210
|
//initLODMultiplierSlider(instancedTilesets);
|
|
260
211
|
}
|
|
261
212
|
|
|
@@ -266,7 +217,7 @@ function initLODMultiplierSlider(instancedTilesets) {
|
|
|
266
217
|
|
|
267
218
|
slider.oninput = () => {
|
|
268
219
|
instancedTilesets.forEach(tileset => {
|
|
269
|
-
tileset.setGeometricErrorMultiplier(slider.value*0.1)
|
|
220
|
+
tileset.setGeometricErrorMultiplier(slider.value * 0.1)
|
|
270
221
|
})
|
|
271
222
|
output.innerHTML = slider.value;
|
|
272
223
|
}
|
|
@@ -275,8 +226,11 @@ function initLODMultiplierSlider(instancedTilesets) {
|
|
|
275
226
|
function initController(camera, dom) {
|
|
276
227
|
const controller = new OrbitControls(camera, dom);
|
|
277
228
|
|
|
278
|
-
controller.target.set(0,
|
|
279
|
-
|
|
229
|
+
controller.target.set(0,0,0);
|
|
230
|
+
//controller.target.set(0,0,0);
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
controller.minDistance = 0.1;
|
|
280
234
|
controller.maxDistance = 100000;
|
|
281
235
|
controller.update();
|
|
282
236
|
return controller;
|
|
@@ -285,7 +239,7 @@ function initController(camera, dom) {
|
|
|
285
239
|
|
|
286
240
|
function animate() {
|
|
287
241
|
requestAnimationFrame(animate);
|
|
288
|
-
instancedTileLoader.update();
|
|
242
|
+
//instancedTileLoader.update();
|
|
289
243
|
composer.render();
|
|
290
244
|
//occlusionCullingService.update(scene, renderer, camera)
|
|
291
245
|
stats.update();
|
package/src/tileset/OGC3DTile.js
CHANGED
|
@@ -5,8 +5,12 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
5
5
|
import * as path from "path-browserify"
|
|
6
6
|
import { clamp } from "three/src/math/MathUtils";
|
|
7
7
|
|
|
8
|
-
const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1)
|
|
9
|
-
|
|
8
|
+
const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 1);
|
|
9
|
+
const tempVec1 = new THREE.Vector3(0, 0, 0);
|
|
10
|
+
const tempVec2 = new THREE.Vector3(0, 0, 0);
|
|
11
|
+
const upVector = new THREE.Vector3(0, 1, 0);
|
|
12
|
+
const rendererSize = new THREE.Vector2();
|
|
13
|
+
const tempQuaternion = new THREE.Quaternion();
|
|
10
14
|
|
|
11
15
|
class OGC3DTile extends THREE.Object3D {
|
|
12
16
|
|
|
@@ -27,7 +31,9 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
27
31
|
* parentTile: OGC3DTile,
|
|
28
32
|
* onLoadCallback: function,
|
|
29
33
|
* occlusionCullingService: OcclusionCullingService,
|
|
30
|
-
* static: Boolean
|
|
34
|
+
* static: Boolean,
|
|
35
|
+
* centerModel: Boolean
|
|
36
|
+
* renderer: Renderer
|
|
31
37
|
* } properties
|
|
32
38
|
*/
|
|
33
39
|
constructor(properties) {
|
|
@@ -43,10 +49,11 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
43
49
|
mesh.material.wireframe = false;
|
|
44
50
|
mesh.material.side = THREE.DoubleSide;
|
|
45
51
|
} : properties.meshCallback);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
}
|
|
53
|
+
// set properties general to the entire tileset
|
|
54
|
+
this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
|
|
55
|
+
|
|
56
|
+
this.renderer = properties.renderer;
|
|
50
57
|
this.meshCallback = properties.meshCallback;
|
|
51
58
|
this.loadOutsideView = properties.loadOutsideView;
|
|
52
59
|
this.cameraOnLoad = properties.cameraOnLoad;
|
|
@@ -58,9 +65,10 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
58
65
|
this.color.setHex(Math.random() * 0xffffff);
|
|
59
66
|
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;
|
|
60
67
|
}
|
|
61
|
-
if(this.static){
|
|
68
|
+
if (this.static) {
|
|
62
69
|
this.matrixAutoUpdate = false;
|
|
63
70
|
}
|
|
71
|
+
|
|
64
72
|
// declare properties specific to the tile for clarity
|
|
65
73
|
this.childrenTiles = [];
|
|
66
74
|
this.meshContent;
|
|
@@ -82,6 +90,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
82
90
|
if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
|
|
83
91
|
self.setup(properties);
|
|
84
92
|
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
93
|
+
|
|
85
94
|
} else if (properties.url) { // If only the url to the tileset.json is provided
|
|
86
95
|
fetch(properties.url, { signal: self.abortController.signal }).then(result => {
|
|
87
96
|
if (!result.ok) {
|
|
@@ -90,6 +99,34 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
90
99
|
result.json().then(json => {
|
|
91
100
|
self.setup({ rootPath: path.dirname(properties.url), json: json });
|
|
92
101
|
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
102
|
+
if (!!properties.centerModel) {
|
|
103
|
+
const tempSphere = new THREE.Sphere();
|
|
104
|
+
if (self.boundingVolume instanceof OBB) {
|
|
105
|
+
// box
|
|
106
|
+
tempSphere.copy(self.boundingVolume.sphere);
|
|
107
|
+
} else if (self.boundingVolume instanceof THREE.Sphere) {
|
|
108
|
+
//sphere
|
|
109
|
+
tempSphere.copy(self.boundingVolume);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//tempSphere.applyMatrix4(self.matrixWorld);
|
|
113
|
+
if (!!this.json.boundingVolume.region) {
|
|
114
|
+
this.transformWGS84ToCartesian(
|
|
115
|
+
(this.json.boundingVolume.region[0] + this.json.boundingVolume.region[2]) * 0.5,
|
|
116
|
+
(this.json.boundingVolume.region[1] + this.json.boundingVolume.region[3]) * 0.5,
|
|
117
|
+
(this.json.boundingVolume.region[4] + this.json.boundingVolume.region[5]) * 0.5,
|
|
118
|
+
tempVec1);
|
|
119
|
+
tempVec2.set(tempVec1.x, tempVec1.z, -tempVec1.y);
|
|
120
|
+
|
|
121
|
+
tempQuaternion.setFromUnitVectors(tempVec2.normalize(), upVector.normalize());
|
|
122
|
+
self.applyQuaternion(tempQuaternion);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
self.translateX(-tempSphere.center.x * self.scale.x);
|
|
126
|
+
self.translateY(-tempSphere.center.y * self.scale.y);
|
|
127
|
+
self.translateZ(-tempSphere.center.z * self.scale.z);
|
|
128
|
+
|
|
129
|
+
}
|
|
93
130
|
});
|
|
94
131
|
});
|
|
95
132
|
}
|
|
@@ -131,7 +168,10 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
131
168
|
this.boundingVolume = new OBB(this.json.boundingVolume.box);
|
|
132
169
|
} else if (!!this.json.boundingVolume.region) {
|
|
133
170
|
const region = this.json.boundingVolume.region;
|
|
134
|
-
this.
|
|
171
|
+
this.transformWGS84ToCartesian(region[0], region[1], region[4], tempVec1);
|
|
172
|
+
this.transformWGS84ToCartesian(region[2], region[3], region[5], tempVec2);
|
|
173
|
+
tempVec1.lerp(tempVec2, 0.5);
|
|
174
|
+
this.boundingVolume = new THREE.Sphere(new THREE.Vector3(tempVec1.x, tempVec1.z, -tempVec1.y), tempVec1.distanceTo(tempVec2));
|
|
135
175
|
} else if (!!this.json.boundingVolume.sphere) {
|
|
136
176
|
const sphere = this.json.boundingVolume.sphere;
|
|
137
177
|
this.boundingVolume = new THREE.Sphere(new THREE.Vector3(sphere[0], sphere[2], -sphere[1]), sphere[3]);
|
|
@@ -176,7 +216,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
176
216
|
if (!!url) {
|
|
177
217
|
if (url.includes(".b3dm")) {
|
|
178
218
|
self.contentURL = url;
|
|
179
|
-
self.tileLoader.get(self.abortController,this.uuid, url, mesh => {
|
|
219
|
+
self.tileLoader.get(self.abortController, this.uuid, url, mesh => {
|
|
180
220
|
if (!!self.deleted) return;
|
|
181
221
|
mesh.traverse((o) => {
|
|
182
222
|
if (o.isMesh) {
|
|
@@ -189,13 +229,13 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
189
229
|
}
|
|
190
230
|
o.geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
191
231
|
}
|
|
192
|
-
if(self.static){
|
|
232
|
+
if (self.static) {
|
|
193
233
|
o.matrixAutoUpdate = false;
|
|
194
234
|
}
|
|
195
235
|
//o.material.visible = false;
|
|
196
236
|
}
|
|
197
237
|
});
|
|
198
|
-
|
|
238
|
+
|
|
199
239
|
self.add(mesh);
|
|
200
240
|
self.updateWorldMatrix(false, true);
|
|
201
241
|
// mesh.layers.disable(0);
|
|
@@ -204,7 +244,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
204
244
|
return self.calculateDistanceToCamera(self.cameraOnLoad);
|
|
205
245
|
}, () => self.getSiblings(), self.level);
|
|
206
246
|
} else if (url.includes(".json")) {
|
|
207
|
-
self.tileLoader.get(self.abortController,this.uuid, url, json => {
|
|
247
|
+
self.tileLoader.get(self.abortController, this.uuid, url, json => {
|
|
208
248
|
if (!!self.deleted) return;
|
|
209
249
|
if (!self.json.children) self.json.children = [];
|
|
210
250
|
json.rootPath = path.dirname(url);
|
|
@@ -273,7 +313,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
273
313
|
if (self.occlusionCullingService && self.hasMeshContent && !self.occlusionCullingService.hasID(self.colorID)) {
|
|
274
314
|
return;
|
|
275
315
|
}
|
|
276
|
-
if (!self.hasMeshContent || (metric < self.geometricError && !!self.meshContent)) {
|
|
316
|
+
if (!self.hasMeshContent || (metric < self.geometricErrorMultiplier * self.geometricError && !!self.meshContent)) {
|
|
277
317
|
if (!!self.json && !!self.json.children && self.childrenTiles.length != self.json.children.length) {
|
|
278
318
|
loadJsonChildren();
|
|
279
319
|
return;
|
|
@@ -307,10 +347,10 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
307
347
|
}
|
|
308
348
|
|
|
309
349
|
// has children
|
|
310
|
-
if (metric >= self.geometricError) { // Ideal LOD or before ideal lod
|
|
350
|
+
if (metric >= self.geometricErrorMultiplier * self.geometricError) { // Ideal LOD or before ideal lod
|
|
311
351
|
|
|
312
352
|
self.changeContentVisibility(true);
|
|
313
|
-
} else if (metric < self.geometricError) { // Ideal LOD is past this one
|
|
353
|
+
} else if (metric < self.geometricErrorMultiplier * self.geometricError) { // Ideal LOD is past this one
|
|
314
354
|
// if children are visible and have been displayed, can be hidden
|
|
315
355
|
let allChildrenReady = true;
|
|
316
356
|
self.childrenTiles.every(child => {
|
|
@@ -345,7 +385,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
345
385
|
updateNodeVisibility(metric);
|
|
346
386
|
return;
|
|
347
387
|
}
|
|
348
|
-
if (metric >= self.geometricError) {
|
|
388
|
+
if (metric >= self.geometricErrorMultiplier * self.geometricError) {
|
|
349
389
|
self.disposeChildren();
|
|
350
390
|
updateNodeVisibility();
|
|
351
391
|
return;
|
|
@@ -367,7 +407,8 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
367
407
|
level: self.level + 1,
|
|
368
408
|
tileLoader: self.tileLoader,
|
|
369
409
|
cameraOnLoad: camera,
|
|
370
|
-
occlusionCullingService:self.occlusionCullingService,
|
|
410
|
+
occlusionCullingService: self.occlusionCullingService,
|
|
411
|
+
renderer: self.renderer,
|
|
371
412
|
static: self.static
|
|
372
413
|
});
|
|
373
414
|
self.childrenTiles.push(childTile);
|
|
@@ -382,11 +423,11 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
382
423
|
const self = this;
|
|
383
424
|
this.childrenTiles.every(child => {
|
|
384
425
|
if (child.hasMeshContent) {
|
|
385
|
-
if(child.childrenTiles.length>0){
|
|
426
|
+
if (child.childrenTiles.length > 0) {
|
|
386
427
|
allLoadedAndHidden = false;
|
|
387
428
|
return false;
|
|
388
429
|
}
|
|
389
|
-
if (!child.inFrustum
|
|
430
|
+
if (!child.inFrustum) {
|
|
390
431
|
return true;
|
|
391
432
|
};
|
|
392
433
|
if (!child.materialVisibility || child.meshesToDisplay != child.meshesDisplayed) {
|
|
@@ -459,15 +500,15 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
459
500
|
|
|
460
501
|
changeContentVisibility(visibility) {
|
|
461
502
|
const self = this;
|
|
462
|
-
if(self.hasMeshContent && self.meshContent){
|
|
463
|
-
if(visibility){
|
|
464
|
-
|
|
503
|
+
if (self.hasMeshContent && self.meshContent) {
|
|
504
|
+
if (visibility) {
|
|
505
|
+
|
|
465
506
|
self.meshContent.traverse((o) => {
|
|
466
507
|
if (o.isMesh) {
|
|
467
508
|
o.layers.enable(0);
|
|
468
509
|
}
|
|
469
510
|
});
|
|
470
|
-
}else{
|
|
511
|
+
} else {
|
|
471
512
|
self.meshContent.traverse((o) => {
|
|
472
513
|
if (o.isMesh) {
|
|
473
514
|
o.layers.disable(0);
|
|
@@ -515,29 +556,30 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
515
556
|
tempSphere.copy(this.boundingVolume);
|
|
516
557
|
tempSphere.applyMatrix4(this.matrixWorld);
|
|
517
558
|
if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
518
|
-
} else
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
return -1;
|
|
559
|
+
} else {
|
|
560
|
+
console.error("unsupported shape");
|
|
561
|
+
return -1
|
|
562
|
+
|
|
523
563
|
}
|
|
524
564
|
|
|
525
565
|
/////// return metric based on geometric error and distance
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
return 0;
|
|
531
|
-
}
|
|
532
|
-
const scale = this.matrixWorld.getMaxScaleOnAxis();
|
|
533
|
-
return Math.pow(distance, 2) /(this.geometricErrorMultiplier*this.geometricError*Math.pow(scale,2.0)*35);
|
|
534
|
-
//return (((distance / Math.pow(scale, 2)) / 100) / this.geometricErrorMultiplier);
|
|
535
|
-
} else if (this.boundingVolume instanceof THREE.Box3) {
|
|
536
|
-
// Region
|
|
537
|
-
// Region not supported
|
|
538
|
-
//throw Error("Region bounding volume not supported");
|
|
539
|
-
return -1;
|
|
566
|
+
|
|
567
|
+
const distance = Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
|
|
568
|
+
if (distance == 0) {
|
|
569
|
+
return 0;
|
|
540
570
|
}
|
|
571
|
+
const scale = this.matrixWorld.getMaxScaleOnAxis();
|
|
572
|
+
this.renderer.getDrawingBufferSize(rendererSize);
|
|
573
|
+
let s = rendererSize.y;
|
|
574
|
+
let fov = camera.fov;
|
|
575
|
+
if (camera.aspect < 1) {
|
|
576
|
+
fov *= camera.aspect;
|
|
577
|
+
s = rendererSize.x;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
let lambda = 2.0 * Math.tan(0.5 * fov * 0.01745329251994329576923690768489) * distance;
|
|
581
|
+
|
|
582
|
+
return (window.devicePixelRatio * 16 * lambda) / (s * scale);
|
|
541
583
|
}
|
|
542
584
|
|
|
543
585
|
getSiblings() {
|
|
@@ -570,8 +612,8 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
570
612
|
tempSphere.applyMatrix4(this.matrixWorld);
|
|
571
613
|
//if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
572
614
|
}
|
|
573
|
-
|
|
574
|
-
|
|
615
|
+
else {
|
|
616
|
+
console.error("unsupported shape")
|
|
575
617
|
}
|
|
576
618
|
return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
|
|
577
619
|
}
|
|
@@ -579,5 +621,21 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
579
621
|
this.geometricErrorMultiplier = geometricErrorMultiplier;
|
|
580
622
|
this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
|
|
581
623
|
}
|
|
624
|
+
|
|
625
|
+
transformWGS84ToCartesian(lon, lat, h, sfct) {
|
|
626
|
+
const a = 6378137.0;
|
|
627
|
+
const e = 0.006694384442042;
|
|
628
|
+
const N = a / (Math.sqrt(1.0 - (e * Math.pow(Math.sin(lat), 2))));
|
|
629
|
+
const cosLat = Math.cos(lat);
|
|
630
|
+
const cosLon = Math.cos(lon);
|
|
631
|
+
const sinLat = Math.sin(lat);
|
|
632
|
+
const sinLon = Math.sin(lon);
|
|
633
|
+
const nPh = (N + h);
|
|
634
|
+
const x = nPh * cosLat * cosLon;
|
|
635
|
+
const y = nPh * cosLat * sinLon;
|
|
636
|
+
const z = (0.993305615557957 * N + h) * sinLat;
|
|
637
|
+
|
|
638
|
+
sfct.set(x, y, z);
|
|
639
|
+
}
|
|
582
640
|
}
|
|
583
641
|
export { OGC3DTile };
|
|
@@ -19,12 +19,14 @@ class InstancedOGC3DTile extends THREE.Object3D {
|
|
|
19
19
|
* cameraOnLoad: camera,
|
|
20
20
|
* parentTile: OGC3DTile,
|
|
21
21
|
* onLoadCallback: function,
|
|
22
|
+
* renderer: Renderer
|
|
22
23
|
* static: Boolean
|
|
23
24
|
* } properties
|
|
24
25
|
*/
|
|
25
26
|
constructor(properties) {
|
|
26
27
|
super();
|
|
27
28
|
properties.master = this;
|
|
29
|
+
this.renderer = properties.renderer;
|
|
28
30
|
this.geometricErrorMultiplier = properties.geometricErrorMultiplier? properties.geometricErrorMultiplier:1.0;
|
|
29
31
|
this.tileset = new InstancedTile(properties);
|
|
30
32
|
if (properties.static) {
|
|
@@ -4,8 +4,13 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
4
4
|
import * as path from "path-browserify";
|
|
5
5
|
import * as _ from "lodash";
|
|
6
6
|
|
|
7
|
-
const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1)
|
|
8
|
-
|
|
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();
|
|
9
14
|
|
|
10
15
|
class InstancedTile extends THREE.Object3D {
|
|
11
16
|
|
|
@@ -23,7 +28,8 @@ class InstancedTile extends THREE.Object3D {
|
|
|
23
28
|
* meshCallback: function,
|
|
24
29
|
* cameraOnLoad: camera,
|
|
25
30
|
* parentTile: OGC3DTile,
|
|
26
|
-
* onLoadCallback: function
|
|
31
|
+
* onLoadCallback: function,
|
|
32
|
+
* centerModel: Boolean
|
|
27
33
|
* } properties
|
|
28
34
|
*/
|
|
29
35
|
constructor(properties) {
|
|
@@ -76,7 +82,36 @@ class InstancedTile extends THREE.Object3D {
|
|
|
76
82
|
//json = JSON.parse(JSON.stringify(json))
|
|
77
83
|
const p = path.dirname(url);
|
|
78
84
|
self.setup({ rootPath: p, json: json });
|
|
85
|
+
if (!!properties.centerModel) {
|
|
86
|
+
const tempSphere = new THREE.Sphere();
|
|
87
|
+
if (self.boundingVolume instanceof OBB) {
|
|
88
|
+
// box
|
|
89
|
+
tempSphere.copy(self.boundingVolume.sphere);
|
|
90
|
+
} else if (self.boundingVolume instanceof THREE.Sphere) {
|
|
91
|
+
//sphere
|
|
92
|
+
tempSphere.copy(self.boundingVolume);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//tempSphere.applyMatrix4(self.matrixWorld);
|
|
96
|
+
if (!!this.json.boundingVolume.region) {
|
|
97
|
+
self.transformWGS84ToCartesian(
|
|
98
|
+
(self.json.boundingVolume.region[0] + self.json.boundingVolume.region[2]) * 0.5,
|
|
99
|
+
(self.json.boundingVolume.region[1] + self.json.boundingVolume.region[3]) * 0.5,
|
|
100
|
+
(self.json.boundingVolume.region[4] + self.json.boundingVolume.region[5]) * 0.5,
|
|
101
|
+
tempVec1);
|
|
102
|
+
tempVec2.set(tempVec1.x, tempVec1.z, -tempVec1.y);
|
|
103
|
+
|
|
104
|
+
tempQuaternion.setFromUnitVectors(tempVec2.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
|
+
}
|
|
79
113
|
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
114
|
+
|
|
80
115
|
}
|
|
81
116
|
self.tileLoader.get(self.abortController, properties.url, self.uuid, self);
|
|
82
117
|
|
|
@@ -132,7 +167,10 @@ class InstancedTile extends THREE.Object3D {
|
|
|
132
167
|
this.boundingVolume = new OBB(this.json.boundingVolume.box);
|
|
133
168
|
} else if (!!this.json.boundingVolume.region) {
|
|
134
169
|
const region = this.json.boundingVolume.region;
|
|
135
|
-
this.
|
|
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.z, -tempVec1.y), tempVec1.distanceTo(tempVec2));
|
|
136
174
|
} else if (!!this.json.boundingVolume.sphere) {
|
|
137
175
|
const sphere = this.json.boundingVolume.sphere;
|
|
138
176
|
this.boundingVolume = new THREE.Sphere(new THREE.Vector3(sphere[0], sphere[2], -sphere[1]), sphere[3]);
|
|
@@ -188,6 +226,8 @@ class InstancedTile extends THREE.Object3D {
|
|
|
188
226
|
|
|
189
227
|
}
|
|
190
228
|
}
|
|
229
|
+
self.matrixWorldNeedsUpdate = true;
|
|
230
|
+
self.updateWorldMatrix(true,true)
|
|
191
231
|
}
|
|
192
232
|
|
|
193
233
|
loadMesh(mesh) {
|
|
@@ -250,7 +290,7 @@ class InstancedTile extends THREE.Object3D {
|
|
|
250
290
|
// If this tile does not have mesh content but it has children
|
|
251
291
|
if (metric < 0 && self.hasMeshContent) return;
|
|
252
292
|
|
|
253
|
-
if ((!self.hasMeshContent && self.rootPath) || (metric < self.geometricError && !!self.meshContent)) {
|
|
293
|
+
if ((!self.hasMeshContent && self.rootPath) || (metric < self.master.geometricErrorMultiplier * self.geometricError && !!self.meshContent)) {
|
|
254
294
|
if (!!self.json && !!self.jsonChildren && self.childrenTiles.length != self.jsonChildren.length) {
|
|
255
295
|
loadJsonChildren();
|
|
256
296
|
return;
|
|
@@ -284,10 +324,10 @@ class InstancedTile extends THREE.Object3D {
|
|
|
284
324
|
}
|
|
285
325
|
|
|
286
326
|
// has children
|
|
287
|
-
if (metric >= self.geometricError) { // Ideal LOD or before ideal lod
|
|
327
|
+
if (metric >= self.master.geometricErrorMultiplier * self.geometricError) { // Ideal LOD or before ideal lod
|
|
288
328
|
|
|
289
329
|
self.changeContentVisibility(true);
|
|
290
|
-
} else if (metric < self.geometricError) { // Ideal LOD is past this one
|
|
330
|
+
} else if (metric < self.master.geometricErrorMultiplier * self.geometricError) { // Ideal LOD is past this one
|
|
291
331
|
// if children are visible and have been displayed, can be hidden
|
|
292
332
|
let allChildrenReady = true;
|
|
293
333
|
self.childrenTiles.every(child => {
|
|
@@ -311,7 +351,7 @@ class InstancedTile extends THREE.Object3D {
|
|
|
311
351
|
updateNodeVisibility(metric);
|
|
312
352
|
return;
|
|
313
353
|
}
|
|
314
|
-
if (metric >= self.geometricError) {
|
|
354
|
+
if (metric >= self.master.geometricErrorMultiplier * self.geometricError) {
|
|
315
355
|
self.disposeChildren();
|
|
316
356
|
updateNodeVisibility();
|
|
317
357
|
return;
|
|
@@ -440,11 +480,9 @@ class InstancedTile extends THREE.Object3D {
|
|
|
440
480
|
tempSphere.copy(this.boundingVolume);
|
|
441
481
|
tempSphere.applyMatrix4(this.master.matrixWorld);
|
|
442
482
|
if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
443
|
-
} else
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
//throw Error("Region bounding volume not supported");
|
|
447
|
-
return -1;
|
|
483
|
+
} else {
|
|
484
|
+
console.error("unsupported shape");
|
|
485
|
+
return -1
|
|
448
486
|
}
|
|
449
487
|
|
|
450
488
|
/////// return metric based on geometric error and distance
|
|
@@ -455,8 +493,18 @@ class InstancedTile extends THREE.Object3D {
|
|
|
455
493
|
return 0;
|
|
456
494
|
}
|
|
457
495
|
const scale = this.master.matrixWorld.getMaxScaleOnAxis();
|
|
458
|
-
|
|
459
|
-
|
|
496
|
+
|
|
497
|
+
this.master.renderer.getDrawingBufferSize(rendererSize);
|
|
498
|
+
let s = rendererSize.y;
|
|
499
|
+
let fov = camera.fov;
|
|
500
|
+
if(camera.aspect < 1){
|
|
501
|
+
fov *= camera.aspect;
|
|
502
|
+
s = rendererSize.x;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let lambda = 2.0 * Math.tan(0.5 * fov * 0.01745329251994329576923690768489) * distance;
|
|
506
|
+
|
|
507
|
+
return (window.devicePixelRatio * 16 * lambda) / (s * scale);
|
|
460
508
|
} else if (this.boundingVolume instanceof THREE.Box3) {
|
|
461
509
|
// Region
|
|
462
510
|
// Region not supported
|
|
@@ -495,8 +543,8 @@ class InstancedTile extends THREE.Object3D {
|
|
|
495
543
|
tempSphere.applyMatrix4(this.master.matrixWorld);
|
|
496
544
|
//if (!frustum.intersectsSphere(tempSphere)) return -1;
|
|
497
545
|
}
|
|
498
|
-
|
|
499
|
-
|
|
546
|
+
else {
|
|
547
|
+
console.error("unsupported shape")
|
|
500
548
|
}
|
|
501
549
|
return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
|
|
502
550
|
}
|
|
@@ -505,5 +553,21 @@ class InstancedTile extends THREE.Object3D {
|
|
|
505
553
|
const self = this;
|
|
506
554
|
return self.master.matrixWorld;
|
|
507
555
|
}
|
|
556
|
+
|
|
557
|
+
transformWGS84ToCartesian(lon, lat, h, sfct) {
|
|
558
|
+
const a = 6378137.0;
|
|
559
|
+
const e = 0.006694384442042;
|
|
560
|
+
const N = a / (Math.sqrt(1.0 - (e * Math.pow(Math.sin(lat), 2))));
|
|
561
|
+
const cosLat = Math.cos(lat);
|
|
562
|
+
const cosLon = Math.cos(lon);
|
|
563
|
+
const sinLat = Math.sin(lat);
|
|
564
|
+
const sinLon = Math.sin(lon);
|
|
565
|
+
const nPh = (N + h);
|
|
566
|
+
const x = nPh * cosLat * cosLon;
|
|
567
|
+
const y = nPh * cosLat * sinLon;
|
|
568
|
+
const z = (0.993305615557957 * N + h) * sinLat;
|
|
569
|
+
|
|
570
|
+
sfct.set(x, y, z);
|
|
571
|
+
}
|
|
508
572
|
}
|
|
509
573
|
export { InstancedTile };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { InstancedMesh } from 'three';
|
|
3
3
|
|
|
4
|
+
const t = new THREE.Matrix4();
|
|
4
5
|
class MeshTile{
|
|
5
6
|
constructor(scene){
|
|
6
7
|
const self = this;
|
|
@@ -60,7 +61,12 @@ class MeshTile{
|
|
|
60
61
|
self.instancedTiles[i].meshContent = self.instancedMesh;
|
|
61
62
|
if(self.instancedTiles[i].materialVisibility && !!self.instancedTiles[i].meshContent){
|
|
62
63
|
self.instancedMesh.count++;
|
|
63
|
-
self.
|
|
64
|
+
self.reuseableMatrix.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);
|
|
65
|
+
self.reuseableMatrix.multiply(self.instancedTiles[i].master.matrixWorld);
|
|
66
|
+
self.reuseableMatrix.multiply(self.instancedMesh.baseMatrix);
|
|
67
|
+
self.instancedMesh.setMatrixAt(self.instancedMesh.count-1, self.reuseableMatrix );
|
|
68
|
+
self.instancedMesh.getMatrixAt(0, t);
|
|
69
|
+
console.log()
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
}
|