@jdultra/threedtiles 3.1.4 → 3.2.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 CHANGED
@@ -1,10 +1,12 @@
1
- # 3DTilesViewer
1
+ # threedtiles
2
2
 
3
3
  3DTiles viewer for three.js
4
4
 
5
- demo : https://ebeaufay.github.io/ThreedTilesViewer.github.io/
5
+ Photogrametry : https://ebeaufay.github.io/ThreedTilesViewer.github.io/
6
6
 
7
- Currently, the library is limmited to B3DM files.
7
+ IFC : https://storage.googleapis.com/jdultra.com/ifc/index.html
8
+
9
+ Occlusion culling : https://storage.googleapis.com/jdultra.com/occlusionCulling/index.html
8
10
 
9
11
  Adding a tileset to a scene is as easy as :
10
12
 
@@ -27,6 +29,8 @@ setInterval(function () {
27
29
  }, 200);
28
30
  ```
29
31
 
32
+ Currently, the library is limmited to B3DM files.
33
+
30
34
  ## Features
31
35
 
32
36
  - Handles nested tileset.json files which are loaded on the fly (a tileset.json may point to another tileset.json file as its child).
@@ -114,6 +118,11 @@ ogc3DTile.rotateOnAxis(new THREE.Vector3(1,0,0), -Math.PI*0.5);
114
118
  ...
115
119
  ```
116
120
 
121
+ ### 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.
125
+
117
126
  # Displaying meshes on a globe
118
127
  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.
119
128
 
package/index.html CHANGED
@@ -4,11 +4,53 @@
4
4
  <head>
5
5
  <meta charset="utf-8" />
6
6
  <title>Three 3DTiles viewer sample</title>
7
- <link rel="manifest" href="manifest.json">
7
+ <style>
8
+ .slidecontainer {
9
+ width: 100%;
10
+ }
11
+
12
+ .slider {
13
+ -webkit-appearance: none;
14
+ width: 100%;
15
+ height: 15px;
16
+ border-radius: 5px;
17
+ background: #d3d3d3;
18
+ outline: none;
19
+ opacity: 0.7;
20
+ -webkit-transition: .2s;
21
+ transition: opacity .2s;
22
+ }
23
+
24
+ .slider:hover {
25
+ opacity: 1;
26
+ }
27
+
28
+ .slider::-webkit-slider-thumb {
29
+ -webkit-appearance: none;
30
+ appearance: none;
31
+ width: 25px;
32
+ height: 25px;
33
+ border-radius: 50%;
34
+ background: #0439aa;
35
+ cursor: pointer;
36
+ }
37
+
38
+ .slider::-moz-range-thumb {
39
+ width: 25px;
40
+ height: 25px;
41
+ border-radius: 50%;
42
+ background: #04AA6D;
43
+ cursor: pointer;
44
+ }
45
+ </style>
8
46
  </head>
9
47
 
10
48
  <body>
11
49
  <div id="screen"></div>
50
+ <div style="position: absolute; top: 1%; z-index: 100; right:1%; ">
51
+ <input type="range" min="0.1" max="2" value="1.0", step="0.001" class="slider" id="lodMultiplier" >
52
+ <p style="color: #0439aa;">LOD multiplier: <span id="multiplierValue"></span></p>
53
+ </div>
12
54
  <div style="position: absolute; bottom: 1%; z-index: 100;">
13
55
  <a href="https://openheritage3d.org/project.php?id=taz6-n215">ORIGINAL MODEL</a>
