@driftengine/core 4.10.0 → 4.11.1
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.
- package/CHANGELOG.json +107 -0
- package/README.md +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2 -2
- package/dist/render/ambientOcclusionPass.d.ts +3 -2
- package/dist/render/ambientOcclusionPass.js +4 -2
- package/dist/render/backend/api.d.ts +21 -1
- package/dist/render/backend/webgl2/renderer.d.ts +141 -3
- package/dist/render/backend/webgl2/renderer.js +454 -42
- package/dist/render/backend/webgpu/buffers.d.ts +5 -0
- package/dist/render/backend/webgpu/buffers.js +98 -48
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
- package/dist/render/backend/webgpu/flatPass.js +2 -1
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
- package/dist/render/backend/webgpu/pipelineCache.js +39 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -1
- package/dist/render/backend/webgpu/renderer.js +423 -95
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- package/dist/render/backend/webgpu/staticBundles.js +2 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -0
- package/dist/render/instances.d.ts +13 -1
- package/dist/render/instances.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/mesh.d.ts +9 -0
- package/dist/render/mesh.js +38 -14
- package/dist/render/occlusionFade.d.ts +21 -0
- package/dist/render/occlusionFade.js +27 -0
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/ambientOcclusion.js +12 -1
- package/dist/render/shaders/flat/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +24 -4
- package/dist/render/shaders/flat/layered.d.ts +27 -0
- package/dist/render/shaders/flat/layered.js +198 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +50 -16
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +23 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +41 -19
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
- package/dist/render/shaders/generated/flat.wgsl.js +791 -391
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/surfaceLayers.d.ts +107 -0
- package/dist/render/surfaceLayers.js +82 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +39 -0
- package/dist/render/transmission.d.ts +21 -0
- package/dist/render/transmission.js +21 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/dist/ui/splash.d.ts +42 -2
- package/dist/ui/splash.js +78 -13
- package/package.json +6 -6
- package/src/index.ts +6 -2
- package/src/render/ambientOcclusionPass.ts +4 -1
- package/src/render/backend/api.ts +31 -0
- package/src/render/backend/webgl2/renderer.ts +519 -56
- package/src/render/backend/webgpu/buffers.ts +153 -54
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/drawCommand.ts +3 -0
- package/src/render/backend/webgpu/flatPass.ts +26 -28
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +46 -7
- package/src/render/backend/webgpu/renderer.ts +622 -164
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/staticBundles.ts +2 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +18 -1
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/occlusionFade.ts +29 -0
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/ambientOcclusion.ts +12 -1
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +39 -4
- package/src/render/shaders/flat/layered.ts +198 -0
- package/src/render/shaders/flat/main.ts +50 -16
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +23 -3
- package/src/render/shaders/flat/overlay.ts +41 -19
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
- package/src/render/shaders/generated/flat.wgsl.ts +791 -391
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/surfaceLayers.ts +176 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +39 -0
- package/src/render/transmission.ts +27 -0
- package/src/render/vertexPacking.ts +109 -0
- package/src/ui/splash.ts +83 -14
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -75,8 +75,13 @@ export interface GpuMesh {
|
|
|
75
75
|
/** The second four influences' offsets, or -1 for a mesh with four. */
|
|
76
76
|
readonly joints2: number;
|
|
77
77
|
readonly weights2: number;
|
|
78
|
+
/** How the weights are read: floats, or sixteen-bit fixed point (`vertexPacking.ts`). */
|
|
79
|
+
readonly weightsFormat: GPUVertexFormat;
|
|
80
|
+
readonly weights2Format: GPUVertexFormat;
|
|
78
81
|
} | null;
|
|
79
82
|
readonly indexBuffer: GPUBuffer;
|
|
83
|
+
/** Sixteen or thirty-two bits an index: every pass that binds `indexBuffer` says which. */
|
|
84
|
+
readonly indexFormat: GPUIndexFormat;
|
|
80
85
|
/**
|
|
81
86
|
* How many indices the mesh draws. **Zero is a mesh with nothing to draw, and no pass draws it**:
|
|
82
87
|
* a level can hold none of some kind of geometry, and a draw of zero indices does nothing but
|
|
@@ -4,6 +4,9 @@ import { validateMeshData } from '../../mesh.js';
|
|
|
4
4
|
import { ABSENT_ATTRIBUTE } from '../../vertexDefaults.js';
|
|
5
5
|
import { UPLOAD_BYTES_PER_STEP } from '../../uploadStep.js';
|
|
6
6
|
import { INSTANCE_STRIDE } from '../../instances.js';
|
|
7
|
+
import { compactIndices } from '../../indexWidth.js';
|
|
8
|
+
import { SNORM16_ONE, UNORM16_ONE, packingFor, sharedValue, vertexPackingOf, } from '../../vertexPacking.js';
|
|
9
|
+
import { formatBytes, interleaveFixed, packedFormat, packingIn } from './vertexFormats.js';
|
|
7
10
|
/**
|
|
8
11
|
* Mesh geometry as WebGPU buffers.
|
|
9
12
|
*
|
|
@@ -160,13 +163,33 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
160
163
|
const bounds = boundsOfPositions(data.positions, createBounds());
|
|
161
164
|
const vertexCount = data.positions.length / 3;
|
|
162
165
|
const layered = data.layers !== undefined;
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
/* How each attribute travels, which the pipeline key says too: `vertexPacking.ts`. */
|
|
167
|
+
const packing = vertexPackingOf(data, dynamic);
|
|
165
168
|
/* Interleaved: one stride holding every attribute the mesh actually supplied — less a dynamic
|
|
166
|
-
mesh's positions and normals, which are buffers of their own
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
169
|
+
mesh's positions and normals, which are buffers of their own, and any attribute every vertex
|
|
170
|
+
shares, which is a constant. */
|
|
171
|
+
const fields = [];
|
|
172
|
+
let stride = 0;
|
|
173
|
+
for (const declared of VERTEX_LAYOUT) {
|
|
174
|
+
if (declared.optional && data[declared.name] === undefined)
|
|
175
|
+
continue;
|
|
176
|
+
if (dynamic && isStreamed(declared))
|
|
177
|
+
continue;
|
|
178
|
+
const attribute = laidOut(declared, layered);
|
|
179
|
+
const how = packingFor(packing, attribute.name);
|
|
180
|
+
if (how === 'constant')
|
|
181
|
+
continue;
|
|
182
|
+
const format = packedFormat(attribute.format, how);
|
|
183
|
+
fields.push({ attribute, packing: how, format, offset: stride });
|
|
184
|
+
stride += formatBytes(format);
|
|
185
|
+
}
|
|
186
|
+
const buffer = new ArrayBuffer(stride * vertexCount);
|
|
187
|
+
const rows = {
|
|
188
|
+
floats: new Float32Array(buffer),
|
|
189
|
+
signed: new Int16Array(buffer),
|
|
190
|
+
unsigned: new Uint16Array(buffer),
|
|
191
|
+
bytes: new Uint8Array(buffer),
|
|
192
|
+
};
|
|
170
193
|
/*
|
|
171
194
|
* **Refused here, in one sentence, rather than by the driver a hundred times.**
|
|
172
195
|
*
|
|
@@ -181,7 +204,7 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
181
204
|
* whose adapter caps at the default, and it deserves a sentence naming the model's size and
|
|
182
205
|
* the device's rather than a wall of validation.
|
|
183
206
|
*/
|
|
184
|
-
const byteLength = align4(
|
|
207
|
+
const byteLength = align4(buffer.byteLength);
|
|
185
208
|
const ceiling = device.limits.maxBufferSize;
|
|
186
209
|
if (typeof ceiling === 'number' && byteLength > ceiling) {
|
|
187
210
|
throw new Error(`this mesh needs a ${Math.round(byteLength / 1024 / 1024)} MB vertex buffer and this GPU ` +
|
|
@@ -216,10 +239,20 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
216
239
|
continue;
|
|
217
240
|
absent.set(value, slot * (CONSTANT_SLOT / 4));
|
|
218
241
|
}
|
|
242
|
+
/* And what every vertex shares, where a mesh's attribute is one value: `vertexPacking.ts`. */
|
|
243
|
+
for (let slot = 0; slot < VERTEX_LAYOUT.length; slot++) {
|
|
244
|
+
const attribute = VERTEX_LAYOUT[slot];
|
|
245
|
+
if (packingFor(packing, attribute.name) !== 'constant')
|
|
246
|
+
continue;
|
|
247
|
+
const values = data[attribute.name];
|
|
248
|
+
absent.set(sharedValue(values, attribute.components), slot * (CONSTANT_SLOT / 4));
|
|
249
|
+
}
|
|
219
250
|
device.queue.writeBuffer(constants, 0, absent);
|
|
251
|
+
/* Sixteen bits where every vertex fits, which every draw of this mesh says: indexWidth.ts. */
|
|
252
|
+
const indices = compactIndices(data.indices, vertexCount);
|
|
220
253
|
const indexBuffer = device.createBuffer({
|
|
221
254
|
label: 'mesh.indices',
|
|
222
|
-
size: align4(
|
|
255
|
+
size: align4(indices.byteLength),
|
|
223
256
|
usage: USAGE.INDEX | USAGE.COPY_DST,
|
|
224
257
|
});
|
|
225
258
|
/* A dynamic mesh's two moving streams: copied from as well as written, for last frame's positions. */
|
|
@@ -243,10 +276,11 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
243
276
|
* buffer never holds a torn one even for the frame between two steps.
|
|
244
277
|
*/
|
|
245
278
|
const verticesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / (stride + (dynamic ? 24 : 0))));
|
|
246
|
-
|
|
247
|
-
const
|
|
279
|
+
/* Whole four-byte words a step, which is all a queue writes: two sixteen-bit indices to one. */
|
|
280
|
+
const indexWidth = indices.BYTES_PER_ELEMENT;
|
|
281
|
+
const indicesPerStep = Math.max(1, Math.floor(UPLOAD_BYTES_PER_STEP / 4)) * (4 / indexWidth);
|
|
248
282
|
const totalSteps = Math.max(1, Math.ceil(vertexCount / verticesPerStep)) +
|
|
249
|
-
Math.max(1, Math.ceil(
|
|
283
|
+
Math.max(1, Math.ceil(indices.length / indicesPerStep));
|
|
250
284
|
/*
|
|
251
285
|
* **The mesh is assembled out here, by functions of its own, and that is the fix for a copy
|
|
252
286
|
* nobody named.** It was an object literal in this scope, and its `complete` getter shared this
|
|
@@ -262,6 +296,7 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
262
296
|
constants,
|
|
263
297
|
streams,
|
|
264
298
|
indexBuffer,
|
|
299
|
+
indexFormat: indices instanceof Uint16Array ? 'uint16' : 'uint32',
|
|
265
300
|
stride: streams === null ? stride : 12,
|
|
266
301
|
indexCount: data.indices.length,
|
|
267
302
|
bounds,
|
|
@@ -271,7 +306,7 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
271
306
|
isSkinnedEight: data.joints !== undefined && data.joints2 !== undefined,
|
|
272
307
|
vertexCount,
|
|
273
308
|
hasUvs: data.uvs !== undefined,
|
|
274
|
-
skinOffsets: streams === null ? skinOffsetsOf(
|
|
309
|
+
skinOffsets: streams === null ? skinOffsetsOf(fields) : null,
|
|
275
310
|
hasChannel: data.channel !== undefined,
|
|
276
311
|
morph: data.morphTargets === undefined || data.morphTargetCount === undefined
|
|
277
312
|
? null
|
|
@@ -282,14 +317,14 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
282
317
|
});
|
|
283
318
|
const job = {
|
|
284
319
|
data,
|
|
285
|
-
|
|
320
|
+
indices,
|
|
321
|
+
rows,
|
|
286
322
|
device,
|
|
287
|
-
|
|
323
|
+
fields,
|
|
288
324
|
streams,
|
|
289
325
|
vertices,
|
|
290
326
|
indexBuffer,
|
|
291
327
|
vertexCount,
|
|
292
|
-
step,
|
|
293
328
|
stride,
|
|
294
329
|
verticesPerStep,
|
|
295
330
|
indicesPerStep,
|
|
@@ -299,26 +334,35 @@ export function createGpuMeshIncremental(device, data, dynamic = false) {
|
|
|
299
334
|
return { mesh, upload: uploadSteps(job) };
|
|
300
335
|
}
|
|
301
336
|
function* uploadSteps(job) {
|
|
302
|
-
const { device,
|
|
337
|
+
const { device, fields, vertices, indexBuffer, vertexCount, stride } = job;
|
|
338
|
+
/* Floats and halves a vertex: every field is whole words, so both divide. */
|
|
339
|
+
const step = stride / 4;
|
|
340
|
+
const halves = stride / 2;
|
|
303
341
|
let taken = 0;
|
|
304
342
|
for (let from = 0; from < vertexCount; from += job.verticesPerStep) {
|
|
305
343
|
const data = job.data;
|
|
306
|
-
const
|
|
344
|
+
const rows = job.rows;
|
|
307
345
|
const to = Math.min(from + job.verticesPerStep, vertexCount);
|
|
308
|
-
|
|
309
|
-
|
|
346
|
+
for (const field of fields) {
|
|
347
|
+
const attribute = field.attribute;
|
|
310
348
|
const source = data[attribute.name];
|
|
311
|
-
if (
|
|
349
|
+
if (field.packing === 'snorm16') {
|
|
350
|
+
interleaveFixed(rows.signed, source, attribute.components, halves, field.offset / 2, from, to, SNORM16_ONE);
|
|
351
|
+
}
|
|
352
|
+
else if (field.packing === 'unorm16') {
|
|
353
|
+
interleaveFixed(rows.unsigned, source, attribute.components, halves, field.offset / 2, from, to, UNORM16_ONE);
|
|
354
|
+
}
|
|
355
|
+
else if (attribute === LAYERED_UVS) {
|
|
312
356
|
/* (u, v) from the coordinates and the layer after them, one field of three. */
|
|
313
|
-
|
|
314
|
-
interleaveField(
|
|
357
|
+
const at = field.offset / 4;
|
|
358
|
+
interleaveField(rows.floats, source, 2, step, at, from, to);
|
|
359
|
+
interleaveField(rows.floats, data.layers, 1, step, at + 2, from, to);
|
|
315
360
|
}
|
|
316
361
|
else {
|
|
317
|
-
interleaveField(
|
|
362
|
+
interleaveField(rows.floats, source, attribute.components, step, field.offset / 4, from, to);
|
|
318
363
|
}
|
|
319
|
-
fieldOffset += attribute.components;
|
|
320
364
|
}
|
|
321
|
-
device.queue.writeBuffer(vertices, from * stride,
|
|
365
|
+
device.queue.writeBuffer(vertices, from * stride, rows.bytes, from * stride, (to - from) * stride);
|
|
322
366
|
if (job.streams !== null) {
|
|
323
367
|
const count = (to - from) * 3;
|
|
324
368
|
device.queue.writeBuffer(job.streams.positions, from * 12, data.positions, from * 3, count);
|
|
@@ -328,7 +372,7 @@ function* uploadSteps(job) {
|
|
|
328
372
|
if (taken < job.totalSteps)
|
|
329
373
|
yield;
|
|
330
374
|
}
|
|
331
|
-
const indices = job.
|
|
375
|
+
const indices = job.indices;
|
|
332
376
|
const indexWidth = indices.BYTES_PER_ELEMENT;
|
|
333
377
|
for (let from = 0; from < indices.length; from += job.indicesPerStep) {
|
|
334
378
|
const to = Math.min(from + job.indicesPerStep, indices.length);
|
|
@@ -338,7 +382,8 @@ function* uploadSteps(job) {
|
|
|
338
382
|
yield;
|
|
339
383
|
}
|
|
340
384
|
job.data = null;
|
|
341
|
-
job.
|
|
385
|
+
job.indices = null;
|
|
386
|
+
job.rows = null;
|
|
342
387
|
job.progress.uploaded = true;
|
|
343
388
|
}
|
|
344
389
|
/**
|
|
@@ -383,28 +428,33 @@ function interleaveField(rows, source, width, step, fieldOffset, from, to) {
|
|
|
383
428
|
* Where the joints and weights sit in a vertex of `supplied`, the attributes a mesh interleaves in
|
|
384
429
|
* layout order; null unless it supplied both.
|
|
385
430
|
*/
|
|
386
|
-
function skinOffsetsOf(
|
|
387
|
-
let floats = 0;
|
|
431
|
+
function skinOffsetsOf(fields) {
|
|
388
432
|
let joints = -1;
|
|
389
433
|
let weights = -1;
|
|
390
434
|
let joints2 = -1;
|
|
391
435
|
let weights2 = -1;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
436
|
+
let weightsFormat = 'float32x4';
|
|
437
|
+
let weights2Format = 'float32x4';
|
|
438
|
+
for (const field of fields) {
|
|
439
|
+
const name = field.attribute.name;
|
|
440
|
+
if (name === 'joints')
|
|
441
|
+
joints = field.offset;
|
|
442
|
+
if (name === 'joints2')
|
|
443
|
+
joints2 = field.offset;
|
|
444
|
+
if (name === 'weights') {
|
|
445
|
+
weights = field.offset;
|
|
446
|
+
weightsFormat = field.format;
|
|
447
|
+
}
|
|
448
|
+
if (name === 'weights2') {
|
|
449
|
+
weights2 = field.offset;
|
|
450
|
+
weights2Format = field.format;
|
|
451
|
+
}
|
|
402
452
|
}
|
|
403
453
|
if (joints < 0 || weights < 0)
|
|
404
454
|
return null;
|
|
405
455
|
return joints2 >= 0 && weights2 >= 0
|
|
406
|
-
? { joints, weights, joints2, weights2 }
|
|
407
|
-
: { joints, weights, joints2: -1, weights2: -1 };
|
|
456
|
+
? { joints, weights, joints2, weights2, weightsFormat, weights2Format }
|
|
457
|
+
: { joints, weights, joints2: -1, weights2: -1, weightsFormat, weights2Format };
|
|
408
458
|
}
|
|
409
459
|
/** The handle, made where it can see only what it hands out. */
|
|
410
460
|
function gpuMeshOf(parts) {
|
|
@@ -419,6 +469,7 @@ function gpuMeshOf(parts) {
|
|
|
419
469
|
vertexStride: parts.stride,
|
|
420
470
|
skinOffsets: parts.skinOffsets,
|
|
421
471
|
indexBuffer,
|
|
472
|
+
indexFormat: parts.indexFormat,
|
|
422
473
|
indexCount: parts.indexCount,
|
|
423
474
|
bounds: parts.bounds,
|
|
424
475
|
get complete() {
|
|
@@ -553,7 +604,9 @@ instanced = false) {
|
|
|
553
604
|
* in the interleaved one. A *skinned* mesh interleaves them, so removing them would shift
|
|
554
605
|
* the stride under every other attribute — which is why `createInstanced` refuses one.
|
|
555
606
|
*/
|
|
556
|
-
|
|
607
|
+
/* One value every vertex shares sits with the absent ones: `vertexPacking.ts`. */
|
|
608
|
+
const packing = packingIn(present, String(attribute.name));
|
|
609
|
+
const supplied = (!attribute.optional || present[String(attribute.name)] === true) && packing !== 'constant';
|
|
557
610
|
if (instanced && INSTANCE_LOCATIONS.has(attribute.shaderLocation)) {
|
|
558
611
|
/*
|
|
559
612
|
* **Free to drop only because the mesh does not supply it.** An absent optional attribute
|
|
@@ -573,12 +626,9 @@ instanced = false) {
|
|
|
573
626
|
return;
|
|
574
627
|
}
|
|
575
628
|
if (supplied) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
format: attribute.format,
|
|
580
|
-
});
|
|
581
|
-
offset += attribute.components * 4;
|
|
629
|
+
const format = packedFormat(attribute.format, packing);
|
|
630
|
+
interleaved.push({ shaderLocation: attribute.shaderLocation, offset, format });
|
|
631
|
+
offset += formatBytes(format);
|
|
582
632
|
return;
|
|
583
633
|
}
|
|
584
634
|
constants.push({
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const CLOTH_ENTRIES: readonly ['clothPose', 'clothRest', 'clothBegin', 'clothPredict', 'clothDistance', 'clothBending', 'clothTether', 'clothLimit', 'clothFinish', 'clothBlend', 'clothPublish'];
|
|
2
|
+
export type ClothEntry = (typeof CLOTH_ENTRIES)[number];
|
|
3
|
+
export interface ClothKernels {
|
|
4
|
+
readonly layout: GPUBindGroupLayout;
|
|
5
|
+
readonly outputLayout: GPUBindGroupLayout;
|
|
6
|
+
readonly pipelines: Readonly<Record<ClothEntry, GPUComputePipeline>>;
|
|
7
|
+
}
|
|
8
|
+
export declare function clothKernelsFor(device: GPUDevice): ClothKernels;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cloth solver's layouts and one compute pipeline an entry point of `shaders/clothSolve.wgsl.ts`,
|
|
3
|
+
* built once a device and shared by every set of garments on it.
|
|
4
|
+
*/
|
|
5
|
+
import { CLOTH_SOLVE_WGSL } from './shaders/clothSolve.wgsl.js';
|
|
6
|
+
import { shaderModule } from './shaderModules.js';
|
|
7
|
+
const COMPUTE = 0x4;
|
|
8
|
+
export const CLOTH_ENTRIES = [
|
|
9
|
+
'clothPose',
|
|
10
|
+
'clothRest',
|
|
11
|
+
'clothBegin',
|
|
12
|
+
'clothPredict',
|
|
13
|
+
'clothDistance',
|
|
14
|
+
'clothBending',
|
|
15
|
+
'clothTether',
|
|
16
|
+
'clothLimit',
|
|
17
|
+
'clothFinish',
|
|
18
|
+
'clothBlend',
|
|
19
|
+
'clothPublish',
|
|
20
|
+
];
|
|
21
|
+
const kernelsByDevice = new WeakMap();
|
|
22
|
+
export function clothKernelsFor(device) {
|
|
23
|
+
const held = kernelsByDevice.get(device);
|
|
24
|
+
if (held !== undefined)
|
|
25
|
+
return held;
|
|
26
|
+
const uniform = (binding, dynamic) => ({
|
|
27
|
+
binding,
|
|
28
|
+
visibility: COMPUTE,
|
|
29
|
+
buffer: { type: 'uniform', hasDynamicOffset: dynamic },
|
|
30
|
+
});
|
|
31
|
+
const storage = (binding, type) => ({
|
|
32
|
+
binding,
|
|
33
|
+
visibility: COMPUTE,
|
|
34
|
+
buffer: { type },
|
|
35
|
+
});
|
|
36
|
+
/* Seven storage buffers of the eight a stage is granted: the shader's header says why so few. */
|
|
37
|
+
const layout = device.createBindGroupLayout({
|
|
38
|
+
label: 'cloth.solve',
|
|
39
|
+
entries: [
|
|
40
|
+
uniform(0, false),
|
|
41
|
+
uniform(1, true),
|
|
42
|
+
uniform(2, true),
|
|
43
|
+
storage(3, 'storage'),
|
|
44
|
+
storage(4, 'storage'),
|
|
45
|
+
storage(5, 'read-only-storage'),
|
|
46
|
+
storage(6, 'read-only-storage'),
|
|
47
|
+
storage(7, 'storage'),
|
|
48
|
+
storage(8, 'read-only-storage'),
|
|
49
|
+
storage(9, 'read-only-storage'),
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
const outputLayout = device.createBindGroupLayout({
|
|
53
|
+
label: 'cloth.publish',
|
|
54
|
+
entries: [
|
|
55
|
+
{
|
|
56
|
+
binding: 0,
|
|
57
|
+
visibility: COMPUTE,
|
|
58
|
+
storageTexture: { access: 'write-only', format: 'rgba32float' },
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
const module = shaderModule(device, { label: 'cloth.solve', code: CLOTH_SOLVE_WGSL });
|
|
63
|
+
const solve = device.createPipelineLayout({ bindGroupLayouts: [layout] });
|
|
64
|
+
const publish = device.createPipelineLayout({ bindGroupLayouts: [layout, outputLayout] });
|
|
65
|
+
const pipelines = {};
|
|
66
|
+
for (const entry of CLOTH_ENTRIES) {
|
|
67
|
+
pipelines[entry] = device.createComputePipeline({
|
|
68
|
+
label: `cloth.${entry}`,
|
|
69
|
+
layout: entry === 'clothPublish' ? publish : solve,
|
|
70
|
+
compute: { module, entryPoint: entry },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const kernels = { layout, outputLayout, pipelines };
|
|
74
|
+
kernelsByDevice.set(device, kernels);
|
|
75
|
+
return kernels;
|
|
76
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one garment of a set has asked the device for and the device has not yet recorded: a pose, a
|
|
3
|
+
* reset to rest, steps — each with the numbers its round reads, taken when it was asked for.
|
|
4
|
+
*
|
|
5
|
+
* **Taken when asked for, because they change after.** `ClothControl` moves its targets to a step's
|
|
6
|
+
* fraction and calls the device; by the time the set records the step, the next step has moved them
|
|
7
|
+
* again. So each op keeps the head of its record — fraction, blend, the poses it reads, the carry and
|
|
8
|
+
* the wind — as it stood, and the set copies it into the round that runs it.
|
|
9
|
+
*
|
|
10
|
+
* Grows by doubling and never shrinks, so a settled set allocates nothing a frame.
|
|
11
|
+
*/
|
|
12
|
+
/** Words of a garment's record in a round: the shader's `Pace`. */
|
|
13
|
+
export declare const PACE_WORDS = 44;
|
|
14
|
+
/** The head of it, which an op carries: everything before the garment's own constants. */
|
|
15
|
+
export declare const PACE_HEAD_WORDS = 32;
|
|
16
|
+
export declare const CLOTH_OP_POSE = 0;
|
|
17
|
+
export declare const CLOTH_OP_REST = 1;
|
|
18
|
+
export declare const CLOTH_OP_STEP = 2;
|
|
19
|
+
export declare class ClothOps {
|
|
20
|
+
private kinds;
|
|
21
|
+
private heads;
|
|
22
|
+
private floats;
|
|
23
|
+
private words;
|
|
24
|
+
private first;
|
|
25
|
+
private end;
|
|
26
|
+
get pending(): number;
|
|
27
|
+
/** The kind of the op to run next, or -1 where there is none. */
|
|
28
|
+
get next(): number;
|
|
29
|
+
/** A new op of `kind`, its head zeroed, for the caller to fill: the float and word offset of it. */
|
|
30
|
+
push(kind: number): number;
|
|
31
|
+
get headFloats(): Float32Array;
|
|
32
|
+
get headWords(): Uint32Array;
|
|
33
|
+
/**
|
|
34
|
+
* The next op's head into `out` at `at`, and the op gone. Copied as words, since a head holds
|
|
35
|
+
* integers too, and a word at a time, since a view a call would be an allocation a round.
|
|
36
|
+
*/
|
|
37
|
+
take(out: Uint32Array, at: number): void;
|
|
38
|
+
private grow;
|
|
39
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one garment of a set has asked the device for and the device has not yet recorded: a pose, a
|
|
3
|
+
* reset to rest, steps — each with the numbers its round reads, taken when it was asked for.
|
|
4
|
+
*
|
|
5
|
+
* **Taken when asked for, because they change after.** `ClothControl` moves its targets to a step's
|
|
6
|
+
* fraction and calls the device; by the time the set records the step, the next step has moved them
|
|
7
|
+
* again. So each op keeps the head of its record — fraction, blend, the poses it reads, the carry and
|
|
8
|
+
* the wind — as it stood, and the set copies it into the round that runs it.
|
|
9
|
+
*
|
|
10
|
+
* Grows by doubling and never shrinks, so a settled set allocates nothing a frame.
|
|
11
|
+
*/
|
|
12
|
+
/** Words of a garment's record in a round: the shader's `Pace`. */
|
|
13
|
+
export const PACE_WORDS = 44;
|
|
14
|
+
/** The head of it, which an op carries: everything before the garment's own constants. */
|
|
15
|
+
export const PACE_HEAD_WORDS = 32;
|
|
16
|
+
export const CLOTH_OP_POSE = 0;
|
|
17
|
+
export const CLOTH_OP_REST = 1;
|
|
18
|
+
export const CLOTH_OP_STEP = 2;
|
|
19
|
+
export class ClothOps {
|
|
20
|
+
kinds = new Uint8Array(8);
|
|
21
|
+
heads = new ArrayBuffer(8 * PACE_HEAD_WORDS * 4);
|
|
22
|
+
floats = new Float32Array(this.heads);
|
|
23
|
+
words = new Uint32Array(this.heads);
|
|
24
|
+
first = 0;
|
|
25
|
+
end = 0;
|
|
26
|
+
get pending() {
|
|
27
|
+
return this.end - this.first;
|
|
28
|
+
}
|
|
29
|
+
/** The kind of the op to run next, or -1 where there is none. */
|
|
30
|
+
get next() {
|
|
31
|
+
return this.first < this.end ? this.kinds[this.first] : -1;
|
|
32
|
+
}
|
|
33
|
+
/** A new op of `kind`, its head zeroed, for the caller to fill: the float and word offset of it. */
|
|
34
|
+
push(kind) {
|
|
35
|
+
if (this.end === this.kinds.length)
|
|
36
|
+
this.grow();
|
|
37
|
+
const at = this.end * PACE_HEAD_WORDS;
|
|
38
|
+
this.floats.fill(0, at, at + PACE_HEAD_WORDS);
|
|
39
|
+
this.kinds[this.end] = kind;
|
|
40
|
+
this.end++;
|
|
41
|
+
return at;
|
|
42
|
+
}
|
|
43
|
+
get headFloats() {
|
|
44
|
+
return this.floats;
|
|
45
|
+
}
|
|
46
|
+
get headWords() {
|
|
47
|
+
return this.words;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The next op's head into `out` at `at`, and the op gone. Copied as words, since a head holds
|
|
51
|
+
* integers too, and a word at a time, since a view a call would be an allocation a round.
|
|
52
|
+
*/
|
|
53
|
+
take(out, at) {
|
|
54
|
+
const from = this.first * PACE_HEAD_WORDS;
|
|
55
|
+
for (let w = 0; w < PACE_HEAD_WORDS; w++)
|
|
56
|
+
out[at + w] = this.words[from + w];
|
|
57
|
+
this.first++;
|
|
58
|
+
if (this.first === this.end) {
|
|
59
|
+
this.first = 0;
|
|
60
|
+
this.end = 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
grow() {
|
|
64
|
+
const live = this.end - this.first;
|
|
65
|
+
const capacity = this.kinds.length * 2;
|
|
66
|
+
const kinds = new Uint8Array(capacity);
|
|
67
|
+
kinds.set(this.kinds.subarray(this.first, this.end));
|
|
68
|
+
const heads = new ArrayBuffer(capacity * PACE_HEAD_WORDS * 4);
|
|
69
|
+
const floats = new Float32Array(heads);
|
|
70
|
+
floats.set(this.floats.subarray(this.first * PACE_HEAD_WORDS, this.end * PACE_HEAD_WORDS));
|
|
71
|
+
this.kinds = kinds;
|
|
72
|
+
this.heads = heads;
|
|
73
|
+
this.floats = floats;
|
|
74
|
+
this.words = new Uint32Array(heads);
|
|
75
|
+
this.first = 0;
|
|
76
|
+
this.end = live;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a set of garments writes once into the buffers its rounds read, and the one per-step number
|
|
3
|
+
* worked out on the CPU: the constants, each round slot's first record, each colour's and each
|
|
4
|
+
* garment's batch slot, and the carry a step hands a garment that keeps less than all of its
|
|
5
|
+
* character's motion. Plain functions over the packed set, apart from the solver that schedules.
|
|
6
|
+
*/
|
|
7
|
+
import type { ResolvedClothParameters } from '@driftengine/physics';
|
|
8
|
+
import type { PackedClothSet } from './clothSetPack.ts';
|
|
9
|
+
/** Bytes of the shader's `Constants`. */
|
|
10
|
+
export declare const CLOTH_CONSTANT_BYTES = 48;
|
|
11
|
+
/** The set's `Constants`: its totals, and the substep length every garment of it shares. */
|
|
12
|
+
export declare function clothConstants(packed: PackedClothSet, h: number): ArrayBuffer;
|
|
13
|
+
/** Each of `rounds` round slots' first record: the round's index times the garments. */
|
|
14
|
+
export declare function clothRoundOffsets(rounds: number, garments: number): Uint32Array<ArrayBuffer>;
|
|
15
|
+
/**
|
|
16
|
+
* Each colour's range, twice — opening a substep's iterations and not — at a slot each, kind by
|
|
17
|
+
* kind; then one slot a garment for publishing, its particles' range and its index.
|
|
18
|
+
*/
|
|
19
|
+
export declare function clothBatchSlots(packed: PackedClothSet): Uint32Array<ArrayBuffer>;
|
|
20
|
+
/**
|
|
21
|
+
* `carryParticles`' rotation, origin and move, into a record's head at `at`, between the model
|
|
22
|
+
* matrices before and after the step; nothing where the garment keeps all of its character's
|
|
23
|
+
* motion, which leaves the head's carry at 0.
|
|
24
|
+
*/
|
|
25
|
+
export declare function writeClothCarry(parameters: ResolvedClothParameters, previous: Float32Array, next: Float32Array, words: Uint32Array, f: Float32Array, at: number): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DYNAMIC_ALIGNMENT } from './uniformRing.js';
|
|
2
|
+
/** Bytes of the shader's `Constants`. */
|
|
3
|
+
export const CLOTH_CONSTANT_BYTES = 48;
|
|
4
|
+
/** The set's `Constants`: its totals, and the substep length every garment of it shares. */
|
|
5
|
+
export function clothConstants(packed, h) {
|
|
6
|
+
const bytes = new ArrayBuffer(CLOTH_CONSTANT_BYTES);
|
|
7
|
+
const u = new Uint32Array(bytes);
|
|
8
|
+
const f = new Float32Array(bytes);
|
|
9
|
+
u[0] = packed.count;
|
|
10
|
+
u[1] = packed.distances;
|
|
11
|
+
u[2] = packed.bendings;
|
|
12
|
+
u[3] = packed.joints;
|
|
13
|
+
u[4] = packed.colliders;
|
|
14
|
+
f[8] = h;
|
|
15
|
+
f[9] = 1 / h;
|
|
16
|
+
f[10] = 1 / (h * h);
|
|
17
|
+
return bytes;
|
|
18
|
+
}
|
|
19
|
+
/** Each of `rounds` round slots' first record: the round's index times the garments. */
|
|
20
|
+
export function clothRoundOffsets(rounds, garments) {
|
|
21
|
+
const words = new Uint32Array((rounds * DYNAMIC_ALIGNMENT) / 4);
|
|
22
|
+
for (let r = 0; r < rounds; r++)
|
|
23
|
+
words[(r * DYNAMIC_ALIGNMENT) / 4] = r * garments;
|
|
24
|
+
return words;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Each colour's range, twice — opening a substep's iterations and not — at a slot each, kind by
|
|
28
|
+
* kind; then one slot a garment for publishing, its particles' range and its index.
|
|
29
|
+
*/
|
|
30
|
+
export function clothBatchSlots(packed) {
|
|
31
|
+
const { distanceBatches, bendingBatches, tetherBatches, garments } = packed;
|
|
32
|
+
const kinds = [distanceBatches, bendingBatches, tetherBatches];
|
|
33
|
+
let slots = garments.length;
|
|
34
|
+
for (const batches of kinds)
|
|
35
|
+
slots += (batches.length - 1) * 2;
|
|
36
|
+
const words = new Uint32Array((slots * DYNAMIC_ALIGNMENT) / 4);
|
|
37
|
+
let slot = 0;
|
|
38
|
+
for (const batches of kinds) {
|
|
39
|
+
for (let b = 0; b + 1 < batches.length; b++) {
|
|
40
|
+
for (let first = 0; first < 2; first++) {
|
|
41
|
+
const at = (slot * DYNAMIC_ALIGNMENT) / 4;
|
|
42
|
+
words[at] = batches[b];
|
|
43
|
+
words[at + 1] = batches[b + 1];
|
|
44
|
+
words[at + 2] = first;
|
|
45
|
+
slot++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
garments.forEach((range, g) => {
|
|
50
|
+
const at = (slot * DYNAMIC_ALIGNMENT) / 4;
|
|
51
|
+
words[at] = range.particleBase;
|
|
52
|
+
words[at + 1] = range.particleBase + range.count;
|
|
53
|
+
words[at + 2] = g;
|
|
54
|
+
slot++;
|
|
55
|
+
});
|
|
56
|
+
return words;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `carryParticles`' rotation, origin and move, into a record's head at `at`, between the model
|
|
60
|
+
* matrices before and after the step; nothing where the garment keeps all of its character's
|
|
61
|
+
* motion, which leaves the head's carry at 0.
|
|
62
|
+
*/
|
|
63
|
+
export function writeClothCarry(parameters, previous, next, words, f, at) {
|
|
64
|
+
const turn = 1 - parameters.angularInertia;
|
|
65
|
+
const move = 1 - parameters.linearInertia;
|
|
66
|
+
if (turn === 0 && move === 0)
|
|
67
|
+
return;
|
|
68
|
+
words[at + 3] = 1;
|
|
69
|
+
/* Column c of R = R_next × R_previousᵀ, at turn0 … turn2. */
|
|
70
|
+
for (let col = 0; col < 3; col++) {
|
|
71
|
+
for (let row = 0; row < 3; row++) {
|
|
72
|
+
let sum = 0;
|
|
73
|
+
for (let k = 0; k < 3; k++) {
|
|
74
|
+
sum += next[k * 4 + row] * previous[k * 4 + col];
|
|
75
|
+
}
|
|
76
|
+
f[at + 8 + col * 4 + row] = sum;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (let k = 0; k < 3; k++) {
|
|
80
|
+
f[at + 20 + k] = previous[12 + k];
|
|
81
|
+
f[at + 24 + k] = (next[12 + k] - previous[12 + k]) * move;
|
|
82
|
+
}
|
|
83
|
+
f[at + 23] = turn;
|
|
84
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PackedCloth } from './clothSolvePack.ts';
|
|
2
|
+
/** Floats of statics a particle in a set: a garment's own, and which garment it is. */
|
|
3
|
+
export declare const CLOTH_SET_STATIC_FLOATS: number;
|
|
4
|
+
/** Where one garment sits in a set. */
|
|
5
|
+
export interface ClothGarmentRange {
|
|
6
|
+
readonly particleBase: number;
|
|
7
|
+
readonly count: number;
|
|
8
|
+
readonly jointBase: number;
|
|
9
|
+
readonly joints: number;
|
|
10
|
+
readonly colliderBase: number;
|
|
11
|
+
readonly colliders: number;
|
|
12
|
+
/** `packCloth`'s limit flags, the garment's own. */
|
|
13
|
+
readonly limits: number;
|
|
14
|
+
readonly rigged: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface PackedClothSet {
|
|
17
|
+
/** Particles across every garment. */
|
|
18
|
+
readonly count: number;
|
|
19
|
+
readonly statics: Float32Array;
|
|
20
|
+
readonly constraints: Uint32Array;
|
|
21
|
+
readonly distances: number;
|
|
22
|
+
readonly bendings: number;
|
|
23
|
+
readonly tethers: number;
|
|
24
|
+
/** Each kind's colours across the set: colour `b` of every garment is `[b]` up to `[b + 1]`. */
|
|
25
|
+
readonly distanceBatches: Uint32Array;
|
|
26
|
+
readonly bendingBatches: Uint32Array;
|
|
27
|
+
readonly tetherBatches: Uint32Array;
|
|
28
|
+
/** Skin matrices and colliders across every garment. */
|
|
29
|
+
readonly joints: number;
|
|
30
|
+
readonly colliders: number;
|
|
31
|
+
readonly garments: readonly ClothGarmentRange[];
|
|
32
|
+
}
|
|
33
|
+
export declare function packClothSet(packs: readonly PackedCloth[]): PackedClothSet;
|