@driftengine/core 4.8.3 → 4.8.5

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 (180) hide show
  1. package/CHANGELOG.json +82 -0
  2. package/README.md +1 -1
  3. package/dist/build/shaderComments.js +0 -0
  4. package/dist/geometry/meshConcat.js +2 -0
  5. package/dist/index.d.ts +10 -4
  6. package/dist/index.js +16 -1
  7. package/dist/math/scalar.js +1 -1
  8. package/dist/render/anisotropicLobe.d.ts +7 -0
  9. package/dist/render/anisotropicLobe.js +33 -0
  10. package/dist/render/backend/api.d.ts +41 -4
  11. package/dist/render/backend/select.js +8 -0
  12. package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
  13. package/dist/render/backend/webgl2/clothTextures.js +93 -0
  14. package/dist/render/backend/webgl2/renderer.d.ts +136 -3
  15. package/dist/render/backend/webgl2/renderer.js +604 -63
  16. package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
  17. package/dist/render/backend/webgl2/skinScatter.js +256 -0
  18. package/dist/render/backend/webgpu/buffers.d.ts +25 -11
  19. package/dist/render/backend/webgpu/buffers.js +98 -49
  20. package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
  21. package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
  22. package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
  23. package/dist/render/backend/webgpu/clothTextures.js +145 -0
  24. package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
  25. package/dist/render/backend/webgpu/compressedUpload.js +38 -0
  26. package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
  27. package/dist/render/backend/webgpu/computeHost.js +5 -0
  28. package/dist/render/backend/webgpu/depthPass.d.ts +16 -6
  29. package/dist/render/backend/webgpu/depthPass.js +39 -9
  30. package/dist/render/backend/webgpu/drawCommand.js +6 -2
  31. package/dist/render/backend/webgpu/flatPass.d.ts +38 -4
  32. package/dist/render/backend/webgpu/flatPass.js +109 -12
  33. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
  34. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
  35. package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
  36. package/dist/render/backend/webgpu/motionPass.js +106 -9
  37. package/dist/render/backend/webgpu/postPass.d.ts +3 -1
  38. package/dist/render/backend/webgpu/postPass.js +6 -3
  39. package/dist/render/backend/webgpu/renderer.d.ts +113 -5
  40. package/dist/render/backend/webgpu/renderer.js +498 -48
  41. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
  42. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
  43. package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
  44. package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
  45. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  46. package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
  47. package/dist/render/clothBindingData.d.ts +41 -0
  48. package/dist/render/clothBindingData.js +72 -0
  49. package/dist/render/clothPlacement.d.ts +25 -0
  50. package/dist/render/clothPlacement.js +85 -0
  51. package/dist/render/compressedSource.d.ts +66 -0
  52. package/dist/render/compressedSource.js +138 -0
  53. package/dist/render/cutoutCaster.d.ts +2 -0
  54. package/dist/render/cutoutCaster.js +2 -0
  55. package/dist/render/cutoutDither.d.ts +51 -0
  56. package/dist/render/cutoutDither.js +90 -0
  57. package/dist/render/eyeRefraction.d.ts +35 -0
  58. package/dist/render/eyeRefraction.js +40 -0
  59. package/dist/render/glCompressed.d.ts +27 -0
  60. package/dist/render/glCompressed.js +53 -0
  61. package/dist/render/hairLobes.d.ts +70 -0
  62. package/dist/render/hairLobes.js +120 -0
  63. package/dist/render/lightBudget.d.ts +22 -0
  64. package/dist/render/lightBudget.js +22 -0
  65. package/dist/render/mesh.d.ts +7 -0
  66. package/dist/render/mesh.js +24 -0
  67. package/dist/render/recon/motionVectors.d.ts +4 -2
  68. package/dist/render/recon/motionVectors.js +6 -3
  69. package/dist/render/renderQuality.d.ts +20 -0
  70. package/dist/render/renderQuality.js +5 -0
  71. package/dist/render/rendererHarness.js +4 -0
  72. package/dist/render/sceneTarget.d.ts +12 -0
  73. package/dist/render/sceneTarget.js +14 -0
  74. package/dist/render/shaders/clothBinding.d.ts +22 -0
  75. package/dist/render/shaders/clothBinding.js +80 -0
  76. package/dist/render/shaders/depth.js +42 -6
  77. package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
  78. package/dist/render/shaders/flat/anisotropic.js +68 -0
  79. package/dist/render/shaders/flat/eye.d.ts +1 -0
  80. package/dist/render/shaders/flat/eye.js +81 -0
  81. package/dist/render/shaders/flat/hair.d.ts +1 -0
  82. package/dist/render/shaders/flat/hair.js +101 -0
  83. package/dist/render/shaders/flat/index.js +36 -2
  84. package/dist/render/shaders/flat/main.d.ts +1 -1
  85. package/dist/render/shaders/flat/main.js +113 -17
  86. package/dist/render/shaders/flat/models.d.ts +14 -0
  87. package/dist/render/shaders/flat/models.js +273 -0
  88. package/dist/render/shaders/flat/preamble.js +26 -24
  89. package/dist/render/shaders/flat/skin.d.ts +1 -0
  90. package/dist/render/shaders/flat/skin.js +114 -0
  91. package/dist/render/shaders/generated/depth.wgsl.d.ts +38 -16
  92. package/dist/render/shaders/generated/depth.wgsl.js +38 -16
  93. package/dist/render/shaders/generated/flat.wgsl.d.ts +1790 -1704
  94. package/dist/render/shaders/generated/flat.wgsl.js +1828 -1742
  95. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
  96. package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
  97. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  98. package/dist/render/shaders/recon/motion.wgsl.js +102 -0
  99. package/dist/render/shaders/skinBlur.d.ts +1 -0
  100. package/dist/render/shaders/skinBlur.js +103 -0
  101. package/dist/render/shaders/skinning.d.ts +3 -1
  102. package/dist/render/shaders/skinning.js +31 -4
  103. package/dist/render/shadowCasters.d.ts +13 -1
  104. package/dist/render/skinBlur.d.ts +41 -0
  105. package/dist/render/skinBlur.js +49 -0
  106. package/dist/render/skinScatter.d.ts +67 -0
  107. package/dist/render/skinScatter.js +80 -0
  108. package/dist/render/skinnedClothRun.d.ts +26 -0
  109. package/dist/render/skinnedClothRun.js +84 -0
  110. package/dist/render/surfaceModel.d.ts +129 -0
  111. package/dist/render/surfaceModel.js +152 -0
  112. package/dist/render/surfaceTexture.d.ts +44 -4
  113. package/dist/render/surfaceTexture.js +60 -46
  114. package/dist/render/vertexDefaults.js +7 -0
  115. package/package.json +6 -6
  116. package/src/build/shaderComments.ts +0 -0
  117. package/src/geometry/meshConcat.ts +2 -0
  118. package/src/index.ts +40 -1
  119. package/src/math/scalar.ts +1 -1
  120. package/src/render/anisotropicLobe.ts +41 -0
  121. package/src/render/backend/api.ts +52 -3
  122. package/src/render/backend/select.ts +8 -0
  123. package/src/render/backend/webgl2/clothTextures.ts +120 -0
  124. package/src/render/backend/webgl2/renderer.ts +693 -64
  125. package/src/render/backend/webgl2/skinScatter.ts +309 -0
  126. package/src/render/backend/webgpu/buffers.ts +137 -63
  127. package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
  128. package/src/render/backend/webgpu/clothTextures.ts +194 -0
  129. package/src/render/backend/webgpu/compressedUpload.ts +65 -0
  130. package/src/render/backend/webgpu/computeHost.ts +25 -0
  131. package/src/render/backend/webgpu/depthPass.ts +39 -7
  132. package/src/render/backend/webgpu/drawCommand.ts +6 -2
  133. package/src/render/backend/webgpu/flatPass.ts +145 -7
  134. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
  135. package/src/render/backend/webgpu/motionPass.ts +128 -9
  136. package/src/render/backend/webgpu/postPass.ts +5 -1
  137. package/src/render/backend/webgpu/renderer.ts +663 -43
  138. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
  139. package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
  140. package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
  141. package/src/render/clothBindingData.ts +89 -0
  142. package/src/render/clothPlacement.ts +114 -0
  143. package/src/render/compressedSource.ts +198 -0
  144. package/src/render/cutoutCaster.ts +5 -0
  145. package/src/render/cutoutDither.ts +99 -0
  146. package/src/render/eyeRefraction.ts +72 -0
  147. package/src/render/glCompressed.ts +86 -0
  148. package/src/render/hairLobes.ts +188 -0
  149. package/src/render/lightBudget.ts +25 -0
  150. package/src/render/mesh.ts +23 -0
  151. package/src/render/recon/motionVectors.ts +6 -3
  152. package/src/render/renderQuality.ts +28 -0
  153. package/src/render/rendererHarness.ts +4 -0
  154. package/src/render/sceneTarget.ts +21 -0
  155. package/src/render/shaders/clothBinding.ts +81 -0
  156. package/src/render/shaders/depth.ts +42 -6
  157. package/src/render/shaders/flat/anisotropic.ts +68 -0
  158. package/src/render/shaders/flat/eye.ts +82 -0
  159. package/src/render/shaders/flat/hair.ts +102 -0
  160. package/src/render/shaders/flat/index.ts +36 -2
  161. package/src/render/shaders/flat/main.ts +113 -17
  162. package/src/render/shaders/flat/models.ts +286 -0
  163. package/src/render/shaders/flat/preamble.ts +26 -24
  164. package/src/render/shaders/flat/skin.ts +116 -0
  165. package/src/render/shaders/generated/depth.wgsl.ts +38 -16
  166. package/src/render/shaders/generated/flat.wgsl.ts +1828 -1742
  167. package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
  168. package/src/render/shaders/recon/motion.wgsl.ts +102 -0
  169. package/src/render/shaders/skinBlur.ts +105 -0
  170. package/src/render/shaders/skinning.ts +31 -4
  171. package/src/render/shadowCasters.ts +13 -0
  172. package/src/render/skinBlur.ts +53 -0
  173. package/src/render/skinScatter.ts +92 -0
  174. package/src/render/skinnedClothRun.ts +128 -0
  175. package/src/render/surfaceModel.ts +274 -0
  176. package/src/render/surfaceTexture.ts +123 -63
  177. package/src/render/vertexDefaults.ts +7 -0
  178. package/dist/math/exact.d.ts +0 -76
  179. package/dist/math/exact.js +0 -327
  180. package/src/math/exact.ts +0 -340
