@jdultra/threedtiles 3.3.2 → 4.0.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.
@@ -0,0 +1,322 @@
1
+ import { LinkedHashMap } from 'js-utils-z';
2
+ import { B3DMDecoder } from "../../decoder/B3DMDecoder";
3
+ import { setIntervalAsync } from 'set-interval-async/dynamic';
4
+ import * as THREE from 'three';
5
+ import { MeshTile } from './MeshTile';
6
+ import { JsonTile } from './JsonTile';
7
+
8
+ let concurentDownloads = 0;
9
+
10
+ class InstancedTileLoader {
11
+ constructor(scene, meshCallback, maxCachedItems, maxInstances) {
12
+ this.meshCallback = meshCallback;
13
+ this.maxInstances = maxInstances;
14
+ this.cache = new LinkedHashMap();
15
+ this.maxCachedItems = !!maxCachedItems ? maxCachedItems : 100;
16
+ this.scene = scene;
17
+
18
+ this.ready = [];
19
+ this.downloads = [];
20
+ this.nextReady = [];
21
+ this.nextDownloads = [];
22
+ this.init();
23
+ }
24
+
25
+ update(){
26
+ const self = this;
27
+
28
+ self.cache._data.forEach(v=>{
29
+ v.update();
30
+ })
31
+
32
+ }
33
+ init(){
34
+
35
+ const self = this;
36
+ setIntervalAsync(() => {
37
+ self.download();
38
+ }, 10);
39
+ setIntervalAsync(() => {
40
+ const start = Date.now();
41
+ let loaded = 0;
42
+ do {
43
+ loaded = self.loadBatch();
44
+ } while (loaded > 0 && (Date.now() - start) <= 0)
45
+
46
+ }, 10);
47
+ }
48
+
49
+ download() {
50
+ const self = this;
51
+ if (self.nextDownloads.length == 0) {
52
+ self.getNextDownloads();
53
+ if (self.nextDownloads.length == 0) return;
54
+ }
55
+ while (self.nextDownloads.length > 0 && concurentDownloads < 500) {
56
+ const nextDownload = self.nextDownloads.shift();
57
+ if (!!nextDownload && nextDownload.shouldDoDownload()) {
58
+ //nextDownload.doDownload();
59
+ concurentDownloads++;
60
+ if(nextDownload.path.includes(".b3dm")){
61
+ fetch(nextDownload.path, {signal: nextDownload.abortController.signal}).then(result => {
62
+ concurentDownloads--;
63
+ if (!result.ok) {
64
+ console.error("could not load tile with path : " + path)
65
+ throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
66
+ }
67
+ return result.arrayBuffer();
68
+
69
+ })
70
+ .then(resultArrayBuffer=>{
71
+ return B3DMDecoder.parseB3DMInstanced(resultArrayBuffer, self.meshCallback, self.maxInstances);
72
+ })
73
+ .then(mesh=>{
74
+ nextDownload.tile.setObject(mesh);
75
+ self.ready.unshift(nextDownload);
76
+
77
+ })
78
+ .catch(e=>console.error(e));
79
+ }else if(nextDownload.path.includes(".json")){
80
+ concurentDownloads++;
81
+ fetch(nextDownload.path, {signal: nextDownload.abortController.signal}).then(result => {
82
+ concurentDownloads--;
83
+ if (!result.ok) {
84
+ console.error("could not load tile with path : " + path)
85
+ throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
86
+ }
87
+ return result.json();
88
+
89
+ }).then(json => {
90
+ nextDownload.tile.setObject(json, nextDownload.path);
91
+ self.ready.unshift(nextDownload);
92
+ })
93
+ .catch(e=>console.error(e))
94
+ }
95
+ }
96
+ }
97
+ return;
98
+ }
99
+
100
+ loadBatch() {
101
+ if (this.nextReady.length == 0) {
102
+ this.getNextReady();
103
+ if (this.nextReady.length == 0) return 0;
104
+ }
105
+ const download = this.nextReady.shift();
106
+ if (!download) return 0;
107
+
108
+ if(!!download.tile.addToScene)download.tile.addToScene();
109
+ return 1;
110
+ }
111
+
112
+ getNextReady() {
113
+ let smallestLevel = Number.MAX_VALUE;
114
+ let smallestDistance = Number.MAX_VALUE;
115
+ let closest = -1;
116
+ for (let i = this.ready.length - 1; i >= 0; i--) {
117
+
118
+ if (!this.ready[i].distanceFunction) {// if no distance function, must be a json, give absolute priority!
119
+ this.nextReady.push(this.ready.splice(i, 1)[0]);
120
+ }
121
+ }
122
+ if (this.nextReady.length > 0) return;
123
+ for (let i = this.ready.length - 1; i >= 0; i--) {
124
+ const dist = this.ready[i].distanceFunction();
125
+ if (dist < smallestDistance) {
126
+ smallestDistance = dist;
127
+ smallestLevel = this.ready[i].level
128
+ closest = i
129
+ } else if (dist == smallestDistance && this.ready[i].level < smallestLevel) {
130
+ smallestLevel = this.ready[i].level
131
+ closest = i
132
+ }
133
+ }
134
+ if (closest >= 0) {
135
+ const closestItem = this.ready.splice(closest, 1).pop();
136
+ this.nextReady.push(closestItem);
137
+ const siblings = closestItem.getSiblings();
138
+ for (let i = this.ready.length - 1; i >= 0; i--) {
139
+ if (siblings.includes(this.ready[i].uuid)) {
140
+ this.nextready.push(this.ready.splice(i, 1).pop());
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ get(abortController, path, uuid, instancedOGC3DTile, distanceFunction, getSiblings, level) {
147
+ const self = this;
148
+ const key = simplifyPath(path);
149
+
150
+ if (!path.includes(".b3dm") && !path.includes(".json")) {
151
+ console.error("the 3DTiles cache can only be used to load B3DM and json data");
152
+ return;
153
+ }
154
+
155
+ const cachedTile = self.cache.get(key);
156
+ if (!!cachedTile) {
157
+ cachedTile.addInstance(instancedOGC3DTile);
158
+ return;
159
+ } else {
160
+
161
+ if (path.includes(".b3dm")) {
162
+ const tile = new MeshTile(self.scene);
163
+ tile.addInstance(instancedOGC3DTile);
164
+ if(self.cache.has(key)){
165
+ console.log("élkbhj")
166
+ }
167
+ self.cache.put(key, tile);
168
+
169
+ const realAbortController = new AbortController();
170
+ abortController.signal.addEventListener("abort", () => {
171
+ if (tile.getCount() == 0) {
172
+ realAbortController.abort();
173
+ }
174
+ })
175
+ this.downloads.push({
176
+ abortController: realAbortController,
177
+ tile: tile,
178
+ key: key,
179
+ path: path,
180
+ distanceFunction: distanceFunction,
181
+ getSiblings: getSiblings,
182
+ level: level,
183
+ uuid: uuid,
184
+ shouldDoDownload: () => {
185
+ return true;
186
+ },
187
+ })
188
+ }else if(path.includes(".json")){
189
+ const tile = new JsonTile();
190
+ tile.addInstance(instancedOGC3DTile);
191
+ self.cache.put(key, tile);
192
+
193
+ const realAbortController = new AbortController();
194
+ abortController.signal.addEventListener("abort", () => {
195
+ if (tile.getCount() == 0) {
196
+ realAbortController.abort();
197
+ }
198
+ })
199
+ this.downloads.push({
200
+ abortController: realAbortController,
201
+ tile: tile,
202
+ key: key,
203
+ path: path,
204
+ distanceFunction: distanceFunction,
205
+ getSiblings: getSiblings,
206
+ level: level,
207
+ shouldDoDownload: () => {
208
+ return true;
209
+ },
210
+ })
211
+
212
+ }
213
+ }
214
+ }
215
+
216
+
217
+
218
+ getNextDownloads() {
219
+ let smallestLevel = Number.MAX_VALUE;
220
+ let smallestDistance = Number.MAX_VALUE;
221
+ let closest = -1;
222
+ for (let i = this.downloads.length - 1; i >= 0; i--) {
223
+ const download = this.downloads[i];
224
+ if (!download.shouldDoDownload()) {
225
+ this.downloads.splice(i, 1);
226
+ continue;
227
+ }
228
+ if (!download.distanceFunction) { // if no distance function, must be a json, give absolute priority!
229
+ this.nextDownloads.push(this.downloads.splice(i, 1)[0]);
230
+ }
231
+ }
232
+ if (this.nextDownloads.length > 0) return;
233
+ for (let i = this.downloads.length - 1; i >= 0; i--) {
234
+ const download = this.downloads[i];
235
+ const dist = download.distanceFunction();
236
+ if (dist < smallestDistance) {
237
+ smallestDistance = dist;
238
+ closest = i;
239
+ } else if (dist == smallestDistance && download.level < smallestLevel) {
240
+ smallestLevel = download.level;
241
+ closest = i
242
+ }
243
+ }
244
+ if (closest >= 0) {
245
+ const closestItem = this.downloads.splice(closest, 1).pop();
246
+ this.nextDownloads.push(closestItem);
247
+ const siblings = closestItem.getSiblings();
248
+ for (let i = this.downloads.length - 1; i >= 0; i--) {
249
+ if (siblings.includes(this.downloads[i].uuid)) {
250
+ this.nextDownloads.push(this.downloads.splice(i, 1).pop());
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ checkSize() {
257
+ const self = this;
258
+
259
+ let i = 0;
260
+
261
+ while (self.cache.size() > self.maxCachedItems && i < self.cache.size()) {
262
+ i++;
263
+ const entry = self.cache.head();
264
+ if(entry.value.getCount()>0){
265
+ self.cache.remove(entry.key);
266
+ self.cache.put(entry.key, entry.value);
267
+ }else{
268
+ self.cache.remove(entry.key);
269
+ if(entry.value.instancedMesh){
270
+ entry.value.instancedMesh.traverse((o) => {
271
+ if (o.material) {
272
+ // dispose materials
273
+ if (o.material.length) {
274
+ for (let i = 0; i < o.material.length; ++i) {
275
+ o.material[i].dispose();
276
+ }
277
+ }
278
+ else {
279
+ o.material.dispose()
280
+ }
281
+ }
282
+ if (o.geometry) {
283
+ // dispose geometry
284
+ o.geometry.dispose();
285
+ }
286
+ });
287
+ }
288
+ }
289
+
290
+ }
291
+ }
292
+ }
293
+
294
+ function simplifyPath(main_path) {
295
+
296
+ var parts = main_path.split('/'),
297
+ new_path = [],
298
+ length = 0;
299
+ for (var i = 0; i < parts.length; i++) {
300
+ var part = parts[i];
301
+ if (part === '.' || part === '' || part === '..') {
302
+ if (part === '..' && length > 0) {
303
+ length--;
304
+ }
305
+ continue;
306
+ }
307
+ new_path[length++] = part;
308
+ }
309
+
310
+ if (length === 0) {
311
+ return '/';
312
+ }
313
+
314
+ var result = '';
315
+ for (var i = 0; i < length; i++) {
316
+ result += '/' + new_path[i];
317
+ }
318
+
319
+ return result;
320
+ }
321
+
322
+ export { InstancedTileLoader };
@@ -0,0 +1,41 @@
1
+
2
+
3
+ class JsonTile{
4
+ constructor(){
5
+ const self = this;
6
+ self.count = 0;
7
+ self.json;
8
+ self.instancedTiles = [];
9
+ }
10
+
11
+ addInstance(instanceTile){
12
+ this.instancedTiles.push(instanceTile);
13
+ if(this.json){
14
+ instanceTile.loadJson(this.json, this.url)
15
+ }
16
+ }
17
+
18
+
19
+
20
+ setObject(json, url){
21
+ const self = this;
22
+ self.json = json;
23
+ self.url = url;
24
+ for(let i = 0; i<self.instancedTiles.length; i++){
25
+ self.instancedTiles[i].loadJson( self.json, self.url );
26
+ }
27
+ }
28
+
29
+ getCount(){
30
+ return this.instancedTiles.length;
31
+ }
32
+ update(){
33
+ const self = this;
34
+ for(let i = self.instancedTiles.length-1; i>=0; i--){
35
+ if(self.instancedTiles[i].deleted){
36
+ self.instancedTiles.splice(i,1);
37
+ }
38
+ }
39
+ }
40
+
41
+ }export { JsonTile };
@@ -0,0 +1,75 @@
1
+ import * as THREE from 'three';
2
+ import { InstancedMesh } from 'three';
3
+
4
+ class MeshTile{
5
+ constructor(scene){
6
+ const self = this;
7
+ self.scene = scene;
8
+ self.instancedTiles = [];
9
+ self.instancedMesh;
10
+
11
+ self.reuseableMatrix = new THREE.Matrix4();
12
+ }
13
+ addInstance(instancedTile){
14
+ const self = this;
15
+ instancedTile.added = true;
16
+ instancedTile.listOMesh = self.instancedTiles;
17
+ self.instancedTiles.push(instancedTile);
18
+ if(self.instancedMesh){
19
+ instancedTile.loadMesh(self.instancedMesh)
20
+ }
21
+ }
22
+
23
+ addToScene(){
24
+ //this.instancedMesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
25
+ const self = this;
26
+ self.instancedMesh.setMatrixAt(0,new THREE.Matrix4());
27
+ self.instancedMesh.instanceMatrix.needsUpdate = true;
28
+ self.instancedMesh.count = 1;
29
+
30
+ self.scene.add(self.instancedMesh);
31
+ self.instancedMesh.onAfterRender = () => {
32
+ delete self.instancedMesh.onAfterRender;
33
+ self.instancedMesh.displayedOnce = true;
34
+ };
35
+ }
36
+
37
+ setObject(instancedMesh){
38
+ const self = this;
39
+ self.instancedMesh = instancedMesh;
40
+
41
+ for(let i = 0; i<self.instancedTiles.length; i++){
42
+ self.instancedTiles[i].loadMesh(self.instancedMesh)
43
+ }
44
+ }
45
+
46
+ update(){
47
+ const self = this;
48
+
49
+ for(let i = self.instancedTiles.length-1; i>=0; i--){
50
+ if(self.instancedTiles[i].deleted){
51
+ self.instancedTiles.splice(i,1);
52
+ }
53
+ }
54
+
55
+ if(!!self.instancedMesh){
56
+
57
+ self.instancedMesh.count = 0;
58
+
59
+ for(let i = 0; i<self.instancedTiles.length; i++){
60
+ self.instancedTiles[i].meshContent = self.instancedMesh;
61
+ if(self.instancedTiles[i].materialVisibility && !!self.instancedTiles[i].meshContent){
62
+ self.instancedMesh.count++;
63
+ self.instancedMesh.setMatrixAt(self.instancedMesh.count-1, self.reuseableMatrix.multiplyMatrices(self.instancedTiles[i].getWorldMatrix(), self.instancedMesh.baseMatrix) )
64
+ }
65
+
66
+ }
67
+ self.instancedMesh.instanceMatrix.needsUpdate = true;
68
+ }
69
+ }
70
+
71
+ getCount(){
72
+ return this.instancedTiles.length;
73
+ }
74
+
75
+ }export { MeshTile };
package/webpack.config.js CHANGED
@@ -3,124 +3,134 @@ const webpack = require("webpack");
3
3
  const HtmlWebpackPlugin = require("html-webpack-plugin");
4
4
  const MiniCssExtractPlugin = require("mini-css-extract-plugin");
5
5
  const TerserPlugin = require('terser-webpack-plugin');
6
+ const CopyPlugin = require('copy-webpack-plugin');
6
7
 
7
8
  const sourceDir = path.resolve(__dirname);
8
9
  const DEFAULT_WEBPACK_PORT = 3001;
9
10
 
10
11
  module.exports = {
11
- mode: "development",
12
- entry: './src/index.js',
12
+ mode: "development",
13
+ entry: './src/index.js',
13
14
 
14
- output: {
15
- filename: "index.js",
16
- path: path.resolve(__dirname, 'dist'),
17
- },
15
+ output: {
16
+ filename: "index.js",
17
+ path: path.resolve(__dirname, 'dist'),
18
+ },
18
19
 
19
- plugins: [
20
- new webpack.ProgressPlugin(),
21
- new HtmlWebpackPlugin({
22
- template: "index.html",
23
- filename: "index.html",
24
- }),
25
- new MiniCssExtractPlugin({
26
- filename: "[name].bundle.[hash].css"
27
- })
28
- ],
20
+ plugins: [
21
+ new webpack.ProgressPlugin(),
22
+ new HtmlWebpackPlugin({
23
+ template: "index.html",
24
+ filename: "index.html",
25
+ }),
26
+ new MiniCssExtractPlugin({
27
+ filename: "[name].bundle.[hash].css"
28
+ }),
29
+ new CopyPlugin({
30
+ patterns: [
31
+ { from: "meshes", to: "meshes" }
32
+ ],
33
+ }),
34
+ ],
29
35
 
30
- devtool: "source-map",
36
+ devtool: "source-map",
31
37
 
32
- module: {
33
- rules: [
34
- {
35
- test: /.(js)$/,
36
- include: [sourceDir],
37
- loader: "babel-loader",
38
- options: {
39
- cacheDirectory: true,
40
- presets: [
41
- ["@babel/preset-env", {
42
- "useBuiltIns": "entry",
43
- "corejs": 3
44
- }]
45
- ]
46
- }
47
- },
48
- {
49
- test: /\.s[ac]ss$/,
50
- use: [
51
- // inserts <link/> tag to generated CSS file, inside the generated index.html
52
- {loader: MiniCssExtractPlugin.loader},
53
- "css-loader",
54
- "resolve-url-loader",
55
- // Compiles Sass to CSS
56
- {
57
- loader: "sass-loader",
58
- options: {
59
- sourceMap: true // resolve-url-loader needs sourcemaps, regardless of devtool (cf. resolve-url-loader's README)
60
- }
61
- }
62
- ]
63
- },
64
- {
65
- test: /\.css$/i,
66
- use: [
67
- {loader: MiniCssExtractPlugin.loader},
68
- "style-loader",
69
- "css-loader",
38
+ module: {
39
+ rules: [
40
+ {
41
+ test: /.(js)$/,
42
+ include: [sourceDir],
43
+ loader: "babel-loader",
44
+ options: {
45
+ cacheDirectory: true,
46
+ presets: [
47
+ ["@babel/preset-env", {
48
+ "useBuiltIns": "entry",
49
+ "corejs": 3
50
+ }]
70
51
  ]
71
- },
72
- {
73
- test: /\.html$/i,
74
- loader: "html-loader"
75
- },
76
- { // loader for fonts
77
- test: /\.(eot|woff|woff2|otf|ttf|svg)$/,
78
- use: [{
79
- loader: "file-loader",
52
+ }
53
+ },
54
+ {
55
+ test: /\.s[ac]ss$/,
56
+ use: [
57
+ // inserts <link/> tag to generated CSS file, inside the generated index.html
58
+ { loader: MiniCssExtractPlugin.loader },
59
+ "css-loader",
60
+ "resolve-url-loader",
61
+ // Compiles Sass to CSS
62
+ {
63
+ loader: "sass-loader",
80
64
  options: {
81
- name: "fonts/[name].[ext]"
65
+ sourceMap: true // resolve-url-loader needs sourcemaps, regardless of devtool (cf. resolve-url-loader's README)
82
66
  }
83
- }]
84
- },
85
- {
86
- test: /\.(png|svg|jpg|jpeg|gif)$/i,
87
- type: 'asset/resource',
88
- },
89
- ],
90
- },
91
- optimization: {
92
- minimizer: [new TerserPlugin({
93
- parallel: true,
94
- terserOptions: {
95
- ecma: undefined,
96
- parse: {},
97
- compress: {},
98
- mangle: true, // Note `mangle.properties` is `false` by default.
99
- module: false,
100
- // Deprecated
101
- output: null,
102
- format: null,
103
- toplevel: false,
104
- nameCache: null,
105
- ie8: false,
106
- keep_classnames: undefined,
107
- keep_fnames: false,
108
- safari10: false,
109
- },
110
- exclude: []
111
- })]
112
- },
113
- devServer: {
114
- hot: true,
115
- open: true,
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
- }
67
+ }
68
+ ]
69
+ },
70
+ {
71
+ test: /\.css$/i,
72
+ use: [
73
+ { loader: MiniCssExtractPlugin.loader },
74
+ "style-loader",
75
+ "css-loader",
76
+ ]
77
+ },
78
+ {
79
+ test: /\.html$/i,
80
+ loader: "html-loader"
81
+ },
82
+ { // loader for fonts
83
+ test: /\.(eot|woff|woff2|otf|ttf|svg)$/,
84
+ use: [{
85
+ loader: "file-loader",
86
+ options: {
87
+ name: "fonts/[name].[ext]"
88
+ }
89
+ }]
90
+ },
91
+ { // loader for shaders
92
+ test: /\.glsl$/,
93
+ loader: 'webpack-glsl-loader'
94
+ },
95
+ {
96
+ test: /\.(png|svg|jpg|jpeg|gif)$/i,
97
+ type: 'asset/resource',
98
+ },
99
+ ],
100
+ },
101
+ optimization: {
102
+ minimizer: [new TerserPlugin({
103
+ parallel: true,
104
+ terserOptions: {
105
+ ecma: undefined,
106
+ parse: {},
107
+ compress: {},
108
+ mangle: true, // Note `mangle.properties` is `false` by default.
109
+ module: false,
110
+ // Deprecated
111
+ output: null,
112
+ format: null,
113
+ toplevel: false,
114
+ nameCache: null,
115
+ ie8: false,
116
+ keep_classnames: undefined,
117
+ keep_fnames: false,
118
+ safari10: false,
119
+ },
120
+ exclude: []
121
+ })]
122
+ },
123
+ devServer: {
124
+ hot: true,
125
+ open: true,
126
+ port: DEFAULT_WEBPACK_PORT
127
+ },
128
+ resolve: {
129
+ extensions: [".js", ".jsx", ".json", ".ts", ".tsx"],// other stuff
130
+ fallback: {
131
+ "fs": false,
132
+ "path": require.resolve("path-browserify")
124
133
  }
125
-
134
+ }
135
+
126
136
  };