@jdultra/threedtiles 3.1.5 → 3.3.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 +53 -2
- package/index.html +2 -3
- package/package.json +2 -2
- package/src/decoder/B3DMDecoder.js +5 -4
- package/src/index.js +27 -24
- package/src/tileset/OGC3DTile.js +95 -37
- package/src/tileset/OcclusionCullingService.js +79 -0
- package/src/tileset/TileLoader.js +37 -26
- package/manifest.json +0 -4
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# threedtiles
|
|
2
2
|
|
|
3
3
|
3DTiles viewer for three.js
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Photogrametry : https://ebeaufay.github.io/ThreedTilesViewer.github.io/
|
|
6
|
+
|
|
7
|
+
IFC : https://storage.googleapis.com/jdultra.com/ifc/index.html
|
|
8
|
+
|
|
9
|
+
Occlusion culling : [https://storage.googleapis.com/jdultra.com/occlusionCulling/index.html](https://storage.googleapis.com/www.jdultra.com/occlusion/index.html)
|
|
6
10
|
|
|
7
11
|
Adding a tileset to a scene is as easy as :
|
|
8
12
|
|
|
@@ -35,6 +39,7 @@ Currently, the library is limmited to B3DM files.
|
|
|
35
39
|
- Optionally load low detail tiles outside of view frustum for correct shadows and basic mesh present when the camera moves quickly.
|
|
36
40
|
- Share a cache between tileset instances
|
|
37
41
|
- Optimal tile load order
|
|
42
|
+
- Occlusion culling (demo)
|
|
38
43
|
|
|
39
44
|
### geometric Error Multiplier
|
|
40
45
|
The geometric error multiplier allows you to multiply the geometric error by a factor.
|
|
@@ -112,6 +117,52 @@ const ogc3DTile = new OGC3DTile({
|
|
|
112
117
|
ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -450);
|
|
113
118
|
ogc3DTile.rotateOnAxis(new THREE.Vector3(1,0,0), -Math.PI*0.5);
|
|
114
119
|
...
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
### Occlusion culling
|
|
123
|
+
Occlusion culling prevents the refinment of data that is hidden by other data, like a wall. It can have a big impact on frame-rate and loading speed for interior scenes.
|
|
124
|
+
A word of warning: activating occlusion culling causes an extra render-pass and as such, has an impact on frame-rate. It will be most beneficial on interior scenes where most of the data is occluded by walls.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
First, instantiate an OcclusionCullingService:
|
|
128
|
+
```
|
|
129
|
+
const occlusionCullingService = new OcclusionCullingService();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This service must be passed to every OGC3DTiles object like so:
|
|
133
|
+
```
|
|
134
|
+
const ogc3DTile = new OGC3DTile({
|
|
135
|
+
url: "path/to/tileset.json",
|
|
136
|
+
occlusionCullingService: occlusionCullingService
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Then, you must update the occlusionCullingService within your render loop:
|
|
141
|
+
```
|
|
142
|
+
function animate() {
|
|
143
|
+
requestAnimationFrame(animate);
|
|
144
|
+
renderer.render(scene, camera);
|
|
145
|
+
occlusionCullingService.update(scene, renderer, camera)
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Finally, you may want to set what side of the faces are drawn in the occlusion pass. By default, THREE.FrontSide is used:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
const occlusionCullingService = new OcclusionCullingService();
|
|
153
|
+
occlusionCullingService.setSide(THREE.DoubleSide);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
### static tilesets (Performance tip)
|
|
158
|
+
When you know your tileset will be static, you can specify it in the OGC3DTile object constructor parameter.
|
|
159
|
+
This will skip recalculating the transformation matrix of every tile each frame and give a few extra frames per second.
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
const ogc3DTile = new OGC3DTile({
|
|
163
|
+
url: "path/to/tileset.json",
|
|
164
|
+
static: true
|
|
165
|
+
});
|
|
115
166
|
```
|
|
116
167
|
|
|
117
168
|
# Displaying meshes on a globe
|
package/index.html
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="utf-8" />
|
|
6
6
|
<title>Three 3DTiles viewer sample</title>
|
|
7
|
-
<link rel="manifest" href="manifest.json">
|
|
8
7
|
<style>
|
|
9
8
|
.slidecontainer {
|
|
10
9
|
width: 100%;
|
|
@@ -49,11 +48,11 @@
|
|
|
49
48
|
<body>
|
|
50
49
|
<div id="screen"></div>
|
|
51
50
|
<div style="position: absolute; top: 1%; z-index: 100; right:1%; ">
|
|
52
|
-
<input type="range" min="0.1" max="2" value="1.0", step="0.
|
|
51
|
+
<input type="range" min="0.1" max="2" value="1.0", step="0.001" class="slider" id="lodMultiplier" >
|
|
53
52
|
<p style="color: #0439aa;">LOD multiplier: <span id="multiplierValue"></span></p>
|
|
54
53
|
</div>
|
|
55
54
|
<div style="position: absolute; bottom: 1%; z-index: 100;">
|
|
56
|
-
<a href="https://
|
|
55
|
+
<a href="https://skfb.ly/6UoNJ">ORIGINAL MODEL</a>
|
|
57
56
|
</div>
|
|
58
57
|
</body>
|
|
59
58
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jdultra/threedtiles",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "An OGC 3DTiles viewer for Three.js",
|
|
5
5
|
"main": "tileset.js",
|
|
6
6
|
"scripts": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"path-browserify": "^1.0.1",
|
|
32
32
|
"regenerator-runtime": ">=0.13.7",
|
|
33
33
|
"set-interval-async": "^2.0.3",
|
|
34
|
-
"three": "0.
|
|
34
|
+
"three": "0.140.2",
|
|
35
35
|
"uuid": "^8.3.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
2
2
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
3
|
-
import { LegacyGLTFLoader } from './LegacyGLTFLoader.js';
|
|
3
|
+
//import { LegacyGLTFLoader } from './LegacyGLTFLoader.js';
|
|
4
4
|
|
|
5
5
|
const gltfLoader = new GLTFLoader();
|
|
6
6
|
const dracoLoader = new DRACOLoader();
|
|
7
7
|
dracoLoader.setDecoderPath( 'https://www.gstatic.com/draco/versioned/decoders/1.4.3/' );
|
|
8
8
|
gltfLoader.setDRACOLoader( dracoLoader );
|
|
9
|
-
const legacyGLTFLoader = new LegacyGLTFLoader();
|
|
9
|
+
//const legacyGLTFLoader = new LegacyGLTFLoader();
|
|
10
10
|
const B3DMDecoder = {
|
|
11
11
|
parseB3DM: (arrayBuffer, meshCallback) => {
|
|
12
12
|
const dataView = new DataView(arrayBuffer);
|
|
@@ -64,7 +64,8 @@ const B3DMDecoder = {
|
|
|
64
64
|
});
|
|
65
65
|
resolve(model.scene);
|
|
66
66
|
}, error=>{
|
|
67
|
-
|
|
67
|
+
console.error(error);
|
|
68
|
+
/* legacyGLTFLoader.parse(gltfBuffer, model => {
|
|
68
69
|
|
|
69
70
|
////TODO
|
|
70
71
|
//model.batchTable = b3dm.batchTable;
|
|
@@ -83,7 +84,7 @@ const B3DMDecoder = {
|
|
|
83
84
|
}
|
|
84
85
|
});
|
|
85
86
|
resolve(model.scene);
|
|
86
|
-
}, null);
|
|
87
|
+
}, null); */
|
|
87
88
|
});
|
|
88
89
|
});
|
|
89
90
|
}
|
package/src/index.js
CHANGED
|
@@ -5,8 +5,11 @@ import { OGC3DTile } from "./tileset/OGC3DTile";
|
|
|
5
5
|
import { TileLoader } from "./tileset/TileLoader";
|
|
6
6
|
import { MapControls, OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
7
7
|
import { setIntervalAsync } from 'set-interval-async/dynamic';
|
|
8
|
+
import { OcclusionCullingService } from "./tileset/OcclusionCullingService";
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
const occlusionCullingService = new OcclusionCullingService();
|
|
12
|
+
occlusionCullingService.setSide(THREE.DoubleSide);
|
|
10
13
|
const scene = initScene();
|
|
11
14
|
const domContainer = initDomContainer("screen");
|
|
12
15
|
const camera = initCamera();
|
|
@@ -17,12 +20,18 @@ const controller = initController(camera, domContainer);
|
|
|
17
20
|
const stats = initStats(domContainer);
|
|
18
21
|
const renderer = initRenderer(camera, domContainer);
|
|
19
22
|
|
|
23
|
+
|
|
20
24
|
animate();
|
|
21
25
|
|
|
22
26
|
function initScene() {
|
|
23
27
|
const scene = new THREE.Scene();
|
|
24
|
-
scene.
|
|
25
|
-
scene.
|
|
28
|
+
scene.matrixAutoUpdate = false;
|
|
29
|
+
scene.background = new THREE.Color(0xaaffcc);
|
|
30
|
+
scene.add(new THREE.AmbientLight(0xFFFFFF, 0.2));
|
|
31
|
+
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
|
|
32
|
+
directionalLight.position.set(100,100,100)
|
|
33
|
+
directionalLight.lookAt(-1,-1,-1)
|
|
34
|
+
scene.add( directionalLight );
|
|
26
35
|
return scene;
|
|
27
36
|
}
|
|
28
37
|
|
|
@@ -37,11 +46,9 @@ function initDomContainer(divID) {
|
|
|
37
46
|
|
|
38
47
|
function initRenderer(camera, dom) {
|
|
39
48
|
|
|
40
|
-
const renderer = new THREE.WebGLRenderer({ antialias: true,
|
|
41
|
-
renderer.antialias = true;
|
|
49
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
|
|
42
50
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
43
51
|
renderer.setSize(dom.offsetWidth, dom.offsetHeight);
|
|
44
|
-
|
|
45
52
|
renderer.outputEncoding = THREE.sRGBEncoding;
|
|
46
53
|
renderer.autoClear = false;
|
|
47
54
|
|
|
@@ -69,9 +76,9 @@ function initStats(dom) {
|
|
|
69
76
|
|
|
70
77
|
|
|
71
78
|
function initCamera() {
|
|
72
|
-
const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1,
|
|
73
|
-
camera.position.set(10,
|
|
74
|
-
|
|
79
|
+
const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 0.1, 1000);
|
|
80
|
+
camera.position.set(-10, 5, 20);
|
|
81
|
+
camera.matrixAutoUpdate = true;
|
|
75
82
|
return camera;
|
|
76
83
|
}
|
|
77
84
|
|
|
@@ -79,32 +86,28 @@ function initTileset(scene) {
|
|
|
79
86
|
|
|
80
87
|
const ogc3DTile = new OGC3DTile({
|
|
81
88
|
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tiledWithSkirts/tileset.json",
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
//url: "http://localhost:8081/tileset.json",
|
|
90
|
+
geometricErrorMultiplier: 0.5,
|
|
91
|
+
loadOutsideView: false,
|
|
84
92
|
tileLoader: new TileLoader(mesh => {
|
|
85
93
|
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
86
94
|
mesh.material.wireframe = false;
|
|
87
95
|
mesh.material.side = THREE.DoubleSide;
|
|
88
96
|
}, 1000),
|
|
89
|
-
|
|
90
|
-
console.log(tileset.json)
|
|
91
|
-
}
|
|
97
|
+
occlusionCullingService: occlusionCullingService
|
|
92
98
|
});
|
|
93
99
|
|
|
94
100
|
|
|
101
|
+
|
|
95
102
|
//// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
|
|
96
|
-
|
|
97
|
-
//ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
|
|
98
|
-
//ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
|
|
99
|
-
//ogc3DTile.scale.set(0.0001,0.0001,0.0001);
|
|
103
|
+
//-172683.125,301451.125,1367762.21875
|
|
100
104
|
//ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
|
|
105
|
+
|
|
106
|
+
|
|
104
107
|
//// 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
|
|
105
108
|
|
|
106
109
|
|
|
107
|
-
|
|
110
|
+
|
|
108
111
|
var interval;
|
|
109
112
|
document.addEventListener('keyup', (e) => {
|
|
110
113
|
console.log(camera.position)
|
|
@@ -141,7 +144,7 @@ function initLODMultiplierSlider(tileset) {
|
|
|
141
144
|
function initController(camera, dom) {
|
|
142
145
|
const controller = new OrbitControls(camera, dom);
|
|
143
146
|
|
|
144
|
-
controller.target.set(
|
|
147
|
+
controller.target.set(0, 0, 0);
|
|
145
148
|
controller.minDistance = 1;
|
|
146
149
|
controller.maxDistance = 5000;
|
|
147
150
|
controller.update();
|
|
@@ -151,11 +154,11 @@ function initController(camera, dom) {
|
|
|
151
154
|
|
|
152
155
|
function animate() {
|
|
153
156
|
requestAnimationFrame(animate);
|
|
154
|
-
|
|
155
|
-
camera.updateMatrixWorld();
|
|
156
157
|
renderer.render(scene, camera);
|
|
158
|
+
occlusionCullingService.update(scene, renderer, camera)
|
|
157
159
|
stats.update();
|
|
158
160
|
|
|
161
|
+
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
|
package/src/tileset/OGC3DTile.js
CHANGED
|
@@ -2,9 +2,8 @@ import * as THREE from 'three';
|
|
|
2
2
|
import { OBB } from "../geometry/obb";
|
|
3
3
|
import { TileLoader } from "./TileLoader";
|
|
4
4
|
import { v4 as uuidv4 } from "uuid";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
const path = require('path');
|
|
5
|
+
import * as path from "path-browserify"
|
|
6
|
+
import { clamp } from "three/src/math/MathUtils";
|
|
8
7
|
|
|
9
8
|
const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1));
|
|
10
9
|
|
|
@@ -26,12 +25,19 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
26
25
|
* meshCallback: function,
|
|
27
26
|
* cameraOnLoad: camera,
|
|
28
27
|
* parentTile: OGC3DTile,
|
|
29
|
-
* onLoadCallback: function
|
|
28
|
+
* onLoadCallback: function,
|
|
29
|
+
* occlusionCullingService: OcclusionCullingService,
|
|
30
|
+
* static: Boolean
|
|
30
31
|
* } properties
|
|
31
32
|
*/
|
|
32
33
|
constructor(properties) {
|
|
33
34
|
super();
|
|
34
35
|
const self = this;
|
|
36
|
+
|
|
37
|
+
if(properties.static){
|
|
38
|
+
this.matrixAutoUpdate = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
this.uuid = uuidv4();
|
|
36
42
|
if (!!properties.tileLoader) {
|
|
37
43
|
this.tileLoader = properties.tileLoader;
|
|
@@ -49,6 +55,12 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
49
55
|
this.loadOutsideView = properties.loadOutsideView;
|
|
50
56
|
this.cameraOnLoad = properties.cameraOnLoad;
|
|
51
57
|
this.parentTile = properties.parentTile;
|
|
58
|
+
this.occlusionCullingService = properties.occlusionCullingService;
|
|
59
|
+
if (this.occlusionCullingService) {
|
|
60
|
+
this.color = new THREE.Color();
|
|
61
|
+
this.color.setHex(Math.random() * 0xffffff);
|
|
62
|
+
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;
|
|
63
|
+
}
|
|
52
64
|
|
|
53
65
|
// declare properties specific to the tile for clarity
|
|
54
66
|
this.childrenTiles = [];
|
|
@@ -65,6 +77,8 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
65
77
|
this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
|
|
66
78
|
this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
|
|
67
79
|
|
|
80
|
+
this.layers.disable(0);
|
|
81
|
+
|
|
68
82
|
if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
|
|
69
83
|
self.setup(properties);
|
|
70
84
|
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
@@ -167,14 +181,24 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
167
181
|
if (!!self.deleted) return;
|
|
168
182
|
mesh.traverse((o) => {
|
|
169
183
|
if (o.isMesh) {
|
|
184
|
+
if (self.occlusionCullingService) {
|
|
185
|
+
const position = o.geometry.attributes.position;
|
|
186
|
+
const colors = [];
|
|
187
|
+
for (let i = 0; i < position.count; i++) {
|
|
188
|
+
colors.push(self.color.r, self.color.g, self.color.b);
|
|
189
|
+
}
|
|
190
|
+
o.geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
191
|
+
}
|
|
170
192
|
o.material.visible = false;
|
|
171
193
|
}
|
|
172
194
|
});
|
|
195
|
+
|
|
173
196
|
self.add(mesh);
|
|
197
|
+
self.updateWorldMatrix(false, true);
|
|
174
198
|
self.meshContent = mesh;
|
|
175
199
|
}, !self.cameraOnLoad ? () => 0 : () => {
|
|
176
200
|
return self.calculateDistanceToCamera(self.cameraOnLoad);
|
|
177
|
-
}, () => self.getSiblings(), self.level
|
|
201
|
+
}, () => self.getSiblings(), self.level);
|
|
178
202
|
} else if (url.includes(".json")) {
|
|
179
203
|
self.tileLoader.get(this.uuid, url, json => {
|
|
180
204
|
if (!!self.deleted) return;
|
|
@@ -185,24 +209,6 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
185
209
|
self.hasUnloadedJSONContent = false;
|
|
186
210
|
});
|
|
187
211
|
|
|
188
|
-
/* self.controller = new AbortController();
|
|
189
|
-
setTimeout(() => {
|
|
190
|
-
fetch(url, { signal: self.controller.signal }).then(result => {
|
|
191
|
-
if (!result.ok) {
|
|
192
|
-
throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
193
|
-
}
|
|
194
|
-
result.json().then(json => {
|
|
195
|
-
// when json content is downloaded, it is inserted into this tile's original JSON as a child
|
|
196
|
-
// and the content object is deleted from the original JSON
|
|
197
|
-
if (!self.json.children) self.json.children = [];
|
|
198
|
-
json.rootPath = path.dirname(url);
|
|
199
|
-
self.json.children.push(json);
|
|
200
|
-
delete self.json.content;
|
|
201
|
-
self.hasUnloadedJSONContent = false;
|
|
202
|
-
}).catch(error => { });
|
|
203
|
-
}).catch(error => { });
|
|
204
|
-
}, 0); */
|
|
205
|
-
|
|
206
212
|
}
|
|
207
213
|
|
|
208
214
|
}
|
|
@@ -212,6 +218,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
212
218
|
dispose() {
|
|
213
219
|
|
|
214
220
|
const self = this;
|
|
221
|
+
self.childrenTiles.forEach(tile => tile.dispose());
|
|
215
222
|
self.deleted = true;
|
|
216
223
|
this.traverse(function (element) {
|
|
217
224
|
if (!!element.contentURL) {
|
|
@@ -244,19 +251,24 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
244
251
|
_update(camera, frustum) {
|
|
245
252
|
const self = this;
|
|
246
253
|
|
|
247
|
-
|
|
254
|
+
const visibilityBeforeUpdate = self.materialVisibility;
|
|
255
|
+
|
|
248
256
|
if (!!self.boundingVolume && !!self.geometricError) {
|
|
249
257
|
self.metric = self.calculateUpdateMetric(camera, frustum);
|
|
250
258
|
}
|
|
259
|
+
self.childrenTiles.forEach(child => child._update(camera, frustum));
|
|
251
260
|
|
|
252
261
|
updateNodeVisibility(self.metric);
|
|
253
262
|
updateTree(self.metric);
|
|
254
|
-
trimTree(self.metric);
|
|
263
|
+
trimTree(self.metric, visibilityBeforeUpdate);
|
|
255
264
|
|
|
256
265
|
|
|
257
266
|
function updateTree(metric) {
|
|
258
267
|
// If this tile does not have mesh content but it has children
|
|
259
268
|
if (metric < 0 && self.hasMeshContent) return;
|
|
269
|
+
if (self.occlusionCullingService && self.hasMeshContent && !self.occlusionCullingService.hasID(self.colorID)) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
260
272
|
if (!self.hasMeshContent || (metric < self.geometricError && !!self.meshContent)) {
|
|
261
273
|
if (!!self.json && !!self.json.children && self.childrenTiles.length != self.json.children.length) {
|
|
262
274
|
loadJsonChildren();
|
|
@@ -296,7 +308,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
296
308
|
self.changeContentVisibility(true);
|
|
297
309
|
} else if (metric < self.geometricError) { // Ideal LOD is past this one
|
|
298
310
|
// if children are visible and have been displayed, can be hidden
|
|
299
|
-
|
|
311
|
+
let allChildrenReady = true;
|
|
300
312
|
self.childrenTiles.every(child => {
|
|
301
313
|
|
|
302
314
|
if (!child.isReady()) {
|
|
@@ -307,24 +319,32 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
307
319
|
});
|
|
308
320
|
if (allChildrenReady) {
|
|
309
321
|
self.changeContentVisibility(false);
|
|
310
|
-
} else {
|
|
311
|
-
//self.changeContentVisibility(true);
|
|
312
|
-
|
|
313
322
|
}
|
|
314
323
|
}
|
|
315
324
|
}
|
|
316
325
|
|
|
317
|
-
function trimTree(metric) {
|
|
326
|
+
function trimTree(metric, visibilityBeforeUpdate) {
|
|
318
327
|
if (!self.hasMeshContent) return;
|
|
319
|
-
if (
|
|
328
|
+
if (!self.inFrustum) { // outside frustum
|
|
329
|
+
self.disposeChildren();
|
|
330
|
+
updateNodeVisibility(metric);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (self.occlusionCullingService &&
|
|
334
|
+
!visibilityBeforeUpdate &&
|
|
335
|
+
self.hasMeshContent &&
|
|
336
|
+
self.meshContent &&
|
|
337
|
+
self.meshesToDisplay == self.meshesDisplayed &&
|
|
338
|
+
self.areAllChildrenLoadedAndHidden()) {
|
|
339
|
+
|
|
320
340
|
self.disposeChildren();
|
|
341
|
+
updateNodeVisibility(metric);
|
|
321
342
|
return;
|
|
322
343
|
}
|
|
323
344
|
if (metric >= self.geometricError) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
345
|
+
self.disposeChildren();
|
|
346
|
+
updateNodeVisibility();
|
|
347
|
+
return;
|
|
328
348
|
}
|
|
329
349
|
|
|
330
350
|
}
|
|
@@ -342,7 +362,8 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
342
362
|
loadOutsideView: self.loadOutsideView,
|
|
343
363
|
level: self.level + 1,
|
|
344
364
|
tileLoader: self.tileLoader,
|
|
345
|
-
cameraOnLoad: camera
|
|
365
|
+
cameraOnLoad: camera,
|
|
366
|
+
occlusionCullingService:self.occlusionCullingService
|
|
346
367
|
});
|
|
347
368
|
self.childrenTiles.push(childTile);
|
|
348
369
|
self.add(childTile);
|
|
@@ -351,6 +372,36 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
351
372
|
|
|
352
373
|
}
|
|
353
374
|
|
|
375
|
+
areAllChildrenLoadedAndHidden() {
|
|
376
|
+
let allLoadedAndHidden = true;
|
|
377
|
+
const self = this;
|
|
378
|
+
this.childrenTiles.every(child => {
|
|
379
|
+
if (child.hasMeshContent) {
|
|
380
|
+
if(child.childrenTiles.length>0){
|
|
381
|
+
allLoadedAndHidden = false;
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
if (!child.inFrustum ) {
|
|
385
|
+
return true;
|
|
386
|
+
};
|
|
387
|
+
if (!child.materialVisibility || child.meshesToDisplay != child.meshesDisplayed) {
|
|
388
|
+
allLoadedAndHidden = false;
|
|
389
|
+
return false;
|
|
390
|
+
} else if (self.occlusionCullingService.hasID(child.colorID)) {
|
|
391
|
+
allLoadedAndHidden = false;
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
if (!child.areAllChildrenLoadedAndHidden()) {
|
|
396
|
+
allLoadedAndHidden = false;
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
});
|
|
402
|
+
return allLoadedAndHidden;
|
|
403
|
+
}
|
|
404
|
+
|
|
354
405
|
/**
|
|
355
406
|
* Node is ready if it is outside frustum, if it was drawn at least once or if all it's children are ready
|
|
356
407
|
* @returns true if ready
|
|
@@ -403,6 +454,13 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
403
454
|
|
|
404
455
|
changeContentVisibility(visibility) {
|
|
405
456
|
const self = this;
|
|
457
|
+
if(self.hasMeshContent && self.meshContent){
|
|
458
|
+
if(visibility){
|
|
459
|
+
self.layers.enable(0);
|
|
460
|
+
}else{
|
|
461
|
+
self.layers.disable(0);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
406
464
|
if (self.materialVisibility == visibility) {
|
|
407
465
|
return;
|
|
408
466
|
}
|
|
@@ -458,7 +516,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
458
516
|
return 0;
|
|
459
517
|
}
|
|
460
518
|
const scale = this.matrixWorld.getMaxScaleOnAxis();
|
|
461
|
-
return ((distance / 100) / this.geometricErrorMultiplier)
|
|
519
|
+
return (((distance / Math.pow(scale, 2)) / 100) / this.geometricErrorMultiplier);
|
|
462
520
|
} else if (this.boundingVolume instanceof THREE.Box3) {
|
|
463
521
|
// Region
|
|
464
522
|
// Region not supported
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { clamp } from "three/src/math/MathUtils";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OcclusionCullingService {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {
|
|
11
|
+
* json: optional,
|
|
12
|
+
* url: optional,
|
|
13
|
+
* rootPath: optional,
|
|
14
|
+
* parentGeometricError: optional,
|
|
15
|
+
* parentBoundingVolume: optional,
|
|
16
|
+
* parentRefinement: optional,
|
|
17
|
+
* geometricErrorMultiplier: Double,
|
|
18
|
+
* loadOutsideView: Boolean,
|
|
19
|
+
* tileLoader : TileLoader,
|
|
20
|
+
* meshCallback: function,
|
|
21
|
+
* cameraOnLoad: camera,
|
|
22
|
+
* parentTile: OGC3DTile,
|
|
23
|
+
* onLoadCallback: function,
|
|
24
|
+
* occlusionCullingService: OcclusionCullingService
|
|
25
|
+
* } properties
|
|
26
|
+
*/
|
|
27
|
+
constructor() {
|
|
28
|
+
this.cullMap = [];
|
|
29
|
+
this.cullMaterial = new THREE.MeshBasicMaterial({ vertexColors: true });
|
|
30
|
+
this.cullMaterial.side = THREE.FrontSide;
|
|
31
|
+
this.cullTarget = this.createCullTarget();
|
|
32
|
+
this.cullPixels = new Uint8Array(4 * this.cullTarget.width * this.cullTarget.height);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setSide(side){
|
|
36
|
+
this.cullMaterial.side = side;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createCullTarget() {
|
|
40
|
+
const target = new THREE.WebGLRenderTarget(Math.floor(window.innerWidth * 0.05), Math.floor(window.innerHeight * 0.05));
|
|
41
|
+
target.texture.format = THREE.RGBAFormat;
|
|
42
|
+
target.texture.encoding = THREE.LinearEncoding;
|
|
43
|
+
target.texture.minFilter = THREE.NearestFilter;
|
|
44
|
+
target.texture.magFilter = THREE.NearestFilter;
|
|
45
|
+
target.texture.generateMipmaps = false;
|
|
46
|
+
target.stencilBuffer = false;
|
|
47
|
+
target.depthBuffer = true;
|
|
48
|
+
target.depthTexture = new THREE.DepthTexture();
|
|
49
|
+
target.depthTexture.format = THREE.DepthFormat;
|
|
50
|
+
target.depthTexture.type = THREE.UnsignedShortType;
|
|
51
|
+
return target;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
update(scene, renderer, camera) {
|
|
55
|
+
let tempRenderTarget = renderer.getRenderTarget();
|
|
56
|
+
let tempOverrideMaterial = scene.overrideMaterial;
|
|
57
|
+
|
|
58
|
+
scene.overrideMaterial = this.cullMaterial;
|
|
59
|
+
renderer.setRenderTarget(this.cullTarget);
|
|
60
|
+
renderer.render(scene, camera);
|
|
61
|
+
|
|
62
|
+
scene.overrideMaterial = tempOverrideMaterial;
|
|
63
|
+
renderer.setRenderTarget(tempRenderTarget);
|
|
64
|
+
|
|
65
|
+
renderer.readRenderTargetPixels(this.cullTarget, 0, 0, this.cullTarget.width, this.cullTarget.height, this.cullPixels);
|
|
66
|
+
this.cullMap = [];
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < this.cullPixels.length; i += 4) {
|
|
69
|
+
const c = clamp(this.cullPixels[i], 0, 255) << 16 ^ clamp(this.cullPixels[i + 1], 0, 255) << 8 ^ clamp(this.cullPixels[i + 2], 0, 255) << 0;
|
|
70
|
+
this.cullMap[c] = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
hasID(id) {
|
|
76
|
+
return this.cullMap[id];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export { OcclusionCullingService };
|
|
@@ -6,6 +6,7 @@ const ready = [];
|
|
|
6
6
|
const downloads = [];
|
|
7
7
|
const nextReady = [];
|
|
8
8
|
const nextDownloads = [];
|
|
9
|
+
let concurentDownloads = 0;
|
|
9
10
|
|
|
10
11
|
function scheduleDownload(f) {
|
|
11
12
|
downloads.unshift(f);
|
|
@@ -13,13 +14,18 @@ function scheduleDownload(f) {
|
|
|
13
14
|
function download() {
|
|
14
15
|
if (nextDownloads.length == 0) {
|
|
15
16
|
getNextDownloads();
|
|
16
|
-
if (nextDownloads.length == 0) return
|
|
17
|
+
if (nextDownloads.length == 0) return;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
nextDownload.
|
|
19
|
+
while (nextDownloads.length > 0 && concurentDownloads < 500) {
|
|
20
|
+
const nextDownload = nextDownloads.shift();
|
|
21
|
+
if (!!nextDownload && nextDownload.shouldDoDownload()) {
|
|
22
|
+
nextDownload.doDownload();
|
|
23
|
+
}
|
|
21
24
|
}
|
|
22
|
-
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
return;
|
|
23
29
|
}
|
|
24
30
|
function meshReceived(cache, register, key, distanceFunction, getSiblings, level, uuid) {
|
|
25
31
|
ready.unshift([cache, register, key, distanceFunction, getSiblings, level, uuid]);
|
|
@@ -56,11 +62,11 @@ function getNextDownloads() {
|
|
|
56
62
|
downloads.splice(i, 1);
|
|
57
63
|
continue;
|
|
58
64
|
}
|
|
59
|
-
if(!downloads[i].distanceFunction){ // if no distance function, must be a json, give absolute priority!
|
|
65
|
+
if (!downloads[i].distanceFunction) { // if no distance function, must be a json, give absolute priority!
|
|
60
66
|
nextDownloads.push(downloads.splice(i, 1)[0]);
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
|
-
if(nextDownloads.length>0) return;
|
|
69
|
+
if (nextDownloads.length > 0) return;
|
|
64
70
|
for (let i = downloads.length - 1; i >= 0; i--) {
|
|
65
71
|
const dist = downloads[i].distanceFunction();
|
|
66
72
|
if (dist < smallestDistance) {
|
|
@@ -88,12 +94,12 @@ function getNextReady() {
|
|
|
88
94
|
let smallestDistance = Number.MAX_VALUE;
|
|
89
95
|
let closest = -1;
|
|
90
96
|
for (let i = ready.length - 1; i >= 0; i--) {
|
|
91
|
-
|
|
92
|
-
if(!ready[i][3]){// if no distance function, must be a json, give absolute priority!
|
|
93
|
-
nextReady.push(ready.splice(i,1)[0]);
|
|
97
|
+
|
|
98
|
+
if (!ready[i][3]) {// if no distance function, must be a json, give absolute priority!
|
|
99
|
+
nextReady.push(ready.splice(i, 1)[0]);
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
|
-
if(nextReady.length>0) return;
|
|
102
|
+
if (nextReady.length > 0) return;
|
|
97
103
|
for (let i = ready.length - 1; i >= 0; i--) {
|
|
98
104
|
const dist = ready[i][3]();
|
|
99
105
|
if (dist < smallestDistance) {
|
|
@@ -116,22 +122,23 @@ function getNextReady() {
|
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
|
-
setIntervalAsync(()=>{
|
|
120
|
-
|
|
125
|
+
setIntervalAsync(() => {
|
|
126
|
+
download();
|
|
127
|
+
/* const start = Date.now();
|
|
121
128
|
let uploaded = 0;
|
|
122
129
|
do{
|
|
123
130
|
uploaded = download();
|
|
124
|
-
}while(uploaded > 0 && (Date.now() - start)<= 2 )
|
|
125
|
-
|
|
126
|
-
},10);
|
|
127
|
-
setIntervalAsync(()=>{
|
|
131
|
+
}while(uploaded > 0 && (Date.now() - start)<= 2 ) */
|
|
132
|
+
|
|
133
|
+
}, 10);
|
|
134
|
+
setIntervalAsync(() => {
|
|
128
135
|
const start = Date.now();
|
|
129
136
|
let loaded = 0;
|
|
130
|
-
do{
|
|
137
|
+
do {
|
|
131
138
|
loaded = loadBatch();
|
|
132
|
-
}while(loaded > 0 && (Date.now() - start)<=
|
|
133
|
-
|
|
134
|
-
},10);
|
|
139
|
+
} while (loaded > 0 && (Date.now() - start) <= 1)
|
|
140
|
+
|
|
141
|
+
}, 10);
|
|
135
142
|
|
|
136
143
|
class TileLoader {
|
|
137
144
|
constructor(meshCallback, maxCachedItems) {
|
|
@@ -141,7 +148,7 @@ class TileLoader {
|
|
|
141
148
|
this.register = {};
|
|
142
149
|
}
|
|
143
150
|
|
|
144
|
-
get(tileIdentifier, path, callback, distanceFunction, getSiblings, level
|
|
151
|
+
get(tileIdentifier, path, callback, distanceFunction, getSiblings, level) {
|
|
145
152
|
const self = this;
|
|
146
153
|
const key = simplifyPath(path);
|
|
147
154
|
|
|
@@ -160,12 +167,14 @@ class TileLoader {
|
|
|
160
167
|
|
|
161
168
|
const cachedObject = self.cache.get(key);
|
|
162
169
|
if (!!cachedObject) {
|
|
163
|
-
meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level,
|
|
170
|
+
meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, tileIdentifier);
|
|
164
171
|
} else if (Object.keys(self.register[key]).length == 1) {
|
|
165
172
|
let downloadFunction;
|
|
166
173
|
if (path.includes(".b3dm")) {
|
|
167
174
|
downloadFunction = () => {
|
|
175
|
+
concurentDownloads++;
|
|
168
176
|
fetch(path).then(result => {
|
|
177
|
+
concurentDownloads--;
|
|
169
178
|
if (!result.ok) {
|
|
170
179
|
console.error("could not load tile with path : " + path)
|
|
171
180
|
throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
@@ -173,14 +182,16 @@ class TileLoader {
|
|
|
173
182
|
result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
|
|
174
183
|
self.cache.put(key, mesh);
|
|
175
184
|
self.checkSize();
|
|
176
|
-
meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level,
|
|
185
|
+
meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, tileIdentifier);
|
|
177
186
|
});
|
|
178
187
|
|
|
179
188
|
});
|
|
180
189
|
}
|
|
181
|
-
}else if (path.includes(".json")) {
|
|
190
|
+
} else if (path.includes(".json")) {
|
|
182
191
|
downloadFunction = () => {
|
|
192
|
+
concurentDownloads++;
|
|
183
193
|
fetch(path).then(result => {
|
|
194
|
+
concurentDownloads--;
|
|
184
195
|
if (!result.ok) {
|
|
185
196
|
console.error("could not load tile with path : " + path)
|
|
186
197
|
throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
|
|
@@ -201,7 +212,7 @@ class TileLoader {
|
|
|
201
212
|
"distanceFunction": distanceFunction,
|
|
202
213
|
"getSiblings": getSiblings,
|
|
203
214
|
"level": level,
|
|
204
|
-
"uuid":
|
|
215
|
+
"uuid": tileIdentifier
|
|
205
216
|
})
|
|
206
217
|
}
|
|
207
218
|
}
|
package/manifest.json
DELETED