@woosh/meep-engine 2.72.0 → 2.73.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.
- package/build/meep.cjs +34 -185
- package/build/meep.min.js +1 -1
- package/build/meep.module.js +34 -185
- package/package.json +3 -2
- package/src/engine/graphics/ecs/camera/FrustumProjector.js +31 -182
- package/src/engine/graphics/texture/virtual/v2/NOTES.md +11 -1
- package/src/engine/graphics/texture/virtual/v2/ShaderUsage.js +7 -5
- package/src/engine/graphics/texture/virtual/v2/SparseTexture.js +182 -0
- package/src/engine/graphics/texture/virtual/v2/TextureTile.js +31 -0
- package/src/engine/graphics/texture/virtual/v2/TileLoader.js +215 -0
- package/src/engine/graphics/texture/virtual/v2/UsageDebugView.js +24 -11
- package/src/engine/graphics/texture/virtual/v2/UsageMetadata.js +77 -145
- package/src/engine/graphics/texture/virtual/v2/UsagePyramidDebugView.js +27 -14
- package/src/engine/graphics/texture/virtual/v2/VirtualTextureManager.js +13 -4
- package/src/engine/graphics/texture/virtual/v2/compose_finger_print.js +13 -0
- package/src/engine/graphics/texture/virtual/v2/finger_print_to_tile_index.js +37 -0
- package/src/engine/graphics/texture/virtual/v2/prototype.js +29 -11
- package/src/engine/graphics/texture/virtual/v2/tile_index_to_finger_print.js +31 -0
- package/src/engine/graphics/clouds/MaterialTransformer.js +0 -0
- package/src/engine/graphics/clouds/TerrainCloudsPlugin.js +0 -0
- package/src/engine/graphics/clouds/cs_build_fragment_shader.js +0 -0
- package/src/engine/graphics/clouds/cs_build_vertex_shader.js +0 -0
- package/src/engine/graphics/ecs/camera/TiltCameraController.js +0 -69
- package/src/engine/graphics/ecs/camera/is_valid_distance_value.js +0 -11
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { WebGLRenderTarget } from "three";
|
|
2
|
+
import { BitSet } from "../../../../../core/binary/BitSet.js";
|
|
3
|
+
import { Cache } from "../../../../../core/cache/Cache.js";
|
|
4
|
+
import { array_copy } from "../../../../../core/collection/array/array_copy.js";
|
|
5
|
+
import { passThrough, strictEquals } from "../../../../../core/function/Functions.js";
|
|
6
|
+
import { compose_finger_print } from "./compose_finger_print.js";
|
|
7
|
+
import { TileLoader } from "./TileLoader.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* How much extra data to store in cache (in bytes)
|
|
11
|
+
* @type {number}
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_CACHE_SIZE = 16 * 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
export class SparseTexture {
|
|
16
|
+
#page_texture = new WebGLRenderTarget(1, 1);
|
|
17
|
+
|
|
18
|
+
#page_texture_size = [1, 1];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @type {TextureTile[]}
|
|
23
|
+
*/
|
|
24
|
+
#resident_tiles = [];
|
|
25
|
+
|
|
26
|
+
#page_slot_occupancy = new BitSet();
|
|
27
|
+
|
|
28
|
+
#tile_resolution = 128;
|
|
29
|
+
|
|
30
|
+
#residency_tile_capacity = 0;
|
|
31
|
+
|
|
32
|
+
#loader = new TileLoader();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @type {AssetManager|null}
|
|
37
|
+
*/
|
|
38
|
+
#asset_manager = null;
|
|
39
|
+
|
|
40
|
+
set asset_manager(v) {
|
|
41
|
+
this.#asset_manager = v
|
|
42
|
+
|
|
43
|
+
this.#loader.asset_manager = v;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#update_residency_capacity() {
|
|
47
|
+
const tile_resolution = this.#tile_resolution;
|
|
48
|
+
const size = this.#page_texture_size;
|
|
49
|
+
|
|
50
|
+
const x = size[0] / tile_resolution;
|
|
51
|
+
const y = size[1] / tile_resolution;
|
|
52
|
+
|
|
53
|
+
this.#residency_tile_capacity = x * y;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @param {number} v
|
|
60
|
+
*/
|
|
61
|
+
set tile_resolution(v) {
|
|
62
|
+
this.#tile_resolution = v;
|
|
63
|
+
|
|
64
|
+
this.#update_residency_capacity();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Tiles that are not currently resident live here
|
|
69
|
+
* key is the tile's "fingerprint"
|
|
70
|
+
* @type {Cache<number,TextureTile>}
|
|
71
|
+
*/
|
|
72
|
+
#tile_cache = new Cache({
|
|
73
|
+
keyHashFunction: passThrough,
|
|
74
|
+
keyEqualityFunction: strictEquals,
|
|
75
|
+
maxWeight: DEFAULT_CACHE_SIZE,
|
|
76
|
+
valueWeigher: () => this.#tile_resolution * this.#tile_resolution * 4
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
#find_eviction_target_index() {
|
|
80
|
+
// find least-recently used
|
|
81
|
+
const tiles = this.#resident_tiles;
|
|
82
|
+
const count = tiles.length;
|
|
83
|
+
|
|
84
|
+
let max = -1;
|
|
85
|
+
let max_index = -1;
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < count; i++) {
|
|
88
|
+
const tile = tiles[i];
|
|
89
|
+
|
|
90
|
+
if (tile.last_used_time > max) {
|
|
91
|
+
max = tile.last_used_time;
|
|
92
|
+
max_index = i;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return max_index;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#evict_one() {
|
|
100
|
+
const index = this.#find_eviction_target_index();
|
|
101
|
+
|
|
102
|
+
if (index === -1) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this.#remove_resident_tile(index);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
*
|
|
111
|
+
* @param {number} index
|
|
112
|
+
* @returns {boolean}
|
|
113
|
+
*/
|
|
114
|
+
#remove_resident_tile(index) {
|
|
115
|
+
const [removed] = this.#resident_tiles.splice(index, 1);
|
|
116
|
+
|
|
117
|
+
const pageSlot = removed.page_slot;
|
|
118
|
+
|
|
119
|
+
// mark slot as empty for next tile
|
|
120
|
+
this.#page_slot_occupancy.clear(pageSlot);
|
|
121
|
+
|
|
122
|
+
// push into cache
|
|
123
|
+
this.#tile_cache.put(removed.finder_print, removed);
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
*
|
|
130
|
+
* @param {number[]} v
|
|
131
|
+
*/
|
|
132
|
+
set page_texture_size(v) {
|
|
133
|
+
array_copy(
|
|
134
|
+
v, 0,
|
|
135
|
+
this.#page_texture_size, 0,
|
|
136
|
+
2
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
this.#page_texture.dispose();
|
|
140
|
+
this.#page_texture.setSize(this.#page_texture[0], this.#page_texture[1]);
|
|
141
|
+
|
|
142
|
+
this.#update_residency_capacity();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
*
|
|
147
|
+
* @param {UsageMetadata} usage
|
|
148
|
+
*/
|
|
149
|
+
update_usage(usage) {
|
|
150
|
+
const max_mip = usage.max_mip_level;
|
|
151
|
+
|
|
152
|
+
for (let mip = 0; mip < max_mip; mip++) {
|
|
153
|
+
|
|
154
|
+
const resolution = 1 << mip;
|
|
155
|
+
|
|
156
|
+
for (let y = 0; y < resolution; y++) {
|
|
157
|
+
for (let x = 0; x < resolution; x++) {
|
|
158
|
+
|
|
159
|
+
const count = usage.getCountBy(mip, x, y);
|
|
160
|
+
|
|
161
|
+
if (count === 0) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const fingerprint = compose_finger_print(mip, x, y);
|
|
166
|
+
|
|
167
|
+
this.#loader.enqueue(fingerprint);
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.#loader.update_usage(usage);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
dispose() {
|
|
178
|
+
this.#page_texture.dispose();
|
|
179
|
+
this.#tile_cache.clear();
|
|
180
|
+
this.#page_slot_occupancy.reset();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class TextureTile {
|
|
2
|
+
|
|
3
|
+
finder_print = 0;
|
|
4
|
+
|
|
5
|
+
last_used_time = 0;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Currently occupied page slot, only valid when page is resident
|
|
9
|
+
* @type {number}
|
|
10
|
+
*/
|
|
11
|
+
page_slot = 0;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @type {Sampler2D|null}
|
|
16
|
+
*/
|
|
17
|
+
data = null;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param {TextureTile} other
|
|
22
|
+
* @returns {boolean}
|
|
23
|
+
*/
|
|
24
|
+
equals(other) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
hash() {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
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 "./compose_finger_print.js";
|
|
7
|
+
import { decompose_finger_print } from "./finger_print_to_tile_index.js";
|
|
8
|
+
import { TextureTile } from "./TextureTile.js";
|
|
9
|
+
|
|
10
|
+
export class TileLoader {
|
|
11
|
+
/**
|
|
12
|
+
* Where the tiles are stored
|
|
13
|
+
* @type {string}
|
|
14
|
+
*/
|
|
15
|
+
#root_path = "";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @type {AssetManager}
|
|
20
|
+
*/
|
|
21
|
+
#asset_manager = null;
|
|
22
|
+
|
|
23
|
+
set asset_manager(v){
|
|
24
|
+
this.#asset_manager = v;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* How many tiles can be loaded at the same time
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
31
|
+
#concurrency = 4;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Tiles that are currently being loaded
|
|
35
|
+
* @readonly
|
|
36
|
+
* @type {number[]}
|
|
37
|
+
*/
|
|
38
|
+
#in_flight = [];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Contains fingerprints
|
|
42
|
+
* @readonly
|
|
43
|
+
* @type {number[]}
|
|
44
|
+
*/
|
|
45
|
+
#queue = [];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @readonly
|
|
49
|
+
*/
|
|
50
|
+
on = {
|
|
51
|
+
/**
|
|
52
|
+
* @type {Signal<TextureTile>}
|
|
53
|
+
*/
|
|
54
|
+
loaded: new Signal()
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* @param {number} fingerprint
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
#queue_remove(fingerprint) {
|
|
64
|
+
assert.isNonNegativeInteger(fingerprint, 'fingerprint');
|
|
65
|
+
|
|
66
|
+
const i = this.#queue.indexOf(fingerprint);
|
|
67
|
+
|
|
68
|
+
if (i === -1) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.#queue.splice(i, 1);
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#queue_add(fingerprint) {
|
|
78
|
+
assert.isNonNegativeInteger(fingerprint, 'fingerprint');
|
|
79
|
+
|
|
80
|
+
if (this.is_queued(fingerprint)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.#queue.push(fingerprint);
|
|
85
|
+
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
*
|
|
91
|
+
* @param {number} fingerprint
|
|
92
|
+
*/
|
|
93
|
+
enqueue(fingerprint) {
|
|
94
|
+
if (this.#in_flight.indexOf(fingerprint) !== -1) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// TODO limit queue size, if the queue grows too large we may end up with completely irrelevant tiles being loaded minutes after they were actually relevant
|
|
99
|
+
if (this.#queue_add(fingerprint) === false) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.#prod();
|
|
104
|
+
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
*
|
|
110
|
+
* @param {number} fingerprint
|
|
111
|
+
* @returns {boolean}
|
|
112
|
+
*/
|
|
113
|
+
is_queued(fingerprint) {
|
|
114
|
+
assert.isNonNegativeInteger(fingerprint, 'fingerprint');
|
|
115
|
+
|
|
116
|
+
return this.#queue.indexOf(fingerprint) !== -1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
*
|
|
121
|
+
* @param {UsageMetadata} usage
|
|
122
|
+
*/
|
|
123
|
+
#sort_queue_by_usage(usage) {
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
*
|
|
127
|
+
* @param {number} fingerprint
|
|
128
|
+
*/
|
|
129
|
+
const score = (fingerprint) => -usage.getCountByFingerprint(fingerprint);
|
|
130
|
+
|
|
131
|
+
arrayQuickSort(
|
|
132
|
+
this.#queue,
|
|
133
|
+
score, null,
|
|
134
|
+
0, this.#queue.length - 1
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
*
|
|
141
|
+
* @param {UsageMetadata} usage
|
|
142
|
+
*/
|
|
143
|
+
update_usage(usage) {
|
|
144
|
+
this.#sort_queue_by_usage(usage);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
*
|
|
149
|
+
* @param {number} mip
|
|
150
|
+
* @param {number} x
|
|
151
|
+
* @param {number} y
|
|
152
|
+
* @returns {string}
|
|
153
|
+
*/
|
|
154
|
+
#build_file_name(mip, x, y) {
|
|
155
|
+
|
|
156
|
+
assert.isNonNegativeInteger(mip, 'mip');
|
|
157
|
+
assert.isNonNegativeInteger(x, 'x');
|
|
158
|
+
assert.isNonNegativeInteger(y, 'y');
|
|
159
|
+
|
|
160
|
+
return `${mip}-${x}-${y}.png`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Initializes load of the next element in the queue if there is capacity
|
|
165
|
+
*/
|
|
166
|
+
#prod() {
|
|
167
|
+
while (this.#in_flight.length < this.#concurrency && this.#queue.length > 0) {
|
|
168
|
+
const fingerprint = this.#queue.pop();
|
|
169
|
+
const { mip, x, y } = decompose_finger_print(fingerprint);
|
|
170
|
+
|
|
171
|
+
this.#load(mip, x, y);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
*
|
|
177
|
+
* @param {number} mip
|
|
178
|
+
* @param {number} x
|
|
179
|
+
* @param {number} y
|
|
180
|
+
* @returns {Promise<TextureTile>}
|
|
181
|
+
*/
|
|
182
|
+
#load(mip, x, y) {
|
|
183
|
+
const file_name = this.#build_file_name(mip, x, y);
|
|
184
|
+
const finger_print = compose_finger_print(mip, x, y);
|
|
185
|
+
|
|
186
|
+
this.#queue_remove(finger_print);
|
|
187
|
+
this.#in_flight.push(finger_print);
|
|
188
|
+
|
|
189
|
+
return this.#asset_manager.promise(`${this.#root_path}/${file_name}`, GameAssetType.Image)
|
|
190
|
+
.then(asset => {
|
|
191
|
+
|
|
192
|
+
const sampler = asset.create();
|
|
193
|
+
|
|
194
|
+
const tile = new TextureTile();
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
tile.data = sampler;
|
|
198
|
+
tile.finder_print = finger_print;
|
|
199
|
+
|
|
200
|
+
this.on.loaded.send1(tile);
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
return tile;
|
|
204
|
+
})
|
|
205
|
+
.finally(() => {
|
|
206
|
+
|
|
207
|
+
const index = this.#in_flight.indexOf(finger_print);
|
|
208
|
+
this.#in_flight.splice(index, 1);
|
|
209
|
+
|
|
210
|
+
this.#prod();
|
|
211
|
+
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -4,6 +4,7 @@ import EmptyView from "../../../../../view/elements/EmptyView.js";
|
|
|
4
4
|
export class UsageDebugView extends EmptyView {
|
|
5
5
|
#tileCount = new LabelView("")
|
|
6
6
|
#tiles = new EmptyView();
|
|
7
|
+
#mip_levels = 0;
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
constructor(opt) {
|
|
@@ -22,29 +23,41 @@ export class UsageDebugView extends EmptyView {
|
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
set mip_levels(v) {
|
|
27
|
+
this.#mip_levels = v;
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
/**
|
|
26
31
|
*
|
|
27
32
|
* @param {UsageMetadata} usage
|
|
28
33
|
*/
|
|
29
34
|
set usage(usage) {
|
|
30
35
|
|
|
31
|
-
const tileCount = usage.tile_count;
|
|
32
|
-
|
|
33
|
-
this.#tileCount.updateText(`Tile Count: ${tileCount}`);
|
|
34
36
|
|
|
35
37
|
this.#tiles.removeAllChildren();
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
let tileCount = 0;
|
|
40
|
+
|
|
41
|
+
for (let mip = 0; mip < this.#mip_levels; mip++) {
|
|
42
|
+
const resolution = 1 << mip;
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const y = (finger_print >> 8) & 0xFF;
|
|
44
|
-
const texture_id = (finger_print) & 0xFF;
|
|
44
|
+
for (let y = 0; y < resolution; y++) {
|
|
45
|
+
for (let x = 0; x < resolution; x++) {
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
const count = usage.getCountBy(mip, x, y);
|
|
48
|
+
|
|
49
|
+
if (count <= 0) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.#tiles.addChild(new LabelView(`ID: ${0} Level: ${mip} X: ${x} Y: ${y} USAGE: ${count}`));
|
|
54
|
+
|
|
55
|
+
tileCount++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
47
58
|
}
|
|
59
|
+
|
|
60
|
+
this.#tileCount.updateText(`Tile Count: ${tileCount}`);
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
|