reze-engine 0.58.1 → 0.58.3

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 (54) hide show
  1. package/dist/effect-points.d.ts +30 -0
  2. package/dist/effect-points.d.ts.map +1 -0
  3. package/dist/effect-points.js +49 -0
  4. package/dist/engine.d.ts +25 -2
  5. package/dist/engine.d.ts.map +1 -1
  6. package/dist/engine.js +199 -35
  7. package/dist/graph/style-group.d.ts +7 -0
  8. package/dist/graph/style-group.d.ts.map +1 -1
  9. package/dist/light-grid.d.ts +25 -0
  10. package/dist/light-grid.d.ts.map +1 -0
  11. package/dist/light-grid.js +91 -0
  12. package/dist/model.d.ts +9 -0
  13. package/dist/model.d.ts.map +1 -1
  14. package/dist/model.js +6 -0
  15. package/dist/pmx-loader.d.ts.map +1 -1
  16. package/dist/pmx-loader.js +11 -6
  17. package/dist/shaders/cast-api.d.ts +1 -1
  18. package/dist/shaders/cast-layout.d.ts +5 -3
  19. package/dist/shaders/cast-layout.d.ts.map +1 -1
  20. package/dist/shaders/cast-layout.js +5 -3
  21. package/dist/shaders/directives.d.ts +3 -0
  22. package/dist/shaders/directives.d.ts.map +1 -1
  23. package/dist/shaders/directives.js +5 -0
  24. package/dist/shaders/lights.d.ts +14 -9
  25. package/dist/shaders/lights.d.ts.map +1 -1
  26. package/dist/shaders/lights.js +118 -65
  27. package/dist/shaders/passes/composite.d.ts +1 -1
  28. package/dist/shaders/passes/composite.d.ts.map +1 -1
  29. package/dist/shaders/passes/composite.js +2 -0
  30. package/dist/shaders/passes/grid.d.ts.map +1 -1
  31. package/dist/shaders/passes/grid.js +2 -0
  32. package/dist/shaders/passes/particles.d.ts +20 -1
  33. package/dist/shaders/passes/particles.d.ts.map +1 -1
  34. package/dist/shaders/passes/particles.js +36 -1
  35. package/dist/shaders/passes/trails.d.ts.map +1 -1
  36. package/dist/shaders/passes/trails.js +2 -1
  37. package/dist/shaders/points-api.d.ts +11 -0
  38. package/dist/shaders/points-api.d.ts.map +1 -0
  39. package/dist/shaders/points-api.js +58 -0
  40. package/package.json +1 -1
  41. package/src/effect-points.ts +67 -0
  42. package/src/engine.ts +215 -35
  43. package/src/graph/style-group.ts +12 -1
  44. package/src/light-grid.ts +139 -0
  45. package/src/model.ts +12 -0
  46. package/src/pmx-loader.ts +13 -6
  47. package/src/shaders/cast-layout.ts +5 -3
  48. package/src/shaders/directives.ts +8 -0
  49. package/src/shaders/lights.ts +118 -65
  50. package/src/shaders/passes/composite.ts +2 -0
  51. package/src/shaders/passes/grid.ts +2 -0
  52. package/src/shaders/passes/particles.ts +46 -2
  53. package/src/shaders/passes/trails.ts +2 -1
  54. package/src/shaders/points-api.ts +66 -0
