@driftengine/core 4.8.2 → 4.8.4

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 (259) hide show
  1. package/CHANGELOG.json +127 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/meshConcat.js +2 -0
  4. package/dist/index.d.ts +11 -4
  5. package/dist/index.js +22 -1
  6. package/dist/math/scalar.js +1 -1
  7. package/dist/render/anisotropicLobe.d.ts +7 -0
  8. package/dist/render/anisotropicLobe.js +33 -0
  9. package/dist/render/backend/api.d.ts +41 -4
  10. package/dist/render/backend/materialChanges.d.ts +7 -2
  11. package/dist/render/backend/materialChanges.js +8 -3
  12. package/dist/render/backend/select.js +8 -0
  13. package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
  14. package/dist/render/backend/webgl2/clothTextures.js +93 -0
  15. package/dist/render/backend/webgl2/renderer.d.ts +159 -3
  16. package/dist/render/backend/webgl2/renderer.js +662 -80
  17. package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
  18. package/dist/render/backend/webgl2/skinScatter.js +256 -0
  19. package/dist/render/backend/webgpu/buffers.d.ts +25 -11
  20. package/dist/render/backend/webgpu/buffers.js +98 -49
  21. package/dist/render/backend/webgpu/causticsPass.js +2 -1
  22. package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
  23. package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
  24. package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
  25. package/dist/render/backend/webgpu/clothTextures.js +145 -0
  26. package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
  27. package/dist/render/backend/webgpu/compressedUpload.js +38 -0
  28. package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
  29. package/dist/render/backend/webgpu/computeHost.js +5 -0
  30. package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
  31. package/dist/render/backend/webgpu/depthPass.js +43 -12
  32. package/dist/render/backend/webgpu/drawCommand.js +6 -2
  33. package/dist/render/backend/webgpu/filmPass.js +2 -1
  34. package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
  35. package/dist/render/backend/webgpu/flatPass.js +111 -13
  36. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
  37. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
  38. package/dist/render/backend/webgpu/linePass.js +2 -1
  39. package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
  40. package/dist/render/backend/webgpu/motionPass.js +106 -9
  41. package/dist/render/backend/webgpu/panelPass.js +2 -1
  42. package/dist/render/backend/webgpu/particlePass.js +2 -1
  43. package/dist/render/backend/webgpu/plumePass.js +2 -1
  44. package/dist/render/backend/webgpu/postPass.d.ts +15 -3
  45. package/dist/render/backend/webgpu/postPass.js +25 -8
  46. package/dist/render/backend/webgpu/renderer.d.ts +123 -6
  47. package/dist/render/backend/webgpu/renderer.js +534 -58
  48. package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
  49. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
  50. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
  51. package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
  52. package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
  53. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  54. package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
  55. package/dist/render/backend/webgpu/textPass.js +2 -1
  56. package/dist/render/backend/webgpu/waterPass.js +2 -1
  57. package/dist/render/backend/webgpu/windStreakPass.js +2 -1
  58. package/dist/render/boltBatch.js +2 -1
  59. package/dist/render/causticsRenderer.js +2 -1
  60. package/dist/render/clothBindingData.d.ts +41 -0
  61. package/dist/render/clothBindingData.js +72 -0
  62. package/dist/render/clothPlacement.d.ts +25 -0
  63. package/dist/render/clothPlacement.js +85 -0
  64. package/dist/render/compressedSource.d.ts +66 -0
  65. package/dist/render/compressedSource.js +138 -0
  66. package/dist/render/cutoutCaster.d.ts +5 -1
  67. package/dist/render/cutoutCaster.js +6 -0
  68. package/dist/render/cutoutDither.d.ts +51 -0
  69. package/dist/render/cutoutDither.js +90 -0
  70. package/dist/render/decalPass.js +2 -1
  71. package/dist/render/eyeRefraction.d.ts +35 -0
  72. package/dist/render/eyeRefraction.js +40 -0
  73. package/dist/render/glCompressed.d.ts +27 -0
  74. package/dist/render/glCompressed.js +53 -0
  75. package/dist/render/globalMediumPass.js +3 -2
  76. package/dist/render/hairLobes.d.ts +70 -0
  77. package/dist/render/hairLobes.js +120 -0
  78. package/dist/render/lightBudget.d.ts +22 -0
  79. package/dist/render/lightBudget.js +22 -0
  80. package/dist/render/lineBatch.js +5 -1
  81. package/dist/render/mesh.d.ts +7 -0
  82. package/dist/render/mesh.js +24 -0
  83. package/dist/render/oitPass.js +2 -1
  84. package/dist/render/particleBatch.js +3 -2
  85. package/dist/render/particlePool.d.ts +6 -2
  86. package/dist/render/plumeRenderer.js +3 -2
  87. package/dist/render/recon/motionVectors.d.ts +4 -2
  88. package/dist/render/recon/motionVectors.js +6 -3
  89. package/dist/render/renderQuality.d.ts +29 -2
  90. package/dist/render/renderQuality.js +5 -0
  91. package/dist/render/rendererHarness.js +4 -0
  92. package/dist/render/sceneCoverage.d.ts +54 -0
  93. package/dist/render/sceneCoverage.js +72 -0
  94. package/dist/render/sceneTarget.d.ts +12 -0
  95. package/dist/render/sceneTarget.js +14 -0
  96. package/dist/render/sdfTextRenderer.js +2 -1
  97. package/dist/render/shaders/clothBinding.d.ts +22 -0
  98. package/dist/render/shaders/clothBinding.js +80 -0
  99. package/dist/render/shaders/depth.js +45 -8
  100. package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
  101. package/dist/render/shaders/flat/anisotropic.js +68 -0
  102. package/dist/render/shaders/flat/eye.d.ts +1 -0
  103. package/dist/render/shaders/flat/eye.js +81 -0
  104. package/dist/render/shaders/flat/hair.d.ts +1 -0
  105. package/dist/render/shaders/flat/hair.js +101 -0
  106. package/dist/render/shaders/flat/index.js +42 -3
  107. package/dist/render/shaders/flat/main.d.ts +1 -1
  108. package/dist/render/shaders/flat/main.js +113 -17
  109. package/dist/render/shaders/flat/models.d.ts +14 -0
  110. package/dist/render/shaders/flat/models.js +273 -0
  111. package/dist/render/shaders/flat/preamble.js +26 -24
  112. package/dist/render/shaders/flat/skin.d.ts +1 -0
  113. package/dist/render/shaders/flat/skin.js +114 -0
  114. package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
  115. package/dist/render/shaders/generated/depth.wgsl.js +66 -24
  116. package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
  117. package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
  118. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
  121. package/dist/render/shaders/generated/particle.wgsl.js +3 -3
  122. package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
  123. package/dist/render/shaders/generated/rush.wgsl.js +1 -1
  124. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
  126. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
  127. package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
  128. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  129. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  130. package/dist/render/shaders/line.d.ts +1 -1
  131. package/dist/render/shaders/outputTransform.d.ts +14 -1
  132. package/dist/render/shaders/outputTransform.js +23 -1
  133. package/dist/render/shaders/particle.d.ts +3 -3
  134. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  135. package/dist/render/shaders/recon/motion.wgsl.js +102 -0
  136. package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
  137. package/dist/render/shaders/rush.js +13 -2
  138. package/dist/render/shaders/sdfText.d.ts +1 -1
  139. package/dist/render/shaders/skinBlur.d.ts +1 -0
  140. package/dist/render/shaders/skinBlur.js +103 -0
  141. package/dist/render/shaders/skinning.d.ts +3 -1
  142. package/dist/render/shaders/skinning.js +31 -4
  143. package/dist/render/shaders/temporalResolve.js +12 -5
  144. package/dist/render/shadowCasters.d.ts +13 -1
  145. package/dist/render/skinBlur.d.ts +41 -0
  146. package/dist/render/skinBlur.js +49 -0
  147. package/dist/render/skinScatter.d.ts +67 -0
  148. package/dist/render/skinScatter.js +80 -0
  149. package/dist/render/skinnedClothRun.d.ts +26 -0
  150. package/dist/render/skinnedClothRun.js +84 -0
  151. package/dist/render/ssrPass.js +2 -1
  152. package/dist/render/surfaceModel.d.ts +129 -0
  153. package/dist/render/surfaceModel.js +152 -0
  154. package/dist/render/surfaceTexture.d.ts +55 -4
  155. package/dist/render/surfaceTexture.js +60 -46
  156. package/dist/render/textRenderer.js +2 -1
  157. package/dist/render/vertexDefaults.js +8 -0
  158. package/dist/render/waterRenderer.js +2 -1
  159. package/dist/render/windStreakRenderer.js +2 -1
  160. package/package.json +6 -6
  161. package/src/geometry/meshConcat.ts +2 -0
  162. package/src/index.ts +53 -1
  163. package/src/math/scalar.ts +1 -1
  164. package/src/render/anisotropicLobe.ts +41 -0
  165. package/src/render/backend/api.ts +52 -3
  166. package/src/render/backend/materialChanges.ts +8 -3
  167. package/src/render/backend/select.ts +8 -0
  168. package/src/render/backend/webgl2/clothTextures.ts +120 -0
  169. package/src/render/backend/webgl2/renderer.ts +750 -80
  170. package/src/render/backend/webgl2/skinScatter.ts +309 -0
  171. package/src/render/backend/webgpu/buffers.ts +137 -63
  172. package/src/render/backend/webgpu/causticsPass.ts +2 -1
  173. package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
  174. package/src/render/backend/webgpu/clothTextures.ts +194 -0
  175. package/src/render/backend/webgpu/compressedUpload.ts +65 -0
  176. package/src/render/backend/webgpu/computeHost.ts +25 -0
  177. package/src/render/backend/webgpu/depthPass.ts +43 -10
  178. package/src/render/backend/webgpu/drawCommand.ts +6 -2
  179. package/src/render/backend/webgpu/filmPass.ts +2 -1
  180. package/src/render/backend/webgpu/flatPass.ts +147 -8
  181. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
  182. package/src/render/backend/webgpu/linePass.ts +2 -1
  183. package/src/render/backend/webgpu/motionPass.ts +128 -9
  184. package/src/render/backend/webgpu/panelPass.ts +2 -1
  185. package/src/render/backend/webgpu/particlePass.ts +2 -1
  186. package/src/render/backend/webgpu/plumePass.ts +2 -1
  187. package/src/render/backend/webgpu/postPass.ts +25 -6
  188. package/src/render/backend/webgpu/renderer.ts +701 -52
  189. package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
  190. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
  191. package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
  192. package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
  193. package/src/render/backend/webgpu/textPass.ts +2 -1
  194. package/src/render/backend/webgpu/waterPass.ts +2 -1
  195. package/src/render/backend/webgpu/windStreakPass.ts +2 -1
  196. package/src/render/boltBatch.ts +2 -1
  197. package/src/render/causticsRenderer.ts +2 -1
  198. package/src/render/clothBindingData.ts +89 -0
  199. package/src/render/clothPlacement.ts +114 -0
  200. package/src/render/compressedSource.ts +198 -0
  201. package/src/render/cutoutCaster.ts +20 -2
  202. package/src/render/cutoutDither.ts +99 -0
  203. package/src/render/decalPass.ts +2 -1
  204. package/src/render/eyeRefraction.ts +72 -0
  205. package/src/render/glCompressed.ts +86 -0
  206. package/src/render/globalMediumPass.ts +3 -2
  207. package/src/render/hairLobes.ts +188 -0
  208. package/src/render/lightBudget.ts +25 -0
  209. package/src/render/lineBatch.ts +3 -1
  210. package/src/render/mesh.ts +23 -0
  211. package/src/render/oitPass.ts +2 -1
  212. package/src/render/particleBatch.ts +3 -2
  213. package/src/render/particlePool.ts +6 -2
  214. package/src/render/plumeRenderer.ts +3 -2
  215. package/src/render/recon/motionVectors.ts +6 -3
  216. package/src/render/renderQuality.ts +37 -2
  217. package/src/render/rendererHarness.ts +4 -0
  218. package/src/render/sceneCoverage.ts +78 -0
  219. package/src/render/sceneTarget.ts +21 -0
  220. package/src/render/sdfTextRenderer.ts +2 -1
  221. package/src/render/shaders/clothBinding.ts +81 -0
  222. package/src/render/shaders/depth.ts +45 -8
  223. package/src/render/shaders/flat/anisotropic.ts +68 -0
  224. package/src/render/shaders/flat/eye.ts +82 -0
  225. package/src/render/shaders/flat/hair.ts +102 -0
  226. package/src/render/shaders/flat/index.ts +42 -3
  227. package/src/render/shaders/flat/main.ts +113 -17
  228. package/src/render/shaders/flat/models.ts +286 -0
  229. package/src/render/shaders/flat/preamble.ts +26 -24
  230. package/src/render/shaders/flat/skin.ts +116 -0
  231. package/src/render/shaders/generated/depth.wgsl.ts +66 -24
  232. package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
  233. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  234. package/src/render/shaders/generated/particle.wgsl.ts +3 -3
  235. package/src/render/shaders/generated/rush.wgsl.ts +1 -1
  236. package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
  237. package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
  238. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  239. package/src/render/shaders/outputTransform.ts +24 -1
  240. package/src/render/shaders/recon/motion.wgsl.ts +102 -0
  241. package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
  242. package/src/render/shaders/rush.ts +13 -2
  243. package/src/render/shaders/skinBlur.ts +105 -0
  244. package/src/render/shaders/skinning.ts +31 -4
  245. package/src/render/shaders/temporalResolve.ts +12 -5
  246. package/src/render/shadowCasters.ts +13 -0
  247. package/src/render/skinBlur.ts +53 -0
  248. package/src/render/skinScatter.ts +92 -0
  249. package/src/render/skinnedClothRun.ts +128 -0
  250. package/src/render/ssrPass.ts +2 -1
  251. package/src/render/surfaceModel.ts +274 -0
  252. package/src/render/surfaceTexture.ts +134 -63
  253. package/src/render/textRenderer.ts +2 -1
  254. package/src/render/vertexDefaults.ts +8 -0
  255. package/src/render/waterRenderer.ts +2 -1
  256. package/src/render/windStreakRenderer.ts +2 -1
  257. package/dist/math/exact.d.ts +0 -76
  258. package/dist/math/exact.js +0 -327
  259. package/src/math/exact.ts +0 -340
