@woosh/meep-engine 2.72.0 → 2.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/meep.cjs +38 -189
  3. package/build/meep.min.js +1 -1
  4. package/build/meep.module.js +38 -189
  5. package/package.json +3 -2
  6. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.js +2 -1
  7. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +4 -5
  8. package/src/engine/asset/loaders/image/png/PNGReader.js +23 -19
  9. package/src/engine/graphics/ecs/camera/FrustumProjector.js +31 -182
  10. package/src/engine/graphics/generate_halton_jitter.js +21 -0
  11. package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.js +3 -20
  12. package/src/engine/graphics/texture/virtual/v2/NOTES.md +11 -1
  13. package/src/engine/graphics/texture/virtual/v2/ShaderUsage.js +7 -5
  14. package/src/engine/graphics/texture/virtual/v2/SparseTexture.js +291 -0
  15. package/src/engine/graphics/texture/virtual/v2/TileLoader.js +232 -0
  16. package/src/engine/graphics/texture/virtual/v2/UsageMetadata.js +118 -134
  17. package/src/engine/graphics/texture/virtual/v2/VirtualTextureManager.js +73 -9
  18. package/src/engine/graphics/texture/virtual/v2/debug/ResidencyDebugView.js +58 -0
  19. package/src/engine/graphics/texture/virtual/v2/debug/UsageDebugView.js +63 -0
  20. package/src/engine/graphics/texture/virtual/v2/{UsagePyramidDebugView.js → debug/UsagePyramidDebugView.js} +27 -23
  21. package/src/engine/graphics/texture/virtual/v2/prototype.js +62 -19
  22. package/src/engine/graphics/texture/virtual/v2/tile/TextureTile.js +31 -0
  23. package/src/engine/graphics/texture/virtual/v2/tile/compose_finger_print.js +23 -0
  24. package/src/engine/graphics/texture/virtual/v2/tile/compose_tile_address.js +22 -0
  25. package/src/engine/graphics/texture/virtual/v2/tile/decompose_finger_print.js +12 -0
  26. package/src/engine/graphics/texture/virtual/v2/tile/finger_print_to_tile_address.js +16 -0
  27. package/src/engine/graphics/texture/virtual/v2/tile/tile_address_to_finger_print.js +35 -0
  28. package/src/view/CSS_ABSOLUTE_POSITIONING.js +5 -0
  29. package/src/engine/graphics/clouds/MaterialTransformer.js +0 -0
  30. package/src/engine/graphics/clouds/TerrainCloudsPlugin.js +0 -0
  31. package/src/engine/graphics/clouds/cs_build_fragment_shader.js +0 -0
  32. package/src/engine/graphics/clouds/cs_build_vertex_shader.js +0 -0
  33. package/src/engine/graphics/ecs/camera/TiltCameraController.js +0 -69
  34. package/src/engine/graphics/ecs/camera/is_valid_distance_value.js +0 -11
  35. package/src/engine/graphics/texture/virtual/v2/UsageDebugView.js +0 -51