14
56
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jdultra/threedtiles",
3
- "version": "3.1.4",
3
+ "version": "3.2.1",
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.131.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
- legacyGLTFLoader.parse(gltfBuffer, model => {
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
@@ -11,6 +11,7 @@ const scene = initScene();
11
11
  const domContainer = initDomContainer("screen");
12
12
  const camera = initCamera();
13
13
  const ogc3DTiles = initTileset(scene);
14
+ initLODMultiplierSlider(ogc3DTiles)
14
15
  const controller = initController(camera, domContainer);
15
16
 
16
17
  const stats = initStats(domContainer);
@@ -20,8 +21,12 @@ animate();
20
21
 
21
22
  function initScene() {
22
23
  const scene = new THREE.Scene();
23
- scene.background = new THREE.Color(0x000000);
24
- scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
24
+ scene.background = new THREE.Color(0xaaffcc);
25
+ scene.add(new THREE.AmbientLight(0xFFFFFF, 0.2));
26
+ const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
27
+ directionalLight.position.set(100,100,100)
28
+ directionalLight.lookAt(-1,-1,-1)
29
+ scene.add( directionalLight );
25
30
  return scene;
26
31
  }
27
32
 
@@ -68,8 +73,8 @@ function initStats(dom) {
68
73
 
69
74
 
70
75
  function initCamera() {
71
- const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1, 10000);
72
- camera.position.set(10, 10, 10);
76
+ const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 0.1, 1000);
77
+ camera.position.set(-10, 5, 20);
73
78
 
74
79
  return camera;
75
80
  }
@@ -77,42 +82,45 @@ function initCamera() {
77
82
  function initTileset(scene) {
78
83
 
79
84
  const ogc3DTile = new OGC3DTile({
80
- url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tiledWithSkirts/tileset.json",
81
- geometricErrorMultiplier: 1.0,
82
- loadOutsideView: true,
85
+ url: "https://storage.googleapis.com/ogc-3d-tiles/house3/tileset.json",
86
+ //url: "http://localhost:8080/tileset.json",
87
+ geometricErrorMultiplier: 1,
88
+ loadOutsideView: false,
83
89
  tileLoader: new TileLoader(mesh => {
84
90
  //// Insert code to be called on every newly decoded mesh e.g.:
85
91
  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)+")")})
86
93
  mesh.material.side = THREE.DoubleSide;
87
94
  }, 1000)
88
95
  });
89
-
90
96
 
97
+
98
+
91
99
  //// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
92
- //ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -10)
93
- //ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
94
- //ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
95
- //ogc3DTile.scale.set(0.0001,0.0001,0.0001);
96
- //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
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
97
105
  // ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
98
106
  // ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
99
107
  // ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
100
108
  //// 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
101
109
 
102
-
103
110
 
