reze-engine 0.57.6 → 0.58.1

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 (37) hide show
  1. package/dist/engine.d.ts +31 -0
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +231 -82
  4. package/dist/shaders/directives.d.ts +18 -1
  5. package/dist/shaders/directives.d.ts.map +1 -1
  6. package/dist/shaders/directives.js +22 -3
  7. package/dist/shaders/lights.d.ts +2 -10
  8. package/dist/shaders/lights.d.ts.map +1 -1
  9. package/dist/shaders/lights.js +4 -24
  10. package/dist/shaders/materials/common.d.ts.map +1 -1
  11. package/dist/shaders/materials/common.js +3 -17
  12. package/dist/shaders/passes/composite.d.ts.map +1 -1
  13. package/dist/shaders/passes/composite.js +2 -0
  14. package/dist/shaders/passes/grid.d.ts.map +1 -1
  15. package/dist/shaders/passes/grid.js +2 -0
  16. package/dist/shaders/passes/particles.d.ts +20 -2
  17. package/dist/shaders/passes/particles.d.ts.map +1 -1
  18. package/dist/shaders/passes/particles.js +77 -5
  19. package/dist/shaders/passes/trails.d.ts +3 -0
  20. package/dist/shaders/passes/trails.d.ts.map +1 -1
  21. package/dist/shaders/passes/trails.js +7 -1
  22. package/dist/shaders/scene-light-api.d.ts +42 -0
  23. package/dist/shaders/scene-light-api.d.ts.map +1 -0
  24. package/dist/shaders/scene-light-api.js +144 -0
  25. package/dist/shaders/shadow-api.d.ts +29 -0
  26. package/dist/shaders/shadow-api.d.ts.map +1 -0
  27. package/dist/shaders/shadow-api.js +112 -0
  28. package/package.json +1 -1
  29. package/src/engine.ts +272 -96
  30. package/src/shaders/directives.ts +37 -4
  31. package/src/shaders/lights.ts +4 -24
  32. package/src/shaders/materials/common.ts +3 -17
  33. package/src/shaders/passes/composite.ts +2 -0
  34. package/src/shaders/passes/grid.ts +2 -0
  35. package/src/shaders/passes/particles.ts +93 -6
  36. package/src/shaders/passes/trails.ts +10 -1
  37. package/src/shaders/scene-light-api.ts +148 -0