@@ -0,0 +1,452 @@
1
+ /**
2
+ * A skinned cloth solved on the device: `ClothControl`'s schedule, over buffers rather than arrays.
3
+ *
4
+ * **The CPU solver's steps, as dispatches.** A step is `clothBegin`, then per substep
5
+ * `clothPredict`, every batch of every kind of constraint per iteration, `clothLimit` and
6
+ * `clothFinish`; then `clothBlend` while a reset blends back in. The particles never leave the
7
+ * device: a frame ends with `clothPublish` writing them, at the frame's `alpha`, into the particle
8
+ * texture the bound mesh's vertex stage reads — so a garment costs no upload a frame at all.
9
+ *
10
+ * **One submit a frame, everything it needs written before it.** `queue.writeBuffer` lands ahead of
11
+ * the whole submit rather than between two dispatches, which is the trap `AGENTS.md` (2026-08-27)
12
+ * records for draws and is no different for dispatches. So every step takes a slot of its own, at a
13
+ * dynamic offset, and the batches' ranges are slots written once; a frame that runs out of slots
14
+ * submits what it has first and goes on, and so does a pose, whose matrices are one buffer.
15
+ *
16
+ * **What it gives up**: single precision, and a dispatch per batch — a garment of nine colours and
17
+ * four iterations is about forty dispatches a step, which is cheap on a desktop part and is what the
18
+ * budget in `demo/dev/skinnedCloth.html` measures. Self-collision is accepted and not simulated, as on
19
+ * the CPU.
20
+ */
21
+ import { ClothControl, exactExp, validateClothSetup } from '@driftengine/physics';
22
+ import { packCloth } from './clothSolvePack.js';
23
+ import { GpuClothParticles } from './clothTextures.js';
24
+ import { CLOTH_SOLVE_WGSL, CLOTH_WORKGROUP } from './shaders/clothSolve.wgsl.js';
25
+ import { shaderModule } from './shaderModules.js';
26
+ import { DYNAMIC_ALIGNMENT } from './uniformRing.js';
27
+ const STORAGE = 0x80;
28
+ const UNIFORM = 0x40;
29
+ const COPY_DST = 0x8;
30
+ const COPY_SRC = 0x4;
31
+ const MAP_READ = 0x1;
32
+ const COMPUTE = 0x4;
33
+ /** Step slots a submit holds before it is sent and the ring starts again. */
34
+ const STEP_SLOTS = 64;
35
+ /** Floats in a step's slot that are written: see `Step` in the shader. */
36
+ const STEP_FLOATS = 32;
37
+ const CONSTANT_BYTES = 80;
38
+ const ENTRIES = [
39
+ 'clothPose',
40
+ 'clothRest',
41
+ 'clothBegin',
42
+ 'clothPredict',
43
+ 'clothDistance',
44
+ 'clothBending',
45
+ 'clothTether',
46
+ 'clothLimit',
47
+ 'clothFinish',
48
+ 'clothBlend',
49
+ 'clothPublish',
50
+ ];
51
+ const kernelsByDevice = new WeakMap();
52
+ function kernelsFor(device) {
53
+ const held = kernelsByDevice.get(device);
54
+ if (held !== undefined)
55
+ return held;
56
+ const uniform = (binding, dynamic) => ({
57
+ binding,
58
+ visibility: COMPUTE,
59
+ buffer: { type: 'uniform', hasDynamicOffset: dynamic },
60
+ });
61
+ const storage = (binding, type) => ({
62
+ binding,
63
+ visibility: COMPUTE,
64
+ buffer: { type },
65
+ });
66
+ const layout = device.createBindGroupLayout({
67
+ label: 'cloth.solve',
68
+ entries: [
69
+ uniform(0, false),
70
+ uniform(1, true),
71
+ uniform(2, true),
72
+ storage(3, 'storage'),
73
+ storage(4, 'storage'),
74
+ storage(5, 'read-only-storage'),
75
+ storage(6, 'read-only-storage'),
76
+ storage(7, 'storage'),
77
+ storage(8, 'read-only-storage'),
78
+ ],
79
+ });
80
+ const outputLayout = device.createBindGroupLayout({
81
+ label: 'cloth.publish',
82
+ entries: [
83
+ {
84
+ binding: 0,
85
+ visibility: COMPUTE,
86
+ storageTexture: { access: 'write-only', format: 'rgba32float' },
87
+ },
88
+ ],
89
+ });
90
+ const module = shaderModule(device, { label: 'cloth.solve', code: CLOTH_SOLVE_WGSL });
91
+ const solve = device.createPipelineLayout({ bindGroupLayouts: [layout] });
92
+ const publish = device.createPipelineLayout({ bindGroupLayouts: [layout, outputLayout] });
93
+ const pipelines = {};
94
+ for (const entry of ENTRIES) {
95
+ pipelines[entry] = device.createComputePipeline({
96
+ label: `cloth.${entry}`,
97
+ layout: entry === 'clothPublish' ? publish : solve,
98
+ compute: { module, entryPoint: entry },
99
+ });
100
+ }
101
+ const kernels = { layout, outputLayout, pipelines };
102
+ kernelsByDevice.set(device, kernels);
103
+ return kernels;
104
+ }
105
+ /** One garment's solver on the device, and the particle textures it publishes into. */
106
+ export class GpuSkinnedCloth {
107
+ device;
108
+ /** The particles a bound mesh draws by: see `RendererApi.setCloth`. */
109
+ particles;
110
+ count;
111
+ control;
112
+ packed;
113
+ kernels;
114
+ buffers = [];
115
+ state;
116
+ frameData;
117
+ stepSlots;
118
+ group;
119
+ outputs;
120
+ frameStaging;
121
+ stepStaging = new ArrayBuffer(STEP_FLOATS * 4);
122
+ stepFloats = new Float32Array(this.stepStaging);
123
+ stepWords = new Uint32Array(this.stepStaging);
124
+ /** Each batch's slot, by kind and by whether it opens a substep's iterations. */
125
+ batchSlot;
126
+ /** The air the steps drag toward: the set-up's until `setWind`, written into every step. */
127
+ wind = new Float32Array(3);
128
+ groups;
129
+ substeps;
130
+ iterations;
131
+ encoder = null;
132
+ pass = null;
133
+ slot = 0;
134
+ /** Where the pose a step starts from and the one it moves toward sit in `pose`, in floats. */
135
+ fromAt = 0;
136
+ toAt = 0;
137
+ constructor(device, setup) {
138
+ this.device = device;
139
+ validateClothSetup(setup);
140
+ const packed = packCloth(setup);
141
+ this.packed = packed;
142
+ const n = packed.count;
143
+ this.count = n;
144
+ this.kernels = kernelsFor(device);
145
+ this.particles = new GpuClothParticles(device, n, true);
146
+ this.groups = Math.ceil(n / CLOTH_WORKGROUP);
147
+ const solver = {
148
+ targetsChanged: (change) => {
149
+ this.targetsChanged(change);
150
+ },
151
+ rest: () => {
152
+ this.rest();
153
+ },
154
+ step: (fraction, blend) => {
155
+ this.step(fraction, blend);
156
+ },
157
+ };
158
+ this.control = new ClothControl(setup, solver, false);
159
+ const parameters = this.control.parameters;
160
+ this.wind.set(parameters.wind);
161
+ this.substeps = parameters.substeps;
162
+ this.iterations = parameters.iterations;
163
+ const buffer = (label, bytes, usage) => {
164
+ const made = device.createBuffer({
165
+ label: `cloth.${label}`,
166
+ size: Math.max(16, bytes),
167
+ usage,
168
+ });
169
+ this.buffers.push(made);
170
+ return made;
171
+ };
172
+ const constants = buffer('constants', CONSTANT_BYTES, UNIFORM | COPY_DST);
173
+ this.stepSlots = buffer('steps', STEP_SLOTS * DYNAMIC_ALIGNMENT, UNIFORM | COPY_DST);
174
+ const batchFloats = this.writeBatches();
175
+ const batches = buffer('batches', batchFloats.byteLength, UNIFORM | COPY_DST);
176
+ this.state = buffer('state', n * 12 * 4, STORAGE | COPY_DST | COPY_SRC);
177
+ const pose = buffer('pose', n * 9 * 4, STORAGE);
178
+ const statics = buffer('statics', packed.statics.byteLength, STORAGE | COPY_DST);
179
+ const constraints = buffer('constraints', packed.constraints.byteLength, STORAGE | COPY_DST);
180
+ const lambda = buffer('lambda', (packed.distances + packed.bendings) * 4, STORAGE);
181
+ this.frameStaging = new Float32Array(packed.joints * 16 + packed.colliders * 8);
182
+ this.frameData = buffer('frame', this.frameStaging.byteLength, STORAGE | COPY_DST);
183
+ const queue = device.queue;
184
+ queue.writeBuffer(constants, 0, this.constants());
185
+ queue.writeBuffer(batches, 0, batchFloats);
186
+ queue.writeBuffer(statics, 0, packed.statics);
187
+ if (packed.constraints.byteLength > 0) {
188
+ queue.writeBuffer(constraints, 0, packed.constraints);
189
+ }
190
+ /* The rest pose until the first `setPose`, which resets the cloth to its own. */
191
+ const state = new Float32Array(n * 12);
192
+ state.set(setup.positions, 0);
193
+ state.set(setup.positions, n * 9);
194
+ queue.writeBuffer(this.state, 0, state);
195
+ const slot = (bufferOf) => ({
196
+ buffer: bufferOf,
197
+ size: DYNAMIC_ALIGNMENT,
198
+ });
199
+ this.group = device.createBindGroup({
200
+ label: 'cloth.solve',
201
+ layout: this.kernels.layout,
202
+ entries: [
203
+ { binding: 0, resource: { buffer: constants } },
204
+ { binding: 1, resource: slot(this.stepSlots) },
205
+ { binding: 2, resource: slot(batches) },
206
+ { binding: 3, resource: { buffer: this.state } },
207
+ { binding: 4, resource: { buffer: pose } },
208
+ { binding: 5, resource: { buffer: statics } },
209
+ { binding: 6, resource: { buffer: constraints } },
210
+ { binding: 7, resource: { buffer: lambda } },
211
+ { binding: 8, resource: { buffer: this.frameData } },
212
+ ],
213
+ });
214
+ const output = (k) => device.createBindGroup({
215
+ label: 'cloth.publish',
216
+ layout: this.kernels.outputLayout,
217
+ entries: [{ binding: 0, resource: this.particles.viewOf(k) }],
218
+ });
219
+ this.outputs = [output(0), output(1)];
220
+ this.batchSlot = {
221
+ distance: 0,
222
+ bending: (packed.distanceBatches.length - 1) * 2,
223
+ tether: (packed.distanceBatches.length - 1 + packed.bendingBatches.length - 1) * 2,
224
+ };
225
+ }
226
+ /**
227
+ * One frame: the rig's pose, `dt` of the caller's time in whole fixed steps, and the particles at
228
+ * the frame's `alpha` published for the draws that follow, as the particles of `frame`.
229
+ */
230
+ advance(globals, model, dt, frame) {
231
+ this.control.setPose(globals, model);
232
+ this.control.advance(dt);
233
+ const view = this.particles.publish(frame);
234
+ this.dispatch('clothPublish', this.takeSlot(0, 0, this.control.alpha, null), 0, view);
235
+ this.flush();
236
+ }
237
+ /** The frame's sample of the scene's wind, for the steps to come. See `SkinnedCloth.setWind`. */
238
+ setWind(x, y, z) {
239
+ this.wind[0] = x;
240
+ this.wind[1] = y;
241
+ this.wind[2] = z;
242
+ }
243
+ /** Back to the skinned pose, settled; the next `advance` publishes it. */
244
+ reset() {
245
+ this.control.reset();
246
+ }
247
+ /**
248
+ * The particles where the last step left them, read back: for the parity check, which compares
249
+ * them with the CPU solver's. Not for a frame — it waits on the device.
250
+ */
251
+ async read() {
252
+ this.flush();
253
+ const bytes = this.count * 12;
254
+ const readback = this.device.createBuffer({
255
+ label: 'cloth.readback',
256
+ size: bytes,
257
+ usage: MAP_READ | COPY_DST,
258
+ });
259
+ const encoder = this.device.createCommandEncoder({ label: 'cloth.read' });
260
+ encoder.copyBufferToBuffer(this.state, 0, readback, 0, bytes);
261
+ this.device.queue.submit([encoder.finish()]);
262
+ await readback.mapAsync(MAP_READ);
263
+ const out = new Float32Array(readback.getMappedRange().slice(0));
264
+ readback.unmap();
265
+ readback.destroy();
266
+ return out;
267
+ }
268
+ /** Its buffers; the particle textures are the renderer's to retire with the frame. */
269
+ dispose() {
270
+ this.flush();
271
+ for (const made of this.buffers)
272
+ made.destroy();
273
+ }
274
+ targetsChanged(change) {
275
+ if (change !== 'posed') {
276
+ this.fromAt = this.toAt;
277
+ return;
278
+ }
279
+ /*
280
+ * The pose's matrices and colliders are written ahead of the next submit, so anything already
281
+ * recorded — a reset's settle steps, which read the colliders — is sent first, or it would run
282
+ * against this pose rather than its own.
283
+ */
284
+ this.flush();
285
+ this.writeFrameData();
286
+ this.fromAt = this.toAt;
287
+ this.toAt = this.toAt === 0 ? this.count * 3 : 0;
288
+ this.dispatch('clothPose', this.takeSlot(0, 0, 0, null), 0, -1);
289
+ }
290
+ rest() {
291
+ this.dispatch('clothRest', this.takeSlot(1, 0, 0, null), 0, -1);
292
+ }
293
+ step(fraction, blend) {
294
+ const at = this.takeSlot(fraction, blend, 0, this.control.targets);
295
+ this.dispatch('clothBegin', at, 0, -1);
296
+ const { distanceBatches, bendingBatches, tetherBatches } = this.packed;
297
+ for (let s = 0; s < this.substeps; s++) {
298
+ this.dispatch('clothPredict', at, 0, -1);
299
+ for (let pass = 0; pass < this.iterations; pass++) {
300
+ const first = pass === 0 ? 1 : 0;
301
+ this.batches('clothDistance', distanceBatches, this.batchSlot.distance, first, at);
302
+ this.batches('clothBending', bendingBatches, this.batchSlot.bending, first, at);
303
+ this.batches('clothTether', tetherBatches, this.batchSlot.tether, first, at);
304
+ }
305
+ this.dispatch('clothLimit', at, 0, -1);
306
+ this.dispatch('clothFinish', at, 0, -1);
307
+ }
308
+ if (blend > 0)
309
+ this.dispatch('clothBlend', at, 0, -1);
310
+ }
311
+ /** Every batch of one kind, in colour order, each a dispatch over its range. */
312
+ batches(entry, batches, firstSlot, first, stepAt) {
313
+ for (let b = 0; b + 1 < batches.length; b++) {
314
+ const size = batches[b + 1] - batches[b];
315
+ if (size === 0)
316
+ continue;
317
+ const slot = firstSlot + b * 2 + first;
318
+ this.dispatch(entry, stepAt, slot * DYNAMIC_ALIGNMENT, -1, Math.ceil(size / CLOTH_WORKGROUP));
319
+ }
320
+ }
321
+ dispatch(entry, stepAt, batchAt, output, groups = this.groups) {
322
+ const pass = this.passFor();
323
+ pass.setPipeline(this.kernels.pipelines[entry]);
324
+ pass.setBindGroup(0, this.group, [stepAt, batchAt]);
325
+ if (output >= 0)
326
+ pass.setBindGroup(1, this.outputs[output]);
327
+ pass.dispatchWorkgroups(groups);
328
+ }
329
+ passFor() {
330
+ if (this.pass === null) {
331
+ this.encoder = this.device.createCommandEncoder({ label: 'cloth' });
332
+ this.pass = this.encoder.beginComputePass({ label: 'cloth' });
333
+ }
334
+ return this.pass;
335
+ }
336
+ /** Send what has been recorded: every slot it reads was written ahead of it on the queue. */
337
+ flush() {
338
+ const { pass, encoder } = this;
339
+ if (pass === null || encoder === null)
340
+ return;
341
+ pass.end();
342
+ this.device.queue.submit([encoder.finish()]);
343
+ this.pass = null;
344
+ this.encoder = null;
345
+ this.slot = 0;
346
+ }
347
+ /**
348
+ * A step's slot: its fraction, blend and alpha, the poses it reads, and — where `targets` is
349
+ * given — the share of the character's motion the cloth is carried by, as `carryParticles` takes
350
+ * it. Returns the slot's byte offset.
351
+ */
352
+ takeSlot(fraction, blend, alpha, targets) {
353
+ if (this.slot === STEP_SLOTS)
354
+ this.flush();
355
+ const f = this.stepFloats;
356
+ const u = this.stepWords;
357
+ f.fill(0);
358
+ f[0] = fraction;
359
+ f[1] = blend;
360
+ f[2] = alpha;
361
+ u[4] = this.fromAt;
362
+ u[5] = this.toAt;
363
+ f.set(this.wind, 28);
364
+ if (targets !== null)
365
+ this.writeCarry(targets.modelBefore, targets.modelNow);
366
+ const at = this.slot * DYNAMIC_ALIGNMENT;
367
+ this.device.queue.writeBuffer(this.stepSlots, at, this.stepStaging);
368
+ this.slot++;
369
+ return at;
370
+ }
371
+ /** `carryParticles`' rotation, origin and move, into the step's slot; carry 0 where none. */
372
+ writeCarry(previous, next) {
373
+ const { linearInertia, angularInertia } = this.control.parameters;
374
+ const turn = 1 - angularInertia;
375
+ const move = 1 - linearInertia;
376
+ if (turn === 0 && move === 0)
377
+ return;
378
+ const f = this.stepFloats;
379
+ this.stepWords[3] = 1;
380
+ /* Column c of R = R_next × R_previousᵀ, at turn0 … turn2. */
381
+ for (let col = 0; col < 3; col++) {
382
+ for (let row = 0; row < 3; row++) {
383
+ let sum = 0;
384
+ for (let k = 0; k < 3; k++) {
385
+ sum += next[k * 4 + row] * previous[k * 4 + col];
386
+ }
387
+ f[8 + col * 4 + row] = sum;
388
+ }
389
+ }
390
+ for (let k = 0; k < 3; k++) {
391
+ f[20 + k] = previous[12 + k];
392
+ f[24 + k] = (next[12 + k] - previous[12 + k]) * move;
393
+ }
394
+ f[23] = turn;
395
+ }
396
+ /** The pose's skin matrices — or the model, for a cloth with no rig — and its colliders. */
397
+ writeFrameData() {
398
+ const targets = this.control.targets;
399
+ const out = this.frameStaging;
400
+ const joints = this.packed.joints;
401
+ out.set(this.packed.rigged && targets.skin !== null ? targets.skin : targets.model, 0);
402
+ const colliders = this.packed.colliders;
403
+ out.set(targets.colliderEnds, joints * 16);
404
+ out.set(targets.colliderRadii, joints * 16 + colliders * 6);
405
+ if (out.byteLength > 0)
406
+ this.device.queue.writeBuffer(this.frameData, 0, out);
407
+ }
408
+ constants() {
409
+ const p = this.control.parameters;
410
+ const packed = this.packed;
411
+ const bytes = new ArrayBuffer(CONSTANT_BYTES);
412
+ const u = new Uint32Array(bytes);
413
+ const f = new Float32Array(bytes);
414
+ const h = p.step / p.substeps;
415
+ u[0] = packed.count;
416
+ u[1] = packed.distances;
417
+ u[2] = packed.bendings;
418
+ u[3] = packed.joints;
419
+ u[4] = packed.colliders;
420
+ u[5] = packed.limits;
421
+ u[6] = this.particles.width;
422
+ f[8] = h;
423
+ f[9] = 1 / h;
424
+ f[10] = 1 / (h * h);
425
+ f[11] = exactExp(p.damping * h);
426
+ f[12] = exactExp(p.drag * h);
427
+ f[13] = p.maxDistanceScale;
428
+ f[14] = p.margin;
429
+ f.set(p.gravity, 16);
430
+ return bytes;
431
+ }
432
+ /** Each batch's range, twice — opening a substep's iterations and not — at a slot each. */
433
+ writeBatches() {
434
+ const { distanceBatches, bendingBatches, tetherBatches } = this.packed;
435
+ const kinds = [distanceBatches, bendingBatches, tetherBatches];
436
+ const slots = kinds.reduce((sum, batches) => sum + (batches.length - 1) * 2, 0);
437
+ const words = new Uint32Array((Math.max(1, slots) * DYNAMIC_ALIGNMENT) / 4);
438
+ let slot = 0;
439
+ for (const batches of kinds) {
440
+ for (let b = 0; b + 1 < batches.length; b++) {
441
+ for (let first = 0; first < 2; first++) {
442
+ const at = (slot * DYNAMIC_ALIGNMENT) / 4;
443
+ words[at] = batches[b];
444
+ words[at + 1] = batches[b + 1];
445
+ words[at + 2] = first;
446
+ slot++;
447
+ }
448
+ }
449
+ }
450
+ return words;
451
+ }
452
+ }
@@ -3,6 +3,7 @@ import { LINE_BINDINGS, LINE_FRAG_WGSL, LINE_VERT_WGSL, } from '../../shaders/ge
3
3
  import { buildSegmentQuads } from '../../segmentQuads.js';
