@driftengine/core 4.9.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.json +133 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +7 -4
  4. package/dist/index.js +5 -4
  5. package/dist/render/ambientHarmonics.d.ts +1 -1
  6. package/dist/render/ambientHarmonics.js +1 -1
  7. package/dist/render/areaLights.d.ts +4 -4
  8. package/dist/render/areaLights.js +1 -1
  9. package/dist/render/backend/api.d.ts +10 -3
  10. package/dist/render/backend/select.js +11 -5
  11. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  12. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  13. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  14. package/dist/render/backend/webgl2/instanced.js +13 -1
  15. package/dist/render/backend/webgl2/renderer.d.ts +189 -4
  16. package/dist/render/backend/webgl2/renderer.js +614 -66
  17. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  18. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  19. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  20. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  21. package/dist/render/backend/webgpu/buffers.d.ts +24 -4
  22. package/dist/render/backend/webgpu/buffers.js +116 -59
  23. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  24. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  25. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  26. package/dist/render/backend/webgpu/clothOps.js +78 -0
  27. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  28. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  29. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  30. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  31. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  32. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  33. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  34. package/dist/render/backend/webgpu/depthPass.js +49 -14
  35. package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
  36. package/dist/render/backend/webgpu/drawCommand.js +11 -3
  37. package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
  38. package/dist/render/backend/webgpu/flatPass.js +137 -20
  39. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  40. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  41. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  42. package/dist/render/backend/webgpu/instanced.js +24 -1
  43. package/dist/render/backend/webgpu/latePass.js +1 -0
  44. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  45. package/dist/render/backend/webgpu/materialStore.js +265 -0
  46. package/dist/render/backend/webgpu/motionPass.js +15 -6
  47. package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
  48. package/dist/render/backend/webgpu/pipelineCache.js +46 -1
  49. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  50. package/dist/render/backend/webgpu/renderer.d.ts +200 -35
  51. package/dist/render/backend/webgpu/renderer.js +993 -168
  52. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  53. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  54. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  55. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  56. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  57. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  58. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  59. package/dist/render/backend/webgpu/staticBundles.js +242 -0
  60. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  61. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  62. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  63. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  64. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  65. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  66. package/dist/render/bloomChain.d.ts +3 -3
  67. package/dist/render/boneAnimation.d.ts +92 -0
  68. package/dist/render/boneAnimation.js +133 -0
  69. package/dist/render/compressedSource.d.ts +33 -13
  70. package/dist/render/compressedSource.js +70 -25
  71. package/dist/render/glCompressed.js +83 -0
  72. package/dist/render/globalMediumPass.d.ts +2 -0
  73. package/dist/render/globalMediumPass.js +1 -0
  74. package/dist/render/indexWidth.d.ts +17 -0
  75. package/dist/render/indexWidth.js +23 -0
  76. package/dist/render/instances.d.ts +26 -0
  77. package/dist/render/lightBudget.d.ts +10 -0
  78. package/dist/render/lightBudget.js +10 -0
  79. package/dist/render/lightMatrix.d.ts +11 -0
  80. package/dist/render/lightMatrix.js +14 -0
  81. package/dist/render/lightmap.d.ts +1 -1
  82. package/dist/render/mesh.d.ts +14 -0
  83. package/dist/render/mesh.js +44 -14
  84. package/dist/render/pointLightSelection.d.ts +4 -4
  85. package/dist/render/renderQuality.d.ts +30 -0
  86. package/dist/render/renderQuality.js +5 -0
  87. package/dist/render/rendererHarness.d.ts +7 -0
  88. package/dist/render/rendererHarness.js +12 -0
  89. package/dist/render/screenSpaceReflection.d.ts +2 -1
  90. package/dist/render/shader.d.ts +15 -0
  91. package/dist/render/shader.js +41 -0
  92. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  93. package/dist/render/shaders/boneAnimation.js +63 -0
  94. package/dist/render/shaders/depth.d.ts +7 -0
  95. package/dist/render/shaders/depth.js +35 -10
  96. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  97. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  98. package/dist/render/shaders/flat/glassTint.js +3 -0
  99. package/dist/render/shaders/flat/index.d.ts +21 -0
  100. package/dist/render/shaders/flat/index.js +42 -7
  101. package/dist/render/shaders/flat/layered.d.ts +19 -0
  102. package/dist/render/shaders/flat/layered.js +115 -0
  103. package/dist/render/shaders/flat/main.d.ts +1 -1
  104. package/dist/render/shaders/flat/main.js +58 -21
  105. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  106. package/dist/render/shaders/flat/mapReads.js +38 -0
  107. package/dist/render/shaders/flat/models.js +1 -1
  108. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  109. package/dist/render/shaders/flat/movingSun.js +46 -0
  110. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  111. package/dist/render/shaders/flat/normalMap.js +14 -3
  112. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  113. package/dist/render/shaders/flat/overlay.js +42 -20
  114. package/dist/render/shaders/flat/pointShadow.js +2 -2
  115. package/dist/render/shaders/flat/preamble.js +59 -27
  116. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  117. package/dist/render/shaders/flat/worldUv.js +113 -0
  118. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  119. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  120. package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
  121. package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
  122. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  123. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  124. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  125. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  126. package/dist/render/shaders/globalMedium.js +9 -1
  127. package/dist/render/shaders/lightVolume.js +9 -1
  128. package/dist/render/shaders/outputTransform.d.ts +7 -0
  129. package/dist/render/shaders/outputTransform.js +28 -14
  130. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  131. package/dist/render/shaders/tangentFrame.js +16 -3
  132. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  133. package/dist/render/shaders/vertexChannel.js +26 -0
  134. package/dist/render/skinnedClothRun.d.ts +28 -0
  135. package/dist/render/skinnedClothRun.js +103 -35
  136. package/dist/render/staticDraws.d.ts +47 -0
  137. package/dist/render/staticDraws.js +43 -0
  138. package/dist/render/surfaceLayers.d.ts +39 -0
  139. package/dist/render/surfaceLayers.js +26 -0
  140. package/dist/render/surfaceOverlay.d.ts +16 -3
  141. package/dist/render/surfaceOverlay.js +11 -6
  142. package/dist/render/surfaceProjection.d.ts +34 -0
  143. package/dist/render/surfaceProjection.js +30 -0
  144. package/dist/render/surfaceTexture.d.ts +68 -7
  145. package/dist/render/surfaceTexture.js +56 -17
  146. package/dist/render/vertexPacking.d.ts +48 -0
  147. package/dist/render/vertexPacking.js +68 -0
  148. package/package.json +6 -6
  149. package/src/index.ts +12 -4
  150. package/src/render/ambientHarmonics.ts +1 -1
  151. package/src/render/areaLights.ts +4 -4
  152. package/src/render/backend/api.ts +13 -3
  153. package/src/render/backend/select.ts +10 -5
  154. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  155. package/src/render/backend/webgl2/instanced.ts +20 -1
  156. package/src/render/backend/webgl2/renderer.ts +723 -79
  157. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  158. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  159. package/src/render/backend/webgpu/buffers.ts +195 -70
  160. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  161. package/src/render/backend/webgpu/clothOps.ts +86 -0
  162. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  163. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  164. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  165. package/src/render/backend/webgpu/depthPass.ts +51 -10
  166. package/src/render/backend/webgpu/drawCommand.ts +25 -4
  167. package/src/render/backend/webgpu/flatPass.ts +212 -41
  168. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  169. package/src/render/backend/webgpu/instanced.ts +32 -1
  170. package/src/render/backend/webgpu/latePass.ts +1 -0
  171. package/src/render/backend/webgpu/materialStore.ts +276 -0
  172. package/src/render/backend/webgpu/motionPass.ts +18 -7
  173. package/src/render/backend/webgpu/pipelineCache.ts +53 -7
  174. package/src/render/backend/webgpu/renderer.ts +1201 -202
  175. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  176. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  177. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  178. package/src/render/backend/webgpu/staticBundles.ts +364 -0
  179. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  180. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  181. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  182. package/src/render/bloomChain.ts +3 -3
  183. package/src/render/boneAnimation.ts +223 -0
  184. package/src/render/compressedSource.ts +121 -29
  185. package/src/render/glCompressed.ts +83 -0
  186. package/src/render/globalMediumPass.ts +7 -0
  187. package/src/render/indexWidth.ts +27 -0
  188. package/src/render/instances.ts +27 -0
  189. package/src/render/lightBudget.ts +11 -0
  190. package/src/render/lightMatrix.ts +15 -0
  191. package/src/render/lightmap.ts +1 -1
  192. package/src/render/mesh.ts +72 -14
  193. package/src/render/pointLightSelection.ts +4 -4
  194. package/src/render/renderQuality.ts +38 -0
  195. package/src/render/rendererHarness.ts +15 -0
  196. package/src/render/screenSpaceReflection.ts +2 -1
  197. package/src/render/shader.ts +59 -0
  198. package/src/render/shaders/boneAnimation.ts +63 -0
  199. package/src/render/shaders/depth.ts +45 -10
  200. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  201. package/src/render/shaders/flat/glassTint.ts +7 -1
  202. package/src/render/shaders/flat/index.ts +63 -7
  203. package/src/render/shaders/flat/layered.ts +115 -0
  204. package/src/render/shaders/flat/main.ts +58 -21
  205. package/src/render/shaders/flat/mapReads.ts +38 -0
  206. package/src/render/shaders/flat/models.ts +1 -1
  207. package/src/render/shaders/flat/movingSun.ts +46 -0
  208. package/src/render/shaders/flat/normalMap.ts +14 -3
  209. package/src/render/shaders/flat/overlay.ts +42 -20
  210. package/src/render/shaders/flat/pointShadow.ts +2 -2
  211. package/src/render/shaders/flat/preamble.ts +59 -27
  212. package/src/render/shaders/flat/worldUv.ts +113 -0
  213. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  214. package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
  215. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  216. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  217. package/src/render/shaders/globalMedium.ts +9 -1
  218. package/src/render/shaders/lightVolume.ts +9 -1
  219. package/src/render/shaders/outputTransform.ts +30 -14
  220. package/src/render/shaders/tangentFrame.ts +16 -3
  221. package/src/render/shaders/vertexChannel.ts +27 -0
  222. package/src/render/skinnedClothRun.ts +138 -38
  223. package/src/render/staticDraws.ts +95 -0
  224. package/src/render/surfaceLayers.ts +57 -0
  225. package/src/render/surfaceOverlay.ts +24 -7
  226. package/src/render/surfaceProjection.ts +57 -0
  227. package/src/render/surfaceTexture.ts +124 -27
  228. package/src/render/vertexPacking.ts +109 -0
  229. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  230. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  231. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -11,6 +11,10 @@
