reze-engine 0.63.0 → 0.65.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/dist/engine.d.ts +226 -96
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1187 -732
- package/dist/graph/compile.d.ts.map +1 -1
- package/dist/graph/compile.js +4 -1
- package/dist/graph/registry.d.ts +3 -0
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +133 -0
- package/dist/graph/schema.d.ts +12 -0
- package/dist/graph/schema.d.ts.map +1 -1
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +15 -18
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model.d.ts +10 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +15 -1
- package/dist/outline-normals.d.ts +12 -0
- package/dist/outline-normals.d.ts.map +1 -0
- package/dist/outline-normals.js +100 -0
- package/dist/pmx-loader.d.ts +3 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +7 -2
- package/dist/shaders/lights.d.ts +3 -2
- package/dist/shaders/lights.d.ts.map +1 -1
- package/dist/shaders/lights.js +16 -3
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +65 -56
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +210 -0
- package/dist/shaders/passes/bloom.d.ts +4 -3
- package/dist/shaders/passes/bloom.d.ts.map +1 -1
- package/dist/shaders/passes/bloom.js +58 -87
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +122 -64
- package/dist/shaders/passes/cull.d.ts +1 -1
- package/dist/shaders/passes/cull.d.ts.map +1 -1
- package/dist/shaders/passes/cull.js +7 -3
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +32 -55
- package/dist/shaders/passes/mirror.d.ts +18 -12
- package/dist/shaders/passes/mirror.d.ts.map +1 -1
- package/dist/shaders/passes/mirror.js +59 -13
- package/dist/shaders/passes/outline.d.ts.map +1 -1
- package/dist/shaders/passes/outline.js +71 -14
- package/dist/shaders/passes/particles.d.ts +1 -1
- package/dist/shaders/passes/particles.js +1 -1
- package/dist/shaders/scene-light-api.d.ts +18 -10
- package/dist/shaders/scene-light-api.d.ts.map +1 -1
- package/dist/shaders/scene-light-api.js +58 -46
- package/dist/shadow-cascades.d.ts +47 -20
- package/dist/shadow-cascades.d.ts.map +1 -1
- package/dist/shadow-cascades.js +36 -13
- package/dist/unity/character.d.ts +59 -0
- package/dist/unity/character.d.ts.map +1 -0
- package/dist/unity/character.js +120 -0
- package/dist/unity/globals.d.ts +93 -0
- package/dist/unity/globals.d.ts.map +1 -0
- package/dist/unity/globals.js +340 -0
- package/dist/unity/host.d.ts +152 -0
- package/dist/unity/host.d.ts.map +1 -0
- package/dist/unity/host.js +529 -0
- package/dist/unity/looks.d.ts +93 -0
- package/dist/unity/looks.d.ts.map +1 -0
- package/dist/unity/looks.js +312 -0
- package/dist/unity/skin.d.ts +9 -0
- package/dist/unity/skin.d.ts.map +1 -0
- package/dist/unity/skin.js +120 -0
- package/dist/unity/stage.d.ts +155 -0
- package/dist/unity/stage.d.ts.map +1 -0
- package/dist/unity/stage.js +410 -0
- package/dist/unity/state.d.ts +22 -0
- package/dist/unity/state.d.ts.map +1 -0
- package/dist/unity/state.js +170 -0
- package/package.json +1 -1
- package/src/engine.ts +1535 -964
- package/src/graph/compile.ts +3 -1
- package/src/graph/registry.ts +142 -0
- package/src/graph/schema.ts +9 -0
- package/src/graph/slots.ts +15 -18
- package/src/index.ts +12 -0
- package/src/model.ts +18 -1
- package/src/outline-normals.ts +110 -0
- package/src/pmx-loader.ts +8 -2
- package/src/shaders/lights.ts +16 -3
- package/src/shaders/materials/common.ts +65 -57
- package/src/shaders/materials/nodes.ts +210 -0
- package/src/shaders/passes/bloom.ts +59 -87
- package/src/shaders/passes/composite.ts +122 -64
- package/src/shaders/passes/cull.ts +7 -3
- package/src/shaders/passes/ground.ts +32 -55
- package/src/shaders/passes/mirror.ts +60 -13
- package/src/shaders/passes/outline.ts +71 -14
- package/src/shaders/passes/particles.ts +1 -1
- package/src/shaders/scene-light-api.ts +59 -46
- package/src/shadow-cascades.ts +61 -32
- package/src/unity/character.ts +169 -0
- package/src/unity/globals.ts +443 -0
- package/src/unity/host.ts +630 -0
- package/src/unity/looks.ts +391 -0
- package/src/unity/skin.ts +131 -0
- package/src/unity/stage.ts +604 -0
- package/src/unity/state.ts +172 -0
- package/src/shaders/agx-lut.ts +0 -30
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sceneFsOutWgsl, sceneIdWriteWgsl } from "./scene-contract";
|
|
2
2
|
import { lightsApi } from "../lights";
|
|
3
|
-
import { SHADOW_CASCADES } from "../../shadow-cascades";
|
|
3
|
+
import { SHADOW_ATLAS_SIZE, SHADOW_CASCADES } from "../../shadow-cascades";
|
|
4
|
+
import { SHADOW_MARGIN } from "../scene-light-api";
|
|
4
5
|
import { WORLD_AMBIENT_WGSL } from "../lights";
|
|
5
6
|
// Ground shadow-catcher: receives directional shadow, grid lines, frosted noise,
|
|
6
7
|
// radial distance fade. Writes bloom mask = 0 (ground never bloom-bleeds).
|
|
@@ -33,6 +34,9 @@ const SOFT_MAX_SPREAD = 14;
|
|
|
33
34
|
* normalised, so there is no `/ 9.0` left at the call site.
|
|
34
35
|
*/
|
|
35
36
|
function pcfWgsl(map, uv, texel, z, acc, pad, soft) {
|
|
37
|
+
// Every tap stays inside the cascade's own tile (tileLo..tileHi, in scope at
|
|
38
|
+
// the call): the soft disk reaches further than the selection margin, and a
|
|
39
|
+
// tap in the neighbouring tile compares against another cascade's depths.
|
|
36
40
|
const golden = 2.39996323;
|
|
37
41
|
// ONE BRANCH IS COMPILED, NOT BOTH.
|
|
38
42
|
//
|
|
@@ -55,7 +59,7 @@ function pcfWgsl(map, uv, texel, z, acc, pad, soft) {
|
|
|
55
59
|
` for (var x = -1; x <= 1; x++) {`,
|
|
56
60
|
// ...Level, not the implicit-derivative form: identical on a single-mip
|
|
57
61
|
// shadow map, and legal inside a branch.
|
|
58
|
-
` ${acc} += textureSampleCompareLevel(${map}, shadowSampler, ${uv} + vec2f(f32(x), f32(y)) * st, ${z});`,
|
|
62
|
+
` ${acc} += textureSampleCompareLevel(${map}, shadowSampler, clamp(${uv} + vec2f(f32(x), f32(y)) * st, tileLo, tileHi), ${z});`,
|
|
59
63
|
` }`,
|
|
60
64
|
`}`,
|
|
61
65
|
`${acc} *= ${1 / 9};`,
|
|
@@ -68,7 +72,7 @@ function pcfWgsl(map, uv, texel, z, acc, pad, soft) {
|
|
|
68
72
|
// keeps successive taps from lining up into spokes.
|
|
69
73
|
` let r = sqrt((fs + 0.5) * ${1 / SOFT_TAPS});`,
|
|
70
74
|
` let a = fs * ${golden} + rot;`,
|
|
71
|
-
` ${acc} += textureSampleCompareLevel(${map}, shadowSampler, ${uv} + vec2f(cos(a), sin(a)) * (r * radius), ${z});`,
|
|
75
|
+
` ${acc} += textureSampleCompareLevel(${map}, shadowSampler, clamp(${uv} + vec2f(cos(a), sin(a)) * (r * radius), tileLo, tileHi), ${z});`,
|
|
72
76
|
`}`,
|
|
73
77
|
`${acc} *= ${1 / SOFT_TAPS};`,
|
|
74
78
|
];
|
|
@@ -85,12 +89,10 @@ struct Light { direction: vec4f, color: vec4f, };
|
|
|
85
89
|
struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, sh: array<vec4f, 9>, };
|
|
86
90
|
struct GroundShadowMat {
|
|
87
91
|
diffuseColor: vec3f, fadeStart: f32,
|
|
88
|
-
fadeEnd: f32, shadowStrength: f32,
|
|
92
|
+
fadeEnd: f32, shadowStrength: f32, _pad6: f32, gridSpacing: f32,
|
|
89
93
|
gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, opacity: f32,
|
|
90
94
|
gridLineColor: vec3f, mirror: f32,
|
|
91
|
-
|
|
92
|
-
// with no stage the far map is never drawn into, so its taps are known.
|
|
93
|
-
mirrorBlur: f32, farCascade: f32, shadowSoftness: f32, groundY: f32,
|
|
95
|
+
mirrorBlur: f32, _pad17: f32, shadowSoftness: f32, groundY: f32,
|
|
94
96
|
// Every shadow caster in one sphere, refreshed per frame. w = radius; 0 means
|
|
95
97
|
// nothing casts, negative means "do not use this" (a rigid caster has no
|
|
96
98
|
// sphere, so a scene with a stage keeps the taps). See rzShadowPossible.
|
|
@@ -101,12 +103,11 @@ struct GroundShadowMat {
|
|
|
101
103
|
struct LightVP { viewProj: array<mat4x4f, ${SHADOW_CASCADES.length}>, };
|
|
102
104
|
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
103
105
|
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
104
|
-
|
|
106
|
+
// The sun's shadow atlas — see shadow-cascades.ts.
|
|
107
|
+
@group(0) @binding(2) var shadowAtlas: texture_depth_2d;
|
|
105
108
|
@group(0) @binding(3) var shadowSampler: sampler_comparison;
|
|
106
109
|
@group(0) @binding(4) var<uniform> material: GroundShadowMat;
|
|
107
110
|
@group(0) @binding(5) var<uniform> lightVP: LightVP;
|
|
108
|
-
// The far cascade's map, binding 7 as in the materials' layout.
|
|
109
|
-
@group(0) @binding(7) var shadowMapFar: texture_depth_2d;
|
|
110
111
|
// The floor mirror (step 7D): the reflection target, the camera that rendered
|
|
111
112
|
// it, and an ordinary sampler — the two shadow bindings above are comparison.
|
|
112
113
|
// params = (projA, projB, _, _) — the depth-linearisation pair of the SHARED
|
|
@@ -140,7 +141,7 @@ struct MirrorVP { viewProj: mat4x4f, params: vec4f, };
|
|
|
140
141
|
struct MirrorClip { plane: vec4f, on: f32, _p0: f32, _p1: f32, _p2: f32, };
|
|
141
142
|
@group(0) @binding(14) var<uniform> clip: MirrorClip;
|
|
142
143
|
${WORLD_AMBIENT_WGSL}
|
|
143
|
-
${lightsApi(0, 6)}
|
|
144
|
+
${lightsApi(0, 6, "1u")}
|
|
144
145
|
|
|
145
146
|
struct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };
|
|
146
147
|
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {
|
|
@@ -174,21 +175,6 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
174
175
|
return out;
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
// Outside a cascade's frustum there IS no shadow information — the clamped
|
|
178
|
-
// border samples compare against unrelated depths and read "shadowed",
|
|
179
|
-
// darkening the whole far plane with a visible band at the frustum edge
|
|
180
|
-
// (masked by the opaque surface normally, glaring in green-screen mode where
|
|
181
|
-
// only the shadow-catcher term renders). So each cascade fades out near its
|
|
182
|
-
// border — the near one INTO the far one's answer, the far one into lit.
|
|
183
|
-
let l0 = lightVP.viewProj[0] * vec4f(i.worldPos, 1.0);
|
|
184
|
-
let ndc = l0.xyz / max(l0.w, 1e-6);
|
|
185
|
-
let inZ = select(0.0, 1.0, ndc.z > 0.0 && ndc.z < 1.0);
|
|
186
|
-
let frustum = (1.0 - smoothstep(0.88, 0.96, abs(ndc.x))) * (1.0 - smoothstep(0.88, 0.96, abs(ndc.y))) * inZ;
|
|
187
|
-
let l1 = lightVP.viewProj[1] * vec4f(i.worldPos, 1.0);
|
|
188
|
-
let ndc1 = l1.xyz / max(l1.w, 1e-6);
|
|
189
|
-
let inZ1 = select(0.0, 1.0, ndc1.z > 0.0 && ndc1.z < 1.0);
|
|
190
|
-
let frustum1 = (1.0 - smoothstep(0.88, 0.96, abs(ndc1.x))) * (1.0 - smoothstep(0.88, 0.96, abs(ndc1.y))) * inZ1;
|
|
191
|
-
|
|
192
178
|
// THE cost of this shader, measured: a full-screen ground ran 25 shadow
|
|
193
179
|
// comparisons per pixel, and each one is hardware-bilinear, so ~100 depth
|
|
194
180
|
// samples per pixel. Gutting the rest of the shader changed nothing; cutting
|
|
@@ -197,11 +183,9 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
197
183
|
// 3x3 at two-texel spacing covers the same +/-2 texel penumbra the 5x5 at
|
|
198
184
|
// one-texel spacing did — that kernel was oversampled, its taps overlapping
|
|
199
185
|
// almost entirely once the comparison sampler's own 2x2 filter is counted.
|
|
200
|
-
// Nine taps instead of twenty-five for the same blur radius
|
|
186
|
+
// Nine taps instead of twenty-five for the same blur radius, and one
|
|
187
|
+
// cascade's worth per pixel: the first that holds it.
|
|
201
188
|
//
|
|
202
|
-
// The far cascade's taps run ONLY where the near one is fading or absent
|
|
203
|
-
// (frustum < 1), so a pixel in the near core costs exactly what it did with
|
|
204
|
-
// one cascade — and that core is where the camera usually looks.
|
|
205
189
|
// Can anything cast a shadow ONTO this point at all?
|
|
206
190
|
//
|
|
207
191
|
// The ground is vastly larger than what stands on it, and the answer for most
|
|
@@ -255,33 +239,26 @@ ${soft
|
|
|
255
239
|
let ign = fract(52.9829189 * fract(dot(i.position.xy, vec2f(0.06711056, 0.00583715))));
|
|
256
240
|
let rot = ign * 6.28318530718;`
|
|
257
241
|
: ""}
|
|
258
|
-
// The
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
let
|
|
270
|
-
let
|
|
271
|
-
let
|
|
272
|
-
|
|
273
|
-
${pcfWgsl("shadowMapFar", "suv1_c", `${1 / SHADOW_CASCADES[SHADOW_CASCADES.length - 1].mapSize}`, "compareZ1", "acc1", " ", soft)}
|
|
274
|
-
vis = mix(1.0, acc1, frustum1);
|
|
275
|
-
}
|
|
276
|
-
if (frustum > 0.0) {
|
|
277
|
-
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
278
|
-
let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));
|
|
242
|
+
// The first cascade whose box holds the point, as the materials pick it.
|
|
243
|
+
// Outside a cascade's box there IS no shadow information — the clamped
|
|
244
|
+
// border samples compare against unrelated depths and read "shadowed",
|
|
245
|
+
// darkening the far floor with a band at the edge — so the outermost one
|
|
246
|
+
// fades into lit near its border. Inner cascades hand over to the next.
|
|
247
|
+
let n = ${SHADOW_CASCADES.length}u;
|
|
248
|
+
for (var ci = 0u; ci < n; ci++) {
|
|
249
|
+
let lc = lightVP.viewProj[ci] * vec4f(i.worldPos, 1.0);
|
|
250
|
+
let ndc = lc.xyz / max(lc.w, 1e-6);
|
|
251
|
+
if (!(all(abs(ndc.xy) < vec2f(${SHADOW_MARGIN})) && ndc.z > 0.0 && ndc.z < 1.0)) { continue; }
|
|
252
|
+
let frustum = select(1.0, (1.0 - smoothstep(0.88, 0.96, abs(ndc.x))) * (1.0 - smoothstep(0.88, 0.96, abs(ndc.y))), ci == n - 1u);
|
|
253
|
+
let tile = vec2f(f32(ci % 2u), f32(ci / 2u)) * ${SHADOW_CASCADES[0].mapSize / SHADOW_ATLAS_SIZE};
|
|
254
|
+
let tileLo = tile + vec2f(${0.5 / SHADOW_ATLAS_SIZE});
|
|
255
|
+
let tileHi = tile + vec2f(${(SHADOW_CASCADES[0].mapSize - 0.5) / SHADOW_ATLAS_SIZE});
|
|
256
|
+
let suv = tile + vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5) * ${SHADOW_CASCADES[0].mapSize / SHADOW_ATLAS_SIZE};
|
|
279
257
|
let compareZ = ndc.z - 0.0035;
|
|
280
258
|
var acc = 0.0;
|
|
281
|
-
${pcfWgsl("
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
vis = mix(vis, acc, frustum);
|
|
259
|
+
${pcfWgsl("shadowAtlas", "suv", `${1 / SHADOW_ATLAS_SIZE}`, "compareZ", "acc", " ", soft)}
|
|
260
|
+
vis = mix(1.0, acc, frustum);
|
|
261
|
+
break;
|
|
285
262
|
}
|
|
286
263
|
}
|
|
287
264
|
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
/** Bytes in MirrorMat below: mat4x4f + three 16-byte rows. */
|
|
2
2
|
export declare const MIRROR_MAT_BYTES: number;
|
|
3
3
|
export declare function mirrorShaderWgsl(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Downsample for the reflection's COLOUR chain, one pass per mip level: the
|
|
6
|
+
* Jimenez/COD 13-tap dual box — five weighted 2x2 averages, which rejects the
|
|
7
|
+
* nyquist ringing a plain box would leave in a blurred reflection.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MIRROR_DOWNSAMPLE_WGSL = "\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n let x = f32((vi & 1u) << 2u) - 1.0;\n let y = f32((vi & 2u) << 1u) - 1.0;\n return vec4f(x, y, 0.0, 1.0);\n}\n\n@group(0) @binding(0) var srcTex: texture_2d<f32>;\n@group(0) @binding(1) var srcSamp: sampler;\n\nfn samp(uv: vec2f, off: vec2f) -> vec3f {\n return textureSampleLevel(srcTex, srcSamp, uv + off, 0.0).rgb;\n}\n\n@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {\n let srcDims = vec2f(textureDimensions(srcTex));\n let t = 1.0 / srcDims;\n // fragCoord.xy reports pixel centers (e.g. 0.5,0.5 for first pixel) \u2014 divide by dst dims directly.\n let dstDims = srcDims * 0.5;\n let uv = p.xy / max(dstDims, vec2f(1.0));\n let A = samp(uv, t * vec2f(-2.0, -2.0));\n let B = samp(uv, t * vec2f( 0.0, -2.0));\n let C = samp(uv, t * vec2f( 2.0, -2.0));\n let D = samp(uv, t * vec2f(-1.0, -1.0));\n let E = samp(uv, t * vec2f( 1.0, -1.0));\n let F = samp(uv, t * vec2f(-2.0, 0.0));\n let G = samp(uv, t * vec2f( 0.0, 0.0));\n let H = samp(uv, t * vec2f( 2.0, 0.0));\n let I = samp(uv, t * vec2f(-1.0, 1.0));\n let J = samp(uv, t * vec2f( 1.0, 1.0));\n let K = samp(uv, t * vec2f(-2.0, 2.0));\n let L = samp(uv, t * vec2f( 0.0, 2.0));\n let M = samp(uv, t * vec2f( 2.0, 2.0));\n var o = (D + E + I + J) * (0.5 / 4.0);\n o = o + (A + B + G + F) * (0.125 / 4.0);\n o = o + (B + C + H + G) * (0.125 / 4.0);\n o = o + (F + G + L + K) * (0.125 / 4.0);\n o = o + (G + H + M + L) * (0.125 / 4.0);\n return vec4f(o, 1.0);\n}\n";
|
|
10
|
+
/**
|
|
11
|
+
* Downsample for the reflection's COVERAGE chain.
|
|
12
|
+
*
|
|
13
|
+
* Colour and coverage are a premultiplied PAIR, and a blur has to move them
|
|
14
|
+
* together. Blurring colour alone drags it toward zero wherever it spreads into
|
|
15
|
+
* the empty part of the reflection, while a coverage still sampled sharp says
|
|
16
|
+
* "fully covered" — so the reflection simply got darker as the dial came up.
|
|
17
|
+
*
|
|
18
|
+
* One bilinear tap at the destination texel centre averages the source's 2x2
|
|
19
|
+
* exactly, which is the box filter this wants and the cheapest way to write it.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MIRROR_MASK_DOWNSAMPLE_WGSL = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1) var samp: sampler;\n\nstruct VSOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f, };\n\n@vertex fn vs(@builtin(vertex_index) i: u32) -> VSOut {\n var out: VSOut;\n let x = f32(i32(i / 2u) * 4 - 1);\n let y = f32(i32(i % 2u) * 4 - 1);\n out.pos = vec4f(x, y, 0.0, 1.0);\n out.uv = vec2f(x * 0.5 + 0.5, 0.5 - y * 0.5);\n return out;\n}\n\n@fragment fn fs(in: VSOut) -> @location(0) vec4f {\n return textureSampleLevel(src, samp, in.uv, 0.0);\n}\n";
|
|
4
22
|
/**
|
|
5
23
|
* The mirror in the SHADOW pass — the frame throwing shade on the floor.
|
|
6
24
|
*
|
|
@@ -16,17 +34,5 @@ export declare function mirrorShaderWgsl(): string;
|
|
|
16
34
|
* offset: the cascade matrices sit 64 bytes apart in one array, and a dynamic
|
|
17
35
|
* uniform offset has to be 256-aligned.
|
|
18
36
|
*/
|
|
19
|
-
/**
|
|
20
|
-
* Downsample for the reflection's COVERAGE chain.
|
|
21
|
-
*
|
|
22
|
-
* Colour and coverage are a premultiplied PAIR, and a blur has to move them
|
|
23
|
-
* together. Blurring colour alone drags it toward zero wherever it spreads into
|
|
24
|
-
* the empty part of the reflection, while a coverage still sampled sharp says
|
|
25
|
-
* "fully covered" — so the reflection simply got darker as the dial came up.
|
|
26
|
-
*
|
|
27
|
-
* One bilinear tap at the destination texel centre averages the source's 2x2
|
|
28
|
-
* exactly, which is the box filter this wants and the cheapest way to write it.
|
|
29
|
-
*/
|
|
30
|
-
export declare const MIRROR_MASK_DOWNSAMPLE_WGSL = "\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1) var samp: sampler;\n\nstruct VSOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f, };\n\n@vertex fn vs(@builtin(vertex_index) i: u32) -> VSOut {\n var out: VSOut;\n let x = f32(i32(i / 2u) * 4 - 1);\n let y = f32(i32(i % 2u) * 4 - 1);\n out.pos = vec4f(x, y, 0.0, 1.0);\n out.uv = vec2f(x * 0.5 + 0.5, 0.5 - y * 0.5);\n return out;\n}\n\n@fragment fn fs(in: VSOut) -> @location(0) vec4f {\n return textureSampleLevel(src, samp, in.uv, 0.0);\n}\n";
|
|
31
37
|
export declare function mirrorShadowWgsl(cascades: number): string;
|
|
32
38
|
//# sourceMappingURL=mirror.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mirror.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/mirror.ts"],"names":[],"mappings":"AAqBA,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,QAAc,CAAA;AAE3C,wBAAgB,gBAAgB,IAAI,MAAM,CAiMzC;AAED
|
|
1
|
+
{"version":3,"file":"mirror.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/mirror.ts"],"names":[],"mappings":"AAqBA,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,QAAc,CAAA;AAE3C,wBAAgB,gBAAgB,IAAI,MAAM,CAiMzC;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,2hDAwClC,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,2hBAkBvC,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAuBzD"}
|
|
@@ -214,20 +214,51 @@ ${sceneIdPadWgsl("out")}
|
|
|
214
214
|
`;
|
|
215
215
|
}
|
|
216
216
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* quad is six generated vertices and the model matrix is the whole of it, while
|
|
221
|
-
* the ordinary shadow shader is built for skinned geometry with an alpha test.
|
|
222
|
-
*
|
|
223
|
-
* Depth only, no fragment stage. The pane is opaque across its whole rectangle
|
|
224
|
-
* — glass included, because this glass is not see-through — so there is nothing
|
|
225
|
-
* to alpha-test and nothing to colour.
|
|
226
|
-
*
|
|
227
|
-
* The cascade index arrives in its own tiny uniform rather than as a dynamic
|
|
228
|
-
* offset: the cascade matrices sit 64 bytes apart in one array, and a dynamic
|
|
229
|
-
* uniform offset has to be 256-aligned.
|
|
217
|
+
* Downsample for the reflection's COLOUR chain, one pass per mip level: the
|
|
218
|
+
* Jimenez/COD 13-tap dual box — five weighted 2x2 averages, which rejects the
|
|
219
|
+
* nyquist ringing a plain box would leave in a blurred reflection.
|
|
230
220
|
*/
|
|
221
|
+
export const MIRROR_DOWNSAMPLE_WGSL = /* wgsl */ `
|
|
222
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
223
|
+
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
224
|
+
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
225
|
+
return vec4f(x, y, 0.0, 1.0);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@group(0) @binding(0) var srcTex: texture_2d<f32>;
|
|
229
|
+
@group(0) @binding(1) var srcSamp: sampler;
|
|
230
|
+
|
|
231
|
+
fn samp(uv: vec2f, off: vec2f) -> vec3f {
|
|
232
|
+
return textureSampleLevel(srcTex, srcSamp, uv + off, 0.0).rgb;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
236
|
+
let srcDims = vec2f(textureDimensions(srcTex));
|
|
237
|
+
let t = 1.0 / srcDims;
|
|
238
|
+
// fragCoord.xy reports pixel centers (e.g. 0.5,0.5 for first pixel) — divide by dst dims directly.
|
|
239
|
+
let dstDims = srcDims * 0.5;
|
|
240
|
+
let uv = p.xy / max(dstDims, vec2f(1.0));
|
|
241
|
+
let A = samp(uv, t * vec2f(-2.0, -2.0));
|
|
242
|
+
let B = samp(uv, t * vec2f( 0.0, -2.0));
|
|
243
|
+
let C = samp(uv, t * vec2f( 2.0, -2.0));
|
|
244
|
+
let D = samp(uv, t * vec2f(-1.0, -1.0));
|
|
245
|
+
let E = samp(uv, t * vec2f( 1.0, -1.0));
|
|
246
|
+
let F = samp(uv, t * vec2f(-2.0, 0.0));
|
|
247
|
+
let G = samp(uv, t * vec2f( 0.0, 0.0));
|
|
248
|
+
let H = samp(uv, t * vec2f( 2.0, 0.0));
|
|
249
|
+
let I = samp(uv, t * vec2f(-1.0, 1.0));
|
|
250
|
+
let J = samp(uv, t * vec2f( 1.0, 1.0));
|
|
251
|
+
let K = samp(uv, t * vec2f(-2.0, 2.0));
|
|
252
|
+
let L = samp(uv, t * vec2f( 0.0, 2.0));
|
|
253
|
+
let M = samp(uv, t * vec2f( 2.0, 2.0));
|
|
254
|
+
var o = (D + E + I + J) * (0.5 / 4.0);
|
|
255
|
+
o = o + (A + B + G + F) * (0.125 / 4.0);
|
|
256
|
+
o = o + (B + C + H + G) * (0.125 / 4.0);
|
|
257
|
+
o = o + (F + G + L + K) * (0.125 / 4.0);
|
|
258
|
+
o = o + (G + H + M + L) * (0.125 / 4.0);
|
|
259
|
+
return vec4f(o, 1.0);
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
231
262
|
/**
|
|
232
263
|
* Downsample for the reflection's COVERAGE chain.
|
|
233
264
|
*
|
|
@@ -258,6 +289,21 @@ struct VSOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f, };
|
|
|
258
289
|
return textureSampleLevel(src, samp, in.uv, 0.0);
|
|
259
290
|
}
|
|
260
291
|
`;
|
|
292
|
+
/**
|
|
293
|
+
* The mirror in the SHADOW pass — the frame throwing shade on the floor.
|
|
294
|
+
*
|
|
295
|
+
* Its own shader because the mirror has no vertex buffer and no skeleton: the
|
|
296
|
+
* quad is six generated vertices and the model matrix is the whole of it, while
|
|
297
|
+
* the ordinary shadow shader is built for skinned geometry with an alpha test.
|
|
298
|
+
*
|
|
299
|
+
* Depth only, no fragment stage. The pane is opaque across its whole rectangle
|
|
300
|
+
* — glass included, because this glass is not see-through — so there is nothing
|
|
301
|
+
* to alpha-test and nothing to colour.
|
|
302
|
+
*
|
|
303
|
+
* The cascade index arrives in its own tiny uniform rather than as a dynamic
|
|
304
|
+
* offset: the cascade matrices sit 64 bytes apart in one array, and a dynamic
|
|
305
|
+
* uniform offset has to be 256-aligned.
|
|
306
|
+
*/
|
|
261
307
|
export function mirrorShadowWgsl(cascades) {
|
|
262
308
|
return /* wgsl */ `
|
|
263
309
|
struct LightVP { viewProj: array<mat4x4f, ${cascades}>, };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outline.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/outline.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAE5C,wBAAgB,iBAAiB,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"outline.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/outline.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAE5C,wBAAgB,iBAAiB,IAAI,MAAM,CA6L1C"}
|
|
@@ -69,14 +69,39 @@ struct VertexOutput {
|
|
|
69
69
|
/** The triangle's own threshold, flat — see the material VertexOutput. The
|
|
70
70
|
* hull traces the body's faces, so it has to lose the same ones. */
|
|
71
71
|
@location(2) @interpolate(flat) faceT: f32,
|
|
72
|
+
/** How much of a pixel-wide line this one really is (1 = all of it): a line
|
|
73
|
+
* held at the minimum width fades by what it was short. */
|
|
74
|
+
@location(3) coverage: f32,
|
|
72
75
|
};
|
|
73
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Half the frame height (engine units, at the subject) below which a line keeps
|
|
79
|
+
* its constant screen width; wider than that it thins as a world-size line.
|
|
80
|
+
* 12.5 = a frame 25 units tall — a full figure (an MMD model is ~20) with
|
|
81
|
+
* room around her.
|
|
82
|
+
*
|
|
83
|
+
* Not the game's own break (w = 1 game unit = 8 engine units). The game's
|
|
84
|
+
* near width is ~3× MMD's 2·edgeSize px; its world-size line comes down to
|
|
85
|
+
* MMD's width only at a full-figure framing, so that is where the break goes:
|
|
86
|
+
* close-ups and full figures keep today's width, and past that the game's rule
|
|
87
|
+
* holds — NDC offset ∝ 1/w, scaled by the zoom as its projection._m11 scales
|
|
88
|
+
* it. At the game's 8 the demo's own camera (33 units) drew a quarter of
|
|
89
|
+
* today's width, which put every line on the faded minimum.
|
|
90
|
+
*/
|
|
91
|
+
const RZ_OUTLINE_FULL_FIGURE = 12.5;
|
|
92
|
+
/** The thinnest a line is drawn, in pixels at 1080p (and never under this many
|
|
93
|
+
* device pixels). A line due to be thinner is drawn this wide and faded by
|
|
94
|
+
* the shortfall, so far lines thin out without aliasing into dashes. */
|
|
95
|
+
const RZ_OUTLINE_MIN_PX = 1.5;
|
|
96
|
+
|
|
74
97
|
@vertex fn vs(
|
|
75
98
|
@location(0) position: vec3f,
|
|
76
99
|
@location(1) normal: vec3f,
|
|
77
100
|
@location(2) uv: vec2f,
|
|
78
101
|
@location(3) joints0: vec4<u32>,
|
|
79
|
-
@location(4) weights0: vec4<f32
|
|
102
|
+
@location(4) weights0: vec4<f32>,
|
|
103
|
+
/** xyz: the smoothed rest normal (outline-normals.ts), w: PMX edge scale. */
|
|
104
|
+
@location(5) outlineNormal: vec4f
|
|
80
105
|
) -> VertexOutput {
|
|
81
106
|
var output: VertexOutput;
|
|
82
107
|
let pos4 = vec4f(position, 1.0);
|
|
@@ -93,30 +118,58 @@ struct VertexOutput {
|
|
|
93
118
|
let m = skinMats[j];
|
|
94
119
|
skinnedPos += (m * pos4) * w;
|
|
95
120
|
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
96
|
-
skinnedNrm += (r3 *
|
|
121
|
+
skinnedNrm += (r3 * outlineNormal.xyz) * w;
|
|
97
122
|
}
|
|
98
123
|
let worldPos = skinnedPos.xyz;
|
|
99
124
|
let worldNormal = normalize(skinnedNrm);
|
|
100
125
|
|
|
101
126
|
let clipPos = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
102
127
|
|
|
103
|
-
//
|
|
128
|
+
// The push direction is the view-space normal's XY, NOT renormalized — the
|
|
129
|
+
// game's: full length at the silhouette, where the normal lies across the
|
|
130
|
+
// view, and shrinking on surfaces turned toward the camera, so creases
|
|
131
|
+
// facing her draw finer than the rim. Skinned exactly as the colour pass
|
|
132
|
+
// skins the normal, from the smoothed rest normal, so every vertex sharing a
|
|
133
|
+
// position pushes the same way and the hull stays closed over hard edges
|
|
134
|
+
// and UV seams.
|
|
104
135
|
let viewNormal = (camera.view * vec4f(worldNormal, 0.0)).xyz;
|
|
105
|
-
let snLen = length(viewNormal.xy);
|
|
106
|
-
let screenNormal = select(vec2f(0.0, 0.0), viewNormal.xy / snLen, snLen > 1e-5);
|
|
107
136
|
|
|
108
137
|
// Reference-height normalization (babylon-mmd ships this variant commented
|
|
109
|
-
// out as \`renderHeight = 1080\`):
|
|
110
|
-
// frame — 2·edgeSize px at 1080p — so retina DPR and 4K export don't
|
|
111
|
-
// the rims to sub-pixel. Width follows the projection aspect.
|
|
138
|
+
// out as \`renderHeight = 1080\`): the near width is a constant FRACTION of
|
|
139
|
+
// the frame — 2·edgeSize px at 1080p — so retina DPR and 4K export don't
|
|
140
|
+
// thin the rims to sub-pixel. Width follows the projection aspect.
|
|
112
141
|
// projection[1][1]/projection[0][0] = width/height for a symmetric frustum.
|
|
113
142
|
let aspect = camera.projection[1][1] / camera.projection[0][0];
|
|
114
|
-
let
|
|
143
|
+
let refViewport = vec2f(1080.0 * aspect, 1080.0);
|
|
144
|
+
let deviceViewport = vec2f(camera.viewportHeight * aspect, camera.viewportHeight);
|
|
145
|
+
|
|
146
|
+
// The game's width (Character/Debug.shader, pass "Outline"): the clip offset
|
|
147
|
+
// is the near offset × min(w, break depth). Up close the ×w cancels the
|
|
148
|
+
// perspective divide — constant screen width; past the break it stops
|
|
149
|
+
// growing, so the NDC offset falls as 1/w like a line of fixed world width.
|
|
150
|
+
// The break is the depth at which the frame is a full figure tall, so a
|
|
151
|
+
// zoomed lens moves it out (see RZ_OUTLINE_FULL_FIGURE). Under an
|
|
152
|
+
// orthographic camera w is 1 and the same min() makes the line world-size
|
|
153
|
+
// once the frame is taller than a figure. The PMX per-vertex edge scale
|
|
154
|
+
// multiplies it all, as vertex colour alpha does in the game.
|
|
155
|
+
let w = max(clipPos.w, 1e-4);
|
|
156
|
+
let breakDepth = RZ_OUTLINE_FULL_FIGURE * camera.projection[1][1];
|
|
157
|
+
let nearNdc = viewNormal.xy * (material.edgeSize * outlineNormal.w * 4.0 / refViewport);
|
|
158
|
+
var ndc = nearNdc * (min(w, breakDepth) / w);
|
|
115
159
|
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
// Antialiased minimum (the game's _OutlineAntialias): held at the minimum
|
|
161
|
+
// width and faded by the shortfall, never thinner. The game fades by
|
|
162
|
+
// sqrt(coverage.x · coverage.y), per axis; for one width that is the ratio
|
|
163
|
+
// itself.
|
|
164
|
+
let px = length(ndc * deviceViewport * 0.5);
|
|
165
|
+
let minPx = RZ_OUTLINE_MIN_PX * max(1.0, camera.viewportHeight / 1080.0);
|
|
166
|
+
var coverage = 1.0;
|
|
167
|
+
if (px < minPx) {
|
|
168
|
+
coverage = px / minPx;
|
|
169
|
+
ndc = select(vec2f(0.0), ndc * (minPx / px), px > 1e-6);
|
|
170
|
+
}
|
|
171
|
+
output.coverage = coverage;
|
|
172
|
+
output.position = vec4f(clipPos.xy + ndc * w, clipPos.z, clipPos.w);
|
|
120
173
|
output.uv = uv;
|
|
121
174
|
output.restPos = position;
|
|
122
175
|
output.faceT = rz_dissolve_threshold(position);
|
|
@@ -151,7 +204,11 @@ ${sceneFsOutWgsl({ name: "FSOut", aux: "mask" })}
|
|
|
151
204
|
discard;
|
|
152
205
|
}
|
|
153
206
|
var out: FSOut;
|
|
154
|
-
|
|
207
|
+
// Blended, not alpha-to-coverage: this pass already blends (the texture
|
|
208
|
+
// alpha above), it is drawn right after the surface it traces so what lies
|
|
209
|
+
// under a faded line is already there, and 4× MSAA would quantize a fade to
|
|
210
|
+
// four steps.
|
|
211
|
+
out.color = vec4f(material.edgeColor.rgb, material.edgeColor.a * texA * input.coverage);
|
|
155
212
|
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
156
213
|
${sceneIdPadWgsl("out")} return out;
|
|
157
214
|
}
|
|
@@ -63,7 +63,7 @@ type ParticleSource = {
|
|
|
63
63
|
* N > 0 — see texture-api.ts. Omitted = 0. */
|
|
64
64
|
textures?: number;
|
|
65
65
|
};
|
|
66
|
-
/** Where the shading stage's
|
|
66
|
+
/** Where the shading stage's four scene-light bindings start — see scene-light-api.ts. */
|
|
67
67
|
export declare const PARTICLE_LIGHT_BINDING = 10;
|
|
68
68
|
/** The effect's named points (`#points`), in both stages — see points-api.ts. */
|
|
69
69
|
export declare const PARTICLE_POINTS_BINDING = 15;
|
|
@@ -43,7 +43,7 @@ fn _rzCastLive() -> i32 {
|
|
|
43
43
|
trailSlotsApi(cast.trailCount) +
|
|
44
44
|
anchorAliasWgsl(cast.alias));
|
|
45
45
|
}
|
|
46
|
-
/** Where the shading stage's
|
|
46
|
+
/** Where the shading stage's four scene-light bindings start — see scene-light-api.ts. */
|
|
47
47
|
export const PARTICLE_LIGHT_BINDING = 10;
|
|
48
48
|
/** The effect's named points (`#points`), in both stages — see points-api.ts. */
|
|
49
49
|
export const PARTICLE_POINTS_BINDING = 15;
|
|
@@ -8,16 +8,25 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const worldAmbientWgsl: (u: string) => string;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* The sun's shadow atlas, as every reader looks it up: `<p>Locate(q)` finds
|
|
12
|
+
* world point q in the first cascade whose box holds it and answers
|
|
13
|
+
* (atlas uv, depth, cascade) — cascade -1 outside them all — and `<p>Taps(a)`
|
|
14
|
+
* is the 3×3 PCF at that place.
|
|
12
15
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* ONE implementation for the materials, the ground and effects, so a blade of
|
|
17
|
+
* grass and her own skin go dark at the same edge. `vp` names the cascades'
|
|
18
|
+
* view-projection array in the module that includes it.
|
|
19
|
+
*
|
|
20
|
+
* The box test keeps a margin (SHADOW_MARGIN of the tile's half-width) so the
|
|
21
|
+
* whole filter stays inside the tile: a tap that crossed into the neighbouring
|
|
22
|
+
* tile would compare against another cascade's depths. The sampler is linear,
|
|
23
|
+
* so each tap is itself a 2×2 compare. Unrolled — Safari's Metal backend
|
|
24
|
+
* doesn't unroll nested shadow loops reliably.
|
|
17
25
|
*/
|
|
18
|
-
export declare const
|
|
26
|
+
export declare const SHADOW_MARGIN = 0.98;
|
|
27
|
+
export declare const sunShadowWgsl: (p: string, map: string, sampler: string, vp: string) => string;
|
|
19
28
|
/** Bindings the real accessors take, from `binding` upward. */
|
|
20
|
-
export declare const SCENE_LIGHT_API_BINDINGS =
|
|
29
|
+
export declare const SCENE_LIGHT_API_BINDINGS = 4;
|
|
21
30
|
/**
|
|
22
31
|
* `rzShadow(p)`: how much of the sun reaches world point `p` — 1 lit, 0 in a
|
|
23
32
|
* caster's shadow, soft in between.
|
|
@@ -25,12 +34,11 @@ export declare const SCENE_LIGHT_API_BINDINGS = 5;
|
|
|
25
34
|
* `rzWorldAmbient(n)`: the world's light arriving at a surface facing `n` —
|
|
26
35
|
* the materials' own ambient, so an effect's shade can match hers.
|
|
27
36
|
*
|
|
28
|
-
* With `on`, declares
|
|
37
|
+
* With `on`, declares four bindings from `binding`:
|
|
29
38
|
* +0 the light uniform (ambient, the sun — direction, and in .w the shadow
|
|
30
39
|
* switch — and the HDRI's irradiance)
|
|
31
40
|
* +1 the cascade view-projections, then the caster sphere
|
|
32
|
-
* +2 the
|
|
33
|
-
* +4 the comparison sampler
|
|
41
|
+
* +2 the sun's shadow atlas +3 the comparison sampler
|
|
34
42
|
*
|
|
35
43
|
* It takes a POINT and no normal. The materials' sampleShadow also rejects a
|
|
36
44
|
* surface facing away from the sun, which is right for skin and wrong for a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-light-api.d.ts","sourceRoot":"","sources":["../../src/shaders/scene-light-api.ts"],"names":[],"mappings":"AAkBA;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,GAAG,MAAM,WAwBzC,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"scene-light-api.d.ts","sourceRoot":"","sources":["../../src/shaders/scene-light-api.ts"],"names":[],"mappings":"AAkBA;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,GAAG,MAAM,WAwBzC,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,OAAO,CAAA;AAEjC,eAAO,MAAM,aAAa,GAAI,GAAG,MAAM,EAAE,KAAK,MAAM,EAAE,SAAS,MAAM,EAAE,IAAI,MAAM,WAkChF,CAAA;AAED,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB,IAAI,CAAA;AAEzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA2CjF"}
|