reze-engine 0.54.16 → 0.55.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 (49) hide show
  1. package/dist/engine.d.ts +210 -0
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +1063 -11
  4. package/dist/graph/presets/pool_floor.d.ts +3 -0
  5. package/dist/graph/presets/pool_floor.d.ts.map +1 -0
  6. package/dist/graph/presets/pool_floor.js +42 -0
  7. package/dist/graph/presets/water.d.ts +3 -0
  8. package/dist/graph/presets/water.d.ts.map +1 -0
  9. package/dist/graph/presets/water.js +31 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +8 -0
  13. package/dist/model.d.ts.map +1 -1
  14. package/dist/model.js +10 -0
  15. package/dist/overlay.d.ts +200 -0
  16. package/dist/overlay.d.ts.map +1 -0
  17. package/dist/overlay.js +806 -0
  18. package/dist/physics/autofit.d.ts +147 -0
  19. package/dist/physics/autofit.d.ts.map +1 -0
  20. package/dist/physics/autofit.js +501 -0
  21. package/dist/pmx-document.d.ts +218 -0
  22. package/dist/pmx-document.d.ts.map +1 -0
  23. package/dist/pmx-document.js +685 -0
  24. package/dist/shaders/passes/field-blit.d.ts +26 -0
  25. package/dist/shaders/passes/field-blit.d.ts.map +1 -0
  26. package/dist/shaders/passes/field-blit.js +65 -0
  27. package/dist/shaders/passes/ground-noise.d.ts +7 -0
  28. package/dist/shaders/passes/ground-noise.d.ts.map +1 -0
  29. package/dist/shaders/passes/ground-noise.js +88 -0
  30. package/dist/shaders/passes/overlay.d.ts +3 -0
  31. package/dist/shaders/passes/overlay.d.ts.map +1 -0
  32. package/dist/shaders/passes/overlay.js +164 -0
  33. package/dist/shaders/passes/sim.d.ts +34 -0
  34. package/dist/shaders/passes/sim.d.ts.map +1 -0
  35. package/dist/shaders/passes/sim.js +169 -0
  36. package/dist/shaders/passes/wireframe.d.ts +2 -0
  37. package/dist/shaders/passes/wireframe.d.ts.map +1 -0
  38. package/dist/shaders/passes/wireframe.js +107 -0
  39. package/dist/shaders/score-api.d.ts +10 -0
  40. package/dist/shaders/score-api.d.ts.map +1 -0
  41. package/dist/shaders/score-api.js +114 -0
  42. package/package.json +1 -1
  43. package/src/engine.ts +1147 -13
  44. package/src/index.ts +50 -0
  45. package/src/model.ts +9 -0
  46. package/src/overlay.ts +1000 -0
  47. package/src/pmx-document.ts +882 -0
  48. package/src/shaders/passes/overlay.ts +166 -0
  49. package/src/shaders/passes/wireframe.ts +108 -0