@@ -0,0 +1,232 @@
1
+ import { assert } from "../../../../../core/assert.js";
2
+ import { arrayQuickSort } from "../../../../../core/collection/array/arrayQuickSort.js";
3
+ import Signal from "../../../../../core/events/signal/Signal.js";
4
+ import { AssetManager } from "../../../../asset/AssetManager.js";
5
+ import { GameAssetType } from "../../../../asset/GameAssetType.js";
6
+ import { compose_finger_print } from "./tile/compose_finger_print.js";
7
+ import { decompose_finger_print } from "./tile/decompose_finger_print.js";
8
+ import { TextureTile } from "./tile/TextureTile.js";
9
+
10
+ export class TileLoader {
11
+ /**
12
+ * Where the tiles are stored
13
+ * @type {string}
14
+ */
15
+ #root_path = "";
16
+
17
+ set path(v) {
18
+ this.#root_path = v;
19
+ }
20
+
21
+ /**
22
+ * When queue gets larger than this, we start discarding elements
23
+ * @type {number}
24
+ */
25
+ queue_limit = Infinity;
26
+
27
+ /**
28
+ *
29
+ * @type {AssetManager}
30
+ */
31
+ #asset_manager = null;
32
+
33
+ set asset_manager(v) {
34
+ this.#asset_manager = v;
35
+ }
36
+
37
+ /**
38
+ * How many tiles can be loaded at the same time
39
+ * @type {number}
40
+ */
41
+ #concurrency = 2;
42
+
43
+ /**
44
+ * Tiles that are currently being loaded
45
+ * @readonly
46
+ * @type {number[]}
47
+ */
48
+ #in_flight = [];
49
+
50
+ /**
51
+ * Contains fingerprints
52
+ * @readonly
53
+ * @type {number[]}
54
+ */
55
+ #queue = [];
56
+
57
+ /**
58
+ * @readonly
59
+ */
60
+ on = {
61
+ /**
62
+ * @type {Signal<TextureTile>}
63
+ */
64
+ loaded: new Signal()
65
+ };
66
+
67
+
68
+ /**
69
+ *
70
+ * @param {number} fingerprint
71
+ * @returns {boolean}
72
+ */
73
+ #queue_remove(fingerprint) {
74
+ assert.isNonNegativeInteger(fingerprint, 'fingerprint');
75
+
76
+ const i = this.#queue.indexOf(fingerprint);
77
+
78
+ if (i === -1) {
79
+ return false;
80
+ }
81
+
82
+ this.#queue.splice(i, 1);
83
+
84
+ return true;
85
+ }
86
+
87
+ #queue_add(fingerprint) {
88
+ assert.isNonNegativeInteger(fingerprint, 'fingerprint');
89
+
90
+ if (this.is_queued(fingerprint)) {
91
+ return false;
92
+ }
93
+
94
+ this.#queue.push(fingerprint);
95
+
96
+ return true;
97
+ }
98
+
99
+ /**
100
+ *
101
+ * @param {number} fingerprint
102
+ */
103
+ enqueue(fingerprint) {
104
+ if (this.#in_flight.indexOf(fingerprint) !== -1) {
105
+ return false;
106
+ }
107
+
108
+ if (this.#queue_add(fingerprint) === false) {
109
+ return false;
110
+ }
111
+
112
+ this.#prod();
113
+
114
+ return true;
115
+ }
116
+
117
+ /**
118
+ *
119
+ * @param {number} fingerprint
120
+ * @returns {boolean}
121
+ */
122
+ is_queued(fingerprint) {
123
+ assert.isNonNegativeInteger(fingerprint, 'fingerprint');
124
+
125
+ return this.#queue.indexOf(fingerprint) !== -1;
126
+ }
127
+
128
+ /**
129
+ *
130
+ * @param {UsageMetadata} usage
131
+ */
132
+ #sort_queue_by_usage(usage) {
133
+
134
+ /**
135
+ *
136
+ * @param {number} fingerprint
137
+ */
138
+ const score = (fingerprint) => -usage.getCountByFingerprint(fingerprint);
139
+
140
+ arrayQuickSort(
141
+ this.#queue,
142
+ score, null,
143
+ 0, this.#queue.length - 1
144
+ );
145
+
146
+ }
147
+
148
+ /**
149
+ *
150
+ * @param {UsageMetadata} usage
151
+ */
152
+ update_usage(usage) {
153
+ this.#sort_queue_by_usage(usage);
154
+
155
+ // truncate the queue if necessary
156
+ const queue_overflow = this.#queue.length - this.queue_limit;
157
+ if (queue_overflow > 0) {
158
+ this.#queue.splice(this.queue_limit, queue_overflow);
159
+ }
160
+ }
161
+
162
+ /**
163
+ *
164
+ * @param {number} mip
165
+ * @param {number} x
166
+ * @param {number} y
167
+ * @returns {string}
168
+ */
169
+ #build_file_name(mip, x, y) {
170
+
171
+ assert.isNonNegativeInteger(mip, 'mip');
172
+ assert.isNonNegativeInteger(x, 'x');
173
+ assert.isNonNegativeInteger(y, 'y');
174
+
175
+ return `${mip}-${x}-${y}.png`;
176
+ }
177
+
178
+ /**
179
+ * Initializes load of the next element in the queue if there is capacity
180
+ */
181
+ #prod() {
182
+ while (this.#in_flight.length < this.#concurrency && this.#queue.length > 0) {
183
+ const fingerprint = this.#queue[0];
184
+ const { mip, x, y } = decompose_finger_print(fingerprint);
185
+
186
+ this.#load(mip, x, y);
187
+ }
188
+ }
189
+
190
+ /**
191
+ *
192
+ * @param {number} mip
193
+ * @param {number} x
194
+ * @param {number} y
195
+ * @returns {Promise<TextureTile>}
196
+ */
197
+ #load(mip, x, y) {
198
+ const file_name = this.#build_file_name(mip, x, y);
199
+ const finger_print = compose_finger_print(mip, x, y);
200
+
201
+ this.#queue_remove(finger_print);
202
+ this.#in_flight.push(finger_print);
203
+
204
+ return this.#asset_manager.promise(`${this.#root_path}/${file_name}`, GameAssetType.Image)
205
+ .then(asset => {
206
+
207
+ const sampler = asset.create();
208
+
209
+ const tile = new TextureTile();
210
+
211
+
212
+ tile.data = sampler;
213
+ tile.finder_print = finger_print;
214
+
215
+ this.on.loaded.send1(tile);
216
+
217
+
218
+ return tile;
219
+ }, (reason) => {
220
+ console.error(`Failed to load tile ${file_name}, reason:${reason}`);
221
+ })
222
+ .finally(() => {
223
+
224
+ const index = this.#in_flight.indexOf(finger_print);
225
+ this.#in_flight.splice(index, 1);
226
+
227
+ this.#prod();
228
+
229
+ })
230
+
231
+ }
232
+ }
@@ -1,182 +1,166 @@
1
- import { array_get_index_in_range } from "../../../../../core/collection/array/array_get_index_in_range.js";
2
- import { array_swap } from "../../../../../core/collection/array/array_swap.js";
3
1
  import { arrayQuickSort } from "../../../../../core/collection/array/arrayQuickSort.js";
