@yali-engine/physics 0.0.0-stage → 0.4.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.
@@ -0,0 +1,193 @@
1
+ import { YaliError } from '@yali-engine/core';
2
+ import { CHARACTER_FIELDS, FIELD_FIELDS } from "./character.js";
3
+ /** `physics.RigidBody.kind`. */
4
+ export const BodyKind = { Dynamic: 0, Kinematic: 1, Fixed: 2 };
5
+ /** `physics.Collider.shape` (`dims` and `asset` per shape in `collider`'s docs). */
6
+ export const ShapeKind = { Sphere: 0, Capsule: 1, Box: 2, Hull: 3, Mesh: 4, Heightfield: 5 };
7
+ /** `physics.Joint.kind`. */
8
+ export const JointType = { Fixed: 0, Revolute: 1, Prismatic: 2, Spherical: 3, Distance: 4, SixDof: 5 };
9
+ const BODY_FIELDS = {
10
+ kind: 'u8',
11
+ linVel: 'vec3<f32>',
12
+ angVel: 'vec3<f32>',
13
+ linDamping: 'f32',
14
+ angDamping: 'f32',
15
+ gravityScale: 'f32',
16
+ canSleep: 'u8',
17
+ sleeping: 'u8',
18
+ handle: 'u32',
19
+ ccd: 'u8',
20
+ };
21
+ const JOINT_FIELDS = {
22
+ kind: 'u8',
23
+ other: 'entity',
24
+ anchorA: 'vec3<f64>',
25
+ anchorB: 'vec3<f32>',
26
+ axis: 'vec3<f32>',
27
+ limited: 'u8',
28
+ limits: 'vec2<f32>',
29
+ drive: 'vec4<f32>',
30
+ maxForce: 'f32',
31
+ cone: 'f32',
32
+ lockMask: 'u8',
33
+ limitMask: 'u8',
34
+ sixLower: { scalar: 'f32', lanes: 6 },
35
+ sixUpper: { scalar: 'f32', lanes: 6 },
36
+ breakForce: 'f32',
37
+ collide: 'u8',
38
+ broken: 'u8',
39
+ force: 'f32',
40
+ handle: 'u32',
41
+ };
42
+ const COLLIDER_FIELDS = {
43
+ shape: 'u8',
44
+ dims: 'vec3<f32>',
45
+ asset: 'u32',
46
+ offsetT: 'vec3<f32>',
47
+ offsetR: 'quat',
48
+ density: 'f32',
49
+ friction: 'f32',
50
+ restitution: 'f32',
51
+ sensor: 'u8',
52
+ layer: 'u32',
53
+ mask: 'u32',
54
+ events: 'u8',
55
+ handle: 'u32',
56
+ };
57
+ /** Registers `physics.RigidBody` and `physics.Collider` on `world` (idempotent). */
58
+ export function registerPhysics(world) {
59
+ return {
60
+ RigidBody: world.registerComponent({ name: 'physics.RigidBody', fields: BODY_FIELDS }),
61
+ Collider: world.registerComponent({ name: 'physics.Collider', fields: COLLIDER_FIELDS }),
62
+ Joint: world.registerComponent({ name: 'physics.Joint', fields: JOINT_FIELDS }),
63
+ CharacterController: world.registerComponent({ name: 'physics.CharacterController', fields: CHARACTER_FIELDS }),
64
+ ForceField: world.registerComponent({ name: 'physics.ForceField', fields: FIELD_FIELDS }),
65
+ };
66
+ }
67
+ /**
68
+ * `physics.Joint` values (ADR-0090). Insert on the second body's entity, beside its `RigidBody`;
69
+ * the joint starts at its spawn pose (angle 0, offset 0) and acts once both bodies exist.
70
+ * `broken`, `force` (N) and `handle` are written back; `physics.JointBroken` fires on a break.
71
+ *
72
+ * @example
73
+ * // A door hinged to a wall post about Y, opening to 100°, sprung shut.
74
+ * world.insert(door, Joint, joint('revolute', { other: post, anchorA: [0.5, 0, 0], anchorB: [-0.5, 0, 0], axis: [0, 1, 0], limits: [0, 1.75], spring: { target: 0, hertz: 1 } }));
75
+ */
76
+ export function joint(type, o = {}) {
77
+ const key = Object.keys(JointType).find((k) => k.toLowerCase() === type.toLowerCase());
78
+ if (!key)
79
+ throw Object.assign(new YaliError('PHYSICS_INVALID', `No joint type ${JSON.stringify(type)}.`, `Use one of ${Object.keys(JointType).join(', ')}.`), { path: 'type' });
80
+ if (o.limits && !(o.limits[0] <= o.limits[1])) {
81
+ throw Object.assign(new YaliError('PHYSICS_INVALID', `Joint limits [${o.limits}] are not lower ≤ upper.`, 'Give [lower, upper] with lower ≤ upper.'), { path: 'limits' });
82
+ }
83
+ if (o.motor && o.spring)
84
+ throw Object.assign(new YaliError('PHYSICS_INVALID', 'A joint has a motor or a spring, not both.', 'Drop one.'), { path: 'motor' });
85
+ const lower = [0, 0, 0, 0, 0, 0];
86
+ const upper = [0, 0, 0, 0, 0, 0];
87
+ let lockMask = 0;
88
+ let limitMask = 0;
89
+ (o.axes ?? []).forEach((a, k) => {
90
+ if (a === 'locked')
91
+ lockMask |= 1 << k;
92
+ else if (a !== 'free') {
93
+ limitMask |= 1 << k;
94
+ [lower[k], upper[k]] = a;
95
+ }
96
+ });
97
+ if (key === 'SixDof' && !o.axes)
98
+ lockMask = 0x3f;
99
+ const s = o.spring;
100
+ return {
101
+ kind: JointType[key],
102
+ other: o.other ?? NULL_ENTITY,
103
+ anchorA: o.anchorA ?? [0, 0, 0],
104
+ anchorB: o.anchorB ?? [0, 0, 0],
105
+ axis: o.axis ?? [1, 0, 0],
106
+ limited: o.limits ? 1 : 0,
107
+ limits: o.limits ?? [0, 0],
108
+ drive: s ? [s.target, 0, s.hertz, s.damping ?? 1] : [0, o.motor?.speed ?? 0, 0, 0],
109
+ maxForce: s ? (s.maxForce ?? 0) : (o.motor?.maxForce ?? 0),
110
+ cone: o.cone ?? 0,
111
+ lockMask,
112
+ limitMask,
113
+ sixLower: lower,
114
+ sixUpper: upper,
115
+ breakForce: o.breakForce ?? 0,
116
+ collide: o.collide ? 1 : 0,
117
+ };
118
+ }
119
+ /** No entity (`Entity::NULL`: index u32::MAX, generation 0): the world, for a joint's `other`. */
120
+ const NULL_ENTITY = 0xffffffff;
121
+ /**
122
+ * `physics.RigidBody` values with defaults (dynamic, at rest, angular damping 0.05, gravity
123
+ * scale 1, may sleep). Insert them with the entity's `core.Transform`; the body's pose is the
124
+ * Transform's.
125
+ *
126
+ * @example
127
+ * world.insert(e, RigidBody, rigidBody({ linearVelocity: [0, 5, 0] }));
128
+ */
129
+ export function rigidBody(o = {}) {
130
+ return {
131
+ kind: o.kind ?? BodyKind.Dynamic,
132
+ linVel: o.linearVelocity ?? [0, 0, 0],
133
+ angVel: o.angularVelocity ?? [0, 0, 0],
134
+ linDamping: o.linearDamping ?? 0,
135
+ angDamping: o.angularDamping ?? 0.05,
136
+ gravityScale: o.gravityScale ?? 1,
137
+ canSleep: (o.canSleep ?? true) ? 1 : 0,
138
+ ccd: o.ccd ? 1 : 0,
139
+ };
140
+ }
141
+ /**
142
+ * `physics.Collider` values. Box half extents; a capsule along local Y; hulls, meshes and
143
+ * heightfields name an asset from `Physics.addHull` / `addMesh` / `addHeightfield`. A collider
144
+ * without a `RigidBody` on its entity is static (meshes and heightfields only can be).
145
+ *
146
+ * @example
147
+ * world.insert(ground, Collider, collider({ box: [50, 0.5, 50] }));
148
+ * world.insert(ball, Collider, collider({ sphere: 0.5 }, { restitution: 0.6 }));
149
+ */
150
+ export function collider(shape, o = {}) {
151
+ let kind;
152
+ let dims = [0, 0, 0];
153
+ let asset = 0;
154
+ if ('sphere' in shape) {
155
+ kind = ShapeKind.Sphere;
156
+ dims = [shape.sphere, 0, 0];
157
+ }
158
+ else if ('capsule' in shape) {
159
+ kind = ShapeKind.Capsule;
160
+ dims = [shape.capsule.halfHeight, shape.capsule.radius, 0];
161
+ }
162
+ else if ('box' in shape) {
163
+ kind = ShapeKind.Box;
164
+ dims = [shape.box[0], shape.box[1], shape.box[2]];
165
+ }
166
+ else if ('hull' in shape) {
167
+ kind = ShapeKind.Hull;
168
+ asset = shape.hull;
169
+ }
170
+ else if ('mesh' in shape) {
171
+ kind = ShapeKind.Mesh;
172
+ asset = shape.mesh;
173
+ }
174
+ else {
175
+ kind = ShapeKind.Heightfield;
176
+ asset = shape.heightfield;
177
+ }
178
+ return {
179
+ shape: kind,
180
+ dims,
181
+ asset,
182
+ offsetT: o.offset ?? [0, 0, 0],
183
+ offsetR: o.rotation ?? [0, 0, 0, 1],
184
+ density: o.density ?? 1000,
185
+ friction: o.friction ?? 0.6,
186
+ restitution: o.restitution ?? 0,
187
+ sensor: o.sensor ? 1 : 0,
188
+ layer: o.layer ?? 1,
189
+ mask: o.mask ?? 0xffffffff,
190
+ events: o.events ? 1 : 0,
191
+ };
192
+ }
193
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Linked from shaders/kanduka/cloth.wesl. */
2
+ export declare const kandukaCloth: {
3
+ readonly hash: "0473fc3b76a0ea73";
4
+ readonly wgsl: "struct Params {\ngravity: vec3f,\nh: f32,\nwind: vec3f,\ntime: f32,\ngust: vec4f,\nsubsteps: u32,\ncolliders: u32,\nt0: f32,\nt1: f32,\nalpha: f32,\nspan: f32,\nmax_speed: f32,\niterations: u32,\nbend_iters: u32,\npad0: u32,\npad1: u32,\npad2: u32,\n}\nstruct Cloth {\nfirst: u32,\ncount: u32,\nbase: u32,\npool: u32,\ntris: u32,\nadj: u32,\ncolours: u32,\nncolours: u32,\ncons: u32,\nlive: u32,\nhash: u32,\ntable: u32,\norigin0: vec4f,\nrot0: vec4f,\norigin1: vec4f,\nrot1: vec4f,\nfabric: vec4f,\nfabric2: vec4f,\nextra: vec4u,\n}\nstruct Collider {\np0: vec4f,\nq0: vec4f,\np1: vec4f,\nq1: vec4f,\ndims: vec4f,\n}\nconst NONE: u32 = 0xffffffffu;\noverride WG: u32 = 256u;\nconst SPHERE: f32 = 0.0;\nconst CAPSULE: f32 = 1.0;\nconst BOX: f32 = 2.0;\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read> cloths: array<Cloth>;\n@group(0) @binding(2) var<storage, read> st: array<u32>;\n@group(0) @binding(3) var<storage, read_write> pos: array<vec4f>;\n@group(0) @binding(4) var<storage, read_write> prev: array<vec4f>;\n@group(0) @binding(5) var<storage, read_write> vel: array<vec4f>;\n@group(0) @binding(6) var<storage, read_write> last: array<vec4f>;\n@group(0) @binding(7) var<storage, read> colliders: array<Collider>;\n@group(0) @binding(11) var<storage, read_write> torn: array<u32>;\nfn tri_torn(c: Cloth, tri: u32) -> bool {\nif (c.fabric2.w <= 0.0) {\nreturn false;\n}\nlet te = c.base + c.cons + st[c.base + c.colours + c.ncolours] * 12u + tri * 3u;\nreturn torn[c.base + st[te]] != 0u || torn[c.base + st[te + 1u]] != 0u || torn[c.base + st[te + 2u]] != 0u;\n}\nfn stf(i: u32) -> f32 {\nreturn bitcast<f32>(st[i]);\n}\nfn quat_conj(q: vec4f) -> vec4f {\nreturn vec4f(-q.xyz, q.w);\n}\nfn nlerp(a: vec4f, b: vec4f, t: f32) -> vec4f {\nlet s = select(1.0, -1.0, dot(a, b) < 0.0);\nreturn normalize(mix(a, b * s, t));\n}\nfn wind_at(p: vec3f, t: f32) -> vec3f {\nlet w = params.wind;\nlet speed = length(w);\nif (speed <= 0.0) {\nreturn vec3f(0.0);\n}\nlet g = params.gust;\nlet gust = 1.0 + g.y * (0.6 * sin(6.2831853 * g.z * t) + 0.4 * sin(6.2831853 * g.z * 2.3 * t + 1.7));\nlet q = p - w * t * 0.35;\nlet n = vec3f(sin(q.y * 1.7 + q.z * 0.9 + t * 1.3) + 0.5 * sin(q.x * 2.9 - q.y * 2.1 + t * 2.7), sin(q.z * 1.3 - q.x * 1.1 + t * 1.9) + 0.5 * sin(q.y * 3.1 + q.z * 2.3 - t * 2.3), sin(q.x * 1.5 + q.y * 0.7 - t * 1.6) + 0.5 * sin(q.z * 2.7 - q.x * 1.9 + t * 3.1));\nreturn w * gust + n * (g.x * speed * 0.5);\n}\nfn aero(c: Cloth, v: u32, t: f32) -> vec3f {\nlet rho = params.gust.w;\nif (rho <= 0.0 || pos[c.first + v].w <= 0.0) {\nreturn vec3f(0.0);\n}\nlet a0 = c.base + c.adj;\nlet lo = st[a0 + v];\nlet hi = st[a0 + v + 1u];\nlet air = wind_at(pos[c.first + v].xyz, t) * c.fabric2.y;\nvar f = vec3f(0.0);\nfor (var k = lo; k < hi; k++) {\nlet tri = st[a0 + k];\nif (tri_torn(c, tri)) {\ncontinue;\n}\nlet o = c.base + c.tris + tri * 3u;\nlet i0 = c.first + st[o];\nlet i1 = c.first + st[o + 1u];\nlet i2 = c.first + st[o + 2u];\nlet p0 = pos[i0].xyz;\nlet cr = cross(pos[i1].xyz - p0, pos[i2].xyz - p0);\nlet len = length(cr);\nif (len < 1e-12) {\ncontinue;\n}\nlet n = cr / len;\nlet area = 0.5 * len;\nlet rel = air - (vel[i0].xyz + vel[i1].xyz + vel[i2].xyz) / 3.0;\nlet vn = dot(rel, n);\nlet vt = rel - vn * n;\nf += 0.5 * rho * area * (c.fabric.w * abs(vn) * vn * n + c.fabric2.x * length(vt) * vt) / 3.0;\n}\nlet acc = f * pos[c.first + v].w;\nlet room = length(air - vel[c.first + v].xyz) / params.h;\nlet al = length(acc);\nreturn select(acc, acc * (room / al), al > room);\n}\nfn pin_target(c: Cloth, v: u32, t: f32) -> vec3f {\nlet r = c.base + v * 4u;\nlet local = vec3f(stf(r), stf(r + 1u), stf(r + 2u));\nlet o = mix(c.origin0, c.origin1, t);\nreturn o.xyz + quat_rotate(nlerp(c.rot0, c.rot1, t), local * o.w);\n}\nfn solve(c: Cloth, j: u32, h2: f32) {\nlet o = c.base + c.cons + j * 12u;\nlet i0 = c.first + st[o];\nlet i1 = c.first + st[o + 1u];\nlet e2 = st[o + 2u];\nif (e2 == NONE) {\nlet a = pos[i0];\nlet b = pos[i1];\nlet w = a.w + b.w;\nlet d = b.xyz - a.xyz;\nlet len = length(d);\nif (w <= 0.0 || len < 1e-9) {\nreturn;\n}\nlet cv = len - stf(o + 4u);\nif (c.fabric2.w > 0.0) {\nif (torn[o] != 0u) {\nreturn;\n}\nif (cv > stf(o + 4u) * c.fabric2.w) {\ntorn[o] = 1u;\ntorn[c.base + st[c.base + c.extra.y + 12u] + j] = 1u;\nreturn;\n}\n}\nlet alpha = select(stf(o + 5u), stf(o + 6u), cv < 0.0) / h2;\nlet dl = -cv / (w + alpha);\nlet n = d / len;\npos[i0] = vec4f(a.xyz - a.w * dl * n, a.w);\npos[i1] = vec4f(b.xyz + b.w * dl * n, b.w);\nreturn;\n}\nif (c.fabric2.w > 0.0 && torn[c.base + st[o + 9u]] != 0u) {\nreturn;\n}\nlet i2 = c.first + e2;\nlet i3 = c.first + st[o + 3u];\nlet x0 = pos[i0];\nlet x1 = pos[i1];\nlet x2 = pos[i2];\nlet x3 = pos[i3];\nlet k = vec4f(stf(o + 4u), stf(o + 5u), stf(o + 6u), stf(o + 7u));\nlet b = k.x * x0.xyz + k.y * x1.xyz + k.z * x2.xyz + k.w * x3.xyz;\nlet wk = x0.w * k.x * k.x + x1.w * k.y * k.y + x2.w * k.z * k.z + x3.w * k.w * k.w;\nlet denom = wk + stf(o + 8u) / h2;\nif (wk <= 0.0) {\nreturn;\n}\nlet s = -1.0 / denom;\npos[i0] = vec4f(x0.xyz + x0.w * k.x * s * b, x0.w);\npos[i1] = vec4f(x1.xyz + x1.w * k.y * s * b, x1.w);\npos[i2] = vec4f(x2.xyz + x2.w * k.z * s * b, x2.w);\npos[i3] = vec4f(x3.xyz + x3.w * k.w * s * b, x3.w);\n}\nfn anchor_of(c: Cloth, v: u32) -> u32 {\nlet at = st[c.base + c.extra.y + 11u];\nif (at == 0u) {\nreturn NONE;\n}\nlet k = st[c.base + at + v];\nreturn select(params.colliders + c.extra.w + k, NONE, k == NONE);\n}\nfn pin_pos(c: Cloth, v: u32, t: f32) -> vec3f {\nlet k = anchor_of(c, v);\nif (k == NONE) {\nreturn pin_target(c, v, t);\n}\nreturn mix(colliders[k].p0.xyz, colliders[k].p1.xyz, t);\n}\nfn backstop(c: Cloth, v: u32, t: f32) {\nlet i = c.first + v;\nlet k = anchor_of(c, v);\nif (k == NONE || pos[i].w <= 0.0) {\nreturn;\n}\nlet a = colliders[k];\nlet n = normalize(mix(a.q0.xyz, a.q1.xyz, t));\nlet d = dot(pos[i].xyz - mix(a.p0.xyz, a.p1.xyz, t), n) + a.p1.w;\nif (d < 0.0) {\npos[i] = vec4f(pos[i].xyz - d * n, pos[i].w);\n}\n}\nconst FIXED: f32 = 1e9;\n@group(0) @binding(10) var<storage, read_write> dbend: array<atomic<i32>>;\nfn bend_scatter(c: Cloth, j: u32, h2: f32, first: bool) {\nlet o = c.base + c.cons + j * 12u;\nif (c.fabric2.w > 0.0 && torn[c.base + st[o + 9u]] != 0u) {\nreturn;\n}\nlet ids = vec4u(c.first + st[o], c.first + st[o + 1u], c.first + st[o + 2u], c.first + st[o + 3u]);\nlet x0 = pos[ids.x];\nlet x1 = pos[ids.y];\nlet x2 = pos[ids.z];\nlet x3 = pos[ids.w];\nlet w = vec4f(stf(o + 4u), stf(o + 5u), stf(o + 6u), stf(o + 7u));\nlet inv = vec4f(x0.w, x1.w, x2.w, x3.w);\nlet bc = c.base + st[c.base + c.extra.y + 3u];\nvar split = vec4f(1.0);\nfor (var q = 0u; q < 4u; q++) {\nlet v = st[o + q];\nsplit[q] = f32(max(1u, st[bc + v + 1u] - st[bc + v]));\n}\nlet wk = dot(split * inv, w * w);\nif (wk <= 0.0) {\nreturn;\n}\nlet b = w.x * x0.xyz + w.y * x1.xyz + w.z * x2.xyz + w.w * x3.xyz;\nlet at = stf(o + 8u) / h2;\nvar lam = vec3f(0.0);\nif (!first) {\nlam = vec3f(bitcast<f32>(torn[o + 3u]), bitcast<f32>(torn[o + 4u]), bitcast<f32>(torn[o + 5u]));\n}\nlet r = b + at * lam;\nlet s = -FIXED / (wk + at);\nif (params.bend_iters > 1u) {\nlet nl = lam + r * (s / FIXED);\ntorn[o + 3u] = bitcast<u32>(nl.x);\ntorn[o + 4u] = bitcast<u32>(nl.y);\ntorn[o + 5u] = bitcast<u32>(nl.z);\n}\nfor (var q = 0u; q < 4u; q++) {\nif (inv[q] <= 0.0) {\ncontinue;\n}\nlet d = vec3i(round(r * (inv[q] * w[q] * s)));\nlet at = ids[q] * 4u;\natomicAdd(&dbend[at], d.x);\natomicAdd(&dbend[at + 1u], d.y);\natomicAdd(&dbend[at + 2u], d.z);\n}\n}\nfn shape_sdf(kind: f32, dims: vec3f, p: vec3f) -> vec4f {\nif (kind == SPHERE) {\nlet l = length(p);\nreturn vec4f(select(vec3f(0.0, 1.0, 0.0), p / l, l > 1e-9), l - dims.x);\n}\nif (kind == CAPSULE) {\nlet q = vec3f(p.x, p.y - clamp(p.y, -dims.x, dims.x), p.z);\nlet l = length(q);\nreturn vec4f(select(vec3f(0.0, 1.0, 0.0), q / l, l > 1e-9), l - dims.y);\n}\nlet q = abs(p) - dims;\nlet outside = max(q, vec3f(0.0));\nlet lo = length(outside);\nif (lo > 0.0) {\nreturn vec4f(outside * sign(p) / lo, lo);\n}\nif (q.x >= q.y && q.x >= q.z) {\nreturn vec4f(sign(p.x), 0.0, 0.0, q.x);\n}\nif (q.y >= q.z) {\nreturn vec4f(0.0, sign(p.y), 0.0, q.y);\n}\nreturn vec4f(0.0, 0.0, sign(p.z), q.z);\n}\nfn collide(c: Cloth, i: u32, t: f32, h: f32) {\nvar x = pos[i].xyz;\nlet start = prev[i].xyz;\nlet thick = c.fabric.z;\nfor (var k = 0u; k < params.colliders; k++) {\nlet cl = colliders[k];\nlet cp = mix(cl.p0.xyz, cl.p1.xyz, t);\nlet cq = nlerp(cl.q0, cl.q1, t);\nlet local = quat_rotate(quat_conj(cq), x - cp);\nlet sd = shape_sdf(cl.p0.w, cl.dims.xyz, local);\nlet depth = thick - sd.w;\nif (depth <= 0.0) {\ncontinue;\n}\nlet n = quat_rotate(cq, sd.xyz);\nx += n * depth;\nlet lc = local - sd.xyz * sd.w;\nlet w0 = cl.p0.xyz + quat_rotate(cl.q0, lc);\nlet w1 = cl.p1.xyz + quat_rotate(cl.q1, lc);\nlet vc = select(vec3f(0.0), (w1 - w0) / params.span, params.span > 0.0);\nlet rel = (x - start) - vc * h;\nlet tang = rel - dot(rel, n) * n;\nlet tl = length(tang);\nlet mu = sqrt(c.fabric.y * cl.p1.w);\nif (tl > 0.0) {\nx -= tang * min(1.0, mu * depth / tl);\n}\n}\npos[i] = vec4f(x, pos[i].w);\n}\nfn ph_air(c: Cloth, v: u32, time: f32, first: bool) {\nlet i = c.first + v;\nprev[i] = vec4f(aero(c, v, time), 0.0);\nif (first) {\nfor (var q = 0u; q < 4u; q++) {\natomicStore(&dbend[i * 4u + q], 0);\n}\n}\n}\nfn ph_predict(c: Cloth, v: u32, t: f32, h: f32) {\nlet i = c.first + v;\nlet p = pos[i];\nif (p.w <= 0.0) {\nprev[i] = p;\npos[i] = vec4f(pin_pos(c, v, t), 0.0);\nreturn;\n}\nlet vv = vel[i].xyz + h * (params.gravity + prev[i].xyz);\nprev[i] = p;\nvel[i] = vec4f(vv, 0.0);\npos[i] = vec4f(p.xyz + h * vv, p.w);\n}\nfn ph_bend_apply(c: Cloth, v: u32) {\nlet i = c.first + v;\nlet d = vec3f(f32(atomicLoad(&dbend[i * 4u])), f32(atomicLoad(&dbend[i * 4u + 1u])), f32(atomicLoad(&dbend[i * 4u + 2u])));\npos[i] = vec4f(pos[i].xyz + d / FIXED, pos[i].w);\nfor (var q = 0u; q < 4u; q++) {\natomicStore(&dbend[i * 4u + q], 0);\n}\n}\nfn ph_tether(c: Cloth, v: u32, teth: u32) {\nlet i = c.first + v;\nlet anchor = st[c.base + teth + v * 2u];\nif (anchor != NONE && pos[i].w > 0.0) {\nlet a = pos[c.first + anchor].xyz;\nlet d = pos[i].xyz - a;\nlet l = length(d);\nlet lim = stf(c.base + teth + v * 2u + 1u);\nif (l > lim) {\npos[i] = vec4f(a + d * (lim / l), pos[i].w);\n}\n}\n}\nfn ph_collide(c: Cloth, v: u32, t: f32, h: f32) {\nlet i = c.first + v;\nif (pos[i].w > 0.0) {\ncollide(c, i, t, h);\nbackstop(c, v, t);\n}\nlet nv = (pos[i].xyz - prev[i].xyz) / h * exp(-c.fabric.x * h);\nlet sp = length(nv);\nvel[i] = vec4f(select(nv, nv * (params.max_speed / sp), sp > params.max_speed), 0.0);\n}\n@compute @workgroup_size(WG) fn step(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.x];\nif (c.live == 0u) {\nreturn;\n}\nlet n = c.count;\nlet h = params.h;\nlet h2 = h * h;\nlet c0 = c.base + c.colours;\nlet nstretch = st[c.base + c.extra.y + 13u];\nlet teth = st[c.base + c.extra.y + 5u];\nfor (var s = 0u; s < params.substeps; s++) {\nlet t = mix(params.t0, params.t1, f32(s + 1u) / f32(params.substeps));\nlet time = params.time + h * f32(s);\nfor (var v = tid; v < n; v += WG) {\nph_air(c, v, time, s == 0u);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nph_predict(c, v, t, h);\n}\nstorageBarrier();\nfor (var k = 0u; k < nstretch; k++) {\nfor (var j = st[c0 + k] + tid; j < st[c0 + k + 1u]; j += WG) {\nsolve(c, j, h2);\n}\nstorageBarrier();\n}\nif (nstretch < c.ncolours) {\nfor (var it = 0u; it < max(params.bend_iters, 1u); it++) {\nfor (var j = st[c0 + nstretch] + tid; j < st[c0 + nstretch + 1u]; j += WG) {\nbend_scatter(c, j, h2, it == 0u);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nph_bend_apply(c, v);\n}\nstorageBarrier();\n}\n}\nif (teth != 0u) {\nfor (var v = tid; v < n; v += WG) {\nph_tether(c, v, teth);\n}\nstorageBarrier();\n}\nfor (var v = tid; v < n; v += WG) {\nph_collide(c, v, t, h);\n}\nstorageBarrier();\n}\n}\noverride WCOLOUR: u32 = 0u;\noverride BFIRST: bool = true;\nconst WW: u32 = 256u;\nfn wide_t() -> f32 {\nreturn mix(params.t0, params.t1, f32(params.iterations + 1u) / f32(params.substeps));\n}\n@compute @workgroup_size(256) fn w_air(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count) {\nreturn;\n}\nph_air(c, v, params.time + params.h * f32(params.iterations), params.iterations == 0u);\n}\n@compute @workgroup_size(256) fn w_predict(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count) {\nreturn;\n}\nph_predict(c, v, wide_t(), params.h);\n}\n@compute @workgroup_size(256) fn w_stretch(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nif (c.live == 0u || WCOLOUR >= st[c.base + c.extra.y + 13u]) {\nreturn;\n}\nlet c0 = c.base + c.colours;\nlet j = st[c0 + WCOLOUR] + wg.x * WW + l;\nif (j < st[c0 + WCOLOUR + 1u]) {\nsolve(c, j, params.h * params.h);\n}\n}\n@compute @workgroup_size(256) fn w_bend(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet nstretch = st[c.base + c.extra.y + 13u];\nif (c.live == 0u || nstretch >= c.ncolours) {\nreturn;\n}\nlet c0 = c.base + c.colours;\nlet j = st[c0 + nstretch] + wg.x * WW + l;\nif (j < st[c0 + nstretch + 1u]) {\nbend_scatter(c, j, params.h * params.h, BFIRST);\n}\n}\n@compute @workgroup_size(256) fn w_bend_apply(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count || st[c.base + c.extra.y + 13u] >= c.ncolours) {\nreturn;\n}\nph_bend_apply(c, v);\n}\n@compute @workgroup_size(256) fn w_tether(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nlet teth = st[c.base + c.extra.y + 5u];\nif (c.live == 0u || v >= c.count || teth == 0u) {\nreturn;\n}\nph_tether(c, v, teth);\n}\n@compute @workgroup_size(256) fn w_collide(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count) {\nreturn;\n}\nph_collide(c, v, wide_t(), params.h);\n}\nconst AL_DECAY: f32 = 0.95;\nconst K_DECAY: f32 = 0.99;\nconst K_RAMP: f32 = 1000.0;\nconst K_MAX: f32 = 10.0;\nconst K_CONTACT: f32 = 100.0;\nconst FRICTION_EPS: f32 = 1e-4;\nfn outer3(a: vec3f, b: vec3f) -> mat3x3f {\nreturn mat3x3f(a * b.x, a * b.y, a * b.z);\n}\nfn hard_reach(x: vec3f, a: vec3f, lim: f32, s: u32, kmin: f32, f: ptr<function, vec3f>, hm: ptr<function, mat3x3f>) {\nlet d = x - a;\nlet l = length(d);\nif (l < 1e-9) {\nreturn;\n}\nlet k = max(bitcast<f32>(torn[s + 1u]), kmin);\nlet fc = k * (lim - l) + bitcast<f32>(torn[s]);\nif (fc < 0.0) {\nlet nd = d / l;\n*f += fc * nd;\n*hm += k * outer3(nd, nd);\n}\n}\nfn dual_reach(x: vec3f, a: vec3f, lim: f32, rest: f32, s: u32, kmin: f32) {\nlet cv = lim - length(x - a);\nlet k = max(bitcast<f32>(torn[s + 1u]), kmin);\nlet lam = clamp(k * cv + bitcast<f32>(torn[s]), -0.1 * K_MAX * kmin * rest, 0.0);\ntorn[s] = bitcast<u32>(lam);\nif (cv < 0.0) {\ntorn[s + 1u] = bitcast<u32>(min(K_MAX * kmin, k + K_RAMP * kmin * (-cv) / max(rest, 1e-6)));\n}\n}\nfn vbd_vertex(c: Cloth, v: u32, h2inv: f32, hd: u32, lim: f32, tc: f32) {\nlet i = c.first + v;\nlet xi = pos[i];\nif (xi.w <= 0.0) {\nreturn;\n}\nlet x = xi.xyz;\nlet mh = h2inv / xi.w;\nvar f = (vel[i].xyz - x) * mh;\nvar hm = mat3x3f(mh, 0.0, 0.0, 0.0, mh, 0.0, 0.0, 0.0, mh);\nlet c11 = stf(hd + 6u);\nlet c22 = stf(hd + 7u);\nlet c12 = stf(hd + 8u);\nlet c33 = stf(hd + 9u);\nlet cs = stf(hd + 10u);\nlet tr = c.base + st[hd];\nlet a0 = c.base + c.adj;\nfor (var k = st[a0 + v]; k < st[a0 + v + 1u]; k++) {\nlet tri = st[a0 + k];\nif (tri_torn(c, tri)) {\ncontinue;\n}\nlet o = c.base + c.tris + tri * 3u;\nlet v1 = st[o + 1u];\nlet v2 = st[o + 2u];\nlet r = tr + tri * 5u;\nlet m = vec4f(stf(r), stf(r + 1u), stf(r + 2u), stf(r + 3u));\nlet area = stf(r + 4u);\nif (area <= 0.0) {\ncontinue;\n}\nvar d = -vec2f(m.x + m.z, m.y + m.w);\nif (v == v1) {\nd = m.xy;\n} else if (v == v2) {\nd = m.zw;\n}\nlet p0 = pos[c.first + st[o]].xyz;\nlet e1 = pos[c.first + v1].xyz - p0;\nlet e2 = pos[c.first + v2].xyz - p0;\nlet f1 = e1 * m.x + e2 * m.z;\nlet f2 = e1 * m.y + e2 * m.w;\nlet g11 = 0.5 * (dot(f1, f1) - 1.0);\nlet g22 = 0.5 * (dot(f2, f2) - 1.0);\nlet g12 = 0.5 * dot(f1, f2);\nlet k11 = c11 * select(1.0, cs, g11 < 0.0);\nlet k22 = c22 * select(1.0, cs, g22 < 0.0);\nlet k12 = c12 * select(1.0, cs, g11 < 0.0 || g22 < 0.0);\nlet s11 = k11 * g11 + k12 * g22;\nlet s22 = k12 * g11 + k22 * g22;\nlet s12 = 2.0 * c33 * g12;\nlet sd = vec2f(s11 * d.x + s12 * d.y, s12 * d.x + s22 * d.y);\nf -= area * (f1 * sd.x + f2 * sd.y);\nlet q11 = k11 * d.x * d.x + c33 * d.y * d.y;\nlet q12 = (k12 + c33) * d.x * d.y;\nlet q22 = k22 * d.y * d.y + c33 * d.x * d.x;\nlet geo = max(0.0, dot(d, sd));\nhm += area * (q11 * outer3(f1, f1) + q12 * (outer3(f1, f2) + outer3(f2, f1)) + q22 * outer3(f2, f2) + mat3x3f(geo, 0.0, 0.0, 0.0, geo, 0.0, 0.0, 0.0, geo));\n}\nlet bc = c.base + st[hd + 3u];\nfor (var k = st[bc + v]; k < st[bc + v + 1u]; k++) {\nlet e = st[bc + k];\nlet o = c.base + (e >> 2u);\nif (c.fabric2.w > 0.0 && torn[c.base + st[o + 9u]] != 0u) {\ncontinue;\n}\nlet w = vec4f(stf(o + 4u), stf(o + 5u), stf(o + 6u), stf(o + 7u));\nlet b = w.x * pos[c.first + st[o]].xyz + w.y * pos[c.first + st[o + 1u]].xyz + w.z * pos[c.first + st[o + 2u]].xyz + w.w * pos[c.first + st[o + 3u]].xyz;\nlet kb = 1.0 / max(stf(o + 8u), 1e-12);\nlet ws = w[e & 3u];\nf -= kb * ws * b;\nlet hb = kb * ws * ws;\nhm += mat3x3f(hb, 0.0, 0.0, 0.0, hb, 0.0, 0.0, 0.0, hb);\n}\nif (lim > 0.0) {\nlet ec = c.base + st[hd + 4u];\nfor (var k = st[ec + v]; k < st[ec + v + 1u]; k++) {\nlet o = c.base + st[ec + k];\nif (c.fabric2.w > 0.0 && torn[o] != 0u) {\ncontinue;\n}\nlet other = select(st[o], st[o + 1u], st[o] == v);\nhard_reach(x, pos[c.first + other].xyz, stf(o + 4u) * (1.0 + lim), o + 1u, c11, &f, &hm);\n}\n}\nlet teth = st[hd + 5u];\nif (teth != 0u) {\nlet t = c.base + teth + v * 2u;\nlet anchor = st[t];\nif (anchor != NONE) {\nhard_reach(x, pos[c.first + anchor].xyz, stf(t + 1u), t, c11, &f, &hm);\n}\n}\nlet kc = K_CONTACT * c11;\nfor (var k = 0u; k < params.colliders; k++) {\nlet cl = colliders[k];\nlet at = mix(cl.p0.xyz, cl.p1.xyz, tc);\nlet cq = nlerp(cl.q0, cl.q1, tc);\nlet local = quat_rotate(quat_conj(cq), x - at);\nlet sd = shape_sdf(cl.p0.w, cl.dims.xyz, local);\nlet depth = c.fabric.z - sd.w;\nif (depth > 0.0) {\nlet nd = quat_rotate(cq, sd.xyz);\nlet fnorm = kc * depth;\nf += fnorm * nd;\nhm += kc * outer3(nd, nd);\nlet lc = local - sd.xyz * sd.w;\nlet vcol = select(vec3f(0.0), (cl.p1.xyz + quat_rotate(cl.q1, lc) - cl.p0.xyz - quat_rotate(cl.q0, lc)) / params.span, params.span > 0.0);\nlet u = (x - prev[i].xyz) - vcol * params.h;\nlet ut = u - dot(u, nd) * nd;\nlet ul = length(ut);\nlet w = sqrt(c.fabric.y * cl.p1.w) * fnorm * select(1.0 / max(ul, 1e-12), (2.0 - ul / FRICTION_EPS) / FRICTION_EPS, ul < FRICTION_EPS);\nf -= w * ut;\nhm += w * (mat3x3f(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0) - outer3(nd, nd));\n}\n}\nlet a = hm[0];\nlet b = hm[1];\nlet cc = hm[2];\nlet r0 = cross(b, cc);\nlet r1 = cross(cc, a);\nlet r2 = cross(a, b);\nlet det = dot(a, r0);\nif (abs(det) < 1e-30) {\nreturn;\n}\npos[i] = vec4f(x + vec3f(dot(r0, f), dot(r1, f), dot(r2, f)) / det, xi.w);\n}\n@compute @workgroup_size(WG) fn vbd(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.x];\nif (c.live == 0u) {\nreturn;\n}\nlet n = c.count;\nlet h = params.h;\nlet h2inv = 1.0 / (h * h);\nlet damp = exp(-c.fabric.x * h);\nlet hd = c.base + c.extra.y;\nlet lim0 = bitcast<f32>(c.extra.z);\nlet lim = select(lim0, max(lim0, c.fabric2.w * 1.5), c.fabric2.w > 0.0 && lim0 > 0.0);\nlet vc = c.base + st[hd + 1u];\nlet ncv = st[hd + 2u];\nlet teth = st[hd + 5u];\nlet m = st[c.base + c.colours + c.ncolours];\nlet c11 = stf(hd + 6u);\nlet iters = max(params.iterations, 1u);\nfor (var s = 0u; s < params.substeps; s++) {\nlet t = mix(params.t0, params.t1, f32(s + 1u) / f32(params.substeps));\nlet time = params.time + h * f32(s);\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nprev[i] = vec4f(aero(c, v, time), 0.0);\n}\nif (s == 0u) {\nfor (var j = tid; j < m; j += WG) {\nlet o = c.base + c.cons + j * 12u;\nif (st[o + 2u] == NONE) {\ntorn[o + 1u] = bitcast<u32>(bitcast<f32>(torn[o + 1u]) * AL_DECAY);\ntorn[o + 2u] = bitcast<u32>(bitcast<f32>(torn[o + 2u]) * K_DECAY);\n}\n}\nif (teth != 0u) {\nfor (var v = tid; v < n; v += WG) {\nlet ts = c.base + teth + v * 2u;\ntorn[ts] = bitcast<u32>(bitcast<f32>(torn[ts]) * AL_DECAY);\ntorn[ts + 1u] = bitcast<u32>(bitcast<f32>(torn[ts + 1u]) * K_DECAY);\n}\n}\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nlet p = pos[i];\nlet air = prev[i].xyz;\nprev[i] = p;\nif (p.w <= 0.0) {\npos[i] = vec4f(pin_pos(c, v, t), 0.0);\ncontinue;\n}\nlet y = p.xyz + h * (vel[i].xyz + h * (params.gravity + air));\nvel[i] = vec4f(y, 0.0);\npos[i] = vec4f(y, p.w);\n}\nstorageBarrier();\nfor (var it = 0u; it < iters; it++) {\nfor (var k = 0u; k < ncv; k++) {\nlet lo = st[vc + k];\nlet hi = st[vc + k + 1u];\nfor (var j = lo + tid; j < hi; j += WG) {\nvbd_vertex(c, st[vc + ncv + 1u + j], h2inv, hd, lim, t);\n}\nstorageBarrier();\n}\nfor (var j = tid; j < m; j += WG) {\nlet o = c.base + c.cons + j * 12u;\nif (st[o + 2u] != NONE) {\ncontinue;\n}\nif (c.fabric2.w > 0.0) {\nif (torn[o] != 0u) {\ncontinue;\n}\nlet rest = stf(o + 4u);\nif (length(pos[c.first + st[o + 1u]].xyz - pos[c.first + st[o]].xyz) > rest * (1.0 + c.fabric2.w)) {\ntorn[o] = 1u;\ntorn[c.base + st[hd + 12u] + j] = 1u;\ncontinue;\n}\n}\nif (lim > 0.0) {\nlet rest = stf(o + 4u);\ndual_reach(pos[c.first + st[o]].xyz, pos[c.first + st[o + 1u]].xyz, rest * (1.0 + lim), rest, o + 1u, c11);\n}\n}\nif (teth != 0u) {\nfor (var v = tid; v < n; v += WG) {\nlet ts = c.base + teth + v * 2u;\nlet anchor = st[ts];\nif (anchor != NONE && pos[c.first + v].w > 0.0) {\nlet lt = stf(ts + 1u);\ndual_reach(pos[c.first + v].xyz, pos[c.first + anchor].xyz, lt, lt, ts, c11);\n}\n}\n}\nstorageBarrier();\n}\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nif (pos[i].w > 0.0) {\ncollide(c, i, t, h);\nbackstop(c, v, t);\n}\nlet nv = (pos[i].xyz - prev[i].xyz) / h * damp;\nlet sp = length(nv);\nvel[i] = vec4f(select(nv, nv * (params.max_speed / sp), sp > params.max_speed), 0.0);\n}\nstorageBarrier();\n}\n}\noverride VCOLOUR: u32 = 0u;\nfn vbd_limit(c: Cloth) -> f32 {\nlet lim0 = bitcast<f32>(c.extra.z);\nreturn select(lim0, max(lim0, c.fabric2.w * 1.5), c.fabric2.w > 0.0 && lim0 > 0.0);\n}\n@compute @workgroup_size(256) fn vw_warm(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nif (c.live == 0u) {\nreturn;\n}\nlet j = wg.x * WW + l;\nlet m = st[c.base + c.colours + c.ncolours];\nif (j < m) {\nlet o = c.base + c.cons + j * 12u;\nif (st[o + 2u] == NONE) {\ntorn[o + 1u] = bitcast<u32>(bitcast<f32>(torn[o + 1u]) * AL_DECAY);\ntorn[o + 2u] = bitcast<u32>(bitcast<f32>(torn[o + 2u]) * K_DECAY);\n}\n}\nlet teth = st[c.base + c.extra.y + 5u];\nif (teth != 0u && j < c.count) {\nlet ts = c.base + teth + j * 2u;\ntorn[ts] = bitcast<u32>(bitcast<f32>(torn[ts]) * AL_DECAY);\ntorn[ts + 1u] = bitcast<u32>(bitcast<f32>(torn[ts + 1u]) * K_DECAY);\n}\n}\n@compute @workgroup_size(256) fn vw_predict(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count) {\nreturn;\n}\nlet i = c.first + v;\nlet h = params.h;\nlet p = pos[i];\nlet air = prev[i].xyz;\nprev[i] = p;\nif (p.w <= 0.0) {\npos[i] = vec4f(pin_pos(c, v, wide_t()), 0.0);\nreturn;\n}\nlet y = p.xyz + h * (vel[i].xyz + h * (params.gravity + air));\nvel[i] = vec4f(y, 0.0);\npos[i] = vec4f(y, p.w);\n}\n@compute @workgroup_size(256) fn vw_colour(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet hd = c.base + c.extra.y;\nlet ncv = st[hd + 2u];\nif (c.live == 0u || VCOLOUR >= ncv) {\nreturn;\n}\nlet vc = c.base + st[hd + 1u];\nlet j = st[vc + VCOLOUR] + wg.x * WW + l;\nif (j < st[vc + VCOLOUR + 1u]) {\nvbd_vertex(c, st[vc + ncv + 1u + j], 1.0 / (params.h * params.h), hd, vbd_limit(c), wide_t());\n}\n}\n@compute @workgroup_size(256) fn vw_dual(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nif (c.live == 0u) {\nreturn;\n}\nlet hd = c.base + c.extra.y;\nlet c11 = stf(hd + 6u);\nlet lim = vbd_limit(c);\nlet j = wg.x * WW + l;\nlet m = st[c.base + c.colours + c.ncolours];\nif (j < m) {\nlet o = c.base + c.cons + j * 12u;\nif (st[o + 2u] == NONE) {\nvar live = true;\nif (c.fabric2.w > 0.0) {\nif (torn[o] != 0u) {\nlive = false;\n} else {\nlet rest = stf(o + 4u);\nif (length(pos[c.first + st[o + 1u]].xyz - pos[c.first + st[o]].xyz) > rest * (1.0 + c.fabric2.w)) {\ntorn[o] = 1u;\ntorn[c.base + st[hd + 12u] + j] = 1u;\nlive = false;\n}\n}\n}\nif (live && lim > 0.0) {\nlet rest = stf(o + 4u);\ndual_reach(pos[c.first + st[o]].xyz, pos[c.first + st[o + 1u]].xyz, rest * (1.0 + lim), rest, o + 1u, c11);\n}\n}\n}\nlet teth = st[hd + 5u];\nif (teth != 0u && j < c.count) {\nlet ts = c.base + teth + j * 2u;\nlet anchor = st[ts];\nif (anchor != NONE && pos[c.first + j].w > 0.0) {\nlet lt = stf(ts + 1u);\ndual_reach(pos[c.first + j].xyz, pos[c.first + anchor].xyz, lt, lt, ts, c11);\n}\n}\n}\n@group(0) @binding(8) var<storage, read_write> pool: array<f32>;\n@group(0) @binding(12) var<storage, read_write> tri_out: array<u32>;\n@compute @workgroup_size(WG) fn write(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.x];\nif (c.live == 0u) {\nreturn;\n}\nlet a = params.alpha;\nlet inv = quat_conj(c.rot1);\nlet scale = 1.0 / c.origin1.w;\nlet a0 = c.base + c.adj;\nfor (var v = tid; v < c.count; v += WG) {\nlet i = c.first + v;\nlet p = mix(last[i].xyz, pos[i].xyz, a);\nvar nrm = vec3f(0.0);\nfor (var k = st[a0 + v]; k < st[a0 + v + 1u]; k++) {\nif (tri_torn(c, st[a0 + k])) {\ncontinue;\n}\nlet o = c.base + c.tris + st[a0 + k] * 3u;\nlet j0 = c.first + st[o];\nlet j1 = c.first + st[o + 1u];\nlet j2 = c.first + st[o + 2u];\nlet q0 = mix(last[j0].xyz, pos[j0].xyz, a);\nnrm += cross(mix(last[j1].xyz, pos[j1].xyz, a) - q0, mix(last[j2].xyz, pos[j2].xyz, a) - q0);\n}\nlet ln = quat_rotate(inv, nrm);\nlet len = length(ln);\nlet nl = select(vec3f(0.0, 1.0, 0.0), ln / len, len > 1e-20);\nlet lp = quat_rotate(inv, p - c.origin1.xyz) * scale;\nlet o = (c.pool + v) * 8u;\npool[o] = lp.x;\npool[o + 1u] = lp.y;\npool[o + 2u] = lp.z;\npool[o + 3u] = nl.x;\npool[o + 4u] = nl.y;\npool[o + 5u] = nl.z;\n}\nif (c.fabric2.w > 0.0) {\nlet ntri = (c.colours - c.tris) / 3u;\nfor (var t = tid; t < ntri; t += WG) {\nlet o = c.base + c.tris + t * 3u;\nlet gone = tri_torn(c, t);\nlet at = c.extra.x + t * 3u;\ntri_out[at] = st[o];\ntri_out[at + 1u] = select(st[o + 1u], st[o], gone);\ntri_out[at + 2u] = select(st[o + 2u], st[o], gone);\n}\n}\n}\n@group(0) @binding(9) var<storage, read_write> hash: array<atomic<u32>>;\n@group(0) @binding(10) var<storage, read_write> delta: array<vec4f>;\nvar<workgroup> partial: array<u32, 1024>;\nfn cell_hash(c: vec3i, mask: u32) -> u32 {\nreturn (bitcast<u32>(c.x * 92837111) ^ bitcast<u32>(c.y * 689287499) ^ bitcast<u32>(c.z * 283923481)) & mask;\n}\n@compute @workgroup_size(WG) fn selfcollide(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.x];\nif (c.live == 0u || c.table == 0u) {\nreturn;\n}\nlet n = c.count;\nlet table = c.table;\nlet mask = table - 1u;\nlet hb = c.hash;\nlet ent = hb + table + 1u;\nlet d = c.fabric2.z;\nlet inv = 1.0 / d;\nlet dt = params.h * f32(params.substeps);\nfor (var k = tid; k <= table; k += WG) {\natomicStore(&hash[hb + k], 0u);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nlet h = cell_hash(vec3i(floor(pos[c.first + v].xyz * inv)), mask);\natomicAdd(&hash[hb + h], 1u);\n}\nstorageBarrier();\nlet per = (table + WG - 1u) / WG;\nlet lo = tid * per;\nlet hi = min(lo + per, table);\nvar run = 0u;\nfor (var k = lo; k < hi; k++) {\nrun += atomicLoad(&hash[hb + k]);\n}\npartial[tid] = run;\nworkgroupBarrier();\nif (tid == 0u) {\nvar acc = 0u;\nfor (var k = 0u; k < WG; k++) {\nlet x = partial[k];\npartial[k] = acc;\nacc += x;\n}\n}\nworkgroupBarrier();\nrun = partial[tid];\nfor (var k = lo; k < hi; k++) {\nrun += atomicLoad(&hash[hb + k]);\natomicStore(&hash[hb + k], run);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nlet h = cell_hash(vec3i(floor(pos[c.first + v].xyz * inv)), mask);\nlet at = atomicSub(&hash[hb + h], 1u) - 1u;\natomicStore(&hash[ent + at], v);\n}\nstorageBarrier();\nfor (var k = tid; k < table; k += WG) {\nlet lo = atomicLoad(&hash[hb + k]);\nlet hi = select(atomicLoad(&hash[hb + k + 1u]), n, k == mask);\nfor (var a = lo + 1u; a < hi; a++) {\nlet key = atomicLoad(&hash[ent + a]);\nvar b = a;\nwhile (b > lo && atomicLoad(&hash[ent + b - 1u]) > key) {\natomicStore(&hash[ent + b], atomicLoad(&hash[ent + b - 1u]));\nb--;\n}\natomicStore(&hash[ent + b], key);\n}\n}\nstorageBarrier();\nlet scale = c.origin1.w;\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nlet a = pos[i];\nvar push = vec3f(0.0);\nvar hits = 0.0;\nif (a.w > 0.0) {\nlet r = c.base + v * 4u;\nlet ra = vec3f(stf(r), stf(r + 1u), stf(r + 2u));\nlet cell = vec3i(floor(a.xyz * inv));\nfor (var dz = -1; dz <= 1; dz++) {\nfor (var dy = -1; dy <= 1; dy++) {\nfor (var dx = -1; dx <= 1; dx++) {\nlet h = cell_hash(cell + vec3i(dx, dy, dz), mask);\nlet end = select(atomicLoad(&hash[hb + h + 1u]), n, h == mask);\nfor (var k = atomicLoad(&hash[hb + h]); k < end; k++) {\nlet u = atomicLoad(&hash[ent + k]);\nif (u == v) {\ncontinue;\n}\nlet b = pos[c.first + u];\nlet diff = a.xyz - b.xyz;\nlet dist = length(diff);\nif (dist >= d || dist < 1e-9) {\ncontinue;\n}\nlet q = c.base + u * 4u;\nif (length(ra - vec3f(stf(q), stf(q + 1u), stf(q + 2u))) * scale < d) {\ncontinue;\n}\npush += diff * ((d - dist) / dist * a.w / (a.w + b.w));\nhits += 1.0;\n}\n}\n}\n}\n}\ndelta[i] = vec4f(push / max(hits, 1.0), 0.0);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nlet p = delta[i].xyz;\npos[i] = vec4f(pos[i].xyz + p, pos[i].w);\nvel[i] = vec4f(vel[i].xyz + p * (0.5 / dt), 0.0);\n}\n}\n@compute @workgroup_size(256) fn sw_clear(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet k = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || k > c.table) {\nreturn;\n}\natomicStore(&hash[c.hash + k], 0u);\n}\n@compute @workgroup_size(256) fn sw_count(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || v >= c.count) {\nreturn;\n}\nlet h = cell_hash(vec3i(floor(pos[c.first + v].xyz * (1.0 / c.fabric2.z))), c.table - 1u);\natomicAdd(&hash[c.hash + h], 1u);\n}\n@compute @workgroup_size(WG) fn sw_scan(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.y];\nif (c.live == 0u || c.table == 0u) {\nreturn;\n}\nlet table = c.table;\nlet hb = c.hash;\nlet per = (table + WG - 1u) / WG;\nlet lo = tid * per;\nlet hi = min(lo + per, table);\nvar run = 0u;\nfor (var k = lo; k < hi; k++) {\nrun += atomicLoad(&hash[hb + k]);\n}\npartial[tid] = run;\nworkgroupBarrier();\nif (tid == 0u) {\nvar acc = 0u;\nfor (var k = 0u; k < WG; k++) {\nlet x = partial[k];\npartial[k] = acc;\nacc += x;\n}\n}\nworkgroupBarrier();\nrun = partial[tid];\nfor (var k = lo; k < hi; k++) {\nrun += atomicLoad(&hash[hb + k]);\natomicStore(&hash[hb + k], run);\n}\n}\n@compute @workgroup_size(256) fn sw_fill(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || v >= c.count) {\nreturn;\n}\nlet h = cell_hash(vec3i(floor(pos[c.first + v].xyz * (1.0 / c.fabric2.z))), c.table - 1u);\nlet at = atomicSub(&hash[c.hash + h], 1u) - 1u;\natomicStore(&hash[c.hash + c.table + 1u + at], v);\n}\n@compute @workgroup_size(256) fn sw_sort(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet k = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || k >= c.table) {\nreturn;\n}\nlet hb = c.hash;\nlet ent = hb + c.table + 1u;\nlet lo = atomicLoad(&hash[hb + k]);\nlet hi = select(atomicLoad(&hash[hb + k + 1u]), c.count, k == c.table - 1u);\nfor (var a = lo + 1u; a < hi; a++) {\nlet key = atomicLoad(&hash[ent + a]);\nvar b = a;\nwhile (b > lo && atomicLoad(&hash[ent + b - 1u]) > key) {\natomicStore(&hash[ent + b], atomicLoad(&hash[ent + b - 1u]));\nb--;\n}\natomicStore(&hash[ent + b], key);\n}\n}\n@compute @workgroup_size(256) fn sw_push(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || v >= c.count) {\nreturn;\n}\nlet n = c.count;\nlet mask = c.table - 1u;\nlet hb = c.hash;\nlet ent = hb + c.table + 1u;\nlet d = c.fabric2.z;\nlet inv = 1.0 / d;\nlet scale = c.origin1.w;\nlet i = c.first + v;\nlet a = pos[i];\nvar push = vec3f(0.0);\nvar hits = 0.0;\nif (a.w > 0.0) {\nlet r = c.base + v * 4u;\nlet ra = vec3f(stf(r), stf(r + 1u), stf(r + 2u));\nlet cell = vec3i(floor(a.xyz * inv));\nfor (var dz = -1; dz <= 1; dz++) {\nfor (var dy = -1; dy <= 1; dy++) {\nfor (var dx = -1; dx <= 1; dx++) {\nlet h = cell_hash(cell + vec3i(dx, dy, dz), mask);\nlet end = select(atomicLoad(&hash[hb + h + 1u]), n, h == mask);\nfor (var k = atomicLoad(&hash[hb + h]); k < end; k++) {\nlet u = atomicLoad(&hash[ent + k]);\nif (u == v) {\ncontinue;\n}\nlet b = pos[c.first + u];\nlet diff = a.xyz - b.xyz;\nlet dist = length(diff);\nif (dist >= d || dist < 1e-9) {\ncontinue;\n}\nlet q = c.base + u * 4u;\nif (length(ra - vec3f(stf(q), stf(q + 1u), stf(q + 2u))) * scale < d) {\ncontinue;\n}\npush += diff * ((d - dist) / dist * a.w / (a.w + b.w));\nhits += 1.0;\n}\n}\n}\n}\n}\ndelta[i] = vec4f(push / max(hits, 1.0), 0.0);\n}\n@compute @workgroup_size(256) fn sw_apply(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || v >= c.count) {\nreturn;\n}\nlet i = c.first + v;\nlet p = delta[i].xyz;\npos[i] = vec4f(pos[i].xyz + p, pos[i].w);\nvel[i] = vec4f(vel[i].xyz + p * (0.5 / (params.h * f32(params.substeps))), 0.0);\n}\nfn quat_rotate(q: vec4f, v: vec3f) -> vec3f {\nlet t = 2.0 * cross(q.xyz, v);\nreturn v + q.w * t + cross(q.xyz, t);\n}";
5
+ };
@@ -0,0 +1,8 @@
1
+ // GENERATED by tools/shaders-gen.mjs from shaders/**/*.wesl. Do not edit.
2
+ // Regenerate with `node tools/shaders-gen.mjs`; CI runs it with --check.
3
+ /** Linked from shaders/kanduka/cloth.wesl. */
4
+ export const kandukaCloth = {
5
+ hash: '0473fc3b76a0ea73',
6
+ wgsl: "struct Params {\ngravity: vec3f,\nh: f32,\nwind: vec3f,\ntime: f32,\ngust: vec4f,\nsubsteps: u32,\ncolliders: u32,\nt0: f32,\nt1: f32,\nalpha: f32,\nspan: f32,\nmax_speed: f32,\niterations: u32,\nbend_iters: u32,\npad0: u32,\npad1: u32,\npad2: u32,\n}\nstruct Cloth {\nfirst: u32,\ncount: u32,\nbase: u32,\npool: u32,\ntris: u32,\nadj: u32,\ncolours: u32,\nncolours: u32,\ncons: u32,\nlive: u32,\nhash: u32,\ntable: u32,\norigin0: vec4f,\nrot0: vec4f,\norigin1: vec4f,\nrot1: vec4f,\nfabric: vec4f,\nfabric2: vec4f,\nextra: vec4u,\n}\nstruct Collider {\np0: vec4f,\nq0: vec4f,\np1: vec4f,\nq1: vec4f,\ndims: vec4f,\n}\nconst NONE: u32 = 0xffffffffu;\noverride WG: u32 = 256u;\nconst SPHERE: f32 = 0.0;\nconst CAPSULE: f32 = 1.0;\nconst BOX: f32 = 2.0;\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read> cloths: array<Cloth>;\n@group(0) @binding(2) var<storage, read> st: array<u32>;\n@group(0) @binding(3) var<storage, read_write> pos: array<vec4f>;\n@group(0) @binding(4) var<storage, read_write> prev: array<vec4f>;\n@group(0) @binding(5) var<storage, read_write> vel: array<vec4f>;\n@group(0) @binding(6) var<storage, read_write> last: array<vec4f>;\n@group(0) @binding(7) var<storage, read> colliders: array<Collider>;\n@group(0) @binding(11) var<storage, read_write> torn: array<u32>;\nfn tri_torn(c: Cloth, tri: u32) -> bool {\nif (c.fabric2.w <= 0.0) {\nreturn false;\n}\nlet te = c.base + c.cons + st[c.base + c.colours + c.ncolours] * 12u + tri * 3u;\nreturn torn[c.base + st[te]] != 0u || torn[c.base + st[te + 1u]] != 0u || torn[c.base + st[te + 2u]] != 0u;\n}\nfn stf(i: u32) -> f32 {\nreturn bitcast<f32>(st[i]);\n}\nfn quat_conj(q: vec4f) -> vec4f {\nreturn vec4f(-q.xyz, q.w);\n}\nfn nlerp(a: vec4f, b: vec4f, t: f32) -> vec4f {\nlet s = select(1.0, -1.0, dot(a, b) < 0.0);\nreturn normalize(mix(a, b * s, t));\n}\nfn wind_at(p: vec3f, t: f32) -> vec3f {\nlet w = params.wind;\nlet speed = length(w);\nif (speed <= 0.0) {\nreturn vec3f(0.0);\n}\nlet g = params.gust;\nlet gust = 1.0 + g.y * (0.6 * sin(6.2831853 * g.z * t) + 0.4 * sin(6.2831853 * g.z * 2.3 * t + 1.7));\nlet q = p - w * t * 0.35;\nlet n = vec3f(sin(q.y * 1.7 + q.z * 0.9 + t * 1.3) + 0.5 * sin(q.x * 2.9 - q.y * 2.1 + t * 2.7), sin(q.z * 1.3 - q.x * 1.1 + t * 1.9) + 0.5 * sin(q.y * 3.1 + q.z * 2.3 - t * 2.3), sin(q.x * 1.5 + q.y * 0.7 - t * 1.6) + 0.5 * sin(q.z * 2.7 - q.x * 1.9 + t * 3.1));\nreturn w * gust + n * (g.x * speed * 0.5);\n}\nfn aero(c: Cloth, v: u32, t: f32) -> vec3f {\nlet rho = params.gust.w;\nif (rho <= 0.0 || pos[c.first + v].w <= 0.0) {\nreturn vec3f(0.0);\n}\nlet a0 = c.base + c.adj;\nlet lo = st[a0 + v];\nlet hi = st[a0 + v + 1u];\nlet air = wind_at(pos[c.first + v].xyz, t) * c.fabric2.y;\nvar f = vec3f(0.0);\nfor (var k = lo; k < hi; k++) {\nlet tri = st[a0 + k];\nif (tri_torn(c, tri)) {\ncontinue;\n}\nlet o = c.base + c.tris + tri * 3u;\nlet i0 = c.first + st[o];\nlet i1 = c.first + st[o + 1u];\nlet i2 = c.first + st[o + 2u];\nlet p0 = pos[i0].xyz;\nlet cr = cross(pos[i1].xyz - p0, pos[i2].xyz - p0);\nlet len = length(cr);\nif (len < 1e-12) {\ncontinue;\n}\nlet n = cr / len;\nlet area = 0.5 * len;\nlet rel = air - (vel[i0].xyz + vel[i1].xyz + vel[i2].xyz) / 3.0;\nlet vn = dot(rel, n);\nlet vt = rel - vn * n;\nf += 0.5 * rho * area * (c.fabric.w * abs(vn) * vn * n + c.fabric2.x * length(vt) * vt) / 3.0;\n}\nlet acc = f * pos[c.first + v].w;\nlet room = length(air - vel[c.first + v].xyz) / params.h;\nlet al = length(acc);\nreturn select(acc, acc * (room / al), al > room);\n}\nfn pin_target(c: Cloth, v: u32, t: f32) -> vec3f {\nlet r = c.base + v * 4u;\nlet local = vec3f(stf(r), stf(r + 1u), stf(r + 2u));\nlet o = mix(c.origin0, c.origin1, t);\nreturn o.xyz + quat_rotate(nlerp(c.rot0, c.rot1, t), local * o.w);\n}\nfn solve(c: Cloth, j: u32, h2: f32) {\nlet o = c.base + c.cons + j * 12u;\nlet i0 = c.first + st[o];\nlet i1 = c.first + st[o + 1u];\nlet e2 = st[o + 2u];\nif (e2 == NONE) {\nlet a = pos[i0];\nlet b = pos[i1];\nlet w = a.w + b.w;\nlet d = b.xyz - a.xyz;\nlet len = length(d);\nif (w <= 0.0 || len < 1e-9) {\nreturn;\n}\nlet cv = len - stf(o + 4u);\nif (c.fabric2.w > 0.0) {\nif (torn[o] != 0u) {\nreturn;\n}\nif (cv > stf(o + 4u) * c.fabric2.w) {\ntorn[o] = 1u;\ntorn[c.base + st[c.base + c.extra.y + 12u] + j] = 1u;\nreturn;\n}\n}\nlet alpha = select(stf(o + 5u), stf(o + 6u), cv < 0.0) / h2;\nlet dl = -cv / (w + alpha);\nlet n = d / len;\npos[i0] = vec4f(a.xyz - a.w * dl * n, a.w);\npos[i1] = vec4f(b.xyz + b.w * dl * n, b.w);\nreturn;\n}\nif (c.fabric2.w > 0.0 && torn[c.base + st[o + 9u]] != 0u) {\nreturn;\n}\nlet i2 = c.first + e2;\nlet i3 = c.first + st[o + 3u];\nlet x0 = pos[i0];\nlet x1 = pos[i1];\nlet x2 = pos[i2];\nlet x3 = pos[i3];\nlet k = vec4f(stf(o + 4u), stf(o + 5u), stf(o + 6u), stf(o + 7u));\nlet b = k.x * x0.xyz + k.y * x1.xyz + k.z * x2.xyz + k.w * x3.xyz;\nlet wk = x0.w * k.x * k.x + x1.w * k.y * k.y + x2.w * k.z * k.z + x3.w * k.w * k.w;\nlet denom = wk + stf(o + 8u) / h2;\nif (wk <= 0.0) {\nreturn;\n}\nlet s = -1.0 / denom;\npos[i0] = vec4f(x0.xyz + x0.w * k.x * s * b, x0.w);\npos[i1] = vec4f(x1.xyz + x1.w * k.y * s * b, x1.w);\npos[i2] = vec4f(x2.xyz + x2.w * k.z * s * b, x2.w);\npos[i3] = vec4f(x3.xyz + x3.w * k.w * s * b, x3.w);\n}\nfn anchor_of(c: Cloth, v: u32) -> u32 {\nlet at = st[c.base + c.extra.y + 11u];\nif (at == 0u) {\nreturn NONE;\n}\nlet k = st[c.base + at + v];\nreturn select(params.colliders + c.extra.w + k, NONE, k == NONE);\n}\nfn pin_pos(c: Cloth, v: u32, t: f32) -> vec3f {\nlet k = anchor_of(c, v);\nif (k == NONE) {\nreturn pin_target(c, v, t);\n}\nreturn mix(colliders[k].p0.xyz, colliders[k].p1.xyz, t);\n}\nfn backstop(c: Cloth, v: u32, t: f32) {\nlet i = c.first + v;\nlet k = anchor_of(c, v);\nif (k == NONE || pos[i].w <= 0.0) {\nreturn;\n}\nlet a = colliders[k];\nlet n = normalize(mix(a.q0.xyz, a.q1.xyz, t));\nlet d = dot(pos[i].xyz - mix(a.p0.xyz, a.p1.xyz, t), n) + a.p1.w;\nif (d < 0.0) {\npos[i] = vec4f(pos[i].xyz - d * n, pos[i].w);\n}\n}\nconst FIXED: f32 = 1e9;\n@group(0) @binding(10) var<storage, read_write> dbend: array<atomic<i32>>;\nfn bend_scatter(c: Cloth, j: u32, h2: f32, first: bool) {\nlet o = c.base + c.cons + j * 12u;\nif (c.fabric2.w > 0.0 && torn[c.base + st[o + 9u]] != 0u) {\nreturn;\n}\nlet ids = vec4u(c.first + st[o], c.first + st[o + 1u], c.first + st[o + 2u], c.first + st[o + 3u]);\nlet x0 = pos[ids.x];\nlet x1 = pos[ids.y];\nlet x2 = pos[ids.z];\nlet x3 = pos[ids.w];\nlet w = vec4f(stf(o + 4u), stf(o + 5u), stf(o + 6u), stf(o + 7u));\nlet inv = vec4f(x0.w, x1.w, x2.w, x3.w);\nlet bc = c.base + st[c.base + c.extra.y + 3u];\nvar split = vec4f(1.0);\nfor (var q = 0u; q < 4u; q++) {\nlet v = st[o + q];\nsplit[q] = f32(max(1u, st[bc + v + 1u] - st[bc + v]));\n}\nlet wk = dot(split * inv, w * w);\nif (wk <= 0.0) {\nreturn;\n}\nlet b = w.x * x0.xyz + w.y * x1.xyz + w.z * x2.xyz + w.w * x3.xyz;\nlet at = stf(o + 8u) / h2;\nvar lam = vec3f(0.0);\nif (!first) {\nlam = vec3f(bitcast<f32>(torn[o + 3u]), bitcast<f32>(torn[o + 4u]), bitcast<f32>(torn[o + 5u]));\n}\nlet r = b + at * lam;\nlet s = -FIXED / (wk + at);\nif (params.bend_iters > 1u) {\nlet nl = lam + r * (s / FIXED);\ntorn[o + 3u] = bitcast<u32>(nl.x);\ntorn[o + 4u] = bitcast<u32>(nl.y);\ntorn[o + 5u] = bitcast<u32>(nl.z);\n}\nfor (var q = 0u; q < 4u; q++) {\nif (inv[q] <= 0.0) {\ncontinue;\n}\nlet d = vec3i(round(r * (inv[q] * w[q] * s)));\nlet at = ids[q] * 4u;\natomicAdd(&dbend[at], d.x);\natomicAdd(&dbend[at + 1u], d.y);\natomicAdd(&dbend[at + 2u], d.z);\n}\n}\nfn shape_sdf(kind: f32, dims: vec3f, p: vec3f) -> vec4f {\nif (kind == SPHERE) {\nlet l = length(p);\nreturn vec4f(select(vec3f(0.0, 1.0, 0.0), p / l, l > 1e-9), l - dims.x);\n}\nif (kind == CAPSULE) {\nlet q = vec3f(p.x, p.y - clamp(p.y, -dims.x, dims.x), p.z);\nlet l = length(q);\nreturn vec4f(select(vec3f(0.0, 1.0, 0.0), q / l, l > 1e-9), l - dims.y);\n}\nlet q = abs(p) - dims;\nlet outside = max(q, vec3f(0.0));\nlet lo = length(outside);\nif (lo > 0.0) {\nreturn vec4f(outside * sign(p) / lo, lo);\n}\nif (q.x >= q.y && q.x >= q.z) {\nreturn vec4f(sign(p.x), 0.0, 0.0, q.x);\n}\nif (q.y >= q.z) {\nreturn vec4f(0.0, sign(p.y), 0.0, q.y);\n}\nreturn vec4f(0.0, 0.0, sign(p.z), q.z);\n}\nfn collide(c: Cloth, i: u32, t: f32, h: f32) {\nvar x = pos[i].xyz;\nlet start = prev[i].xyz;\nlet thick = c.fabric.z;\nfor (var k = 0u; k < params.colliders; k++) {\nlet cl = colliders[k];\nlet cp = mix(cl.p0.xyz, cl.p1.xyz, t);\nlet cq = nlerp(cl.q0, cl.q1, t);\nlet local = quat_rotate(quat_conj(cq), x - cp);\nlet sd = shape_sdf(cl.p0.w, cl.dims.xyz, local);\nlet depth = thick - sd.w;\nif (depth <= 0.0) {\ncontinue;\n}\nlet n = quat_rotate(cq, sd.xyz);\nx += n * depth;\nlet lc = local - sd.xyz * sd.w;\nlet w0 = cl.p0.xyz + quat_rotate(cl.q0, lc);\nlet w1 = cl.p1.xyz + quat_rotate(cl.q1, lc);\nlet vc = select(vec3f(0.0), (w1 - w0) / params.span, params.span > 0.0);\nlet rel = (x - start) - vc * h;\nlet tang = rel - dot(rel, n) * n;\nlet tl = length(tang);\nlet mu = sqrt(c.fabric.y * cl.p1.w);\nif (tl > 0.0) {\nx -= tang * min(1.0, mu * depth / tl);\n}\n}\npos[i] = vec4f(x, pos[i].w);\n}\nfn ph_air(c: Cloth, v: u32, time: f32, first: bool) {\nlet i = c.first + v;\nprev[i] = vec4f(aero(c, v, time), 0.0);\nif (first) {\nfor (var q = 0u; q < 4u; q++) {\natomicStore(&dbend[i * 4u + q], 0);\n}\n}\n}\nfn ph_predict(c: Cloth, v: u32, t: f32, h: f32) {\nlet i = c.first + v;\nlet p = pos[i];\nif (p.w <= 0.0) {\nprev[i] = p;\npos[i] = vec4f(pin_pos(c, v, t), 0.0);\nreturn;\n}\nlet vv = vel[i].xyz + h * (params.gravity + prev[i].xyz);\nprev[i] = p;\nvel[i] = vec4f(vv, 0.0);\npos[i] = vec4f(p.xyz + h * vv, p.w);\n}\nfn ph_bend_apply(c: Cloth, v: u32) {\nlet i = c.first + v;\nlet d = vec3f(f32(atomicLoad(&dbend[i * 4u])), f32(atomicLoad(&dbend[i * 4u + 1u])), f32(atomicLoad(&dbend[i * 4u + 2u])));\npos[i] = vec4f(pos[i].xyz + d / FIXED, pos[i].w);\nfor (var q = 0u; q < 4u; q++) {\natomicStore(&dbend[i * 4u + q], 0);\n}\n}\nfn ph_tether(c: Cloth, v: u32, teth: u32) {\nlet i = c.first + v;\nlet anchor = st[c.base + teth + v * 2u];\nif (anchor != NONE && pos[i].w > 0.0) {\nlet a = pos[c.first + anchor].xyz;\nlet d = pos[i].xyz - a;\nlet l = length(d);\nlet lim = stf(c.base + teth + v * 2u + 1u);\nif (l > lim) {\npos[i] = vec4f(a + d * (lim / l), pos[i].w);\n}\n}\n}\nfn ph_collide(c: Cloth, v: u32, t: f32, h: f32) {\nlet i = c.first + v;\nif (pos[i].w > 0.0) {\ncollide(c, i, t, h);\nbackstop(c, v, t);\n}\nlet nv = (pos[i].xyz - prev[i].xyz) / h * exp(-c.fabric.x * h);\nlet sp = length(nv);\nvel[i] = vec4f(select(nv, nv * (params.max_speed / sp), sp > params.max_speed), 0.0);\n}\n@compute @workgroup_size(WG) fn step(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.x];\nif (c.live == 0u) {\nreturn;\n}\nlet n = c.count;\nlet h = params.h;\nlet h2 = h * h;\nlet c0 = c.base + c.colours;\nlet nstretch = st[c.base + c.extra.y + 13u];\nlet teth = st[c.base + c.extra.y + 5u];\nfor (var s = 0u; s < params.substeps; s++) {\nlet t = mix(params.t0, params.t1, f32(s + 1u) / f32(params.substeps));\nlet time = params.time + h * f32(s);\nfor (var v = tid; v < n; v += WG) {\nph_air(c, v, time, s == 0u);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nph_predict(c, v, t, h);\n}\nstorageBarrier();\nfor (var k = 0u; k < nstretch; k++) {\nfor (var j = st[c0 + k] + tid; j < st[c0 + k + 1u]; j += WG) {\nsolve(c, j, h2);\n}\nstorageBarrier();\n}\nif (nstretch < c.ncolours) {\nfor (var it = 0u; it < max(params.bend_iters, 1u); it++) {\nfor (var j = st[c0 + nstretch] + tid; j < st[c0 + nstretch + 1u]; j += WG) {\nbend_scatter(c, j, h2, it == 0u);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nph_bend_apply(c, v);\n}\nstorageBarrier();\n}\n}\nif (teth != 0u) {\nfor (var v = tid; v < n; v += WG) {\nph_tether(c, v, teth);\n}\nstorageBarrier();\n}\nfor (var v = tid; v < n; v += WG) {\nph_collide(c, v, t, h);\n}\nstorageBarrier();\n}\n}\noverride WCOLOUR: u32 = 0u;\noverride BFIRST: bool = true;\nconst WW: u32 = 256u;\nfn wide_t() -> f32 {\nreturn mix(params.t0, params.t1, f32(params.iterations + 1u) / f32(params.substeps));\n}\n@compute @workgroup_size(256) fn w_air(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count) {\nreturn;\n}\nph_air(c, v, params.time + params.h * f32(params.iterations), params.iterations == 0u);\n}\n@compute @workgroup_size(256) fn w_predict(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count) {\nreturn;\n}\nph_predict(c, v, wide_t(), params.h);\n}\n@compute @workgroup_size(256) fn w_stretch(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nif (c.live == 0u || WCOLOUR >= st[c.base + c.extra.y + 13u]) {\nreturn;\n}\nlet c0 = c.base + c.colours;\nlet j = st[c0 + WCOLOUR] + wg.x * WW + l;\nif (j < st[c0 + WCOLOUR + 1u]) {\nsolve(c, j, params.h * params.h);\n}\n}\n@compute @workgroup_size(256) fn w_bend(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet nstretch = st[c.base + c.extra.y + 13u];\nif (c.live == 0u || nstretch >= c.ncolours) {\nreturn;\n}\nlet c0 = c.base + c.colours;\nlet j = st[c0 + nstretch] + wg.x * WW + l;\nif (j < st[c0 + nstretch + 1u]) {\nbend_scatter(c, j, params.h * params.h, BFIRST);\n}\n}\n@compute @workgroup_size(256) fn w_bend_apply(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count || st[c.base + c.extra.y + 13u] >= c.ncolours) {\nreturn;\n}\nph_bend_apply(c, v);\n}\n@compute @workgroup_size(256) fn w_tether(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nlet teth = st[c.base + c.extra.y + 5u];\nif (c.live == 0u || v >= c.count || teth == 0u) {\nreturn;\n}\nph_tether(c, v, teth);\n}\n@compute @workgroup_size(256) fn w_collide(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count) {\nreturn;\n}\nph_collide(c, v, wide_t(), params.h);\n}\nconst AL_DECAY: f32 = 0.95;\nconst K_DECAY: f32 = 0.99;\nconst K_RAMP: f32 = 1000.0;\nconst K_MAX: f32 = 10.0;\nconst K_CONTACT: f32 = 100.0;\nconst FRICTION_EPS: f32 = 1e-4;\nfn outer3(a: vec3f, b: vec3f) -> mat3x3f {\nreturn mat3x3f(a * b.x, a * b.y, a * b.z);\n}\nfn hard_reach(x: vec3f, a: vec3f, lim: f32, s: u32, kmin: f32, f: ptr<function, vec3f>, hm: ptr<function, mat3x3f>) {\nlet d = x - a;\nlet l = length(d);\nif (l < 1e-9) {\nreturn;\n}\nlet k = max(bitcast<f32>(torn[s + 1u]), kmin);\nlet fc = k * (lim - l) + bitcast<f32>(torn[s]);\nif (fc < 0.0) {\nlet nd = d / l;\n*f += fc * nd;\n*hm += k * outer3(nd, nd);\n}\n}\nfn dual_reach(x: vec3f, a: vec3f, lim: f32, rest: f32, s: u32, kmin: f32) {\nlet cv = lim - length(x - a);\nlet k = max(bitcast<f32>(torn[s + 1u]), kmin);\nlet lam = clamp(k * cv + bitcast<f32>(torn[s]), -0.1 * K_MAX * kmin * rest, 0.0);\ntorn[s] = bitcast<u32>(lam);\nif (cv < 0.0) {\ntorn[s + 1u] = bitcast<u32>(min(K_MAX * kmin, k + K_RAMP * kmin * (-cv) / max(rest, 1e-6)));\n}\n}\nfn vbd_vertex(c: Cloth, v: u32, h2inv: f32, hd: u32, lim: f32, tc: f32) {\nlet i = c.first + v;\nlet xi = pos[i];\nif (xi.w <= 0.0) {\nreturn;\n}\nlet x = xi.xyz;\nlet mh = h2inv / xi.w;\nvar f = (vel[i].xyz - x) * mh;\nvar hm = mat3x3f(mh, 0.0, 0.0, 0.0, mh, 0.0, 0.0, 0.0, mh);\nlet c11 = stf(hd + 6u);\nlet c22 = stf(hd + 7u);\nlet c12 = stf(hd + 8u);\nlet c33 = stf(hd + 9u);\nlet cs = stf(hd + 10u);\nlet tr = c.base + st[hd];\nlet a0 = c.base + c.adj;\nfor (var k = st[a0 + v]; k < st[a0 + v + 1u]; k++) {\nlet tri = st[a0 + k];\nif (tri_torn(c, tri)) {\ncontinue;\n}\nlet o = c.base + c.tris + tri * 3u;\nlet v1 = st[o + 1u];\nlet v2 = st[o + 2u];\nlet r = tr + tri * 5u;\nlet m = vec4f(stf(r), stf(r + 1u), stf(r + 2u), stf(r + 3u));\nlet area = stf(r + 4u);\nif (area <= 0.0) {\ncontinue;\n}\nvar d = -vec2f(m.x + m.z, m.y + m.w);\nif (v == v1) {\nd = m.xy;\n} else if (v == v2) {\nd = m.zw;\n}\nlet p0 = pos[c.first + st[o]].xyz;\nlet e1 = pos[c.first + v1].xyz - p0;\nlet e2 = pos[c.first + v2].xyz - p0;\nlet f1 = e1 * m.x + e2 * m.z;\nlet f2 = e1 * m.y + e2 * m.w;\nlet g11 = 0.5 * (dot(f1, f1) - 1.0);\nlet g22 = 0.5 * (dot(f2, f2) - 1.0);\nlet g12 = 0.5 * dot(f1, f2);\nlet k11 = c11 * select(1.0, cs, g11 < 0.0);\nlet k22 = c22 * select(1.0, cs, g22 < 0.0);\nlet k12 = c12 * select(1.0, cs, g11 < 0.0 || g22 < 0.0);\nlet s11 = k11 * g11 + k12 * g22;\nlet s22 = k12 * g11 + k22 * g22;\nlet s12 = 2.0 * c33 * g12;\nlet sd = vec2f(s11 * d.x + s12 * d.y, s12 * d.x + s22 * d.y);\nf -= area * (f1 * sd.x + f2 * sd.y);\nlet q11 = k11 * d.x * d.x + c33 * d.y * d.y;\nlet q12 = (k12 + c33) * d.x * d.y;\nlet q22 = k22 * d.y * d.y + c33 * d.x * d.x;\nlet geo = max(0.0, dot(d, sd));\nhm += area * (q11 * outer3(f1, f1) + q12 * (outer3(f1, f2) + outer3(f2, f1)) + q22 * outer3(f2, f2) + mat3x3f(geo, 0.0, 0.0, 0.0, geo, 0.0, 0.0, 0.0, geo));\n}\nlet bc = c.base + st[hd + 3u];\nfor (var k = st[bc + v]; k < st[bc + v + 1u]; k++) {\nlet e = st[bc + k];\nlet o = c.base + (e >> 2u);\nif (c.fabric2.w > 0.0 && torn[c.base + st[o + 9u]] != 0u) {\ncontinue;\n}\nlet w = vec4f(stf(o + 4u), stf(o + 5u), stf(o + 6u), stf(o + 7u));\nlet b = w.x * pos[c.first + st[o]].xyz + w.y * pos[c.first + st[o + 1u]].xyz + w.z * pos[c.first + st[o + 2u]].xyz + w.w * pos[c.first + st[o + 3u]].xyz;\nlet kb = 1.0 / max(stf(o + 8u), 1e-12);\nlet ws = w[e & 3u];\nf -= kb * ws * b;\nlet hb = kb * ws * ws;\nhm += mat3x3f(hb, 0.0, 0.0, 0.0, hb, 0.0, 0.0, 0.0, hb);\n}\nif (lim > 0.0) {\nlet ec = c.base + st[hd + 4u];\nfor (var k = st[ec + v]; k < st[ec + v + 1u]; k++) {\nlet o = c.base + st[ec + k];\nif (c.fabric2.w > 0.0 && torn[o] != 0u) {\ncontinue;\n}\nlet other = select(st[o], st[o + 1u], st[o] == v);\nhard_reach(x, pos[c.first + other].xyz, stf(o + 4u) * (1.0 + lim), o + 1u, c11, &f, &hm);\n}\n}\nlet teth = st[hd + 5u];\nif (teth != 0u) {\nlet t = c.base + teth + v * 2u;\nlet anchor = st[t];\nif (anchor != NONE) {\nhard_reach(x, pos[c.first + anchor].xyz, stf(t + 1u), t, c11, &f, &hm);\n}\n}\nlet kc = K_CONTACT * c11;\nfor (var k = 0u; k < params.colliders; k++) {\nlet cl = colliders[k];\nlet at = mix(cl.p0.xyz, cl.p1.xyz, tc);\nlet cq = nlerp(cl.q0, cl.q1, tc);\nlet local = quat_rotate(quat_conj(cq), x - at);\nlet sd = shape_sdf(cl.p0.w, cl.dims.xyz, local);\nlet depth = c.fabric.z - sd.w;\nif (depth > 0.0) {\nlet nd = quat_rotate(cq, sd.xyz);\nlet fnorm = kc * depth;\nf += fnorm * nd;\nhm += kc * outer3(nd, nd);\nlet lc = local - sd.xyz * sd.w;\nlet vcol = select(vec3f(0.0), (cl.p1.xyz + quat_rotate(cl.q1, lc) - cl.p0.xyz - quat_rotate(cl.q0, lc)) / params.span, params.span > 0.0);\nlet u = (x - prev[i].xyz) - vcol * params.h;\nlet ut = u - dot(u, nd) * nd;\nlet ul = length(ut);\nlet w = sqrt(c.fabric.y * cl.p1.w) * fnorm * select(1.0 / max(ul, 1e-12), (2.0 - ul / FRICTION_EPS) / FRICTION_EPS, ul < FRICTION_EPS);\nf -= w * ut;\nhm += w * (mat3x3f(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0) - outer3(nd, nd));\n}\n}\nlet a = hm[0];\nlet b = hm[1];\nlet cc = hm[2];\nlet r0 = cross(b, cc);\nlet r1 = cross(cc, a);\nlet r2 = cross(a, b);\nlet det = dot(a, r0);\nif (abs(det) < 1e-30) {\nreturn;\n}\npos[i] = vec4f(x + vec3f(dot(r0, f), dot(r1, f), dot(r2, f)) / det, xi.w);\n}\n@compute @workgroup_size(WG) fn vbd(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.x];\nif (c.live == 0u) {\nreturn;\n}\nlet n = c.count;\nlet h = params.h;\nlet h2inv = 1.0 / (h * h);\nlet damp = exp(-c.fabric.x * h);\nlet hd = c.base + c.extra.y;\nlet lim0 = bitcast<f32>(c.extra.z);\nlet lim = select(lim0, max(lim0, c.fabric2.w * 1.5), c.fabric2.w > 0.0 && lim0 > 0.0);\nlet vc = c.base + st[hd + 1u];\nlet ncv = st[hd + 2u];\nlet teth = st[hd + 5u];\nlet m = st[c.base + c.colours + c.ncolours];\nlet c11 = stf(hd + 6u);\nlet iters = max(params.iterations, 1u);\nfor (var s = 0u; s < params.substeps; s++) {\nlet t = mix(params.t0, params.t1, f32(s + 1u) / f32(params.substeps));\nlet time = params.time + h * f32(s);\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nprev[i] = vec4f(aero(c, v, time), 0.0);\n}\nif (s == 0u) {\nfor (var j = tid; j < m; j += WG) {\nlet o = c.base + c.cons + j * 12u;\nif (st[o + 2u] == NONE) {\ntorn[o + 1u] = bitcast<u32>(bitcast<f32>(torn[o + 1u]) * AL_DECAY);\ntorn[o + 2u] = bitcast<u32>(bitcast<f32>(torn[o + 2u]) * K_DECAY);\n}\n}\nif (teth != 0u) {\nfor (var v = tid; v < n; v += WG) {\nlet ts = c.base + teth + v * 2u;\ntorn[ts] = bitcast<u32>(bitcast<f32>(torn[ts]) * AL_DECAY);\ntorn[ts + 1u] = bitcast<u32>(bitcast<f32>(torn[ts + 1u]) * K_DECAY);\n}\n}\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nlet p = pos[i];\nlet air = prev[i].xyz;\nprev[i] = p;\nif (p.w <= 0.0) {\npos[i] = vec4f(pin_pos(c, v, t), 0.0);\ncontinue;\n}\nlet y = p.xyz + h * (vel[i].xyz + h * (params.gravity + air));\nvel[i] = vec4f(y, 0.0);\npos[i] = vec4f(y, p.w);\n}\nstorageBarrier();\nfor (var it = 0u; it < iters; it++) {\nfor (var k = 0u; k < ncv; k++) {\nlet lo = st[vc + k];\nlet hi = st[vc + k + 1u];\nfor (var j = lo + tid; j < hi; j += WG) {\nvbd_vertex(c, st[vc + ncv + 1u + j], h2inv, hd, lim, t);\n}\nstorageBarrier();\n}\nfor (var j = tid; j < m; j += WG) {\nlet o = c.base + c.cons + j * 12u;\nif (st[o + 2u] != NONE) {\ncontinue;\n}\nif (c.fabric2.w > 0.0) {\nif (torn[o] != 0u) {\ncontinue;\n}\nlet rest = stf(o + 4u);\nif (length(pos[c.first + st[o + 1u]].xyz - pos[c.first + st[o]].xyz) > rest * (1.0 + c.fabric2.w)) {\ntorn[o] = 1u;\ntorn[c.base + st[hd + 12u] + j] = 1u;\ncontinue;\n}\n}\nif (lim > 0.0) {\nlet rest = stf(o + 4u);\ndual_reach(pos[c.first + st[o]].xyz, pos[c.first + st[o + 1u]].xyz, rest * (1.0 + lim), rest, o + 1u, c11);\n}\n}\nif (teth != 0u) {\nfor (var v = tid; v < n; v += WG) {\nlet ts = c.base + teth + v * 2u;\nlet anchor = st[ts];\nif (anchor != NONE && pos[c.first + v].w > 0.0) {\nlet lt = stf(ts + 1u);\ndual_reach(pos[c.first + v].xyz, pos[c.first + anchor].xyz, lt, lt, ts, c11);\n}\n}\n}\nstorageBarrier();\n}\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nif (pos[i].w > 0.0) {\ncollide(c, i, t, h);\nbackstop(c, v, t);\n}\nlet nv = (pos[i].xyz - prev[i].xyz) / h * damp;\nlet sp = length(nv);\nvel[i] = vec4f(select(nv, nv * (params.max_speed / sp), sp > params.max_speed), 0.0);\n}\nstorageBarrier();\n}\n}\noverride VCOLOUR: u32 = 0u;\nfn vbd_limit(c: Cloth) -> f32 {\nlet lim0 = bitcast<f32>(c.extra.z);\nreturn select(lim0, max(lim0, c.fabric2.w * 1.5), c.fabric2.w > 0.0 && lim0 > 0.0);\n}\n@compute @workgroup_size(256) fn vw_warm(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nif (c.live == 0u) {\nreturn;\n}\nlet j = wg.x * WW + l;\nlet m = st[c.base + c.colours + c.ncolours];\nif (j < m) {\nlet o = c.base + c.cons + j * 12u;\nif (st[o + 2u] == NONE) {\ntorn[o + 1u] = bitcast<u32>(bitcast<f32>(torn[o + 1u]) * AL_DECAY);\ntorn[o + 2u] = bitcast<u32>(bitcast<f32>(torn[o + 2u]) * K_DECAY);\n}\n}\nlet teth = st[c.base + c.extra.y + 5u];\nif (teth != 0u && j < c.count) {\nlet ts = c.base + teth + j * 2u;\ntorn[ts] = bitcast<u32>(bitcast<f32>(torn[ts]) * AL_DECAY);\ntorn[ts + 1u] = bitcast<u32>(bitcast<f32>(torn[ts + 1u]) * K_DECAY);\n}\n}\n@compute @workgroup_size(256) fn vw_predict(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || v >= c.count) {\nreturn;\n}\nlet i = c.first + v;\nlet h = params.h;\nlet p = pos[i];\nlet air = prev[i].xyz;\nprev[i] = p;\nif (p.w <= 0.0) {\npos[i] = vec4f(pin_pos(c, v, wide_t()), 0.0);\nreturn;\n}\nlet y = p.xyz + h * (vel[i].xyz + h * (params.gravity + air));\nvel[i] = vec4f(y, 0.0);\npos[i] = vec4f(y, p.w);\n}\n@compute @workgroup_size(256) fn vw_colour(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet hd = c.base + c.extra.y;\nlet ncv = st[hd + 2u];\nif (c.live == 0u || VCOLOUR >= ncv) {\nreturn;\n}\nlet vc = c.base + st[hd + 1u];\nlet j = st[vc + VCOLOUR] + wg.x * WW + l;\nif (j < st[vc + VCOLOUR + 1u]) {\nvbd_vertex(c, st[vc + ncv + 1u + j], 1.0 / (params.h * params.h), hd, vbd_limit(c), wide_t());\n}\n}\n@compute @workgroup_size(256) fn vw_dual(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nif (c.live == 0u) {\nreturn;\n}\nlet hd = c.base + c.extra.y;\nlet c11 = stf(hd + 6u);\nlet lim = vbd_limit(c);\nlet j = wg.x * WW + l;\nlet m = st[c.base + c.colours + c.ncolours];\nif (j < m) {\nlet o = c.base + c.cons + j * 12u;\nif (st[o + 2u] == NONE) {\nvar live = true;\nif (c.fabric2.w > 0.0) {\nif (torn[o] != 0u) {\nlive = false;\n} else {\nlet rest = stf(o + 4u);\nif (length(pos[c.first + st[o + 1u]].xyz - pos[c.first + st[o]].xyz) > rest * (1.0 + c.fabric2.w)) {\ntorn[o] = 1u;\ntorn[c.base + st[hd + 12u] + j] = 1u;\nlive = false;\n}\n}\n}\nif (live && lim > 0.0) {\nlet rest = stf(o + 4u);\ndual_reach(pos[c.first + st[o]].xyz, pos[c.first + st[o + 1u]].xyz, rest * (1.0 + lim), rest, o + 1u, c11);\n}\n}\n}\nlet teth = st[hd + 5u];\nif (teth != 0u && j < c.count) {\nlet ts = c.base + teth + j * 2u;\nlet anchor = st[ts];\nif (anchor != NONE && pos[c.first + j].w > 0.0) {\nlet lt = stf(ts + 1u);\ndual_reach(pos[c.first + j].xyz, pos[c.first + anchor].xyz, lt, lt, ts, c11);\n}\n}\n}\n@group(0) @binding(8) var<storage, read_write> pool: array<f32>;\n@group(0) @binding(12) var<storage, read_write> tri_out: array<u32>;\n@compute @workgroup_size(WG) fn write(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.x];\nif (c.live == 0u) {\nreturn;\n}\nlet a = params.alpha;\nlet inv = quat_conj(c.rot1);\nlet scale = 1.0 / c.origin1.w;\nlet a0 = c.base + c.adj;\nfor (var v = tid; v < c.count; v += WG) {\nlet i = c.first + v;\nlet p = mix(last[i].xyz, pos[i].xyz, a);\nvar nrm = vec3f(0.0);\nfor (var k = st[a0 + v]; k < st[a0 + v + 1u]; k++) {\nif (tri_torn(c, st[a0 + k])) {\ncontinue;\n}\nlet o = c.base + c.tris + st[a0 + k] * 3u;\nlet j0 = c.first + st[o];\nlet j1 = c.first + st[o + 1u];\nlet j2 = c.first + st[o + 2u];\nlet q0 = mix(last[j0].xyz, pos[j0].xyz, a);\nnrm += cross(mix(last[j1].xyz, pos[j1].xyz, a) - q0, mix(last[j2].xyz, pos[j2].xyz, a) - q0);\n}\nlet ln = quat_rotate(inv, nrm);\nlet len = length(ln);\nlet nl = select(vec3f(0.0, 1.0, 0.0), ln / len, len > 1e-20);\nlet lp = quat_rotate(inv, p - c.origin1.xyz) * scale;\nlet o = (c.pool + v) * 8u;\npool[o] = lp.x;\npool[o + 1u] = lp.y;\npool[o + 2u] = lp.z;\npool[o + 3u] = nl.x;\npool[o + 4u] = nl.y;\npool[o + 5u] = nl.z;\n}\nif (c.fabric2.w > 0.0) {\nlet ntri = (c.colours - c.tris) / 3u;\nfor (var t = tid; t < ntri; t += WG) {\nlet o = c.base + c.tris + t * 3u;\nlet gone = tri_torn(c, t);\nlet at = c.extra.x + t * 3u;\ntri_out[at] = st[o];\ntri_out[at + 1u] = select(st[o + 1u], st[o], gone);\ntri_out[at + 2u] = select(st[o + 2u], st[o], gone);\n}\n}\n}\n@group(0) @binding(9) var<storage, read_write> hash: array<atomic<u32>>;\n@group(0) @binding(10) var<storage, read_write> delta: array<vec4f>;\nvar<workgroup> partial: array<u32, 1024>;\nfn cell_hash(c: vec3i, mask: u32) -> u32 {\nreturn (bitcast<u32>(c.x * 92837111) ^ bitcast<u32>(c.y * 689287499) ^ bitcast<u32>(c.z * 283923481)) & mask;\n}\n@compute @workgroup_size(WG) fn selfcollide(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.x];\nif (c.live == 0u || c.table == 0u) {\nreturn;\n}\nlet n = c.count;\nlet table = c.table;\nlet mask = table - 1u;\nlet hb = c.hash;\nlet ent = hb + table + 1u;\nlet d = c.fabric2.z;\nlet inv = 1.0 / d;\nlet dt = params.h * f32(params.substeps);\nfor (var k = tid; k <= table; k += WG) {\natomicStore(&hash[hb + k], 0u);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nlet h = cell_hash(vec3i(floor(pos[c.first + v].xyz * inv)), mask);\natomicAdd(&hash[hb + h], 1u);\n}\nstorageBarrier();\nlet per = (table + WG - 1u) / WG;\nlet lo = tid * per;\nlet hi = min(lo + per, table);\nvar run = 0u;\nfor (var k = lo; k < hi; k++) {\nrun += atomicLoad(&hash[hb + k]);\n}\npartial[tid] = run;\nworkgroupBarrier();\nif (tid == 0u) {\nvar acc = 0u;\nfor (var k = 0u; k < WG; k++) {\nlet x = partial[k];\npartial[k] = acc;\nacc += x;\n}\n}\nworkgroupBarrier();\nrun = partial[tid];\nfor (var k = lo; k < hi; k++) {\nrun += atomicLoad(&hash[hb + k]);\natomicStore(&hash[hb + k], run);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nlet h = cell_hash(vec3i(floor(pos[c.first + v].xyz * inv)), mask);\nlet at = atomicSub(&hash[hb + h], 1u) - 1u;\natomicStore(&hash[ent + at], v);\n}\nstorageBarrier();\nfor (var k = tid; k < table; k += WG) {\nlet lo = atomicLoad(&hash[hb + k]);\nlet hi = select(atomicLoad(&hash[hb + k + 1u]), n, k == mask);\nfor (var a = lo + 1u; a < hi; a++) {\nlet key = atomicLoad(&hash[ent + a]);\nvar b = a;\nwhile (b > lo && atomicLoad(&hash[ent + b - 1u]) > key) {\natomicStore(&hash[ent + b], atomicLoad(&hash[ent + b - 1u]));\nb--;\n}\natomicStore(&hash[ent + b], key);\n}\n}\nstorageBarrier();\nlet scale = c.origin1.w;\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nlet a = pos[i];\nvar push = vec3f(0.0);\nvar hits = 0.0;\nif (a.w > 0.0) {\nlet r = c.base + v * 4u;\nlet ra = vec3f(stf(r), stf(r + 1u), stf(r + 2u));\nlet cell = vec3i(floor(a.xyz * inv));\nfor (var dz = -1; dz <= 1; dz++) {\nfor (var dy = -1; dy <= 1; dy++) {\nfor (var dx = -1; dx <= 1; dx++) {\nlet h = cell_hash(cell + vec3i(dx, dy, dz), mask);\nlet end = select(atomicLoad(&hash[hb + h + 1u]), n, h == mask);\nfor (var k = atomicLoad(&hash[hb + h]); k < end; k++) {\nlet u = atomicLoad(&hash[ent + k]);\nif (u == v) {\ncontinue;\n}\nlet b = pos[c.first + u];\nlet diff = a.xyz - b.xyz;\nlet dist = length(diff);\nif (dist >= d || dist < 1e-9) {\ncontinue;\n}\nlet q = c.base + u * 4u;\nif (length(ra - vec3f(stf(q), stf(q + 1u), stf(q + 2u))) * scale < d) {\ncontinue;\n}\npush += diff * ((d - dist) / dist * a.w / (a.w + b.w));\nhits += 1.0;\n}\n}\n}\n}\n}\ndelta[i] = vec4f(push / max(hits, 1.0), 0.0);\n}\nstorageBarrier();\nfor (var v = tid; v < n; v += WG) {\nlet i = c.first + v;\nlet p = delta[i].xyz;\npos[i] = vec4f(pos[i].xyz + p, pos[i].w);\nvel[i] = vec4f(vel[i].xyz + p * (0.5 / dt), 0.0);\n}\n}\n@compute @workgroup_size(256) fn sw_clear(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet k = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || k > c.table) {\nreturn;\n}\natomicStore(&hash[c.hash + k], 0u);\n}\n@compute @workgroup_size(256) fn sw_count(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || v >= c.count) {\nreturn;\n}\nlet h = cell_hash(vec3i(floor(pos[c.first + v].xyz * (1.0 / c.fabric2.z))), c.table - 1u);\natomicAdd(&hash[c.hash + h], 1u);\n}\n@compute @workgroup_size(WG) fn sw_scan(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) tid: u32) {\nlet c = cloths[wg.y];\nif (c.live == 0u || c.table == 0u) {\nreturn;\n}\nlet table = c.table;\nlet hb = c.hash;\nlet per = (table + WG - 1u) / WG;\nlet lo = tid * per;\nlet hi = min(lo + per, table);\nvar run = 0u;\nfor (var k = lo; k < hi; k++) {\nrun += atomicLoad(&hash[hb + k]);\n}\npartial[tid] = run;\nworkgroupBarrier();\nif (tid == 0u) {\nvar acc = 0u;\nfor (var k = 0u; k < WG; k++) {\nlet x = partial[k];\npartial[k] = acc;\nacc += x;\n}\n}\nworkgroupBarrier();\nrun = partial[tid];\nfor (var k = lo; k < hi; k++) {\nrun += atomicLoad(&hash[hb + k]);\natomicStore(&hash[hb + k], run);\n}\n}\n@compute @workgroup_size(256) fn sw_fill(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || v >= c.count) {\nreturn;\n}\nlet h = cell_hash(vec3i(floor(pos[c.first + v].xyz * (1.0 / c.fabric2.z))), c.table - 1u);\nlet at = atomicSub(&hash[c.hash + h], 1u) - 1u;\natomicStore(&hash[c.hash + c.table + 1u + at], v);\n}\n@compute @workgroup_size(256) fn sw_sort(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet k = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || k >= c.table) {\nreturn;\n}\nlet hb = c.hash;\nlet ent = hb + c.table + 1u;\nlet lo = atomicLoad(&hash[hb + k]);\nlet hi = select(atomicLoad(&hash[hb + k + 1u]), c.count, k == c.table - 1u);\nfor (var a = lo + 1u; a < hi; a++) {\nlet key = atomicLoad(&hash[ent + a]);\nvar b = a;\nwhile (b > lo && atomicLoad(&hash[ent + b - 1u]) > key) {\natomicStore(&hash[ent + b], atomicLoad(&hash[ent + b - 1u]));\nb--;\n}\natomicStore(&hash[ent + b], key);\n}\n}\n@compute @workgroup_size(256) fn sw_push(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || v >= c.count) {\nreturn;\n}\nlet n = c.count;\nlet mask = c.table - 1u;\nlet hb = c.hash;\nlet ent = hb + c.table + 1u;\nlet d = c.fabric2.z;\nlet inv = 1.0 / d;\nlet scale = c.origin1.w;\nlet i = c.first + v;\nlet a = pos[i];\nvar push = vec3f(0.0);\nvar hits = 0.0;\nif (a.w > 0.0) {\nlet r = c.base + v * 4u;\nlet ra = vec3f(stf(r), stf(r + 1u), stf(r + 2u));\nlet cell = vec3i(floor(a.xyz * inv));\nfor (var dz = -1; dz <= 1; dz++) {\nfor (var dy = -1; dy <= 1; dy++) {\nfor (var dx = -1; dx <= 1; dx++) {\nlet h = cell_hash(cell + vec3i(dx, dy, dz), mask);\nlet end = select(atomicLoad(&hash[hb + h + 1u]), n, h == mask);\nfor (var k = atomicLoad(&hash[hb + h]); k < end; k++) {\nlet u = atomicLoad(&hash[ent + k]);\nif (u == v) {\ncontinue;\n}\nlet b = pos[c.first + u];\nlet diff = a.xyz - b.xyz;\nlet dist = length(diff);\nif (dist >= d || dist < 1e-9) {\ncontinue;\n}\nlet q = c.base + u * 4u;\nif (length(ra - vec3f(stf(q), stf(q + 1u), stf(q + 2u))) * scale < d) {\ncontinue;\n}\npush += diff * ((d - dist) / dist * a.w / (a.w + b.w));\nhits += 1.0;\n}\n}\n}\n}\n}\ndelta[i] = vec4f(push / max(hits, 1.0), 0.0);\n}\n@compute @workgroup_size(256) fn sw_apply(@builtin(workgroup_id) wg: vec3u, @builtin(local_invocation_index) l: u32) {\nlet c = cloths[wg.y];\nlet v = wg.x * WW + l;\nif (c.live == 0u || c.table == 0u || v >= c.count) {\nreturn;\n}\nlet i = c.first + v;\nlet p = delta[i].xyz;\npos[i] = vec4f(pos[i].xyz + p, pos[i].w);\nvel[i] = vec4f(vel[i].xyz + p * (0.5 / (params.h * f32(params.substeps))), 0.0);\n}\nfn quat_rotate(q: vec4f, v: vec3f) -> vec3f {\nlet t = 2.0 * cross(q.xyz, v);\nreturn v + q.w * t + cross(q.xyz, t);\n}",
7
+ };
8
+ //# sourceMappingURL=gpu-shaders.gen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu-shaders.gen.js","sourceRoot":"","sources":["../src/gpu-shaders.gen.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,yEAAyE;AAEzE,8CAA8C;AAC9C,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,wkkCAAwkkC;CACtkkC,CAAC"}
package/dist/gpu.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { bendWeights, type ClothFabric, clothGrid, type ClothGridOptions, type ClothMesh, type ClothTopology, clothTopology, FABRICS, type FabricName, fabricFromKes, KES, type KesFabric, packCloth, type PackedCloth, splitTorn, STENCIL_BEND } from './cloth-asset.ts';
2
+ export { cloth, type ClothOptions, clothPlugin, ClothSystem, type ClothTorn, type ClothValues, type WindSettings } from './cloth.ts';
package/dist/gpu.js ADDED
@@ -0,0 +1,5 @@
1
+ // `@yali-engine/physics/gpu` (ADR-0101): Kanduka's GPU deformables, an entry of its own so a game
2
+ // without cloth carries none of it. Phase 3 step 1: XPBD cloth on WebGPU core.
3
+ export { bendWeights, clothGrid, clothTopology, FABRICS, fabricFromKes, KES, packCloth, splitTorn, STENCIL_BEND } from "./cloth-asset.js";
4
+ export { cloth, clothPlugin, ClothSystem } from "./cloth.js";
5
+ //# sourceMappingURL=gpu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu.js","sourceRoot":"","sources":["../src/gpu.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,+EAA+E;AAC/E,OAAO,EAAE,WAAW,EAAoB,SAAS,EAA6D,aAAa,EAAE,OAAO,EAAmB,aAAa,EAAE,GAAG,EAAkB,SAAS,EAAoB,SAAS,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC1Q,OAAO,EAAE,KAAK,EAAqB,WAAW,EAAE,WAAW,EAAuD,MAAM,YAAY,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@yali-engine/physics`: rigid bodies, colliders, ray casts and collision assets over Kanduka, Yali's
3
+ * own Soft Step solver, compiled into the core module's `physics` variant (ADR-0082, ADR-0084).
4
+ * `@yali-engine/vite` loads that variant when an app depends on this package.
5
+ *
6
+ * @example
7
+ * import { collider, physicsPlugin, rigidBody, type PhysicsComponents } from '@yali-engine/physics';
8
+ * const app = App.create({ core, plugins: [transformsPlugin(), physicsPlugin()] });
9
+ * const { RigidBody, Collider } = app.resource<PhysicsComponents>('physicsComponents');
10
+ * const ball = app.world.spawn([...transforms.bundle, RigidBody, Collider]);
11
+ * app.world.insert(ball, Transform, { t: [0, 5, 0], r: [0, 0, 0, 1], s: 1 });
12
+ * app.world.insert(ball, RigidBody, rigidBody());
13
+ * app.world.insert(ball, Collider, collider({ sphere: 0.5 }, { restitution: 0.5 }));
14
+ */
15
+ export { BodyKind, collider, type ColliderOptions, type ColliderShape, joint, type JointAxis, type JointOptions, JointType, type PhysicsComponents, registerPhysics, rigidBody, type RigidBodyOptions, ShapeKind } from './components.ts';
16
+ export { Physics, type PhysicsDebug, type PhysicsDebugLines, type PhysicsSettings, type PhysicsStats, type RayHit } from './physics.ts';
17
+ export { type CharacterOptions, characterController, FieldKind, forceField, type ForceFieldOptions } from './character.ts';
18
+ export { type CollisionAsset, type CollisionPiece, readCollision, spawnCollision, writeCollision } from './collision-asset.ts';
19
+ export { physicsPlugin, type PhysicsPluginOptions } from './plugin.ts';
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@yali-engine/physics`: rigid bodies, colliders, ray casts and collision assets over Kanduka, Yali's
3
+ * own Soft Step solver, compiled into the core module's `physics` variant (ADR-0082, ADR-0084).
4
+ * `@yali-engine/vite` loads that variant when an app depends on this package.
5
+ *
6
+ * @example
7
+ * import { collider, physicsPlugin, rigidBody, type PhysicsComponents } from '@yali-engine/physics';
8
+ * const app = App.create({ core, plugins: [transformsPlugin(), physicsPlugin()] });
9
+ * const { RigidBody, Collider } = app.resource<PhysicsComponents>('physicsComponents');
10
+ * const ball = app.world.spawn([...transforms.bundle, RigidBody, Collider]);
11
+ * app.world.insert(ball, Transform, { t: [0, 5, 0], r: [0, 0, 0, 1], s: 1 });
12
+ * app.world.insert(ball, RigidBody, rigidBody());
13
+ * app.world.insert(ball, Collider, collider({ sphere: 0.5 }, { restitution: 0.5 }));
14
+ */
15
+ export { BodyKind, collider, joint, JointType, registerPhysics, rigidBody, ShapeKind } from "./components.js";
16
+ export { Physics } from "./physics.js";
17
+ export { characterController, FieldKind, forceField } from "./character.js";
18
+ export { readCollision, spawnCollision, writeCollision } from "./collision-asset.js";
19
+ export { physicsPlugin } from "./plugin.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAA4C,KAAK,EAAqC,SAAS,EAA0B,eAAe,EAAE,SAAS,EAAyB,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1O,OAAO,EAAE,OAAO,EAAmG,MAAM,cAAc,CAAC;AACxI,OAAO,EAAyB,mBAAmB,EAAE,SAAS,EAAE,UAAU,EAA0B,MAAM,gBAAgB,CAAC;AAC3H,OAAO,EAA4C,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC/H,OAAO,EAAE,aAAa,EAA6B,MAAM,aAAa,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { type Entity, type World } from '@yali-engine/core';
2
+ /** A ray hit. */
3
+ export interface RayHit {
4
+ /** The entity whose collider was hit. */
5
+ readonly entity: Entity;
6
+ /** Distance along the ray (0 when it starts inside a solid shape). */
7
+ readonly distance: number;
8
+ /** Surface normal at the hit (zero when the ray starts inside). */
9
+ readonly normal: readonly [number, number, number];
10
+ }
11
+ /** The last step's counts. */
12
+ export interface PhysicsStats {
13
+ readonly bodies: number;
14
+ readonly awake: number;
15
+ readonly pairs: number;
16
+ readonly contacts: number;
17
+ /** Graph colours of the last solve (0: a small solve, sequential in pair order). */
18
+ readonly colors: number;
19
+ /** Threads that ran the last solve's substeps (1 without a pool, or for a small solve). */
20
+ readonly threads: number;
21
+ }
22
+ /** What debug draw shows (`Physics.debug`, ADR-0087). */
23
+ export interface PhysicsDebug {
24
+ /** Every collider's wireframe, coloured by its body's state. */
25
+ colliders: boolean;
26
+ /** Contact points (small crosses) and their normals. */
27
+ contacts: boolean;
28
+ /** Segments per frame at most. */
29
+ maxSegments: number;
30
+ }
31
+ /** A frame's debug segments, as views into the core's memory (valid until the next step). */
32
+ export interface PhysicsDebugLines {
33
+ readonly f32: Float32Array<ArrayBufferLike>;
34
+ readonly u32: Uint32Array<ArrayBufferLike>;
35
+ /** First vertex (16 bytes each: x, y, z relative to `origin`, RGBA8). */
36
+ readonly first: number;
37
+ /** Vertices (two per segment). */
38
+ readonly count: number;
39
+ /** The physics frame's f64 origin. */
40
+ readonly origin: readonly [number, number, number];
41
+ }
42
+ /** The physics settings `Physics.configure` takes. */
43
+ export interface PhysicsSettings {
44
+ /** m/s² (default [0, -9.81, 0]). */
45
+ readonly gravity?: readonly [number, number, number];
46
+ /** Soft Step substeps per fixed step (default 4). */
47
+ readonly substeps?: number;
48
+ }
49
+ /**
50
+ * The physics world of an app (the `physics` resource): ray casts, collision assets, settings
51
+ * and stats. Bodies and colliders are entities (`physics.RigidBody`, `physics.Collider`); this is
52
+ * everything else.
53
+ *
54
+ * @example
55
+ * const physics = app.resource<Physics>('physics');
56
+ * const hit = physics.raycast([0, 10, 0], [0, -1, 0], 100);
57
+ * if (hit) console.log(hit.entity, hit.distance);
58
+ */
59
+ export declare class Physics {
60
+ #private;
61
+ readonly world: World;
62
+ constructor(world: World, fixedDelta: number, settings?: PhysicsSettings);
63
+ /** What debug draw shows; both off by default. The plugin hands the lines to `render.debug`. */
64
+ readonly debug: PhysicsDebug;
65
+ /**
66
+ * This step's debug segments (what `debug` asks for), or null when it asks for nothing. The
67
+ * views point into the core's memory: read them before the next step. Allocation-free.
68
+ */
69
+ debugLines(): PhysicsDebugLines | null;
70
+ /** Gravity and substeps (the fixed step is the app's). */
71
+ configure(s: PhysicsSettings): void;
72
+ /** The first collider the ray hits within `maxDistance` (layers in `mask`), or null. */
73
+ raycast(origin: readonly [number, number, number], dir: readonly [number, number, number], maxDistance?: number, mask?: number): RayHit | null;
74
+ /** Many rays in one call into the core (each `[origin, dir, maxDistance, mask]`). */
75
+ raycasts(rays: readonly (readonly [readonly [number, number, number], readonly [number, number, number], number, number])[]): (RayHit | null)[];
76
+ /** Adds a convex hull of `points` (x, y, z triples, local frame); returns its asset index. */
77
+ addHull(points: Float32Array): number;
78
+ /** Adds a static triangle mesh; returns its asset index. */
79
+ addMesh(vertices: Float32Array, indices: Uint32Array, doubleSided?: boolean): number;
80
+ /** Adds a heightfield (`rows × cols` heights, row-major); returns its asset index. */
81
+ addHeightfield(rows: number, cols: number, heights: Float32Array, cell: readonly [number, number], heightScale?: number): number;
82
+ /**
83
+ * Saves the physics state (ADR-0091: bodies, contacts with their warm-start impulses, joints,
84
+ * sleep); returns its id. Restoring it continues bit for bit. The ECS world's own state is the
85
+ * caller's to save (rollback, replay, undo).
86
+ */
87
+ snapshot(): number;
88
+ /** Back to snapshot `id`, between frames; every body's Transform and velocity is written back. */
89
+ restore(id: number): void;
90
+ /** Frees snapshot `id`. */
91
+ dropSnapshot(id: number): void;
92
+ /** The last step's counts. */
93
+ stats(): PhysicsStats;
94
+ }