@woosh/meep-engine 2.71.0 → 2.72.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.
@@ -0,0 +1,221 @@
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
+ 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";
6
+
7
+ const DEFAULT_CAPACITY = 64;
8
+
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;
19
+
20
+ // figure out resolution of this mip level
21
+ const mip_resolution = 1 << mip;
22
+
23
+ // this is basically converting something like 0100 to 0011;
24
+ const mip_mask = mip_resolution - 1;
25
+
26
+ // where data for this mip starts
27
+ const index_offset = split_by_2(mip_mask);
28
+
29
+ return index_offset + x + y * mip_resolution;
30
+ }
31
+
32
+ export class UsageMetadata {
33
+
34
+ #cursor = 0;
35
+
36
+ #lookup = new Uint32Array(0);
37
+ #counts = new Uint32Array(0);
38
+
39
+ #counts_intrinsic = new Uint32Array(0);
40
+
41
+ #capacity = 0;
42
+
43
+ constructor() {
44
+ this.#ensureCapacity(DEFAULT_CAPACITY);
45
+ }
46
+
47
+ /**
48
+ *
49
+ * @param {number} min_size
50
+ */
51
+ #ensureCapacity(min_size) {
52
+ if (this.#capacity >= min_size) {
53
+ return;
54
+ }
55
+
56
+ const buffer = new ArrayBuffer(min_size * 2 * 4);
57
+
58
+ this.#lookup = new Uint32Array(buffer, 0, min_size);
59
+ this.#counts = new Uint32Array(buffer, min_size * 4, min_size);
60
+
61
+ this.#capacity = min_size;
62
+ }
63
+
64
+ get tile_count() {
65
+ return this.#cursor;
66
+ }
67
+
68
+ getTile(index) {
69
+ return this.#lookup[index];
70
+ }
71
+
72
+ getCount(index) {
73
+ return this.#counts[index];
74
+ }
75
+
76
+ clear() {
77
+ this.#cursor = 0;
78
+ this.#counts.fill(0);
79
+ }
80
+
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
+ );
96
+ }
97
+
98
+ sortByCount() {
99
+ arrayQuickSort(this.#counts, passThrough, undefined, 0, this.#cursor - 1, (counts, i, j) => {
100
+ this.swap(i, j);
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Given existing usage, award same usage to all tiles up the chain all the way to the root
106
+ * This helps avoid popping and ensures that when there's not enough space - at least the higher level LOD tiles are available
107
+ * @param {number} bias added to the ancestor tiles to make them more likely to be loaded
108
+ * @returns {number} number of tiles added
109
+ */
110
+ promoteAncestors(bias = 0) {
111
+ const start_cursor = this.#cursor;
112
+
113
+ for (let tile_index = 0; tile_index < this.#cursor; tile_index++) {
114
+ let finger_print = this.#lookup[tile_index];
115
+
116
+ let level = (finger_print >> 24) & 0xFF;
117
+
118
+ let index = tile_index;
119
+
120
+ while (level > 0) {
121
+
122
+ const x = (finger_print >> 16) & 0xFF;
123
+ const y = (finger_print >> 8) & 0xFF;
124
+ const texture_id = (finger_print) & 0xFF;
125
+
126
+ // get higher-level lod
127
+ const parent_level = level - 1;
128
+ const parent_x = x >>> 1;
129
+ const parent_y = y >>> 1;
130
+
131
+ const parent_finger_print =
132
+ (parent_level << 24)
133
+ | (parent_x << 16)
134
+ | (parent_y << 8)
135
+ | (texture_id << 0);
136
+
137
+ const parent_index = this.bindTileIndex(parent_finger_print);
138
+
139
+ const push_value = this.#counts[tile_index] + bias;
140
+ const parent_count = this.#counts[parent_index];
141
+
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;
148
+
149
+
150
+ //
151
+ finger_print = parent_finger_print;
152
+ level = parent_level;
153
+
154
+ index = parent_index;
155
+ }
156
+ }
157
+
158
+ return this.#cursor - start_cursor;
159
+ }
160
+
161
+ /**
162
+ *
163
+ * @param {number} finger_print
164
+ * @returns {number}
165
+ */
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
+ }
176
+
177
+ return index;
178
+ }
179
+
180
+ /**
181
+ *
182
+ * @param {Uint8Array} data usage texture data
183
+ */
184
+ fromTexture(data) {
185
+ this.clear();
186
+
187
+ const data_size = data.length;
188
+
189
+ this.#ensureCapacity(data_size >> 2);
190
+
191
+ const counts = this.#counts;
192
+
193
+ for (let offset = 0; offset < data_size; offset += 4) {
194
+
195
+ const mip_level = data[offset];
196
+
197
+ // we create a mask to make sure we don't end up with invalid tile position values
198
+ const max_tile_value = (1 << mip_level) - 1;
199
+
200
+ const tile_x = data[offset + 1] & max_tile_value;
201
+ const tile_y = data[offset + 2] & max_tile_value;
202
+
203
+ const texture_id = data[offset + 3];
204
+
205
+ const finger_print =
206
+ (mip_level << 24)
207
+ | (tile_x << 16)
208
+ | (tile_y << 8)
209
+ | (texture_id << 0);
210
+
211
+ if (finger_print === 0) {
212
+ continue;
213
+ }
214
+
215
+ const index = this.bindTileIndex(finger_print);
216
+
217
+ counts[index]++;
218
+ }
219
+
220
+ }
221
+ }
@@ -0,0 +1,239 @@
1
+ import { assert } from "../../../../../core/assert.js";
2
+ import LabelView from "../../../../../view/common/LabelView.js";
3
+ import EmptyView from "../../../../../view/elements/EmptyView.js";
4
+
5
+ const ABSOLUTE_CSS = {
6
+ position: "absolute",
7
+ top: 0,
8
+ left: 0
9
+ };
10
+
11
+ class TextureLODView extends EmptyView {
12
+
13
+ /**
14
+ *
15
+ * @type {View[]}
16
+ */
17
+ #tiles = [];
18
+
19
+ #resolution = [1, 1];
20
+
21
+ constructor({
22
+ resolution = [1, 1],
23
+ tile_size = 8
24
+ }) {
25
+ super({ css: ABSOLUTE_CSS });
26
+
27
+ this.css({
28
+ background: "rgba(255,255,255,0.05)"
29
+ });
30
+
31
+ this.#resolution = resolution;
32
+
33
+ const width = resolution[0];
34
+ const height = resolution[1];
35
+
36
+ assert.isNonNegativeInteger(width, 'width');
37
+ assert.isNonNegativeInteger(height, 'height');
38
+
39
+ for (let j = 0; j < height; j++) {
40
+ for (let i = 0; i < width; i++) {
41
+ const tileView = new EmptyView({ css: ABSOLUTE_CSS });
42
+
43
+ // const flipped_y = height - j - 1;
44
+ tileView.position.set(i * tile_size, j * tile_size);
45
+ tileView.size.set(tile_size, tile_size);
46
+
47
+ this.clearTile(tileView);
48
+
49
+ this.#tiles.push(tileView)
50
+
51
+ this.addChild(tileView);
52
+ }
53
+ }
54
+
55
+ this.size.set(width * tile_size, height * tile_size);
56
+
57
+
58
+ const label = new LabelView(`${width} x ${height}`, {
59
+ css: {
60
+ ...ABSOLUTE_CSS,
61
+ color: '#FFFFFF',
62
+ textShadow: '0 0 2px black',
63
+ whiteSpace: "pre"
64
+ }
65
+ });
66
+
67
+ label.position.set(this.size.x + 4, (this.size.y - 20) * 0.5);
68
+
69
+ this.addChild(label);
70
+ }
71
+
72
+ clear() {
73
+ const resolution = this.#resolution;
74
+ const count = resolution[0] * resolution[1];
75
+ for (let i = 0; i < count; i++) {
76
+
77
+ const view = this.#tiles[i];
78
+
79
+ this.clearTile(view);
80
+ }
81
+ }
82
+
83
+ /**
84
+ *
85
+ * @param {View} view
86
+ */
87
+ clearTile(view) {
88
+
89
+ view.css({
90
+ background: "none"
91
+ });
92
+
93
+ view.visible = false;
94
+ }
95
+
96
+ getTile(x, y) {
97
+ assert.isNonNegativeInteger(x, 'x');
98
+ assert.isNonNegativeInteger(y, 'y');
99
+
100
+ const resolution = this.#resolution;
101
+
102
+ // assert.lessThan(x, resolution[0]);
103
+ // assert.lessThan(y, resolution[1]);
104
+
105
+ const width = resolution[0];
106
+
107
+ const view = this.#tiles[x + width * y];
108
+
109
+ if (view === undefined) {
110
+ debugger
111
+ }
112
+
113
+ return view;
114
+
115
+ }
116
+
117
+ }
118
+
119
+ export class UsagePyramidDebugView extends EmptyView {
120
+ #levels = [];
121
+ #resolution = [1, 1];
122
+ #tile_size = 0;
123
+
124
+ #used_set = [];
125
+ #used_set_size = 0;
126
+
127
+ #image_url = null
128
+
129
+ setImageURL(url) {
130
+ this.#image_url = url;
131
+
132
+ this.#applyImageURL();
133
+ }
134
+
135
+ #applyImageURL() {
136
+
137
+ const background = {
138
+ backgroundImage: this.#image_url === null ? "none" : `url(${this.#image_url})`,
139
+ backgroundSize: "contain"
140
+ };
141
+
142
+ this.#levels.forEach(l => {
143
+
144
+ l.css(background);
145
+
146
+ });
147
+ }
148
+
149
+ setTextureParameters(resolution, tile_size) {
150
+ if (this.#resolution[0] === resolution && this.#resolution[1] === resolution && this.#tile_size === tile_size) {
151
+ return;
152
+ }
153
+
154
+ this.#tile_size = tile_size;
155
+ this.#resolution = [resolution, resolution];
156
+
157
+ this.#levels.splice(0, this.#levels.length);
158
+ this.removeAllChildren();
159
+
160
+ let size = resolution / tile_size;
161
+
162
+ assert.isNonNegativeInteger(size, 'size');
163
+
164
+ while (size > 0) {
165
+ const lod = new TextureLODView({
166
+ resolution: [size, size],
167
+ });
168
+
169
+ lod.css({
170
+ border: "1px solid rgba(0,0,0,0.7)"
171
+ });
172
+
173
+ this.#levels.push(lod);
174
+
175
+ this.addChild(lod);
176
+
177
+ size >>>= 1;
178
+ }
179
+
180
+ this.#levels.reverse();
181
+
182
+ let y = 0;
183
+ for (let i = 0; i < this.#levels.length; i++) {
184
+ const level = this.#levels[i];
185
+
186
+ level.position.set(2, y);
187
+
188
+ y += level.size.y + 4;
189
+ }
190
+
191
+ this.#applyImageURL();
192
+ }
193
+
194
+ /**
195
+ *
196
+ * @param {UsageMetadata} usage
197
+ */
198
+ set usage(usage) {
199
+
200
+ for (let i = 0; i < this.#used_set_size; i++) {
201
+ const finger_print = this.#used_set[i];
202
+
203
+
204
+ const mip = (finger_print >> 24) & 0xFF;
205
+ const x = (finger_print >> 16) & 0xFF;
206
+ const y = (finger_print >> 8) & 0xFF;
207
+
208
+ const mipView = this.#levels[mip];
209
+
210
+ const tile = mipView.getTile(x, y);
211
+
212
+ mipView.clearTile(tile);
213
+ }
214
+ this.#used_set_size = 0;
215
+
216
+ const tile_count = usage.tile_count;
217
+
218
+ for (let i = 0; i < tile_count; i++) {
219
+ const finger_print = usage.getTile(i);
220
+
221
+ const mip = (finger_print >> 24) & 0xFF;
222
+ const x = (finger_print >> 16) & 0xFF;
223
+ const y = (finger_print >> 8) & 0xFF;
224
+
225
+ const mipView = this.#levels[mip];
226
+
227
+ const tile = mipView.getTile(x, y);
228
+
229
+ tile.css({
230
+ background: "rgba(255,0,0,0.8)"
231
+ });
232
+
233
+ tile.visible = true;
234
+
235
+ this.#used_set[this.#used_set_size] = finger_print;
236
+ this.#used_set_size++;
237
+ }
238
+ }
239
+ }
@@ -0,0 +1,248 @@
1
+ import {
2
+ ClampToEdgeWrapping,
3
+ GLSL3,
4
+ Matrix4,
5
+ NearestFilter,
6
+ NoBlending,
7
+ RawShaderMaterial,
8
+ RGBAIntegerFormat,
9
+ UnsignedByteType,
10
+ Vector2,
11
+ Vector4,
12
+ WebGLRenderTarget
13
+ } from "three";
14
+ import { assert } from "../../../../../core/assert.js";
15
+ import { clamp } from "../../../../../core/math/clamp.js";
16
+ import { max2 } from "../../../../../core/math/max2.js";
17
+ import { renderScreenSpace } from "../../../render/utils/renderScreenSpace.js";
18
+ import { Sampler2D } from "../../sampler/Sampler2D.js";
19
+ import { fragment, vertex } from "./ShaderUsage.js";
20
+ import { UsageMetadata } from "./UsageMetadata.js";
21
+
22
+ const usage_material_uniforms = {
23
+ "u_mt_params": { value: new Vector2(0.1, 1) },
24
+ /**
25
+ * Format: VT_WIDTH, VT_HEIGHT, VT_TILE_SIZE, VT_ID
26
+ * - VT_WIDTH : width of the virtual texture
27
+ * - VT_HEIGHT : height of the virtual texture
28
+ * - VT_TILE_SIZE : resolution of a single tile
29
+ * - VT_ID : multiple different virtual textures may be used, this identifies current texture
30
+ */
31
+ "u_mt_tex": { value: new Vector4(1, 1, 1, 3) },
32
+ "u_mt_viewport_size": { value: new Vector2(1, 1) },
33
+ "modelViewMatrix": { value: new Matrix4() },
34
+ "projectionMatrix": { value: new Matrix4() }
35
+ };
36
+ const usage_material = new RawShaderMaterial({
37
+
38
+ uniforms: usage_material_uniforms,
39
+ vertexShader: vertex(),
40
+ fragmentShader: fragment(),
41
+ glslVersion: GLSL3
42
+
43
+ });
44
+ usage_material.blending = NoBlending;
45
+
46
+
47
+ const clear_usage_material = new RawShaderMaterial({
48
+ vertexShader: `
49
+ precision mediump float;
50
+ in vec2 uv;
51
+ void main() {
52
+
53
+ gl_Position = vec4( (uv - 0.5)*2.0, 0.0, 1.0 );
54
+
55
+ }`,
56
+ fragmentShader: `
57
+
58
+ precision mediump int;
59
+ out uvec4 out_decoded;
60
+
61
+ void main(){
62
+ out_decoded = uvec4(0u);
63
+ }
64
+ `,
65
+ glslVersion: GLSL3
66
+ });
67
+
68
+ export class VirtualTextureManager {
69
+
70
+ #usage_buffer = new WebGLRenderTarget(1, 1, {
71
+ wrapT: ClampToEdgeWrapping,
72
+ wrapS: ClampToEdgeWrapping,
73
+ generateMipmaps: false,
74
+ minFilter: NearestFilter,
75
+ magFilter: NearestFilter,
76
+ format: RGBAIntegerFormat,
77
+ depthBuffer: true,
78
+ stencilBuffer: false,
79
+ type: UnsignedByteType,
80
+ internalFormat: "RGBA8UI"
81
+ });
82
+
83
+ #usage_pixel_data = Sampler2D.uint8(4, 1, 1);
84
+
85
+ #viewport_resolution = [1, 1];
86
+
87
+ /**
88
+ *
89
+ * @type {number}
90
+ */
91
+ #usage_resolution_scale = 32;
92
+
93
+ #usage_metadata = new UsageMetadata();
94
+
95
+ #texture_id = 3;
96
+ #texture_resolution = 65536;
97
+ #tile_resolution = 256;
98
+
99
+ get texture_resolution() {
100
+ return this.#texture_resolution;
101
+ }
102
+
103
+ get tile_resolution() {
104
+ return this.#tile_resolution;
105
+ }
106
+
107
+ /**
108
+ * Used to fetch higher resolution tiles
109
+ * @type {number}
110
+ */
111
+ #usage_texture_bias = 0.1;
112
+
113
+ /**
114
+ *
115
+ * @param {number} resolution
116
+ * @param {number} tile_size
117
+ * @param {number} texture_id
118
+ */
119
+ setTextureParameters(
120
+ resolution,
121
+ tile_size,
122
+ texture_id
123
+ ) {
124
+ this.#texture_id = texture_id;
125
+ this.#texture_resolution = resolution;
126
+ this.#tile_resolution = tile_size;
127
+
128
+ this.#initialize_usage_uniforms();
129
+ }
130
+
131
+ get usage_metadata() {
132
+ return this.#usage_metadata;
133
+ }
134
+
135
+ get usage_data() {
136
+ return this.#usage_pixel_data;
137
+ }
138
+
139
+ constructor() {
140
+ this.#initialize_usage_uniforms();
141
+ }
142
+
143
+ #initialize_usage_uniforms() {
144
+
145
+ const uniforms = usage_material.uniforms;
146
+
147
+ const max_mip_level = Math.log2(this.#texture_resolution / this.#tile_resolution);
148
+
149
+ if (!Number.isInteger(max_mip_level)) {
150
+ throw new Error(`texture resolution must be a power-of-two multiple of tile resolution`);
151
+ }
152
+
153
+ uniforms.u_mt_params.value.set(
154
+ this.#usage_texture_bias,
155
+ max_mip_level
156
+ );
157
+
158
+ uniforms.u_mt_tex.value.set(
159
+ this.#texture_resolution,
160
+ this.#texture_resolution,
161
+ this.#tile_resolution,
162
+ this.#texture_id
163
+ );
164
+ }
165
+
166
+ /**
167
+ *
168
+ * @param {number} x
169
+ * @param {number} y
170
+ */
171
+ setViewportResolution(x, y) {
172
+ assert.isNumber(x, 'x');
173
+ assert.isNumber(y, 'y');
174
+
175
+ assert.notNaN(x, 'x');
176
+ assert.notNaN(y, 'y');
177
+
178
+ const _x = max2(1, Math.floor(x));
179
+ const _y = max2(1, Math.floor(y));
180
+
181
+ const viewport = this.#viewport_resolution;
182
+
183
+ if (_x === viewport[0] && _y === viewport[1]) {
184
+ // no change, do nothing
185
+ return;
186
+ }
187
+
188
+ viewport[0] = _x;
189
+ viewport[1] = _y;
190
+
191
+ const usage_resolution_x = clamp(Math.floor(_x / this.#usage_resolution_scale), 1, _x);
192
+ const usage_resolution_y = clamp(Math.floor(_y / this.#usage_resolution_scale), 1, _y);
193
+
194
+ this.#usage_buffer.setSize(usage_resolution_x, usage_resolution_y);
195
+ this.#usage_pixel_data.resize(usage_resolution_x, usage_resolution_y, true);
196
+
197
+ const usage_material_uniforms = usage_material.uniforms;
198
+ usage_material_uniforms.u_mt_viewport_size.value.set(_x / usage_resolution_x, _y / usage_resolution_y);
199
+
200
+ }
201
+
202
+ /**
203
+ *
204
+ * @param {WebGLRenderer} renderer
205
+ * @param {Scene} scene
206
+ * @param {Camera} camera
207
+ */
208
+ updateUsage(renderer, scene, camera) {
209
+
210
+ const usage_buffer = this.#usage_buffer;
211
+
212
+ const uniforms = usage_material.uniforms;
213
+
214
+ uniforms.modelViewMatrix.value.multiplyMatrices(camera.matrixWorldInverse, camera.matrixWorld);
215
+ uniforms.projectionMatrix.value.copy(camera.projectionMatrix);
216
+
217
+ // remember existing state
218
+ const _old_render_target = renderer.getRenderTarget();
219
+ const _auto_clear = renderer.autoClear;
220
+
221
+ const _old_material = scene.overrideMaterial;
222
+
223
+ renderer.autoClear = false;
224
+ renderer.setRenderTarget(usage_buffer);
225
+ scene.overrideMaterial = usage_material;
226
+
227
+ // clear out usage texture
228
+ renderScreenSpace(renderer, clear_usage_material);
229
+
230
+ renderer.clearDepth();
231
+
232
+ renderer.render(scene, camera);
233
+
234
+ scene.overrideMaterial = _old_material;
235
+
236
+ renderer.readRenderTargetPixels(usage_buffer, 0, 0, usage_buffer.width, usage_buffer.height, this.#usage_pixel_data.data);
237
+
238
+ renderer.setRenderTarget(_old_render_target);
239
+ renderer.autoClear = _auto_clear;
240
+
241
+ this.#usage_metadata.fromTexture(this.#usage_pixel_data.data);
242
+ this.#usage_metadata.promoteAncestors(1);
243
+ }
244
+
245
+ dispose() {
246
+ this.#usage_buffer.dispose();
247
+ }
248
+ }