reze-engine 0.43.0 → 0.50.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 (135) hide show
  1. package/README.md +40 -438
  2. package/dist/camera.d.ts +3 -0
  3. package/dist/camera.d.ts.map +1 -1
  4. package/dist/camera.js +33 -8
  5. package/dist/engine.d.ts +841 -53
  6. package/dist/engine.d.ts.map +1 -1
  7. package/dist/engine.js +3498 -451
  8. package/dist/graph/registry.d.ts +2 -2
  9. package/dist/graph/registry.d.ts.map +1 -1
  10. package/dist/graph/registry.js +1 -1
  11. package/dist/graph/slots.d.ts +0 -1
  12. package/dist/graph/slots.d.ts.map +1 -1
  13. package/dist/graph/slots.js +37 -9
  14. package/dist/hdr.d.ts +18 -0
  15. package/dist/hdr.d.ts.map +1 -0
  16. package/dist/hdr.js +162 -0
  17. package/dist/ibl.d.ts +19 -0
  18. package/dist/ibl.d.ts.map +1 -0
  19. package/dist/ibl.js +113 -0
  20. package/dist/ik-solver.d.ts +2 -1
  21. package/dist/ik-solver.d.ts.map +1 -1
  22. package/dist/index.d.ts +5 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +10 -0
  25. package/dist/math.d.ts +20 -1
  26. package/dist/math.d.ts.map +1 -1
  27. package/dist/math.js +23 -16
  28. package/dist/midi-loader.d.ts +10 -0
  29. package/dist/midi-loader.d.ts.map +1 -0
  30. package/dist/midi-loader.js +247 -0
  31. package/dist/model.d.ts +2 -13
  32. package/dist/model.d.ts.map +1 -1
  33. package/dist/param-track.d.ts +48 -0
  34. package/dist/param-track.d.ts.map +1 -0
  35. package/dist/param-track.js +80 -0
  36. package/dist/physics/types.d.ts.map +1 -1
  37. package/dist/physics/types.js +3 -0
  38. package/dist/reflection.d.ts +27 -0
  39. package/dist/reflection.d.ts.map +1 -0
  40. package/dist/reflection.js +93 -0
  41. package/dist/shaders/anchor-table.d.ts +56 -0
  42. package/dist/shaders/anchor-table.d.ts.map +1 -0
  43. package/dist/shaders/anchor-table.js +128 -0
  44. package/dist/shaders/cast-api.d.ts +2 -0
  45. package/dist/shaders/cast-api.d.ts.map +1 -0
  46. package/dist/shaders/cast-api.js +121 -0
  47. package/dist/shaders/cast-layout.d.ts +21 -0
  48. package/dist/shaders/cast-layout.d.ts.map +1 -0
  49. package/dist/shaders/cast-layout.js +20 -0
  50. package/dist/shaders/lights.d.ts +79 -0
  51. package/dist/shaders/lights.d.ts.map +1 -0
  52. package/dist/shaders/lights.js +269 -0
  53. package/dist/shaders/lyrics-api.d.ts +39 -0
  54. package/dist/shaders/lyrics-api.d.ts.map +1 -0
  55. package/dist/shaders/lyrics-api.js +187 -0
  56. package/dist/shaders/materials/common.d.ts +2 -4
  57. package/dist/shaders/materials/common.d.ts.map +1 -1
  58. package/dist/shaders/materials/common.js +87 -35
  59. package/dist/shaders/midi-api.d.ts +10 -0
  60. package/dist/shaders/midi-api.d.ts.map +1 -0
  61. package/dist/shaders/midi-api.js +114 -0
  62. package/dist/shaders/passes/composite.d.ts +28 -22
  63. package/dist/shaders/passes/composite.d.ts.map +1 -1
  64. package/dist/shaders/passes/composite.js +165 -138
  65. package/dist/shaders/passes/cull.d.ts +2 -0
  66. package/dist/shaders/passes/cull.d.ts.map +1 -0
  67. package/dist/shaders/passes/cull.js +138 -0
  68. package/dist/shaders/passes/field-blit.d.ts +26 -0
  69. package/dist/shaders/passes/field-blit.d.ts.map +1 -0
  70. package/dist/shaders/passes/field-blit.js +65 -0
  71. package/dist/shaders/passes/grid.d.ts +31 -0
  72. package/dist/shaders/passes/grid.d.ts.map +1 -0
  73. package/dist/shaders/passes/grid.js +169 -0
  74. package/dist/shaders/passes/ground.d.ts +13 -1
  75. package/dist/shaders/passes/ground.d.ts.map +1 -1
  76. package/dist/shaders/passes/ground.js +170 -25
  77. package/dist/shaders/passes/hosted-api.d.ts +57 -0
  78. package/dist/shaders/passes/hosted-api.d.ts.map +1 -0
  79. package/dist/shaders/passes/hosted-api.js +166 -0
  80. package/dist/shaders/passes/id-debug.d.ts +28 -0
  81. package/dist/shaders/passes/id-debug.d.ts.map +1 -0
  82. package/dist/shaders/passes/id-debug.js +74 -0
  83. package/dist/shaders/passes/particles.d.ts +10 -2
  84. package/dist/shaders/passes/particles.d.ts.map +1 -1
  85. package/dist/shaders/passes/particles.js +37 -109
  86. package/dist/shaders/passes/scene-contract.d.ts +128 -0
  87. package/dist/shaders/passes/scene-contract.d.ts.map +1 -0
  88. package/dist/shaders/passes/scene-contract.js +207 -0
  89. package/dist/shaders/passes/sim.d.ts +34 -0
  90. package/dist/shaders/passes/sim.d.ts.map +1 -0
  91. package/dist/shaders/passes/sim.js +169 -0
  92. package/dist/shaders/passes/trails.d.ts +14 -2
  93. package/dist/shaders/passes/trails.d.ts.map +1 -1
  94. package/dist/shaders/passes/trails.js +55 -90
  95. package/dist/shaders/score-api.d.ts +10 -0
  96. package/dist/shaders/score-api.d.ts.map +1 -0
  97. package/dist/shaders/score-api.js +114 -0
  98. package/dist/shadow-cascades.d.ts +45 -0
  99. package/dist/shadow-cascades.d.ts.map +1 -0
  100. package/dist/shadow-cascades.js +70 -0
  101. package/dist/vmd-loader.d.ts +3 -2
  102. package/dist/vmd-loader.d.ts.map +1 -1
  103. package/package.json +1 -1
  104. package/src/camera.ts +31 -8
  105. package/src/engine.ts +4015 -556
  106. package/src/graph/registry.ts +2 -2
  107. package/src/graph/slots.ts +37 -9
  108. package/src/hdr.ts +156 -0
  109. package/src/ibl.ts +115 -0
  110. package/src/ik-solver.ts +1 -1
  111. package/src/index.ts +12 -0
  112. package/src/math.ts +23 -17
  113. package/src/midi-loader.ts +246 -0
  114. package/src/model.ts +3 -3
  115. package/src/param-track.ts +83 -0
  116. package/src/physics/types.ts +4 -1
  117. package/src/reflection.ts +94 -0
  118. package/src/shaders/anchor-table.ts +147 -0
  119. package/src/shaders/cast-api.ts +123 -0
  120. package/src/shaders/cast-layout.ts +20 -0
  121. package/src/shaders/lights.ts +280 -0
  122. package/src/shaders/lyrics-api.ts +202 -0
  123. package/src/shaders/materials/common.ts +89 -35
  124. package/src/shaders/midi-api.ts +116 -0
  125. package/src/shaders/passes/composite.ts +182 -139
  126. package/src/shaders/passes/cull.ts +139 -0
  127. package/src/shaders/passes/grid.ts +178 -0
  128. package/src/shaders/passes/ground.ts +172 -25
  129. package/src/shaders/passes/hosted-api.ts +171 -0
  130. package/src/shaders/passes/id-debug.ts +75 -0
  131. package/src/shaders/passes/particles.ts +54 -112
  132. package/src/shaders/passes/scene-contract.ts +266 -0
  133. package/src/shaders/passes/trails.ts +77 -93
  134. package/src/shadow-cascades.ts +97 -0
  135. package/src/vmd-loader.ts +2 -2
