@jdultra/threedtiles 3.2.0 → 3.3.1
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 +59 -5
- package/index.html +1 -1
- package/package.json +1 -1
- package/src/index.js +42 -29
- package/src/tileset/OGC3DTile.js +113 -41
- package/src/tileset/OcclusionCullingService.js +79 -0
- package/src/tileset/TileLoader.js +146 -128
- package/src/draco/draco_decoder.js +0 -48
- package/src/draco/draco_decoder.wasm +0 -0
- package/src/draco/draco_encoder.js +0 -33
- package/src/draco/draco_wasm_wrapper.js +0 -104
package/README.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
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
|
|
38
43
|
|
|
39
44
|
### geometric Error Multiplier
|
|
40
45
|
The geometric error multiplier allows you to multiply the geometric error by a factor.
|
|
@@ -55,8 +60,8 @@ A value of 1.0 is the default.
|
|
|
55
60
|
|
|
56
61
|
|
|
57
62
|
### load tiles outside of view
|
|
58
|
-
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.
|
|
59
|
-
Instead of this behaviour, you can force the lowest possible LODs to be loaded for tiles
|
|
63
|
+
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.
|
|
64
|
+
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 correctly.
|
|
60
65
|
|
|
61
66
|
```
|
|
62
67
|
const ogc3DTile = new OGC3DTile({
|
|
@@ -73,6 +78,7 @@ const ogc3DTile = new OGC3DTile({
|
|
|
73
78
|
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
|
|
74
79
|
meshCallback: mesh => {
|
|
75
80
|
mesh.material.wireframe = true;
|
|
81
|
+
mesh.material.side = THREE.DoubleSide;
|
|
76
82
|
}
|
|
77
83
|
});
|
|
78
84
|
```
|
|
@@ -111,7 +117,55 @@ const ogc3DTile = new OGC3DTile({
|
|
|
111
117
|
|
|
112
118
|
ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -450);
|
|
113
119
|
ogc3DTile.rotateOnAxis(new THREE.Vector3(1,0,0), -Math.PI*0.5);
|
|
114
|
-
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
### Occlusion culling
|
|
124
|
+
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.
|
|
125
|
+
|
|
126
|
+
A word of warning: activating occlusion culling causes an extra render-pass and as such, has an impact on frame-rate.
|
|
127
|
+
It will be most beneficial on interior scenes where most of the data is occluded by walls. All the tiles that don't need to be downloaded or drawn will balance out the cost of the extra render pass.
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
First, instantiate an OcclusionCullingService:
|
|
131
|
+
```
|
|
132
|
+
const occlusionCullingService = new OcclusionCullingService();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This service must be passed to every OGC3DTiles object like so:
|
|
136
|
+
```
|
|
137
|
+
const ogc3DTile = new OGC3DTile({
|
|
138
|
+
url: "path/to/tileset.json",
|
|
139
|
+
occlusionCullingService: occlusionCullingService
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Then, you must update the occlusionCullingService within your render loop:
|
|
144
|
+
```
|
|
145
|
+
function animate() {
|
|
146
|
+
requestAnimationFrame(animate);
|
|
147
|
+
renderer.render(scene, camera);
|
|
148
|
+
occlusionCullingService.update(scene, renderer, camera)
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Finally, if you are drawing the back-side of faces or both-sides (see Callback section), you'll need to specify it for the occlusion pass too. By default, THREE.FrontSide is used:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
const occlusionCullingService = new OcclusionCullingService();
|
|
156
|
+
occlusionCullingService.setSide(THREE.DoubleSide);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
### static tilesets (Performance tip)
|
|
161
|
+
When you know your tileset will be static, you can specify it in the OGC3DTile object constructor parameter.
|
|
162
|
+
This will skip recalculating the transformation matrix of every tile each frame and give a few extra frames per second.
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
const ogc3DTile = new OGC3DTile({
|
|
166
|
+
url: "path/to/tileset.json",
|
|
167
|
+
static: true
|
|
168
|
+
});
|
|
115
169
|
```
|
|
116
170
|
|
|
117
171
|
# Displaying meshes on a globe
|
|
@@ -121,5 +175,5 @@ I'm working on this project in parallel https://github.com/ebeaufay/UltraGlobe w
|
|
|
121
175
|
|
|
122
176
|
I also have code to convert meshes to 3DTiles with no limit to the size of the dataset relative to faces or textures.
|
|
123
177
|
It works for all types of meshes: photogrametry, BIM, colored or textured meshes with a single texture atlas or many individual textures.
|
|
124
|
-
I'm keeping the code private for now but
|
|
178
|
+
I'm keeping the code private for now but feel free to contact me about it.
|
|
125
179
|
Contact: emericbeaufays@gmail.com
|
package/index.html
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
<p style="color: #0439aa;">LOD multiplier: <span id="multiplierValue"></span></p>
|
|
53
53
|
</div>
|
|
54
54
|
<div style="position: absolute; bottom: 1%; z-index: 100;">
|
|
55
|
-
<a href="https://
|
|
55
|
+
<a href="https://skfb.ly/6UoNJ">ORIGINAL MODEL</a>
|
|
56
56
|
</div>
|
|
57
57
|
</body>
|
|
58
58
|
|
package/package.json
CHANGED
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,14 +20,19 @@ 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();
|
|
28
|
+
scene.matrixAutoUpdate = false;
|
|
24
29
|
scene.background = new THREE.Color(0xaaffcc);
|
|
25
|
-
scene.add(new THREE.AmbientLight(0xFFFFFF, 0.
|
|
26
|
-
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.
|
|
30
|
+
scene.add(new THREE.AmbientLight(0xFFFFFF, 0.8));
|
|
31
|
+
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.2 );
|
|
32
|
+
directionalLight.position.set(100,100,100)
|
|
33
|
+
directionalLight.lookAt(-1,-1,-1)
|
|
27
34
|
scene.add( directionalLight );
|
|
35
|
+
scene.autoUpdate = false;
|
|
28
36
|
return scene;
|
|
29
37
|
}
|
|
30
38
|
|
|
@@ -39,11 +47,9 @@ function initDomContainer(divID) {
|
|
|
39
47
|
|
|
40
48
|
function initRenderer(camera, dom) {
|
|
41
49
|
|
|
42
|
-
const renderer = new THREE.WebGLRenderer({ antialias: true,
|
|
43
|
-
renderer.antialias = true;
|
|
50
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
|
|
44
51
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
45
52
|
renderer.setSize(dom.offsetWidth, dom.offsetHeight);
|
|
46
|
-
|
|
47
53
|
renderer.outputEncoding = THREE.sRGBEncoding;
|
|
48
54
|
renderer.autoClear = false;
|
|
49
55
|
|
|
@@ -71,41 +77,46 @@ function initStats(dom) {
|
|
|
71
77
|
|
|
72
78
|
|
|
73
79
|
function initCamera() {
|
|
74
|
-
const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1,
|
|
75
|
-
camera.position.set(
|
|
76
|
-
|
|
80
|
+
const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 0.1, 1000);
|
|
81
|
+
camera.position.set(-10, 5, 20);
|
|
82
|
+
camera.matrixAutoUpdate = true;
|
|
77
83
|
return camera;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
function initTileset(scene) {
|
|
81
87
|
|
|
88
|
+
const tileLoader = new TileLoader(mesh => {
|
|
89
|
+
//// Insert code to be called on every newly decoded mesh e.g.:
|
|
90
|
+
mesh.material.wireframe = false;
|
|
91
|
+
mesh.material.side = THREE.DoubleSide;
|
|
92
|
+
}, 1000)
|
|
82
93
|
const ogc3DTile = new OGC3DTile({
|
|
83
94
|
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tiledWithSkirts/tileset.json",
|
|
84
95
|
//url: "http://localhost:8080/tileset.json",
|
|
85
|
-
geometricErrorMultiplier:
|
|
86
|
-
loadOutsideView:
|
|
87
|
-
tileLoader:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
geometricErrorMultiplier: 0.5,
|
|
97
|
+
loadOutsideView: false,
|
|
98
|
+
tileLoader: tileLoader,
|
|
99
|
+
occlusionCullingService: occlusionCullingService,
|
|
100
|
+
static: false
|
|
101
|
+
});
|
|
102
|
+
ogc3DTile.translateZ(200) // Z-UP to Y-UP
|
|
103
|
+
const ogc3DTile2 = new OGC3DTile({
|
|
104
|
+
url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tiledWithSkirts/tileset.json",
|
|
105
|
+
//url: "http://localhost:8080/tileset.json",
|
|
106
|
+
geometricErrorMultiplier: 0.5,
|
|
107
|
+
loadOutsideView: false,
|
|
108
|
+
//tileLoader: tileLoader,
|
|
109
|
+
occlusionCullingService: occlusionCullingService,
|
|
110
|
+
static: false
|
|
96
111
|
});
|
|
97
112
|
|
|
98
113
|
|
|
99
114
|
|
|
100
115
|
//// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
|
|
101
|
-
//ogc3DTile.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
//ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * 0.5) // Z-UP to Y-UP
|
|
106
|
-
// ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
|
|
107
|
-
// ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
|
|
108
|
-
// ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
|
|
116
|
+
//ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
|
|
117
|
+
//// If the OGC3DTile object is marked as "static" (constructorParameter), these operations will not work.
|
|
118
|
+
|
|
119
|
+
|
|
109
120
|
//// 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
|
|
110
121
|
|
|
111
122
|
|
|
@@ -124,11 +135,13 @@ function initTileset(scene) {
|
|
|
124
135
|
function startInterval() {
|
|
125
136
|
interval = setIntervalAsync(function () {
|
|
126
137
|
ogc3DTile.update(camera);
|
|
138
|
+
ogc3DTile2.update(camera);
|
|
127
139
|
}, 20);
|
|
128
140
|
}
|
|
129
141
|
startInterval();
|
|
130
142
|
|
|
131
143
|
scene.add(ogc3DTile)
|
|
144
|
+
scene.add(ogc3DTile2)
|
|
132
145
|
return ogc3DTile;
|
|
133
146
|
}
|
|
134
147
|
|
|
@@ -156,11 +169,11 @@ function initController(camera, dom) {
|
|
|
156
169
|
|
|
157
170
|
function animate() {
|
|
158
171
|
requestAnimationFrame(animate);
|
|
159
|
-
|
|
160
|
-
camera.updateMatrixWorld();
|
|
161
172
|
renderer.render(scene, camera);
|
|
173
|
+
occlusionCullingService.update(scene, renderer, camera)
|
|
162
174
|
stats.update();
|
|
163
175
|
|
|
176
|
+
|
|
164
177
|
}
|
|
165
178
|
|
|
166
179
|
|
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,15 @@ 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
|
+
|
|
35
37
|
this.uuid = uuidv4();
|
|
36
38
|
if (!!properties.tileLoader) {
|
|
37
39
|
this.tileLoader = properties.tileLoader;
|
|
@@ -41,15 +43,24 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
41
43
|
mesh.material.wireframe = false;
|
|
42
44
|
mesh.material.side = THREE.DoubleSide;
|
|
43
45
|
} : properties.meshCallback);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
}
|
|
47
|
+
// set properties general to the entire tileset
|
|
48
|
+
this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
|
|
49
|
+
|
|
48
50
|
this.meshCallback = properties.meshCallback;
|
|
49
51
|
this.loadOutsideView = properties.loadOutsideView;
|
|
50
52
|
this.cameraOnLoad = properties.cameraOnLoad;
|
|
51
53
|
this.parentTile = properties.parentTile;
|
|
52
|
-
|
|
54
|
+
this.occlusionCullingService = properties.occlusionCullingService;
|
|
55
|
+
this.static = properties.static;
|
|
56
|
+
if (this.occlusionCullingService) {
|
|
57
|
+
this.color = new THREE.Color();
|
|
58
|
+
this.color.setHex(Math.random() * 0xffffff);
|
|
59
|
+
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
|
+
}
|
|
61
|
+
if(this.static){
|
|
62
|
+
this.matrixAutoUpdate = false;
|
|
63
|
+
}
|
|
53
64
|
// declare properties specific to the tile for clarity
|
|
54
65
|
this.childrenTiles = [];
|
|
55
66
|
this.meshContent;
|
|
@@ -65,6 +76,8 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
65
76
|
this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
|
|
66
77
|
this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
|
|
67
78
|
|
|
79
|
+
this.layers.disable(0);
|
|
80
|
+
|
|
68
81
|
if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
|
|
69
82
|
self.setup(properties);
|
|
70
83
|
if (properties.onLoadCallback) properties.onLoadCallback(self);
|
|
@@ -167,10 +180,25 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
167
180
|
if (!!self.deleted) return;
|
|
168
181
|
mesh.traverse((o) => {
|
|
169
182
|
if (o.isMesh) {
|
|
183
|
+
o.layers.disable(0);
|
|
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
|
+
}
|
|
192
|
+
if(self.static){
|
|
193
|
+
o.matrixAutoUpdate = false;
|
|
194
|
+
}
|
|
170
195
|
o.material.visible = false;
|
|
171
196
|
}
|
|
172
197
|
});
|
|
198
|
+
|
|
173
199
|
self.add(mesh);
|
|
200
|
+
self.updateWorldMatrix(false, true);
|
|
201
|
+
// mesh.layers.disable(0);
|
|
174
202
|
self.meshContent = mesh;
|
|
175
203
|
}, !self.cameraOnLoad ? () => 0 : () => {
|
|
176
204
|
return self.calculateDistanceToCamera(self.cameraOnLoad);
|
|
@@ -185,24 +213,6 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
185
213
|
self.hasUnloadedJSONContent = false;
|
|
186
214
|
});
|
|
187
215
|
|
|
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
216
|
}
|
|
207
217
|
|
|
208
218
|
}
|
|
@@ -212,6 +222,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
212
222
|
dispose() {
|
|
213
223
|
|
|
214
224
|
const self = this;
|
|
225
|
+
self.childrenTiles.forEach(tile => tile.dispose());
|
|
215
226
|
self.deleted = true;
|
|
216
227
|
this.traverse(function (element) {
|
|
217
228
|
if (!!element.contentURL) {
|
|
@@ -244,19 +255,24 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
244
255
|
_update(camera, frustum) {
|
|
245
256
|
const self = this;
|
|
246
257
|
|
|
247
|
-
|
|
258
|
+
const visibilityBeforeUpdate = self.materialVisibility;
|
|
259
|
+
|
|
248
260
|
if (!!self.boundingVolume && !!self.geometricError) {
|
|
249
261
|
self.metric = self.calculateUpdateMetric(camera, frustum);
|
|
250
262
|
}
|
|
263
|
+
self.childrenTiles.forEach(child => child._update(camera, frustum));
|
|
251
264
|
|
|
252
265
|
updateNodeVisibility(self.metric);
|
|
253
266
|
updateTree(self.metric);
|
|
254
|
-
trimTree(self.metric);
|
|
267
|
+
trimTree(self.metric, visibilityBeforeUpdate);
|
|
255
268
|
|
|
256
269
|
|
|
257
270
|
function updateTree(metric) {
|
|
258
271
|
// If this tile does not have mesh content but it has children
|
|
259
272
|
if (metric < 0 && self.hasMeshContent) return;
|
|
273
|
+
if (self.occlusionCullingService && self.hasMeshContent && !self.occlusionCullingService.hasID(self.colorID)) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
260
276
|
if (!self.hasMeshContent || (metric < self.geometricError && !!self.meshContent)) {
|
|
261
277
|
if (!!self.json && !!self.json.children && self.childrenTiles.length != self.json.children.length) {
|
|
262
278
|
loadJsonChildren();
|
|
@@ -296,7 +312,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
296
312
|
self.changeContentVisibility(true);
|
|
297
313
|
} else if (metric < self.geometricError) { // Ideal LOD is past this one
|
|
298
314
|
// if children are visible and have been displayed, can be hidden
|
|
299
|
-
|
|
315
|
+
let allChildrenReady = true;
|
|
300
316
|
self.childrenTiles.every(child => {
|
|
301
317
|
|
|
302
318
|
if (!child.isReady()) {
|
|
@@ -307,24 +323,32 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
307
323
|
});
|
|
308
324
|
if (allChildrenReady) {
|
|
309
325
|
self.changeContentVisibility(false);
|
|
310
|
-
} else {
|
|
311
|
-
//self.changeContentVisibility(true);
|
|
312
|
-
|
|
313
326
|
}
|
|
314
327
|
}
|
|
315
328
|
}
|
|
316
329
|
|
|
317
|
-
function trimTree(metric) {
|
|
330
|
+
function trimTree(metric, visibilityBeforeUpdate) {
|
|
318
331
|
if (!self.hasMeshContent) return;
|
|
319
|
-
if (
|
|
332
|
+
if (!self.inFrustum) { // outside frustum
|
|
320
333
|
self.disposeChildren();
|
|
334
|
+
updateNodeVisibility(metric);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (self.occlusionCullingService &&
|
|
338
|
+
!visibilityBeforeUpdate &&
|
|
339
|
+
self.hasMeshContent &&
|
|
340
|
+
self.meshContent &&
|
|
341
|
+
self.meshesToDisplay == self.meshesDisplayed &&
|
|
342
|
+
self.areAllChildrenLoadedAndHidden()) {
|
|
343
|
+
|
|
344
|
+
self.disposeChildren();
|
|
345
|
+
updateNodeVisibility(metric);
|
|
321
346
|
return;
|
|
322
347
|
}
|
|
323
348
|
if (metric >= self.geometricError) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
349
|
+
self.disposeChildren();
|
|
350
|
+
updateNodeVisibility();
|
|
351
|
+
return;
|
|
328
352
|
}
|
|
329
353
|
|
|
330
354
|
}
|
|
@@ -342,7 +366,9 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
342
366
|
loadOutsideView: self.loadOutsideView,
|
|
343
367
|
level: self.level + 1,
|
|
344
368
|
tileLoader: self.tileLoader,
|
|
345
|
-
cameraOnLoad: camera
|
|
369
|
+
cameraOnLoad: camera,
|
|
370
|
+
occlusionCullingService:self.occlusionCullingService,
|
|
371
|
+
static: self.static
|
|
346
372
|
});
|
|
347
373
|
self.childrenTiles.push(childTile);
|
|
348
374
|
self.add(childTile);
|
|
@@ -351,6 +377,36 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
351
377
|
|
|
352
378
|
}
|
|
353
379
|
|
|
380
|
+
areAllChildrenLoadedAndHidden() {
|
|
381
|
+
let allLoadedAndHidden = true;
|
|
382
|
+
const self = this;
|
|
383
|
+
this.childrenTiles.every(child => {
|
|
384
|
+
if (child.hasMeshContent) {
|
|
385
|
+
if(child.childrenTiles.length>0){
|
|
386
|
+
allLoadedAndHidden = false;
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
if (!child.inFrustum ) {
|
|
390
|
+
return true;
|
|
391
|
+
};
|
|
392
|
+
if (!child.materialVisibility || child.meshesToDisplay != child.meshesDisplayed) {
|
|
393
|
+
allLoadedAndHidden = false;
|
|
394
|
+
return false;
|
|
395
|
+
} else if (self.occlusionCullingService.hasID(child.colorID)) {
|
|
396
|
+
allLoadedAndHidden = false;
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
if (!child.areAllChildrenLoadedAndHidden()) {
|
|
401
|
+
allLoadedAndHidden = false;
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
});
|
|
407
|
+
return allLoadedAndHidden;
|
|
408
|
+
}
|
|
409
|
+
|
|
354
410
|
/**
|
|
355
411
|
* Node is ready if it is outside frustum, if it was drawn at least once or if all it's children are ready
|
|
356
412
|
* @returns true if ready
|
|
@@ -403,6 +459,22 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
403
459
|
|
|
404
460
|
changeContentVisibility(visibility) {
|
|
405
461
|
const self = this;
|
|
462
|
+
if(self.hasMeshContent && self.meshContent){
|
|
463
|
+
if(visibility){
|
|
464
|
+
|
|
465
|
+
self.meshContent.traverse((o) => {
|
|
466
|
+
if (o.isMesh) {
|
|
467
|
+
o.layers.enable(0);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
}else{
|
|
471
|
+
self.meshContent.traverse((o) => {
|
|
472
|
+
if (o.isMesh) {
|
|
473
|
+
o.layers.disable(0);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
406
478
|
if (self.materialVisibility == visibility) {
|
|
407
479
|
return;
|
|
408
480
|
}
|
|
@@ -458,7 +530,7 @@ class OGC3DTile extends THREE.Object3D {
|
|
|
458
530
|
return 0;
|
|
459
531
|
}
|
|
460
532
|
const scale = this.matrixWorld.getMaxScaleOnAxis();
|
|
461
|
-
return ((distance / 100) / this.geometricErrorMultiplier)
|
|
533
|
+
return (((distance / Math.pow(scale, 2)) / 100) / this.geometricErrorMultiplier);
|
|
462
534
|
} else if (this.boundingVolume instanceof THREE.Box3) {
|
|
463
535
|
// Region
|
|
464
536
|
// 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 };
|