11
11
  * millisecond a frame on the main thread, and 4,500 are twenty — a garment that size wants the
12
12
  * device. The device solver ran 4,500 particles in 0.65 ms a frame, most of it the dispatches
13
13
  * rather than the particles (`demo/dev/skinnedCloth.html?bench=1`).
14
+ *
15
+ * **Several garments step as one with `createSkinnedClothSet`**: one pass, one submit and one
16
+ * garment's dispatches a step for all of them, where a garment each was a pass, a submit and its
17
+ * own dispatches each. They must share a step, substeps, iterations and most steps a frame.
14
18
  */
15
19
  import { SkinnedCloth } from '@driftengine/physics';
16
20
  import type { SkinnedClothSetup } from '@driftengine/physics';
@@ -18,7 +22,7 @@ import type { SkinnedClothSetup } from '@driftengine/physics';
18
22
  import type { ClothParticlesHandle, RendererApi } from './backend/api.ts';
19
23
  import { computeHostOf } from './backend/webgpu/computeHost.ts';
20
24
  import type { ComputeHost } from './backend/webgpu/computeHost.ts';
21
- import { GpuSkinnedCloth } from './backend/webgpu/gpuSkinnedCloth.ts';
25
+ import { GpuClothSet } from './backend/webgpu/gpuClothSet.ts';
22
26
 