@@ -0,0 +1,10 @@
1
+ /** Floats before the key map: count, pitch range, clock, duration, release. */
2
+ export declare const SCORE_HEADER = 8;
3
+ /** One energy slot per MIDI pitch. 128 is the MIDI range, not a cap we chose. */
4
+ export declare const SCORE_KEYS = 128;
5
+ /** Where the note records begin. */
6
+ export declare const SCORE_NOTES: number;
7
+ /** Floats per note: start, duration, pitch, velocity. */
8
+ export declare const SCORE_STRIDE = 4;
9
+ /** The rzNote*/ export declare function scoreApi(group: number, binding: number): string;
10
+ //# sourceMappingURL=score-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score-api.d.ts","sourceRoot":"","sources":["../../src/shaders/score-api.ts"],"names":[],"mappings":"AA8BA,+EAA+E;AAC/E,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,iFAAiF;AACjF,eAAO,MAAM,UAAU,MAAM,CAAA;AAC7B,oCAAoC;AACpC,eAAO,MAAM,WAAW,QAA4B,CAAA;AACpD,yDAAyD;AACzD,eAAO,MAAM,YAAY,IAAI,CAAA;AAE7B,gBAAgB,CAChB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA2E/D"}
@@ -0,0 +1,114 @@
1
+ // The score data interface, as WGSL — shared verbatim by every effect module.
2
+ //
3
+ // The sibling of the audio interface, and precomputed for the same reason: an
4
+ // export steps the engine frame by frame rather than playing in real time, so
5
+ // anything read live from a player would render empty into the exported video.
6
+ // A note list is inherently precomputed, so it cannot even be tempted.
7
+ //
8
+ // WHY THIS EXISTS BESIDE rzAudio* RATHER THAN INSTEAD OF IT. The audio interface
9
+ // answers "how loud is the bass right now" — a spectrum, smeared across
10
+ // frequency and time. That is the right shape for a reactive backdrop and the
11
+ // wrong shape for a note: you cannot recover a discrete onset, a pitch and a
12
+ // duration from band energy, and the crispness of a falling note is the whole
13
+ // point of the look. The two are complementary — a scene can pulse its fog on
14
+ // rzAudioOnset while its notes fall on rzNoteAge.
15
+ //
16
+ // LAYOUT. An 8-float header, then a 128-float live key map, then the notes at 4
17
+ // floats each (start, duration, pitch, velocity). `time` and `playing` are
18
+ // rewritten every frame by whoever owns playback, exactly as the audio header
19
+ // is, so "now" is always the clock actually running.
20
+ //
21
+ // THE KEY MAP IS THE POINT OF THE DESIGN. Falling notes index the note list
22
+ // directly — one particle per note, which is what makes the geometry trivial.
23
+ // But a keyboard glow asks the opposite question, per pixel: is anything
24
+ // sounding at THIS pitch? Answering that by scanning the note list would be
25
+ // thousands of iterations per fragment. So the engine keeps a 128-entry
26
+ // per-pitch energy map, updated once per frame on the CPU, and the glow becomes
27
+ // one lookup — the same trade the audio interface makes by precomputing onset
28
+ // so "a beat-triggered effect is one comparison instead of a per-pixel history
29
+ // scan".
30
+ /** Floats before the key map: count, pitch range, clock, duration, release. */
31
+ export const SCORE_HEADER = 8;
32
+ /** One energy slot per MIDI pitch. 128 is the MIDI range, not a cap we chose. */
33
+ export const SCORE_KEYS = 128;
34
+ /** Where the note records begin. */
35
+ export const SCORE_NOTES = SCORE_HEADER + SCORE_KEYS;
36
+ /** Floats per note: start, duration, pitch, velocity. */
37
+ export const SCORE_STRIDE = 4;
38
+ /** The rzNote*/ /* rzKey* accessors, with the buffer declared at the given binding. */
39
+ export function scoreApi(group, binding) {
40
+ return /* wgsl */ `
41
+ @group(${group}) @binding(${binding}) var<storage, read> _rzScore: array<f32>;
42
+
43
+ /** Notes in the score; 0 when none is loaded, which every accessor below
44
+ * tolerates by returning zero rather than reading past the end. */
45
+ fn rzNoteCount() -> i32 { return i32(_rzScore[0]); }
46
+ /** Where the score is NOW, in seconds — the clock every age below hangs from. */
47
+ fn rzScoreTime() -> f32 { return _rzScore[3]; }
48
+ /** 1 while the score is advancing, 0 paused or absent. */
49
+ fn rzScorePlaying() -> f32 { return select(0.0, _rzScore[4], i32(_rzScore[0]) > 0); }
50
+ /** Last note-off, in seconds — the length of the piece. */
51
+ fn rzScoreDuration() -> f32 { return _rzScore[5]; }
52
+ /** Lowest and highest pitch the score actually uses. Lay a keyboard out against
53
+ * these rather than against 0..127 and a piano piece fills the screen instead
54
+ * of occupying its middle third. */
55
+ fn rzPitchLow() -> f32 { return _rzScore[1]; }
56
+ fn rzPitchHigh() -> f32 { return _rzScore[2]; }
57
+
58
+ fn _rzNoteAt(i: i32, field: i32) -> f32 {
59
+ if (i < 0 || i >= i32(_rzScore[0])) { return 0.0; }
60
+ return _rzScore[${SCORE_NOTES} + i * ${SCORE_STRIDE} + field];
61
+ }
62
+
63
+ /** When note i begins, in seconds. */
64
+ fn rzNoteStart(i: i32) -> f32 { return _rzNoteAt(i, 0); }
65
+ /** How long note i sounds, in seconds. */
66
+ fn rzNoteLength(i: i32) -> f32 { return _rzNoteAt(i, 1); }
67
+ /** MIDI pitch of note i — 60 is middle C. */
68
+ fn rzNotePitch(i: i32) -> f32 { return _rzNoteAt(i, 2); }
69
+ /** How hard note i was struck, 0..1. */
70
+ fn rzNoteVelocity(i: i32) -> f32 { return _rzNoteAt(i, 3); }
71
+
72
+ /**
73
+ * Seconds since note i began — NEGATIVE before it does.
74
+ *
75
+ * The falling-note function, and the sign is the whole of it: a note's height is
76
+ * -rzNoteAge(i) * speed, so a note not yet played sits above the keyboard at a
77
+ * distance proportional to how long is left, crosses zero exactly as it sounds,
78
+ * and keeps going. One expression, no state, no spawning — which is why one
79
+ * particle per note is the natural mapping and the pool index IS the note index.
80
+ */
81
+ fn rzNoteAge(i: i32) -> f32 { return _rzScore[3] - rzNoteStart(i); }
82
+
83
+ /** 1 while note i is sounding, 0 either side of it. */
84
+ fn rzNoteHeld(i: i32) -> f32 {
85
+ let age = rzNoteAge(i);
86
+ return select(0.0, 1.0, age >= 0.0 && age < rzNoteLength(i));
87
+ }
88
+
89
+ /**
90
+ * Energy at a MIDI pitch right now: 1 the instant it is struck, holding while
91
+ * sounding, then falling away over the release set at install time.
92
+ *
93
+ * Per PITCH rather than per note, because that is the question a keyboard asks
94
+ * and a note list cannot answer cheaply — see the header. Fractional pitches
95
+ * read the nearest key, so a caller can hand this a continuous x across the
96
+ * keyboard without rounding first.
97
+ */
98
+ fn rzKeyEnergy(pitch: f32) -> f32 {
99
+ let k = i32(round(pitch));
100
+ if (k < 0 || k >= ${SCORE_KEYS}) { return 0.0; }
101
+ return _rzScore[${SCORE_HEADER} + k];
102
+ }
103
+
104
+ /**
105
+ * A pitch's place across the score's own range, 0..1 — the x of a falling note
106
+ * and of the key it lands on, which have to agree or the effect is nonsense.
107
+ */
108
+ fn rzPitchX(pitch: f32) -> f32 {
109
+ let lo = _rzScore[1];
110
+ let hi = _rzScore[2];
111
+ return select(0.5, (pitch - lo) / max(hi - lo, 1.0), hi > lo);
112
+ }
113
+ `;
114
+ }
@@ -0,0 +1,45 @@
1
+ type ShadowCascade = {
2
+ /** World units across the ortho box, both axes. */
3
+ span: number;
4
+ /** How far behind the target the light's eye sits, along -sunDir. */
5
+ back: number;
6
+ /** Ortho near/far, in world units from that eye. */
7
+ near: number;
8
+ far: number;
9
+ /** Texels per side of this cascade's map — sets the snap quantum. */
10
+ mapSize: number;
11
+ };
12
+ /**
13
+ * The list, inner to outer. INVARIANT the sampler and the cull both lean on:
14
+ * each cascade's box must CONTAIN the previous one (same snapped target, wider
15
+ * span, deeper reach), because
16
+ *
17
+ * - the sampler falls from cascade i to i+1 at the box edge, which is only
18
+ * seamless if i+1 covers where i ends, and
19
+ * - the cull tests ONE frustum — the outermost — and the rasterizer clips
20
+ * each cascade to its own box. That is the same argument that made
21
+ * single-volume shadow culling exact: anything rejected was contributing
22
+ * nothing anywhere. Concentric containment is what keeps it true for a
23
+ * LIST. tests/shadow-cascades.test.mjs pins it.
24
+ */
25
+ export declare const SHADOW_CASCADES: readonly ShadowCascade[];
26
+ /**
27
+ * One cascade's view-projection, following the camera target.
28
+ *
29
+ * The target is snapped to this cascade's OWN texel quantum in the light's
30
+ * right/up plane, so a moving volume doesn't shimmer its shadow edges while
31
+ * running — each cascade snaps to its own grid, coarser maps snapping coarser.
32
+ *
33
+ * Writes the 16 floats into `out` at `offset` and returns `out`.
34
+ */
35
+ export declare function buildShadowVP(target: {
36
+ x: number;
37
+ y: number;
38
+ z: number;
39
+ }, sunDirection: {
40
+ x: number;
41
+ y: number;
42
+ z: number;
43
+ }, cascade: ShadowCascade, out: Float32Array, offset: number): Float32Array;
44
+ export {};
45
+ //# sourceMappingURL=shadow-cascades.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow-cascades.d.ts","sourceRoot":"","sources":["../src/shadow-cascades.ts"],"names":[],"mappings":"AAeA,KAAK,aAAa,GAAG;IACnB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAA;IACZ,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAWnD,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3C,YAAY,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACjD,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,MAAM,GACb,YAAY,CA4Bd"}
@@ -0,0 +1,70 @@
1
+ // The shadow volumes, as data — a list the engine iterates rather than one
2
+ // hardcoded box, so a second cascade is a list entry and not a rewrite.
3
+ //
4
+ // Pure math, its own module for the same reason param-track.ts is: the engine
5
+ // class needs a GPU to construct, and the one thing that ever goes WRONG with a
6
+ // shadow volume is arithmetic — a snap that stops snapping, an eye that lands
7
+ // inside the near plane. Headless tests can hold this half to golden values.
8
+ //
9
+ // The arithmetic is the shipped single-volume code, operation for operation:
10
+ // float addition is not associative, so "the same formula, reordered" is not
11
+ // the same matrix, and cascade 0 must be BIT-IDENTICAL to the volume every
12
+ // published scene was lit by.
13
+ import { Mat4, Vec3 } from "./math";
14
+ /**
15
+ * The list, inner to outer. INVARIANT the sampler and the cull both lean on:
16
+ * each cascade's box must CONTAIN the previous one (same snapped target, wider
17
+ * span, deeper reach), because
18
+ *
19
+ * - the sampler falls from cascade i to i+1 at the box edge, which is only
20
+ * seamless if i+1 covers where i ends, and
21
+ * - the cull tests ONE frustum — the outermost — and the rasterizer clips
22
+ * each cascade to its own box. That is the same argument that made
23
+ * single-volume shadow culling exact: anything rejected was contributing
24
+ * nothing anywhere. Concentric containment is what keeps it true for a
25
+ * LIST. tests/shadow-cascades.test.mjs pins it.
26
+ */
27
+ export const SHADOW_CASCADES = [
28
+ // The shipped volume: 64 units at 4096² ≈ 64 texels/unit — crisp contact
29
+ // shadows on the ground catcher (2048 read visibly blurry).
30
+ { span: 64, back: 72, near: 1, far: 140, mapSize: 4096 },
31
+ // The stage volume: 4× the span on each side, so a set piece 100 units out
32
+ // still throws and receives shade instead of popping lit at the near box's
33
+ // edge. 2048² over 256 units ≈ 8 texels/unit — soft, and read at distances
34
+ // where soft is what a shadow looks like anyway. 16 MB where the near map
35
+ // is 64. Depth reach scales with the span (same eye direction, deeper box),
36
+ // keeping the containment invariant checkable from the specs alone.
37
+ { span: 256, back: 288, near: 1, far: 560, mapSize: 2048 },
38
+ ];
39
+ /**
40
+ * One cascade's view-projection, following the camera target.
41
+ *
42
+ * The target is snapped to this cascade's OWN texel quantum in the light's
43
+ * right/up plane, so a moving volume doesn't shimmer its shadow edges while
44
+ * running — each cascade snaps to its own grid, coarser maps snapping coarser.
45
+ *
46
+ * Writes the 16 floats into `out` at `offset` and returns `out`.
47
+ */
48
+ export function buildShadowVP(target, sunDirection, cascade, out, offset) {
49
+ const dir = new Vec3(sunDirection.x, sunDirection.y, sunDirection.z);
50
+ dir.normalize();
51
+ const up = Math.abs(dir.y) > 0.99 ? new Vec3(0, 0, -1) : new Vec3(0, 1, 0);
52
+ const t = new Vec3(target.x, target.y, target.z);
53
+ const right = Vec3.crossInto(up, dir, new Vec3(0, 0, 0)).normalize();
54
+ const upv = Vec3.crossInto(dir, right, new Vec3(0, 0, 0));
55
+ const texel = cascade.span / cascade.mapSize;
56
+ const tr = Math.round(t.dot(right) / texel) * texel;
57
+ const tu = Math.round(t.dot(upv) / texel) * texel;
58
+ const td = t.dot(dir);
59
+ const snapped = new Vec3(right.x * tr + upv.x * tu + dir.x * td, right.y * tr + upv.y * tu + dir.y * td, right.z * tr + upv.z * tu + dir.z * td);
60
+ const eye = new Vec3(snapped.x - dir.x * cascade.back, snapped.y - dir.y * cascade.back, snapped.z - dir.z * cascade.back);
61
+ const view = Mat4.lookAt(eye, snapped, up);
62
+ const half = cascade.span / 2;
63
+ // The shadow map keeps the NON-reversed convention (orthographicLh maps z to
64
+ // [0,1] front-to-back) — reversing it buys nothing for an ortho box and the
65
+ // +2 pipeline depth bias is signed against this direction.
66
+ const proj = Mat4.orthographicLh(-half, half, -half, half, cascade.near, cascade.far);
67
+ const vp = proj.multiply(view);
68
+ out.set(vp.values, offset);
69
+ return out;
70
+ }
@@ -1,12 +1,12 @@
1
1
  import { Quat, Vec3 } from "./math";
