@jdultra/threedtiles 3.0.7 → 3.1.2

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.7",
3
+ "version": "3.1.2",
4
4
  "description": "An OGC 3DTiles viewer for Three.js",
5
5
  "main": "tileset.js",
6
6
  "scripts": {
@@ -24,10 +24,15 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "gltf-validator": ">=2.0.0-dev.3.3",
27
+ "js-utils-z": "1.2.1",
27
28
  "lodash": ">=4.17.20",
29
+ "lru-cache": "^7.4.1",
30
+ "mnemonist": "^0.39.0",
31
+ "path-browserify": "^1.0.1",
28
32
  "regenerator-runtime": ">=0.13.7",
33
+ "set-interval-async": "^2.0.3",
29
34
  "three": "0.131.0",
30
- "js-utils-z": "1.2.1"
35
+ "uuid": "^8.3.2"
31
36
  },
32
37
  "devDependencies": {
33
38
  "@babel/core": "^7.12.9",
@@ -38,9 +43,9 @@
38
43
  "html-loader": "^1.3.2",
39
44
  "html-webpack-plugin": "^4.5.0",
40
45
  "mini-css-extract-plugin": "^1.3.1",
41
- "webpack": "4.44.2",
42
- "webpack-cli": "^3.3.12",
43
- "webpack-dev-server": "3.11.0",
46
+ "webpack": "^5.65.0",
47
+ "webpack-cli": "4.9.1",
48
+ "webpack-dev-server": "^4.7.4",
44
49
  "whatwg-fetch": "^3.5.0"
45
50
  }
46
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
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/index.js CHANGED
@@ -1,75 +1,28 @@
1
1
  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
+ import TilesetStats from './tileset/TilesetStats';
4
5
  import { OGC3DTile } from "./tileset/OGC3DTile";