package/dist/engine.js CHANGED
@@ -41,7 +41,7 @@ import { boneOverlay, boneMarkerPositions, buildOverlayShapes, jointOverlay, lin
41
41
  import { BLOOM_BLIT_SHADER_WGSL, BLOOM_DOWNSAMPLE_SHADER_WGSL, BLOOM_UPSAMPLE_SHADER_WGSL, } from "./shaders/passes/bloom";
42
42
  import { AGX_LUT_GZ, AGX_LUT_SIZE } from "./shaders/agx-lut";
43
43
  import { buildCompositeShader, EFFECT_SCENE_API, buildFieldShader, EFFECT_ANCHORS, EFFECT_SUBJECTS, EFFECT_TRAIL_BASE, EFFECT_TRAIL_SAMPLES, } from "./shaders/passes/composite";
44
- import { buildParticleComputeShader, buildParticleRenderShader, particleEntryPoints, PARTICLE_STRIDE, } from "./shaders/passes/particles";
44
+ import { buildParticleComputeShader, buildParticleRenderShader, PARTICLE_LIGHT_BINDING, particleEntryPoints, PARTICLE_STRIDE, } from "./shaders/passes/particles";
45
45
  import { SIM_FORMAT, GRID_MAX, buildSimShader, gridEntryPoint, } from "./shaders/passes/grid";
46
46
  import { buildCastResolveShader, buildCastSeedShader, buildCastStepShader, castDistanceUsed, CAST_COVERAGE_FORMAT, CAST_DIST_FORMAT, CAST_FIELD_DIV, CAST_SEED_FORMAT, } from "./shaders/passes/cast-distance";
47
47
  import { buildTrailShader, trailEntryPoints, TRAIL_SUBDIVISIONS } from "./shaders/passes/trails";
@@ -1305,6 +1305,13 @@ export class Engine {
1305
1305
  /** The ground's uniform block, kept so the caster sphere can be refreshed in
1306
1306
  * it every frame rather than rebuilding the buffer (addGround allocates). */
1307
1307
  this.groundMaterialData = null;
1308
+ /** What addGround was given, so an effect's `#ground` can be lifted again
1309
+ * without rebuilding the floor. */
1310
+ this.groundBaseColor = new Vec3(1, 1, 1);
1311
+ this.groundBaseNoise = 0;
1312
+ /** The caster sphere as last written after the cascade matrices; NaN so the
1313
+ * first frame always writes. */
1314
+ this.shadowCastersWritten = new Float32Array([NaN, NaN, NaN, NaN]);
1308
1315
  /** Set by the first getGpuTimings() call. See it for why asking is the switch. */
1309
1316
  this.timestampsWanted = false;
1310
1317
  this.groundMirrorPipeline = null;
@@ -3034,9 +3041,9 @@ export class Engine {
3034
3041
  for (const e of this.effects) {
3035
3042
  if (e.particles) {
3036
3043
  const b = e.particles.rebind();
3037
- e.particles.computeBind = b.computeBind;
3038
- e.particles.renderBind = b.renderBind;
3039
- e.particles.mirrorRenderBind = b.mirrorRenderBind;
3044
+ e.particles.computeBinds = b.computeBinds;
3045
+ e.particles.renderBinds = b.renderBinds;
3046
+ e.particles.mirrorRenderBinds = b.mirrorRenderBinds;
3040
3047
  }
3041
3048
  if (e.grid)
3042
3049
  e.grid.binds = [this.gridBindGroup(e.grid, 0), this.gridBindGroup(e.grid, 1)];
@@ -3567,18 +3574,21 @@ export class Engine {
3567
3574
  trails?.uniform.destroy();
3568
3575
  return { ok: false, diagnostics, mounts, params: d.params, duration: d.duration, readsCast: false };
3569
3576
  };
3570
- if (wantsParticles) {
3571
- const built = await this.buildParticles(wgsl, d, anchors, alias, paramsFor);
3572
- if (!built.ok)
3573
- return abandon(built.diagnostics);
3574
- particles = built.state;
3575
- }
3577
+ // THE GRID FIRST. Particles and ribbons READ it — a blade of grass reads the
3578
+ // bend a foot left there — so the textures have to exist before their bind
3579
+ // groups are built. Nothing reads them back, so the grid depends on neither.
3576
3580
  if (gridEntryPoint(wgsl)) {
3577
3581
  const built = await this.buildSim(wgsl, d, anchors, alias, paramsFor);
3578
3582
  if (!built.ok)
3579
3583
  return abandon(built.diagnostics);
3580
3584
  grid = built.state;
3581
3585
  }
3586
+ if (wantsParticles) {
3587
+ const built = await this.buildParticles(wgsl, d, anchors, alias, paramsFor, grid);
3588
+ if (!built.ok)
3589
+ return abandon(built.diagnostics);
3590
+ particles = built.state;
3591
+ }
3582
3592
  if (wantsTrails) {
3583
3593
  // Only anchors that asked for `trail` have a path to draw; a ribbon on a
3584
3594
  // bone recorded without one would read zeroes and paint a line to the origin.
@@ -3586,7 +3596,7 @@ export class Engine {
3586
3596
  if (trailSlots === 0) {
3587
3597
  return abandon(["a ribbon effect needs at least one #anchor <bone> trail"]);
3588
3598
  }
3589
- const built = await this.buildTrails(wgsl, d, anchors, alias, paramsFor);
3599
+ const built = await this.buildTrails(wgsl, d, anchors, alias, paramsFor, grid);
3590
3600
  if (!built.ok)
3591
3601
  return abandon(built.diagnostics);
3592
3602
  trails = built.state;
@@ -3665,6 +3675,7 @@ export class Engine {
3665
3675
  simStep: 0,
3666
3676
  simReset: false,
3667
3677
  dissolve: d.dissolve ? dissolveConstants(authored) : null,
3678
+ ground: d.ground ? { color: new Vec3(...d.ground.color), noise: d.ground.noise } : null,
3668
3679
  // Its OWN resolution, no longer the scene's: an effect that never asked
3669
3680
  // for full res is not promoted because a neighbour did.
3670
3681
  // FULL RESOLUTION UNLESS TOLD OTHERWISE.
@@ -3895,11 +3906,23 @@ export class Engine {
3895
3906
  alias,
3896
3907
  /** The effect's declared dials. Spliced into both stages and bound at 7,
3897
3908
  * the same binding the composite gives them. */
3898
- params) {
3909
+ params,
3910
+ /** This effect's own grid, already built, or null when it declared none.
3911
+ * Read-only here: the pool samples it, the grid pass owns it. */
3912
+ grid) {
3899
3913
  // No pragma means "some": an author who wrote the trio clearly wants
3900
3914
  // particles, and failing over a missing comment would be pedantry.
3901
3915
  const count = Math.min(d.particles || 1024, Engine.MAX_PARTICLES);
3902
- const src = { wgsl, count, blend: d.particleBlend, bloom: d.bloom, paramsDecl: params.wgsl(EFFECT_PARAMS_BINDING) };
3916
+ const src = {
3917
+ wgsl,
3918
+ count,
3919
+ blend: d.particleBlend,
3920
+ bloom: d.bloom,
3921
+ paramsDecl: params.wgsl(EFFECT_PARAMS_BINDING),
3922
+ gridSize: grid?.size ?? 0,
3923
+ cover: particleEntryPoints(wgsl).cover,
3924
+ };
3925
+ const prepass = src.blend === "cutout" && src.cover;
3903
3926
  // Sparks want to spawn where a trail is, so the particle stages see the same
3904
3927
  // cast buffer the trail draw reads.
3905
3928
  const cast = {
@@ -3952,7 +3975,7 @@ export class Engine {
3952
3975
  // Declaring one set of flags for both layouts is what made the pipeline
3953
3976
  // layout invalid, and the error surfaces later and unhelpfully as "invalid
3954
3977
  // due to a previous error".
3955
- const layoutFor = (storage, visibility) => this.device.createBindGroupLayout({
3978
+ const layoutFor = (storage, visibility, shadow) => this.device.createBindGroupLayout({
3956
3979
  entries: [
3957
3980
  { binding: 0, visibility, buffer: { type: storage } },
3958
3981
  { binding: 1, visibility, buffer: { type: "uniform" } },
@@ -3966,9 +3989,30 @@ export class Engine {
3966
3989
  // Only when the effect declared any: WGSL has no empty struct, so a
3967
3990
  // param-less effect must not carry the decl OR the binding.
3968
3991
  ...(params.buffer ? [{ binding: 7, visibility, buffer: { type: "uniform" } }] : []),
3992
+ // The grid, ALWAYS — the accessor is compiled in unconditionally, and a
3993
+ // binding the shader declares must exist in the layout whether the
3994
+ // author calls it or not. With no grid it is the 1x1 of zeroes.
3995
+ { binding: 8, visibility, texture: { sampleType: "float" } },
3996
+ { binding: 9, visibility, sampler: { type: "filtering" } },
3997
+ // The scene's light, for rzShadow and rzWorldAmbient — the shading
3998
+ // stage only; the compute module compiles the stubs. See
3999
+ // scene-light-api.ts.
4000
+ ...(shadow
4001
+ ? [
4002
+ { binding: PARTICLE_LIGHT_BINDING, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
4003
+ { binding: PARTICLE_LIGHT_BINDING + 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
4004
+ { binding: PARTICLE_LIGHT_BINDING + 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
4005
+ { binding: PARTICLE_LIGHT_BINDING + 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
4006
+ { binding: PARTICLE_LIGHT_BINDING + 4, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "comparison" } },
4007
+ ]
4008
+ : []),
3969
4009
  ],
3970
4010
  });
3971
- const bindFor = (layout, camera) => this.device.createBindGroup({
4011
+ /** One per GRID PARITY, for the reason rebuildFieldBindGroup gives: the grid
4012
+ * alternates which texture is current, and rebuilding a single group every
4013
+ * frame is waste for a change that only ever toggles between two known
4014
+ * states. Both entries are the same view when there is no grid. */
4015
+ const bindFor = (layout, camera, shadow) => [0, 1].map((parity) => this.device.createBindGroup({
3972
4016
  layout,
3973
4017
  entries: [
3974
4018
  { binding: 0, resource: { buffer } },
@@ -3979,15 +4023,31 @@ export class Engine {
3979
4023
  { binding: 5, resource: { buffer: this.midiBuffer } },
3980
4024
  { binding: 6, resource: { buffer: this.lyricsBuffer } },
3981
4025
  ...(params.buffer ? [{ binding: 7, resource: { buffer: params.buffer } }] : []),
4026
+ { binding: 8, resource: grid ? grid.read[parity] : this.simFallbackView },
4027
+ { binding: 9, resource: this.simSampler },
4028
+ ...(shadow
4029
+ ? [
4030
+ { binding: PARTICLE_LIGHT_BINDING, resource: { buffer: this.lightUniformBuffer } },
4031
+ { binding: PARTICLE_LIGHT_BINDING + 1, resource: { buffer: this.shadowLightVPBuffer } },
4032
+ { binding: PARTICLE_LIGHT_BINDING + 2, resource: this.shadowMapDepthViews[0] },
4033
+ { binding: PARTICLE_LIGHT_BINDING + 3, resource: this.shadowMapDepthViews[SHADOW_CASCADES.length - 1] },
4034
+ { binding: PARTICLE_LIGHT_BINDING + 4, resource: this.shadowComparisonSampler },
4035
+ ]
4036
+ : []),
3982
4037
  ],
3983
- });
3984
- const computeLayout = layoutFor("storage", GPUShaderStage.COMPUTE);
3985
- const renderLayout = layoutFor("read-only-storage", GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT);
4038
+ }));
4039
+ const computeLayout = layoutFor("storage", GPUShaderStage.COMPUTE, false);
4040
+ const renderLayout = layoutFor("read-only-storage", GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, true);
3986
4041
  // Additive keeps the destination and adds to it, and leaves alpha alone, so
3987
4042
  // a glow does not claim coverage it never occluded. The MASK sums with it —
3988
4043
  // otherwise an additive effect could never reach the bloom gate. Both live
3989
4044
  // in scene-contract as the "particle-additive" class.
4045
+ //
4046
+ // Cutout blends exactly like alpha and differs only in depth and coverage,
4047
+ // below — so it shares alpha's targets rather than adding a class whose
4048
+ // blends would be a copy.
3990
4049
  const targets = sceneTargetsFor(src.blend === "additive" ? "particle-additive" : "particle", this.sceneFormats);
4050
+ const cutout = src.blend === "cutout";
3991
4051
  this.device.pushErrorScope("validation");
3992
4052
  try {
3993
4053
  const compute = await this.device.createComputePipelineAsync({
@@ -3995,15 +4055,41 @@ export class Engine {
3995
4055
  layout: this.device.createPipelineLayout({ bindGroupLayouts: [computeLayout] }),
3996
4056
  compute: { module: computeModule, entryPoint: "main" },
3997
4057
  });
4058
+ const renderLayoutObj = this.device.createPipelineLayout({ bindGroupLayouts: [renderLayout] });
4059
+ // The prepass, when the effect gave the cutout a cheap shape: coverage to
4060
+ // depth, colour targets at writeMask 0 (the depth-prepass class), and
4061
+ // the shading pass below then tests EQUAL and writes no depth.
4062
+ const depth = prepass
4063
+ ? await this.device.createRenderPipelineAsync({
4064
+ label: "particle depth prepass pipeline",
4065
+ layout: renderLayoutObj,
4066
+ vertex: { module: renderModule, entryPoint: "vs" },
4067
+ fragment: { module: renderModule, entryPoint: "fsDepth", targets: sceneTargetsFor("depth-prepass", this.sceneFormats) },
4068
+ primitive: { topology: "triangle-list", cullMode: "none" },
4069
+ depthStencil: { format: this.depthFormat, depthWriteEnabled: true, depthCompare: this.depthAhead },
4070
+ multisample: { count: Engine.MULTISAMPLE_COUNT },
4071
+ })
4072
+ : null;
3998
4073
  const render = await this.device.createRenderPipelineAsync({
3999
4074
  label: "particle render pipeline",
4000
- layout: this.device.createPipelineLayout({ bindGroupLayouts: [renderLayout] }),
4075
+ layout: renderLayoutObj,
4001
4076
  vertex: { module: renderModule, entryPoint: "vs" },
4002
4077
  fragment: { module: renderModule, entryPoint: "fs", targets },
4003
4078
  primitive: { topology: "triangle-list", cullMode: "none" },
4004
4079
  // Tested but not WRITTEN: particles are transparent, so writing depth
4005
4080
  // would make whichever quad drew first occlude the ones behind it.
4006
- depthStencil: { format: this.depthFormat, depthWriteEnabled: false, depthCompare: this.depthAhead },
4081
+ //
4082
+ // Except a CUTOUT, which is opaque where it is drawn at all. Its alpha
4083
+ // becomes per-sample coverage (in the shader — see FSOut.samples, the
4084
+ // HDR target has no alpha for the hardware path), so its edges stay
4085
+ // antialiased under MSAA while its depth is real — and a blade behind
4086
+ // another blade is then rejected at the depth test rather than shaded
4087
+ // and blended under it.
4088
+ // For a lawn stacked ten blades deep that is the difference between
4089
+ // shading ten layers and shading about three.
4090
+ depthStencil: prepass
4091
+ ? { format: this.depthFormat, depthWriteEnabled: false, depthCompare: "equal" }
4092
+ : { format: this.depthFormat, depthWriteEnabled: cutout, depthCompare: this.depthAhead },
4007
4093
  multisample: { count: Engine.MULTISAMPLE_COUNT },
4008
4094
  });
4009
4095
  const scoped = await this.device.popErrorScope();
@@ -4025,15 +4111,16 @@ export class Engine {
4025
4111
  counts: uniformView.uints,
4026
4112
  compute,
4027
4113
  computeLayout,
4028
- computeBind: bindFor(computeLayout, this.cameraUniformBuffer),
4114
+ computeBinds: bindFor(computeLayout, this.cameraUniformBuffer, false),
4115
+ depth,
4029
4116
  render,
4030
4117
  renderLayout,
4031
- renderBind: bindFor(renderLayout, this.cameraUniformBuffer),
4032
- mirrorRenderBind: bindFor(renderLayout, this.mirrorCameraBuffer),
4118
+ renderBinds: bindFor(renderLayout, this.cameraUniformBuffer, true),
4119
+ mirrorRenderBinds: bindFor(renderLayout, this.mirrorCameraBuffer, true),
4033
4120
  rebind: () => ({
4034
- computeBind: bindFor(computeLayout, this.cameraUniformBuffer),
4035
- renderBind: bindFor(renderLayout, this.cameraUniformBuffer),
4036
- mirrorRenderBind: bindFor(renderLayout, this.mirrorCameraBuffer),
4121
+ computeBinds: bindFor(computeLayout, this.cameraUniformBuffer, false),
4122
+ renderBinds: bindFor(renderLayout, this.cameraUniformBuffer, true),
4123
+ mirrorRenderBinds: bindFor(renderLayout, this.mirrorCameraBuffer, true),
4037
4124
  }),
4038
4125
  },
4039
4126
  };
@@ -4226,7 +4313,7 @@ export class Engine {
4226
4313
  this.device.queue.writeBuffer(p.uniform, 0, p.data.buffer);
4227
4314
  const cp = encoder.beginComputePass({ label: "particles" });
4228
4315
  cp.setPipeline(p.compute);
4229
- cp.setBindGroup(0, p.computeBind);
4316
+ cp.setBindGroup(0, p.computeBinds[e.grid?.parity ?? 0]);
4230
4317
  cp.dispatchWorkgroups(Math.ceil(p.count / 64));
4231
4318
  cp.end();
4232
4319
  }
@@ -4237,8 +4324,15 @@ export class Engine {
4237
4324
  const p = e.particles;
4238
4325
  if (!p || e.weight === 0)
4239
4326
  continue;
4327
+ const parity = e.grid?.parity ?? 0;
4328
+ const bind = view === "mirror" ? p.mirrorRenderBinds[parity] : p.renderBinds[parity];
4329
+ if (p.depth) {
4330
+ pass.setPipeline(p.depth);
4331
+ pass.setBindGroup(0, bind);
4332
+ pass.draw(6, p.count);
4333
+ }
4240
4334
  pass.setPipeline(p.render);
4241
- pass.setBindGroup(0, view === "mirror" ? p.mirrorRenderBind : p.renderBind);
4335
+ pass.setBindGroup(0, bind);
4242
4336
  pass.draw(6, p.count);
4243
4337
  }
4244
4338
  }
@@ -4260,7 +4354,10 @@ export class Engine {
4260
4354
  * describes the effect that is still on screen. */
4261
4355
  alias,
4262
4356
  /** The effect's declared dials, spliced and bound at 7 as everywhere else. */
4263
- params) {
4357
+ params,
4358
+ /** This effect's own grid, already built, or null. Ribbons read it; the grid
4359
+ * pass owns it. */
4360
+ grid) {
4264
4361
  // `slots` here is how many RIBBONS to draw — one per trailed anchor — which
4265
4362
  // is a different number from the anchor ADDRESS SPACE the accessors index
4266
4363
  // by. Conflating the two is what made a trail declared after a bare anchor
@@ -4270,7 +4367,15 @@ export class Engine {
4270
4367
  // nothing before: ribbon i was read as anchor slot i.
4271
4368
  const ribbonSlots = anchors.map((a, i) => (a.trail ? i : -1)).filter((i) => i >= 0);
4272
4369
  const slots = ribbonSlots.length;
4273
- const src = { wgsl, slots, ribbonSlots, blend: d.particleBlend, bloom: d.bloom, paramsDecl: params.wgsl(EFFECT_PARAMS_BINDING) };
4370
+ const src = {
4371
+ wgsl,
4372
+ slots,
4373
+ ribbonSlots,
4374
+ blend: (d.particleBlend === "additive" ? "additive" : "alpha"),
4375
+ bloom: d.bloom,
4376
+ paramsDecl: params.wgsl(EFFECT_PARAMS_BINDING),
4377
+ gridSize: grid?.size ?? 0,
4378
+ };
4274
4379
  const code = buildTrailShader(src, {
4275
4380
  subjects: MAX_EFFECT_SUBJECTS,
4276
4381
  samples: TRAIL_SAMPLES,
@@ -4318,6 +4423,13 @@ export class Engine {
4318
4423
  ...(params.buffer
4319
4424
  ? [{ binding: 7, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }]
4320
4425
  : []),
4426
+ // The grid, always — compiled in whether the author calls it or not.
4427
+ {
4428
+ binding: 8,
4429
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
4430
+ texture: { sampleType: "float" },
4431
+ },
4432
+ { binding: 9, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
4321
4433
  ],
4322
4434
  });
4323
4435
  // TWO targets, the scene pass's own: HDR colour and the aux (bloom mask,
@@ -4363,30 +4475,8 @@ export class Engine {
4363
4475
  data: new Float32Array(4),
4364
4476
  pipeline,
4365
4477
  layout,
4366
- bind: this.device.createBindGroup({
4367
- layout,
4368
- entries: [
4369
- { binding: 0, resource: { buffer: this.castBuffer } },
4370
- { binding: 1, resource: { buffer: uniform } },
4371
- { binding: 2, resource: { buffer: this.cameraUniformBuffer } },
4372
- { binding: 4, resource: { buffer: this.audioBuffer } },
4373
- { binding: 5, resource: { buffer: this.midiBuffer } },
4374
- { binding: 6, resource: { buffer: this.lyricsBuffer } },
4375
- ...(params.buffer ? [{ binding: 7, resource: { buffer: params.buffer } }] : []),
4376
- ],
4377
- }),
4378
- mirrorBind: this.device.createBindGroup({
4379
- layout,
4380
- entries: [
4381
- { binding: 0, resource: { buffer: this.castBuffer } },
4382
- { binding: 1, resource: { buffer: uniform } },
4383
- { binding: 2, resource: { buffer: this.mirrorCameraBuffer } },
4384
- { binding: 4, resource: { buffer: this.audioBuffer } },
4385
- { binding: 5, resource: { buffer: this.midiBuffer } },
4386
- { binding: 6, resource: { buffer: this.lyricsBuffer } },
4387
- ...(params.buffer ? [{ binding: 7, resource: { buffer: params.buffer } }] : []),
4388
- ],
4389
- }),
4478
+ binds: this.trailBindGroups(layout, uniform, params.buffer, this.cameraUniformBuffer, grid),
4479
+ mirrorBinds: this.trailBindGroups(layout, uniform, params.buffer, this.mirrorCameraBuffer, grid),
4390
4480
  },
4391
4481
  };
4392
4482
  }
@@ -4447,7 +4537,8 @@ export class Engine {
4447
4537
  this.device.queue.writeBuffer(t.uniform, 0, t.data.buffer);
4448
4538
  }
4449
4539
  pass.setPipeline(t.pipeline);
4450
- pass.setBindGroup(0, view === "mirror" ? t.mirrorBind : t.bind);
4540
+ const parity = e.grid?.parity ?? 0;
4541
+ pass.setBindGroup(0, view === "mirror" ? t.mirrorBinds[parity] : t.binds[parity]);
4451
4542
  pass.draw(6, t.slots * live * (TRAIL_SAMPLES - 1) * TRAIL_SUBDIVISIONS);
4452
4543
  }
4453
4544
  }
@@ -4575,33 +4666,35 @@ export class Engine {
4575
4666
  // one, so it has to be part of every group built against that layout —
4576
4667
  // including this one. Omitting it here is not a missing uniform, it is a
4577
4668
  // group WebGPU refuses outright, which takes the whole frame down.
4578
- const paramsEntry = t.params ? [{ binding: EFFECT_PARAMS_BINDING, resource: { buffer: t.params } }] : [];
4579
- t.bind = this.device.createBindGroup({
4580
- layout: t.layout,
4581
- entries: [
4582
- { binding: 0, resource: { buffer: this.castBuffer } },
4583
- { binding: 1, resource: { buffer: t.uniform } },
4584
- { binding: 2, resource: { buffer: this.cameraUniformBuffer } },
4585
- { binding: 4, resource: { buffer: this.audioBuffer } },
4586
- { binding: 5, resource: { buffer: this.midiBuffer } },
4587
- { binding: 6, resource: { buffer: this.lyricsBuffer } },
4588
- ...paramsEntry,
4589
- ],
4590
- });
4591
- t.mirrorBind = this.device.createBindGroup({
4592
- layout: t.layout,
4593
- entries: [
4594
- { binding: 0, resource: { buffer: this.castBuffer } },
4595
- { binding: 1, resource: { buffer: t.uniform } },
4596
- { binding: 2, resource: { buffer: this.mirrorCameraBuffer } },
4597
- { binding: 4, resource: { buffer: this.audioBuffer } },
4598
- { binding: 5, resource: { buffer: this.midiBuffer } },
4599
- { binding: 6, resource: { buffer: this.lyricsBuffer } },
4600
- ...paramsEntry,
4601
- ],
4602
- });
4669
+ t.binds = this.trailBindGroups(t.layout, t.uniform, t.params, this.cameraUniformBuffer, e.grid);
4670
+ t.mirrorBinds = this.trailBindGroups(t.layout, t.uniform, t.params, this.mirrorCameraBuffer, e.grid);
4603
4671
  }
4604
4672
  }
4673
+ /**
4674
+ * A ribbon's bind group, ONE PER GRID PARITY.
4675
+ *
4676
+ * One author for both the build and the rebind, because they drifted once
4677
+ * already: the params binding is part of the LAYOUT whenever the effect
4678
+ * declared one, so it has to be part of every group built against that
4679
+ * layout, and a group missing it is not a missing uniform but one WebGPU
4680
+ * refuses outright, taking the whole frame down.
4681
+ */
4682
+ trailBindGroups(layout, uniform, params, camera, grid) {
4683
+ return [0, 1].map((parity) => this.device.createBindGroup({
4684
+ layout,
4685
+ entries: [
4686
+ { binding: 0, resource: { buffer: this.castBuffer } },
4687
+ { binding: 1, resource: { buffer: uniform } },
4688
+ { binding: 2, resource: { buffer: camera } },
4689
+ { binding: 4, resource: { buffer: this.audioBuffer } },
4690
+ { binding: 5, resource: { buffer: this.midiBuffer } },
4691
+ { binding: 6, resource: { buffer: this.lyricsBuffer } },
4692
+ ...(params ? [{ binding: EFFECT_PARAMS_BINDING, resource: { buffer: params } }] : []),
4693
+ { binding: 8, resource: grid ? grid.read[parity] : this.simFallbackView },
4694
+ { binding: 9, resource: this.simSampler },
4695
+ ],
4696
+ }));
4697
+ }
4605
4698
  releaseParticles() {
4606
4699
  for (const e of this.effects) {
4607
4700
  e.particles?.buffer.destroy();
@@ -5896,8 +5989,10 @@ export class Engine {
5896
5989
  targets: sceneTargetsFor("depth-prepass", this.sceneFormats),
5897
5990
  },
5898
5991
  });
5992
+ // The matrices, then one vec4: the caster sphere, which effects' rzShadow
5993
+ // tests before any tap. The materials and the ground bind the matrices only.
5899
5994
  this.shadowLightVPBuffer = this.device.createBuffer({
5900
- size: 64 * SHADOW_CASCADES.length,
5995
+ size: 64 * SHADOW_CASCADES.length + 16,
5901
5996
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
5902
5997
  });
5903
5998
  this.shadowCascadeVPBuffers = SHADOW_CASCADES.map((_, i) => this.device.createBuffer({
@@ -10435,6 +10530,51 @@ export class Engine {
10435
10530
  out[2] = cz;
10436
10531
  out[3] = any ? r : 0;
10437
10532
  }
10533
+ /**
10534
+ * Dress the floor in whatever the installed effects ask for.
10535
+ *
10536
+ * The LAST effect declaring `#ground` wins, faded by its own weight so a
10537
+ * scheduled lawn brings its soil in with it and takes it away again. Three
10538
+ * floats at the top of the ground block, written only when they change —
10539
+ * the same bargain writeGroundCasterSphere strikes, for the same reason.
10540
+ */
10541
+ writeGroundDress() {
10542
+ const gb = this.groundMaterialData;
10543
+ if (!gb || !this.groundShadowMaterialBuffer)
10544
+ return;
10545
+ let want = this.groundBaseColor;
10546
+ let noise = this.groundBaseNoise;
10547
+ for (const e of this.effects) {
10548
+ if (!e.ground || e.weight <= 0)
10549
+ continue;
10550
+ const w = e.weight;
10551
+ want = new Vec3(want.x + (e.ground.color.x - want.x) * w, want.y + (e.ground.color.y - want.y) * w, want.z + (e.ground.color.z - want.z) * w);
10552
+ noise += (e.ground.noise - noise) * w;
10553
+ }
10554
+ if (gb[0] === want.x && gb[1] === want.y && gb[2] === want.z && gb[10] === noise)
10555
+ return;
10556
+ gb[0] = want.x;
10557
+ gb[1] = want.y;
10558
+ gb[2] = want.z;
10559
+ gb[10] = noise;
10560
+ // Two writes rather than one over the span: the seven floats between hold
10561
+ // the fades and the shadow strength, and this is not the place that owns
10562
+ // them.
10563
+ this.device.queue.writeBuffer(this.groundShadowMaterialBuffer, 0, gb.subarray(0, 3));
10564
+ this.device.queue.writeBuffer(this.groundShadowMaterialBuffer, 40, gb.subarray(10, 11));
10565
+ }
10566
+ /**
10567
+ * The same sphere, into the cascade block after its matrices — rzShadow's
10568
+ * copy. There whether or not a ground exists: a lawn is often the only floor.
10569
+ */
10570
+ writeShadowCasters() {
10571
+ const w = this.shadowCastersWritten;
10572
+ const c = this.casterSphere;
10573
+ if (w[0] === c[0] && w[1] === c[1] && w[2] === c[2] && w[3] === c[3])
10574
+ return;
10575
+ w.set(c);
10576
+ this.device.queue.writeBuffer(this.shadowLightVPBuffer, 64 * SHADOW_CASCADES.length, c);
10577
+ }
10438
10578
  /**
10439
10579
  * Push this frame's caster sphere into the ground's uniform.
10440
10580
  *
@@ -11307,6 +11447,8 @@ export class Engine {
11307
11447
  // keeping the uniform vec4-aligned.
11308
11448
  const gb = new Float32Array(24);
11309
11449
  this.groundMaterialData = gb;
11450
+ this.groundBaseColor = new Vec3(diffuseColor.x, diffuseColor.y, diffuseColor.z);
11451
+ this.groundBaseNoise = noiseStrength;
11310
11452
  gb[0] = diffuseColor.x;
11311
11453
  gb[1] = diffuseColor.y;
11312
11454
  gb[2] = diffuseColor.z;
@@ -13006,6 +13148,8 @@ export class Engine {
13006
13148
  this.dispatchCull(encoder);
13007
13149
  // After the cull, which is what recomputes the spheres it unions.
13008
13150
  this.writeGroundCasterSphere();
13151
+ this.writeShadowCasters();
13152
+ this.writeGroundDress();
13009
13153
  // After the cull, because a rebuild there can reallocate the argument
13010
13154
  // buffers and a bundle captures the buffer it recorded against.
13011
13155
  if (this.bundlesDirty)
@@ -14843,7 +14987,12 @@ Engine.JIGGLE_DAMPING_SCALE = 0.5;
14843
14987
  * prefix sum and readback a compacted draw list would need every frame.
14844
14988
  */
14845
14989
  /** Ceiling for `#particles`. Past this an author is asking for a stall. */
14846
- Engine.MAX_PARTICLES = 65536;
14990
+ // Raised for the lawn: 65536 blades at real density is a 40-unit patch and
14991
+ // an 80-unit one wants four hundred thousand. A cutout pool's cost is
14992
+ // bounded by the pixels it covers, not by the count — the step is one
14993
+ // compute invocation per slot, the pool buffer is 48 bytes each (24 MB at
14994
+ // the cap), and a culled blade is a degenerate quad the rasteriser drops.
14995
+ Engine.MAX_PARTICLES = 524288;
14847
14996
  /**
14848
14997
  * The field layer: user background/foreground mounts, ONE TARGET PAIR PER
14849
14998
  * RESOLUTION. Index 0 is full, index 1 is half — coarsest last, so the
@@ -20,13 +20,30 @@ export type EffectDirectives = {
20
20
  /** The field layer composites additively rather than over. */
21
21
  additiveLayer: boolean;
22
22
  /** Particle blend, which is a different axis from the field layer's. */
23
- particleBlend: "alpha" | "additive";
23
+ /** `cutout` is alpha WITH DEPTH: the pipeline writes depth and turns the
24
+ * fragment's alpha into MSAA sample coverage, so a blade of grass behind
25
+ * another is rejected by the depth test instead of blended under it. What
26
+ * every real grass renderer does, and the only thing that bounds a dense
27
+ * lawn's cost by the pixels it covers rather than by how deep it stacks. */
28
+ particleBlend: "alpha" | "additive" | "cutout";
24
29
  particles: number;
25
30
  lights: number;
26
31
  grid: number;
27
32
  bloom: boolean;
28
33
  /** This effect takes the cast apart — the host reads the timing. */
29
34
  dissolve: boolean;
35
+ /** `#ground #rrggbb [noise]`: how this effect DRESSES THE FLOOR while it is
36
+ * installed — a colour as linear RGB, and the strength of the ground's own
37
+ * grain (0 flat, 1 strong), which is what keeps soil from being a swatch.
38
+ * A lawn wants earth under it, and the earth has to be the engine's own
39
+ * ground rather than a layer painted over the frame: the ground is geometry
40
+ * in the scene pass, so a shadow lands on it and it tone-maps with
41
+ * everything else. The scene's own floor returns when the effect goes.
42
+ * Null when the file declares none. */
43
+ ground: {
44
+ color: [number, number, number];
45
+ noise: number;
46
+ } | null;
30
47
  /**
31
48
  * This effect IS a mirror: a plane in the scene showing a true reflection of
32
49
  * it, folded and re-rendered by the engine.
@@ -1 +1 @@
1
- {"version":3,"file":"directives.d.ts","sourceRoot":"","sources":["../../src/shaders/directives.ts"],"names":[],"mappings":"AA8BA,oEAAoE;AACpE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;IAChC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACjD,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6EAA6E;IAC7E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAC3C,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,0EAA0E;IAC1E,UAAU,EAAE,CAAC,GAAG,CAAC,CAAA;IACjB,8DAA8D;IAC9D,aAAa,EAAE,OAAO,CAAA;IACtB,wEAAwE;IACxE,aAAa,EAAE,OAAO,GAAG,UAAU,CAAA;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,MAAM,EAAE,OAAO,CAAA;IACf;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAkBD;;;+BAG+B;AAC/B,eAAO,MAAM,cAAc,QAAmC,CAAA;AAG9D;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,QAA8B,CAAA;AAmBzD,MAAM,MAAM,eAAe,GAAG;IAAE,UAAU,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEhF,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAyJ7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD"}
1
+ {"version":3,"file":"directives.d.ts","sourceRoot":"","sources":["../../src/shaders/directives.ts"],"names":[],"mappings":"AA8BA,oEAAoE;AACpE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;IAChC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACjD,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6EAA6E;IAC7E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAC3C,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,0EAA0E;IAC1E,UAAU,EAAE,CAAC,GAAG,CAAC,CAAA;IACjB,8DAA8D;IAC9D,aAAa,EAAE,OAAO,CAAA;IACtB,wEAAwE;IACxE;;;;iFAI6E;IAC7E,aAAa,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAA;IAC9C,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;;;;4CAOwC;IACxC,MAAM,EAAE;QAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACjE;;;;;;;;;;;;;OAaG;IACH,MAAM,EAAE,OAAO,CAAA;IACf;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAmBD;;;+BAG+B;AAC/B,eAAO,MAAM,cAAc,QAAmC,CAAA;AAG9D;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,QAA8B,CAAA;AAmBzD,MAAM,MAAM,eAAe,GAAG;IAAE,UAAU,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEhF,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CA2K7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD"}
@@ -41,6 +41,7 @@ const SPEC = {
41
41
  dissolve: 0,
42
42
  mirror: 0,
43
43
  duration: 1,
44
+ ground: "rest",
44
45
  };
45
46
  /** A line that declares something, and what it declares. Exported because an
46
47
  * editor highlights by the same rule the parser reads by — a highlighter with
@@ -89,6 +90,7 @@ export function parseDirectives(wgsl) {
89
90
  grid: 0,
90
91
  bloom: false,
91
92
  dissolve: false,
93
+ ground: null,
92
94
  mirror: false,
93
95
  duration: 0,
94
96
  };
@@ -173,15 +175,32 @@ export function parseDirectives(wgsl) {
173
175
  d.additiveLayer = true;
174
176
  return;
175
177
  case "blend":
176
- if (args[0] !== "additive") {
177
- errors.push(`${at}: #blend takes "additive" — alpha is the default`);
178
+ if (args[0] !== "additive" && args[0] !== "cutout") {
179
+ errors.push(`${at}: #blend takes "additive" or "cutout" — alpha is the default`);
178
180
  return;
179
181
  }
180
- d.particleBlend = "additive";
182
+ d.particleBlend = args[0];
181
183
  return;
182
184
  case "bloom":
183
185
  d.bloom = true;
184
186
  return;
187
+ case "ground": {
188
+ const m = /^#([0-9a-fA-F]{6})$/.exec(args[0] ?? "");
189
+ const noise = args[1] === undefined ? 0 : num(args[1]);
190
+ if (!m || args.length > 2 || noise === null) {
191
+ errors.push(`${at}: #ground takes a colour like #4b4026 and optionally a grain strength 0..1`);
192
+ return;
193
+ }
194
+ const v = parseInt(m[1], 16);
195
+ // sRGB in the file, linear in the uniform — the same transfer the
196
+ // ground's own colour goes through on the way in.
197
+ const lin = (c) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
198
+ d.ground = {
199
+ color: [lin(((v >> 16) & 255) / 255), lin(((v >> 8) & 255) / 255), lin((v & 255) / 255)],
200
+ noise: Math.min(Math.max(noise, 0), 1),
201
+ };
202
+ return;
203
+ }
185
204
  case "dissolve":
186
205
  d.dissolve = true;
187
206
  return;
@@ -33,16 +33,8 @@ export declare const LIGHTS_FLOATS: number;
33
33
  * write, which is the same trap the grid's step-only half documents.
34
34
  */
35
35
  export declare const RZ_LIGHT_STRUCT_WGSL = "\n/** What an effect returns for one of its lights. */\nstruct RzLight {\n pos: vec3f,\n color: vec3f,\n intensity: f32,\n radius: f32,\n}\n";
36
- /**
37
- * The world's light at a surface facing n — the flat colour, or the installed
38
- * HDRI's irradiance (sh[0].w = 1), evaluated from folded SH coefficients (see
39
- * ibl.ts for the folding; the shader is a plain polynomial in the normal).
40
- *
41
- * One string included by every module that declares LightUniforms with the sh
42
- * block — the hosted-api lesson: a helper defined in some modules and not
43
- * others is a compile error waiting for the first file that crosses them.
44
- */
45
- export declare const WORLD_AMBIENT_WGSL = "\nfn rzWorldAmbient(n: vec3f) -> vec3f {\n if (light.sh[0].w < 0.5) { return light.ambientColor.xyz; }\n let x = n.x;\n let y = n.y;\n let z = n.z;\n let c = light.sh[0].xyz\n + light.sh[1].xyz * y + light.sh[2].xyz * z + light.sh[3].xyz * x\n + light.sh[4].xyz * (x * y) + light.sh[5].xyz * (y * z)\n + light.sh[6].xyz * (3.0 * z * z - 1.0) + light.sh[7].xyz * (x * z)\n + light.sh[8].xyz * (x * x - y * y);\n return max(c, vec3f(0.0));\n}\n";
36
+ /** rzWorldAmbient against the materials' and the ground's `light` uniform. */
37
+ export declare const WORLD_AMBIENT_WGSL: string;
46
38
  /** Does this source define the emit mount? */
47
39
  export declare function hasLightEmit(wgsl: string): boolean;
48
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../src/shaders/lights.ts"],"names":[],"mappings":"AAyCA;yEACyE;AACzE,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,+CAA+C;AAC/C,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,UAAU,MAAM,CAAA;AAC7B,kCAAkC;AAClC,eAAO,MAAM,aAAa,QAA2C,CAAA;AAErE;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,qJAQhC,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,idAa9B,CAAA;AAED,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,SAAK,GACd,MAAM,CAoGR;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA0FhE"}
1
+ {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../src/shaders/lights.ts"],"names":[],"mappings":"AA0CA;yEACyE;AACzE,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,+CAA+C;AAC/C,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,UAAU,MAAM,CAAA;AAC7B,kCAAkC;AAClC,eAAO,MAAM,aAAa,QAA2C,CAAA;AAErE;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,qJAQhC,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,QAA4B,CAAA;AAE3D,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,SAAK,GACd,MAAM,CAoGR;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA0FhE"}