2
- export interface BoneFrame {
2
+ interface BoneFrame {
3
3
  boneName: string;
4
4
  frame: number;
5
5
  rotation: Quat;
6
6
  translation: Vec3;
7
7
  interpolation: Uint8Array;
8
8
  }
9
- export interface MorphFrame {
9
+ interface MorphFrame {
10
10
  morphName: string;
11
11
  frame: number;
12
12
  weight: number;
@@ -70,4 +70,5 @@ export declare class VMDLoader {
70
70
  private getString;
71
71
  private skip;
72
72
  }
73
+ export {};
73
74
  //# sourceMappingURL=vmd-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vmd-loader.d.ts","sourceRoot":"","sources":["../src/vmd-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAEnC,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,IAAI,CAAA;IACd,WAAW,EAAE,IAAI,CAAA;IACjB,aAAa,EAAE,UAAU,CAAA;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAED;;qFAEqF;AACrF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,IAAI,CAAA;IACZ,QAAQ,EAAE,IAAI,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,UAAU,CAAA;CAC1B;AAED,0EAA0E;AAC1E,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CACjD;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAa;IAE5B,OAAO;WAWM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAKtD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,EAAE;IAKzD;mGAC+F;WAClF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAK/D,MAAM,CAAC,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,cAAc,EAAE;IAOlE,OAAO,CAAC,WAAW;IA4BnB,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,EAAE;IAIvD;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;IA8Bf,OAAO,CAAC,KAAK;IAgGb,OAAO,CAAC,aAAa;IAuDrB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,UAAU;IASlB,gFAAgF;IAChF,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,IAAI;CAMb"}
1
+ {"version":3,"file":"vmd-loader.d.ts","sourceRoot":"","sources":["../src/vmd-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAEnC,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,IAAI,CAAA;IACd,WAAW,EAAE,IAAI,CAAA;IACjB,aAAa,EAAE,UAAU,CAAA;CAC1B;AAED,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAED;;qFAEqF;AACrF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,IAAI,CAAA;IACZ,QAAQ,EAAE,IAAI,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,UAAU,CAAA;CAC1B;AAED,0EAA0E;AAC1E,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CACjD;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAa;IAE5B,OAAO;WAWM,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAKtD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,EAAE;IAKzD;mGAC+F;WAClF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAK/D,MAAM,CAAC,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,cAAc,EAAE;IAOlE,OAAO,CAAC,WAAW;IA4BnB,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,EAAE;IAIvD;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;IA8Bf,OAAO,CAAC,KAAK;IAgGb,OAAO,CAAC,aAAa;IAuDrB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,UAAU;IASlB,gFAAgF;IAChF,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,IAAI;CAMb"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.43.0",
3
+ "version": "0.50.0",
4
4
  "description": "A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/camera.ts CHANGED
@@ -4,11 +4,25 @@ import type { CameraPose } from "./camera-animation"
4
4
  /** Far cap / zoom limit; large enough for wide shots without clipping distant ground */
5
5
  const FAR_CAP = 8000
6
6
  const FAR_MIN = 200
7
- /** The character-framing value the cloth z-fighting fix settled on — never go below it. */
8
- const NEAR_MIN = 0.5
9
- /** ~40cm in MMD units. Past this the near plane would start clipping geometry a
10
- * user has deliberately pushed the camera up against. */
11
- const NEAR_MAX = 5
7
+ /**
8
+ * Near-plane floor and cap, in two sets, because what a near plane costs depends
9
+ * entirely on the depth buffer underneath it.
10
+ *
11
+ * The single set that used to live here was written against a projection that
12
+ * silently doubled it (see Mat4.perspectiveInto), so the real clip was at 1.0 and
13
+ * 10 rather than the 0.5 and 5 stated. UNORM keeps exactly that behaviour, now
14
+ * said out loud: identical precision, identical z-fighting margin on coplanar
15
+ * cloth, no change to any scene that renders correctly today.
16
+ *
17
+ * REVERSED gets the floor the content actually wants. Float depth read backwards
18
+ * holds roughly constant RELATIVE precision across the range, so pulling the near
19
+ * plane in by twenty times costs almost nothing — and a 4 mm near plane is closer
20
+ * than any camera VMD will ever push.
21
+ */
22
+ const NEAR_MIN_UNORM = 1.0
23
+ const NEAR_MAX_UNORM = 10
24
+ const NEAR_MIN_REVERSED = 0.05
25
+ const NEAR_MAX_REVERSED = 5
12
26
 
13
27
  export class Camera {
14
28
  alpha: number
@@ -21,8 +35,11 @@ export class Camera {
21
35
  * nearly all of it within a hand's reach and coplanar cloth/body layers z-fought
22
36
  * (white crack flashes, worse the further the camera). 0.5 MMD units ≈ 4 cm —
23
37
  * closer than any real framing — and buys 10× precision at character range. */
24
- near: number = 0.5
38
+ near: number = NEAR_MIN_UNORM
25
39
  far: number = FAR_CAP
40
+ /** Set once by the engine, from whether it got a float depth format. Decides
41
+ * both the near-plane floor and which way round the projection maps z. */
42
+ reversedZ = false
26
43
 
27
44
  // Input state
28
45
  private canvas: HTMLCanvasElement | null = null
@@ -248,13 +265,19 @@ export class Camera {
248
265
  * user is deliberately close to.
249
266
  */
250
267
  private updateNearFromRadius(): void {
251
- this.near = Math.min(NEAR_MAX, Math.max(NEAR_MIN, this.radius / 50))
268
+ const lo = this.reversedZ ? NEAR_MIN_REVERSED : NEAR_MIN_UNORM
269
+ const hi = this.reversedZ ? NEAR_MAX_REVERSED : NEAR_MAX_UNORM
270
+ this.near = Math.min(hi, Math.max(lo, this.radius / 50))
252
271
  }
253
272
 
254
273
  getProjectionMatrix(): Mat4 {
255
274
  this.updateFarFromRadius()
256
275
  this.updateNearFromRadius()
257
- Mat4.perspectiveInto(this._projMat.values, this.fov, this.aspect, this.near, this.far)
276
+ // Reversed-Z is the same matrix with near and far handed over the other way
277
+ // round — near → 1, far → 0. One projection function, and the reversal is a
278
+ // property of the depth buffer rather than a second matrix to keep in step.
279
+ if (this.reversedZ) Mat4.perspectiveInto(this._projMat.values, this.fov, this.aspect, this.far, this.near)
280
+ else Mat4.perspectiveInto(this._projMat.values, this.fov, this.aspect, this.near, this.far)
258
281
  return this._projMat
259
282
  }
260
283