@@ -11,6 +11,7 @@ import { clockApi, EFFECT_MATH_API, PARTICLE_STRUCT_WGSL, trailSlotsApi, viewpor
11
11
  import { sceneIdFieldWgsl, sceneIdPadWgsl } from "./scene-contract"
12
12
  import { idApi } from "../id-api"
13
13
  import { sceneLightApi } from "../scene-light-api"
14
+ import { pointsApi } from "../points-api"
14
15
  // GPU particles for user effects: a compute step and an instanced quad draw.
15
16
  //
16
17
  // Its own shader MODULE rather than more source spliced into composite.ts, for
@@ -100,6 +101,15 @@ type ParticleSource = {
100
101
  * where every stacked fragment is otherwise shaded in full; the prepass is
101
102
  * the shape of that rejection, done by hand and cheaply. */
102
103
  cover: boolean
104
+ /** The effect defines `particleCount() -> u32`: how many of the pool are
105
+ * live this frame, as a function of its dials. The step is dispatched and
106
+ * the quads drawn for that many slots and no more, through indirect
107
+ * arguments a one-thread kernel writes first. A pool sized for its densest
108
+ * setting then costs its current setting: a lawn's spare 1.3M slots were
109
+ * read, written and issued as degenerate quads every frame, and that
110
+ * traffic alone was a third of the frame. The live slots are always the
111
+ * first N — an author whose live set is not a prefix has no use for this. */
112
+ live: boolean
103
113
  /** The side of this effect's grid, or 0 when it declared none. Particles READ
104
114
  * the grid; the kernel that steps it lives in the grid pass. A blade of grass
105
115
  * bending where a foot pressed is the whole case: the bend has to persist and
@@ -109,6 +119,14 @@ type ParticleSource = {
109
119
 
110
120
  /** Where the shading stage's five scene-light bindings start — see scene-light-api.ts. */
111
121
  export const PARTICLE_LIGHT_BINDING = 10
122
+ /** The effect's named points (`#points`), in both stages — see points-api.ts. */
123
+ export const PARTICLE_POINTS_BINDING = 15
124
+ /** The indirect arguments a `particleCount` effect writes — compute stage only.
125
+ * Eight u32: the step's dispatch (groups, 1, 1, pad) then the quads' draw
126
+ * (6, count, 0, 0), so the draw reads at byte 16. */
127
+ export const PARTICLE_INDIRECT_BINDING = 16
128
+ export const PARTICLE_INDIRECT_BYTES = 32
129
+ export const PARTICLE_INDIRECT_DRAW_OFFSET = 16
112
130
 
113
131
  /** Bytes per particle. Explicitly padded — see the struct below. */
114
132
  export const PARTICLE_STRIDE = 48
@@ -168,13 +186,15 @@ fn rzCamPos() -> vec3f { return cam.camPos; }
168
186
  `
169
187
 
170
188
  /** Does the source define the particle contract? All three are required
171
- * together; `cover` is optional and only means anything to a cutout. */
172
- export function particleEntryPoints(wgsl: string): { init: boolean; step: boolean; shade: boolean; cover: boolean } {
189
+ * together; `cover` is optional and only means anything to a cutout, and
190
+ * `count` is optional and bounds the frame's work — see particleCount. */
191
+ export function particleEntryPoints(wgsl: string): { init: boolean; step: boolean; shade: boolean; cover: boolean; count: boolean } {
173
192
  return {
174
193
  init: /\bfn\s+particleInit\s*\(/.test(wgsl),
175
194
  step: /\bfn\s+particleStep\s*\(/.test(wgsl),
176
195
  shade: /\bfn\s+particleShade\s*\(/.test(wgsl),
177
196
  cover: /\bfn\s+particleCover\s*\(/.test(wgsl),
197
+ count: /\bfn\s+particleCount\s*\(/.test(wgsl),
178
198
  }
179
199
  }
180
200
 
@@ -212,8 +232,31 @@ ${src.paramsDecl}
212
232
  // Stubbed: this module cannot read an attachment the scene pass writes — see
213
233
  // id-api.ts. The author's whole file compiles here, so the names must exist.
214
234
  idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) +
235
+ // The named points, for real: particleInit is where a flame is put on a wick.
236
+ pointsApi(true, 0, PARTICLE_POINTS_BINDING) +
237
+ (src.live
238
+ ? `@group(0) @binding(${PARTICLE_INDIRECT_BINDING}) var<storage, read_write> _rzIndirect: array<u32, 8>;\n`
239
+ : "") +
215
240
  "\n// ── user effect ──\n" +
216
241
  src.wgsl +
242
+ (src.live
243
+ ? /* wgsl */ `
244
+ // The frame's live count, from the author's dials, into the arguments the
245
+ // step dispatch and both draws read. One thread; runs before the step.
246
+ @compute @workgroup_size(1)
247
+ fn rzCount() {
248
+ let n = min(particleCount(), pu.count);
249
+ _rzIndirect[0] = (n + 63u) / 64u;
250
+ _rzIndirect[1] = 1u;
251
+ _rzIndirect[2] = 1u;
252
+ _rzIndirect[3] = 0u;
253
+ _rzIndirect[4] = 6u;
254
+ _rzIndirect[5] = n;
255
+ _rzIndirect[6] = 0u;
256
+ _rzIndirect[7] = 0u;
257
+ }
258
+ `
259
+ : "") +
217
260
  /* wgsl */ `
218
261
  @compute @workgroup_size(64)
219
262
  fn main(@builtin(global_invocation_id) gid: vec3u) {
@@ -289,6 +332,7 @@ ${src.paramsDecl}
289
332
  // The sun's shadow and the world's ambient, for real: the shadow pass has
290
333
  // run by the time this draws, inside the scene pass. See scene-light-api.ts.
291
334
  sceneLightApi(true, 0, PARTICLE_LIGHT_BINDING) +
335
+ pointsApi(true, 0, PARTICLE_POINTS_BINDING) +
292
336
  "\n// ── user effect ──\n" +
293
337
  src.wgsl +
294
338
  /* wgsl */ `
@@ -11,6 +11,7 @@ import { clockApi, EFFECT_MATH_API, PARTICLE_STRUCT_WGSL, trailSlotsApi, viewpor
11
11
  import { sceneIdFieldWgsl, sceneIdPadWgsl } from "./scene-contract"
12
12
  import { idApi } from "../id-api"
13
13
  import { sceneLightApi } from "../scene-light-api"
14
+ import { pointsApi } from "../points-api"
14
15
  // Ribbons along a bone's recorded path, drawn as geometry.
15
16
  //
16
17
  // This is the effect the whole geometry path was built for. As a fullscreen
@@ -302,7 +303,7 @@ fn rzTurnRadius(a: vec3f, b: vec3f, c: vec3f) -> f32 {
302
303
  gridReadApi(0, 8, 9, src.gridSize) +
303
304
  // Stubbed: this module cannot read an attachment the scene pass writes — see
304
305
  // id-api.ts. The author's whole file compiles here, so the names must exist.
305
- idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) +
306
+ idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) + pointsApi(false) +
306
307
  "\n// ── user effect ──\n" +
307
308
  src.wgsl +
308
309
  /* wgsl */ `
@@ -0,0 +1,66 @@
1
+ // Named points on the models, for an effect that puts something at each one.
2
+ //
3
+ // #points flame
4
+ //
5
+ // gives the effect every bone whose name starts with `flame`, on every model in
6
+ // the scene — a stage, its parts, a prop, a character — as RzPoint: where the
7
+ // bone stands and where its tail points, in world space, this frame. A candle
8
+ // stage carries a bone per wick; a flame effect draws one flame on each, sized
9
+ // and stood up by the tail.
10
+ //
11
+ // BONES, because a bone is MMD's own word for a named point in a model. A stage
12
+ // that brings its candles as bones needs no side file, a hand-made stage can
13
+ // add them in any PMX editor, and a candle a character carries moves with the
14
+ // hand that holds it.
15
+ //
16
+ // Not the cast's anchors: those name up to sixteen bones on four characters,
17
+ // one bone per name. A stage is not a character, and fifty wicks share a
18
+ // prefix rather than each having a name worth declaring.
19
+
20
+ /** How many points one effect can read. A candlelit hall is fifty. */
21
+ export const MAX_EFFECT_POINTS = 256
22
+
23
+ /** Floats in a points buffer: a vec4 header (count), then two vec4s a point. */
24
+ export const POINTS_FLOATS = 4 + MAX_EFFECT_POINTS * 8
25
+
26
+ const STRUCT = /* wgsl */ `
27
+ /** One named point: where the bone stands, and where its tail points to. */
28
+ struct RzPoint {
29
+ pos: vec3f,
30
+ tip: vec3f,
31
+ }
32
+ `
33
+
34
+ /**
35
+ * rzPointCount / rzPoint. `bound` is false in every module that cannot read the
36
+ * buffer — the author's whole file is spliced into each module it has a mount
37
+ * in, so the names must resolve there too, and there they read as no points.
38
+ */
39
+ export function pointsApi(bound: boolean, group = 0, binding = 0): string {
40
+ if (!bound) {
41
+ return (
42
+ STRUCT +
43
+ /* wgsl */ `
44
+ fn rzPointCount() -> u32 { return 0u; }
45
+ fn rzPoint(i: u32) -> RzPoint { var p: RzPoint; return p; }
46
+ `
47
+ )
48
+ }
49
+ return (
50
+ STRUCT +
51
+ /* wgsl */ `
52
+ @group(${group}) @binding(${binding}) var<storage, read> _rzPoints: array<vec4f>;
53
+
54
+ /** How many named points the scene has for this effect, this frame. */
55
+ fn rzPointCount() -> u32 { return min(u32(_rzPoints[0].x), ${MAX_EFFECT_POINTS}u); }
56
+
57
+ /** Point i, world space. */
58
+ fn rzPoint(i: u32) -> RzPoint {
59
+ var p: RzPoint;
60
+ p.pos = _rzPoints[1u + i * 2u].xyz;
61
+ p.tip = _rzPoints[2u + i * 2u].xyz;
62
+ return p;
63
+ }
64
+ `
65
+ )
66
+ }