@woosh/meep-engine 2.48.12 → 2.48.13

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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/src/core/collection/queue/Deque.d.ts +9 -0
  3. package/src/core/collection/queue/Deque.js +3 -0
  4. package/src/core/collection/queue/Deque.spec.js +51 -0
  5. package/src/core/math/noise/create_noise_2d.js +193 -0
  6. package/src/engine/Engine.js +9 -5
  7. package/src/engine/asset/AssetManager.d.ts +10 -5
  8. package/src/engine/asset/AssetManager.js +22 -9
  9. package/src/engine/asset/PendingAsset.js +6 -6
  10. package/src/engine/asset/loaders/AssetLoader.d.ts +7 -2
  11. package/src/engine/asset/loaders/AssetLoader.js +17 -15
  12. package/src/engine/asset/loaders/GLTFAssetLoader.js +1 -1
  13. package/src/engine/asset/loaders/SoundAssetLoader.js +3 -5
  14. package/src/engine/development/performance/MetricStatistics.js +7 -5
  15. package/src/engine/development/performance/RingBufferMetric.js +2 -2
  16. package/src/engine/ecs/foliage/ecs/InstancedMeshUtils.js +14 -1
  17. package/src/engine/ecs/gui/GUIElementSystem.d.ts +1 -1
  18. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +3 -21
  19. package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +4 -2
  20. package/src/engine/graphics/texture/3d/SingleChannelSampler3D.js +146 -0
  21. package/src/engine/graphics/texture/3d/scs3d_read_2d_slice.js +26 -0
  22. package/src/engine/graphics/texture/sampler/Sampler2D.js +2 -2
  23. package/src/engine/physics/fluid/FluidField.js +153 -1
  24. package/src/engine/physics/fluid/prototype.js +201 -0
  25. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.js +67 -0
  26. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.js +17 -12
  27. package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js +14 -10
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "description": "Fully featured ECS game engine written in JavaScript",
6
6
  "type": "module",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.48.12",
8
+ "version": "2.48.13",
9
9
  "main": "build/meep.module.js",
10
10
  "module": "build/meep.module.js",
