@jdultra/threedtiles 3.1.3 → 3.2.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
@@ -1,11 +1,9 @@
1
- # 3DTilesViewer
1
+ # threedtiles
2
2
 
3
3
  3DTiles viewer for three.js
4
4
 
5
5
  demo : https://ebeaufay.github.io/ThreedTilesViewer.github.io/
6
6
 
7
- Currently, the library is limmited to B3DM files.
8
-
9
7
  Adding a tileset to a scene is as easy as :
10
8
 
11
9
  ```
@@ -27,6 +25,8 @@ setInterval(function () {
27
25
  }, 200);
28
26
  ```
29
27
 
28
+ Currently, the library is limmited to B3DM files.
29
+
30
30
  ## Features
31
31
 
32
32
  - Handles nested tileset.json files which are loaded on the fly (a tileset.json may point to another tileset.json file as its child).
@@ -34,8 +34,7 @@ setInterval(function () {
34
34
  - callback on loaded geometry to assign a custom material or use the meshes for computations.
35
35
  - Optionally load low detail tiles outside of view frustum for correct shadows and basic mesh present when the camera moves quickly.
36
36
  - Share a cache between tileset instances
37
- - Automatically tune the geometric error multiplier to 60 FPS
38
- - Automatic scaling of the cache
37
+ - Optimal tile load order
39
38
 
40
39
  ### geometric Error Multiplier
41
40
  The geometric error multiplier allows you to multiply the geometric error by a factor.
@@ -53,27 +52,6 @@ const ogc3DTile = new OGC3DTile({
53
52
  A lower value will result in lower detail tiles being loaded and a higher value results in higher detail tiles being loaded.
54
53
  A value of 1.0 is the default.
55
54
 
56
- #### Automatic Geometric error multiplier
57
- In order to reach a steady 60 FPS, you can specify a TilesetStats object.
58
- This object is basically the Stats object from the Three.js samples without the UI component.
59
- It must be updated in the animate function and given to the tileset at construction.
60
- ```
61
- import TilesetStats from '@jdultra/threedtiles/src/tileset/TilesetStats';
62
-
63
- const tilesetStats = TilesetStats();
64
-
65
- const ogc3DTile = new OGC3DTile({
66
- url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
67
- stats: tilesetStats
68
- });
69
-
70
- function animate() {
71
-
72
- ...
73
-
74
- tilesetStats.update();
75
- }
76
- ```
77
55
 
78
56
 
79
57
  ### load tiles outside of view
@@ -98,12 +76,14 @@ const ogc3DTile = new OGC3DTile({
98
76
  }
99
77
  });
100
78
  ```
79
+ If using a shared cache between tilesets, check out the next section.
101
80
 
102
81
  ### Cache
103
82
  You may instanciate a cache through the TileLoader class and re-use it for several or all your tilesets.
104
83
  The limitation is that all the tilesets using the same cache will have the same callback.
105
84
 
106
- If a TilesetStats object is passed, it will be used to monitor the size of the cache when the browser allows it, otherwise, each cache is limitted to 1000 items.
85
+ The TileLoader constructor takes 2 arguments. The first is a callback for meshes (see above section) and the second is
86
+ the maximum number of items in the cache (default is 1000).
107
87
 
108
88
  ```
109
89
  import { TileLoader } from "@jdultra/threedtiles/src/tileset/TileLoader";
@@ -114,7 +94,9 @@ const ogc3DTile = new OGC3DTile({
114
94
  //// Insert code to be called on every newly decoded mesh e.g.:
115
95
  mesh.material.wireframe = false;
116
96
  mesh.material.side = THREE.DoubleSide;
117
- }, tilesetStats),
97
+ },
98
+ 2000
99
+ ),
118
100
  meshCallback: mesh => { mesh.material.wireframe = true;} // This callback will not be used as the callback provided to the TileLoader takes priority
119
101
  });