@@ -0,0 +1,470 @@
1
+ /**
2
+ * The skinned cloth's solver on the device, in WGSL, hand-authored under the compute exception
3
+ * `AGENTS.md` records (2026-08-24): a compute shader has no WebGL2 twin to generate from, and
4
+ * `npm run wgsl` neither writes nor polices this file.
5
+ *
6
+ * **Written to be read beside `@driftengine/physics`' `skinnedClothSolve.ts` and `clothLimits.ts`**:
7
+ * each kernel there is an entry point here, one invocation a particle or a constraint, and the loop
8
+ * over a batch is the dispatch. `clothPose.ts`' `skinParticles` is `pose`. The order of a step —
9
+ * begin, then per substep predict, the batches, the limits, finish; then the blend — is
10
+ * `gpuSkinnedCloth.ts`', taken from the same `ClothControl` the CPU solver is driven by.
11
+ *
12
+ * **Where it differs, it says so.** Single precision throughout, where the CPU rounds to single
13
+ * only on a store. And the bending angle is `atan2(|n1 × n2|, n1 · n2)` rather than `acos(n1 · n2)`
14
+ * with its sine from `sqrt(1 − d²)`: the same function, but a garment spends its life near flat,
15
+ * where `d` is near −1 and `1 − d²` in single precision is rounding — a sine of a few ten-thousandths
16
+ * out of nothing, and a fold pushed by it. The cross product's length is exact there.
17
+ *
18
+ * **How far the two solvers part, measured** (`demo/dev/skinnedCloth.html?parity=1`, RX 9070 XT):
19
+ * a cape hanging in still air agrees to 0.016 mm after 150 frames. With tethers, backstops and a
20
+ * gusting wind it parts by up to 11 mm — and the CPU solver against itself, nudged a tenth of a
21
+ * micrometre a frame, parts by 9 mm at the same frame, and by as much as the device at every
22
+ * checkpoint from the sixtieth on. Contact, buckling and gusts carry a rounding difference that
23
+ * far; the device adds rounding and nothing else. Dropping one multiplier reset parts them by
24
+ * 67 mm, which is the instrument seeing a real difference.
25
+ *
26
+ * **Every particle's state in a few buffers of sections**, because WebGPU grants eight storage
27
+ * buffers to a stage: `state` is the positions, the substep's start, the velocities and the step's
28
+ * start; `pose` the two skinned poses a step interpolates between and the latest skinned normals;
29
+ * `statics` everything per particle that never changes. A section's offset is a multiple of the
30
+ * particle count, which the constants carry — so no offset is a uniform to keep in step.
31
+ */
32
+ export const CLOTH_WORKGROUP = 64;
33
+
34
+ export const CLOTH_SOLVE_WGSL = /* wgsl */ `
35
+ struct Constants {
36
+ count: u32,
37
+ distances: u32,
38
+ bendings: u32,
39
+ joints: u32,
40
+ colliders: u32,
41
+ /* 1 a max distance, 2 a backstop, 4 a frontstop: which limits the set-up has at all. */
42
+ limits: u32,
43
+ width: u32,
44
+ pad0: u32,
45
+ h: f32,
46
+ invH: f32,
47
+ invH2: f32,
48
+ keep: f32,
49
+ dragKeep: f32,
50
+ maxDistanceScale: f32,
51
+ margin: f32,
52
+ pad1: f32,
53
+ gravity: vec4<f32>,
54
+ }
55
+
56
+ /* One step's numbers, at a dynamic offset: every step of a frame has its own slot. */
57
+ struct Step {
58
+ fraction: f32,
59
+ blend: f32,
60
+ alpha: f32,
61
+ carry: u32,
62
+ /* Where the pose a step starts from and the one it moves toward sit in pose, in floats. */
63
+ fromAt: u32,
64
+ toAt: u32,
65
+ pad0: u32,
66
+ pad1: u32,
67
+ /* The character's turn since the last step, by columns; origin.w is the share carried. */
68
+ turn0: vec4<f32>,
69
+ turn1: vec4<f32>,
70
+ turn2: vec4<f32>,
71
+ origin: vec4<f32>,
72
+ shift: vec4<f32>,
73
+ /* The frame's wind: one wind, sampled once a frame, so a step's rather than the set-up's. */
74
+ wind: vec4<f32>,
75
+ }
76
+
77
+ /* One batch of one kind of constraint, at a dynamic offset; first resets the multipliers. */
78
+ struct Batch {
79
+ begin: u32,
80
+ end: u32,
81
+ first: u32,
82
+ pad: u32,
83
+ }
84
+
85
+ @group(0) @binding(0) var<uniform> c: Constants;
86
+ @group(0) @binding(1) var<uniform> pace: Step;
87
+ @group(0) @binding(2) var<uniform> batch: Batch;
88
+ @group(0) @binding(3) var<storage, read_write> state: array<f32>;
89
+ @group(0) @binding(4) var<storage, read_write> pose: array<f32>;
90
+ @group(0) @binding(5) var<storage, read> statics: array<f32>;
91
+ @group(0) @binding(6) var<storage, read> constraints: array<u32>;
92
+ @group(0) @binding(7) var<storage, read_write> lambda: array<f32>;
93
+ @group(0) @binding(8) var<storage, read> frame: array<f32>;
94
+
95
+ fn stateAt(section: u32, i: u32) -> u32 {
96
+ return section * 3u * c.count + i * 3u;
97
+ }
98
+
99
+ fn load(at: u32) -> vec3<f32> {
100
+ return vec3<f32>(state[at], state[at + 1u], state[at + 2u]);
101
+ }
102
+
103
+ fn store(at: u32, v: vec3<f32>) {
104
+ state[at] = v.x;
105
+ state[at + 1u] = v.y;
106
+ state[at + 2u] = v.z;
107
+ }
108
+
109
+ fn poseOf(at: u32) -> vec3<f32> {
110
+ return vec3<f32>(pose[at], pose[at + 1u], pose[at + 2u]);
111
+ }
112
+
113
+ /* Sections of state. */
114
+ const POSITION = 0u;
115
+ const PREVIOUS = 1u;
116
+ const VELOCITY = 2u;
117
+ const START = 3u;
118
+
119
+ fn inverseMass(i: u32) -> f32 {
120
+ return statics[i];
121
+ }
122
+
123
+ /* Where skinning puts particle i, fraction of the way from the last pose to the latest. */
124
+ fn skinnedAt(i: u32) -> vec3<f32> {
125
+ let a = poseOf(pace.fromAt + i * 3u);
126
+ let b = poseOf(pace.toAt + i * 3u);
127
+ return a + (b - a) * pace.fraction;
128
+ }
129
+
130
+ fn skinMatrix(j: u32) -> mat4x4<f32> {
131
+ let at = j * 16u;
132
+ return mat4x4<f32>(
133
+ vec4<f32>(frame[at], frame[at + 1u], frame[at + 2u], frame[at + 3u]),
134
+ vec4<f32>(frame[at + 4u], frame[at + 5u], frame[at + 6u], frame[at + 7u]),
135
+ vec4<f32>(frame[at + 8u], frame[at + 9u], frame[at + 10u], frame[at + 11u]),
136
+ vec4<f32>(frame[at + 12u], frame[at + 13u], frame[at + 14u], frame[at + 15u]));
137
+ }
138
+
139
+ /* skinParticles: each particle's skinned position and normal by up to eight influences. */
140
+ @compute @workgroup_size(64)
141
+ fn clothPose(@builtin(global_invocation_id) id: vec3<u32>) {
142
+ let i = id.x;
143
+ if (i >= c.count) {
144
+ return;
145
+ }
146
+ let n = c.count;
147
+ let restAt = 7u * n + i * 3u;
148
+ let normalAt = 10u * n + i * 3u;
149
+ let x = vec4<f32>(statics[restAt], statics[restAt + 1u], statics[restAt + 2u], 1.0);
150
+ let q0 = vec3<f32>(statics[normalAt], statics[normalAt + 1u], statics[normalAt + 2u]);
151
+ var p = vec3<f32>(0.0);
152
+ var q = vec3<f32>(0.0);
153
+ for (var half = 0u; half < 2u; half++) {
154
+ for (var k = 0u; k < 4u; k++) {
155
+ let at = 13u * n + i * 16u + half * 8u;
156
+ let w = statics[at + 4u + k];
157
+ if (w == 0.0) {
158
+ continue;
159
+ }
160
+ let m = skinMatrix(u32(statics[at + k]));
161
+ p += w * (m * x).xyz;
162
+ q += w * (mat3x3<f32>(m[0].xyz, m[1].xyz, m[2].xyz) * q0);
163
+ }
164
+ }
165
+ let to = pace.toAt + i * 3u;
166
+ pose[to] = p.x;
167
+ pose[to + 1u] = p.y;
168
+ pose[to + 2u] = p.z;
169
+ let len = sqrt(dot(q, q));
170
+ let inv = select(0.0, 1.0 / len, len > 1e-12);
171
+ let normal = 6u * n + i * 3u;
172
+ pose[normal] = q.x * inv;
173
+ pose[normal + 1u] = q.y * inv;
174
+ pose[normal + 2u] = q.z * inv;
175
+ }
176
+
177
+ /* SkinnedCloth.rest: every particle at the latest skinned position, at rest. */
178
+ @compute @workgroup_size(64)
179
+ fn clothRest(@builtin(global_invocation_id) id: vec3<u32>) {
180
+ let i = id.x;
181
+ if (i >= c.count) {
182
+ return;
183
+ }
184
+ let p = poseOf(pace.toAt + i * 3u);
185
+ store(stateAt(POSITION, i), p);
186
+ store(stateAt(START, i), p);
187
+ store(stateAt(VELOCITY, i), vec3<f32>(0.0));
188
+ }
189
+
190
+ /* The step's start: the step start kept, a free particle carried, a kinematic one placed. */
191
+ @compute @workgroup_size(64)
192
+ fn clothBegin(@builtin(global_invocation_id) id: vec3<u32>) {
193
+ let i = id.x;
194
+ if (i >= c.count) {
195
+ return;
196
+ }
197
+ let at = stateAt(POSITION, i);
198
+ let p = load(at);
199
+ store(stateAt(START, i), p);
200
+ if (inverseMass(i) == 0.0) {
201
+ store(at, skinnedAt(i));
202
+ return;
203
+ }
204
+ if (pace.carry == 0u) {
205
+ return;
206
+ }
207
+ /* carryParticles: the share of the character's turn about its old origin, and of its move. */
208
+ let turn = mat3x3<f32>(pace.turn0.xyz, pace.turn1.xyz, pace.turn2.xyz);
209
+ let share = pace.origin.w;
210
+ let x = p - pace.origin.xyz;
211
+ store(at, p + (turn * x - x) * share + pace.shift.xyz);
212
+ let vAt = stateAt(VELOCITY, i);
213
+ let v = load(vAt);
214
+ store(vAt, v + (turn * v - v) * share);
215
+ }
216
+
217
+ /* predictParticles. */
218
+ @compute @workgroup_size(64)
219
+ fn clothPredict(@builtin(global_invocation_id) id: vec3<u32>) {
220
+ let i = id.x;
221
+ if (i >= c.count) {
222
+ return;
223
+ }
224
+ let at = stateAt(POSITION, i);
225
+ let p = load(at);
226
+ store(stateAt(PREVIOUS, i), p);
227
+ if (inverseMass(i) == 0.0) {
228
+ return;
229
+ }
230
+ let vAt = stateAt(VELOCITY, i);
231
+ let air = pace.wind.xyz;
232
+ let v = air + (load(vAt) * c.keep - air) * c.dragKeep + c.gravity.xyz * c.h;
233
+ store(vAt, v);
234
+ store(at, p + v * c.h);
235
+ }
236
+
237
+ /* solveDistanceBatch: one invocation a constraint of the batch, constraints sorted by batch. */
238
+ @compute @workgroup_size(64)
239
+ fn clothDistance(@builtin(global_invocation_id) id: vec3<u32>) {
240
+ let k = batch.begin + id.x;
241
+ if (k >= batch.end) {
242
+ return;
243
+ }
244
+ /* The first pass of a substep starts the multiplier at nothing, as the CPU's fill(0) does —
245
+ written before any early return, so a skipped constraint does not carry the last one. */
246
+ if (batch.first == 1u) {
247
+ lambda[k] = 0.0;
248
+ }
249
+ let a = constraints[k * 4u];
250
+ let b = constraints[k * 4u + 1u];
251
+ let wa = inverseMass(a);
252
+ let wb = inverseMass(b);
253
+ let w = wa + wb;
254
+ if (w == 0.0) {
255
+ return;
256
+ }
257
+ let aAt = stateAt(POSITION, a);
258
+ let bAt = stateAt(POSITION, b);
259
+ let d = load(bAt) - load(aAt);
260
+ let len = sqrt(dot(d, d));
261
+ if (len < 1e-9) {
262
+ return;
263
+ }
264
+ let alpha = bitcast<f32>(constraints[k * 4u + 3u]) * c.invH2;
265
+ let old = lambda[k];
266
+ let delta = (-(len - bitcast<f32>(constraints[k * 4u + 2u])) - alpha * old) / (w + alpha);
267
+ lambda[k] = old + delta;
268
+ let s = delta / len;
269
+ /* A pinned particle is shared across a batch, so it is never written, even with itself. */
270
+ if (wa > 0.0) {
271
+ store(aAt, load(aAt) - d * s * wa);
272
+ }
273
+ if (wb > 0.0) {
274
+ store(bAt, load(bAt) + d * s * wb);
275
+ }
276
+ }
277
+
278
+ /* solveBendingBatch, with the angle from atan2 (see the header). */
279
+ @compute @workgroup_size(64)
280
+ fn clothBending(@builtin(global_invocation_id) id: vec3<u32>) {
281
+ let k = batch.begin + id.x;
282
+ if (k >= batch.end) {
283
+ return;
284
+ }
285
+ let at = c.distances * 4u + k * 6u;
286
+ let slot = c.distances + k;
287
+ if (batch.first == 1u) {
288
+ lambda[slot] = 0.0;
289
+ }
290
+ let ids = vec4<u32>(constraints[at], constraints[at + 1u], constraints[at + 2u], constraints[at + 3u]);
291
+ let w = vec4<f32>(inverseMass(ids.x), inverseMass(ids.y), inverseMass(ids.z), inverseMass(ids.w));
292
+ if (w.x + w.y + w.z + w.w == 0.0) {
293
+ return;
294
+ }
295
+ let origin = load(stateAt(POSITION, ids.x));
296
+ let e = load(stateAt(POSITION, ids.y)) - origin;
297
+ let p3 = load(stateAt(POSITION, ids.z)) - origin;
298
+ let p4 = load(stateAt(POSITION, ids.w)) - origin;
299
+ var n1 = cross(e, p3);
300
+ var n2 = cross(e, p4);
301
+ let l1 = sqrt(dot(n1, n1));
302
+ let l2 = sqrt(dot(n2, n2));
303
+ if (l1 < 1e-12 || l2 < 1e-12) {
304
+ return;
305
+ }
306
+ n1 = n1 / l1;
307
+ n2 = n2 / l2;
308
+ let d = clamp(dot(n1, n2), -1.0, 1.0);
309
+ let sine = sqrt(dot(cross(n1, n2), cross(n1, n2)));
310
+ if (sine < 1e-6) {
311
+ return;
312
+ }
313
+ /* The q vectors: the negative of the derivative of n1 · n2. See solveBendingBatch. */
314
+ let q3 = (cross(e, n2) + cross(n1, e) * d) / l1;
315
+ let q4 = (cross(e, n1) + cross(n2, e) * d) / l2;
316
+ let q2 = -((cross(p3, n2) + cross(n1, p3) * d) / l1 + (cross(p4, n1) + cross(n2, p4) * d) / l2);
317
+ let q1 = -(q2 + q3 + q4);
318
+ var sum = w.x * dot(q1, q1) + w.y * dot(q2, q2) + w.z * dot(q3, q3) + w.w * dot(q4, q4);
319
+ sum = sum / (sine * sine);
320
+ if (sum < 1e-12) {
321
+ return;
322
+ }
323
+ let alpha = bitcast<f32>(constraints[at + 5u]) * c.invH2;
324
+ let old = lambda[slot];
325
+ let angle = atan2(sine, d);
326
+ let delta = (-(angle - bitcast<f32>(constraints[at + 4u])) - alpha * old) / (sum + alpha);
327
+ lambda[slot] = old + delta;
328
+ let scale = delta / sine;
329
+ if (w.x > 0.0) {
330
+ let p = stateAt(POSITION, ids.x);
331
+ store(p, load(p) + w.x * scale * q1);
332
+ }
333
+ if (w.y > 0.0) {
334
+ let p = stateAt(POSITION, ids.y);
335
+ store(p, load(p) + w.y * scale * q2);
336
+ }
337
+ if (w.z > 0.0) {
338
+ let p = stateAt(POSITION, ids.z);
339
+ store(p, load(p) + w.z * scale * q3);
340
+ }
341
+ if (w.w > 0.0) {
342
+ let p = stateAt(POSITION, ids.w);
343
+ store(p, load(p) + w.w * scale * q4);
344
+ }
345
+ }
346
+
347
+ /* solveTethers, one invocation a tether of the batch: a batch holds a particle once. */
348
+ @compute @workgroup_size(64)
349
+ fn clothTether(@builtin(global_invocation_id) id: vec3<u32>) {
350
+ let k = batch.begin + id.x;
351
+ if (k >= batch.end) {
352
+ return;
353
+ }
354
+ let at = c.distances * 4u + c.bendings * 6u + k * 3u;
355
+ let pAt = stateAt(POSITION, constraints[at]);
356
+ let anchor = load(stateAt(POSITION, constraints[at + 1u]));
357
+ let d = load(pAt) - anchor;
358
+ let dist = sqrt(dot(d, d));
359
+ let reach = bitcast<f32>(constraints[at + 2u]);
360
+ if (dist <= reach || dist < 1e-9) {
361
+ return;
362
+ }
363
+ store(pAt, anchor + d * (reach / dist));
364
+ }
365
+
366
+ /* pushOut: the point onto the sphere's surface if it is inside. A point at the centre stays. */
367
+ fn pushOut(p: vec3<f32>, centre: vec3<f32>, radius: f32) -> vec3<f32> {
368
+ let d = p - centre;
369
+ let d2 = dot(d, d);
370
+ if (d2 >= radius * radius || d2 < 1e-18) {
371
+ return p;
372
+ }
373
+ return centre + d * (radius / sqrt(d2));
374
+ }
375
+
376
+ /* applyMaxDistance, applyStops (back, then front) and applyColliders, in that order. */
377
+ @compute @workgroup_size(64)
378
+ fn clothLimit(@builtin(global_invocation_id) id: vec3<u32>) {
379
+ let i = id.x;
380
+ if (i >= c.count || inverseMass(i) == 0.0) {
381
+ return;
382
+ }
383
+ let n = c.count;
384
+ let at = stateAt(POSITION, i);
385
+ var p = load(at);
386
+ let t = skinnedAt(i);
387
+ /* A max distance below zero is none: the CPU's Infinity, which a device may not be handed. */
388
+ let maxDistance = statics[n + i];
389
+ if ((c.limits & 1u) != 0u && maxDistance >= 0.0) {
390
+ let limit = maxDistance * c.maxDistanceScale;
391
+ let d = p - t;
392
+ let d2 = dot(d, d);
393
+ if (d2 > limit * limit) {
394
+ p = t + d * (limit / sqrt(d2));
395
+ }
396
+ }
397
+ let normal = poseOf(6u * n + i * 3u);
398
+ if ((c.limits & 2u) != 0u) {
399
+ let radius = statics[2u * n + i * 2u + 1u];
400
+ if (radius > 0.0) {
401
+ p = pushOut(p, t - normal * (statics[2u * n + i * 2u] + radius), radius);
402
+ }
403
+ }
404
+ if ((c.limits & 4u) != 0u) {
405
+ let radius = statics[4u * n + i * 2u + 1u];
406
+ if (radius > 0.0) {
407
+ p = pushOut(p, t + normal * (statics[4u * n + i * 2u] + radius), radius);
408
+ }
409
+ }
410
+ let pad = statics[6u * n + i] + c.margin;
411
+ let ends = c.joints * 16u;
412
+ let radii = ends + c.colliders * 6u;
413
+ for (var k = 0u; k < c.colliders; k++) {
414
+ let a = vec3<f32>(frame[ends + k * 6u], frame[ends + k * 6u + 1u], frame[ends + k * 6u + 2u]);
415
+ let e = vec3<f32>(frame[ends + k * 6u + 3u], frame[ends + k * 6u + 4u], frame[ends + k * 6u + 5u]) - a;
416
+ let e2 = dot(e, e);
417
+ var s = 0.0;
418
+ if (e2 > 1e-18) {
419
+ s = clamp(dot(p - a, e) / e2, 0.0, 1.0);
420
+ }
421
+ let r0 = frame[radii + k * 2u];
422
+ let r1 = frame[radii + k * 2u + 1u];
423
+ p = pushOut(p, a + e * s, r0 + (r1 - r0) * s + pad);
424
+ }
425
+ store(at, p);
426
+ }
427
+
428
+ /* finishParticles. */
429
+ @compute @workgroup_size(64)
430
+ fn clothFinish(@builtin(global_invocation_id) id: vec3<u32>) {
431
+ let i = id.x;
432
+ if (i >= c.count) {
433
+ return;
434
+ }
435
+ let vAt = stateAt(VELOCITY, i);
436
+ if (inverseMass(i) == 0.0) {
437
+ store(vAt, vec3<f32>(0.0));
438
+ return;
439
+ }
440
+ store(vAt, (load(stateAt(POSITION, i)) - load(stateAt(PREVIOUS, i))) * c.invH);
441
+ }
442
+
443
+ /* SkinnedCloth.blend: after a reset, the simulation's share grows over the blend's steps. */
444
+ @compute @workgroup_size(64)
445
+ fn clothBlend(@builtin(global_invocation_id) id: vec3<u32>) {
446
+ let i = id.x;
447
+ if (i >= c.count) {
448
+ return;
449
+ }
450
+ let t = skinnedAt(i);
451
+ let at = stateAt(POSITION, i);
452
+ store(at, t + (load(at) - t) * pace.blend);
453
+ let vAt = stateAt(VELOCITY, i);
454
+ store(vAt, load(vAt) * pace.blend);
455
+ }
456
+
457
+ @group(1) @binding(0) var output: texture_storage_2d<rgba32float, write>;
458
+
459
+ /* SkinnedCloth.interpolate, into the particle texture the vertex stage reads. */
460
+ @compute @workgroup_size(64)
461
+ fn clothPublish(@builtin(global_invocation_id) id: vec3<u32>) {
462
+ let i = id.x;
463
+ if (i >= c.count) {
464
+ return;
465
+ }
466
+ let a = load(stateAt(START, i));
467
+ let p = a + (load(stateAt(POSITION, i)) - a) * pace.alpha;
468
+ textureStore(output, vec2<u32>(i % c.width, i / c.width), vec4<f32>(p, 0.0));
469
+ }
470
+ `;