4
- import { passThrough } from "../../../../../core/function/Functions.js";
5
- import { split_by_2 } from "../../../../../core/geom/3d/morton/split_by_2.js";
2
+ import { compose_finger_print } from "./tile/compose_finger_print.js";
3
+ import { finger_print_to_tile_address } from "./tile/finger_print_to_tile_address.js";
6
4
 
7
- const DEFAULT_CAPACITY = 64;
8
5
 
9
- /**
10
- *
11
- * @param {number} finger_print
12
- * @returns {number}
13
- */
14
- function finger_print_to_tile_index(finger_print) {
15
- // decode fingerprint
16
- const mip = (finger_print >> 24) & 0xFF;
17
- const x = (finger_print >> 16) & 0xFF;
18
- const y = (finger_print >> 8) & 0xFF;
6
+ function compose_tile_address(mip, x, y) {
7
+ const finger_print = compose_finger_print(mip, x, y);
19
8
 
20
- // figure out resolution of this mip level
21
- const mip_resolution = 1 << mip;
9
+ return finger_print_to_tile_address(finger_print);
10
+ }
22
11
 
23
- // this is basically converting something like 0100 to 0011;
24
- const mip_mask = mip_resolution - 1;
12
+ export class UsageMetadata {
25
13
 
26
- // where data for this mip starts
27
- const index_offset = split_by_2(mip_mask);
14
+ #counts_intrinsic = new Uint32Array(0);
15
+ /**
16
+ * Stores indices of tiles with usage > 0
17
+ * @type {Uint32Array}
18
+ */
19
+ #occupancy = new Uint32Array(0);
20
+ /**
21
+ * Points at the end of the occupancy list
22
+ * @type {number}
23
+ */
24
+ #occupancy_cursor = 0;
28
25
 
29
- return index_offset + x + y * mip_resolution;
30
- }
26
+ get counts() {
27
+ return this.#counts_intrinsic;
28
+ }
31
29
 
32
- export class UsageMetadata {
30
+ #max_mip_level = 0;
33
31
 
34
- #cursor = 0;
35
32
 
36
- #lookup = new Uint32Array(0);
37
- #counts = new Uint32Array(0);
33
+ set max_mip_level(v) {
34
+ this.#max_mip_level = v;
38
35
 
39
- #counts_intrinsic = new Uint32Array(0);
36
+ this.#ensureCapacity();
37
+ }
38
+
39
+ get max_mip_level() {
40
+ return this.#max_mip_level;
41
+ }
40
42
 
41
- #capacity = 0;
42
43
 
43
- constructor() {
44
- this.#ensureCapacity(DEFAULT_CAPACITY);
44
+ get occupancy() {
45
+ return this.#occupancy;
46
+ }
47
+
48
+ get occupancy_count() {
49
+ return this.#occupancy_cursor;
45
50
  }
46
51
 
47
52
  /**
48
53
  *
49
- * @param {number} min_size
50
54
  */
51
- #ensureCapacity(min_size) {
52
- if (this.#capacity >= min_size) {
53
- return;
54
- }
55
-
56
- const buffer = new ArrayBuffer(min_size * 2 * 4);
55
+ #ensureCapacity() {
57
56
 
58
- this.#lookup = new Uint32Array(buffer, 0, min_size);
59
- this.#counts = new Uint32Array(buffer, min_size * 4, min_size);
57
+ const size = compose_tile_address(this.#max_mip_level + 1, 0, 0);
60
58
 
61
- this.#capacity = min_size;
62
- }
59
+ if (this.#counts_intrinsic.length >= size) {
60
+ // already large enough
61
+ return;
62
+ }
63
63
 
64
- get tile_count() {
65
- return this.#cursor;
64
+ this.#counts_intrinsic = new Uint32Array(size);
65
+ this.#occupancy = new Uint32Array(size);
66
66
  }
67
67
 
68
- getTile(index) {
69
- return this.#lookup[index];
70
- }
68
+ /**
69
+ *
70
+ * @param {number} fingerprint
71
+ * @returns {number}
72
+ */
73
+ getCountByFingerprint(fingerprint) {
74
+ const index = finger_print_to_tile_address(fingerprint);
71
75
 
72
- getCount(index) {
73
- return this.#counts[index];
76
+ return this.#counts_intrinsic[index];
74
77
  }
75
78
 
76
- clear() {
77
- this.#cursor = 0;
78
- this.#counts.fill(0);
79
- }
79
+ /**
80
+ *
81
+ * @param {number} mip
82
+ * @param {number} x
83
+ * @param {number} y
84
+ * @returns {number}
85
+ */
86
+ getCountBy(mip, x, y) {
87
+ const address = compose_tile_address(mip, x, y);
80
88
 
81
- swap(a, b) {
82
- array_swap(
83
- this.#lookup,
84
- a,
85
- this.#lookup,
86
- b,
87
- 1
88
- );
89
- array_swap(
90
- this.#counts,
91
- a,
92
- this.#counts,
93
- b,
94
- 1
95
- );
89
+ return this.#counts_intrinsic[address];
96
90
  }
97
91
 
98
- sortByCount() {
99
- arrayQuickSort(this.#counts, passThrough, undefined, 0, this.#cursor - 1, (counts, i, j) => {
100
- this.swap(i, j);
101
- });
92
+ clear() {
93
+ this.#counts_intrinsic.fill(0);
94
+ this.#occupancy_cursor = 0;
102
95
  }
103
96
 
104
97
  /**
105
98
  * Given existing usage, award same usage to all tiles up the chain all the way to the root
106
99
  * This helps avoid popping and ensures that when there's not enough space - at least the higher level LOD tiles are available
107
100
  * @param {number} bias added to the ancestor tiles to make them more likely to be loaded
108
- * @returns {number} number of tiles added
109
101
  */
110
- promoteAncestors(bias = 0) {
111
- const start_cursor = this.#cursor;
102
+ promoteAncestors(bias = 1) {
112
103
 
113
- for (let tile_index = 0; tile_index < this.#cursor; tile_index++) {
114
- let finger_print = this.#lookup[tile_index];
104
+ // traverse mip pyramid in reverse, so we can push counts to parents one level at a time
105
+ for (let mip = this.#max_mip_level; mip > 0; mip--) {
115
106
 
116
- let level = (finger_print >> 24) & 0xFF;
107
+ const mip_resolution = 1 << mip;
117
108
 
118
- let index = tile_index;
109
+ for (let y = 0; y < mip_resolution; y++) {
110
+ for (let x = 0; x < mip_resolution; x++) {
119
111
 
120
- while (level > 0) {
112
+ const count = this.getCountBy(mip, x, y);
121
113
 
122
- const x = (finger_print >> 16) & 0xFF;
123
- const y = (finger_print >> 8) & 0xFF;
124
- const texture_id = (finger_print) & 0xFF;
114
+ if (count <= 0) {
115
+ continue;
116
+ }
125
117
 
126
- // get higher-level lod
127
- const parent_level = level - 1;
128
- const parent_x = x >>> 1;
129
- const parent_y = y >>> 1;
118
+ // get higher-level lod
119
+ const parent_level = mip - 1;
120
+ const parent_x = x >>> 1;
121
+ const parent_y = y >>> 1;
130
122
 
131
- const parent_finger_print =
132
- (parent_level << 24)
133
- | (parent_x << 16)
134
- | (parent_y << 8)
135
- | (texture_id << 0);
123
+ const parent_index = compose_tile_address(parent_level, parent_x, parent_y);
136
124
 
137
- const parent_index = this.bindTileIndex(parent_finger_print);
125
+ const parent_count = this.#counts_intrinsic[parent_index];
138
126
 
139
- const push_value = this.#counts[tile_index] + bias;
140
- const parent_count = this.#counts[parent_index];
127
+ const expected_count = count + bias;
141
128
 
142
- if (parent_count >= push_value) {
143
- // already high, no need to do anything
144
- break;
145
- }
146
-
147
- this.#counts[parent_index] = push_value;
129
+ if (parent_count < expected_count) {
130
+ this.#counts_intrinsic[parent_index] = expected_count;
148
131
 
132
+ if (parent_count === 0) {
133
+ this.#append_to_occupancy(parent_index);
134
+ }
135
+ }
149
136
 
150
- //
151
- finger_print = parent_finger_print;
152
- level = parent_level;
153
-
154
- index = parent_index;
137
+ }
155
138
  }
156
- }
157
139
 
158
- return this.#cursor - start_cursor;
140
+ }
159
141
  }
160
142
 
161
143
  /**
162
144
  *
163
- * @param {number} finger_print
164
- * @returns {number}
145
+ * @param {number} address
165
146
  */
166
- bindTileIndex(finger_print) {
167
-
168
- const lookup = this.#lookup;
169
-
170
- let index = array_get_index_in_range(lookup, finger_print, 0, this.#cursor - 1);
171
-
172
- if (index === -1) {
173
- index = this.#cursor++;
174
- lookup[index] = finger_print;
175
- }
147
+ #append_to_occupancy(address) {
148
+ this.#occupancy[this.#occupancy_cursor] = address;
149
+ this.#occupancy_cursor++;
150
+ }
176
151
 
177
- return index;
152
+ /**
153
+ * Sort occupancy list in descending order
154
+ */
155
+ sortOccupancy() {
156
+ arrayQuickSort(
157
+ this.#occupancy,
158
+ (index) => -this.#counts_intrinsic[index], null,
159
+ 0, this.#occupancy_cursor - 1
160
+ );
178
161
  }
179
162
 
163
+
180
164
  /**
181
165
  *
182
166
  * @param {Uint8Array} data usage texture data
@@ -186,12 +170,17 @@ export class UsageMetadata {
186
170
 
187
171
  const data_size = data.length;
188
172
 
189
- this.#ensureCapacity(data_size >> 2);
173
+ this.#ensureCapacity();
190
174
 
191
- const counts = this.#counts;
175
+ const counts = this.#counts_intrinsic;
192
176
 
193
177
  for (let offset = 0; offset < data_size; offset += 4) {
194
178
 
179
+ if (data[offset + 3] === 0) {
180
+ // no data
181
+ continue;
182
+ }
183
+
195
184
  const mip_level = data[offset];
196
185
 
197
186
  // we create a mask to make sure we don't end up with invalid tile position values
@@ -200,21 +189,16 @@ export class UsageMetadata {
200
189
  const tile_x = data[offset + 1] & max_tile_value;
201
190
  const tile_y = data[offset + 2] & max_tile_value;
202
191
 
203
- const texture_id = data[offset + 3];
192
+ const tile_address = compose_tile_address(mip_level, tile_x, tile_y);
204
193
 
205
- const finger_print =
206
- (mip_level << 24)
207
- | (tile_x << 16)
208
- | (tile_y << 8)
209
- | (texture_id << 0);
194
+ const previous_count = counts[tile_address];
210
195
 
211
- if (finger_print === 0) {
212
- continue;
196
+ if (previous_count === 0) {
197
+ // first occurrence, mark occupancy
198
+ this.#append_to_occupancy(tile_address);
213
199
  }
214
200
 
215
- const index = this.bindTileIndex(finger_print);
216
-
217
- counts[index]++;
201
+ counts[tile_address] = previous_count + 1;
218
202
  }
219
203
 
220
204
  }