@jdultra/threedtiles 4.0.2 → 4.0.4
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 +88 -4
- package/index.html +3 -3
- package/package.json +1 -1
- package/src/index.js +39 -29
- package/src/tileset/OGC3DTile.js +2 -1
- package/src/tileset/instanced/InstancedOGC3DTile.js +11 -3
- package/src/tileset/instanced/InstancedTile.js +3 -4
package/README.md
CHANGED
|
@@ -177,10 +177,60 @@ occlusionCullingService.setSide(THREE.DoubleSide);
|
|
|
177
177
|
```
|
|
178
178
|
|
|
179
179
|
### Instanced Tilesets
|
|
180
|
+
|
|
181
|
+
<p align="center">
|
|
182
|
+
<img src="https://storage.googleapis.com/jdultra-website/assets/instancedPic.png" width="800" style="display: block; margin: 0 auto"/>
|
|
183
|
+
</p>
|
|
184
|
+
|
|
180
185
|
Using InstancedTileLoader and InstancedOGC3DTile allows displaying the same Tileset at many different places with little impact on performance.
|
|
181
186
|
Each Tileset is independent in terms of it's position, orientation and level of detail but each tile is created as an "InstancedMesh" giving much
|
|
182
187
|
higher performance when displaying the same Tileset many times.
|
|
183
188
|
|
|
189
|
+
```
|
|
190
|
+
// First create the InstancedTileLoader that will manage caching
|
|
191
|
+
const instancedTileLoader = new InstancedTileLoader(scene, mesh => {
|
|
192
|
+
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
193
|
+
mesh.material.wireframe = false;
|
|
194
|
+
mesh.material.side = THREE.DoubleSide;
|
|
195
|
+
},
|
|
196
|
+
1000, // cache size as in the number of tiles cached in memory
|
|
197
|
+
100, // max number of tilesets from the same source
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// then create some tilesets
|
|
201
|
+
const instancedTilesets = [];
|
|
202
|
+
for (let i = 0; i < 100; i++) {
|
|
203
|
+
const tileset = new InstancedOGC3DTile({
|
|
204
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/droneship/tileset.json",
|
|
205
|
+
geometricErrorMultiplier: 1.0,
|
|
206
|
+
loadOutsideView: false,
|
|
207
|
+
tileLoader: instancedTileLoader,
|
|
208
|
+
static: true // when static is set to true, don't forget to call InstancedOGC3DTile#updateMatrix manually
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
tileset.translateOnAxis(new THREE.Vector3(1, 0, 0), 50 * i);
|
|
212
|
+
tileset.updateMatrix();
|
|
213
|
+
scene.add(tileset);
|
|
214
|
+
instancedTilesets.push(tileset);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
//setup an update loop for the LODs
|
|
218
|
+
setInterval(() => {
|
|
219
|
+
instancedTilesets[updateIndex].update(camera);
|
|
220
|
+
updateIndex= (updateIndex+1)%instancedTilesets.length;
|
|
221
|
+
},50);
|
|
222
|
+
|
|
223
|
+
//in the animate function, you also need to update the instancedTileLoader
|
|
224
|
+
function animate() {
|
|
225
|
+
requestAnimationFrame(animate);
|
|
226
|
+
instancedTileLoader.update();
|
|
227
|
+
|
|
228
|
+
... // rest of render loop
|
|
229
|
+
}
|
|
230
|
+
animate();
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
|
|
184
234
|
### static tilesets and other performance tips
|
|
185
235
|
When you know your tileset will be static, you can specify it in the OGC3DTile object constructor parameter.
|
|
186
236
|
This will skip recalculating the transformation matrix of every tile each frame and give a few extra frames per second.
|
|
@@ -192,16 +242,50 @@ const ogc3DTile = new OGC3DTile({
|
|
|
192
242
|
});
|
|
193
243
|
```
|
|
194
244
|
|
|
195
|
-
Either way, it's advised to set the autoUpdate property of the scene to false and
|
|
245
|
+
Either way, it's advised to set the autoUpdate property of the scene to false and call Scene#updateMatrixWorld manually whenever you move things around.
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
scene.matrixAutoUpdate = false;
|
|
249
|
+
scene.matrixWorldAutoUpdate = false;
|
|
250
|
+
|
|
251
|
+
// and when objects move:
|
|
252
|
+
scene.updateMatrixWorld(true);
|
|
196
253
|
|
|
197
254
|
```
|
|
198
|
-
|
|
255
|
+
#### tileset update loop
|
|
256
|
+
Updating a single tileset via OGC3DTile#update or InstancedOGC3DTile#update is quite fast, even when the tree is deep.
|
|
257
|
+
For a single tileset, it's safe to call it regularly with a setInterval:
|
|
258
|
+
```
|
|
259
|
+
function startInterval() {
|
|
260
|
+
interval = setIntervalAsync(function () {
|
|
261
|
+
ogc3DTile.update(camera);
|
|
262
|
+
}, 20);
|
|
263
|
+
}
|
|
199
264
|
```
|
|
200
265
|
|
|
266
|
+
However, with instancedTilesets, you may have hundreds or even thousands of LOD trees that need to be updated individually. In order to preserve frame-rate,
|
|
267
|
+
you may want to implement something a little smarter that yields the CPU to the render loop. In the example below, the process tries to update as many tilesets as it can in under 4 ms.
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
function now() {
|
|
271
|
+
return (typeof performance === 'undefined' ? Date : performance).now();
|
|
272
|
+
}
|
|
273
|
+
let updateIndex = 0;
|
|
274
|
+
setInterval(() => {
|
|
275
|
+
let startTime = now();
|
|
276
|
+
do{
|
|
277
|
+
instancedTilesets[updateIndex].update(camera);
|
|
278
|
+
updateIndex= (updateIndex+1)%instancedTilesets.length;
|
|
279
|
+
}while(updateIndex < instancedTilesets.length && now()-startTime<4);
|
|
280
|
+
},50);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
window#requestIdleCallback is also a good option but the rate of updates becomes slightly unpredictable.
|
|
284
|
+
|
|
201
285
|
# Projects that use this library
|
|
202
286
|
https://github.com/ebeaufay/UltraGlobe allows displaying a globe with multi resolution imagery, elevation and 3DTiles.
|
|
203
287
|
|
|
204
|
-
|
|
288
|
+
If you have a project that stems from this code. I'd love to link to it here and I'm always open to implementing extra features.
|
|
205
289
|
Contact: emeric.beaufays@jdultra.com
|
|
206
290
|
|
|
207
291
|
|
|
@@ -209,5 +293,5 @@ Contact: emeric.beaufays@jdultra.com
|
|
|
209
293
|
|
|
210
294
|
I also have code to convert meshes to 3DTiles with no limit to the size of the dataset relative to faces or textures.
|
|
211
295
|
It works for all types of meshes: photogrametry, BIM, colored or textured meshes with a single texture atlas or many individual textures.
|
|
212
|
-
|
|
296
|
+
The code is not open source but feel free to contact me for a trial.
|
|
213
297
|
Contact: emeric.beaufays@jdultra.com
|
package/index.html
CHANGED
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
|
|
48
48
|
<body>
|
|
49
49
|
<div id="screen"></div>
|
|
50
|
-
<div style="position: absolute; top: 1%; z-index: 100; right:1%; ">
|
|
51
|
-
<input type="range" min="0.0" max="1.0" value="
|
|
50
|
+
<!-- <div style="position: absolute; top: 1%; z-index: 100; right:1%; ">
|
|
51
|
+
<input type="range" min="0.0" max="1.0" value="0.5", step="0.001" class="slider" id="lodMultiplier" >
|
|
52
52
|
<p style="color: #0439aa;">LOD multiplier: <span id="multiplierValue"></span></p>
|
|
53
|
-
</div>
|
|
53
|
+
</div> -->
|
|
54
54
|
</body>
|
|
55
55
|
|
|
56
56
|
</html>
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -69,10 +69,11 @@ function initComposer(scene, camera, renderer) {
|
|
|
69
69
|
function initScene() {
|
|
70
70
|
const scene = new THREE.Scene();
|
|
71
71
|
scene.matrixAutoUpdate = false;
|
|
72
|
+
scene.matrixWorldAutoUpdate = false;
|
|
72
73
|
scene.background = new THREE.Color(0x000000);
|
|
73
|
-
scene.add(new THREE.AmbientLight(0xFFFFFF, 0
|
|
74
|
+
scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
|
|
74
75
|
|
|
75
|
-
const light = new THREE.PointLight(0xbbbbff, 2, 5000);
|
|
76
|
+
/*const light = new THREE.PointLight(0xbbbbff, 2, 5000);
|
|
76
77
|
const sphere = new THREE.SphereGeometry(2, 16, 8);
|
|
77
78
|
light.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0xbbbbff })));
|
|
78
79
|
scene.add(light);
|
|
@@ -83,9 +84,9 @@ function initScene() {
|
|
|
83
84
|
const sphere2 = new THREE.SphereGeometry(2, 16, 8);
|
|
84
85
|
light2.add(new THREE.Mesh(sphere2, new THREE.MeshBasicMaterial({ color: 0xffbbbb })));
|
|
85
86
|
scene.add(light2);
|
|
86
|
-
light2.position.set(200, 100, -100)
|
|
87
|
+
light2.position.set(200, 100, -100);*/
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
|
|
89
90
|
return scene;
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -132,8 +133,8 @@ function initStats(dom) {
|
|
|
132
133
|
|
|
133
134
|
|
|
134
135
|
function initCamera() {
|
|
135
|
-
const camera = new THREE.PerspectiveCamera(
|
|
136
|
-
camera.position.set(
|
|
136
|
+
const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1.0, 100000);
|
|
137
|
+
camera.position.set(-800, 800, 800);
|
|
137
138
|
camera.matrixAutoUpdate = true;
|
|
138
139
|
return camera;
|
|
139
140
|
}
|
|
@@ -143,12 +144,14 @@ function initTileset(scene) {
|
|
|
143
144
|
const tileLoader = new TileLoader(mesh => {
|
|
144
145
|
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
145
146
|
mesh.material.wireframe = false;
|
|
146
|
-
mesh.material.side = THREE.
|
|
147
|
+
mesh.material.side = THREE.DoubleSide;
|
|
147
148
|
}, 1000)
|
|
148
149
|
const ogc3DTile = new OGC3DTile({
|
|
150
|
+
//url: "http://localhost:8080/tileset.json",
|
|
149
151
|
//url: "https://storage.googleapis.com/ogc-3d-tiles/droneship/tileset.json",
|
|
150
152
|
url: "https://storage.googleapis.com/ogc-3d-tiles/berlinTileset/tileset.json",
|
|
151
|
-
|
|
153
|
+
//url: "https://s3.eu-central-2.wasabisys.com/construkted-assets-eu/ands2ty8orz/tileset.json",
|
|
154
|
+
geometricErrorMultiplier: 0.01,
|
|
152
155
|
loadOutsideView: false,
|
|
153
156
|
tileLoader: tileLoader,
|
|
154
157
|
//occlusionCullingService: occlusionCullingService,
|
|
@@ -161,6 +164,7 @@ function initTileset(scene) {
|
|
|
161
164
|
|
|
162
165
|
//// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
|
|
163
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)
|
|
164
168
|
//// If the OGC3DTile object is marked as "static" (constructorParameter), these operations will not work.
|
|
165
169
|
|
|
166
170
|
|
|
@@ -205,28 +209,30 @@ function createInstancedTileLoader(scene) {
|
|
|
205
209
|
return new InstancedTileLoader(scene, mesh => {
|
|
206
210
|
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
207
211
|
mesh.material.wireframe = false;
|
|
208
|
-
mesh.material.side = THREE.
|
|
209
|
-
}, 1000,
|
|
212
|
+
mesh.material.side = THREE.DoubleSide;
|
|
213
|
+
}, 1000, 3375);
|
|
210
214
|
}
|
|
211
215
|
function initInstancedTilesets(instancedTileLoader) {
|
|
212
216
|
|
|
213
217
|
const instancedTilesets = [];
|
|
214
218
|
|
|
215
|
-
for (let x = 0; x <
|
|
216
|
-
for (let y = 0; y <
|
|
217
|
-
for (let z = 0; z <
|
|
219
|
+
for (let x = 0; x < 15; x++) {
|
|
220
|
+
for (let y = 0; y < 15; y++) {
|
|
221
|
+
for (let z = 0; z < 15; z++) {
|
|
218
222
|
const tileset = new InstancedOGC3DTile({
|
|
219
223
|
url: "https://storage.googleapis.com/ogc-3d-tiles/droneship/tileset.json",
|
|
224
|
+
//url: "https://storage.googleapis.com/ogc-3d-tiles/berlinTileset/tileset.json",
|
|
220
225
|
//url: "http://localhost:8080/tileset.json",
|
|
221
|
-
geometricErrorMultiplier: 0
|
|
222
|
-
loadOutsideView:
|
|
226
|
+
geometricErrorMultiplier: 1.0,
|
|
227
|
+
loadOutsideView: true,
|
|
223
228
|
tileLoader: instancedTileLoader,
|
|
224
|
-
static:
|
|
229
|
+
static: true,
|
|
225
230
|
});
|
|
226
|
-
tileset.rotateOnAxis(new THREE.Vector3(
|
|
231
|
+
//tileset.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
|
|
227
232
|
tileset.translateOnAxis(new THREE.Vector3(1, 0, 0), 50 * x)
|
|
228
233
|
tileset.translateOnAxis(new THREE.Vector3(0, 1, 0), 50 * y)
|
|
229
234
|
tileset.translateOnAxis(new THREE.Vector3(0, 0, 1), 50 * z)
|
|
235
|
+
tileset.updateMatrix()
|
|
230
236
|
scene.add(tileset);
|
|
231
237
|
instancedTilesets.push(tileset);
|
|
232
238
|
|
|
@@ -235,18 +241,22 @@ function initInstancedTilesets(instancedTileLoader) {
|
|
|
235
241
|
}
|
|
236
242
|
}
|
|
237
243
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
instancedTilesets.forEach(tileset=>{
|
|
242
|
-
tileset.update(camera);
|
|
243
|
-
})
|
|
244
|
-
setTimeout(() => {
|
|
245
|
-
window.requestIdleCallback(idleCallback, { timeout: 50 })
|
|
246
|
-
}, 20)
|
|
247
|
-
|
|
244
|
+
scene.updateMatrixWorld(true)
|
|
245
|
+
function now() {
|
|
246
|
+
return (typeof performance === 'undefined' ? Date : performance).now();
|
|
248
247
|
}
|
|
249
|
-
|
|
248
|
+
let updateIndex = 0;
|
|
249
|
+
setInterval(() => {
|
|
250
|
+
let startTime = now();
|
|
251
|
+
do{
|
|
252
|
+
const frustum = new THREE.Frustum();
|
|
253
|
+
frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));
|
|
254
|
+
instancedTilesets[updateIndex].update(camera, frustum);
|
|
255
|
+
updateIndex= (updateIndex+1)%instancedTilesets.length;
|
|
256
|
+
}while(updateIndex < instancedTilesets.length && now()-startTime<4);
|
|
257
|
+
},40);
|
|
258
|
+
|
|
259
|
+
//initLODMultiplierSlider(instancedTilesets);
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
function initLODMultiplierSlider(instancedTilesets) {
|
|
@@ -256,7 +266,7 @@ function initLODMultiplierSlider(instancedTilesets) {
|
|
|
256
266
|
|
|
257
267
|
slider.oninput = () => {
|
|
258
268
|
instancedTilesets.forEach(tileset => {
|
|
259
|
-
tileset.setGeometricErrorMultiplier(slider.value)
|
|
269
|
+
tileset.setGeometricErrorMultiplier(slider.value*0.1)
|
|
260
270
|
})
|
|
261
271
|
output.innerHTML = slider.value;
|
|
262
272
|
}
|
package/src/tileset/OGC3DTile.js
CHANGED
|
@@ -530,7 +530,8 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
530
530
|
return 0;
|
|
531
531
|
}
|
|
532
532
|
const scale = this.matrixWorld.getMaxScaleOnAxis();
|
|
533
|
-
return
|
|
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);
|
|
534
535
|
} else if (this.boundingVolume instanceof THREE.Box3) {
|
|
535
536
|
// Region
|
|
536
537
|
// Region not supported
|
|
@@ -32,9 +32,17 @@ class InstancedOGC3DTile extends THREE.Object3D {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
update(camera){
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
update(camera, frustum){
|
|
36
|
+
if(!!frustum){
|
|
37
|
+
this.tileset._update(camera, frustum);
|
|
38
|
+
}else{
|
|
39
|
+
const frustum = new THREE.Frustum();
|
|
40
|
+
frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));
|
|
41
|
+
this.tileset._update(camera, frustum);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
updateWithFrustum(camera, frustum){
|
|
38
46
|
this.tileset._update(camera, frustum);
|
|
39
47
|
}
|
|
40
48
|
|
|
@@ -23,8 +23,7 @@ class InstancedTile extends THREE.Object3D {
|
|
|
23
23
|
* meshCallback: function,
|
|
24
24
|
* cameraOnLoad: camera,
|
|
25
25
|
* parentTile: OGC3DTile,
|
|
26
|
-
* onLoadCallback: function
|
|
27
|
-
* static: Boolean
|
|
26
|
+
* onLoadCallback: function
|
|
28
27
|
* } properties
|
|
29
28
|
*/
|
|
30
29
|
constructor(properties) {
|
|
@@ -333,7 +332,6 @@ class InstancedTile extends THREE.Object3D {
|
|
|
333
332
|
level: self.level + 1,
|
|
334
333
|
tileLoader: self.tileLoader,
|
|
335
334
|
cameraOnLoad: camera,
|
|
336
|
-
static: self.static,
|
|
337
335
|
master: self.master
|
|
338
336
|
});
|
|
339
337
|
self.childrenTiles.push(childTile);
|
|
@@ -457,7 +455,8 @@ class InstancedTile extends THREE.Object3D {
|
|
|
457
455
|
return 0;
|
|
458
456
|
}
|
|
459
457
|
const scale = this.master.matrixWorld.getMaxScaleOnAxis();
|
|
460
|
-
return (((distance / Math.pow(scale, 2)) / 100) / this.master.geometricErrorMultiplier);
|
|
458
|
+
//return (((distance / Math.pow(scale, 2)) / 100) / this.master.geometricErrorMultiplier);
|
|
459
|
+
return Math.pow(distance, 2) /(this.master.geometricErrorMultiplier*this.geometricError*Math.pow(scale,2.0)*35);
|
|
461
460
|
} else if (this.boundingVolume instanceof THREE.Box3) {
|
|
462
461
|
// Region
|
|
463
462
|
// Region not supported
|