23
27
  /** A garment a renderer solves, from `createSkinnedCloth`. */
24
28
  export interface SkinnedClothSolver {
@@ -43,86 +47,182 @@ export interface SkinnedClothSolver {
43
47
  dispose(): void;
44
48
  }
45
49
 
50
+ /** Garments a renderer solves together, from `createSkinnedClothSet`, each named by its index. */
51
+ export interface SkinnedClothSet {
52
+ /** Each garment's particles, in the order its set-up was given: what `setCloth` draws it by. */
53
+ readonly particles: readonly ClothParticlesHandle[];
54
+ /** Where the set runs: on the device, or on the CPU beneath a backend without compute. */
55
+ readonly runsOn: 'device' | 'cpu';
56
+ /**
57
+ * Garment `g`'s rig for the next `step`: each joint's global matrix, model space, sixteen floats a
58
+ * joint, and the model matrix. A garment not posed in a frame steps toward its last pose.
59
+ */
60
+ setPose(g: number, globals: Float32Array, model: Float32Array): void;
61
+ /** The air garment `g` drags toward from its next step: the frame's sample of the one wind. */
62
+ setWind(g: number, x: number, y: number, z: number): void;
63
+ /** Garment `g` back to its skinned pose, settled, as a teleport resets it. */
64
+ reset(g: number): void;
65
+ /**
66
+ * `dt` seconds for every garment, run as whole fixed steps; each garment's particles at its
67
+ * `alpha` are then what the frame draws. **Once a frame, before the draws**, as a solver's `step`.
68
+ */
69
+ step(dt: number): void;
70
+ dispose(): void;
71
+ }
72
+
46
73
  /** A garment for `renderer`, from a set-up checked by name. */
47
74
  export function createSkinnedCloth(
48
75
  renderer: RendererApi,
49
76
  setup: SkinnedClothSetup,
50
77
  ): SkinnedClothSolver {
51
78
  const host = computeHostOf(renderer);
52
- return host === null ? new CpuCloth(renderer, setup) : new DeviceCloth(host, setup);
79
+ const set =
80
+ host === null ? new CpuClothSet(renderer, [setup]) : new DeviceClothSet(host, [setup]);
81
+ return new OneGarment(set);
53
82
  }
54
83
 
55
- class DeviceCloth implements SkinnedClothSolver {
56
- readonly runsOn = 'device';
84
+ /** A set of one, as a solver: what `createSkinnedCloth` hands back. */
85
+ class OneGarment implements SkinnedClothSolver {
57
86
  readonly particles: ClothParticlesHandle;
58
- private readonly cloth: GpuSkinnedCloth;
87
+ readonly runsOn: 'device' | 'cpu';
88
+
89
+ constructor(private readonly set: DeviceClothSet | CpuClothSet) {
90
+ this.particles = set.particles[0] as ClothParticlesHandle;
91
+ this.runsOn = set.runsOn;
92
+ }
93
+
94
+ step(globals: Float32Array, model: Float32Array, dt: number): void {
95
+ this.set.setPose(0, globals, model);
96
+ this.set.step(dt);
97
+ }
98
+
99
+ setWind(x: number, y: number, z: number): void {
100
+ this.set.setWind(0, x, y, z);
101
+ }
102
+
103
+ reset(): void {
104
+ this.set.reset(0);
105
+ }
106
+
107
+ dispose(): void {
108
+ this.set.dispose();
109
+ }
110
+
111
+ /** The particles where the last step left them: the parity check's door. */
112
+ read(): Promise<Float32Array> {
113
+ return this.set.read(0);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Garments for `renderer` stepped together, from set-ups checked by name: one pass, one submit and
119
+ * one garment's dispatches a step on the device, whatever the count. They must agree on `step`,
120
+ * `substeps`, `iterations` and `maxSteps`, and are refused by the first they disagree on.
121
+ */
122
+ export function createSkinnedClothSet(
123
+ renderer: RendererApi,
124
+ setups: readonly SkinnedClothSetup[],
125
+ ): SkinnedClothSet {
126
+ const host = computeHostOf(renderer);
127
+ return host === null ? new CpuClothSet(renderer, setups) : new DeviceClothSet(host, setups);
128
+ }
129
+
130
+ class DeviceClothSet implements SkinnedClothSet {
131
+ readonly runsOn = 'device';
132
+ readonly particles: readonly ClothParticlesHandle[];
133
+ private readonly set: GpuClothSet;
59
134
 
60
135
  constructor(
61
136
  private readonly host: ComputeHost,
62
- setup: SkinnedClothSetup,
137
+ setups: readonly SkinnedClothSetup[],
63
138
  ) {
64
- this.cloth = new GpuSkinnedCloth(host.device(), setup);
65
- this.particles = this.cloth.particles;
139
+ this.set = new GpuClothSet(host.device(), setups);
140
+ const particles: ClothParticlesHandle[] = [];
141
+ for (let g = 0; g < this.set.size; g++) particles.push(this.set.particles(g));
142
+ this.particles = particles;
66
143
  }
67
144
 
68
- step(globals: Float32Array, model: Float32Array, dt: number): void {
145
+ setPose(g: number, globals: Float32Array, model: Float32Array): void {
69
146
  if (this.host.lost()) return;
70
- this.cloth.advance(globals, model, dt, this.host.changeFrame());
147
+ this.set.setPose(g, globals, model);
71
148
  }
72
149
 
73
- setWind(x: number, y: number, z: number): void {
74
- this.cloth.setWind(x, y, z);
150
+ setWind(g: number, x: number, y: number, z: number): void {
151
+ this.set.setWind(g, x, y, z);
75
152
  }
76
153
 
77
- reset(): void {
154
+ reset(g: number): void {
78
155
  if (this.host.lost()) return;
79
- this.cloth.reset();
156
+ this.set.reset(g);
157
+ }
158
+
159
+ step(dt: number): void {
160
+ if (this.host.lost()) return;
161
+ this.set.step(dt, this.host.changeFrame());
80
162
  }
81
163
 
82
164
  /** Its buffers at once — its work is submitted — and its textures with the frame's. */
83
165
  dispose(): void {
84
- this.cloth.dispose();
85
- this.host.retire(this.cloth.particles.textures);
166
+ this.set.dispose();
167
+ for (let g = 0; g < this.set.size; g++) this.host.retire(this.set.particles(g).textures);
86
168
  }
87
169
 
88
- /** The particles where the last step left them: the parity check's door, waits on the device. */
89
- read(): Promise<Float32Array> {
90
- return this.cloth.read();
170
+ /** Garment `g`'s particles where its last step left them: the parity check's, waits on the device. */
171
+ read(g: number): Promise<Float32Array> {
172
+ return this.set.read(g);
91
173
  }
92
174
  }
93
175
 
94
- class CpuCloth implements SkinnedClothSolver {
176
+ class CpuClothSet implements SkinnedClothSet {
95
177
  readonly runsOn = 'cpu';
96
- readonly particles: ClothParticlesHandle;
97
- private readonly cloth: SkinnedCloth;
98
- /** The frame's particles at `alpha`, before they go up. */
99
- private readonly staging: Float32Array;
178
+ readonly particles: readonly ClothParticlesHandle[];
179
+ private readonly cloths: readonly SkinnedCloth[];
180
+ /** Each garment's particles at `alpha`, before they go up. */
181
+ private readonly staging: readonly Float32Array[];
100
182
 
101
183
  constructor(
102
184
  private readonly renderer: RendererApi,
103
- setup: SkinnedClothSetup,
185
+ setups: readonly SkinnedClothSetup[],
104
186
  ) {
105
- this.cloth = new SkinnedCloth(setup);
106
- this.particles = renderer.createClothParticles(this.cloth.count);
107
- this.staging = new Float32Array(this.cloth.count * 3);
187
+ this.cloths = setups.map((setup) => new SkinnedCloth(setup));
188
+ this.particles = this.cloths.map((cloth) => renderer.createClothParticles(cloth.count));
189
+ this.staging = this.cloths.map((cloth) => new Float32Array(cloth.count * 3));
108
190
  }
109
191
 
110
- step(globals: Float32Array, model: Float32Array, dt: number): void {
111
- this.cloth.setPose(globals, model);
112
- this.cloth.advance(dt);
113
- this.cloth.interpolate(this.cloth.alpha, this.staging);
114
- this.renderer.updateClothParticles(this.particles, this.staging);
192
+ setPose(g: number, globals: Float32Array, model: Float32Array): void {
193
+ this.at(g).setPose(globals, model);
115
194
  }
116
195
 
117
- setWind(x: number, y: number, z: number): void {
118
- this.cloth.setWind(x, y, z);
196
+ setWind(g: number, x: number, y: number, z: number): void {
197
+ this.at(g).setWind(x, y, z);
119
198
  }
120
199
 
121
- reset(): void {
122
- this.cloth.reset();
200
+ reset(g: number): void {
201
+ this.at(g).reset();
202
+ }
203
+
204
+ step(dt: number): void {
205
+ for (let g = 0; g < this.cloths.length; g++) {
206
+ const cloth = this.cloths[g] as SkinnedCloth;
207
+ const staging = this.staging[g] as Float32Array;
208
+ cloth.advance(dt);
209
+ cloth.interpolate(cloth.alpha, staging);
210
+ this.renderer.updateClothParticles(this.particles[g] as ClothParticlesHandle, staging);
211
+ }
123
212
  }
124
213
 
125
214
  dispose(): void {
126
- this.renderer.disposeClothParticles(this.particles);
215
+ for (const particles of this.particles) this.renderer.disposeClothParticles(particles);
216
+ }
217
+
218
+ /** Garment `g`'s particles where its last step left them, as the device set reads its own. */
219
+ read(g: number): Promise<Float32Array> {
220
+ return Promise.resolve(new Float32Array(this.at(g).positions));
221
+ }
222
+
223
+ private at(g: number): SkinnedCloth {
224
+ const cloth = this.cloths[g];
225
+ if (cloth === undefined) throw new Error(`skinned cloth set: no garment ${g}`);
226
+ return cloth;
127
227
  }
128
228
  }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Draws that do not change between frames, captured once from an enumeration and replayed into any
3
+ * colour pass: what `createStaticDraws` keeps, and the one rule both backends replay it by.
4
+ *
5
+ * **Captured, not referenced.** An enumeration is a closure over the caller's state, written to
6
+ * allocate nothing, so it hands a sink its scratch: the matrix it fills for every entry and the
7
+ * count it rewrites for the next batch. A list kept across frames copies both when it is made, so
8
+ * what it draws is what the enumeration said then. Meshes, batches and materials are held by
9
+ * reference: they are the caller's, and disposing one a list still holds is the caller's error.
10
+ *
11
+ * **Rigid meshes and instanced batches only.** A skinned mesh is posed every frame and a scatter
12
+ * batch bends with every frame's wind, so neither can be kept, and both are refused by name rather
13
+ * than captured in the pose they happened to have. A caller draws them each frame as it always has.
14
+ *
15
+ * What a backend does with the list is its own: WebGL2 replays the entries every frame through
16
+ * the scene-caster path, and WebGPU records them once into a render bundle per view. See
17
+ * `RendererApi.createStaticDraws` for what a caller is promised, which is the same on both.
18
+ */
19
+ import type { InstancedHandle, MeshHandle } from './backend/api.ts';
20
+ import type { MeshInstances } from './instances.ts';
21
+ import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from './shadowCasters.ts';
22
+
23
+ /** A list from `createStaticDraws`. Opaque but for its size. */
24
+ export interface StaticDrawsHandle {
25
+ /** How many entries it holds: meshes and batches, a batch counting once. */
26
+ readonly draws: number;
27
+ }
28
+
29
+ /** One captured entry. */
30
+ export type StaticEntry =
31
+ | {
32
+ readonly kind: 'mesh';
33
+ readonly mesh: MeshHandle;
34
+ readonly model: Float32Array;
35
+ readonly material: SceneCasterMaterial;
36
+ }
37
+ | {
38
+ readonly kind: 'instanced';
39
+ readonly batch: InstancedHandle;
40
+ readonly data: MeshInstances;
41
+ readonly material: SceneCasterMaterial;
42
+ };
43
+
44
+ /** What both backends hold: the entries, and whether the list has been disposed. */
45
+ export interface StaticDrawsList extends StaticDrawsHandle {
46
+ readonly entries: readonly StaticEntry[];
47
+ disposed: boolean;
48
+ }
49
+
50
+ /** Enumerate `casters` once into a list. Allocates: it runs when the list is made, not per frame. */
51
+ export function captureStaticDraws(casters: ShadowCasters): StaticDrawsList {
52
+ const entries: StaticEntry[] = [];
53
+ const sink: ShadowCasterSink = {
54
+ mesh: (mesh, model, material) => {
55
+ entries.push({
56
+ kind: 'mesh',
57
+ mesh,
58
+ model: new Float32Array(model as Float32Array),
59
+ material: material ?? null,
60
+ });
61
+ },
62
+ instanced: (batch, data, material) => {
63
+ /* The batch's placements are on the device already; the count is the caller's scratch. */
64
+ entries.push({ kind: 'instanced', batch, data: { ...data }, material: material ?? null });
65
+ },
66
+ skinnedMesh: () => {
67
+ throw new Error(
68
+ 'createStaticDraws: a skinned mesh is posed every frame, so a list kept across frames ' +
69
+ 'cannot hold one. Draw it each frame, with drawMesh or drawSceneCasters.',
70
+ );
71
+ },
72
+ scatter: () => {
73
+ throw new Error(
74
+ 'createStaticDraws: a scatter batch bends with every frame’s wind, so a list kept across ' +
75
+ 'frames cannot hold one. Draw it each frame with drawScatter.',
76
+ );
77
+ },
78
+ };
79
+ casters(sink);
80
+ return {
81
+ entries,
82
+ get draws() {
83
+ return entries.length;
84
+ },
85
+ disposed: false,
86
+ };
87
+ }
88
+
89
+ /** Hand every entry to `sink`, in the order it was captured. */
90
+ export function replayStaticDraws(list: StaticDrawsList, sink: ShadowCasterSink): void {
91
+ for (const entry of list.entries) {
92
+ if (entry.kind === 'mesh') sink.mesh(entry.mesh, entry.model, entry.material);
93
+ else sink.instanced?.(entry.batch, entry.data, entry.material);
94
+ }
95
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * A material blended from layers by a mask: `SurfaceMaterial.layers`, and the two vectors
3
+ * `shaders/flat/layered.ts` reads it from.
4
+ */
5
+
6
+ /** The most layers a material blends: a base and one for each of the mask's four channels. */
7
+ export const MAX_SURFACE_LAYERS = 5;
8
+
9
+ /**
10
+ * Up to five layers, each its own colour, normal and roughness at its own repeat, blended by a mask.
11
+ *
12
+ * **Layer `i` is layer `i` of the material's arrays**: `albedo`, `normal` and `orm` each an array
13
+ * whose layers are the material's layers in order, read at the mesh's coordinates times
14
+ * `repeats[i]`, so rock can repeat forty times across a cliff while the moss on it repeats three.
15
+ * **The mask** is read at the mesh's own coordinates and lays each layer over those before it: red
16
+ * lays layer 1 over the base, green layer 2 over that, blue layer 3, alpha layer 4. Its layer 0 is
17
+ * read, from where a shading model's map goes, which is why a layered material carries no
18
+ * `modelMap`. `emissiveLayer` gives the material's emissive map to one layer, glowing where that
19
+ * layer shows and nowhere it is covered; absent, the map glows as on any material.
20
+ *
21
+ * **What it gives up**: every layer's maps are read wherever any shows, three reads a layer, so five
22
+ * layers are fifteen reads where one material is three; layers share an array's size and format; a
23
+ * cutout reads the base layer's alpha; and the mask is sampled linear, so it is uploaded as data
24
+ * (`colorSpace: 'linear'`).
25
+ */
26
+ export interface SurfaceLayers<Texture> {
27
+ /** The weights, red to alpha laying layers 1 to 4, read at the mesh's coordinates. */
28
+ readonly mask: Texture;
29
+ /** Each layer's repeats across the mesh's coordinates, base first: one to five of them. */
30
+ readonly repeats: readonly number[];
31
+ /** The layer the emissive map glows on, 0 to 4. Absent: everywhere, as on any material. */
32
+ readonly emissiveLayer?: number;
33
+ }
34
+
35
+ /** Floats `packSurfaceLayers` writes: two vectors. */
36
+ export const LAYER_FLOATS = 8;
37
+
38
+ /**
39
+ * `layers` into `out`: the five repeats (1 for a layer not given), then how many layers there are
40
+ * and which one glows (−1 for none); zeros, no layers, where there are none. A repeat that is not a
41
+ * positive number is 1, so a layer is never laid at a scale that collapses it to a point.
42
+ */
43
+ export function packSurfaceLayers<Texture>(
44
+ layers: SurfaceLayers<Texture> | null | undefined,
45
+ out: Float32Array,
46
+ ): void {
47
+ out.fill(0);
48
+ if (layers === null || layers === undefined || layers.repeats.length === 0) return;
49
+ const count = Math.min(layers.repeats.length, MAX_SURFACE_LAYERS);
50
+ for (let i = 0; i < MAX_SURFACE_LAYERS; i++) {
51
+ const repeat = i < count ? (layers.repeats[i] as number) : 1;
52
+ out[i] = Number.isFinite(repeat) && repeat > 0 ? repeat : 1;
53
+ }
54
+ out[5] = count;
55
+ const glow = layers.emissiveLayer;
56
+ out[6] = glow !== undefined && Number.isInteger(glow) && glow >= 0 && glow < count ? glow : -1;
57
+ }
@@ -29,12 +29,24 @@ export interface OverlayRegion {
29
29
  * raised to a contrast, times a noise laid in screen space, a slow pulse and a mask, in a colour.
30
30
  * Added to what the surface emits, and not gated on the night as emission is: it is a glow the
31
31
  * surface wears, not light it gives off.
32
+ *
33
+ * **Or, with `mode: 'blend'`, a colour the surface is pulled toward**, lit like the rest of it: the
34
+ * base colour mixed toward the rim's by the edge times `alpha`, before any light reads it, so the
35
+ * rim is bright where light falls and dark where none does. The colour is `colour` times
36
+ * `intensity`, each channel held at 1 as a base colour is, so a colour of 8 red is a red of 1 and
37
+ * not a glow; `alpha` is the weight, which may pass 1 to saturate the edge sooner.
32
38
  */
33
39
  export interface SurfaceRim {
34
40
  readonly colour: Vec3;
35
- /** How bright, 0 for none. Multiplied with `alpha`. */
41
+ /**
42
+ * How bright, 0 for none, multiplied with `alpha`. Where the rim blends it scales `colour`
43
+ * instead, so 0 there is a rim of black, and `alpha` alone says how much: 0 for none.
44
+ */
36
45
  readonly intensity: number;
46
+ /** The rim's weight, 1 unless given. */
37
47
  readonly alpha?: number;
48
+ /** `'add'`, the default, lays the rim on as glow; `'blend'` pulls the base colour toward it, lit. */
49
+ readonly mode?: 'add' | 'blend';
38
50
  /** The edge's exponent: larger keeps the rim nearer the silhouette. 1.5 unless given. */
39
51
  readonly falloff?: number;
40
52
  /**
@@ -122,10 +134,11 @@ function region(out: Float32Array, at: number, r: OverlayRegion | undefined | nu
122
134
  * no overlay. A region with a zero scale is an image not given, and its term reads as none — a
123
135
  * noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
124
136
  *
125
- * - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
137
+ * - 0: rim colour, rim strength (intensity times alpha; 0 is no rim). A blending rim: the colour
138
+ * times intensity, each channel held at 1, and the alpha alone
126
139
  * - 1: falloff, upward lean, contrast, mask weight
127
140
  * - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
128
- * - 4: the pulse's low and high (1 and 1 where none)
141
+ * - 4: the pulse's low and high (1 and 1 where none), and the mode: 0 adds, 1 blends
129
142
  * - 5: the rim's mask region
130
143
  * - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
131
144
  * - 8: the edge's light and its strength; 9: the laid-over colour and its amount
@@ -138,10 +151,14 @@ export function packSurfaceOverlay(overlay: SurfaceOverlay | null, out: Float32A
138
151
  const images = overlay.maps !== undefined;
139
152
  const rim = overlay.rim;
140
153
  if (rim !== undefined) {
141
- out[0] = finite(rim.colour[0], 0);
142
- out[1] = finite(rim.colour[1], 0);
143
- out[2] = finite(rim.colour[2], 0);
144
- out[3] = Math.max(0, finite(rim.intensity, 0) * finite(rim.alpha, 1));
154
+ const blend = rim.mode === 'blend';
155
+ const scale = blend ? finite(rim.intensity, 0) : 1;
156
+ for (let c = 0; c < 3; c++) {
157
+ const channel = finite(rim.colour[c], 0) * scale;
158
+ out[c] = blend ? Math.min(Math.max(channel, 0), 1) : channel;
159
+ }
160
+ out[3] = Math.max(0, (blend ? 1 : finite(rim.intensity, 0)) * finite(rim.alpha, 1));
161
+ out[18] = blend ? 1 : 0;
145
162
  out[4] = Math.max(0, finite(rim.falloff, 1.5));
146
163
  out[5] = Math.max(0, finite(rim.upward, 0.4));
147
164
  out[6] = Math.max(0, finite(rim.contrast, 1));
@@ -0,0 +1,57 @@
1
+ /**
2
+ * A material's maps placed by where a point is in the world: `SurfaceMaterial.projection`, and the
3
+ * one vector `shaders/flat/worldUv.ts` reads it from.
4
+ */
5
+
6
+ /**
7
+ * Texture coordinates from the world rather than from the mesh.
8
+ *
9
+ * **`'planar'`** lays the maps across the two horizontal axes, `x` and `z`: ground whose mesh
10
+ * carries flat coordinates, or none worth reading, shows its texture at `scale` repeats a metre.
11
+ * **`'triplanar'`** reads each map on the three planes across `x`, `y` and `z` and blends them by
12
+ * how squarely the surface faces each, the normal's components raised to `sharpness`, so walls and
13
+ * rock wear a texture without stretching. See `worldUv.ts` for the normal map's blend.
14
+ *
15
+ * **What it gives up**: the world's coordinates do not move with a mesh, so a moving mesh slides
16
+ * under its texture; triplanar reads every map three times; and the material's `uScale`, `vScale`
17
+ * and offsets, which are the mesh's coordinates', do not apply.
18
+ */
19
+ export interface SurfaceProjection {
20
+ readonly kind: 'planar' | 'triplanar';
21
+ /** Repeats a metre: 2 lays a texture every half metre. */
22
+ readonly scale: number;
23
+ /** How sharply triplanar's planes meet: the exponent on the normal. 4 unless given. */
24
+ readonly sharpness?: number;
25
+ }
26
+
27
+ /** Floats `packSurfaceProjection` writes: one vector. */
28
+ export const PROJECTION_FLOATS = 4;
29
+
30
+ /**
31
+ * `projection` into `out`: its kind (0 none, 1 planar, 2 triplanar), its repeats a metre and its
32
+ * sharpness; zeros for none. A scale that is not finite and above zero projects nothing, so a
33
+ * material is never drawn at a scale that collapses its texture to a point.
34
+ */
35
+ export function packSurfaceProjection(
36
+ projection: SurfaceProjection | null | undefined,
37
+ out: Float32Array,
38
+ ): void {
39
+ out.fill(0);
40
+ if (projection === null || projection === undefined) return;
41
+ const scale = projection.scale;
42
+ if (!Number.isFinite(scale) || scale <= 0) return;
43
+ out[0] = projection.kind === 'triplanar' ? 2 : 1;
44
+ out[1] = scale;
45
+ const sharpness = projection.sharpness ?? 4;
46
+ out[2] = Number.isFinite(sharpness) ? Math.min(Math.max(sharpness, 1), 64) : 4;
47
+ }
48
+
49
+ /** Whether a material projects its maps at all. */
50
+ export function projects(projection: SurfaceProjection | null | undefined): boolean {
51
+ return (
52
+ projection !== null &&
53
+ projection !== undefined &&
54
+ Number.isFinite(projection.scale) &&
55
+ projection.scale > 0
56
+ );
57
+ }