11
11
  "exports": {
@@ -7,6 +7,8 @@ export class Deque<T> {
7
7
 
8
8
  size(): number
9
9
 
10
+ isEmpty(): boolean
11
+
10
12
  remove(e: T): boolean
11
13
 
12
14
  has(e: T): boolean
@@ -17,4 +19,11 @@ export class Deque<T> {
17
19
 
18
20
  add(e: T): void
19
21
 
22
+ addFirst(e: T): void
23
+
24
+ removeFirst(): T
25
+
26
+ addLast(e: T): void
27
+
28
+ removeLast(): T
20
29
  }
@@ -1,6 +1,7 @@
1
1
  import { ceilPowerOfTwo } from "../../binary/operations/ceilPowerOfTwo.js";
2
2
  import { max2 } from "../../math/max2.js";
3
3
  import { array_copy } from "../array/copyArray.js";
4
+ import { assert } from "../../assert.js";
4
5
 
5
6
  const DEFAULT_SIZE = 16;
6
7
 
@@ -18,6 +19,8 @@ export class Deque {
18
19
  */
19
20
  constructor(minSize = DEFAULT_SIZE) {
20
21
 
22
+ assert.isNonNegativeInteger(minSize, 'minSize');
23
+
21
24
  const size = ceilPowerOfTwo(max2(DEFAULT_SIZE, minSize));
22
25
 
23
26
  /**
@@ -87,3 +87,54 @@ test('size', () => {
87
87
 
88
88
  expect(deque.size()).toBe(0);
89
89
  });
90
+
91
+ test("remove the only item", () => {
92
+ const deque = new Deque();
93
+
94
+ deque.add(7);
95
+
96
+ expect(deque.remove(7)).toBe(true);
97
+
98
+ expect(deque.size()).toBe(0);
99
+ });
100
+
101
+ test("remove first item", () => {
102
+
103
+ const deque = new Deque();
104
+
105
+ deque.add(7);
106
+ deque.add(13);
107
+
108
+ expect(deque.remove(7)).toBe(true);
109
+ expect(deque.size()).toBe(1);
110
+
111
+ expect(deque.pop()).toBe(13);
112
+ });
113
+
114
+ test("remove last item", () => {
115
+
116
+ const deque = new Deque();
117
+
118
+ deque.add(7);
119
+ deque.add(13);
120
+
121
+ expect(deque.remove(13)).toBe(true);
122
+ expect(deque.size()).toBe(1);
123
+
124
+ expect(deque.pop()).toBe(7);
125
+ });
126
+
127
+ test("remove middle item", () => {
128
+
129
+ const deque = new Deque();
130
+
131
+ deque.add(7);
132
+ deque.add(13);
133
+ deque.add(17);
134
+
135
+ expect(deque.remove(13)).toBe(true);
136
+ expect(deque.size()).toBe(2);
137
+
138
+ expect(deque.pop()).toBe(7);
139
+ expect(deque.pop()).toBe(17);
140
+ });
@@ -0,0 +1,193 @@
1
+ /*
2
+ NOTE: this implementation is adapted from https://github.com/jwagner/simplex-noise.js
3
+ */
4
+
5
+ // these #__PURE__ comments help uglifyjs with dead code removal
6
+ //
7
+ const F2 = /*#__PURE__*/ 0.5 * (Math.sqrt(3.0) - 1.0);
8
+ const G2 = /*#__PURE__*/ (3.0 - Math.sqrt(3.0)) / 6.0;
9
+ const F3 = 1.0 / 3.0;
10
+ const G3 = 1.0 / 6.0;
11
+ const F4 = /*#__PURE__*/ (Math.sqrt(5.0) - 1.0) / 4.0;
12
+ const G4 = /*#__PURE__*/ (5.0 - Math.sqrt(5.0)) / 20.0;
13
+ const grad2 = /*#__PURE__*/ new Int8Array([1, 1,
14
+ -1, 1,
15
+ 1, -1,
16
+
17
+ -1, -1,
18
+ 1, 0,
19
+ -1, 0,
20
+
21
+ 1, 0,
22
+ -1, 0,
23
+ 0, 1,
24
+
25
+ 0, -1,
26
+ 0, 1,
27
+ 0, -1]);
28
+
29
+ // double seems to be faster than single or int's
30
+ // probably because most operations are in double precision
31
+ const grad3 = /*#__PURE__*/ new Float64Array([1, 1, 0,
32
+ -1, 1, 0,
33
+ 1, -1, 0,
34
+
35
+ -1, -1, 0,
36
+ 1, 0, 1,
37
+ -1, 0, 1,
38
+
39
+ 1, 0, -1,
40
+ -1, 0, -1,
41
+ 0, 1, 1,
42
+
43
+ 0, -1, 1,
44
+ 0, 1, -1,
45
+ 0, -1, -1]);
46
+
47
+ // double is a bit quicker here as well
48
+ const grad4 = /*#__PURE__*/ new Float64Array([0, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1,
49
+ 0, -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1,
50
+ 1, 0, 1, 1, 1, 0, 1, -1, 1, 0, -1, 1, 1, 0, -1, -1,
51
+ -1, 0, 1, 1, -1, 0, 1, -1, -1, 0, -1, 1, -1, 0, -1, -1,
52
+ 1, 1, 0, 1, 1, 1, 0, -1, 1, -1, 0, 1, 1, -1, 0, -1,
53
+ -1, 1, 0, 1, -1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, -1,
54
+ 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 0,
55
+ -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 0]);
56
+
57
+
58
+ /**
59
+ * Creates a 2D noise function
60
+ * @param random the random function that will be used to build the permutation table
61
+ * @returns {function(x:number, y:number):number}
62
+ */
63
+ export function createNoise2D(random = Math.random) {
64
+ // allocate continuous chunk of memory
65
+ const buffer = new ArrayBuffer(512 * 2);
66
+
67
+ const perm = new Uint8Array(buffer, 0, 512);
68
+ buildPermutationTable(random, perm);
69
+
70
+ const permMod12 = new Uint8Array(buffer, 512, 512);
71
+
72
+ for (let i = 0; i < 512; i++) {
73
+ permMod12[i] = (perm[i] % 12) * 2;
74
+ }
75
+
76
+ return function noise2D(x, y) {
77
+ // if(!isFinite(x) || !isFinite(y)) return 0;
78
+ let n0 = 0; // Noise contributions from the three corners
79
+ let n1 = 0;
80
+ let n2 = 0;
81
+ // Skew the input space to determine which simplex cell we're in
82
+ const s = (x + y) * F2; // Hairy factor for 2D
83
+
84
+ const i = (x + s) | 0;
85
+ const j = (y + s) | 0;
86
+
87
+ const t = (i + j) * G2;
88
+ const X0 = i - t; // Unskew the cell origin back to (x,y) space
89
+ const Y0 = j - t;
90
+ const x0 = x - X0; // The x,y distances from the cell origin
91
+ const y0 = y - Y0;
92
+ // For the 2D case, the simplex shape is an equilateral triangle.
93
+ // Determine which simplex we are in.
94
+ let i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
95
+ if (x0 > y0) {
96
+ i1 = 1;
97
+ j1 = 0;
98
+ } // lower triangle, XY order: (0,0)->(1,0)->(1,1)
99
+ else {
100
+ i1 = 0;
101
+ j1 = 1;
102
+ } // upper triangle, YX order: (0,0)->(0,1)->(1,1)
103
+ // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
104
+ // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
105
+ // c = (3-sqrt(3))/6
106
+ const x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
107
+ const y1 = y0 - j1 + G2;
108
+ const x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords
109
+ const y2 = y0 - 1.0 + 2.0 * G2;
110
+ // Work out the hashed gradient indices of the three simplex corners
111
+ const ii = i & 255;
112
+ const jj = j & 255;
113
+ // Calculate the contribution from the three corners
114
+ let t0 = 0.5 - x0 * x0 - y0 * y0;
115
+
116
+ if (t0 >= 0) {
117
+ const gi0 = ii + perm[jj];
118
+ const gi01 = permMod12[gi0];
119
+
120
+ const g0x = grad2[gi01];
121
+ const g0y = grad2[gi01 + 1];
122
+
123
+ t0 *= t0;
124
+ // n0 = t0 * t0 * (grad2[gi0] * x0 + grad2[gi0 + 1] * y0); // (x,y) of grad3 used for 2D gradient
125
+ n0 = t0 * t0 * (g0x * x0 + g0y * y0);
126
+ }
127
+
128
+ let t1 = 0.5 - x1 * x1 - y1 * y1;
129
+
130
+ if (t1 >= 0) {
131
+ const gi1 = ii + i1 + perm[jj + j1];
132
+ const gi11 = permMod12[gi1];
133
+
134
+ const g1x = grad2[gi11];
135
+ const g1y = grad2[gi11 + 1];
136
+
137
+ t1 *= t1;
138
+ // n1 = t1 * t1 * (grad2[gi1] * x1 + grad2[gi1 + 1] * y1);
139
+ n1 = t1 * t1 * (g1x * x1 + g1y * y1);
140
+ }
141
+
142
+ let t2 = 0.5 - x2 * x2 - y2 * y2;
143
+
144
+ if (t2 >= 0) {
145
+ const gi2 = ii + 1 + perm[jj + 1];
146
+
147
+ const gi21 = permMod12[gi2];
148
+
149
+ const g2x = grad2[gi21];
150
+ const g2y = grad2[gi21 + 1];
151
+
152
+ t2 *= t2;
153
+ // n2 = t2 * t2 * (grad2[gi2] * x2 + grad2[gi2 + 1] * y2);
154
+ n2 = t2 * t2 * (g2x * x2 + g2y * y2);
155
+ }
156
+
157
+ // Add contributions from each corner to get the final noise value.
158
+ // The result is scaled to return values in the interval [-1,1].
159
+ return 70.0 * (n0 + n1 + n2);
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Builds a random permutation table.
165
+ * This is exported only for (internal) testing purposes.
166
+ * Do not rely on this export.
167
+ * @param {function} random
168
+ * @param {Uint8Array} p
169
+ * @private
170
+ */
171
+ export function buildPermutationTable(random, p) {
172
+ const table_size = 512;
173
+
174
+ const half_table_size = table_size >>> 1;
175
+
176
+ for (let i = 0; i < half_table_size; i++) {
177
+ p[i] = i;
178
+ }
179
+
180
+ for (let i = 0; i < half_table_size - 1; i++) {
181
+ const r = i + ~~(random() * (256 - i));
182
+ const aux = p[i];
183
+
184
+ p[i] = p[r];
185
+ p[r] = aux;
186
+ }
187
+
188
+ for (let i = 256; i < table_size; i++) {
189
+ p[i] = p[i - 256];
190
+ }
191
+
192
+ return p;
193
+ }
@@ -100,6 +100,15 @@ class Engine {
100
100
  */
101
101
  this.ticker = new Ticker();
102
102
 
103
+ /**
104
+ *
105
+ * @type {AssetManager<Engine>}
106
+ */
107
+ this.assetManager = new AssetManager({
108
+ context:this,
109
+ executor: this.executor
110
+ });
111
+
103
112
  this.__performacne_monitor = new PeriodicConsolePrinter(15, () => {
104
113
  const metrics = this.performance;
105
114
  const stats = new MetricStatistics();
@@ -198,11 +207,6 @@ class Engine {
198
207
  */
199
208
  this.storage = this.platform.getStorage();
200
209
 
201
- /**
202
- *
203
- * @type {AssetManager}
204
- */
205
- this.assetManager = new AssetManager(this);
206
210
 
207
211
  this.localization = new Localization();
208
212
  this.localization.setAssetManager(this.assetManager);
@@ -2,14 +2,19 @@ import {AssetLoader} from "./loaders/AssetLoader";
2
2
  import {Asset} from "./Asset";
3
3
  import {CrossOriginConfig} from "./CORS/CrossOriginConfig";
4
4
  import {AssetTransformer} from "./AssetTransformer";
5
- import Engine from "../Engine";
5
+ import ConcurrentExecutor from "../../core/process/executor/ConcurrentExecutor";
6
6
 
7
7
  interface PromiseOptions {
8
8
  skip_queue: boolean
9
9
  }
10
10
 
11
- export class AssetManager {
12
- constructor(engine: Engine)
11
+ interface AssetManagerOptions<CTX> {
12
+ context: CTX,
13
+ executor?: ConcurrentExecutor
14
+ }
15
+
16
+ export class AssetManager<CTX> {
17
+ constructor(options?: AssetManagerOptions<CTX>)
13
18
 
14
19
  public readonly crossOriginConfig: CrossOriginConfig
15
20
 
@@ -25,11 +30,11 @@ export class AssetManager {
25
30
 
26
31
  clear(): void
27
32
 
28
- registerLoader<T>(type: string, loader: AssetLoader<T>): Promise<AssetLoader<T>>
33
+ registerLoader<T>(type: string, loader: AssetLoader<T, CTX>): Promise<AssetLoader<T,CTX>>
29
34
 
30
35
  unregisterLoader(type: string): Promise<void>
31
36
 
32
- getLoaderByType<T>(type: string): AssetLoader<T> | undefined
37
+ getLoaderByType<T>(type: string): AssetLoader<T, CTX> | undefined
33
38
 
34
39
  registerTransformer<T extends Asset<X>, X = any>(type: string, transformer: AssetTransformer<T>): void
35
40
 
@@ -19,6 +19,7 @@ import { AssetRequest, AssetRequestFlags } from "./AssetRequest.js";
19
19
  import FastBinaryHeap from "../../core/collection/heap/FastBinaryHeap.js";
20
20
  import { AssetLoadState } from "./AssetLoadState.js";
21
21
  import { PendingAsset } from "./PendingAsset.js";
22
+ import ConcurrentExecutor from "../../core/process/executor/ConcurrentExecutor.js";
22
23
 
23
24
 
24
25
  class Response {
@@ -45,6 +46,7 @@ function get_pending_asset_priority_score(pending_asset) {
45
46
 
46
47
  /**
47
48
  * Handles loading/generating assets
49
+ * @template CTX
48
50
  * @class
49
51
  */
50
52
  export class AssetManager {
@@ -164,19 +166,30 @@ export class AssetManager {
164
166
 
165
167
  /**
166
168
  *
167
- * @type {Engine|null}
169
+ * @type {CTX|null}
168
170
  * @private
169
171
  */
170
- #engine = null;
172
+ #context = null;
171
173
 
172
174
  /**
173
175
  *
174
- * @param {Engine} engine
176
+ * @type {ConcurrentExecutor|null}
177
+ */
178
+ #executor = null;
179
+
180
+ /**
181
+ *
182
+ * @param {CTX} context
183
+ * @param {ConcurrentExecutor} executor
175
184
  * @constructor
176
185
  */
177
- constructor(engine) {
186
+ constructor({
187
+ context,
188
+ executor = new ConcurrentExecutor()
189
+ } = {}) {
178
190
 
179
- this.#engine = engine;
191
+ this.#context = context;
192
+ this.#executor = executor;
180
193
 
181
194
  }
182
195
 
@@ -185,7 +198,7 @@ export class AssetManager {
185
198
  return;
186
199
  }
187
200
 
188
- this.#engine.executor.run(this.#response_processor);
201
+ this.#executor.run(this.#response_processor);
189
202
 
190
203
  this.#is_running = true;
191
204
  }
@@ -205,7 +218,7 @@ export class AssetManager {
205
218
  await Promise.allSettled([Task.promise(this.#response_processor)]);
206
219
  }
207
220
 
208
- this.#engine.executor.removeTask(this.#response_processor);
221
+ this.#executor.removeTask(this.#response_processor);
209
222
 
210
223
  this.#is_running = false;
211
224
  }
@@ -260,7 +273,7 @@ export class AssetManager {
260
273
  throw new Error("Path must be string. Path = " + JSON.stringify(path));
261
274
  }
262
275
 
263
- if(typeof type !== "string"){
276
+ if (typeof type !== "string") {
264
277
  throw new TypeError(`type must be a string, instead was '${typeof type}'`);
265
278
  }
266
279
 
@@ -696,7 +709,7 @@ export class AssetManager {
696
709
 
697
710
  } else {
698
711
 
699
- await loader.link(this, this.#engine);
712
+ await loader.link(this, this.#context);
700
713
 
701
714
  }
702
715
 
@@ -38,16 +38,16 @@ export class PendingAsset {
38
38
  * @return {number}
39
39
  */
40
40
  get priority() {
41
- let max_priority = 0;
42
-
43
41
  const requests = this.requests;
44
- const n = requests.length;
42
+ const request_count = requests.length;
45
43
 
46
- if (n > 0) {
47
- max_priority = requests[0].priority;
44
+ if (request_count === 0) {
45
+ return 0;
48
46
  }
49
47
 
50
- for (let i = 1; i < n; i++) {
48
+ let max_priority = requests[0].priority;
49
+
50
+ for (let i = 1; i < request_count; i++) {
51
51
  const request = requests[i];
52
52
  const priority = request.priority;
53
53
 
@@ -1,5 +1,10 @@
1
1
  import {Asset} from "../Asset";
2
2
 
3
- export class AssetLoader<T> {
4
- public load(path: string, success: (asset: Asset<T>) => void, failure: (reason: any) => void, progress: (current: number, total: number) => void)
3
+ export class AssetLoader<T,CTX> {
4
+ public load(
5
+ path: string,
6
+ success: (asset: Asset<T>) => void,
7
+ failure: (reason: any) => void,
8
+ progress: (current: number, total: number) => void
9
+ )
5
10
  }
@@ -1,26 +1,28 @@
1
+ /**
2
+ * @template CTX
3
+ * @template Asset
4
+ */
1
5
  export class AssetLoader {
2
- constructor() {
3
- /**
4
- *
5
- * @type {AssetManager}
6
- */
7
- this.assetManager = null;
6
+ /**
7
+ *
8
+ * @type {AssetManager}
9
+ */
10
+ assetManager = null;
8
11
 
9
- /**
10
- *
11
- * @type {Engine}
12
- */
13
- this.engine = null;
14
- }
12
+ /**
13
+ *
14
+ * @type {Engine}
15
+ */
16
+ context = null;
15
17
 
16
18
  /**
17
19
  *
18
20
  * @param {AssetManager} assetManager
19
- * @param {Engine} engine
21
+ * @param {CTX} context
20
22
  */
21
- async link(assetManager, engine) {
23
+ async link(assetManager, context) {
22
24
  this.assetManager = assetManager;
23
- this.engine = engine;
25
+ this.context = context;
24
26
  }
25
27
 
26
28
  /**
@@ -241,7 +241,7 @@ export class GLTFAssetLoader extends AssetLoader {
241
241
  *
242
242
  * @type {GraphicsEngine}
243
243
  */
244
- const graphics = this.engine.graphics;
244
+ const graphics = this.context.graphics;
245
245
 
246
246
  /**
247
247
  *
@@ -13,7 +13,7 @@ export class SoundAssetLoader extends AssetLoader {
13
13
  *
14
14
  * @type {AudioContext}
15
15
  */
16
- this.context = context;
16
+ this.audioContext = context;
17
17
  }
18
18
 
19
19
  load(path, success, failure, progress) {
@@ -24,16 +24,14 @@ export class SoundAssetLoader extends AssetLoader {
24
24
 
25
25
  request.responseType = "arraybuffer";
26
26
 
27
- const context = this.context;
27
+ const context = this.audioContext;
28
28
 
29
29
  request.onload = function (e) {
30
30
  //decode works asynchronously, this is important to prevent lag in main thread
31
31
  context.decodeAudioData(request.response, function (buffer) {
32
32
  const byteSize = e.total;
33
33
 
34
- const asset = new Asset(function () {
35
- return buffer;
36
- }, byteSize);
34
+ const asset = new Asset(() => buffer, byteSize);
37
35
 
38
36
  success(asset);
39
37
  }, failure);
@@ -1,8 +1,10 @@
1
1
  export class MetricStatistics {
2
- constructor() {
3
- this.mean = 0;
4
- this.meadian = 0;
5
- this.max = 0;
6
- this.min = 0;
2
+ mean = 0;
3
+ median = 0;
4
+ max = 0;
5
+ min = 0;
6
+
7
+ toString() {
8
+ return `{min:${this.min}, max:${this.max}, mean:${this.mean}, median:${this.median}`;
7
9
  }
8
10
  }
@@ -49,7 +49,7 @@ export class RingBufferMetric extends AbstractMetric {
49
49
 
50
50
  // no data
51
51
  result.mean = 0;
52
- result.meadian = 0;
52
+ result.median = 0;
53
53
  result.min = 0;
54
54
  result.max = 0;
55
55
 
@@ -58,7 +58,7 @@ export class RingBufferMetric extends AbstractMetric {
58
58
  } else {
59
59
 
60
60
  result.mean = computeStatisticalMean(array, 0, data_count);
61
- result.meadian = computeStatisticalPartialMedian(array, 0, data_count);
61
+ result.median = computeStatisticalPartialMedian(array, 0, data_count);
62
62
  result.max = computeArrayMax(array, 0, data_count);
63
63
  result.min = computeArrayMin(array, 0, data_count);
64
64
 
@@ -78,6 +78,7 @@ export function convertInstancedMeshComponents2Entities(dataset, assetManager) {
78
78
  export function optimizeIndividualMeshesEntitiesToInstances(dataset, threshold = 30) {
79
79
  //get all entities that have a translation and mesh only
80
80
  const candidates = {};
81
+ const not_loaded = {};
81
82
 
82
83
  /**
83
84
  *
@@ -99,7 +100,14 @@ export function optimizeIndividualMeshesEntitiesToInstances(dataset, threshold =
99
100
  }
100
101
 
101
102
  if (!mesh.getFlag(MeshFlags.Loaded)) {
102
- console.warn(`Mesh[${entity}] not loaded - '${model_url}'`);
103
+ let misses = not_loaded[model_url];
104
+
105
+ if (misses === undefined) {
106
+ misses = not_loaded[model_url] = [];
107
+ }
108
+
109
+ misses.push(entity);
110
+
103
111
  return;
104
112
  }
105
113
 
@@ -125,6 +133,11 @@ export function optimizeIndividualMeshesEntitiesToInstances(dataset, threshold =
125
133
  // traverse entities that have only Mesh and a Transform
126
134
  dataset.traverseEntitiesExact([Mesh, Transform], visitMeshTransformEntity);
127
135
 
136
+ for (const mesh_url in not_loaded) {
137
+
138
+ console.warn(`Mesh[${mesh_url}] not loaded, ${not_loaded[mesh_url].length} entities affected`);
139
+ }
140
+
128
141
  const tasks = [];
129
142
 
130
143
  const foliage2 = new InstancedMeshComponent();
@@ -1,7 +1,7 @@
1
1
  import {System} from "../System";
2
2
  import Engine from "../../Engine";
3
3
  import View from "../../../view/View";
4
- import {GUIElement} from "./GUIElement";
4
+ import GUIElement from "./GUIElement";
5
5
 
6
6
  export default class GUIElementSystem extends System<GUIElement> {
7
7
  constructor(container: View, engine: Engine)