120
102
  ```
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.3",
3
+ "version": "3.2.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.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
@@ -4,12 +4,14 @@ import Stats from 'three/examples/jsm/libs/stats.module.js';
4
4
  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
+ import { setIntervalAsync } from 'set-interval-async/dynamic';
7
8
 
8
9
 
9
10
  const scene = initScene();
10
11
  const domContainer = initDomContainer("screen");
11
12
  const camera = initCamera();
12
13
  const ogc3DTiles = initTileset(scene);
14
+ initLODMultiplierSlider(ogc3DTiles)
13
15
  const controller = initController(camera, domContainer);
14
16
 
15
17
  const stats = initStats(domContainer);
@@ -19,8 +21,10 @@ animate();
19
21
 
20
22
  function initScene() {
21
23
  const scene = new THREE.Scene();
22
- scene.background = new THREE.Color(0x000000);
23
- scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
24
+ scene.background = new THREE.Color(0xaaffcc);
25
+ scene.add(new THREE.AmbientLight(0xFFFFFF, 0.5));
26
+ const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
27
+ scene.add( directionalLight );
24
28
  return scene;
25
29
  }
26
30
 
@@ -68,7 +72,7 @@ function initStats(dom) {
68
72
 
69
73
  function initCamera() {
70
74
  const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1, 10000);
71
- camera.position.set(10, 10, 10);
75
+ camera.position.set(20, 10, 20);
72
76
 
73
77
  return camera;
74
78
  }
@@ -76,43 +80,49 @@ function initCamera() {
76
80
  function initTileset(scene) {
77
81
 
78
82
  const ogc3DTile = new OGC3DTile({
79
- url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
83
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tiledWithSkirts/tileset.json",
84
+ //url: "http://localhost:8080/tileset.json",
80
85
  geometricErrorMultiplier: 1.0,
81
86
  loadOutsideView: true,
82
87
  tileLoader: new TileLoader(mesh => {
83
88
  //// Insert code to be called on every newly decoded mesh e.g.:
84
- mesh.material.wireframe = false;
89
+ //mesh.material.wireframe = true;
90
+ //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)+")")})
85
91
  mesh.material.side = THREE.DoubleSide;
86
- }, 2000)
92
+ }, 1000),
93
+ onLoadCallback: tileset => {
94
+ console.log(tileset.json)
95
+ }
87
96
  });
88
-
89
97
 
98
+
99
+
90
100
  //// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
91
101
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -10)
92
102
  //ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
93
103
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
94
- //ogc3DTile.scale.set(0.0001,0.0001,0.0001);
95
- //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
104
+ ogc3DTile.scale.set(1,1,1);
105
+ //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * 0.5) // Z-UP to Y-UP
96
106
  // ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
97
107
  // ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
98
108
  // ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
99
109
  //// 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
100
110
 
101
-
102
111
 
103
- var interval ;
112
+
113
+ var interval;
104
114
  document.addEventListener('keyup', (e) => {
105
115
  console.log(camera.position)
106
- if(!e.key || e.key !== "p") return;
107
- if(!!interval){
116
+ if (!e.key || e.key !== "p") return;
117
+ if (!!interval) {
108
118
  clearInterval(interval);
109
119
  interval = null;
110
- }else{
120
+ } else {
111
121
  startInterval();
112
122
  }
113
123
  });
114
- function startInterval(){
115
- interval = setInterval(function () {
124
+ function startInterval() {
125
+ interval = setIntervalAsync(function () {
116
126
  ogc3DTile.update(camera);
117
127
  }, 20);
118
128
  }
@@ -122,10 +132,21 @@ function initTileset(scene) {
122
132
  return ogc3DTile;
123
133
  }
124
134
 
135
+ function initLODMultiplierSlider(tileset) {
136
+ var slider = document.getElementById("lodMultiplier");
137
+ var output = document.getElementById("multiplierValue");
138
+ output.innerHTML = slider.value;
139
+
140
+ slider.oninput = () => {
141
+ tileset.setGeometricErrorMultiplier(slider.value)
142
+ output.innerHTML = slider.value;
143
+ }
144
+ }
145
+
125
146
  function initController(camera, dom) {
126
147
  const controller = new OrbitControls(camera, dom);
127
148
 
128
- controller.target.set(-11.50895,0.058452500000001, 3.1369285);
149
+ controller.target.set(0, 0, 0);
129
150
  controller.minDistance = 1;
130
151
  controller.maxDistance = 5000;
131
152
  controller.update();
@@ -25,7 +25,8 @@ class OGC3DTile extends THREE.Object3D {
25
25
  * tileLoader : TileLoader,
26
26
  * meshCallback: function,
27
27
  * cameraOnLoad: camera,
28
- * parentTile: OGC3DTile
28
+ * parentTile: OGC3DTile,
29
+ * onLoadCallback: function
29
30
  * } properties
30
31
  */
31
32
  constructor(properties) {
@@ -66,13 +67,17 @@ class OGC3DTile extends THREE.Object3D {
66
67
 
67
68
  if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
68
69
  self.setup(properties);
70
+ if (properties.onLoadCallback) properties.onLoadCallback(self);
69
71
  } else if (properties.url) { // If only the url to the tileset.json is provided
70
72
  self.controller = new AbortController();
71
73
  fetch(properties.url, { signal: self.controller.signal }).then(result => {
72
74
  if (!result.ok) {
73
75
  throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
74
76
  }
75
- result.json().then(json => self.setup({ rootPath: path.dirname(properties.url), json: json }))
77
+ result.json().then(json => {
78
+ self.setup({ rootPath: path.dirname(properties.url), json: json });
79
+ if (properties.onLoadCallback) properties.onLoadCallback(self);
80
+ });
76
81
  });
77
82
  }
78
83
  }
@@ -169,7 +174,7 @@ class OGC3DTile extends THREE.Object3D {
169
174
  self.meshContent = mesh;
170
175
  }, !self.cameraOnLoad ? () => 0 : () => {
171
176
  return self.calculateDistanceToCamera(self.cameraOnLoad);
172
- }, () => self.getSiblings(), self.level, self.uuid);
177
+ }, () => self.getSiblings(), self.level);
173
178
  } else if (url.includes(".json")) {
174
179
  self.tileLoader.get(this.uuid, url, json => {
175
180
  if (!!self.deleted) return;
@@ -1,36 +1,40 @@
1
1
  import { LinkedHashMap } from 'js-utils-z';
2
2
  import { B3DMDecoder } from "../decoder/B3DMDecoder";
3
+ import { setIntervalAsync } from 'set-interval-async/dynamic';
3
4
 
4
5
  const ready = [];
5
6
  const downloads = [];
6
7
  const nextReady = [];
7
8
  const nextDownloads = [];
9
+ let concurentDownloads = 0;
8
10
 
9
11
  function scheduleDownload(f) {
10
12
  downloads.unshift(f);
11
- //setTimeout(()=>download(),0);
12
13
  }
13
14
  function download() {
14
- if (nextDownloads.length == 0) {
15
- getNextDownloads();
16
- if (nextDownloads.length == 0) return;
17
- }
18
- const nextDownload = nextDownloads.shift();
19
- if (!!nextDownload && nextDownload.shouldDoDownload()) {
20
- nextDownload.doDownload();
15
+ if(concurentDownloads<10){
16
+ if (nextDownloads.length == 0) {
17
+ getNextDownloads();
18
+ if (nextDownloads.length == 0) return;
19
+ }
20
+ const nextDownload = nextDownloads.shift();
21
+ if (!!nextDownload && nextDownload.shouldDoDownload()) {
22
+ nextDownload.doDownload();
23
+ }
21
24
  }
25
+
26
+ return;
22
27
  }
23
28
  function meshReceived(cache, register, key, distanceFunction, getSiblings, level, uuid) {
24
29
  ready.unshift([cache, register, key, distanceFunction, getSiblings, level, uuid]);
25
- //setTimeout(()=>loadBatch(),1);
26
30
  }
27
31
  function loadBatch() {
28
32
  if (nextReady.length == 0) {
29
33
  getNextReady();
30
- if (nextReady.length == 0) return;
34
+ if (nextReady.length == 0) return 0;
31
35
  }
32
36
  const data = nextReady.shift();
33
- if (!data) return;
37
+ if (!data) return 0;
34
38
  const cache = data[0];
35
39
  const register = data[1];
36
40
  const key = data[2];
@@ -44,6 +48,7 @@ function loadBatch() {
44
48
  }
45
49
  });
46
50
  }
51
+ return 1;
47
52
  }
48
53
 
49
54
  function getNextDownloads() {
@@ -115,12 +120,23 @@ function getNextReady() {
115
120
  }
116
121
  }
117
122
  }
118
- setInterval(() => {
119
- download()
120
- }, 1)
121
- setInterval(() => {
122
- loadBatch()
123
- }, 1)
123
+ setIntervalAsync(()=>{
124
+ download();
125
+ /* const start = Date.now();
126
+ let uploaded = 0;
127
+ do{
128
+ uploaded = download();
129
+ }while(uploaded > 0 && (Date.now() - start)<= 2 ) */
130
+
131
+ },10);
132
+ setIntervalAsync(()=>{
133
+ const start = Date.now();
134
+ let loaded = 0;
135
+ do{
136
+ loaded = loadBatch();
137
+ }while(loaded > 0 && (Date.now() - start)<= 0 )
138
+
139
+ },10);
124
140
 
125
141
  class TileLoader {
126
142
  constructor(meshCallback, maxCachedItems) {
@@ -130,7 +146,7 @@ class TileLoader {
130
146
  this.register = {};
131
147
  }
132
148
 
133
- get(tileIdentifier, path, callback, distanceFunction, getSiblings, level, uuid) {
149
+ get(tileIdentifier, path, callback, distanceFunction, getSiblings, level) {
134
150
  const self = this;
135
151
  const key = simplifyPath(path);
136
152
 
@@ -149,12 +165,14 @@ class TileLoader {
149
165
 
150
166
  const cachedObject = self.cache.get(key);
151
167
  if (!!cachedObject) {
152
- meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
168
+ meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, tileIdentifier);
153
169
  } else if (Object.keys(self.register[key]).length == 1) {
154
170
  let downloadFunction;
155
171
  if (path.includes(".b3dm")) {
156
172
  downloadFunction = () => {
173
+ concurentDownloads++;
157
174
  fetch(path).then(result => {
175
+ concurentDownloads--;
158
176
  if (!result.ok) {
159
177
  console.error("could not load tile with path : " + path)
160
178
  throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
@@ -162,14 +180,16 @@ class TileLoader {
162
180
  result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
163
181
  self.cache.put(key, mesh);
164
182
  self.checkSize();
165
- meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
183
+ meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, tileIdentifier);
166
184
  });
167
185
 
168
186
  });
169
187
  }
170
188
  }else if (path.includes(".json")) {
171
189
  downloadFunction = () => {
190
+ concurentDownloads++;
172
191
  fetch(path).then(result => {
192
+ concurentDownloads--;
173
193
  if (!result.ok) {
174
194
  console.error("could not load tile with path : " + path)
175
195
  throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
@@ -190,7 +210,7 @@ class TileLoader {
190
210
  "distanceFunction": distanceFunction,
191
211
  "getSiblings": getSiblings,
192
212
  "level": level,
193
- "uuid": uuid
213
+ "uuid": tileIdentifier
194
214
  })
195
215
  }
196
216
  }
@@ -208,7 +228,6 @@ class TileLoader {
208
228
  let i = 0;
209
229
 
210
230
  while (self.cache.size() > self.maxCachedItems && i < self.cache.size()) {
211
- console.log(self.cache.size())
212
231
  i++;
213
232
  const entry = self.cache.head();
214
233
  const reg = self.register[entry.key];
package/manifest.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "display": "fullscreen",
3
- "orientation": "landscape"
4
- }