@jdultra/threedtiles 3.0.8 → 3.1.3

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.
@@ -0,0 +1,2 @@
1
+ {
2
+ }
package/README.md CHANGED
@@ -14,7 +14,7 @@ import { OGC3DTile } from "./tileset/OGC3DTile";
14
14
  ...
15
15
 
16
16
  const ogc3DTile = new OGC3DTile({
17
- url: "https://ebeaufay.github.io/ThreedTilesViewer.github.io/momoyama/tileset.json"
17
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json"
18
18
  });
19
19
 
20
20
  scene.add(ogc3DTile);
@@ -33,6 +33,9 @@ setInterval(function () {
33
33
  - Allows tilesets transformations. Translate, scale and rotate a tilesets in real-time.
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
+ - Share a cache between tileset instances
37
+ - Automatically tune the geometric error multiplier to 60 FPS
38
+ - Automatic scaling of the cache
36
39
 
37
40
  ### geometric Error Multiplier
38
41
  The geometric error multiplier allows you to multiply the geometric error by a factor.
@@ -43,20 +46,43 @@ you may also set this value at initialization:
43
46
 
44
47
  ```
45
48
  const ogc3DTile = new OGC3DTile({
46
- url: "https://ebeaufay.github.io/ThreedTilesViewer.github.io/momoyama/tileset.json",
49
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
47
50
  geometricErrorMultiplier: 2.0
48
51
  });
49
52
  ```
50
53
  A lower value will result in lower detail tiles being loaded and a higher value results in higher detail tiles being loaded.
51
54
  A value of 1.0 is the default.
52
55
 
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
+
78
+
53
79
  ### load tiles outside of view
54
80
  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.
55
81
  Instead of this behaviour, you can force the lowest possible LODs to be loaded for tiles around the view so that there are no gaps in the mesh when the camera moves or when displaying shadows.
56
82
 
57
83
  ```
58
84
  const ogc3DTile = new OGC3DTile({
59
- url: "https://ebeaufay.github.io/ThreedTilesViewer.github.io/momoyama/tileset.json",
85
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
60
86
  loadOutsideView: true
61
87
  });
62
88
  ```
@@ -66,13 +92,33 @@ Add a callback on loaded tiles in order to set a material or do some logic on th
66
92
 
67
93
  ```
68
94
  const ogc3DTile = new OGC3DTile({
69
- url: "https://ebeaufay.github.io/ThreedTilesViewer.github.io/momoyama/tileset.json",
95
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
70
96
  meshCallback: mesh => {
71
97
  mesh.material.wireframe = true;
72
98
  }
73
99
  });
74
100
  ```
75
101
 
102
+ ### Cache
103
+ You may instanciate a cache through the TileLoader class and re-use it for several or all your tilesets.
104
+ The limitation is that all the tilesets using the same cache will have the same callback.
105
+
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.
107
+
108
+ ```
109
+ import { TileLoader } from "@jdultra/threedtiles/src/tileset/TileLoader";
110
+
111
+ const ogc3DTile = new OGC3DTile({
112
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
113
+ tileLoader: new TileLoader(mesh => {
114
+ //// Insert code to be called on every newly decoded mesh e.g.:
115
+ mesh.material.wireframe = false;
116
+ mesh.material.side = THREE.DoubleSide;
117
+ }, tilesetStats),
118
+ meshCallback: mesh => { mesh.material.wireframe = true;} // This callback will not be used as the callback provided to the TileLoader takes priority
119
+ });
120
+ ```
121
+
76
122
  ### Transformations
77
123
  The OGC 3DTile object is a regular three.js Object3D so it can be transformed via the standard three.js API:
78
124
 
package/index.html CHANGED
@@ -4,53 +4,11 @@
4
4
  <head>
5
5
  <meta charset="utf-8" />
6
6
  <title>Three 3DTiles viewer sample</title>
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>
7
+ <link rel="manifest" href="manifest.json">
46
8
  </head>
47
9
 
48
10
  <body>
49
11
  <div id="screen"></div>
50
- <div style="position: absolute; top: 1%; z-index: 100; right:1%; ">
51
- <input type="range" min="0.1" max="4" value="1.0", step="0.1" class="slider" id="lodMultiplier" >
52
- <p style="color: #0439aa;">LOD multiplier: <span id="multiplierValue"></span></p>
53
- </div>
54
12
  <div style="position: absolute; bottom: 1%; z-index: 100;">
55
13
  <a href="https://openheritage3d.org/project.php?id=taz6-n215">ORIGINAL MODEL</a>
56
14
  </div>
package/manifest.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "display": "fullscreen",
3
+ "orientation": "landscape"
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jdultra/threedtiles",
3
- "version": "3.0.8",
3
+ "version": "3.1.3",
4
4
  "description": "An OGC 3DTiles viewer for Three.js",
5
5
  "main": "tileset.js",
6
6
  "scripts": {
@@ -26,9 +26,13 @@
26
26
  "gltf-validator": ">=2.0.0-dev.3.3",
27
27
  "js-utils-z": "1.2.1",
28
28
  "lodash": ">=4.17.20",
29
+ "lru-cache": "^7.4.1",
30
+ "mnemonist": "^0.39.0",
29
31
  "path-browserify": "^1.0.1",
30
32
  "regenerator-runtime": ">=0.13.7",
31
- "three": "0.131.0"
33
+ "set-interval-async": "^2.0.3",
34
+ "three": "0.131.0",
35
+ "uuid": "^8.3.2"
32
36
  },
33
37
  "devDependencies": {
34
38
  "@babel/core": "^7.12.9",
@@ -41,7 +45,7 @@
41
45
  "mini-css-extract-plugin": "^1.3.1",
42
46
  "webpack": "^5.65.0",
43
47
  "webpack-cli": "4.9.1",
44
- "webpack-dev-server": "4.7.1",
48
+ "webpack-dev-server": "^4.7.4",
45
49
  "whatwg-fetch": "^3.5.0"
46
50
  }
47
51
  }
@@ -1,13 +1,10 @@
1
1
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
2
- import * as THREE from 'three';
3
2
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
4
3
  import { LegacyGLTFLoader } from './LegacyGLTFLoader.js';
5
- import { Color, DoubleSide, BufferAttribute, Mesh } from "three";
6
- import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
7
4
 
8
5
  const gltfLoader = new GLTFLoader();
9
6
  const dracoLoader = new DRACOLoader();
10
- dracoLoader.setDecoderPath( '/src/draco/' );
7
+ dracoLoader.setDecoderPath( 'https://www.gstatic.com/draco/versioned/decoders/1.4.3/' );
11
8
  gltfLoader.setDRACOLoader( dracoLoader );
12
9
  const legacyGLTFLoader = new LegacyGLTFLoader();
13
10
  const B3DMDecoder = {
@@ -106,7 +103,7 @@ const B3DMDecoder = {
106
103
  * @param {*} scene
107
104
  * @returns
108
105
  */
109
- function mergeColoredObject(scene) {
106
+ /*function mergeColoredObject(scene) {
110
107
 
111
108
  const coloredMeshes = {};
112
109
  const texturedMeshes = {};
@@ -213,7 +210,7 @@ function mergeColoredObject(scene) {
213
210
  console.log();
214
211
  scene.matrix = new THREE.Matrix4();
215
212
  return scene;
216
- }
213
+ }*/
217
214
 
218
215
  export { B3DMDecoder }
219
216
 
package/src/index.js CHANGED
@@ -2,80 +2,25 @@ import "regenerator-runtime/runtime.js";
2
2
  import * as THREE from 'three';
3
3
  import Stats from 'three/examples/jsm/libs/stats.module.js';
4
4
  import { OGC3DTile } from "./tileset/OGC3DTile";
5
+ import { TileLoader } from "./tileset/TileLoader";
5
6
  import { MapControls, OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
6
- import back from './images/skybox/back.png';
7
- import front from './images/skybox/front.png';
8
- import top from './images/skybox/top.png';
9
- import bottom from './images/skybox/bottom.png';
10
- import right from './images/skybox/right.png';
11
- import left from './images/skybox/left.png';
12
7
 
13
8
 
14
9
  const scene = initScene();
15
10
  const domContainer = initDomContainer("screen");
16
11
  const camera = initCamera();
17
12
  const ogc3DTiles = initTileset(scene);
18
- initLODMultiplierSlider(ogc3DTiles);
19
13
  const controller = initController(camera, domContainer);
20
- const skybox = initSkybox(controller, camera, scene);
21
14
 
22
15
  const stats = initStats(domContainer);
23
16
  const renderer = initRenderer(camera, domContainer);
24
17
 
25
18
  animate();
26
19
 
27
- function initSkybox(controller, camera, scene) {
28
- const geometry = new THREE.BoxGeometry(8000, 8000, 8000);
29
- const textures = [
30
- loadTexture(back),
31
- loadTexture(front),
32
- loadTexture(top),
33
- loadTexture(bottom),
34
- loadTexture(right),
35
- loadTexture(left),
36
- ];
37
- function loadTexture(url) {
38
- return new THREE.TextureLoader().load(url, (texture => {
39
- texture.wrapS = THREE.ClampToEdgeWrapping;
40
- texture.wrapT = THREE.ClampToEdgeWrapping;
41
- texture.magFilter = THREE.LinearFilter;
42
- texture.minFilter = THREE.LinearFilter;
43
- }))
44
-
45
- }
46
- const materials = [];
47
- textures.forEach(tex => {
48
- materials.push(new THREE.MeshBasicMaterial({ map: tex, side: THREE.BackSide }));
49
- })
50
- const mesh = new THREE.Mesh(geometry, materials);
51
- mesh.position.copy(camera.position);
52
- controller.addEventListener("change", () => {
53
- mesh.position.copy(camera.position);
54
- });
55
- scene.add(mesh);
56
- return mesh;
57
- }
58
- function initLODMultiplierSlider(tileset) {
59
- var slider = document.getElementById("lodMultiplier");
60
- var output = document.getElementById("multiplierValue");
61
- output.innerHTML = slider.value;
62
-
63
- slider.oninput = () => {
64
- tileset.setGeometricErrorMultiplier(slider.value)
65
- output.innerHTML = slider.value;
66
- }
67
- }
68
20
  function initScene() {
69
21
  const scene = new THREE.Scene();
70
- scene.background = new THREE.Color(0xFF0000);
71
- scene.add(new THREE.AmbientLight(0xFFFFFF, 0.5));
72
-
73
- var dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
74
- dirLight.position.set(-400, 500, -100);
75
- dirLight.target.position.set(0, 0, 0);
76
-
77
- scene.add(dirLight);
78
- scene.add(dirLight.target);
22
+ scene.background = new THREE.Color(0x000000);
23
+ scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
79
24
  return scene;
80
25
  }
81
26
 
@@ -115,16 +60,15 @@ function initRenderer(camera, dom) {
115
60
  }
116
61
 
117
62
  function initStats(dom) {
118
- const stats = new Stats();
119
- dom.appendChild(stats.dom);
63
+ const stats = Stats();
64
+ document.body.appendChild(stats.dom);
120
65
  return stats;
121
66
  }
122
67
 
123
68
 
124
69
  function initCamera() {
125
70
  const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1, 10000);
126
- camera.position.set(-60, 80, -30);
127
- camera.lookAt(-100, 40, 0);
71
+ camera.position.set(10, 10, 10);
128
72
 
129
73
  return camera;
130
74
  }
@@ -132,28 +76,33 @@ function initCamera() {
132
76
  function initTileset(scene) {
133
77
 
134
78
  const ogc3DTile = new OGC3DTile({
135
- //url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
136
- //url: "https://storage.googleapis.com/ogc-3d-tiles/castleX/tileset.json",
137
- url: "https://storage.googleapis.com/ogc-3d-tiles/berlinSubsetTiled/tileset.json",
138
- geometricErrorMultiplier: 1,
79
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
80
+ geometricErrorMultiplier: 1.0,
139
81
  loadOutsideView: true,
140
- meshCallback: mesh => {
82
+ tileLoader: new TileLoader(mesh => {
141
83
  //// Insert code to be called on every newly decoded mesh e.g.:
142
84
  mesh.material.wireframe = false;
143
85
  mesh.material.side = THREE.DoubleSide;
144
- }
86
+ }, 2000)
145
87
  });
88
+
146
89
 
147
90
  //// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
148
91
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -10)
149
92
  //ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
150
93
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
151
- //ogc3DTile.scale.set(0.1,0.1,0.1);
152
- //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), -Math.PI * 0.5) // Z-UP to Y-UP
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
96
+ // ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
97
+ // ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
98
+ // ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
153
99
  //// 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
154
100
 
101
+
102
+
155
103
  var interval ;
156
104
  document.addEventListener('keyup', (e) => {
105
+ console.log(camera.position)
157
106
  if(!e.key || e.key !== "p") return;
158
107
  if(!!interval){
159
108
  clearInterval(interval);
@@ -165,10 +114,9 @@ function initTileset(scene) {
165
114
  function startInterval(){
166
115
  interval = setInterval(function () {
167
116
  ogc3DTile.update(camera);
168
- }, 200);
117
+ }, 20);
169
118
  }
170
119
  startInterval();
171
-
172
120
 
173
121
  scene.add(ogc3DTile)
174
122
  return ogc3DTile;
@@ -177,9 +125,9 @@ function initTileset(scene) {
177
125
  function initController(camera, dom) {
178
126
  const controller = new OrbitControls(camera, dom);
179
127
 
180
- controller.target.set(-20, 40, 35);
128
+ controller.target.set(-11.50895,0.058452500000001, 3.1369285);
181
129
  controller.minDistance = 1;
182
- controller.maxDistance = 500;
130
+ controller.maxDistance = 5000;
183
131
  controller.update();
184
132
  return controller;
185
133
  }
@@ -190,7 +138,6 @@ function animate() {
190
138
 
191
139
  camera.updateMatrixWorld();
192
140
  renderer.render(scene, camera);
193
-
194
141
  stats.update();
195
142
 
196
143
  }
@@ -1,19 +1,11 @@
1
1
  import * as THREE from 'three';
2
2
  import { OBB } from "../geometry/obb";
3
- import { B3DMDecoder } from "../decoder/B3DMDecoder";
4
- import { Cache } from "../cache/Cache";
3
+ import { TileLoader } from "./TileLoader";
4
+ import { v4 as uuidv4 } from "uuid";
5
+ import { setIntervalAsync } from 'set-interval-async/dynamic';
6
+ // import { clearIntervalAsync } from 'set-interval-async';
5
7
  const path = require('path');
6
8
 
7
- const tilesToLoad = [];
8
- function scheduleLoadTile(tile) {
9
- tilesToLoad.push(tile);
10
- }
11
-
12
- setInterval(() => {
13
- const tile = tilesToLoad.shift();
14
- if (!!tile) tile.load();
15
- }, 5)
16
-
17
9
  const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1));
18
10
 
19
11
 
@@ -30,15 +22,32 @@ class OGC3DTile extends THREE.Object3D {
30
22
  * parentRefinement: optional,
31
23
  * geometricErrorMultiplier: Double,
32
24
  * loadOutsideView: Boolean,
33
- * cache : Cache
25
+ * tileLoader : TileLoader,
26
+ * meshCallback: function,
27
+ * cameraOnLoad: camera,
28
+ * parentTile: OGC3DTile
34
29
  * } properties
35
30
  */
36
31
  constructor(properties) {
37
32
  super();
33
+ const self = this;
34
+ this.uuid = uuidv4();
35
+ if (!!properties.tileLoader) {
36
+ this.tileLoader = properties.tileLoader;
37
+ } else {
38
+ this.tileLoader = new TileLoader(!properties.meshCallback ?
39
+ mesh => {
40
+ mesh.material.wireframe = false;
41
+ mesh.material.side = THREE.DoubleSide;
42
+ } : properties.meshCallback);
43
+ }
38
44
  // set properties general to the entire tileset
39
45
  this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
46
+
40
47
  this.meshCallback = properties.meshCallback;
41
48
  this.loadOutsideView = properties.loadOutsideView;
49
+ this.cameraOnLoad = properties.cameraOnLoad;
50
+ this.parentTile = properties.parentTile;
42
51
 
43
52
  // declare properties specific to the tile for clarity
44
53
  this.childrenTiles = [];
@@ -55,7 +64,6 @@ class OGC3DTile extends THREE.Object3D {
55
64
  this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
56
65
  this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
57
66
 
58
- const self = this;
59
67
  if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
60
68
  self.setup(properties);
61
69
  } else if (properties.url) { // If only the url to the tileset.json is provided
@@ -95,9 +103,9 @@ class OGC3DTile extends THREE.Object3D {
95
103
  }
96
104
  // decode transform
97
105
  if (!!this.json.transform) {
98
- //this.matrix = new THREE.Matrix4();
99
- this.matrix.elements = this.json.transform;
100
- this.updateWorldMatrix(false, false);
106
+ let mat = new THREE.Matrix4();
107
+ mat.elements = this.json.transform;
108
+ this.applyMatrix4(mat);
101
109
  }
102
110
  // decode volume
103
111
  if (!!this.json.boundingVolume) {
@@ -124,11 +132,13 @@ class OGC3DTile extends THREE.Object3D {
124
132
  } else {
125
133
  this.hasMeshContent = true;
126
134
  }
127
- scheduleLoadTile(this);
135
+ this.load();
136
+ //scheduleLoadTile(this);
128
137
  }
129
138
  }
130
139
  load() {
131
140
  var self = this;
141
+ if (self.deleted) return;
132
142
  if (!!self.json.content) {
133
143
  let url;
134
144
  if (!!self.json.content.uri) {
@@ -146,77 +156,78 @@ class OGC3DTile extends THREE.Object3D {
146
156
  }
147
157
 
148
158
  if (!!url) {
149
- self.controller = new AbortController();
150
- fetch(url, { signal: self.controller.signal }).then(result => {
151
- if (!result.ok) {
152
- throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
153
- }
154
- if (url.includes("b3dm")) {// if the content is B3DM
155
- result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
156
- mesh.traverse((o) => {
157
- if (o.isMesh) {
158
- o.material.visible = false;
159
- }
160
- });
161
- self.add(mesh);
162
- self.meshContent = mesh;
163
-
159
+ if (url.includes(".b3dm")) {
160
+ self.contentURL = url;
161
+ self.tileLoader.get(this.uuid, url, mesh => {
162
+ if (!!self.deleted) return;
163
+ mesh.traverse((o) => {
164
+ if (o.isMesh) {
165
+ o.material.visible = false;
166
+ }
167
+ });
168
+ self.add(mesh);
169
+ self.meshContent = mesh;
170
+ }, !self.cameraOnLoad ? () => 0 : () => {
171
+ return self.calculateDistanceToCamera(self.cameraOnLoad);
172
+ }, () => self.getSiblings(), self.level, self.uuid);
173
+ } else if (url.includes(".json")) {
174
+ self.tileLoader.get(this.uuid, url, json => {
175
+ if (!!self.deleted) return;
176
+ if (!self.json.children) self.json.children = [];
177
+ json.rootPath = path.dirname(url);
178
+ self.json.children.push(json);
179
+ delete self.json.content;
180
+ self.hasUnloadedJSONContent = false;
181
+ });
164
182
 
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 => { });
165
198
  }).catch(error => { });
166
- } else if (url.includes("json")) {// if the content is json
167
- result.json().then(json => {
168
- // when json content is downloaded, it is inserted into this tile's original JSON as a child
169
- // and the content object is deleted from the original JSON
170
- if (!self.json.children) self.json.children = [];
171
- json.rootPath = path.dirname(url);
172
- self.json.children.push(json);
173
- delete self.json.content;
174
- self.hasUnloadedJSONContent = false;
175
- }).catch(error => { });
176
- }
177
- }).catch(error => {
178
- });
199
+ }, 0); */
200
+
201
+ }
202
+
179
203
  }
180
204
  }
181
205
  }
182
206
 
183
- disposeChildren() {
184
- var self = this;
207
+ dispose() {
185
208
 
186
- self.childrenTiles.forEach(tile => tile.traverse(function (element) {
209
+ const self = this;
210
+ self.deleted = true;
211
+ this.traverse(function (element) {
212
+ if (!!element.contentURL) {
213
+ self.tileLoader.invalidate(element.contentURL, element.uuid);
214
+ }
187
215
  if (!!element.controller) { // abort tile request
188
216
  element.controller.abort();
189
217
  }
190
- if (element.material) {
191
- // dispose materials
192
- if (element.material.length) {
193
- for (let i = 0; i < element.material.length; ++i) {
194
- element.material[i].dispose();
195
- }
196
- }
197
- else {
198
- element.material.dispose()
199
- }
200
-
201
- }
202
- if (element.geometry) {
203
- // dispose geometry
204
- element.geometry.dispose();
205
-
206
- }
207
- }));
208
- for (let i = 0; i < this.childrenTiles.length; i++) {
209
218
 
210
- const object = this.childrenTiles[i];
211
-
212
- object.parent = null;
213
-
214
- object.dispatchEvent({ type: 'removed' });
219
+ });
220
+ this.parent = null;
221
+ this.parentTile = null;
222
+ this.dispatchEvent({ type: 'removed' });
223
+ }
224
+ disposeChildren() {
225
+ var self = this;
215
226
 
216
- }
217
- this.childrenTiles = [];
218
- this.children = [];
219
- if (!!this.meshContent) this.children.push(this.meshContent);
227
+ self.childrenTiles.forEach(tile => tile.dispose());
228
+ self.childrenTiles = [];
229
+ self.children = [];
230
+ if (!!self.meshContent) self.children.push(self.meshContent);
220
231
  }
221
232
 
222
233
 
@@ -228,14 +239,14 @@ class OGC3DTile extends THREE.Object3D {
228
239
  _update(camera, frustum) {
229
240
  const self = this;
230
241
 
242
+ self.childrenTiles.forEach(child => child._update(camera, frustum));
231
243
  if (!!self.boundingVolume && !!self.geometricError) {
232
- var metric = self.calculateUpdateMetric(camera, frustum);
244
+ self.metric = self.calculateUpdateMetric(camera, frustum);
233
245
  }
234
246
 
235
- updateNodeVisibility(metric);
236
- updateTree(metric);
237
- self.childrenTiles.forEach(child => child._update(camera, frustum));
238
- trimTree(metric);
247
+ updateNodeVisibility(self.metric);
248
+ updateTree(self.metric);
249
+ trimTree(self.metric);
239
250
 
240
251
 
241
252
  function updateTree(metric) {
@@ -293,7 +304,7 @@ class OGC3DTile extends THREE.Object3D {
293
304
  self.changeContentVisibility(false);
294
305
  } else {
295
306
  //self.changeContentVisibility(true);
296
-
307
+
297
308
  }
298
309
  }
299
310
  }
@@ -316,15 +327,17 @@ class OGC3DTile extends THREE.Object3D {
316
327
  function loadJsonChildren() {
317
328
  self.json.children.forEach(childJSON => {
318
329
  let childTile = new OGC3DTile({
330
+ parentTile: self,
319
331
  parentGeometricError: self.geometricError,
320
332
  parentBoundingVolume: self.boundingVolume,
321
333
  parentRefinement: self.refinement,
322
334
  json: childJSON,
323
335
  rootPath: self.rootPath,
324
336
  geometricErrorMultiplier: self.geometricErrorMultiplier,
325
- meshCallback: self.meshCallback,
326
337
  loadOutsideView: self.loadOutsideView,
327
- level: self.level + 1
338
+ level: self.level + 1,
339
+ tileLoader: self.tileLoader,
340
+ cameraOnLoad: camera
328
341
  });
329
342
  self.childrenTiles.push(childTile);
330
343
  self.add(childTile);
@@ -376,7 +389,7 @@ class OGC3DTile extends THREE.Object3D {
376
389
  return true;
377
390
  }
378
391
 
379
- return false;
392
+ return true;
380
393
 
381
394
  }
382
395
 
@@ -447,36 +460,46 @@ class OGC3DTile extends THREE.Object3D {
447
460
  //throw Error("Region bounding volume not supported");
448
461
  return -1;
449
462
  }
450
-
451
463
  }
452
464
 
465
+ getSiblings() {
466
+ const self = this;
467
+ const tiles = [];
468
+ if (!self.parentTile) return tiles;
469
+ let p = self.parentTile;
470
+ while (!p.hasMeshContent && !!p.parentTile) {
471
+ p = p.parentTile;
472
+ }
473
+ p.childrenTiles.forEach(child => {
474
+ if (!!child && child != self) {
475
+ while (!child.hasMeshContent && !!child.childrenTiles[0]) {
476
+ child = child.childrenTiles[0];
477
+ }
478
+ tiles.push(child);
479
+ }
480
+ });
481
+ return tiles;
482
+ }
483
+ calculateDistanceToCamera(camera) {
484
+ if (this.boundingVolume instanceof OBB) {
485
+ // box
486
+ tempSphere.copy(this.boundingVolume.sphere);
487
+ tempSphere.applyMatrix4(this.matrixWorld);
488
+ //if (!frustum.intersectsSphere(tempSphere)) return -1;
489
+ } else if (this.boundingVolume instanceof THREE.Sphere) {
490
+ //sphere
491
+ tempSphere.copy(this.boundingVolume);
492
+ tempSphere.applyMatrix4(this.matrixWorld);
493
+ //if (!frustum.intersectsSphere(tempSphere)) return -1;
494
+ }
495
+ if (this.boundingVolume instanceof THREE.Box3) {
496
+ return -1; // region not supported
497
+ }
498
+ return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
499
+ }
453
500
  setGeometricErrorMultiplier(geometricErrorMultiplier) {
454
501
  this.geometricErrorMultiplier = geometricErrorMultiplier;
455
502
  this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
456
503
  }
457
504
  }
458
-
459
- /**
460
- *
461
- * @param {Integer} size a number of vertices
462
- */
463
- function createMeshCache(size = 5000000, meshCallback = () => { }) {
464
- /* return new Cache(
465
- (url, self)=>{
466
- fetch(url, { signal: self.controller.signal }).then(result => {
467
- if (!result.ok) {
468
- throw new Error(`couldn't load "${properties.url}". Request failed with status ${result.status} : ${result.statusText}`);
469
- }
470
- result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
471
- mesh.traverse((o) => {
472
- if (o.isMesh) {
473
- o.material.visible = false;
474
- }
475
- });
476
- return mesh;
477
- }).catch(error => { });
478
- }
479
- }
480
- ) */
481
- }
482
- export { OGC3DTile, createMeshCache };
505
+ export { OGC3DTile };
@@ -0,0 +1,276 @@
1
+ import { LinkedHashMap } from 'js-utils-z';
2
+ import { B3DMDecoder } from "../decoder/B3DMDecoder";
3
+
4
+ const ready = [];
5
+ const downloads = [];
6
+ const nextReady = [];
7
+ const nextDownloads = [];
8
+
9
+ function scheduleDownload(f) {
10
+ downloads.unshift(f);
11
+ //setTimeout(()=>download(),0);
12
+ }
13
+ 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();
21
+ }
22
+ }
23
+ function meshReceived(cache, register, key, distanceFunction, getSiblings, level, uuid) {
24
+ ready.unshift([cache, register, key, distanceFunction, getSiblings, level, uuid]);
25
+ //setTimeout(()=>loadBatch(),1);
26
+ }
27
+ function loadBatch() {
28
+ if (nextReady.length == 0) {
29
+ getNextReady();
30
+ if (nextReady.length == 0) return;
31
+ }
32
+ const data = nextReady.shift();
33
+ if (!data) return;
34
+ const cache = data[0];
35
+ const register = data[1];
36
+ const key = data[2];
37
+ const mesh = cache.get(key);
38
+ if (!!mesh && !!register[key]) {
39
+ Object.keys(register[key]).forEach(tile => {
40
+ const callback = register[key][tile];
41
+ if (!!callback) {
42
+ callback(mesh);
43
+ register[key][tile] = null;
44
+ }
45
+ });
46
+ }
47
+ }
48
+
49
+ function getNextDownloads() {
50
+ let smallestLevel = Number.MAX_VALUE;
51
+ let smallestDistance = Number.MAX_VALUE;
52
+ let closest = -1;
53
+ for (let i = downloads.length - 1; i >= 0; i--) {
54
+ if (!downloads[i].shouldDoDownload()) {
55
+ downloads.splice(i, 1);
56
+ continue;
57
+ }
58
+ if(!downloads[i].distanceFunction){ // if no distance function, must be a json, give absolute priority!
59
+ nextDownloads.push(downloads.splice(i, 1)[0]);
60
+ }
61
+ }
62
+ if(nextDownloads.length>0) return;
63
+ for (let i = downloads.length - 1; i >= 0; i--) {
64
+ const dist = downloads[i].distanceFunction();
65
+ if (dist < smallestDistance) {
66
+ smallestDistance = dist;
67
+ closest = i;
68
+ } else if (dist == smallestDistance && downloads[i].level < smallestLevel) {
69
+ smallestLevel = downloads[i].level;
70
+ closest = i
71
+ }
72
+ }
73
+ if (closest >= 0) {
74
+ const closestItem = downloads.splice(closest, 1).pop();
75
+ nextDownloads.push(closestItem);
76
+ const siblings = closestItem.getSiblings();
77
+ for (let i = downloads.length - 1; i >= 0; i--) {
78
+ if (siblings.includes(downloads[i].uuid)) {
79
+ nextDownloads.push(downloads.splice(i, 1).pop());
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ function getNextReady() {
86
+ let smallestLevel = Number.MAX_VALUE;
87
+ let smallestDistance = Number.MAX_VALUE;
88
+ let closest = -1;
89
+ for (let i = ready.length - 1; i >= 0; i--) {
90
+
91
+ if(!ready[i][3]){// if no distance function, must be a json, give absolute priority!
92
+ nextReady.push(ready.splice(i,1)[0]);
93
+ }
94
+ }
95
+ if(nextReady.length>0) return;
96
+ for (let i = ready.length - 1; i >= 0; i--) {
97
+ const dist = ready[i][3]();
98
+ if (dist < smallestDistance) {
99
+ smallestDistance = dist;
100
+ smallestLevel = ready[i][5]
101
+ closest = i
102
+ } else if (dist == smallestDistance && ready[i][5] < smallestLevel) {
103
+ smallestLevel = ready[i][5]
104
+ closest = i
105
+ }
106
+ }
107
+ if (closest >= 0) {
108
+ const closestItem = ready.splice(closest, 1).pop();
109
+ nextReady.push(closestItem);
110
+ const siblings = closestItem[4]();
111
+ for (let i = ready.length - 1; i >= 0; i--) {
112
+ if (siblings.includes(ready[i][6])) {
113
+ nextready.push(ready.splice(i, 1).pop());
114
+ }
115
+ }
116
+ }
117
+ }
118
+ setInterval(() => {
119
+ download()
120
+ }, 1)
121
+ setInterval(() => {
122
+ loadBatch()
123
+ }, 1)
124
+
125
+ class TileLoader {
126
+ constructor(meshCallback, maxCachedItems) {
127
+ this.meshCallback = meshCallback;
128
+ this.cache = new LinkedHashMap();
129
+ this.maxCachedItems = !!maxCachedItems ? maxCachedItems : 1000;
130
+ this.register = {};
131
+ }
132
+
133
+ get(tileIdentifier, path, callback, distanceFunction, getSiblings, level, uuid) {
134
+ const self = this;
135
+ const key = simplifyPath(path);
136
+
137
+
138
+ if (!path.includes(".b3dm") && !path.includes(".json")) {
139
+ console.error("the 3DTiles cache can only be used to load B3DM and json data");
140
+ return;
141
+ }
142
+ if (!self.register[key]) {
143
+ self.register[key] = {};
144
+ }
145
+ if (!!self.register[key][tileIdentifier]) {
146
+ console.error(" a tile should only be loaded once");
147
+ }
148
+ self.register[key][tileIdentifier] = callback;
149
+
150
+ const cachedObject = self.cache.get(key);
151
+ if (!!cachedObject) {
152
+ meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
153
+ } else if (Object.keys(self.register[key]).length == 1) {
154
+ let downloadFunction;
155
+ if (path.includes(".b3dm")) {
156
+ downloadFunction = () => {
157
+ fetch(path).then(result => {
158
+ if (!result.ok) {
159
+ console.error("could not load tile with path : " + path)
160
+ throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
161
+ }
162
+ result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
163
+ self.cache.put(key, mesh);
164
+ self.checkSize();
165
+ meshReceived(self.cache, self.register, key, distanceFunction, getSiblings, level, uuid);
166
+ });
167
+
168
+ });
169
+ }
170
+ }else if (path.includes(".json")) {
171
+ downloadFunction = () => {
172
+ fetch(path).then(result => {
173
+ if (!result.ok) {
174
+ console.error("could not load tile with path : " + path)
175
+ throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
176
+ }
177
+ result.json().then(json => {
178
+ self.cache.put(key, json);
179
+ self.checkSize();
180
+ meshReceived(self.cache, self.register, key);
181
+ });
182
+ });
183
+ }
184
+ }
185
+ scheduleDownload({
186
+ "shouldDoDownload": () => {
187
+ return !!self.register[key] && Object.keys(self.register[key]).length > 0;
188
+ },
189
+ "doDownload": downloadFunction,
190
+ "distanceFunction": distanceFunction,
191
+ "getSiblings": getSiblings,
192
+ "level": level,
193
+ "uuid": uuid
194
+ })
195
+ }
196
+ }
197
+
198
+
199
+
200
+ invalidate(path, tileIdentifier) {
201
+ const key = simplifyPath(path);
202
+ delete this.register[key][tileIdentifier];
203
+ }
204
+
205
+ checkSize() {
206
+ const self = this;
207
+
208
+ let i = 0;
209
+
210
+ while (self.cache.size() > self.maxCachedItems && i < self.cache.size()) {
211
+ console.log(self.cache.size())
212
+ i++;
213
+ const entry = self.cache.head();
214
+ const reg = self.register[entry.key];
215
+ if (!!reg) {
216
+ if (Object.keys(reg).length > 0) {
217
+ self.cache.remove(entry.key);
218
+ self.cache.put(entry.key, entry.value);
219
+ } else {
220
+ self.cache.remove(entry.key);
221
+ delete self.register[entry.key];
222
+ entry.value.traverse((o) => {
223
+
224
+ if (o.material) {
225
+ // dispose materials
226
+ if (o.material.length) {
227
+ for (let i = 0; i < o.material.length; ++i) {
228
+ o.material[i].dispose();
229
+ }
230
+ }
231
+ else {
232
+ o.material.dispose()
233
+ }
234
+ }
235
+ if (o.geometry) {
236
+ // dispose geometry
237
+ o.geometry.dispose();
238
+
239
+ }
240
+ });
241
+ }
242
+ }
243
+
244
+ }
245
+ }
246
+ }
247
+
248
+ function simplifyPath(main_path) {
249
+
250
+ var parts = main_path.split('/'),
251
+ new_path = [],
252
+ length = 0;
253
+ for (var i = 0; i < parts.length; i++) {
254
+ var part = parts[i];
255
+ if (part === '.' || part === '' || part === '..') {
256
+ if (part === '..' && length > 0) {
257
+ length--;
258
+ }
259
+ continue;
260
+ }
261
+ new_path[length++] = part;
262
+ }
263
+
264
+ if (length === 0) {
265
+ return '/';
266
+ }
267
+
268
+ var result = '';
269
+ for (var i = 0; i < length; i++) {
270
+ result += '/' + new_path[i];
271
+ }
272
+
273
+ return result;
274
+ }
275
+
276
+ export { TileLoader };
@@ -1,44 +0,0 @@
1
-
2
- import { LinkedHashMap } from 'js-utils-z';
3
-
4
- class Cache {
5
- constructor(loader, counter, dispose, max) {
6
- this.loader = loader;
7
- this.counter = counter;
8
- this.dispose = dispose;
9
- this.max = max;
10
- this.currentSize = 0;
11
- this.objects = new LinkedHashMap();
12
- }
13
-
14
- get(name){
15
- if(this.objects.has(name)){
16
- const item = this.objects.remove(name);
17
- item.users++;
18
- this.objects.put(name, item, false);
19
- return new Promise.resolve({dispose:()=>item.users--,content:item.content});
20
- }else{
21
- return this.loader(name).then(content=>{
22
- const item = { users: 1, content: content };
23
- this.objects.put(name, item, false);
24
- currentSize+=this.counter(item);
25
- checkSize();
26
- return {dispose:()=>item.users--,content:item.content};
27
- });
28
- }
29
- }
30
-
31
- checkSize(){
32
- let object = this.objects.head();
33
- while(this.currentSize > this.max && !!object){
34
- if(object.value.users <=0){
35
- const item = this.objects.remove(object.key);
36
- this.currentSize -= this.counter(item.content);
37
- this.dispose(item.content);
38
- }
39
- object = object.next();
40
- }
41
- }
42
- }
43
-
44
- export{Cache};