@woosh/meep-engine 2.138.17 → 2.138.18
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/package.json +1 -1
- package/src/engine/ecs/grid/HeightMap2AOMap.d.ts.map +1 -1
- package/src/engine/ecs/grid/HeightMap2AOMap.js +3 -2
- package/src/engine/graphics/shaders/AmbientOcclusionShader.d.ts.map +1 -1
- package/src/engine/graphics/shaders/AmbientOcclusionShader.js +20 -5
- package/src/engine/graphics/util/build_max_height_pyramid.d.ts +21 -14
- package/src/engine/graphics/util/build_max_height_pyramid.d.ts.map +1 -1
- package/src/engine/graphics/util/build_max_height_pyramid.js +107 -64
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"description": "Pure JavaScript game engine. Fully featured and production ready.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "Alexander Goldring",
|
|
8
|
-
"version": "2.138.
|
|
8
|
+
"version": "2.138.18",
|
|
9
9
|
"main": "build/meep.module.js",
|
|
10
10
|
"module": "build/meep.module.js",
|
|
11
11
|
"exports": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeightMap2AOMap.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/grid/HeightMap2AOMap.js"],"names":[],"mappings":";AA6BA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qEAPW,SAAS,cACT,OAAO,cACP,OAAO,aAEP,MAAM,GACJ,SAAS,
|
|
1
|
+
{"version":3,"file":"HeightMap2AOMap.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/grid/HeightMap2AOMap.js"],"names":[],"mappings":";AA6BA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qEAPW,SAAS,cACT,OAAO,cACP,OAAO,aAEP,MAAM,GACJ,SAAS,CA8CrB;0BAzFyB,6CAA6C;wBAJiB,OAAO"}
|
|
@@ -57,9 +57,10 @@ function heightMap2AOMap(
|
|
|
57
57
|
const width = resultSize.x;
|
|
58
58
|
const height = resultSize.y;
|
|
59
59
|
|
|
60
|
-
// Pre-pass: max-height pyramid for Hi-Z ray traversal
|
|
60
|
+
// Pre-pass: max-height pyramid for Hi-Z ray traversal.
|
|
61
|
+
// The pyramid is a DataTexture2DArray; each Hi-Z mip is one layer.
|
|
61
62
|
const pyramid_texture = build_max_height_pyramid(heightMap);
|
|
62
|
-
const max_mip_level = pyramid_texture.
|
|
63
|
+
const max_mip_level = pyramid_texture.userData.mipCount - 1;
|
|
63
64
|
|
|
64
65
|
// AO bake
|
|
65
66
|
const shader_ao = AmbientOcclusionShader({ numRays: rayCount });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AmbientOcclusionShader.d.ts","sourceRoot":"","sources":["../../../../../src/engine/graphics/shaders/AmbientOcclusionShader.js"],"names":[],"mappings":";AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH;IAFyB,OAAO,GAArB,MAAM;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"AmbientOcclusionShader.d.ts","sourceRoot":"","sources":["../../../../../src/engine/graphics/shaders/AmbientOcclusionShader.js"],"names":[],"mappings":";AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH;IAFyB,OAAO,GAArB,MAAM;;;;;;;;;;;;;;;;;;;;;;EA4ThB;wBA/X8B,OAAO"}
|
|
@@ -93,10 +93,15 @@ function AmbientOcclusionShader({ numRays = DEFAULT_NUM_RAYS } = {}) {
|
|
|
93
93
|
|
|
94
94
|
fragmentShader: `
|
|
95
95
|
// three.js auto-prefixes float/int precision; declare highp for
|
|
96
|
-
// the heightmap sampler so R32F reads keep full precision
|
|
97
|
-
|
|
96
|
+
// the heightmap sampler so R32F reads keep full precision.
|
|
97
|
+
//
|
|
98
|
+
// The pyramid is stored as a DataTexture2DArray with one mip per
|
|
99
|
+
// layer (all at full mip-0 resolution; smaller mips are nearest-
|
|
100
|
+
// upscaled). This sidesteps a three.js r136 upload bug for
|
|
101
|
+
// DataTexture custom mipmap chains — see build_max_height_pyramid.
|
|
102
|
+
precision highp sampler2DArray;
|
|
98
103
|
|
|
99
|
-
uniform
|
|
104
|
+
uniform sampler2DArray heightMap;
|
|
100
105
|
uniform vec2 worldSize;
|
|
101
106
|
uniform vec2 heightMapSize;
|
|
102
107
|
uniform int maxMipLevel;
|
|
@@ -217,8 +222,18 @@ function AmbientOcclusionShader({ numRays = DEFAULT_NUM_RAYS } = {}) {
|
|
|
217
222
|
// Heightmap utilities
|
|
218
223
|
// ============================================================
|
|
219
224
|
|
|
225
|
+
// Sample mip-0 (the original heightmap) at uv. Layer 0 of the
|
|
226
|
+
// 2D array holds the un-reduced heightmap data.
|
|
220
227
|
float sample_height(vec2 uv) {
|
|
221
|
-
return
|
|
228
|
+
return texture(heightMap, vec3(uv, 0.0)).r;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Sample max-height at a specific Hi-Z level. Mip k lives in
|
|
232
|
+
// layer k, nearest-upscaled to mip-0 resolution. NearestFilter
|
|
233
|
+
// on the array snaps the layer to integer 'mip' and returns
|
|
234
|
+
// the underlying mip-k texel containing uv.
|
|
235
|
+
float sample_max_height(vec2 uv, int mip) {
|
|
236
|
+
return texture(heightMap, vec3(uv, float(mip))).r;
|
|
222
237
|
}
|
|
223
238
|
|
|
224
239
|
// Central-difference normal in world space. Faster than building
|
|
@@ -293,7 +308,7 @@ function AmbientOcclusionShader({ numRays = DEFAULT_NUM_RAYS } = {}) {
|
|
|
293
308
|
float t_exit = min(t_to_boundary.x, t_to_boundary.y) + T_EPS;
|
|
294
309
|
|
|
295
310
|
// Max occluder height in this cell at this mip
|
|
296
|
-
float max_h =
|
|
311
|
+
float max_h = sample_max_height(cell_center, mip);
|
|
297
312
|
|
|
298
313
|
// Ray height is linear in t, so the minimum across the
|
|
299
314
|
// cell is at one of the endpoints
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build a max-reduction
|
|
3
|
-
*
|
|
2
|
+
* Build a max-reduction Hi-Z height pyramid from a single-channel float
|
|
3
|
+
* heightmap sampler.
|
|
4
4
|
*
|
|
5
|
-
* Each
|
|
6
|
-
* of level k-1 it covers. A ray trace can then skip whole cells
|
|
7
|
-
* the ray's height is strictly above the cell's max-height at
|
|
8
|
-
* cell can be advanced past without checking any finer level.
|
|
5
|
+
* Each pyramid level k holds, per texel, the maximum height across the 2×2
|
|
6
|
+
* block of level k-1 it covers. A ray trace can then skip whole cells
|
|
7
|
+
* safely: if the ray's height is strictly above the cell's max-height at
|
|
8
|
+
* any level, the cell can be advanced past without checking any finer level.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Storage layout — **DataTexture2DArray, one mip per layer**, all layers at
|
|
11
|
+
* full mip-0 resolution. Smaller mips are nearest-upscaled into their layer.
|
|
12
|
+
* The shader samples mip k via `texture(heightMap, vec3(uv, float(k)))`.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* are
|
|
14
|
+
* This sidesteps the three.js r136 bug in `WebGLTextures.uploadTexture`
|
|
15
|
+
* where DataTexture custom mipmap chains are uploaded with `texSubImage2D`
|
|
16
|
+
* called with level hardcoded to 0 — every "mip" smashes the previous one
|
|
17
|
+
* into level 0 and the actual mip levels 1..N are left uninitialized. The
|
|
18
|
+
* 2DArray upload path uses one `texSubImage3D` call to level 0 covering
|
|
19
|
+
* all layers, so it works correctly.
|
|
20
|
+
*
|
|
21
|
+
* Memory cost is (mip_count × W × H × 4) bytes versus (≈ 4/3 × W × H × 4)
|
|
22
|
+
* for a true mip pyramid — about a 3× overhead, traded for correctness.
|
|
16
23
|
*
|
|
17
24
|
* Non-power-of-two heightmaps are handled by halving with floor and stopping
|
|
18
25
|
* once both dimensions reach 1.
|
|
19
26
|
*
|
|
20
27
|
* @param {Sampler2D} sampler single-channel Float32 heightmap (itemSize=1)
|
|
21
|
-
* @returns {
|
|
28
|
+
* @returns {DataTexture2DArray} R32F array texture; depth = mip count
|
|
22
29
|
*/
|
|
23
|
-
export function build_max_height_pyramid(sampler: Sampler2D):
|
|
24
|
-
import {
|
|
30
|
+
export function build_max_height_pyramid(sampler: Sampler2D): DataTexture2DArray;
|
|
31
|
+
import { DataTexture2DArray } from "three";
|
|
25
32
|
//# sourceMappingURL=build_max_height_pyramid.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build_max_height_pyramid.d.ts","sourceRoot":"","sources":["../../../../../src/engine/graphics/util/build_max_height_pyramid.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build_max_height_pyramid.d.ts","sourceRoot":"","sources":["../../../../../src/engine/graphics/util/build_max_height_pyramid.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,8DAFa,kBAAkB,CA2G9B;mCAxIM,OAAO"}
|
|
@@ -1,100 +1,143 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ClampToEdgeWrapping,
|
|
3
|
-
|
|
3
|
+
DataTexture2DArray,
|
|
4
4
|
FloatType,
|
|
5
5
|
NearestFilter,
|
|
6
|
-
NearestMipmapNearestFilter,
|
|
7
6
|
RedFormat
|
|
8
7
|
} from "three";
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
|
-
* Build a max-reduction
|
|
12
|
-
*
|
|
10
|
+
* Build a max-reduction Hi-Z height pyramid from a single-channel float
|
|
11
|
+
* heightmap sampler.
|
|
13
12
|
*
|
|
14
|
-
* Each
|
|
15
|
-
* of level k-1 it covers. A ray trace can then skip whole cells
|
|
16
|
-
* the ray's height is strictly above the cell's max-height at
|
|
17
|
-
* cell can be advanced past without checking any finer level.
|
|
13
|
+
* Each pyramid level k holds, per texel, the maximum height across the 2×2
|
|
14
|
+
* block of level k-1 it covers. A ray trace can then skip whole cells
|
|
15
|
+
* safely: if the ray's height is strictly above the cell's max-height at
|
|
16
|
+
* any level, the cell can be advanced past without checking any finer level.
|
|
18
17
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* Storage layout — **DataTexture2DArray, one mip per layer**, all layers at
|
|
19
|
+
* full mip-0 resolution. Smaller mips are nearest-upscaled into their layer.
|
|
20
|
+
* The shader samples mip k via `texture(heightMap, vec3(uv, float(k)))`.
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
* are
|
|
22
|
+
* This sidesteps the three.js r136 bug in `WebGLTextures.uploadTexture`
|
|
23
|
+
* where DataTexture custom mipmap chains are uploaded with `texSubImage2D`
|
|
24
|
+
* called with level hardcoded to 0 — every "mip" smashes the previous one
|
|
25
|
+
* into level 0 and the actual mip levels 1..N are left uninitialized. The
|
|
26
|
+
* 2DArray upload path uses one `texSubImage3D` call to level 0 covering
|
|
27
|
+
* all layers, so it works correctly.
|
|
28
|
+
*
|
|
29
|
+
* Memory cost is (mip_count × W × H × 4) bytes versus (≈ 4/3 × W × H × 4)
|
|
30
|
+
* for a true mip pyramid — about a 3× overhead, traded for correctness.
|
|
25
31
|
*
|
|
26
32
|
* Non-power-of-two heightmaps are handled by halving with floor and stopping
|
|
27
33
|
* once both dimensions reach 1.
|
|
28
34
|
*
|
|
29
35
|
* @param {Sampler2D} sampler single-channel Float32 heightmap (itemSize=1)
|
|
30
|
-
* @returns {
|
|
36
|
+
* @returns {DataTexture2DArray} R32F array texture; depth = mip count
|
|
31
37
|
*/
|
|
32
38
|
export function build_max_height_pyramid(sampler) {
|
|
33
39
|
const w0 = sampler.width;
|
|
34
40
|
const h0 = sampler.height;
|
|
35
41
|
|
|
42
|
+
// Step 1: build the conventional mip pyramid in JS
|
|
36
43
|
/** @type {Array<{data: Float32Array, width: number, height: number}>} */
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (c > m) m = c;
|
|
75
|
-
if (d > m) m = d;
|
|
76
|
-
|
|
77
|
-
curData[y * curW + x] = m;
|
|
44
|
+
const mips = [];
|
|
45
|
+
mips.push({ data: sampler.data, width: w0, height: h0 });
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
let prevW = w0;
|
|
49
|
+
let prevH = h0;
|
|
50
|
+
let prevData = sampler.data;
|
|
51
|
+
|
|
52
|
+
while (prevW > 1 || prevH > 1) {
|
|
53
|
+
const curW = Math.max(1, prevW >> 1);
|
|
54
|
+
const curH = Math.max(1, prevH >> 1);
|
|
55
|
+
const curData = new Float32Array(curW * curH);
|
|
56
|
+
|
|
57
|
+
const lastSrcX = prevW - 1;
|
|
58
|
+
const lastSrcY = prevH - 1;
|
|
59
|
+
|
|
60
|
+
for (let y = 0; y < curH; y++) {
|
|
61
|
+
const sy0 = y << 1;
|
|
62
|
+
const sy1 = Math.min(sy0 + 1, lastSrcY);
|
|
63
|
+
const row0 = sy0 * prevW;
|
|
64
|
+
const row1 = sy1 * prevW;
|
|
65
|
+
|
|
66
|
+
for (let x = 0; x < curW; x++) {
|
|
67
|
+
const sx0 = x << 1;
|
|
68
|
+
const sx1 = Math.min(sx0 + 1, lastSrcX);
|
|
69
|
+
|
|
70
|
+
const a = prevData[row0 + sx0];
|
|
71
|
+
const b = prevData[row0 + sx1];
|
|
72
|
+
const c = prevData[row1 + sx0];
|
|
73
|
+
const d = prevData[row1 + sx1];
|
|
74
|
+
|
|
75
|
+
let m = a > b ? a : b;
|
|
76
|
+
if (c > m) m = c;
|
|
77
|
+
if (d > m) m = d;
|
|
78
|
+
|
|
79
|
+
curData[y * curW + x] = m;
|
|
80
|
+
}
|
|
78
81
|
}
|
|
82
|
+
|
|
83
|
+
mips.push({ data: curData, width: curW, height: curH });
|
|
84
|
+
|
|
85
|
+
prevW = curW;
|
|
86
|
+
prevH = curH;
|
|
87
|
+
prevData = curData;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Step 2: pack each mip nearest-upscaled to w0 × h0 as a layer of the
|
|
92
|
+
// array texture. Layer k holds mip k blown up; sampling with NearestFilter
|
|
93
|
+
// at any uv returns mip k's value at that uv exactly.
|
|
94
|
+
const depth = mips.length;
|
|
95
|
+
const layerSize = w0 * h0;
|
|
96
|
+
const layered = new Float32Array(depth * layerSize);
|
|
97
|
+
|
|
98
|
+
for (let k = 0; k < depth; k++) {
|
|
99
|
+
const mip = mips[k];
|
|
100
|
+
const mw = mip.width;
|
|
101
|
+
const mh = mip.height;
|
|
102
|
+
const mdata = mip.data;
|
|
103
|
+
|
|
104
|
+
const layerOffset = k * layerSize;
|
|
105
|
+
|
|
106
|
+
if (mw === w0 && mh === h0) {
|
|
107
|
+
// Level 0 — direct copy
|
|
108
|
+
layered.set(mdata, layerOffset);
|
|
109
|
+
continue;
|
|
79
110
|
}
|
|
80
111
|
|
|
81
|
-
|
|
112
|
+
// Nearest-upscale: dst (x, y) → src (floor(x * mw / w0), floor(y * mh / h0))
|
|
113
|
+
// Works for both POT and NPO2 reductions.
|
|
114
|
+
for (let y = 0; y < h0; y++) {
|
|
115
|
+
const sy = Math.min(mh - 1, ((y * mh) / h0) | 0);
|
|
116
|
+
const srcRow = sy * mw;
|
|
117
|
+
const dstRow = layerOffset + y * w0;
|
|
82
118
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
for (let x = 0; x < w0; x++) {
|
|
120
|
+
const sx = Math.min(mw - 1, ((x * mw) / w0) | 0);
|
|
121
|
+
layered[dstRow + x] = mdata[srcRow + sx];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
86
124
|
}
|
|
87
125
|
|
|
88
|
-
const tex = new
|
|
89
|
-
tex.
|
|
90
|
-
tex.
|
|
91
|
-
tex.
|
|
126
|
+
const tex = new DataTexture2DArray(layered, w0, h0, depth);
|
|
127
|
+
tex.format = RedFormat;
|
|
128
|
+
tex.type = FloatType;
|
|
129
|
+
tex.internalFormat = 'R32F';
|
|
130
|
+
tex.minFilter = NearestFilter;
|
|
92
131
|
tex.magFilter = NearestFilter;
|
|
93
132
|
tex.wrapS = ClampToEdgeWrapping;
|
|
94
133
|
tex.wrapT = ClampToEdgeWrapping;
|
|
95
|
-
tex.
|
|
134
|
+
tex.generateMipmaps = false;
|
|
96
135
|
tex.flipY = false;
|
|
97
136
|
tex.needsUpdate = true;
|
|
98
137
|
|
|
138
|
+
// Expose the depth so callers can plumb `maxMipLevel` to the shader
|
|
139
|
+
// without re-deriving it
|
|
140
|
+
tex.userData.mipCount = depth;
|
|
141
|
+
|
|
99
142
|
return tex;
|
|
100
143
|
}
|