104
- var interval ;
111
+
112
+ var interval;
105
113
  document.addEventListener('keyup', (e) => {
106
114
  console.log(camera.position)
107
- if(!e.key || e.key !== "p") return;
108
- if(!!interval){
115
+ if (!e.key || e.key !== "p") return;
116
+ if (!!interval) {
109
117
  clearInterval(interval);
110
118
  interval = null;
111
- }else{
119
+ } else {
112
120
  startInterval();
113
121
  }
114
122
  });
115
- function startInterval(){
123
+ function startInterval() {
116
124
  interval = setIntervalAsync(function () {
117
125
  ogc3DTile.update(camera);
118
126
  }, 20);
@@ -123,10 +131,21 @@ function initTileset(scene) {
123
131
  return ogc3DTile;
124
132
  }
125
133
 
134
+ function initLODMultiplierSlider(tileset) {
135
+ var slider = document.getElementById("lodMultiplier");
136
+ var output = document.getElementById("multiplierValue");
137
+ output.innerHTML = slider.value;
138
+
139
+ slider.oninput = () => {
140
+ tileset.setGeometricErrorMultiplier(slider.value)
141
+ output.innerHTML = slider.value;
142
+ }
143
+ }
144
+
126
145
  function initController(camera, dom) {
127
146
  const controller = new OrbitControls(camera, dom);
128
147
 
129
- controller.target.set(-11.50895,0.058452500000001, 3.1369285);
148
+ controller.target.set(0, 0, 0);
130
149
  controller.minDistance = 1;
131
150
  controller.maxDistance = 5000;
132
151
  controller.update();
@@ -2,9 +2,7 @@ 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 { setIntervalAsync } from 'set-interval-async/dynamic';
6
- // import { clearIntervalAsync } from 'set-interval-async';
7
- const path = require('path');
5
+ import * as path from "path-browserify"
8
6
 
9
7
  const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1));
10
8
 
@@ -25,7 +23,8 @@ class OGC3DTile extends THREE.Object3D {
25
23
  * tileLoader : TileLoader,
26
24
  * meshCallback: function,
27
25
  * cameraOnLoad: camera,
28
- * parentTile: OGC3DTile
26
+ * parentTile: OGC3DTile,
27
+ * onLoadCallback: function
29
28
  * } properties
30
29
  */
31
30
  constructor(properties) {
@@ -66,13 +65,17 @@ class OGC3DTile extends THREE.Object3D {
66
65
 
67
66
  if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
68
67
  self.setup(properties);
68
+ if (properties.onLoadCallback) properties.onLoadCallback(self);
69
69
  } else if (properties.url) { // If only the url to the tileset.json is provided
70
70
  self.controller = new AbortController();
71
71
  fetch(properties.url, { signal: self.controller.signal }).then(result => {
72
72
  if (!result.ok) {
73
73
  throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
74
74
  }
75
- result.json().then(json => self.setup({ rootPath: path.dirname(properties.url), json: json }))
75
+ result.json().then(json => {
76
+ self.setup({ rootPath: path.dirname(properties.url), json: json });
77
+ if (properties.onLoadCallback) properties.onLoadCallback(self);
78
+ });
76
79
  });
77
80
  }
78
81
  }
@@ -169,7 +172,7 @@ class OGC3DTile extends THREE.Object3D {
169
172
  self.meshContent = mesh;
170
173
  }, !self.cameraOnLoad ? () => 0 : () => {
171
174
  return self.calculateDistanceToCamera(self.cameraOnLoad);
172
- }, () => self.getSiblings(), self.level, self.uuid);
175
+ }, () => self.getSiblings(), self.level);
173
176
  } else if (url.includes(".json")) {
174
177
  self.tileLoader.get(this.uuid, url, json => {
175
178
  if (!!self.deleted) return;
@@ -180,24 +183,6 @@ class OGC3DTile extends THREE.Object3D {
180
183
  self.hasUnloadedJSONContent = false;
181
184
  });
182
185
 
183
- /* self.controller = new AbortController();
184
- setTimeout(() => {
185
- fetch(url, { signal: self.controller.signal }).then(result => {
186
- if (!result.ok) {
187
- throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
188
- }
189
- result.json().then(json => {
190
- // when json content is downloaded, it is inserted into this tile's original JSON as a child
191
- // and the content object is deleted from the original JSON
192
- if (!self.json.children) self.json.children = [];
193
- json.rootPath = path.dirname(url);
194
- self.json.children.push(json);
195
- delete self.json.content;
196
- self.hasUnloadedJSONContent = false;
197
- }).catch(error => { });
198
- }).catch(error => { });
199
- }, 0); */
200
-
201
186
  }
202
187
 
203
188
  }
@@ -316,10 +301,9 @@ class OGC3DTile extends THREE.Object3D {
316
301
  return;
317
302
  }
318
303
  if (metric >= self.geometricError) {
319
- if (self.isReady()) {
320
- self.disposeChildren();
321
- return;
322
- }
304
+ self.disposeChildren();
305
+ updateNodeVisibility();
306
+ return;
323
307
  }
324
308
 
325
309
  }
@@ -453,7 +437,7 @@ class OGC3DTile extends THREE.Object3D {
453
437
  return 0;
454
438
  }
455
439
  const scale = this.matrixWorld.getMaxScaleOnAxis();
456
- return ((distance / 100) / this.geometricErrorMultiplier) / scale;
440
+ return ((distance / 100) / this.geometricErrorMultiplier) * scale;
457
441
  } else if (this.boundingVolume instanceof THREE.Box3) {
458
442
  // Region
459
443
  // Region not supported
@@ -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 0;
17
+ if (nextDownloads.length == 0) return;
17
18
  }
18
- const nextDownload = nextDownloads.shift();
19
- if (!!nextDownload && nextDownload.shouldDoDownload()) {
20
- nextDownload.doDownload();
19
+ while (nextDownloads.length > 0 && concurentDownloads < 500) {
20
+ const nextDownload = nextDownloads.shift();
21
+ if (!!nextDownload && nextDownload.shouldDoDownload()) {
22
+ nextDownload.doDownload();
23
+ }
21
24
  }
22
- return 1;
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
- const start = Date.now();
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)<= 2 )
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, uuid) {
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, uuid);
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, uuid);
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": uuid
215
+ "uuid": tileIdentifier
205
216
  })
206
217
  }
207
218
  }
package/manifest.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "display": "fullscreen",
3
- "orientation": "landscape"
4
- }