6
+ import { TileLoader } from "./tileset/TileLoader";
5
7
  import { MapControls, OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
6
8
 
7
9
 
8
10
  const scene = initScene();
11
+ const tilesetStats = TilesetStats();
9
12
  const domContainer = initDomContainer("screen");
10
13
  const camera = initCamera();
11
14
  const ogc3DTiles = initTileset(scene);
12
- initLODMultiplierSlider(ogc3DTiles);
13
15
  const controller = initController(camera, domContainer);
14
- const skybox = initSkybox(controller, camera, scene);
15
16
 
16
17
  const stats = initStats(domContainer);
17
18
  const renderer = initRenderer(camera, domContainer);
18
19
 
19
20
  animate();
20
21
 
21
- function initSkybox(controller, camera, scene) {
22
- const geometry = new THREE.BoxGeometry(8000, 8000, 8000);
23
- const textures = [
24
- loadTexture("./skybox/back.png"),
25
- loadTexture("./skybox/front.png"),
26
- loadTexture("./skybox/top.png"),
27
- loadTexture("./skybox/bottom.png"),
28
- loadTexture("./skybox/right.png"),
29
- loadTexture("./skybox/left.png"),
30
- ];
31
- function loadTexture(url) {
32
- return new THREE.TextureLoader().load(url, (texture => {
33
- texture.wrapS = THREE.ClampToEdgeWrapping;
34
- texture.wrapT = THREE.ClampToEdgeWrapping;
35
- texture.magFilter = THREE.LinearFilter;
36
- texture.minFilter = THREE.LinearFilter;
37
- }))
38
-
39
- }
40
- const materials = [];
41
- textures.forEach(tex => {
42
- materials.push(new THREE.MeshBasicMaterial({ map: tex, side: THREE.BackSide }));
43
- })
44
- const mesh = new THREE.Mesh(geometry, materials);
45
- mesh.position.copy(camera.position);
46
- controller.addEventListener("change", () => {
47
- mesh.position.copy(camera.position);
48
- });
49
- scene.add(mesh);
50
- return mesh;
51
- }
52
- function initLODMultiplierSlider(tileset) {
53
- var slider = document.getElementById("lodMultiplier");
54
- var output = document.getElementById("multiplierValue");
55
- output.innerHTML = slider.value;
56
-
57
- slider.oninput = () => {
58
- tileset.setGeometricErrorMultiplier(slider.value)
59
- output.innerHTML = slider.value;
60
- }
61
- }
62
22
  function initScene() {
63
23
  const scene = new THREE.Scene();
64
- scene.background = new THREE.Color(0xFF0000);
65
- scene.add(new THREE.AmbientLight(0xFFFFFF, 0.5));
66
-
67
- var dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
68
- dirLight.position.set(-400, 500, -100);
69
- dirLight.target.position.set(0, 0, 0);
70
-
71
- scene.add(dirLight);
72
- scene.add(dirLight.target);
24
+ scene.background = new THREE.Color(0x000000);
25
+ scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
73
26
  return scene;
74
27
  }
75
28
 
@@ -109,16 +62,15 @@ function initRenderer(camera, dom) {
109
62
  }
110
63
 
111
64
  function initStats(dom) {
112
- const stats = new Stats();
113
- dom.appendChild(stats.dom);
65
+ const stats = Stats();
66
+ document.body.appendChild(stats.dom);
114
67
  return stats;
115
68
  }
116
69
 
117
70
 
118
71
  function initCamera() {
119
72
  const camera = new THREE.PerspectiveCamera(70, window.offsetWidth / window.offsetHeight, 1, 10000);
120
- camera.position.set(-60, 5, -30);
121
- camera.lookAt(-100, 0, 0);
73
+ camera.position.set(10, 10, 10);
122
74
 
123
75
  return camera;
124
76
  }
@@ -127,33 +79,33 @@ function initTileset(scene) {
127
79
 
128
80
  const ogc3DTile = new OGC3DTile({
129
81
  url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
130
- //url: "https://storage.googleapis.com/ogc-3d-tiles/castleX/tileset.json",
131
- //url: "https://storage.googleapis.com/ogc-3d-tiles/berlinSubsetTiled/tileset.json",
132
- //url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
133
- //url: "https://s3.us-east-2.wasabisys.com/construkted-assets/arkcnfuk9fw/tileset.json",
134
- //url: "https://s3.us-east-2.wasabisys.com/construkted-assets/a73faxnydqk/tileset.json",
135
- //url: "https://s3.us-east-2.wasabisys.com/construkted-assets/e63mubnpmg/tileset.json",
136
- //url: "https://assets.cesium.com/697512/tileset.json?v=1",
137
- //url:"https://a.3d.blc.shc.eu/WAB/base_layer/cesium_mesh_2020/tileset.json",
138
82
  geometricErrorMultiplier: 1,
139
83
  loadOutsideView: true,
140
- meshCallback: mesh => {
84
+ tileLoader: new TileLoader(mesh => {
141
85
  //// Insert code to be called on every newly decoded mesh e.g.:
142
86
  mesh.material.wireframe = false;
143
87
  mesh.material.side = THREE.DoubleSide;
144
- }
88
+ }, tilesetStats),
89
+ stats: tilesetStats
145
90
  });
91
+
146
92
 
147
93
  //// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
148
94
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -10)
149
95
  //ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -65)
150
96
  //ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -80)
151
- //ogc3DTile.scale.set(0.01,0.01,0.01);
152
- //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), -Math.PI * 0.5) // Z-UP to Y-UP
97
+ //ogc3DTile.scale.set(0.0001,0.0001,0.0001);
98
+ // ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * 0.5) // Z-UP to Y-UP
99
+ // ogc3DTile.translateOnAxis(new THREE.Vector3(1,0,0), -16.5)
100
+ // ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), 0)
101
+ // ogc3DTile.translateOnAxis(new THREE.Vector3(0,0,1), -9.5)
153
102
  //// 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
103
 
104
+
105
+
155
106
  var interval ;
156
107
  document.addEventListener('keyup', (e) => {
108
+ console.log(camera.position)
157
109
  if(!e.key || e.key !== "p") return;
158
110
  if(!!interval){
159
111
  clearInterval(interval);
@@ -165,10 +117,9 @@ function initTileset(scene) {
165
117
  function startInterval(){
166
118
  interval = setInterval(function () {
167
119
  ogc3DTile.update(camera);
168
- }, 200);
120
+ }, 25);
169
121
  }
170
122
  startInterval();
171
-
172
123
 
173
124
  scene.add(ogc3DTile)
174
125
  return ogc3DTile;
@@ -177,9 +128,9 @@ function initTileset(scene) {
177
128
  function initController(camera, dom) {
178
129
  const controller = new OrbitControls(camera, dom);
179
130
 
180
- controller.target.set(-20, -20, 35);
131
+ controller.target.set(-11.50895,0.058452500000001, 3.1369285);
181
132
  controller.minDistance = 1;
182
- controller.maxDistance = 500;
133
+ controller.maxDistance = 5000;
183
134
  controller.update();
184
135
  return controller;
185
136
  }
@@ -190,7 +141,7 @@ function animate() {
190
141
 
191
142
  camera.updateMatrixWorld();
192
143
  renderer.render(scene, camera);
193
-
144
+ tilesetStats.update();
194
145
  stats.update();
195
146
 
196
147
  }
@@ -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,13 +22,42 @@ class OGC3DTile extends THREE.Object3D {
30
22
  * parentRefinement: optional,
31
23
  * geometricErrorMultiplier: Double,
32
24
  * loadOutsideView: Boolean,
33
- * cache : Cache
25
+ * tileLoader : TileLoader,
26
+ * stats: TilesetStats,
27
+ * meshCallback: function
34
28
  * } properties
35
29
  */
36
30
  constructor(properties) {
37
31
  super();
32
+ const self = this;
33
+ this.uuid = uuidv4();
34
+ if (!!properties.tileLoader) {
35
+ this.tileLoader = properties.tileLoader;
36
+ } else {
37
+ this.tileLoader = new TileLoader(!properties.meshCallback ?
38
+ mesh => {
39
+ mesh.material.wireframe = false;
40
+ mesh.material.side = THREE.DoubleSide;
41
+ } : properties.meshCallback);
42
+ }
38
43
  // set properties general to the entire tileset
39
44
  this.geometricErrorMultiplier = !!properties.geometricErrorMultiplier ? properties.geometricErrorMultiplier : 1.0;
45
+ if (properties.stats) {
46
+ // Automatic geometric error multiplier
47
+ this.stats = properties.stats;
48
+
49
+ setIntervalAsync(() => {
50
+ if (!!document.hidden) return;
51
+ const framerate = self.stats.fps();
52
+ if (framerate < 0) return;
53
+ if (framerate < 58) {
54
+ self.setGeometricErrorMultiplier(Math.max(0.05, self.geometricErrorMultiplier - 0.05));
55
+ } else if (framerate > 58) {
56
+ self.setGeometricErrorMultiplier(self.geometricErrorMultiplier + 0.05);
57
+ }
58
+ }, 1000);
59
+ }
60
+
40
61
  this.meshCallback = properties.meshCallback;
41
62
  this.loadOutsideView = properties.loadOutsideView;
42
63
 
@@ -55,7 +76,6 @@ class OGC3DTile extends THREE.Object3D {
55
76
  this.hasMeshContent = false; // true when the provided json has a content field pointing to a B3DM file
56
77
  this.hasUnloadedJSONContent = false; // true when the provided json has a content field pointing to a JSON file that is not yet loaded
57
78
 
58
- const self = this;
59
79
  if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
60
80
  self.setup(properties);
61
81
  } else if (properties.url) { // If only the url to the tileset.json is provided
@@ -95,9 +115,9 @@ class OGC3DTile extends THREE.Object3D {
95
115
  }
96
116
  // decode transform
97
117
  if (!!this.json.transform) {
98
- //this.matrix = new THREE.Matrix4();
99
- this.matrix.elements = this.json.transform;
100
- this.updateWorldMatrix(false, false);
118
+ let mat = new THREE.Matrix4();
119
+ mat.elements = this.json.transform;
120
+ this.applyMatrix4(mat);
101
121
  }
102
122
  // decode volume
103
123
  if (!!this.json.boundingVolume) {
@@ -124,11 +144,13 @@ class OGC3DTile extends THREE.Object3D {
124
144
  } else {
125
145
  this.hasMeshContent = true;
126
146
  }
127
- scheduleLoadTile(this);
147
+ this.load();
148
+ //scheduleLoadTile(this);
128
149
  }
129
150
  }
130
151
  load() {
131
152
  var self = this;
153
+ if (self.deleted) return;
132
154
  if (!!self.json.content) {
133
155
  let url;
134
156
  if (!!self.json.content.uri) {
@@ -146,24 +168,24 @@ class OGC3DTile extends THREE.Object3D {
146
168
  }
147
169
 
148
170
  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
-
164
-
165
- }).catch(error => { });
166
- } else if (url.includes("json")) {// if the content is json
171
+ if (url.includes(".b3dm")) {
172
+ self.contentURL = url;
173
+ self.tileLoader.get(this.uuid, url, mesh => {
174
+ if (!!self.deleted) return;
175
+ mesh.traverse((o) => {
176
+ if (o.isMesh) {
177
+ o.material.visible = false;
178
+ }
179
+ });
180
+ self.add(mesh);
181
+ self.meshContent = mesh;
182
+ })
183
+ } else if (url.includes(".json")) {
184
+ self.controller = new AbortController();
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
+ }
167
189
  result.json().then(json => {
168
190
  // when json content is downloaded, it is inserted into this tile's original JSON as a child
169
191
  // and the content object is deleted from the original JSON
@@ -173,50 +195,36 @@ class OGC3DTile extends THREE.Object3D {
173
195
  delete self.json.content;
174
196
  self.hasUnloadedJSONContent = false;
175
197
  }).catch(error => { });
176
- }
177
- }).catch(error => {
178
- });
198
+ }).catch(error => { });
199
+ }
200
+
179
201
  }
