@jdultra/threedtiles 3.2.1 → 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 CHANGED
@@ -6,7 +6,7 @@ Photogrametry : https://ebeaufay.github.io/ThreedTilesViewer.github.io/
6
6
 
7
7
  IFC : https://storage.googleapis.com/jdultra.com/ifc/index.html
8
8
 
9
- Occlusion culling : https://storage.googleapis.com/jdultra.com/occlusionCulling/index.html
9
+ Occlusion culling : [https://storage.googleapis.com/jdultra.com/occlusionCulling/index.html](https://storage.googleapis.com/www.jdultra.com/occlusion/index.html)
10
10
 
11
11
  Adding a tileset to a scene is as easy as :
12
12
 
@@ -39,6 +39,7 @@ Currently, the library is limmited to B3DM files.
39
39
  - Optionally load low detail tiles outside of view frustum for correct shadows and basic mesh present when the camera moves quickly.
40
40
  - Share a cache between tileset instances
41
41
  - Optimal tile load order
42
+ - Occlusion culling (demo)
42
43
 
43
44
  ### geometric Error Multiplier
44
45
  The geometric error multiplier allows you to multiply the geometric error by a factor.
@@ -116,12 +117,53 @@ const ogc3DTile = new OGC3DTile({
116
117
  ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -450);
117
118
  ogc3DTile.rotateOnAxis(new THREE.Vector3(1,0,0), -Math.PI*0.5);
118
119
  ...
119
- ```
120
+
120
121
 
121
122
  ### Occlusion culling
122
- Occlusion culling is curently only available as a demo.
123
- The reason is that it requires a specific render pass hence the setup is a little more complex.
124
- I can expose this feature upon request.
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
+ });
166
+ ```
125
167
 
126
168
  # Displaying meshes on a globe
127
169
  I'm working on this project in parallel https://github.com/ebeaufay/UltraGlobe which allows displaying a globe with multi resolution imagery, elevation and 3DTiles.
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://openheritage3d.org/project.php?id=taz6-n215">ORIGINAL MODEL</a>
55
+ <a href="https://skfb.ly/6UoNJ">ORIGINAL MODEL</a>
56
56
  </div>
57
57
  </body>
58
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jdultra/threedtiles",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "An OGC 3DTiles viewer for Three.js",
5
5
  "main": "tileset.js",
6
6
  "scripts": {
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,10 +20,12 @@ 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
30
  scene.add(new THREE.AmbientLight(0xFFFFFF, 0.2));
26
31
  const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
@@ -41,11 +46,9 @@ function initDomContainer(divID) {
41
46
 
42
47
  function initRenderer(camera, dom) {
43
48
 
44
- const renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true });
45
- renderer.antialias = true;
49
+ const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
46
50
  renderer.setPixelRatio(window.devicePixelRatio);
47
51
  renderer.setSize(dom.offsetWidth, dom.offsetHeight);
48
-
49
52
  renderer.outputEncoding = THREE.sRGBEncoding;
50
53
  renderer.autoClear = false;
51
54
 
@@ -75,36 +78,32 @@ function initStats(dom) {
75
78
  function initCamera() {
76
79
  const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 0.1, 1000);
77
80
  camera.position.set(-10, 5, 20);
78
-
81
+ camera.matrixAutoUpdate = true;
79
82
  return camera;
80
83
  }
81
84
 
82
85
  function initTileset(scene) {
83
86
 
84
87
  const ogc3DTile = new OGC3DTile({
85
- url: "https://storage.googleapis.com/ogc-3d-tiles/house3/tileset.json",
86
- //url: "http://localhost:8080/tileset.json",
87
- geometricErrorMultiplier: 1,
88
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tiledWithSkirts/tileset.json",
89
+ //url: "http://localhost:8081/tileset.json",
90
+ geometricErrorMultiplier: 0.5,
88
91
  loadOutsideView: false,
89
92
  tileLoader: new TileLoader(mesh => {
90
93
  //// Insert code to be called on every newly decoded mesh e.g.:
91
94
  mesh.material.wireframe = false;
92
- //mesh.material = new THREE.MeshBasicMaterial({color:new THREE.Color("rgb("+Math.floor(Math.random()*256)+", "+Math.floor(Math.random()*256)+", "+Math.floor(Math.random()*256)+")")})
93
95
  mesh.material.side = THREE.DoubleSide;
94
- }, 1000)
96
+ }, 1000),
97
+ occlusionCullingService: occlusionCullingService
95
98
  });
96
99
 
97
100
 
98
101
 
99
102
  //// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
100
- // ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -2177749.59059337)
101
- // ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 4388730.67973434)
102
- // ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), 4070064.60934734)
103
- //ogc3DTile.scale.set(0.001,0.001,0.001);
104
- ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
105
- // ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
106
- // ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
107
- // ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
103
+ //-172683.125,301451.125,1367762.21875
104
+ //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
105
+
106
+
108
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
109
108
 
110
109
 
@@ -155,11 +154,11 @@ function initController(camera, dom) {
155
154
 
156
155
  function animate() {
157
156
  requestAnimationFrame(animate);
158
-
159
- camera.updateMatrixWorld();
160
157
  renderer.render(scene, camera);
158
+ occlusionCullingService.update(scene, renderer, camera)
161
159
  stats.update();
162
160
 
161
+
163
162
  }
164
163
 
165
164
 
@@ -3,6 +3,7 @@ import { OBB } from "../geometry/obb";
3
3
  import { TileLoader } from "./TileLoader";
4
4
  import { v4 as uuidv4 } from "uuid";
5
5
  import * as path from "path-browserify"
6
+ import { clamp } from "three/src/math/MathUtils";
6
7
 
7
8
  const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1));
8
9
 
@@ -24,12 +25,19 @@ class OGC3DTile extends THREE.Object3D {
24
25
  * meshCallback: function,
25
26
  * cameraOnLoad: camera,
26
27
  * parentTile: OGC3DTile,
27
- * onLoadCallback: function
28
+ * onLoadCallback: function,
29
+ * occlusionCullingService: OcclusionCullingService,
30
+ * static: Boolean
28
31
  * } properties
29
32
  */
30
33
  constructor(properties) {
31
34
  super();
32
35
  const self = this;
36
+
37
+ if(properties.static){
38
+ this.matrixAutoUpdate = false;
39
+ }
40
+
33
41
  this.uuid = uuidv4();
34
42
  if (!!properties.tileLoader) {
35
43
  this.tileLoader = properties.tileLoader;
@@ -47,6 +55,12 @@ class OGC3DTile extends THREE.Object3D {
47
55
  this.loadOutsideView = properties.loadOutsideView;
48
56
  this.cameraOnLoad = properties.cameraOnLoad;
49
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
+ }
50
64
 
51
65
  // declare properties specific to the tile for clarity
52
66
  this.childrenTiles = [];
@@ -63,6 +77,8 @@ class OGC3DTile extends THREE.Object3D {
63
77
  this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
64
78
  this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
65
79
 
80
+ this.layers.disable(0);
81
+
66
82
  if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
67
83
  self.setup(properties);
68
84
  if (properties.onLoadCallback) properties.onLoadCallback(self);
@@ -165,10 +181,20 @@ class OGC3DTile extends THREE.Object3D {
165
181
  if (!!self.deleted) return;
166
182
  mesh.traverse((o) => {
167
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
+ }
168
192
  o.material.visible = false;
169
193
  }
170
194
  });
195
+
171
196
  self.add(mesh);
197
+ self.updateWorldMatrix(false, true);
172
198
  self.meshContent = mesh;
173
199
  }, !self.cameraOnLoad ? () => 0 : () => {
174
200
  return self.calculateDistanceToCamera(self.cameraOnLoad);
@@ -192,6 +218,7 @@ class OGC3DTile extends THREE.Object3D {
192
218
  dispose() {
193
219
 
194
220
  const self = this;
221
+ self.childrenTiles.forEach(tile => tile.dispose());
195
222
  self.deleted = true;
196
223
  this.traverse(function (element) {
197
224
  if (!!element.contentURL) {
@@ -224,19 +251,24 @@ class OGC3DTile extends THREE.Object3D {
224
251
  _update(camera, frustum) {
225
252
  const self = this;
226
253
 
227
- self.childrenTiles.forEach(child => child._update(camera, frustum));
254
+ const visibilityBeforeUpdate = self.materialVisibility;
255
+
228
256
  if (!!self.boundingVolume && !!self.geometricError) {
229
257
  self.metric = self.calculateUpdateMetric(camera, frustum);
230
258
  }
259
+ self.childrenTiles.forEach(child => child._update(camera, frustum));
231
260
 
232
261
  updateNodeVisibility(self.metric);
233
262
  updateTree(self.metric);
234
- trimTree(self.metric);
263
+ trimTree(self.metric, visibilityBeforeUpdate);
235
264
 
236
265
 
237
266
  function updateTree(metric) {
238
267
  // If this tile does not have mesh content but it has children
239
268
  if (metric < 0 && self.hasMeshContent) return;
269
+ if (self.occlusionCullingService && self.hasMeshContent && !self.occlusionCullingService.hasID(self.colorID)) {
270
+ return;
271
+ }
240
272
  if (!self.hasMeshContent || (metric < self.geometricError && !!self.meshContent)) {
241
273
  if (!!self.json && !!self.json.children && self.childrenTiles.length != self.json.children.length) {
242
274
  loadJsonChildren();
@@ -276,7 +308,7 @@ class OGC3DTile extends THREE.Object3D {
276
308
  self.changeContentVisibility(true);
277
309
  } else if (metric < self.geometricError) { // Ideal LOD is past this one
278
310
  // if children are visible and have been displayed, can be hidden
279
- var allChildrenReady = true;
311
+ let allChildrenReady = true;
280
312
  self.childrenTiles.every(child => {
281
313
 
282
314
  if (!child.isReady()) {
@@ -287,17 +319,26 @@ class OGC3DTile extends THREE.Object3D {
287
319
  });
288
320
  if (allChildrenReady) {
289
321
  self.changeContentVisibility(false);
290
- } else {
291
- //self.changeContentVisibility(true);
292
-
293
322
  }
294
323
  }
295
324
  }
296
325
 
297
- function trimTree(metric) {
326
+ function trimTree(metric, visibilityBeforeUpdate) {
298
327
  if (!self.hasMeshContent) return;
299
- if (metric < 0) { // outside frustum
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
+
300
340
  self.disposeChildren();
341
+ updateNodeVisibility(metric);
301
342
  return;
302
343
  }
303
344
  if (metric >= self.geometricError) {
@@ -321,7 +362,8 @@ class OGC3DTile extends THREE.Object3D {
321
362
  loadOutsideView: self.loadOutsideView,
322
363
  level: self.level + 1,
323
364
  tileLoader: self.tileLoader,
324
- cameraOnLoad: camera
365
+ cameraOnLoad: camera,
366
+ occlusionCullingService:self.occlusionCullingService
325
367
  });
326
368
  self.childrenTiles.push(childTile);
327
369
  self.add(childTile);
@@ -330,6 +372,36 @@ class OGC3DTile extends THREE.Object3D {
330
372
 
331
373
  }
332
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
+
333
405
  /**
334
406
  * Node is ready if it is outside frustum, if it was drawn at least once or if all it's children are ready
335
407
  * @returns true if ready
@@ -382,6 +454,13 @@ class OGC3DTile extends THREE.Object3D {
382
454
 
383
455
  changeContentVisibility(visibility) {
384
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
+ }
385
464
  if (self.materialVisibility == visibility) {
386
465
  return;
387
466
  }
@@ -437,7 +516,7 @@ class OGC3DTile extends THREE.Object3D {
437
516
  return 0;
438
517
  }
439
518
  const scale = this.matrixWorld.getMaxScaleOnAxis();
440
- return ((distance / 100) / this.geometricErrorMultiplier) * scale;
519
+ return (((distance / Math.pow(scale, 2)) / 100) / this.geometricErrorMultiplier);
441
520
  } else if (this.boundingVolume instanceof THREE.Box3) {
442
521
  // Region
443
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 };