4
4
  import { DEPTH_FORMAT } from './flatPass.js';
5
5
  import { shaderModule } from './shaderModules.js';
6
+ import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
6
7
  /**
7
8
  * The line pass: a polyline with a real width, four vertices a segment.
8
9
  *
@@ -179,7 +180,7 @@ additive = false) {
179
180
  dstFactor: 'one-minus-src-alpha',
180
181
  operation: 'add',
181
182
  },
182
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
183
+ alpha: SCENE_ALPHA_COVERS,
183
184
  },
184
185
  },
185
186
  ],
@@ -11,6 +11,19 @@ export declare class MotionPass {
11
11
  /** A skinned record's two palettes, this frame's and last frame's, by record index. */
12
12
  private readonly paletteNow;
13
13
  private readonly paletteWas;
14
+ /** A cloth record's binding and its particles now and last frame, by record index. */
15
+ private readonly clothBinding;
16
+ private readonly clothNow;
17
+ private readonly clothWas;
18
+ private clothLayout;
19
+ private clothPipelineLayout;
20
+ /** Keyed as `skinnedPipelines` is. */
21
+ private readonly clothPipelines;
22
+ /**
23
+ * A bind group per particles now, particles then and binding. Weak at the top, by this frame's
24
+ * particles, so a character's groups go when its particles do; a steady garment settles on three.
25
+ */
26
+ private clothGroups;
14
27
  /** A rewritten record's last-frame rows, by record index. */
