@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,253 @@
1
+ /**
2
+ * Skin's screen-space scattering on this backend: the targets, the kept draws and the two passes
3
+ * that spread them. `skinBlur.ts` is what the blur computes and why; this is where it runs.
4
+ *
5
+ * **What the renderer hands it.** Each skin draw, recorded into the frame with its diffuse taken
6
+ * out, is recorded a second time here with the diffuse alone. When the frame first draws something
7
+ * blended — or ends, if nothing is — the renderer ends its pass and calls `drawDiffuse`, which
8
+ * replays them into a half-float target against the frame's own depth; resolves the depth; and
9
+ * calls `spread`, which blurs across into a second target and then down, adding into the frame.
10
+ *
11
+ * **The draws are commands, not calls**, for the reason `LatePass` keeps its own pool: the frame
12
+ * graph's is spent at every flush, and these are replayed after the flush that ends the frame's
13
+ * pass. Each holds the per-draw ring slot its frame half used, and those stay valid until the
14
+ * encoder is replaced, which is after this has run.
15
+ *
16
+ * **What it costs**: every skin draw twice; two half-float targets the size of the frame, and a
17
+ * third multisampled one under multisampling; a depth resolve; and two full-screen passes that
18
+ * leave at once wherever no skin was drawn.
19
+ */
20
+ import { SKIN_PROFILES } from '../../skinBlur.js';
21
+ import { SKINBLUR_BINDINGS, SKIN_BLUR_FRAG_WGSL } from '../../shaders/generated/skinBlur.wgsl.js';
22
+ import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.js';
23
+ import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
24
+ import { createPostStageLayout, postPipeline, postPipelineTargets } from './postPass.js';
25
+ import { DYNAMIC_ALIGNMENT } from './uniformRing.js';
26
+ const BINDINGS = SKINBLUR_BINDINGS.SKIN_BLUR_FRAG;
27
+ const SLOT = Math.ceil(BINDINGS.uniformSize / DYNAMIC_ALIGNMENT) * DYNAMIC_ALIGNMENT;
28
+ const USAGE_TARGET = 0x10 | 0x4; // RENDER_ATTACHMENT | TEXTURE_BINDING
29
+ const USAGE_UNIFORM_DST = 0x40 | 0x8; // UNIFORM | COPY_DST
30
+ /** The format the diffuse lands in: half floats, because it is linear light before any grade. */
31
+ export const SKIN_TARGET_FORMAT = 'rgba16float';
32
+ export class SkinScatterPass {
33
+ device;
34
+ layout;
35
+ uniforms;
36
+ staging = new Float32Array((SLOT * 2) / 4);
37
+ linear;
38
+ nearest;
39
+ commands = createCommandPool(16);
40
+ /** Each profile's scatter distance per channel, in metres: four floats a profile. */
41
+ profiles = new Float32Array(SKIN_PROFILES * 4);
42
+ diffuse = null;
43
+ diffuseMsaa = null;
44
+ across = null;
45
+ diffuseView = null;
46
+ diffuseMsaaView = null;
47
+ acrossView = null;
48
+ groupAcross = null;
49
+ groupDown = null;
50
+ groupsDepth = null;
51
+ constructor(device) {
52
+ this.device = device;
53
+ this.layout = createPostStageLayout(device, BINDINGS.uniforms, BINDINGS.uniformSize, [
54
+ { binding: BINDINGS.textures.uSkin },
55
+ { binding: BINDINGS.textures.uDepth, filterable: false },
56
+ ]);
57
+ this.uniforms = device.createBuffer({
58
+ label: 'skin.blurUniforms',
59
+ size: SLOT * 2,
60
+ usage: USAGE_UNIFORM_DST,
61
+ });
62
+ this.linear = device.createSampler({
63
+ label: 'skin.linear',
64
+ magFilter: 'linear',
65
+ minFilter: 'linear',
66
+ addressModeU: 'clamp-to-edge',
67
+ addressModeV: 'clamp-to-edge',
68
+ });
69
+ this.nearest = device.createSampler({
70
+ label: 'skin.nearest',
71
+ addressModeU: 'clamp-to-edge',
72
+ addressModeV: 'clamp-to-edge',
73
+ });
74
+ }
75
+ /** Whether any skin was kept for this frame. */
76
+ get pending() {
77
+ return this.commands.taken > 0;
78
+ }
79
+ /** A command for one skin draw's diffuse half, filled by the caller. */
80
+ take() {
81
+ return this.commands.commands[takeCommand(this.commands)];
82
+ }
83
+ /** What one profile scatters, per channel, in metres: the last material to name it says. */
84
+ setProfile(index, r, g, b) {
85
+ const at = Math.min(Math.max(Math.round(index), 0), SKIN_PROFILES - 1) * 4;
86
+ this.profiles[at] = r;
87
+ this.profiles[at + 1] = g;
88
+ this.profiles[at + 2] = b;
89
+ }
90
+ /** The targets at the frame's size and sample count, made again only when either changes. */
91
+ size(width, height, samples) {
92
+ if (this.diffuse !== null &&
93
+ this.diffuse.width === width &&
94
+ this.diffuse.height === height &&
95
+ samples > 1 === (this.diffuseMsaa !== null) &&
96
+ (this.diffuseMsaa === null || this.diffuseMsaa.sampleCount === samples)) {
97
+ return;
98
+ }
99
+ this.releaseTargets();
100
+ const make = (label, count) => this.device.createTexture({
101
+ label,
102
+ size: [width, height],
103
+ format: SKIN_TARGET_FORMAT,
104
+ sampleCount: count,
105
+ usage: count > 1 ? 0x10 : USAGE_TARGET,
106
+ });
107
+ this.diffuse = make('skin.diffuse', 1);
108
+ this.across = make('skin.across', 1);
109
+ this.diffuseMsaa = samples > 1 ? make('skin.diffuseMsaa', samples) : null;
110
+ this.diffuseView = this.diffuse.createView();
111
+ this.acrossView = this.across.createView();
112
+ this.diffuseMsaaView = this.diffuseMsaa?.createView() ?? null;
113
+ this.groupAcross = null;
114
+ this.groupDown = null;
115
+ }
116
+ /**
117
+ * Every kept draw's diffuse into the target, against the frame's own depth, which it reads and
118
+ * does not write. Called with no pass open on `encoder`.
119
+ */
120
+ drawDiffuse(encoder, depth, issue, timestampWrites) {
121
+ const target = this.diffuseView;
122
+ if (target === null)
123
+ return;
124
+ const pass = encoder.beginRenderPass({
125
+ label: 'skin.diffuse',
126
+ timestampWrites,
127
+ colorAttachments: [
128
+ {
129
+ view: this.diffuseMsaaView ?? target,
130
+ resolveTarget: this.diffuseMsaaView === null ? undefined : target,
131
+ clearValue: [0, 0, 0, 0],
132
+ loadOp: 'clear',
133
+ storeOp: this.diffuseMsaaView === null ? 'store' : 'discard',
134
+ },
135
+ ],
136
+ depthStencilAttachment: { view: depth, depthLoadOp: 'load', depthStoreOp: 'store' },
137
+ });
138
+ for (let i = 0; i < this.commands.taken; i += 1) {
139
+ issue(pass, this.commands.commands[i]);
140
+ }
141
+ pass.end();
142
+ }
143
+ /**
144
+ * Across into the second target, then down and added into the frame's colour — `frame` the
145
+ * attachment its pass draws into, `resolve` where a multisampled one resolves, `samples` its
146
+ * count. `projection` is the frame's, `height` the frame's height in pixels.
147
+ */
148
+ spread(encoder, pipelines, depth, frame, resolve, samples, projection, inverseProjection, timestampWrites) {
149
+ const diffuse = this.diffuse;
150
+ if (diffuse === null || this.diffuseView === null || this.acrossView === null)
151
+ return;
152
+ if (this.groupAcross === null || this.groupDown === null || this.groupsDepth !== depth) {
153
+ this.groupAcross = this.group('skin.blurAcross', this.diffuseView, depth);
154
+ this.groupDown = this.group('skin.blurDown', this.acrossView, depth);
155
+ this.groupsDepth = depth;
156
+ }
157
+ this.writeUniforms(diffuse.width, diffuse.height, projection, inverseProjection);
158
+ const across = postPipeline(pipelines, this.device, this.layout, 'skin.blurAcross', SKIN_BLUR_FRAG_WGSL, SKIN_TARGET_FORMAT);
159
+ const down = postPipelineTargets(pipelines, this.device, this.layout, `skin.blurDown:${pipelines.format}:${samples}`, SKIN_BLUR_FRAG_WGSL, [
160
+ {
161
+ format: pipelines.format,
162
+ /* Added: its colour onto the frame's, the frame's alpha kept. */
163
+ blend: {
164
+ color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
165
+ alpha: SCENE_ALPHA_KEEPS,
166
+ },
167
+ },
168
+ ], samples);
169
+ const first = encoder.beginRenderPass({
170
+ label: 'skin.blurAcross',
171
+ timestampWrites: timestampWrites(),
172
+ colorAttachments: [
173
+ { view: this.acrossView, clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store' },
174
+ ],
175
+ });
176
+ first.setPipeline(across);
177
+ first.setBindGroup(0, this.groupAcross, [0]);
178
+ first.draw(3);
179
+ first.end();
180
+ const second = encoder.beginRenderPass({
181
+ label: 'skin.blurDown',
182
+ timestampWrites: timestampWrites(),
183
+ colorAttachments: [
184
+ {
185
+ view: frame,
186
+ resolveTarget: resolve ?? undefined,
187
+ loadOp: 'load',
188
+ storeOp: 'store',
189
+ },
190
+ ],
191
+ });
192
+ second.setPipeline(down);
193
+ second.setBindGroup(0, this.groupDown, [SLOT]);
194
+ second.draw(3);
195
+ second.end();
196
+ }
197
+ /** Spent at the frame's start: the kept draws were this frame's. */
198
+ reset() {
199
+ resetPool(this.commands);
200
+ }
201
+ dispose() {
202
+ this.releaseTargets();
203
+ this.uniforms.destroy();
204
+ }
205
+ group(label, skin, depth) {
206
+ return this.device.createBindGroup({
207
+ label,
208
+ layout: this.layout,
209
+ entries: [
210
+ {
211
+ binding: BINDINGS.uniforms,
212
+ resource: { buffer: this.uniforms, size: BINDINGS.uniformSize },
213
+ },
214
+ { binding: BINDINGS.textures.uSkin.texture, resource: skin },
215
+ { binding: BINDINGS.textures.uSkin.sampler, resource: this.linear },
216
+ { binding: BINDINGS.textures.uDepth.texture, resource: depth },
217
+ { binding: BINDINGS.textures.uDepth.sampler, resource: this.nearest },
218
+ ],
219
+ });
220
+ }
221
+ /** Both axes' blocks: the step along each, and what they share. Column-major matrices. */
222
+ writeUniforms(width, height, projection, inverse) {
223
+ const f = this.staging;
224
+ const fields = BINDINGS.fields;
225
+ for (let axis = 0; axis < 2; axis += 1) {
226
+ const base = (axis * SLOT) / 4;
227
+ f[base + fields.uStep.offset / 4] = axis === 0 ? 1 / width : 0;
228
+ f[base + fields.uStep.offset / 4 + 1] = axis === 0 ? 0 : 1 / height;
229
+ /* The four terms that carry a depth back to view-space metres: the occlusion blur's own. */
230
+ const toZ = base + fields.uDepthToViewZ.offset / 4;
231
+ f[toZ] = inverse[10] ?? 0;
232
+ f[toZ + 1] = inverse[14] ?? 0;
233
+ f[toZ + 2] = inverse[11] ?? 0;
234
+ f[toZ + 3] = inverse[15] ?? 1;
235
+ /* Pixels a metre spans at a metre: the vertical focal length, which is the horizontal one
236
+ too wherever a pixel is square. */
237
+ f[base + fields.uFocal.offset / 4] = (projection[5] * height) / 2;
238
+ f.set(this.profiles, base + fields.uProfiles.offset / 4);
239
+ }
240
+ this.device.queue.writeBuffer(this.uniforms, 0, f);
241
+ }
242
+ releaseTargets() {
243
+ this.diffuse?.destroy();
244
+ this.diffuseMsaa?.destroy();
245
+ this.across?.destroy();
246
+ this.diffuse = null;
247
+ this.diffuseMsaa = null;
248
+ this.across = null;
249
+ this.diffuseView = null;
250
+ this.diffuseMsaaView = null;
251
+ this.acrossView = null;
252
+ }
253
+ }
@@ -1,4 +1,5 @@
1
1
  import type { SurfaceTextureOptions } from '../../surfaceTexture.ts';
2
+ import type { CompressedTextureFormat, SurfaceSource } from '../../compressedSource.ts';
2
3
  import type { PipelineCache } from './pipelineCache.ts';
3
4
  /** How many levels an image of this size has, counting the one it arrives with. */
4
5
  export declare function mipLevelCount(width: number, height: number): number;
@@ -59,7 +60,9 @@ export declare class GpuSurfaceTexture {
59
60
  private effectsTable;
60
61
  /** Its view, which the flat group binds beside this image when it is a material's albedo. */
61
62
  effectsView: GPUTextureView | null;
62
- constructor(device: GPUDevice, pipelines: PipelineCache, source: TexImageSource | readonly TexImageSource[], options?: SurfaceTextureOptions);
63
+ /** The block format a compressed texture holds, or null for one uploaded from images. */
64
+ private readonly blockFormat;
65
+ constructor(device: GPUDevice, pipelines: PipelineCache, source: SurfaceSource | readonly SurfaceSource[], options?: SurfaceTextureOptions, compressed?: readonly CompressedTextureFormat[]);
63
66
  /** A view of every layer, as the shaders' `texture_2d_array` bindings read it. */
64
67
  private arrayView;
65
68
  /** The view every binding of this image holds. A new one when `update` changes the size. */
@@ -1,6 +1,8 @@
1
1
  import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from '../../surfaceEffects.js';
2
2
  import { toHalfFloats } from '../../halfFloat.js';
3
- import { isSourceList, layerSize, refuseArrayUpdate, sourceSize } from '../../textureSource.js';
3
+ import { layerSize, refuseArrayUpdate, sourceSize } from '../../textureSource.js';
4
+ import { compressedLayers, planBlocks, refuseBlockUpdate } from '../../compressedSource.js';
5
+ import { createCompressedTexture } from './compressedUpload.js';
4
6
  import { shaderModule } from './shaderModules.js';
5
7
  /**
6
8
  * A caller's image on the device, and the mip chain WebGPU will not build for it.
@@ -148,20 +150,41 @@ export class GpuSurfaceTexture {
148
150
  effectsTable = null;
149
151
  /** Its view, which the flat group binds beside this image when it is a material's albedo. */
150
152
  effectsView = null;
151
- constructor(device, pipelines, source, options = {}) {
153
+ /** The block format a compressed texture holds, or null for one uploaded from images. */
154
+ blockFormat;
155
+ constructor(device, pipelines, source, options = {}, compressed = []) {
152
156
  this.device = device;
153
157
  this.pipelines = pipelines;
154
- const sources = isSourceList(source) ? source : [source];
155
- const { width, height } = layerSize(sources);
156
- this.layers = sources.length;
157
- this.mipmapped = options.mipmap ?? true;
158
+ const listed = (Array.isArray(source) ? source : [source]);
159
+ const blocks = compressedLayers(listed);
160
+ const srgb = (options.colorSpace ?? 'linear') === 'srgb';
158
161
  /* `SRGB8_ALPHA8`'s equivalent. Decoded in the sampler, before filtering, which is the only
159
162
  place it is correct — `surfaceTexture.ts` makes the argument in full. */
160
- this.format = (options.colorSpace ?? 'linear') === 'srgb' ? 'rgba8unorm-srgb' : 'rgba8unorm';
161
- this.width = width;
162
- this.height = height;
163
- this.levels = this.mipmapped ? mipLevelCount(width, height) : 1;
164
- this.texture = this.allocate();
163
+ this.format = srgb ? 'rgba8unorm-srgb' : 'rgba8unorm';
164
+ if (blocks !== null) {
165
+ /* Refused before anything is allocated; the stored chain, or level 0 alone where no chain
166
+ was asked for. See `compressedUpload.ts`. */
167
+ const plan = planBlocks(blocks, srgb, compressed);
168
+ const first = blocks[0];
169
+ this.layers = blocks.length;
170
+ this.blockFormat = first.format;
171
+ this.mipmapped = (options.mipmap ?? true) && plan.levels > 1;
172
+ this.width = first.width;
173
+ this.height = first.height;
174
+ this.levels = this.mipmapped ? plan.levels : 1;
175
+ this.texture = createCompressedTexture(device, plan.name, blocks, this.levels);
176
+ }
177
+ else {
178
+ const sources = listed;
179
+ const { width, height } = layerSize(sources);
180
+ this.layers = sources.length;
181
+ this.blockFormat = null;
182
+ this.mipmapped = options.mipmap ?? true;
183
+ this.width = width;
184
+ this.height = height;
185
+ this.levels = this.mipmapped ? mipLevelCount(width, height) : 1;
186
+ this.texture = this.allocate();
187
+ }
165
188
  this.current = this.arrayView(this.texture);
166
189
  const wrap = (options.wrap ?? 'repeat') === 'repeat' ? 'repeat' : 'clamp-to-edge';
167
190
  /*
@@ -193,7 +216,8 @@ export class GpuSurfaceTexture {
193
216
  ? Math.max(1, Math.floor(options.anisotropy ?? DEFAULT_ANISOTROPY))
194
217
  : 1,
195
218
  });
196
- this.upload(sources);
219
+ if (blocks === null)
220
+ this.upload(listed);
197
221
  /* Half floats, as `renderer.ts` uploads it: filterable, so the ordinary float layout takes it,
198
222
  and read by `textureLoad` alone, so the filter never runs. */
199
223
  const effects = options.effects ?? [];
@@ -236,6 +260,7 @@ export class GpuSurfaceTexture {
236
260
  if (replaced === null)
237
261
  return null;
238
262
  refuseArrayUpdate(this.layers);
263
+ refuseBlockUpdate(this.blockFormat);
239
264
  const { width, height } = sourceSize(source);
240
265
  if (width === this.width && height === this.height) {
241
266
  this.upload([source]);
@@ -3,6 +3,7 @@ import { TEXT_BINDINGS, TEXT_FRAG_WGSL, TEXT_VERT_WGSL, } from '../../shaders/ge
3
3
  import { MAX_CELLS, TEXT_CUBE, TextLayout } from '../../textLayout.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 text pass: a string as instanced glyph cubes, drawn over the scene.
8
9
  *
@@ -174,7 +175,7 @@ export function textPipeline(cache, device, layout) {
174
175
  format: cache.format,
175
176
  blend: {
176
177
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
177
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
178
+ alpha: SCENE_ALPHA_COVERS,
178
179
  },
179
180
  },
180
181
  ],
@@ -3,6 +3,7 @@ import { WATER_BINDINGS, WATER_FRAG_WGSL, WATER_VERT_WGSL, } from '../../shaders
3
3
  import { PATCH_RESOLUTION, buildUnitSheet, buildWaterGrid } from '../../waterGrid.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 water pass: two sheets and a patch, displaced by a wave field in the vertex stage.
8
9
  *
@@ -148,7 +149,7 @@ export function waterPipeline(cache, device, layout) {
148
149
  format: cache.format,
149
150
  blend: {
150
151
  color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
151
- alpha: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
152
+ alpha: SCENE_ALPHA_COVERS,
152
153
  },
153
154
  },
154
155
  ],
@@ -3,6 +3,7 @@ import { WINDSTREAKS_BINDINGS, WIND_STREAK_FRAG_WGSL, WIND_STREAK_VERT_WGSL, } f
3
3
  import { buildWindStreakLattice } from '../../windStreakDraw.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 wind-streak pass: a lattice of debris that follows the camera and wraps.
8
9
  *
@@ -141,7 +142,7 @@ export function windStreakPipeline(cache, device, layout) {
141
142
  dstFactor: 'one-minus-src-alpha',
142
143
  operation: 'add',
143
144
  },
144
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
145
+ alpha: SCENE_ALPHA_COVERS,
145
146
  },
146
147
  },
147
148
  ],
@@ -1,6 +1,7 @@
1
1
  import { compileProgram, uniformLocations } from './shader.js';
2
2
  import { BOLT_FRAG, BOLT_VERT } from './shaders/bolt.js';
3
3
  import { buildSegmentQuads, expandBoltSegments } from './segmentQuads.js';
4
+ import { blendKeeping } from './sceneCoverage.js';
4
5
  /**
5
6
  * One draw call for a pool of electrical arcs.
6
7
  *
@@ -80,7 +81,7 @@ export class BoltBatch {
80
81
  if (segmentCount === 0)
81
82
  return;
82
83
  gl.enable(gl.BLEND);
83
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
84
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
84
85
  gl.depthMask(false);
85
86
  gl.disable(gl.CULL_FACE);
86
87
  gl.bindVertexArray(this.vao);
@@ -3,6 +3,7 @@ import { CAUSTICS_FRAG, CAUSTICS_VERT } from './shaders/caustics.js';
3
3
  import { bindAtmosphere } from './atmosphere.js';
4
4
  import { buildSheets } from './surfaceSheet.js';
5
5
  import { seaStateForWind } from './seaState.js';
6
+ import { blendKeeping } from './sceneCoverage.js';
6
7
  /**
7
8
  * How far from the water the effect has died out.
8
9
  *
@@ -97,7 +98,7 @@ export class CausticsRenderer {
97
98
  gl.uniform1f(u['uWaveGain'] ?? null, sea.steepness);
98
99
  bindAtmosphere(gl, u, atmosphere, camera.position[1] ?? 0, underwaterEnabled);
99
100
  gl.enable(gl.BLEND);
100
- gl.blendFunc(gl.ONE, gl.ONE);
101
+ blendKeeping(gl, gl.ONE, gl.ONE);
101
102
  /*
102
103
  * No depth write, but depth *testing* stays on: the sheet sits a couple of
103
104
  * centimetres under the surface it lights, so geometry in front of it — the
@@ -0,0 +1,41 @@
1
+ /**
2
+ * A mesh's cloth binding as data, and the texel layouts both backends upload it in.
3
+ *
4
+ * **What a consumer hands over** is per render vertex: the simulation triangle it follows (three
5
+ * particle indices — the same index three times for a vertex that follows one particle), where on
6
+ * it (barycentric u and v for the second and third particle), how far along the triangle's normal,
7
+ * and how much it follows at all (0 skinned, 1 cloth — commonly painted into a vertex colour); and
8
+ * the particles' rest positions in the mesh's model space. A garment's port decodes all of this
9
+ * from the cooked data; the engine owns placing the vertices, in `shaders/clothBinding.ts`.
10
+ *
11
+ * **Textures wrap onto rows of at most 2048 texels**, the width WebGL2 guarantees, padded to whole
12
+ * rows: a garment of 40,000 vertices is 80,000 binding texels, 40 rows. Validated by name before a
13
+ * byte goes to a device, because an index past the particles reads the padding — a vertex pinned
14
+ * to the origin, which looks like a broken rig rather than a bad binding.
15
+ */
16
+ /** A mesh's cloth binding, one entry a render vertex unless said otherwise. */
17
+ export interface ClothBindingData {
18
+ /** Three particle indices a vertex. */
19
+ readonly triangles: Uint32Array;
20
+ /** Barycentric u and v a vertex, for the second and third particle; the first takes the rest. */
21
+ readonly coordinates: Float32Array;
22
+ /** Metres along the triangle's normal. */
23
+ readonly offsets: Float32Array;
24
+ /** How much of the cloth's place a vertex takes: 0 skinned, 1 cloth. */
25
+ readonly weights: Float32Array;
26
+ /** The particles at rest, three floats each, in the mesh's model space. */
27
+ readonly rest: Float32Array;
28
+ }
29
+ /** The widest row a texture is wrapped onto. */
30
+ export declare const CLOTH_TEXTURE_WIDTH = 2048;
31
+ /** A texture of `texels`, wrapped onto rows. */
32
+ export declare function clothTextureSize(texels: number): {
33
+ width: number;
34
+ height: number;
35
+ };
36
+ /** Refuse a binding whose arrays disagree with a mesh of `vertices`, or that names a missing particle. */
37
+ export declare function validateClothBinding(data: ClothBindingData, vertices: number): void;
38
+ /** The binding as RGBA float texels, two a vertex, padded to whole rows. */
39
+ export declare function packClothBinding(data: ClothBindingData): Float32Array;
40
+ /** Particles, three floats each, into RGBA texels in `out`. Allocation-free, for every frame. */
41
+ export declare function packClothParticles(positions: Float32Array, out: Float32Array): void;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * A mesh's cloth binding as data, and the texel layouts both backends upload it in.
3
+ *
4
+ * **What a consumer hands over** is per render vertex: the simulation triangle it follows (three
5
+ * particle indices — the same index three times for a vertex that follows one particle), where on
6
+ * it (barycentric u and v for the second and third particle), how far along the triangle's normal,
7
+ * and how much it follows at all (0 skinned, 1 cloth — commonly painted into a vertex colour); and
8
+ * the particles' rest positions in the mesh's model space. A garment's port decodes all of this
9
+ * from the cooked data; the engine owns placing the vertices, in `shaders/clothBinding.ts`.
10
+ *
11
+ * **Textures wrap onto rows of at most 2048 texels**, the width WebGL2 guarantees, padded to whole
12
+ * rows: a garment of 40,000 vertices is 80,000 binding texels, 40 rows. Validated by name before a
13
+ * byte goes to a device, because an index past the particles reads the padding — a vertex pinned
14
+ * to the origin, which looks like a broken rig rather than a bad binding.
15
+ */
16
+ /** The widest row a texture is wrapped onto. */
17
+ export const CLOTH_TEXTURE_WIDTH = 2048;
18
+ /** A texture of `texels`, wrapped onto rows. */
19
+ export function clothTextureSize(texels) {
20
+ const width = Math.max(1, Math.min(CLOTH_TEXTURE_WIDTH, texels));
21
+ return { width, height: Math.max(1, Math.ceil(texels / width)) };
22
+ }
23
+ /** Refuse a binding whose arrays disagree with a mesh of `vertices`, or that names a missing particle. */
24
+ export function validateClothBinding(data, vertices) {
25
+ const count = data.weights.length;
26
+ const fail = (what) => {
27
+ throw new Error(`cloth binding: ${what}`);
28
+ };
29
+ if (count !== vertices)
30
+ fail(`binds ${count} vertices and the mesh has ${vertices}`);
31
+ if (data.triangles.length !== count * 3)
32
+ fail(`${data.triangles.length / 3} triangles for ${count} vertices`);
33
+ if (data.coordinates.length !== count * 2)
34
+ fail(`${data.coordinates.length / 2} coordinates for ${count} vertices`);
35
+ if (data.offsets.length !== count)
36
+ fail(`${data.offsets.length} offsets for ${count} vertices`);
37
+ const particles = data.rest.length / 3;
38
+ if (!Number.isInteger(particles) || particles === 0)
39
+ fail(`rest holds ${data.rest.length} floats`);
40
+ for (let i = 0; i < data.triangles.length; i++) {
41
+ if (data.triangles[i] >= particles) {
42
+ fail(`vertex ${Math.floor(i / 3)} names particle ${data.triangles[i]} of ${particles}`);
43
+ }
44
+ }
45
+ }
46
+ /** The binding as RGBA float texels, two a vertex, padded to whole rows. */
47
+ export function packClothBinding(data) {
48
+ const vertices = data.weights.length;
49
+ const { width, height } = clothTextureSize(vertices * 2);
50
+ const out = new Float32Array(width * height * 4);
51
+ for (let v = 0; v < vertices; v++) {
52
+ const at = v * 8;
53
+ out[at] = data.triangles[v * 3];
54
+ out[at + 1] = data.triangles[v * 3 + 1];
55
+ out[at + 2] = data.triangles[v * 3 + 2];
56
+ out[at + 3] = data.weights[v];
57
+ out[at + 4] = data.coordinates[v * 2];
58
+ out[at + 5] = data.coordinates[v * 2 + 1];
59
+ out[at + 6] = data.offsets[v];
60
+ }
61
+ return out;
62
+ }
63
+ /** Particles, three floats each, into RGBA texels in `out`. Allocation-free, for every frame. */
64
+ export function packClothParticles(positions, out) {
65
+ const count = positions.length / 3;
66
+ for (let i = 0; i < count; i++) {
67
+ out[i * 4] = positions[i * 3];
68
+ out[i * 4 + 1] = positions[i * 3 + 1];
69
+ out[i * 4 + 2] = positions[i * 3 + 2];
70
+ out[i * 4 + 3] = 0;
71
+ }
72
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Where a cloth-bound vertex goes: the reference for what the vertex stage computes.
3
+ *
4
+ * A render vertex bound to a simulation triangle (particles a, b, c) sits at its barycentric point
5
+ * — `a·(1 − u − v) + b·u + c·v` — raised `offset` along the triangle's normal; its normal turns as
6
+ * the triangle turned since rest. The turn is the rotation from the triangle's rest frame
7
+ * to its frame now, each frame built from its first edge and its normal, so a vertex beside the
8
+ * triangle, a fold's width away, moves as the cloth moves rather than as its skeleton does.
9
+ *
10
+ * **A vertex bound to one particle** — all three indices equal — sits on it and keeps its normal:
11
+ * a single point has no frame to turn by. That is how a binding says "follow this particle".
12
+ *
13
+ * **A reference, as `recon/motionVectors.ts` is**: nothing at run time calls it. The vertex stage in
14
+ * `shaders/clothBinding.ts` and the cloth motion stage in `shaders/recon/motion.wgsl.ts` are the
15
+ * same arithmetic for the device, and nothing but a device holds them to this — the source tests
16
+ * pin where the shader moves a vertex, not the numbers it moves it by. `demo/dev/skinnedCloth.html`
17
+ * is where the two backends were compared, and they agree to two levels in 255.
18
+ */
19
+ /** Where a placement lands, written into caller-owned arrays. */
20
+ export interface ClothPlacement {
21
+ readonly position: Float64Array;
22
+ readonly normal: Float64Array;
23
+ }
24
+ /** Place one vertex: `now` and `rest` are particle positions, three floats each. */
25
+ export declare function clothPlace(now: ArrayLike<number>, rest: ArrayLike<number>, a: number, b: number, c: number, u: number, v: number, offset: number, restNormal: readonly [number, number, number], out: ClothPlacement): void;