@@ -0,0 +1,166 @@
1
+ // Editor overlays — bones, rigidbodies and joints — as one instanced pass.
2
+ //
3
+ // Two kinds of geometry, because thick lines and thick rings want different
4
+ // answers.
5
+ //
6
+ // STROKED shapes (mode 0, 2) expand each segment into a screen-space quad, so
7
+ // their width is a pixel count and holds at any zoom. That works while a segment
8
+ // is longer on screen than the stroke is wide; past that, neighbouring quads
9
+ // meet at a corner nothing mitres and a circle drawn this way turns into a
10
+ // sunburst. So the one shape that must be both small and thick — the bone
11
+ // marker — is not stroked at all.
12
+ //
13
+ // FILLED shapes (mode 3) are triangles already: the marker's ring is an annulus
14
+ // and its centre is a disc, both facing the camera. No joins exist to get wrong,
15
+ // and the ring can be as heavy as it likes.
16
+ //
17
+ // The layer is 4x multisampled and composited over the finished frame rather
18
+ // than drawn into it: the swapchain is single-sample and an MSAA attachment
19
+ // cannot load from one. So fragments here are PREMULTIPLIED — rgb already
20
+ // carries alpha — and the two blends compose correctly, once against a
21
+ // transparent layer and once onto the frame.
22
+ //
23
+ // The capsule is why `caps` exists. A PMX capsule is a radius and a cylinder
24
+ // length, so scaling a unit capsule non-uniformly would stretch its hemispheres
25
+ // into ellipsoids. Instead the unit shape carries both caps at the origin and
26
+ // each vertex names which cap it belongs to (-1, 0, +1); the VS pushes it along
27
+ // local Y by that sign times `extent`. A segment can straddle the two — the
28
+ // cylinder's side lines run from +1 to -1 — so `caps` holds the sign at BOTH
29
+ // ends, not one.
30
+
31
+ export const OVERLAY_SHADER_WGSL = /* wgsl */ `
32
+ struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _pad: f32 };
33
+ // 16 bytes exactly. Scalars rather than a vec3f tail: a vec3f aligns to 16 and
34
+ // would push the struct past the buffer the pipeline is handed.
35
+ struct OverlayUniforms { viewport: vec2f, dashPeriodPx: f32, _pad0: f32 };
36
+
37
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
38
+ @group(0) @binding(1) var<uniform> overlay: OverlayUniforms;
39
+
40
+ struct VSOut {
41
+ @builtin(position) pos: vec4f,
42
+ @location(0) @interpolate(flat) color: vec4f,
43
+ }
44
+
45
+ fn qrot(q: vec4f, v: vec3f) -> vec3f {
46
+ let t = 2.0 * cross(q.xyz, v);
47
+ return v + q.w * t + cross(q.xyz, t);
48
+ }
49
+
50
+ // Placement mode, carried per VERTEX because it belongs to the shape rather than
51
+ // to the copy.
52
+ //
53
+ // 0 world — rotate, scale, translate. The shapes that ARE geometry:
54
+ // rigidbody volumes, joint crosses, plain lines.
55
+ // 3 facing — a flat filled shape in the camera's plane, radius scale.x.
56
+ // The bone marker's solid centre.
57
+ // 5 solid — placed in world like mode 0, but filled: the rigidbody
58
+ // volumes, which read as volumes rather than as a cage of lines.
59
+ // 4 annulus — the marker's ring: world radius scale.x, and a stroke that is
60
+ // a PIXEL width like every other line, so the two links leave it
61
+ // as a continuation of the same stroke rather than a step. The
62
+ // inner and outer rims are offset radially in screen space,
63
+ // which is what lets a small circle carry a thick stroke — the
64
+ // stroked polyline that preceded it tore into a sunburst as soon
65
+ // as the width passed the segment length.
66
+ // 2 link — the taper from a bone to its child: along the bone, splayed
67
+ // across it in whichever direction the camera can actually see,
68
+ // so the two lines never collapse into one.
69
+ fn placeWorld(p: vec3f, capSign: f32, iRot: vec4f, iPos: vec4f, iScale: vec4f, mode: f32) -> vec3f {
70
+ let right = vec3f(camera.view[0].x, camera.view[1].x, camera.view[2].x);
71
+ let up = vec3f(camera.view[0].y, camera.view[1].y, camera.view[2].y);
72
+
73
+ if (mode > 2.5) {
74
+ return iPos.xyz + (right * p.x + up * p.y) * iScale.x;
75
+ }
76
+ if (mode > 1.5) {
77
+ let boneDir = normalize(qrot(iRot, vec3f(0.0, 1.0, 0.0)));
78
+ let camFwd = vec3f(camera.view[0].z, camera.view[1].z, camera.view[2].z);
79
+ var splay = cross(boneDir, camFwd);
80
+ let sl = length(splay);
81
+ // Looking straight down the bone there is no visible across, and any
82
+ // perpendicular does — the taper is a dot on screen either way.
83
+ splay = select(right, splay / max(sl, 1e-6), sl > 1e-4);
84
+ return iPos.xyz + splay * (p.x * iScale.x) + boneDir * (p.y * iScale.y);
85
+ }
86
+ return qrot(iRot, p * iScale.xyz + vec3f(0.0, capSign * iPos.w, 0.0)) + iPos.xyz;
87
+ }
88
+
89
+ @vertex fn vs(
90
+ @location(0) pos: vec3f,
91
+ @location(1) dir: vec3f,
92
+ @location(2) caps: vec2f,
93
+ @location(3) side: f32,
94
+ @location(4) t: f32,
95
+ @location(5) mode: f32,
96
+ @location(6) iRot: vec4f,
97
+ @location(7) iPos: vec4f,
98
+ @location(8) iScale: vec4f,
99
+ @location(9) iColor: vec4f,
100
+ ) -> VSOut {
101
+ let vp = camera.projection * camera.view;
102
+ let half = 0.5 * overlay.viewport;
103
+
104
+ var out: VSOut;
105
+ out.color = iColor;
106
+
107
+ // A solid volume is placed in world, like a stroked shape, but filled.
108
+ if (mode > 4.5) {
109
+ out.pos = vp * vec4f(placeWorld(pos, caps.x, iRot, iPos, iScale, 0.0), 1.0);
110
+ return out;
111
+ }
112
+
113
+ // The marker's ring: a circle of world radius, stroked to a pixel width.
114
+ if (mode > 3.5) {
115
+ let centre = vp * vec4f(iPos.xyz, 1.0);
116
+ let w = max(abs(centre.w), 1e-6);
117
+ let pxPerWorld = camera.projection[1].y * half.y / w;
118
+ // side is -1 on the inner rim and +1 on the outer.
119
+ let radiusPx = iScale.x * pxPerWorld + side * abs(iScale.w) * 0.5;
120
+ let radial = normalize(pos.xy);
121
+ out.pos = vec4f(centre.xy + ((radial * radiusPx) / half) * centre.w, centre.z, centre.w);
122
+ return out;
123
+ }
124
+
125
+ let c0 = vp * vec4f(placeWorld(pos, caps.x, iRot, iPos, iScale, mode), 1.0);
126
+
127
+ // A filled vertex is already where it belongs.
128
+ if (mode > 2.5) {
129
+ out.pos = c0;
130
+ return out;
131
+ }
132
+
133
+ let c1 = vp * vec4f(placeWorld(pos + dir, caps.y, iRot, iPos, iScale, mode), 1.0);
134
+ let w0 = max(abs(c0.w), 1e-6);
135
+ let w1 = max(abs(c1.w), 1e-6);
136
+ let s0 = (c0.xy / w0) * half;
137
+ let s1 = (c1.xy / w1) * half;
138
+ let delta = s1 - s0;
139
+ let segLenPx = max(length(delta), 1e-6);
140
+ let tangent = delta / segLenPx;
141
+ let normalPx = vec2f(-tangent.y, tangent.x);
142
+ let offsetPx = normalPx * side * abs(iScale.w) * 0.5;
143
+ out.pos = vec4f(c0.xy + (offsetPx / half) * c0.w, c0.z, c0.w);
144
+ return out;
145
+ }
146
+
147
+ @fragment fn fs(in: VSOut) -> @location(0) vec4f {
148
+ return vec4f(in.color.rgb * in.color.a, in.color.a);
149
+ }
150
+ `
151
+
152
+ // Lays the resolved overlay layer over the finished frame. Its input is
153
+ // premultiplied, so the blend is a straight `src + dst * (1 - src.a)`.
154
+ export const OVERLAY_COMPOSITE_SHADER_WGSL = /* wgsl */ `
155
+ @group(0) @binding(0) var overlayTex: texture_2d<f32>;
156
+
157
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
158
+ let x = f32((vi & 1u) << 2u) - 1.0;
159
+ let y = f32((vi & 2u) << 1u) - 1.0;
160
+ return vec4f(x, y, 0.0, 1.0);
161
+ }
162
+
163
+ @fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
164
+ return textureLoad(overlayTex, vec2<i32>(p.xy), 0);
165
+ }
166
+ `
@@ -0,0 +1,108 @@
1
+ // The mesh itself, as lines, in the overlay layer.
2
+ //
3
+ // It skins here rather than reading the loader's CPU positions, and that is most
4
+ // of why the pass exists. What is on screen is skinned and morphed on the GPU;
5
+ // the CPU-side array is BIND POSE. Draw from that and the wireframe sits
6
+ // perfectly on a T-posed model and slides off every animated one — which is
7
+ // exactly the state a user is in while looking at weights.
8
+ //
9
+ // Each edge is expanded into a screen-space QUAD rather than drawn with line
10
+ // topology. WebGPU pins a line to one device pixel, and one device pixel on a 2x
11
+ // display is half a CSS pixel, which 4x MSAA then spreads across two of them:
12
+ // the result is a grey suggestion of a wireframe rather than a wireframe. So the
13
+ // draw is six vertices per edge, instanced, and the mesh is read through STORAGE
14
+ // buffers — there is no per-vertex stream, because a quad's two corners come
15
+ // from two different vertices of the model.
16
+ //
17
+ // The depth prepass shares this module's `vs` through its own entry point: it
18
+ // draws the solid triangles with vertex buffers, writing depth only, so the
19
+ // wireframe can be occluded by the body it belongs to.
20
+
21
+ export const WIREFRAME_SHADER_WGSL = /* wgsl */ `
22
+ struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _pad: f32 };
23
+ struct WireUniforms { color: vec4f, viewport: vec2f, thicknessPx: f32, _pad: f32 };
24
+
25
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
26
+ @group(0) @binding(1) var<uniform> wire: WireUniforms;
27
+
28
+ @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
29
+ // The model's own vertex buffer: pos(3), normal(3), uv(2) — 8 floats a vertex.
30
+ @group(1) @binding(1) var<storage, read> vertices: array<f32>;
31
+ // uint16x4 per vertex, so two u32 each.
32
+ @group(1) @binding(2) var<storage, read> joints: array<u32>;
33
+ // unorm8x4 per vertex, one u32 each.
34
+ @group(1) @binding(3) var<storage, read> weights: array<u32>;
35
+ // Pairs of vertex indices, one pair an edge.
36
+ @group(1) @binding(4) var<storage, read> edges: array<u32>;
37
+
38
+ fn skinnedPos(i: u32) -> vec3f {
39
+ let o = i * 8u;
40
+ let rest = vec4f(vertices[o], vertices[o + 1u], vertices[o + 2u], 1.0);
41
+
42
+ let j0 = joints[i * 2u];
43
+ let j1 = joints[i * 2u + 1u];
44
+ let packed = weights[i];
45
+ var w = vec4f(
46
+ f32(packed & 0xffu),
47
+ f32((packed >> 8u) & 0xffu),
48
+ f32((packed >> 16u) & 0xffu),
49
+ f32((packed >> 24u) & 0xffu),
50
+ ) / 255.0;
51
+ let sum = w.x + w.y + w.z + w.w;
52
+ w = select(vec4f(1.0, 0.0, 0.0, 0.0), w / max(sum, 1e-4), sum > 0.0001);
53
+
54
+ var acc = vec4f(0.0);
55
+ acc += (skinMats[j0 & 0xffffu] * rest) * w.x;
56
+ acc += (skinMats[j0 >> 16u] * rest) * w.y;
57
+ acc += (skinMats[j1 & 0xffffu] * rest) * w.z;
58
+ acc += (skinMats[j1 >> 16u] * rest) * w.w;
59
+ return acc.xyz;
60
+ }
61
+
62
+ // Six vertices an edge: (a,-1) (a,+1) (b,-1) (a,+1) (b,+1) (b,-1).
63
+ @vertex fn vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> @builtin(position) vec4f {
64
+ let a = edges[ii * 2u];
65
+ let b = edges[ii * 2u + 1u];
66
+ let vp = camera.projection * camera.view;
67
+ let ca = vp * vec4f(skinnedPos(a), 1.0);
68
+ let cb = vp * vec4f(skinnedPos(b), 1.0);
69
+
70
+ let atB = vi == 2u || vi == 4u || vi == 5u;
71
+ let side = select(-1.0, 1.0, vi == 1u || vi == 3u || vi == 4u);
72
+ let own = select(ca, cb, atB);
73
+ let other = select(cb, ca, atB);
74
+
75
+ let half = 0.5 * wire.viewport;
76
+ let s0 = (own.xy / max(abs(own.w), 1e-6)) * half;
77
+ let s1 = (other.xy / max(abs(other.w), 1e-6)) * half;
78
+ let d = s1 - s0;
79
+ // A degenerate edge has no direction on screen; any perpendicular will do,
80
+ // and it is a dot either way.
81
+ let tangent = select(vec2f(1.0, 0.0), normalize(d), length(d) > 1e-6);
82
+ let normalPx = vec2f(-tangent.y, tangent.x);
83
+ let offsetPx = normalPx * side * wire.thicknessPx * 0.5;
84
+ return vec4f(own.xy + (offsetPx / half) * own.w, own.z, own.w);
85
+ }
86
+
87
+ // The depth prepass: the solid mesh through the ordinary vertex stream, writing
88
+ // depth and nothing else, so the far side of a body is hidden behind the near.
89
+ @vertex fn vsDepth(
90
+ @location(0) position: vec3f,
91
+ @location(1) joints0: vec4<u32>,
92
+ @location(2) weights0: vec4<f32>,
93
+ ) -> @builtin(position) vec4f {
94
+ let rest = vec4f(position, 1.0);
95
+ let sum = weights0.x + weights0.y + weights0.z + weights0.w;
96
+ let w = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 / max(sum, 1e-4), sum > 0.0001);
97
+ var acc = vec4f(0.0);
98
+ for (var i = 0u; i < 4u; i++) {
99
+ acc += (skinMats[joints0[i]] * rest) * w[i];
100
+ }
101
+ return camera.projection * camera.view * vec4f(acc.xyz, 1.0);
102
+ }
103
+
104
+ @fragment fn fs() -> @location(0) vec4f {
105
+ // Premultiplied, like every other fragment in this layer.
106
+ return vec4f(wire.color.rgb * wire.color.a, wire.color.a);
107
+ }
108
+ `