15
28
  private readonly previousRows;
16
29
  /** One pipeline per stride for rewritten meshes: current and previous rows at that stride. */
@@ -48,6 +61,11 @@ export declare class MotionPass {
48
61
  recordRigid(mesh: GpuMesh, model: ArrayLike<number>, previousModel: ArrayLike<number>): void;
49
62
  /** A skinned mover: its positions under last frame's pose as well as last frame's model. */
50
63
  recordSkinned(mesh: GpuMesh, model: ArrayLike<number>, previousModel: ArrayLike<number>, palette: GPUTextureView, previousPalette: GPUTextureView): void;
64
+ /**
65
+ * A cloth-bound skinned draw: its skinned positions under both poses, each carried toward the
66
+ * cloth by its painted weight — this frame's `particles` and last frame's `previousParticles`.
67
+ */
68
+ recordCloth(mesh: GpuMesh, model: ArrayLike<number>, previousModel: ArrayLike<number>, palette: GPUTextureView, previousPalette: GPUTextureView, binding: GPUTextureView, particles: GPUTextureView, previousParticles: GPUTextureView): void;
51
69
  /** A rewritten mesh: its vertices as they were, read from `previous`, under last frame's model. */
52
70
  recordDynamic(mesh: GpuMesh, model: ArrayLike<number>, previousModel: ArrayLike<number>, previous: GPUBuffer): void;
53
71
  /** An instanced batch: its first `count` slots, each under last frame's placement of that slot. */
@@ -77,6 +95,8 @@ export declare class MotionPass {
77
95
  private instancedPipelineFor;
78
96
  /** The bind group holding one pair of palettes, built the first time the pair is seen. */
79
97
  private paletteGroup;
98
+ /** The bind group holding a cloth's binding and its two particle textures, built on first sight. */
99
+ private clothGroup;
80
100
  /** The pipeline for one vertex stride, built on first sight of it. */
81
101
  private pipelineFor;
82
102
  /**