reze-engine 0.60.4 → 0.61.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 (95) hide show
  1. package/dist/_dbg-loader.mjs +1100 -0
  2. package/dist/engine.d.ts +121 -5
  3. package/dist/engine.d.ts.map +1 -1
  4. package/dist/engine.js +284 -19
  5. package/dist/graph/compile.d.ts.map +1 -1
  6. package/dist/graph/compile.js +4 -1
  7. package/dist/graph/registry.d.ts.map +1 -1
  8. package/dist/graph/registry.js +14 -2
  9. package/dist/graph/slots.d.ts +1 -1
  10. package/dist/graph/slots.d.ts.map +1 -1
  11. package/dist/graph/slots.js +19 -6
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/physics-debug.d.ts +30 -0
  15. package/dist/physics-debug.d.ts.map +1 -0
  16. package/dist/physics-debug.js +526 -0
  17. package/dist/shaders/lights.d.ts.map +1 -1
  18. package/dist/shaders/lights.js +19 -0
  19. package/dist/shaders/materials/body.d.ts +2 -0
  20. package/dist/shaders/materials/body.d.ts.map +1 -0
  21. package/dist/shaders/materials/body.js +95 -0
  22. package/dist/shaders/materials/cloth_rough.d.ts +2 -0
  23. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -0
  24. package/dist/shaders/materials/cloth_rough.js +69 -0
  25. package/dist/shaders/materials/cloth_smooth.d.ts +2 -0
  26. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -0
  27. package/dist/shaders/materials/cloth_smooth.js +61 -0
  28. package/dist/shaders/materials/common.d.ts.map +1 -1
  29. package/dist/shaders/materials/common.js +47 -0
  30. package/dist/shaders/materials/default.d.ts +2 -0
  31. package/dist/shaders/materials/default.d.ts.map +1 -0
  32. package/dist/shaders/materials/default.js +43 -0
  33. package/dist/shaders/materials/eye.d.ts +2 -0
  34. package/dist/shaders/materials/eye.d.ts.map +1 -0
  35. package/dist/shaders/materials/eye.js +60 -0
  36. package/dist/shaders/materials/face.d.ts +2 -0
  37. package/dist/shaders/materials/face.d.ts.map +1 -0
  38. package/dist/shaders/materials/face.js +95 -0
  39. package/dist/shaders/materials/hair.d.ts +2 -0
  40. package/dist/shaders/materials/hair.d.ts.map +1 -0
  41. package/dist/shaders/materials/hair.js +90 -0
  42. package/dist/shaders/materials/metal.d.ts +2 -0
  43. package/dist/shaders/materials/metal.d.ts.map +1 -0
  44. package/dist/shaders/materials/metal.js +77 -0
  45. package/dist/shaders/materials/mmd_classic.d.ts +2 -0
  46. package/dist/shaders/materials/mmd_classic.d.ts.map +1 -0
  47. package/dist/shaders/materials/mmd_classic.js +66 -0
  48. package/dist/shaders/materials/nodes.d.ts +1 -1
  49. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  50. package/dist/shaders/materials/nodes.js +110 -23
  51. package/dist/shaders/materials/stockings.d.ts +2 -0
  52. package/dist/shaders/materials/stockings.d.ts.map +1 -0
  53. package/dist/shaders/materials/stockings.js +122 -0
  54. package/dist/shaders/passes/composite.d.ts +1 -1
  55. package/dist/shaders/passes/composite.d.ts.map +1 -1
  56. package/dist/shaders/passes/composite.js +34 -2
  57. package/dist/shaders/passes/physics-debug.d.ts +2 -0
  58. package/dist/shaders/passes/physics-debug.d.ts.map +1 -0
  59. package/dist/shaders/passes/physics-debug.js +69 -0
  60. package/dist/shaders/scene-tap.js +1 -1
  61. package/package.json +1 -1
  62. package/src/engine.ts +312 -19
  63. package/src/graph/compile.ts +4 -1
  64. package/src/graph/registry.ts +14 -2
  65. package/src/graph/slots.ts +20 -5
  66. package/src/index.ts +4 -0
  67. package/src/shaders/lights.ts +19 -0
  68. package/src/shaders/materials/common.ts +47 -0
  69. package/src/shaders/materials/nodes.ts +110 -23
  70. package/src/shaders/passes/composite.ts +34 -2
  71. package/src/shaders/scene-tap.ts +1 -1
  72. package/dist/graph/presets/pool_floor.d.ts +0 -3
  73. package/dist/graph/presets/pool_floor.d.ts.map +0 -1
  74. package/dist/graph/presets/pool_floor.js +0 -42
  75. package/dist/graph/presets/water.d.ts +0 -3
  76. package/dist/graph/presets/water.d.ts.map +0 -1
  77. package/dist/graph/presets/water.js +0 -31
  78. package/dist/physics/autofit.d.ts +0 -147
  79. package/dist/physics/autofit.d.ts.map +0 -1
  80. package/dist/physics/autofit.js +0 -501
  81. package/dist/shaders/passes/field-blit.d.ts +0 -26
  82. package/dist/shaders/passes/field-blit.d.ts.map +0 -1
  83. package/dist/shaders/passes/field-blit.js +0 -65
  84. package/dist/shaders/passes/ground-noise.d.ts +0 -7
  85. package/dist/shaders/passes/ground-noise.d.ts.map +0 -1
  86. package/dist/shaders/passes/ground-noise.js +0 -88
  87. package/dist/shaders/passes/sim.d.ts +0 -34
  88. package/dist/shaders/passes/sim.d.ts.map +0 -1
  89. package/dist/shaders/passes/sim.js +0 -169
  90. package/dist/shaders/score-api.d.ts +0 -10
  91. package/dist/shaders/score-api.d.ts.map +0 -1
  92. package/dist/shaders/score-api.js +0 -114
  93. package/dist/shaders/shadow-api.d.ts +0 -29
  94. package/dist/shaders/shadow-api.d.ts.map +0 -1
  95. package/dist/shaders/shadow-api.js +0 -112