180
202
  }
181
203
  }
182
204
 
183
- disposeChildren() {
184
- var self = this;
205
+ dispose() {
185
206
 
186
- self.childrenTiles.forEach(tile => tile.traverse(function (element) {
207
+ const self = this;
208
+ self.deleted = true;
209
+ this.traverse(function (element) {
210
+ if (!!element.contentURL) {
211
+ self.tileLoader.invalidate(element.contentURL, element.uuid);
212
+ }
187
213
  if (!!element.controller) { // abort tile request
188
214
  element.controller.abort();
189
215
  }
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
216
 
210
- const object = this.childrenTiles[i];
211
-
212
- object.parent = null;
213
-
214
- object.dispatchEvent({ type: 'removed' });
217
+ });
218
+ this.parent = null;
219
+ this.dispatchEvent({ type: 'removed' });
220
+ }
221
+ disposeChildren() {
222
+ var self = this;
215
223
 
216
- }
217
- this.childrenTiles = [];
218
- this.children = [];
219
- if (!!this.meshContent) this.children.push(this.meshContent);
224
+ self.childrenTiles.forEach(tile => tile.dispose());
225
+ self.childrenTiles = [];
226
+ self.children = [];
227
+ if (!!self.meshContent) self.children.push(self.meshContent);
220
228
  }
221
229
 
222
230
 
@@ -228,14 +236,14 @@ class OGC3DTile extends THREE.Object3D {
228
236
  _update(camera, frustum) {
229
237
  const self = this;
230
238
 
239
+ self.childrenTiles.forEach(child => child._update(camera, frustum));
231
240
  if (!!self.boundingVolume && !!self.geometricError) {
232
- var metric = self.calculateUpdateMetric(camera, frustum);
241
+ self.metric = self.calculateUpdateMetric(camera, frustum);
233
242
  }
234
243
 
235
- updateNodeVisibility(metric);
236
- updateTree(metric);
237
- self.childrenTiles.forEach(child => child._update(camera, frustum));
238
- trimTree(metric);
244
+ updateNodeVisibility(self.metric);
245
+ updateTree(self.metric);
246
+ trimTree(self.metric);
239
247
 
240
248
 
241
249
  function updateTree(metric) {
@@ -293,7 +301,7 @@ class OGC3DTile extends THREE.Object3D {
293
301
  self.changeContentVisibility(false);
294
302
  } else {
295
303
  //self.changeContentVisibility(true);
296
-
304
+
297
305
  }
298
306
  }
299
307
  }
@@ -322,9 +330,9 @@ class OGC3DTile extends THREE.Object3D {
322
330
  json: childJSON,
323
331
  rootPath: self.rootPath,
324
332
  geometricErrorMultiplier: self.geometricErrorMultiplier,
325
- meshCallback: self.meshCallback,
326
333
  loadOutsideView: self.loadOutsideView,
327
- level: self.level + 1
334
+ level: self.level + 1,
335
+ tileLoader: self.tileLoader
328
336
  });
329
337
  self.childrenTiles.push(childTile);
330
338
  self.add(childTile);
@@ -376,7 +384,7 @@ class OGC3DTile extends THREE.Object3D {
376
384
  return true;
377
385
  }
378
386
 
379
- return false;
387
+ return true;
380
388
 
381
389
  }
382
390
 
@@ -455,28 +463,4 @@ class OGC3DTile extends THREE.Object3D {
455
463
  this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
456
464
  }
457
465
  }
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 };
466
+ export { OGC3DTile };
@@ -0,0 +1,188 @@
1
+ import { LinkedHashMap } from 'js-utils-z';
2
+ import { B3DMDecoder } from "../decoder/B3DMDecoder";
3
+ import { setIntervalAsync } from 'set-interval-async/dynamic';
4
+
5
+ const ready = [];
6
+ const downloads = [];
7
+ function scheduleDownload(f) {
8
+ downloads.unshift(f);
9
+ }
10
+ function download() {
11
+ if(downloads.length <=0) return;
12
+ const nextDownload = downloads.shift();
13
+ if (!!nextDownload && nextDownload.shouldDoDownload()) {
14
+ nextDownload.doDownload();
15
+ }
16
+ }
17
+ function meshReceived(cache, register, key) {
18
+ ready.unshift([cache, register, key]);
19
+ }
20
+ function loadBatch() {
21
+ for (let i = 0; i < 1; i++) {
22
+ const data = ready.shift();
23
+ if (!data) return;
24
+ const cache = data[0];
25
+ const register = data[1];
26
+ const key = data[2];
27
+ const mesh = cache.get(key);
28
+ if (!!mesh) {
29
+ Object.keys(register[key]).forEach(tile => {
30
+ const callback = register[key][tile];
31
+ if (!!callback) {
32
+ callback(mesh);
33
+ register[key][tile] = null;
34
+ }
35
+ });
36
+ }
37
+ }
38
+ }
39
+ setIntervalAsync(() => {
40
+ loadBatch();
41
+ }, 10)
42
+ setIntervalAsync(() => {
43
+ download();
44
+ }, 10)
45
+
46
+ class TileLoader {
47
+ constructor(meshCallback, stats) {
48
+ this.meshCallback = meshCallback;
49
+ this.cache = new LinkedHashMap();
50
+ this.maxSize = 1000;
51
+ this.stats = stats;
52
+ this.register = {};
53
+ }
54
+
55
+ get(tileIdentifier, path, callback) {
56
+ const self = this;
57
+ const key = simplifyPath(path);
58
+
59
+
60
+ if (!path.includes(".b3dm")) {
61
+ console.error("the 3DTiles cache can only be used to load B3DM data");
62
+ return;
63
+ }
64
+ if (!self.register[key]) {
65
+ self.register[key] = {};
66
+ }
67
+ if (!!self.register[key][tileIdentifier]) {
68
+ console.error(" a tile should only be loaded once");
69
+ }
70
+ self.register[key][tileIdentifier] = callback;
71
+
72
+ const cachedObject = self.cache.get(key);
73
+ if (!!cachedObject) {
74
+ meshReceived(self.cache, self.register, key);
75
+ } else if (Object.keys(self.register[key]).length == 1) {
76
+ scheduleDownload({
77
+ "shouldDoDownload":()=>{
78
+ return Object.keys(self.register[key]).length > 0;
79
+ },
80
+ "doDownload": () => {
81
+ fetch(path).then(result => {
82
+ if (!result.ok) {
83
+ console.error("could not load tile with path : " + path)
84
+ throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
85
+ }
86
+ result.arrayBuffer().then(buffer => B3DMDecoder.parseB3DM(buffer, self.meshCallback)).then(mesh => {
87
+ self.cache.put(key, mesh);
88
+ self.checkSize();
89
+ meshReceived(self.cache, self.register, key);
90
+ });
91
+
92
+ });
93
+ }
94
+ })
95
+ }
96
+ }
97
+
98
+ meshReceived(key, mesh) {
99
+ const self = this;
100
+ Object.keys(self.register[key]).forEach(tile => {
101
+ const callback = self.register[key][tile];
102
+ if (!!callback) {
103
+ callback(mesh);
104
+ self.register[key][tile] = null;
105
+ }
106
+ });
107
+ }
108
+
109
+ invalidate(path, tileIdentifier) {
110
+ const key = simplifyPath(path);
111
+ delete this.register[key][tileIdentifier];
112
+ }
113
+
114
+ checkSize() {
115
+ const self = this;
116
+
117
+ let i = 0;
118
+ function memOverflowCheck(){
119
+ if(!!self.stats && self.stats.memory()>0){
120
+ if(self.stats.memory()/self.stats.maxMemory()<0.25){
121
+ return false;
122
+ }
123
+ return true;
124
+ }
125
+ return self.cache.size() > self.maxSize;
126
+ }
127
+ while (memOverflowCheck() && i < self.cache.size()) {
128
+ i++;
129
+ const entry = self.cache.head();
130
+ if (Object.keys(self.register[entry.key]).length > 0) {
131
+ self.cache.remove(entry.key);
132
+ self.cache.put(entry.key, entry.value);
133
+ } else {
134
+ self.cache.remove(entry.key);
135
+ delete self.register[entry.key];
136
+ entry.value.traverse((o) => {
137
+
138
+ if (o.material) {
139
+ // dispose materials
140
+ if (o.material.length) {
141
+ for (let i = 0; i < o.material.length; ++i) {
142
+ o.material[i].dispose();
143
+ }
144
+ }
145
+ else {
146
+ o.material.dispose()
147
+ }
148
+ }
149
+ if (o.geometry) {
150
+ // dispose geometry
151
+ o.geometry.dispose();
152
+
153
+ }
154
+ });
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ function simplifyPath(main_path) {
161
+
162
+ var parts = main_path.split('/'),
163
+ new_path = [],
164
+ length = 0;
165
+ for (var i = 0; i < parts.length; i++) {
166
+ var part = parts[i];
167
+ if (part === '.' || part === '' || part === '..') {
168
+ if (part === '..' && length > 0) {
169
+ length--;
170
+ }
171
+ continue;
172
+ }
173
+ new_path[length++] = part;
174
+ }
175
+
176
+ if (length === 0) {
177
+ return '/';
178
+ }
179
+
180
+ var result = '';
181
+ for (var i = 0; i < length; i++) {
182
+ result += '/' + new_path[i];
183
+ }
184
+
185
+ return result;
186
+ }
187
+
188
+ export { TileLoader };
@@ -0,0 +1,65 @@
1
+ var TilesetStats = function () {
2
+
3
+
4
+ var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;
5
+
6
+ var fps = -1;
7
+
8
+ if ( self.performance && self.performance.memory ) {
9
+
10
+ var mem = -1;
11
+ var maxMem = -1;
12
+
13
+ }
14
+
15
+
16
+ return {
17
+
18
+ begin: function () {
19
+
20
+ beginTime = ( performance || Date ).now();
21
+
22
+ },
23
+
24
+ end: function () {
25
+
26
+ frames ++;
27
+
28
+ var time = ( performance || Date ).now();
29
+
30
+ if ( time >= prevTime + 1000 ) {
31
+
32
+ fps = ( frames * 1000 ) / ( time - prevTime );
33
+
34
+ prevTime = time;
35
+ frames = 0;
36
+
37
+ if ( !!mem ) {
38
+
39
+ var memory = performance.memory;
40
+ mem = memory.usedJSHeapSize;
41
+ maxMem = memory.jsHeapSizeLimit;
42
+
43
+ }
44
+
45
+ }
46
+
47
+ return time;
48
+
49
+ },
50
+
51
+ update: function () {
52
+
53
+ beginTime = this.end();
54
+
55
+ },
56
+
57
+ fps: ()=>fps,
58
+ memory: ()=>mem,
59
+ maxMemory: ()=>maxMem
60
+
61
+ };
62
+
63
+ };
64
+
65
+ export default TilesetStats;
package/webpack.config.js CHANGED
@@ -83,19 +83,13 @@ module.exports = {
83
83
  }]
84
84
  },
85
85
  {
86
- test: /\.(png|gif)$/,
87
- use: [{
88
- loader: "file-loader",
89
- options: {
90
- name: "images/[name].[contenthash].[ext]"
91
- }
92
- }]
93
- }
86
+ test: /\.(png|svg|jpg|jpeg|gif)$/i,
87
+ type: 'asset/resource',
88
+ },
94
89
  ],
95
90
  },
96
91
  optimization: {
97
92
  minimizer: [new TerserPlugin({
98
- cache: true,
99
93
  parallel: true,
100
94
  terserOptions: {
101
95
  ecma: undefined,
@@ -119,8 +113,14 @@ module.exports = {
119
113
  devServer: {
120
114
  hot: true,
121
115
  open: true,
122
- openPage: "",
123
116
  port: DEFAULT_WEBPACK_PORT
117
+ },
118
+ resolve: {
119
+ extensions: [".js", ".jsx", ".json", ".ts", ".tsx"],// other stuff
120
+ fallback: {
121
+ "fs": false,
122
+ "path": require.resolve("path-browserify")
123
+ }
124
124
  }
125
125
 
126
126
  };
@@ -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};