@@ -362,6 +362,25 @@ fn rzLightsDiffuse(p: vec3f, n: vec3f) -> vec3f {
362
362
  return _rzLightsIrradiance(p, n) * (1.0 / 3.141592653589793);
363
363
  }
364
364
 
365
+ // ONE WALK PER FRAGMENT. A principled closure walks the lamps for its specular
366
+ // anyway, and every lamp's reach, falloff and cone are the same numbers the
367
+ // diffuse layer needs — so that walk computes both, against the normal the
368
+ // material prelude recorded here, and leaves the diffuse for the epilogue.
369
+ // Walking the grid twice was ~60% of a lit stage's frame.
370
+ var<private> _rzLampN: vec3f;
371
+ var<private> _rzLampDiffuse: vec3f;
372
+ var<private> _rzLampDiffuseSet: bool = false;
373
+ // Set by a closure that shaded the lamps' diffuse itself (a Unity-mode
374
+ // principled, which tints it by its own base) — the epilogue then adds none.
375
+ var<private> _rzLampDiffuseTaken: bool = false;
376
+
377
+ /** rzLightsDiffuse(p, _rzLampN), taken from a principled walk when one ran. */
378
+ fn rzLightsDiffuseOnce(p: vec3f, n: vec3f) -> vec3f {
379
+ if (_rzLampDiffuseTaken) { return vec3f(0.0); }
380
+ if (_rzLampDiffuseSet) { return _rzLampDiffuse; }
381
+ return rzLightsDiffuse(p, n);
382
+ }
383
+
365
384
  fn _rzLightsIrradiance(p: vec3f, n: vec3f) -> vec3f {
366
385
  var acc = vec3f(0.0);
367
386
  let count = rzLightCount();
@@ -54,6 +54,13 @@ struct LightUniforms {
54
54
  lights: array<Light, 4>,
55
55
  /** Irradiance SH of the HDRI world; [0].w is the on-flag. */
56
56
  sh: array<vec4f, 9>,
57
+ /** The scene fog (Engine.setSceneFog): haze (colour, amount), (distance,
58
+ * height), then the second layer the same. Amount 0 is none. */
59
+ fog: array<vec4f, 4>,
60
+ /** The cast's shadow on a stage (Engine.setStageCastShadow): colour, amount;
61
+ * amount 0 is off. Then the view-projection its map was drawn with. */
62
+ castShadow: vec4f,
63
+ castVP: mat4x4f,
57
64
  };
58
65
 
59
66
  // Per-material uniforms. Every material binds this layout even if it ignores fields;
@@ -121,8 +128,19 @@ struct VertexOutput {
121
128
  * must agree — a pass that disagrees punches holes through her.
122
129
  */
123
130
  @location(4) @interpolate(flat) faceT: f32,
131
+ /** The scene fog's two amounts, computed PER VERTEX as the game does — see
132
+ * Engine.setSceneFog — and interpolated across the face. */
133
+ @location(5) fog: vec2f,
124
134
  };
125
135
 
136
+ /** One fog layer's amount at a vertex of view depth d and world height y. */
137
+ fn _rzFogAmount(d: f32, y: f32, col: vec4f, p: vec4f) -> f32 {
138
+ if (col.w <= 0.0) { return 0.0; }
139
+ let f = clamp(d * p.x + p.y, 0.0, 1.0);
140
+ let h = clamp((p.z - y) / p.w, -1.0, 1.0);
141
+ return (1.0 - clamp(f - (1.0 - f) * h, 0.0, 1.0)) * col.w;
142
+ }
143
+
126
144
  // One view-projection per shadow cascade, inner to outer — the volumes built
127
145
  // by shadow-cascades.ts, in the same order.
128
146
  struct LightVP { viewProj: array<mat4x4f, ${SHADOW_CASCADES.length}>, };
@@ -144,6 +162,30 @@ struct LightVP { viewProj: array<mat4x4f, ${SHADOW_CASCADES.length}>, };
144
162
  // has the 1x1 fallback bound here; rzWorldSpecular checks the SH flag and
145
163
  // answers with the flat ambient instead, so nothing samples the placeholder.
146
164
  @group(0) @binding(8) var worldEnvTexture: texture_2d<f32>;
165
+ // The cast's own shadow map for a stage's floor — see _rzCastShadow.
166
+ @group(0) @binding(10) var castShadowMap: texture_depth_2d;
167
+
168
+ /**
169
+ * How much of the cast stands between this point and the stage's shadow
170
+ * direction, 0–1 — setStageCastShadow. A 5×5 comparison filter at 1.5 texels:
171
+ * the game blurs its character shadow before laying it, and a single tap reads
172
+ * as a stencil.
173
+ */
174
+ fn _rzCastShadow(wp: vec3f) -> f32 {
175
+ if (light.castShadow.w <= 0.0) { return 0.0; }
176
+ let c = light.castVP * vec4f(wp, 1.0);
177
+ let ndc = c.xyz / c.w;
178
+ if (abs(ndc.x) >= 1.0 || abs(ndc.y) >= 1.0 || ndc.z <= 0.0 || ndc.z >= 1.0) { return 0.0; }
179
+ let uv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
180
+ let ts = 1.5 / f32(textureDimensions(castShadowMap).x);
181
+ var lit = 0.0;
182
+ for (var y = -2; y <= 2; y++) {
183
+ for (var x = -2; x <= 2; x++) {
184
+ lit += textureSampleCompareLevel(castShadowMap, shadowSampler, uv + vec2f(f32(x), f32(y)) * ts, ndc.z - 0.001);
185
+ }
186
+ }
187
+ return 1.0 - lit / 25.0;
188
+ }
147
189
  // binding(9) brdfLut is declared inside NODES_WGSL (nodes.ts).
148
190
  @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
149
191
  // The light this model takes apart from the scene's — see Engine.setModelFill
@@ -273,6 +315,11 @@ const COMMON_VS_WGSL = /* wgsl */ `
273
315
  output.worldPos = skinnedPos.xyz;
274
316
  output.restPos = position;
275
317
  output.faceT = rz_dissolve_threshold(position);
318
+ let viewZ = (camera.view * vec4f(skinnedPos.xyz, 1.0)).z;
319
+ output.fog = vec2f(
320
+ _rzFogAmount(viewZ, skinnedPos.y, light.fog[0], light.fog[1]),
321
+ _rzFogAmount(viewZ, skinnedPos.y, light.fog[2], light.fog[3]),
322
+ );
276
323
  return output;
277
324
  }
278
325
 
@@ -312,6 +312,21 @@ fn point_world_to_camera(p: vec3f) -> vec3f { return (camera.view * vec4f(p, 1.0
312
312
  * texture sees no change at all.
313
313
  */
314
314
  fn rzWorldSpecular(dir: vec3f, roughness: f32) -> vec3f {
315
+ return rzWorldSpecularLod(dir, roughness, 0.0);
316
+ }
317
+
318
+ /**
319
+ * The same, with the blur curve chosen. unity = 0 is the sqrt ramp above; 1 is
320
+ * Unity's reflection-probe lookup, mip = p·(1.7 − 0.7p)·8 — the curve a game's
321
+ * glossy floor was tuned under, about a level sharper than sqrt through the
322
+ * glossy range (roughness 0.1–0.3) and the same by 0.5.
323
+ *
324
+ * Its levels map one to one onto this chain's: a cube of N texels a face and an
325
+ * equirect 4N wide resolve the same angle per texel, so a cube's last level —
326
+ * one texel a face — is this chain's log2(width/4), and the curve stops there
327
+ * rather than averaging the whole sky, as the game's does.
328
+ */
329
+ fn rzWorldSpecularLod(dir: vec3f, roughness: f32, unity: f32) -> vec3f {
315
330
  // A SKY TEXTURE, or the best the scene has. The flag is 2 when an HDRI is
316
331
  // installed, 1 when the world is a gradient fitted to the same harmonics,
317
332
  // and 0 when it is one flat colour — and in the last two cases the ambient
@@ -327,7 +342,9 @@ fn rzWorldSpecular(dir: vec3f, roughness: f32) -> vec3f {
327
342
  // sqrt, not linear: mip n covers roughly twice the angle of n-1, so a linear
328
343
  // ramp spends most of its travel in the blurry end and a mirror-to-satin
329
344
  // sweep happens entirely in the first tenth of the dial.
330
- let lod = clamp(sqrt(clamp(roughness, 0.0, 1.0)) * levels, 0.0, levels);
345
+ let r = clamp(roughness, 0.0, 1.0);
346
+ let unityLod = min(r * (1.7 - 0.7 * r) * 8.0, max(levels - 2.0, 0.0));
347
+ let lod = clamp(mix(sqrt(r) * levels, unityLod, unity), 0.0, levels);
331
348
  return textureSampleLevel(worldEnvTexture, diffuseSampler, vec2f(u, v), lod).rgb * light.ambientColor.w;
332
349
  }
333
350
 
@@ -866,6 +883,28 @@ fn bsdf_ggx(N: vec3f, L: vec3f, V: vec3f, NL_in: f32, NV_in: f32, roughness: f32
866
883
  return NL * a2 / (D_opti * G);
867
884
  }
868
885
 
886
+ // URP's direct-light specular, as a game on URP (or a fork of it) shades its
887
+ // lamps and sun: D·V·F folded into a2 / (d²·max(0.1, LoH²)·(4a + 2)), a the
888
+ // SQUARED roughness. Unity folds π into its light units, and a light that came
889
+ // from one reaches this engine with that π restored (see the app's stage
890
+ // loader), so it comes back out here. Returns BRDF × NL, like bsdf_ggx.
891
+ fn bsdf_urp(N: vec3f, L: vec3f, V: vec3f, NL: f32, roughness: f32) -> f32 {
892
+ let a = max(roughness * roughness, 6.1035156e-5);
893
+ let a2 = a * a;
894
+ let H = normalize(L + V);
895
+ let NH = max(dot(N, H), 0.0);
896
+ let LH = max(dot(L, H), 0.0);
897
+ let d = NH * NH * (a2 - 1.0) + 1.00001;
898
+ return a2 / (d * d * max(0.1, LH * LH) * (a * 4.0 + 2.0)) * NL / EEVEE_PI;
899
+ }
900
+
901
+ /** The direct-light lobe a principled closure asked for: 0 the engine's own,
902
+ * 1 URP's (principled's unity_direct). */
903
+ fn _rzDirectLobe(N: vec3f, L: vec3f, V: vec3f, NL: f32, NV: f32, roughness: f32, unity: f32) -> f32 {
904
+ if (unity > 0.5) { return bsdf_urp(N, L, V, NL, roughness); }
905
+ return bsdf_ggx(N, L, V, NL, NV, roughness);
906
+ }
907
+
869
908
  // Split-sum DFG LUT — Karis 2013 curve fit stand-in for the 64×64 baked LUT.
870
909
  // Returns (lut.x, lut.y) in Blender convention: tint = f0·lut.x + f90·lut.y.
871
910
  fn brdf_lut_approx(NV: f32, roughness: f32) -> vec2f {
@@ -997,50 +1036,74 @@ fn principled_specular(ior: f32, level: f32) -> f32 {
997
1036
  // It walks the light grid a second time — the lamps THIS CELL can see, which
998
1037
  // is what the grid is for — and with no lamps at all neither walk runs.
999
1038
 
1000
- fn _rzLampSpecOne(i: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32) -> vec3f {
1039
+ // The walk also yields the DIFFUSE layer, against the prelude's normal
1040
+ // (_rzLampN, not the mapped N) and in the order rzLightsDiffuse sums it, so the
1041
+ // epilogue's rzLightsDiffuseOnce is that function's exact answer without a
1042
+ // second pass over the grid. See lights.ts.
1043
+ struct _RzLampPair { d: vec3f, s: vec3f };
1044
+
1045
+ fn _rzLampOne(i: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32, unity: f32) -> _RzLampPair {
1046
+ var out = _RzLampPair(vec3f(0.0), vec3f(0.0));
1001
1047
  let pr = _rzLightVec(i, 0u);
1002
1048
  let d = pr.xyz - p;
1003
1049
  let dist = length(d);
1004
- if (dist >= pr.w) { return vec3f(0.0); }
1050
+ if (dist >= pr.w) { return out; }
1005
1051
  let toLight = d / max(dist, 1e-4);
1052
+ let ndlD = max(dot(_rzLampN, toLight), 0.0);
1006
1053
  let ndl = dot(n, toLight);
1007
- if (ndl <= 0.0) { return vec3f(0.0); }
1054
+ if (ndlD <= 0.0 && ndl <= 0.0) { return out; }
1008
1055
  let t = clamp(dist / max(pr.w, 1e-4), 0.0, 1.0);
1009
1056
  let t2 = t * t;
1010
1057
  let window = 1.0 - t2 * t2;
1011
1058
  let falloff = window * window / max(dist * dist, RZ_LAMP_NEAR * RZ_LAMP_NEAR);
1012
1059
  let cone = rzLightCone(i);
1013
1060
  let aim = clamp((dot(-toLight, rzLightAim(i)) - cone.x) / max(cone.y - cone.x, 1e-4), 0.0, 1.0);
1014
- return rzLightColor(i) * (bsdf_ggx(n, toLight, v, ndl, ndv, roughness) * falloff * aim * aim);
1061
+ let c = rzLightColor(i);
1062
+ if (ndlD > 0.0) { out.d = c * (ndlD * falloff * aim * aim); }
1063
+ if (ndl > 0.0) { out.s = c * (_rzDirectLobe(n, toLight, v, ndl, ndv, roughness, unity) * falloff * aim * aim); }
1064
+ return out;
1015
1065
  }
1016
1066
 
1017
- fn _rzLampSpecWord(bits0: u32, base: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32) -> vec3f {
1018
- var acc = vec3f(0.0);
1067
+ fn _rzLampWord(bits0: u32, base: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32, unity: f32) -> _RzLampPair {
1068
+ var acc = _RzLampPair(vec3f(0.0), vec3f(0.0));
1019
1069
  var bits = bits0;
1020
1070
  loop {
1021
1071
  if (bits == 0u) { break; }
1022
1072
  let i = base + firstTrailingBit(bits);
1023
1073
  bits = bits & (bits - 1u);
1024
- acc = acc + _rzLampSpecOne(i, p, n, v, ndv, roughness);
1074
+ let one = _rzLampOne(i, p, n, v, ndv, roughness, unity);
1075
+ acc.d = acc.d + one.d;
1076
+ acc.s = acc.s + one.s;
1025
1077
  }
1026
1078
  return acc;
1027
1079
  }
1028
1080
 
1029
- fn rzLampsSpecular(p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32) -> vec3f {
1030
- var acc = vec3f(0.0);
1081
+ fn rzLampsSpecular(p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32, unity: f32) -> vec3f {
1082
+ var d = vec3f(0.0);
1083
+ var s = vec3f(0.0);
1031
1084
  let count = rzLightCount();
1032
1085
  let docs = _rzLightDocCount();
1033
1086
  if (docs > 0u) {
1034
1087
  let m = _rzLightCellMask(p);
1035
- acc = acc + _rzLampSpecWord(m.x, 0u, p, n, v, ndv, roughness) +
1036
- _rzLampSpecWord(m.y, 32u, p, n, v, ndv, roughness) +
1037
- _rzLampSpecWord(m.z, 64u, p, n, v, ndv, roughness) +
1038
- _rzLampSpecWord(m.w, 96u, p, n, v, ndv, roughness);
1088
+ let wx = _rzLampWord(m.x, 0u, p, n, v, ndv, roughness, unity);
1089
+ let wy = _rzLampWord(m.y, 32u, p, n, v, ndv, roughness, unity);
1090
+ let wz = _rzLampWord(m.z, 64u, p, n, v, ndv, roughness, unity);
1091
+ let ww = _rzLampWord(m.w, 96u, p, n, v, ndv, roughness, unity);
1092
+ d = d + wx.d + wy.d + wz.d + ww.d;
1093
+ s = s + wx.s + wy.s + wz.s + ww.s;
1039
1094
  }
1040
1095
  for (var i = docs; i < count; i = i + 1u) {
1041
- acc = acc + _rzLampSpecOne(i, p, n, v, ndv, roughness);
1096
+ let one = _rzLampOne(i, p, n, v, ndv, roughness, unity);
1097
+ d = d + one.d;
1098
+ s = s + one.s;
1042
1099
  }
1043
- return acc;
1100
+ // The first closure's walk speaks for the fragment; a second principled in
1101
+ // the same graph shares p and the prelude normal, so its answer is the same.
1102
+ if (!_rzLampDiffuseSet) {
1103
+ _rzLampDiffuse = d * (1.0 / 3.141592653589793);
1104
+ _rzLampDiffuseSet = true;
1105
+ }
1106
+ return s;
1044
1107
  }
1045
1108
 
1046
1109
  struct PrincipledIn {
@@ -1051,6 +1114,8 @@ struct PrincipledIn {
1051
1114
  spec_clamp: f32,
1052
1115
  sheen: f32,
1053
1116
  sheen_tint: f32,
1117
+ reflection_lod: f32,
1118
+ unity_direct: f32,
1054
1119
  };
1055
1120
 
1056
1121
  fn eval_principled(
@@ -1077,9 +1142,12 @@ fn eval_principled(
1077
1142
  // split-sum indirect path, matching EEVEE closure_eval_glossy_lib behavior.
1078
1143
  // The sun, and every lamp that reaches this point. One clamp over the pair:
1079
1144
  // a candle a hand's width from a polished floor is a firefly otherwise.
1080
- let spec_direct_raw = (bsdf_ggx(N, L, V, NL, NV, p.roughness) * sun_rgb * shadow
1081
- + rzLampsSpecular(wp, N, V, NV, p.roughness))
1082
- * ltc_brdf_scale_from_lut(lut);
1145
+ // URP's lobe carries its own normalisation, so the LTC rescale that matches
1146
+ // EEVEE's direct light to its split-sum indirect does not apply to it.
1147
+ let direct_scale = select(ltc_brdf_scale_from_lut(lut), 1.0, p.unity_direct > 0.5);
1148
+ let spec_direct_raw = (_rzDirectLobe(N, L, V, NL, NV, p.roughness, p.unity_direct) * sun_rgb * shadow
1149
+ + rzLampsSpecular(wp, N, V, NV, p.roughness, p.unity_direct))
1150
+ * direct_scale;
1083
1151
  let spec_direct = min(spec_direct_raw, vec3f(p.spec_clamp));
1084
1152
  // Indirect specular reads the world ALONG THE REFLECTION, at a roughness-
1085
1153
  // picked level — EEVEE's probe_evaluate_world_spec, where the diffuse half
@@ -1088,8 +1156,13 @@ fn eval_principled(
1088
1156
  // roughness, so a chrome rail mirrored what a matte wall did and a stage's
1089
1157
  // metal only ever lost its diffuse. A uniform sky gives the same number
1090
1158
  // either way, so this moves direction, not energy.
1091
- let spec_indirect = rzWorldSpecular(reflect(-V, N), p.roughness);
1092
- let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
1159
+ let spec_indirect = rzWorldSpecularLod(reflect(-V, N), p.roughness, p.reflection_lod);
1160
+ // URP tints a lamp's highlight by f0 alone — its D·V·F already folds the
1161
+ // Fresnel in — where the split-sum reflectance grows toward grazing; applied
1162
+ // to the direct term it made a far glossy floor several times brighter than
1163
+ // the game draws it. Reflections keep the split sum either way.
1164
+ let direct_tint = select(reflection_color, f0, p.unity_direct > 0.5);
1165
+ let spec_radiance = spec_direct * direct_tint + spec_indirect * reflection_color;
1093
1166
 
1094
1167
  // Sheen add — when p.sheen=0 the whole term collapses, leaving diffuse_color=base.
1095
1168
  let base_tint = tint_from_color(p.base);
@@ -1099,9 +1172,23 @@ fn eval_principled(
1099
1172
  // diffuse_weight = (1-metallic). Indirect diffuse uses amb (L_w) with no π factor
1100
1173
  // (probe_evaluate_world_diff returns SH-projected radiance, not cosine-convolved).
1101
1174
  let diffuse_weight = 1.0 - p.metallic;
1102
- let diffuse_radiance = diffuse_color * (sun_rgb * NL * shadow / EEVEE_PI + amb_rgb) * diffuse_weight;
1175
+ var diffuse_radiance = diffuse_color * (sun_rgb * NL * shadow / EEVEE_PI + amb_rgb) * diffuse_weight;
1176
+ // The lamps' diffuse, in Unity mode, as URP shades it: THIS surface's albedo
1177
+ // — its tint and its maps, not the raw texture the epilogue would use —
1178
+ // times 0.96·(1 − metal). X340's floor wears a 0.588 tint the epilogue never
1179
+ // saw, and every lamp on it landed 1.7× too bright.
1180
+ if (p.unity_direct > 0.5 && !_rzLampDiffuseTaken) {
1181
+ diffuse_radiance += p.base * (0.96 * (1.0 - p.metallic)) * rzLightsDiffuseOnce(wp, N);
1182
+ _rzLampDiffuseTaken = true;
1183
+ }
1103
1184
 
1104
- return diffuse_radiance + spec_radiance;
1185
+ // The cast's shadow on a stage (Unity mode): the game lays its character
1186
+ // shadow over the whole lit surface as a multiply toward the shadow colour.
1187
+ var lit = diffuse_radiance + spec_radiance;
1188
+ if (p.unity_direct > 0.5) {
1189
+ lit = lit * mix(vec3f(1.0), light.castShadow.rgb, _rzCastShadow(wp) * light.castShadow.w);
1190
+ }
1191
+ return lit;
1105
1192
  }
1106
1193
 
1107
1194
  `;
@@ -157,7 +157,8 @@ override APPLY_GAMMA: bool = true;
157
157
  // per frame while skybox/effect active.
158
158
  // viewU[6] = (time seconds, view transform id, canvas width, canvas height).
159
159
  // viewU[7] = (grade offset.rgb, contrast); viewU[8] = (grade power.rgb, saturation);
160
- // viewU[9] = (grade slope.rgb, grade on/off) — see grade() below.
160
+ // viewU[9] = (grade slope.rgb, grade flags) — bit 0 the CDL grade(), bit 1 the
161
+ // scene's own LUT (setStageGrade) — see _rzGradeScene() below.
161
162
  // viewU[10] = (camera world position, _) — refreshed with the basis above.
162
163
  // invGamma = 1/gamma precomputed on CPU — avoids a per-pixel divide.
163
164
  @group(0) @binding(6) var bgEquirect: texture_2d<f32>;
@@ -178,6 +179,9 @@ override APPLY_GAMMA: bool = true;
178
179
  // Blender's AgX, as the 57³ lookup it ships as rather than a reconstruction of
179
180
  // it. Sampled in the log-encoded E-Gamut space the cube expects — see agxTransform.
180
181
  @group(0) @binding(10) var agxLut: texture_3d<f32>;
182
+ // The scene's own colour grade, as a cube — see _rzStageGrade. sRGB-encoded
183
+ // storage, so what a sample returns is already linear.
184
+ @group(0) @binding(12) var stageGradeLut: texture_3d<f32>;
181
185
  // The cast, as data. Read through rzSubject/rzAnchor below — the LAYOUT IS NOT
182
186
  // STABLE and never will be, because it depends on what each effect declared.
183
187
  // Reading it directly is the one thing that would freeze it forever.
@@ -306,6 +310,33 @@ fn viewTransform(c: vec3f) -> vec3f {
306
310
  if (mode > 0.5) { return vec3f(srgb_encode(c.r), srgb_encode(c.g), srgb_encode(c.b)); }
307
311
  return vec3f(filmic(c.r), filmic(c.g), filmic(c.b));
308
312
  }
313
+
314
+ fn _rzSrgbDecode(x: f32) -> f32 {
315
+ let c = max(x, 0.0);
316
+ return select(pow((c + 0.055) / 1.055, 2.4), c / 12.92, c <= 0.04045);
317
+ }
318
+
319
+ /** The grade a scene arrived with — a game's, baked to a cube by its converter
320
+ * (setStageGrade). Looked up the way that game's final pass looks it up: the
321
+ * formed colour, linear, clamped to the cube, with texel centres at the ends,
322
+ * and trilinear between. The lookup is the whole grade; it has no dials. */
323
+ fn _rzStageGrade(c: vec3f) -> vec3f {
324
+ let lin = clamp(vec3f(_rzSrgbDecode(c.r), _rzSrgbDecode(c.g), _rzSrgbDecode(c.b)), vec3f(0.0), vec3f(1.0));
325
+ let n = f32(textureDimensions(stageGradeLut).x);
326
+ let graded = textureSampleLevel(stageGradeLut, bloomSamp, lin * ((n - 1.0) / n) + 0.5 / n, 0.0).rgb;
327
+ return vec3f(srgb_encode(graded.r), srgb_encode(graded.g), srgb_encode(graded.b));
328
+ }
329
+
330
+ /** Both grades, in order: the scene's own first, as the room was graded, then
331
+ * the author's CDL on top of it. Each is behind its own flag bit in viewU[9].w
332
+ * so a scene with neither pays one uniform branch. */
333
+ fn _rzGradeScene(c: vec3f) -> vec3f {
334
+ let flags = u32(viewU[9].w);
335
+ var x = c;
336
+ if ((flags & 2u) != 0u) { x = _rzStageGrade(x); }
337
+ if ((flags & 1u) != 0u) { x = grade(x); }
338
+ return x;
339
+ }
309
340
  `
310
341
 
311
342
  /**
@@ -455,6 +486,7 @@ fn grade(c: vec3f) -> vec3f {
455
486
  let luma = dot(x, vec3f(0.2126, 0.7152, 0.0722));
456
487
  return max(mix(vec3f(luma), x, viewU[8].w), vec3f(0.0));
457
488
  }
489
+
458
490
  `
459
491
 
460
492
  const COMPOSITE_BODY = /* wgsl */ `
@@ -537,7 +569,7 @@ const COMPOSITE_BODY = /* wgsl */ `
537
569
  // load-bearing — leaves green-screen mode's key color unshifted so chroma
538
570
  // keying still works. Skipped entirely when the grade is neutral.
539
571
  if (viewU[9].w > 0.5) {
540
- disp = grade(disp);
572
+ disp = _rzGradeScene(disp);
541
573
  }
542
574
  if (APPLY_GAMMA) {
543
575
  disp = pow(disp, vec3f(viewU[0].y));
@@ -202,7 +202,7 @@ fn rzSceneHit(uv: vec2f) -> bool {
202
202
  */
203
203
  fn rzSceneDisplay(uv: vec2f) -> vec3f {
204
204
  var c = max(viewTransform(rzScene(uv) * exp2(viewU[0].x)), vec3f(0.0));
205
- if (viewU[9].w > 0.5) { c = grade(c); }
205
+ if (viewU[9].w > 0.5) { c = _rzGradeScene(c); }
206
206
  return pow(max(c, vec3f(0.0)), vec3f(viewU[0].y));
207
207
  }
208
208
 
@@ -1,3 +0,0 @@
1
- import type { ShaderGraph } from "../schema";
2
- export declare const POOL_FLOOR_GRAPH: ShaderGraph;
3
- //# sourceMappingURL=pool_floor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pool_floor.d.ts","sourceRoot":"","sources":["../../../src/graph/presets/pool_floor.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,eAAO,MAAM,gBAAgB,EAAE,WA6B9B,CAAA"}
@@ -1,42 +0,0 @@
1
- // Pool floor — the tiled bottom seen through water, with caustics on it.
2
- //
3
- // The other half of a pool, and the half that does most of the work. A water
4
- // surface can be tuned for a long time without the shot reading as a pool;
5
- // putting the focused-light web on the FLOOR is what settles it, because that
6
- // is the cue a photograph actually carries. It is a floor material, so it is
7
- // applied to the tile under the water rather than to the water itself.
8
- //
9
- // Caustics land between the albedo and the shading deliberately: they are
10
- // light, so they should still be dimmed by the floor's own shadow rather than
11
- // glowing through it. That is the difference between water-focused sun and a
12
- // decal.
13
- export const POOL_FLOOR_GRAPH = {
14
- version: 1,
15
- name: "Pool floor",
16
- tags: ["stage", "water", "pool"],
17
- nodes: [
18
- { id: "tex", type: "texture" },
19
- { id: "mat", type: "material_diffuse" },
20
- { id: "base", type: "mix/multiply", inputs: { fac: 1.0 } },
21
- { id: "caust", type: "caustics" },
22
- {
23
- id: "principled",
24
- type: "principled",
25
- inputs: { metallic: 0.0, specular_ior_level: 0.4, roughness: 0.45, spec_clamp: 10.0, sheen_weight: 0.0, sheen_tint: 0.0 },
26
- },
27
- ],
28
- links: [
29
- { from: { node: "tex", socket: "color" }, to: { node: "base", socket: "a" } },
30
- { from: { node: "mat", socket: "color" }, to: { node: "base", socket: "b" } },
31
- { from: { node: "base", socket: "color" }, to: { node: "caust", socket: "color" } },
32
- { from: { node: "caust", socket: "color" }, to: { node: "principled", socket: "base_color" } },
33
- ],
34
- output: { node: "principled", socket: "color" },
35
- params: [
36
- { id: "tint", label: "Light tint", target: { node: "caust", socket: "tint" }, kind: "color", default: [1.0, 1.0, 1.0] },
37
- { id: "scale", label: "Caustic scale", target: { node: "caust", socket: "scale" }, kind: "float", min: 0.01, max: 1, default: 0.12 },
38
- { id: "speed", label: "Caustic speed", target: { node: "caust", socket: "speed" }, kind: "float", min: 0, max: 1, default: 0.15 },
39
- { id: "strength", label: "Caustic strength", target: { node: "caust", socket: "strength" }, kind: "float", min: 0, max: 3, default: 0.6 },
40
- { id: "fade", label: "Depth fade", target: { node: "caust", socket: "fade" }, kind: "float", min: 1, max: 200, default: 30 },
41
- ],
42
- };
@@ -1,3 +0,0 @@
1
- import type { ShaderGraph } from "../schema";
2
- export declare const WATER_GRAPH: ShaderGraph;
3
- //# sourceMappingURL=water.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"water.d.ts","sourceRoot":"","sources":["../../../src/graph/presets/water.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,eAAO,MAAM,WAAW,EAAE,WAgBzB,CAAA"}
@@ -1,31 +0,0 @@
1
- // Water — a planar reflection, rippled, seen through Fresnel.
2
- //
3
- // Put a stage's water materials in a style group carrying this graph and the
4
- // pool starts reflecting. The reflection itself is the engine's mirror pass,
5
- // which this is the only graph to read; everything else here is the shaping
6
- // that keeps it from reading as a mirror laid flat in the floor.
7
- //
8
- // The exposed params are the ones worth a slider while judging a shot. Scale
9
- // and speed are the ripple; a real pool's surface is slow and broad, so both
10
- // defaults sit low. Tint is the water's own colour — a game rip carries that
11
- // in its material, and the converter hands it straight over.
12
- //
13
- // How see-through the water is belongs to the MATERIAL's alpha, not to this
14
- // graph, which is where every other group's coverage comes from too.
15
- export const WATER_GRAPH = {
16
- version: 1,
17
- name: "Water",
18
- tags: ["water", "stage"],
19
- nodes: [{ id: "water", type: "water" }],
20
- links: [],
21
- output: { node: "water", socket: "color" },
22
- params: [
23
- { id: "tint", label: "Tint", target: { node: "water", socket: "tint" }, kind: "color", default: [0.04, 0.62, 0.66] },
24
- { id: "scale", label: "Ripple scale", target: { node: "water", socket: "scale" }, kind: "float", min: 0.01, max: 1, default: 0.18 },
25
- { id: "speed", label: "Ripple speed", target: { node: "water", socket: "speed" }, kind: "float", min: 0, max: 1, default: 0.12 },
26
- { id: "strength", label: "Ripple depth", target: { node: "water", socket: "strength" }, kind: "float", min: 0, max: 2, default: 0.7 },
27
- { id: "blur", label: "Reflection blur", target: { node: "water", socket: "blur" }, kind: "float", min: 0, max: 1, default: 0.25 },
28
- { id: "mirroring", label: "Mirroring", target: { node: "water", socket: "mirroring" }, kind: "float", min: 0, max: 1, default: 0.20 },
29
- { id: "sparkle", label: "Sparkle", target: { node: "water", socket: "sparkle" }, kind: "float", min: 0, max: 4, default: 1.0 },
30
- ],
31
- };
@@ -1,147 +0,0 @@
1
- import { type Rigidbody } from "./types";
2
- import type { Skinning } from "../model";
3
- /**
4
- * Supplementary colliders fitted to the mesh a PMX rigid body fails to cover.
5
- *
6
- * PMX body proxies are bodies of revolution — 下半身 is a capsule lying
7
- * transversely across the hips, so its cross-section is a circle. A pelvis seen
8
- * from the side is not a circle, and across every rig measured the butt sits
9
- * 0.18–0.71 units OUTSIDE that capsule while the front is flush or inside. That
10
- * gap is where a skirt goes through a character: the cloth is resting correctly
11
- * on the collider it was given, and the collider stops half a unit short of the
12
- * skin.
13
- *
14
- * A rest offset cannot close it (see RigidBodyStore.restOffset) — it is one
15
- * scalar on a radially symmetric shape, and the error is asymmetric. Measured
16
- * on 托特's run cycle, the offset that clears the butt is 0.5, which is simply
17
- * the size of the missing geometry, and at that value the skirt stands off the
18
- * front and hips by the same 0.5.
19
- *
20
- * So fit the missing piece instead: take each bone-following body's own skinned
21
- * vertices, find the ones that escape its shape in a concentrated lobe, and fit
22
- * a capsule to that lobe. That is the fix riggers apply by hand, derived from
23
- * the mesh rather than guessed.
24
- *
25
- * Measured on 托特's run cycle, counting butt vertices swallowed by a cloth
26
- * body: 32.4 per frame with nothing, 30.9 with a 0.02 rest offset, 12.4 with
27
- * this pass, 10.4 with both.
28
- */
29
- export interface AutoFitOptions {
30
- /** A vertex counts as protruding past this much (model units). Below it the
31
- * mesh is within authoring tolerance of the proxy and needs no help. */
32
- minProtrusion?: number;
33
- /**
34
- * Ignore vertices further out than this multiple of the source shape's
35
- * RADIUS (its smallest characteristic dimension).
36
- *
37
- * A coarse first cut at keeping hair and loose clothing out — anything
38
- * standing further off the proxy than the proxy is wide is not skin. The
39
- * discriminator that actually does the work is minConcentration.
40
- */
41
- maxProtrusionRatio?: number;
42
- /** Fewer protruding vertices than this and it is a spike, not a region. */
43
- minVertices?: number;
44
- /**
45
- * Per-axis quantile the fitted lobe's extent is clipped to, each end. A
46
- * handful of stray vertices (a weight-painting slip, a seam) would otherwise
47
- * stretch the capsule far past the surface everyone can see.
48
- */
49
- quantile?: number;
50
- /** Skip the fit unless the capsule actually reaches this far past what is
51
- * already covered — otherwise it adds solver rows for nothing. */
52
- minGain?: number;
53
- /** How tightly the kept lobe's outward directions must agree, 0–1. A loose
54
- * sanity check; the real discriminator is minConcentration. */
55
- minDirectionality?: number;
56
- /** Vertices outside the dominant lobe, by this cosine, are not part of the
57
- * feature being fitted. */
58
- lobeCos?: number;
59
- /**
60
- * How much deeper the fitted lobe must be than protrusion typically is on
61
- * this body, averaged over the vertices that protrude at all.
62
- *
63
- * This is the guard that separates "the proxy is missing a lobe" from "the
64
- * proxy is not a proxy for this mesh at all". A pelvis escapes its capsule by
65
- * 0.53 at the back and 0.14 at the front, so the back lobe stands well clear
66
- * of the average. Hair escapes the skull by a similar depth in every
67
- * direction, so its deepest lobe is no deeper than typical and the ratio sits
68
- * near 1. See the note at the meanProtrusion computation for what this
69
- * deliberately refuses to fit.
70
- *
71
- * Without it the pass fits the hairstyle — measured on 托特, 頭 reports
72
- * 3653 of 4669 vertices outside its capsule, and the fitted shape was a crate
73
- * around the whole head. No threshold on depth or vertex count tells those
74
- * two cases apart; concentration does.
75
- */
76
- minConcentration?: number;
77
- /**
78
- * How many capsules may be fitted to one body, each against what the previous
79
- * ones already cover.
80
- *
81
- * One capsule inscribed in a lobe does not fill it — on 托特's butt a single
82
- * fit removed 58% of the clipping and left the rest at the edges the capsule
83
- * could not reach. Each further pass re-measures protrusion against the
84
- * original shape UNION everything fitted so far, so it lands on whatever is
85
- * still exposed instead of re-fitting the same bulge.
86
- */
87
- maxShapesPerBody?: number;
88
- /**
89
- * Quantile of the lobe's perpendicular spread used as the capsule radius.
90
- *
91
- * The clip-versus-float dial, and the one to reach for if a result looks
92
- * wrong. Lower hugs the body: less standoff in idle, more clipping left. 1.0
93
- * encloses the lobe entirely, which is what produced a visibly hovering
94
- * skirt on 托特.
95
- */
96
- radiusQuantile?: number;
97
- /**
98
- * Fit only the deepest part of each lobe: points at least this fraction of
99
- * the lobe's own maximum protrusion.
100
- *
101
- * A lobe tapers to nothing at its edges, and a capsule sized to span the
102
- * whole thing has to be fat enough to reach the edges too. Fitting the deep
103
- * core keeps each capsule small and lets a later pass handle the shallows
104
- * with its own, which is both tighter and closer to what the surface is
105
- * actually doing.
106
- */
107
- lobeDepthFraction?: number;
108
- }
109
- /** What the pass decided for one source body — surfaced so a rig can be
110
- * inspected rather than silently rebuilt underneath its author. */
111
- export interface AutoFitResult {
112
- /** Index of the body in the input array that this supplements. */
113
- sourceIndex: number;
114
- sourceName: string;
115
- /** Vertices dominated by that body's bone, and how many were outside it. */
116
- vertexCount: number;
117
- protrudingCount: number;
118
- /** Furthest any kept vertex sat outside the source shape. */
119
- maxProtrusion: number;
120
- /** How far the fitted capsule reaches past what was already covered. */
121
- gain: number;
122
- /** Length of the kept lobe's mean outward direction, 0–1. */
123
- directionality: number;
124
- /** Lobe depth over the body's average protrusion. See minConcentration. */
125
- concentration: number;
126
- /**
127
- * Furthest the capsule's newly exposed surface sits from the skin it was
128
- * fitted to — the amount cloth resting on it will stand off the body at the
129
- * worst point, and the number to watch if a result looks puffy. Only the part
130
- * reaching outside the source shape is measured; the rest is buried.
131
- */
132
- overshoot: number;
133
- body: Rigidbody;
134
- }
135
- /**
136
- * Fit supplementary colliders for every bone-following body whose own mesh
137
- * escapes it.
138
- *
139
- * `vertices` is the model's interleaved vertex buffer (stride 8, position
140
- * first) and `skinning` its joints/weights — both in PMX bind pose, the same
141
- * space as `shapePosition` / `shapeRotation`, so nothing has to be skinned.
142
- *
143
- * Returns one entry per body it chose to supplement; the caller appends
144
- * `.body` to the rigid body list before constructing RezePhysics.
145
- */
146
- export declare function fitSupplementaryColliders(rigidbodies: Rigidbody[], vertices: Float32Array, skinning: Skinning, options?: AutoFitOptions): AutoFitResult[];
147
- //# sourceMappingURL=autofit.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"autofit.d.ts","sourceRoot":"","sources":["../../src/physics/autofit.ts"],"names":[],"mappings":"AACA,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,SAAS,CAAA;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,cAAc;IAC7B;6EACyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;uEACmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;oEACgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;gCAC4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AA2BD;oEACoE;AACpE,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAA;IACtB,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,SAAS,CAAA;CAChB;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,SAAS,EAAE,EACxB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,cAAmB,GAC3B,aAAa,EAAE,CA4CjB"}