@flare-engine/dynamics 0.3.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.
package/dist/index.js ADDED
@@ -0,0 +1,554 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _flare_engine_math = require("@flare-engine/math");
3
+ //#region src/body.ts
4
+ function createBody(opts) {
5
+ const half = opts.half;
6
+ const density = opts.density ?? 1;
7
+ let invMass = 0;
8
+ let invInertia = 0;
9
+ if (!opts.static) {
10
+ const w = half.x * 2;
11
+ const h = half.y * 2;
12
+ const mass = density * w * h;
13
+ const inertia = mass * (w * w + h * h) / 12;
14
+ invMass = mass > 0 ? 1 / mass : 0;
15
+ invInertia = inertia > 0 ? 1 / inertia : 0;
16
+ }
17
+ return {
18
+ position: {
19
+ x: opts.position.x,
20
+ y: opts.position.y
21
+ },
22
+ velocity: opts.velocity ? {
23
+ x: opts.velocity.x,
24
+ y: opts.velocity.y
25
+ } : {
26
+ x: 0,
27
+ y: 0
28
+ },
29
+ angle: opts.angle ?? 0,
30
+ angularVelocity: opts.angularVelocity ?? 0,
31
+ half: {
32
+ x: half.x,
33
+ y: half.y
34
+ },
35
+ invMass,
36
+ invInertia,
37
+ restitution: opts.restitution ?? .2,
38
+ friction: opts.friction ?? .4
39
+ };
40
+ }
41
+ function isStatic(b) {
42
+ return b.invMass === 0;
43
+ }
44
+ /** The four world-space corners of `b`, written as x,y pairs at `offset`. */
45
+ function writeBoxVertices(b, out, offset) {
46
+ const c = Math.cos(b.angle);
47
+ const s = Math.sin(b.angle);
48
+ const hx = b.half.x;
49
+ const hy = b.half.y;
50
+ const px = b.position.x;
51
+ const py = b.position.y;
52
+ let o = offset;
53
+ for (let i = 0; i < 4; i++) {
54
+ const lx = i === 0 || i === 3 ? -hx : hx;
55
+ const ly = i === 0 || i === 1 ? -hy : hy;
56
+ out[o] = px + (lx * c - ly * s);
57
+ out[o + 1] = py + (lx * s + ly * c);
58
+ o += 2;
59
+ }
60
+ }
61
+ /** Outward face normals, one per edge `i → i+1`, guaranteed to point away
62
+ * from `(cx, cy)` regardless of winding or the frame's y-direction. Reads
63
+ * four already-written corners from `verts` at `vOffset`. */
64
+ function writeBoxNormals(verts, vOffset, cx, cy, out, offset) {
65
+ let o = offset;
66
+ for (let i = 0; i < 4; i++) {
67
+ const j = (i + 1) % 4;
68
+ const ax = verts[vOffset + i * 2];
69
+ const ay = verts[vOffset + i * 2 + 1];
70
+ const bx = verts[vOffset + j * 2];
71
+ const by = verts[vOffset + j * 2 + 1];
72
+ const ex = bx - ax;
73
+ const ey = by - ay;
74
+ const l = Math.sqrt(ex * ex + ey * ey) || 1;
75
+ let nx = ey / l;
76
+ let ny = -ex / l;
77
+ const midX = (ax + bx) / 2 - cx;
78
+ const midY = (ay + by) / 2 - cy;
79
+ if (nx * midX + ny * midY < 0) {
80
+ nx = -nx;
81
+ ny = -ny;
82
+ }
83
+ out[o] = nx;
84
+ out[o + 1] = ny;
85
+ o += 2;
86
+ }
87
+ }
88
+ /** World-space axis-aligned bounds of four already-written corners, written
89
+ * as `minX, minY, maxX, maxY` at `offset`. Does not recompute vertices. */
90
+ function writeAabb(verts, vOffset, out, offset) {
91
+ const x0 = verts[vOffset];
92
+ const y0 = verts[vOffset + 1];
93
+ let minX = x0;
94
+ let minY = y0;
95
+ let maxX = x0;
96
+ let maxY = y0;
97
+ for (let i = 1; i < 4; i++) {
98
+ const x = verts[vOffset + i * 2];
99
+ const y = verts[vOffset + i * 2 + 1];
100
+ if (x < minX) minX = x;
101
+ if (y < minY) minY = y;
102
+ if (x > maxX) maxX = x;
103
+ if (y > maxY) maxY = y;
104
+ }
105
+ out[offset] = minX;
106
+ out[offset + 1] = minY;
107
+ out[offset + 2] = maxX;
108
+ out[offset + 3] = maxY;
109
+ }
110
+ //#endregion
111
+ //#region src/manifold.ts
112
+ /** OBB-OBB collision: separating-axis test + reference/incident face clipping,
113
+ * building a 1-2 point contact manifold with per-point penetration depth.
114
+ *
115
+ * Ported from a prototype implementation onto flat `Float64Array` body geometry
116
+ * and a pooled `Manifold`/`ManifoldPoint` — no per-call allocation.
117
+ * The algorithm is the standard 2D clipping manifold (reference face → incident
118
+ * face → Sutherland-Hodgman side-plane clip), stable for stacked / tumbling boxes. */
119
+ const faceScratch = /* @__PURE__ */ new Int32Array(1);
120
+ const clipScratch = /* @__PURE__ */ new Float64Array(4);
121
+ const normalScratch = {
122
+ x: 0,
123
+ y: 0
124
+ };
125
+ const tangentScratch = {
126
+ x: 0,
127
+ y: 0
128
+ };
129
+ /** Index (0-3) of the vertex of the box at `offset` with greatest support
130
+ * along direction `(dx, dy)`. */
131
+ function supportIndex(verts, offset, dx, dy) {
132
+ let bestDot = Number.NEGATIVE_INFINITY;
133
+ let best = 0;
134
+ for (let i = 0; i < 4; i++) {
135
+ const x = verts[offset + i * 2];
136
+ const y = verts[offset + i * 2 + 1];
137
+ const d = x * dx + y * dy;
138
+ if (d > bestDot) {
139
+ bestDot = d;
140
+ best = i;
141
+ }
142
+ }
143
+ return best;
144
+ }
145
+ /** Max separation of the box at `otherOffset` from the faces of the box at
146
+ * `selfOffset`. Positive → a gap exists on some axis (no overlap); negative
147
+ * → the least-penetrating axis. Writes the winning face index into
148
+ * `faceScratch[0]`. */
149
+ function leastPenetration(verts, normals, selfOffset, otherOffset) {
150
+ let best = Number.NEGATIVE_INFINITY;
151
+ let bestFace = 0;
152
+ for (let i = 0; i < 4; i++) {
153
+ const nx = normals[selfOffset + i * 2];
154
+ const ny = normals[selfOffset + i * 2 + 1];
155
+ const vx = verts[selfOffset + i * 2];
156
+ const vy = verts[selfOffset + i * 2 + 1];
157
+ const si = supportIndex(verts, otherOffset, -nx, -ny);
158
+ const sx = verts[otherOffset + si * 2];
159
+ const sy = verts[otherOffset + si * 2 + 1];
160
+ const sep = nx * (sx - vx) + ny * (sy - vy);
161
+ if (sep > best) {
162
+ best = sep;
163
+ bestFace = i;
164
+ }
165
+ }
166
+ faceScratch[0] = bestFace;
167
+ return best;
168
+ }
169
+ /** Bias toward keeping the current reference face for solver coherence. */
170
+ function biasGreaterThan(a, b) {
171
+ return a >= b * .95 + a * .01;
172
+ }
173
+ /** Index (0-3) of the incident box's face whose normal is most anti-parallel
174
+ * to the reference normal. */
175
+ function incidentFaceIndex(refNx, refNy, incNormals, incOffset) {
176
+ let minDot = Number.POSITIVE_INFINITY;
177
+ let index = 0;
178
+ for (let i = 0; i < 4; i++) {
179
+ const nx = incNormals[incOffset + i * 2];
180
+ const ny = incNormals[incOffset + i * 2 + 1];
181
+ const d = refNx * nx + refNy * ny;
182
+ if (d < minDot) {
183
+ minDot = d;
184
+ index = i;
185
+ }
186
+ }
187
+ return index;
188
+ }
189
+ /** Clips segment `(ax,ay)-(bx,by)` to the halfspace `dot(n, p) <= c`. Writes
190
+ * 0-2 surviving points into `clipScratch` and returns the count. */
191
+ function clip(nx, ny, c, ax, ay, bx, by) {
192
+ let count = 0;
193
+ const d1 = nx * ax + ny * ay - c;
194
+ const d2 = nx * bx + ny * by - c;
195
+ if (d1 <= 0) {
196
+ clipScratch[count * 2] = ax;
197
+ clipScratch[count * 2 + 1] = ay;
198
+ count++;
199
+ }
200
+ if (d2 <= 0) {
201
+ clipScratch[count * 2] = bx;
202
+ clipScratch[count * 2 + 1] = by;
203
+ count++;
204
+ }
205
+ if (d1 * d2 < 0) {
206
+ const t = d1 / (d1 - d2);
207
+ clipScratch[count * 2] = ax + (bx - ax) * t;
208
+ clipScratch[count * 2 + 1] = ay + (by - ay) * t;
209
+ count++;
210
+ }
211
+ return count;
212
+ }
213
+ /** Builds a contact manifold for boxes `a` (index `ai`) and `b` (index `bi`)
214
+ * into `out`, reading pre-written geometry from `verts`/`normals` (8 doubles
215
+ * per body). Returns `false` — leaving `out` untouched — when the boxes do
216
+ * not overlap. */
217
+ function collideInto(a, b, verts, normals, ai, bi, out) {
218
+ const aOff = ai * 8;
219
+ const bOff = bi * 8;
220
+ const sepA = leastPenetration(verts, normals, aOff, bOff);
221
+ if (sepA >= 0) return false;
222
+ const faceA = faceScratch[0];
223
+ const sepB = leastPenetration(verts, normals, bOff, aOff);
224
+ if (sepB >= 0) return false;
225
+ const faceB = faceScratch[0];
226
+ const refIsA = biasGreaterThan(sepA, sepB);
227
+ const refOffset = refIsA ? aOff : bOff;
228
+ const incOffset = refIsA ? bOff : aOff;
229
+ const refIndex = refIsA ? faceA : faceB;
230
+ const refNx = normals[refOffset + refIndex * 2];
231
+ const refNy = normals[refOffset + refIndex * 2 + 1];
232
+ const refJ = (refIndex + 1) % 4;
233
+ const v1x = verts[refOffset + refIndex * 2];
234
+ const v1y = verts[refOffset + refIndex * 2 + 1];
235
+ const v2x = verts[refOffset + refJ * 2];
236
+ const v2y = verts[refOffset + refJ * 2 + 1];
237
+ const incIndex = incidentFaceIndex(refNx, refNy, normals, incOffset);
238
+ const incJ = (incIndex + 1) % 4;
239
+ const iax = verts[incOffset + incIndex * 2];
240
+ const iay = verts[incOffset + incIndex * 2 + 1];
241
+ const ibx = verts[incOffset + incJ * 2];
242
+ const iby = verts[incOffset + incJ * 2 + 1];
243
+ let sideX = v2x - v1x;
244
+ let sideY = v2y - v1y;
245
+ const sideInv = 1 / (Math.sqrt(sideX * sideX + sideY * sideY) || 1);
246
+ sideX *= sideInv;
247
+ sideY *= sideInv;
248
+ const negSide = -(sideX * v1x + sideY * v1y);
249
+ const posSide = sideX * v2x + sideY * v2y;
250
+ let count = clip(-sideX, -sideY, negSide, iax, iay, ibx, iby);
251
+ if (count < 2) return false;
252
+ const c0x = clipScratch[0];
253
+ const c0y = clipScratch[1];
254
+ const c1x = clipScratch[2];
255
+ const c1y = clipScratch[3];
256
+ count = clip(sideX, sideY, posSide, c0x, c0y, c1x, c1y);
257
+ if (count < 2) return false;
258
+ const p0x = clipScratch[0];
259
+ const p0y = clipScratch[1];
260
+ const p1x = clipScratch[2];
261
+ const p1y = clipScratch[3];
262
+ const refC = refNx * v1x + refNy * v1y;
263
+ let written = 0;
264
+ const sep0 = refNx * p0x + refNy * p0y - refC;
265
+ if (sep0 <= 0) {
266
+ const point = out.points[written];
267
+ point.x = p0x;
268
+ point.y = p0y;
269
+ point.penetration = -sep0;
270
+ point.normalImpulse = 0;
271
+ point.tangentImpulse = 0;
272
+ written++;
273
+ }
274
+ const sep1 = refNx * p1x + refNy * p1y - refC;
275
+ if (sep1 <= 0) {
276
+ const point = out.points[written];
277
+ point.x = p1x;
278
+ point.y = p1y;
279
+ point.penetration = -sep1;
280
+ point.normalImpulse = 0;
281
+ point.tangentImpulse = 0;
282
+ written++;
283
+ }
284
+ if (written === 0) return false;
285
+ const normalX = refIsA ? refNx : -refNx;
286
+ const normalY = refIsA ? refNy : -refNy;
287
+ out.a = a;
288
+ out.b = b;
289
+ out.normalX = normalX;
290
+ out.normalY = normalY;
291
+ normalScratch.x = normalX;
292
+ normalScratch.y = normalY;
293
+ _flare_engine_math.Vec2.perp(normalScratch, tangentScratch);
294
+ out.tangentX = tangentScratch.x;
295
+ out.tangentY = tangentScratch.y;
296
+ out.friction = Math.sqrt(a.friction * b.friction);
297
+ out.pointCount = written;
298
+ return true;
299
+ }
300
+ //#endregion
301
+ //#region src/pool.ts
302
+ function createManifoldPoint() {
303
+ return {
304
+ x: 0,
305
+ y: 0,
306
+ penetration: 0,
307
+ normalImpulse: 0,
308
+ tangentImpulse: 0,
309
+ raX: 0,
310
+ raY: 0,
311
+ rbX: 0,
312
+ rbY: 0,
313
+ massNormal: 0,
314
+ massTangent: 0,
315
+ bias: 0
316
+ };
317
+ }
318
+ function createManifold() {
319
+ return {
320
+ a: void 0,
321
+ b: void 0,
322
+ normalX: 0,
323
+ normalY: 0,
324
+ tangentX: 0,
325
+ tangentY: 0,
326
+ friction: 0,
327
+ pointCount: 0,
328
+ points: [createManifoldPoint(), createManifoldPoint()]
329
+ };
330
+ }
331
+ function createPool() {
332
+ return {
333
+ manifolds: [],
334
+ manifoldCount: 0,
335
+ verts: /* @__PURE__ */ new Float64Array(0),
336
+ normals: /* @__PURE__ */ new Float64Array(0),
337
+ aabbs: /* @__PURE__ */ new Float64Array(0),
338
+ capacityBodies: 0
339
+ };
340
+ }
341
+ /** Grows the per-body geometry buffers to hold at least `count` bodies (8
342
+ * doubles per body for verts/normals, 4 for the AABB). No-op if already
343
+ * large enough. */
344
+ function ensureBodyCapacity(pool, count) {
345
+ if (count <= pool.capacityBodies) return;
346
+ let cap = pool.capacityBodies === 0 ? 8 : pool.capacityBodies;
347
+ while (cap < count) cap *= 2;
348
+ pool.verts = new Float64Array(cap * 8);
349
+ pool.normals = new Float64Array(cap * 8);
350
+ pool.aabbs = new Float64Array(cap * 4);
351
+ pool.capacityBodies = cap;
352
+ }
353
+ /** Grows `pool.manifolds` to hold at least `count` entries, appending
354
+ * fully-formed `Manifold` objects (each with two pre-created points).
355
+ * Never truncates — manifolds past `manifoldCount` simply go unused, which
356
+ * is what keeps the steady state allocation-free. */
357
+ function ensureManifoldCapacity(pool, count) {
358
+ while (pool.manifolds.length < count) pool.manifolds.push(createManifold());
359
+ }
360
+ //#endregion
361
+ //#region src/world.ts
362
+ const BAUMGARTE = .2;
363
+ const PENETRATION_SLOP = .5;
364
+ const pools = /* @__PURE__ */ new WeakMap();
365
+ function createWorld(opts = {}) {
366
+ const world = {
367
+ bodies: [],
368
+ gravity: opts.gravity ? {
369
+ x: opts.gravity.x,
370
+ y: opts.gravity.y
371
+ } : {
372
+ x: 0,
373
+ y: 1800
374
+ },
375
+ iterations: opts.iterations ?? 10
376
+ };
377
+ pools.set(world, createPool());
378
+ return world;
379
+ }
380
+ function addBody(world, body) {
381
+ world.bodies.push(body);
382
+ return body;
383
+ }
384
+ /** Relative velocity at a contact projected onto (dx, dy). The vector never
385
+ * escapes — its only use is a dot product, so it collapses to two scalars. */
386
+ function relativeVelocityAlong(a, b, raX, raY, rbX, rbY, dx, dy) {
387
+ const vax = a.velocity.x - a.angularVelocity * raY;
388
+ const vay = a.velocity.y + a.angularVelocity * raX;
389
+ const vbx = b.velocity.x - b.angularVelocity * rbY;
390
+ const vby = b.velocity.y + b.angularVelocity * rbX;
391
+ return (vbx - vax) * dx + (vby - vay) * dy;
392
+ }
393
+ function applyImpulse(body, ix, iy, rx, ry) {
394
+ if (body.invMass === 0) return;
395
+ body.velocity.x += ix * body.invMass;
396
+ body.velocity.y += iy * body.invMass;
397
+ body.angularVelocity += body.invInertia * (rx * iy - ry * ix);
398
+ }
399
+ /** Writes every body's world-space vertices, outward normals, and AABB into
400
+ * `pool`, once per step, so the broadphase pair loop never recomputes them. */
401
+ function writeGeometry(pool, bodies) {
402
+ ensureBodyCapacity(pool, bodies.length);
403
+ for (let i = 0; i < bodies.length; i++) {
404
+ const b = bodies[i];
405
+ const vOffset = i * 8;
406
+ const aOffset = i * 4;
407
+ writeBoxVertices(b, pool.verts, vOffset);
408
+ writeBoxNormals(pool.verts, vOffset, b.position.x, b.position.y, pool.normals, vOffset);
409
+ writeAabb(pool.verts, vOffset, pool.aabbs, aOffset);
410
+ }
411
+ }
412
+ /** Broadphase (precomputed-AABB reject) + narrowphase (OBB manifold),
413
+ * writing surviving contacts into `pool.manifolds[0..manifoldCount)`. */
414
+ function collectManifolds(pool, bodies) {
415
+ pool.manifoldCount = 0;
416
+ const aabbs = pool.aabbs;
417
+ for (let i = 0; i < bodies.length; i++) {
418
+ const a = bodies[i];
419
+ const aOff = i * 4;
420
+ const aMinX = aabbs[aOff];
421
+ const aMinY = aabbs[aOff + 1];
422
+ const aMaxX = aabbs[aOff + 2];
423
+ const aMaxY = aabbs[aOff + 3];
424
+ for (let j = i + 1; j < bodies.length; j++) {
425
+ const b = bodies[j];
426
+ if (isStatic(a) && isStatic(b)) continue;
427
+ const bOff = j * 4;
428
+ const bMinX = aabbs[bOff];
429
+ const bMinY = aabbs[bOff + 1];
430
+ const bMaxX = aabbs[bOff + 2];
431
+ const bMaxY = aabbs[bOff + 3];
432
+ if (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) continue;
433
+ ensureManifoldCapacity(pool, pool.manifoldCount + 1);
434
+ const m = pool.manifolds[pool.manifoldCount];
435
+ if (collideInto(a, b, pool.verts, pool.normals, i, j, m)) pool.manifoldCount++;
436
+ }
437
+ }
438
+ }
439
+ /** Derives per-point solver mass terms and the velocity/position bias for
440
+ * every live manifold, writing them onto the manifold's own contact points. */
441
+ function prepare(pool, gravity, dt) {
442
+ const restThreshold = Math.hypot(gravity.x, gravity.y) * dt;
443
+ for (let mi = 0; mi < pool.manifoldCount; mi++) {
444
+ const m = pool.manifolds[mi];
445
+ const { a, b } = m;
446
+ const normalX = m.normalX;
447
+ const normalY = m.normalY;
448
+ const tangentX = m.tangentX;
449
+ const tangentY = m.tangentY;
450
+ const restitution = Math.min(a.restitution, b.restitution);
451
+ for (let pi = 0; pi < m.pointCount; pi++) {
452
+ const cp = m.points[pi];
453
+ const raX = cp.x - a.position.x;
454
+ const raY = cp.y - a.position.y;
455
+ const rbX = cp.x - b.position.x;
456
+ const rbY = cp.y - b.position.y;
457
+ const rnA = raX * normalY - raY * normalX;
458
+ const rnB = rbX * normalY - rbY * normalX;
459
+ const kNormal = a.invMass + b.invMass + a.invInertia * rnA * rnA + b.invInertia * rnB * rnB;
460
+ const rtA = raX * tangentY - raY * tangentX;
461
+ const rtB = rbX * tangentY - rbY * tangentX;
462
+ const kTangent = a.invMass + b.invMass + a.invInertia * rtA * rtA + b.invInertia * rtB * rtB;
463
+ const vn = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, normalX, normalY);
464
+ const restitutionBias = vn < -restThreshold ? -restitution * vn : 0;
465
+ const baumgarte = BAUMGARTE / dt * Math.max(0, cp.penetration - PENETRATION_SLOP);
466
+ cp.raX = raX;
467
+ cp.raY = raY;
468
+ cp.rbX = rbX;
469
+ cp.rbY = rbY;
470
+ cp.massNormal = kNormal > 0 ? 1 / kNormal : 0;
471
+ cp.massTangent = kTangent > 0 ? 1 / kTangent : 0;
472
+ cp.bias = restitutionBias + baumgarte;
473
+ }
474
+ }
475
+ }
476
+ /** One sequential-impulse sweep over every live manifold's contact points. */
477
+ function solve(pool) {
478
+ for (let mi = 0; mi < pool.manifoldCount; mi++) {
479
+ const m = pool.manifolds[mi];
480
+ const { a, b, friction } = m;
481
+ const normalX = m.normalX;
482
+ const normalY = m.normalY;
483
+ const tangentX = m.tangentX;
484
+ const tangentY = m.tangentY;
485
+ for (let pi = 0; pi < m.pointCount; pi++) {
486
+ const cp = m.points[pi];
487
+ const raX = cp.raX;
488
+ const raY = cp.raY;
489
+ const rbX = cp.rbX;
490
+ const rbY = cp.rbY;
491
+ const vn = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, normalX, normalY);
492
+ let dPn = cp.massNormal * (-vn + cp.bias);
493
+ const newNormal = Math.max(cp.normalImpulse + dPn, 0);
494
+ dPn = newNormal - cp.normalImpulse;
495
+ cp.normalImpulse = newNormal;
496
+ const pnX = normalX * dPn;
497
+ const pnY = normalY * dPn;
498
+ applyImpulse(a, -pnX, -pnY, raX, raY);
499
+ applyImpulse(b, pnX, pnY, rbX, rbY);
500
+ const vt = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, tangentX, tangentY);
501
+ let dPt = cp.massTangent * -vt;
502
+ const maxPt = friction * cp.normalImpulse;
503
+ const newTangent = Math.max(-maxPt, Math.min(maxPt, cp.tangentImpulse + dPt));
504
+ dPt = newTangent - cp.tangentImpulse;
505
+ cp.tangentImpulse = newTangent;
506
+ const ptX = tangentX * dPt;
507
+ const ptY = tangentY * dPt;
508
+ applyImpulse(a, -ptX, -ptY, raX, raY);
509
+ applyImpulse(b, ptX, ptY, rbX, rbY);
510
+ }
511
+ }
512
+ }
513
+ /** Advance the world by `dt` seconds (fixed steps recommended).
514
+ *
515
+ * Resting contacts settle to a stable *position* but retain a small, bounded
516
+ * velocity oscillation at the penetration-slop equilibrium — a sequential-
517
+ * impulse solver with Baumgarte bias and no sleeping does not converge
518
+ * velocity to exactly zero at rest, only the position does. This is
519
+ * inherent to the algorithm, not a bug; sleeping and damping are
520
+ * deliberately out of scope for v1. A consumer needing pixel-exact
521
+ * stillness should clamp small resting velocities at its own layer. */
522
+ function step(world, dt) {
523
+ if (dt <= 0) return;
524
+ let pool = pools.get(world);
525
+ if (!pool) {
526
+ pool = createPool();
527
+ pools.set(world, pool);
528
+ }
529
+ const { bodies } = world;
530
+ for (let i = 0; i < bodies.length; i++) {
531
+ const body = bodies[i];
532
+ if (isStatic(body)) continue;
533
+ body.velocity.x += world.gravity.x * dt;
534
+ body.velocity.y += world.gravity.y * dt;
535
+ }
536
+ writeGeometry(pool, bodies);
537
+ collectManifolds(pool, bodies);
538
+ prepare(pool, world.gravity, dt);
539
+ for (let iter = 0; iter < world.iterations; iter++) solve(pool);
540
+ for (let i = 0; i < bodies.length; i++) {
541
+ const body = bodies[i];
542
+ if (isStatic(body)) continue;
543
+ body.position.x += body.velocity.x * dt;
544
+ body.position.y += body.velocity.y * dt;
545
+ body.angle += body.angularVelocity * dt;
546
+ }
547
+ }
548
+ //#endregion
549
+ exports.addBody = addBody;
550
+ exports.createBody = createBody;
551
+ exports.createWorld = createWorld;
552
+ exports.step = step;
553
+
554
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/body.ts","../src/manifold.ts","../src/pool.ts","../src/world.ts"],"sourcesContent":["/** A rigid body: an oriented box (OBB) with linear + angular state.\n *\n * Boxes only. Mass properties are derived from the box extents; a body with\n * `invMass === 0` is static (floor / walls). */\n\nimport type { Vec2 } from '@flare-engine/math';\n\nexport interface Body {\n /** Center of mass, world space. */\n position: Vec2;\n /** Linear velocity. */\n velocity: Vec2;\n /** Orientation in radians. */\n angle: number;\n /** Angular velocity in radians/second. */\n angularVelocity: number;\n /** Half-extents (half width, half height) in the body's local frame. */\n half: Vec2;\n /** 1 / mass. `0` marks a static (immovable, infinite-mass) body. */\n invMass: number;\n /** 1 / rotational inertia. `0` locks rotation (all static bodies). */\n invInertia: number;\n /** Bounciness in [0, 1]. */\n restitution: number;\n /** Coulomb friction coefficient (≈0 slippery … ≈1 grippy). */\n friction: number;\n}\n\nexport interface BodyOptions {\n position: Vec2;\n half: Vec2;\n /** Uniform density; drives both mass and rotational inertia. Ignored when `static`. */\n density?: number;\n velocity?: Vec2;\n angle?: number;\n angularVelocity?: number;\n restitution?: number;\n friction?: number;\n /** Static bodies have infinite mass and never move. */\n static?: boolean;\n}\n\nexport function createBody(opts: BodyOptions): Body {\n const half = opts.half;\n const density = opts.density ?? 1;\n\n let invMass = 0;\n let invInertia = 0;\n if (!opts.static) {\n const w = half.x * 2;\n const h = half.y * 2;\n const mass = density * w * h;\n // Solid-rectangle inertia about its center: m·(w² + h²) / 12.\n const inertia = (mass * (w * w + h * h)) / 12;\n invMass = mass > 0 ? 1 / mass : 0;\n invInertia = inertia > 0 ? 1 / inertia : 0;\n }\n\n return {\n position: { x: opts.position.x, y: opts.position.y },\n velocity: opts.velocity ? { x: opts.velocity.x, y: opts.velocity.y } : { x: 0, y: 0 },\n angle: opts.angle ?? 0,\n angularVelocity: opts.angularVelocity ?? 0,\n half: { x: half.x, y: half.y },\n invMass,\n invInertia,\n restitution: opts.restitution ?? 0.2,\n friction: opts.friction ?? 0.4,\n };\n}\n\nexport function isStatic(b: Body): boolean {\n return b.invMass === 0;\n}\n\n/** The four world-space corners of `b`, written as x,y pairs at `offset`. */\nexport function writeBoxVertices(b: Body, out: Float64Array, offset: number): void {\n const c = Math.cos(b.angle);\n const s = Math.sin(b.angle);\n const hx = b.half.x;\n const hy = b.half.y;\n const px = b.position.x;\n const py = b.position.y;\n let o = offset;\n for (let i = 0; i < 4; i++) {\n const lx = i === 0 || i === 3 ? -hx : hx;\n const ly = i === 0 || i === 1 ? -hy : hy;\n out[o] = px + (lx * c - ly * s);\n out[o + 1] = py + (lx * s + ly * c);\n o += 2;\n }\n}\n\n/** Outward face normals, one per edge `i → i+1`, guaranteed to point away\n * from `(cx, cy)` regardless of winding or the frame's y-direction. Reads\n * four already-written corners from `verts` at `vOffset`. */\nexport function writeBoxNormals(\n verts: Float64Array,\n vOffset: number,\n cx: number,\n cy: number,\n out: Float64Array,\n offset: number,\n): void {\n let o = offset;\n for (let i = 0; i < 4; i++) {\n const j = (i + 1) % 4;\n const ax = verts[vOffset + i * 2]!;\n const ay = verts[vOffset + i * 2 + 1]!;\n const bx = verts[vOffset + j * 2]!;\n const by = verts[vOffset + j * 2 + 1]!;\n const ex = bx - ax;\n const ey = by - ay;\n const l = Math.sqrt(ex * ex + ey * ey) || 1;\n let nx = ey / l;\n let ny = -ex / l;\n const midX = (ax + bx) / 2 - cx;\n const midY = (ay + by) / 2 - cy;\n if (nx * midX + ny * midY < 0) {\n nx = -nx;\n ny = -ny;\n }\n out[o] = nx;\n out[o + 1] = ny;\n o += 2;\n }\n}\n\n/** World-space axis-aligned bounds of four already-written corners, written\n * as `minX, minY, maxX, maxY` at `offset`. Does not recompute vertices. */\nexport function writeAabb(\n verts: Float64Array,\n vOffset: number,\n out: Float64Array,\n offset: number,\n): void {\n const x0 = verts[vOffset]!;\n const y0 = verts[vOffset + 1]!;\n let minX = x0;\n let minY = y0;\n let maxX = x0;\n let maxY = y0;\n for (let i = 1; i < 4; i++) {\n const x = verts[vOffset + i * 2]!;\n const y = verts[vOffset + i * 2 + 1]!;\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n out[offset] = minX;\n out[offset + 1] = minY;\n out[offset + 2] = maxX;\n out[offset + 3] = maxY;\n}\n","/** OBB-OBB collision: separating-axis test + reference/incident face clipping,\n * building a 1-2 point contact manifold with per-point penetration depth.\n *\n * Ported from a prototype implementation onto flat `Float64Array` body geometry\n * and a pooled `Manifold`/`ManifoldPoint` — no per-call allocation.\n * The algorithm is the standard 2D clipping manifold (reference face → incident\n * face → Sutherland-Hodgman side-plane clip), stable for stacked / tumbling boxes. */\n\nimport { Vec2 } from '@flare-engine/math';\nimport type { Body } from './body.js';\nimport type { Manifold } from './pool.js';\n\n// Module-level scratch — collideInto is called every step (broad-phase pairs\n// × narrow-phase), so nothing here may allocate.\nconst faceScratch = new Int32Array(1);\nconst clipScratch = new Float64Array(4);\nconst normalScratch: Vec2 = { x: 0, y: 0 };\nconst tangentScratch: Vec2 = { x: 0, y: 0 };\n\n/** Index (0-3) of the vertex of the box at `offset` with greatest support\n * along direction `(dx, dy)`. */\nfunction supportIndex(verts: Float64Array, offset: number, dx: number, dy: number): number {\n let bestDot = Number.NEGATIVE_INFINITY;\n let best = 0;\n for (let i = 0; i < 4; i++) {\n const x = verts[offset + i * 2]!;\n const y = verts[offset + i * 2 + 1]!;\n const d = x * dx + y * dy;\n if (d > bestDot) {\n bestDot = d;\n best = i;\n }\n }\n return best;\n}\n\n/** Max separation of the box at `otherOffset` from the faces of the box at\n * `selfOffset`. Positive → a gap exists on some axis (no overlap); negative\n * → the least-penetrating axis. Writes the winning face index into\n * `faceScratch[0]`. */\nfunction leastPenetration(\n verts: Float64Array,\n normals: Float64Array,\n selfOffset: number,\n otherOffset: number,\n): number {\n let best = Number.NEGATIVE_INFINITY;\n let bestFace = 0;\n for (let i = 0; i < 4; i++) {\n const nx = normals[selfOffset + i * 2]!;\n const ny = normals[selfOffset + i * 2 + 1]!;\n const vx = verts[selfOffset + i * 2]!;\n const vy = verts[selfOffset + i * 2 + 1]!;\n const si = supportIndex(verts, otherOffset, -nx, -ny);\n const sx = verts[otherOffset + si * 2]!;\n const sy = verts[otherOffset + si * 2 + 1]!;\n const sep = nx * (sx - vx) + ny * (sy - vy);\n if (sep > best) {\n best = sep;\n bestFace = i;\n }\n }\n faceScratch[0] = bestFace;\n return best;\n}\n\n/** Bias toward keeping the current reference face for solver coherence. */\nfunction biasGreaterThan(a: number, b: number): boolean {\n return a >= b * 0.95 + a * 0.01;\n}\n\n/** Index (0-3) of the incident box's face whose normal is most anti-parallel\n * to the reference normal. */\nfunction incidentFaceIndex(\n refNx: number,\n refNy: number,\n incNormals: Float64Array,\n incOffset: number,\n): number {\n let minDot = Number.POSITIVE_INFINITY;\n let index = 0;\n for (let i = 0; i < 4; i++) {\n const nx = incNormals[incOffset + i * 2]!;\n const ny = incNormals[incOffset + i * 2 + 1]!;\n const d = refNx * nx + refNy * ny;\n if (d < minDot) {\n minDot = d;\n index = i;\n }\n }\n return index;\n}\n\n/** Clips segment `(ax,ay)-(bx,by)` to the halfspace `dot(n, p) <= c`. Writes\n * 0-2 surviving points into `clipScratch` and returns the count. */\nfunction clip(\n nx: number,\n ny: number,\n c: number,\n ax: number,\n ay: number,\n bx: number,\n by: number,\n): number {\n let count = 0;\n const d1 = nx * ax + ny * ay - c;\n const d2 = nx * bx + ny * by - c;\n if (d1 <= 0) {\n clipScratch[count * 2] = ax;\n clipScratch[count * 2 + 1] = ay;\n count++;\n }\n if (d2 <= 0) {\n clipScratch[count * 2] = bx;\n clipScratch[count * 2 + 1] = by;\n count++;\n }\n if (d1 * d2 < 0) {\n const t = d1 / (d1 - d2);\n clipScratch[count * 2] = ax + (bx - ax) * t;\n clipScratch[count * 2 + 1] = ay + (by - ay) * t;\n count++;\n }\n return count;\n}\n\n/** Builds a contact manifold for boxes `a` (index `ai`) and `b` (index `bi`)\n * into `out`, reading pre-written geometry from `verts`/`normals` (8 doubles\n * per body). Returns `false` — leaving `out` untouched — when the boxes do\n * not overlap. */\nexport function collideInto(\n a: Body,\n b: Body,\n verts: Float64Array,\n normals: Float64Array,\n ai: number,\n bi: number,\n out: Manifold,\n): boolean {\n const aOff = ai * 8;\n const bOff = bi * 8;\n\n const sepA = leastPenetration(verts, normals, aOff, bOff);\n if (sepA >= 0) return false;\n const faceA = faceScratch[0]!;\n\n const sepB = leastPenetration(verts, normals, bOff, aOff);\n if (sepB >= 0) return false;\n const faceB = faceScratch[0]!;\n\n const refIsA = biasGreaterThan(sepA, sepB);\n const refOffset = refIsA ? aOff : bOff;\n const incOffset = refIsA ? bOff : aOff;\n const refIndex = refIsA ? faceA : faceB;\n\n const refNx = normals[refOffset + refIndex * 2]!;\n const refNy = normals[refOffset + refIndex * 2 + 1]!;\n const refJ = (refIndex + 1) % 4;\n const v1x = verts[refOffset + refIndex * 2]!;\n const v1y = verts[refOffset + refIndex * 2 + 1]!;\n const v2x = verts[refOffset + refJ * 2]!;\n const v2y = verts[refOffset + refJ * 2 + 1]!;\n\n const incIndex = incidentFaceIndex(refNx, refNy, normals, incOffset);\n const incJ = (incIndex + 1) % 4;\n const iax = verts[incOffset + incIndex * 2]!;\n const iay = verts[incOffset + incIndex * 2 + 1]!;\n const ibx = verts[incOffset + incJ * 2]!;\n const iby = verts[incOffset + incJ * 2 + 1]!;\n\n // Tangent along the reference face; its two end-planes form the clip slab.\n // `|| 1` guards a degenerate (zero- or near-zero-extent) reference edge the\n // same way `writeBoxNormals` already does for the identical computation —\n // without it, a squared-length underflow makes this a division by zero.\n let sideX = v2x - v1x;\n let sideY = v2y - v1y;\n const sideInv = 1 / (Math.sqrt(sideX * sideX + sideY * sideY) || 1);\n sideX *= sideInv;\n sideY *= sideInv;\n const negSide = -(sideX * v1x + sideY * v1y);\n const posSide = sideX * v2x + sideY * v2y;\n\n let count = clip(-sideX, -sideY, negSide, iax, iay, ibx, iby);\n if (count < 2) return false;\n const c0x = clipScratch[0]!;\n const c0y = clipScratch[1]!;\n const c1x = clipScratch[2]!;\n const c1y = clipScratch[3]!;\n\n count = clip(sideX, sideY, posSide, c0x, c0y, c1x, c1y);\n if (count < 2) return false;\n const p0x = clipScratch[0]!;\n const p0y = clipScratch[1]!;\n const p1x = clipScratch[2]!;\n const p1y = clipScratch[3]!;\n\n const refC = refNx * v1x + refNy * v1y;\n\n let written = 0;\n const sep0 = refNx * p0x + refNy * p0y - refC;\n if (sep0 <= 0) {\n const point = out.points[written]!;\n point.x = p0x;\n point.y = p0y;\n point.penetration = -sep0;\n point.normalImpulse = 0;\n point.tangentImpulse = 0;\n written++;\n }\n const sep1 = refNx * p1x + refNy * p1y - refC;\n if (sep1 <= 0) {\n const point = out.points[written]!;\n point.x = p1x;\n point.y = p1y;\n point.penetration = -sep1;\n point.normalImpulse = 0;\n point.tangentImpulse = 0;\n written++;\n }\n if (written === 0) return false;\n\n // Manifold normal must point a → b; refNormal points off the reference\n // box, which is b when we flipped, so negate in that case.\n const normalX = refIsA ? refNx : -refNx;\n const normalY = refIsA ? refNy : -refNy;\n\n out.a = a;\n out.b = b;\n out.normalX = normalX;\n out.normalY = normalY;\n normalScratch.x = normalX;\n normalScratch.y = normalY;\n Vec2.perp(normalScratch, tangentScratch);\n out.tangentX = tangentScratch.x;\n out.tangentY = tangentScratch.y;\n out.friction = Math.sqrt(a.friction * b.friction);\n out.pointCount = written;\n return true;\n}\n","/** Per-world scratch: contact manifolds and the flat vertex/normal/AABB\n * buffers `world.ts` writes body geometry into each step. Growth doubles\n * capacity and rebuilds arrays from scratch — pool contents are fully\n * rewritten every step, so nothing needs to be copied forward. */\n\nimport type { Body } from './body.js';\n\nexport interface ManifoldPoint {\n x: number;\n y: number;\n penetration: number;\n normalImpulse: number;\n tangentImpulse: number;\n raX: number;\n raY: number;\n rbX: number;\n rbY: number;\n massNormal: number;\n massTangent: number;\n bias: number;\n}\n\nexport interface Manifold {\n a: Body;\n b: Body;\n normalX: number;\n normalY: number;\n tangentX: number;\n tangentY: number;\n friction: number;\n pointCount: number;\n points: ManifoldPoint[];\n}\n\nexport interface SolverPool {\n manifolds: Manifold[];\n manifoldCount: number;\n verts: Float64Array;\n normals: Float64Array;\n aabbs: Float64Array;\n capacityBodies: number;\n}\n\nfunction createManifoldPoint(): ManifoldPoint {\n return {\n x: 0,\n y: 0,\n penetration: 0,\n normalImpulse: 0,\n tangentImpulse: 0,\n raX: 0,\n raY: 0,\n rbX: 0,\n rbY: 0,\n massNormal: 0,\n massTangent: 0,\n bias: 0,\n };\n}\n\nfunction createManifold(): Manifold {\n return {\n a: undefined as unknown as Body,\n b: undefined as unknown as Body,\n normalX: 0,\n normalY: 0,\n tangentX: 0,\n tangentY: 0,\n friction: 0,\n pointCount: 0,\n points: [createManifoldPoint(), createManifoldPoint()],\n };\n}\n\nexport function createPool(): SolverPool {\n return {\n manifolds: [],\n manifoldCount: 0,\n verts: new Float64Array(0),\n normals: new Float64Array(0),\n aabbs: new Float64Array(0),\n capacityBodies: 0,\n };\n}\n\n/** Grows the per-body geometry buffers to hold at least `count` bodies (8\n * doubles per body for verts/normals, 4 for the AABB). No-op if already\n * large enough. */\nexport function ensureBodyCapacity(pool: SolverPool, count: number): void {\n if (count <= pool.capacityBodies) return;\n let cap = pool.capacityBodies === 0 ? 8 : pool.capacityBodies;\n while (cap < count) cap *= 2;\n pool.verts = new Float64Array(cap * 8);\n pool.normals = new Float64Array(cap * 8);\n pool.aabbs = new Float64Array(cap * 4);\n pool.capacityBodies = cap;\n}\n\n/** Grows `pool.manifolds` to hold at least `count` entries, appending\n * fully-formed `Manifold` objects (each with two pre-created points).\n * Never truncates — manifolds past `manifoldCount` simply go unused, which\n * is what keeps the steady state allocation-free. */\nexport function ensureManifoldCapacity(pool: SolverPool, count: number): void {\n while (pool.manifolds.length < count) {\n pool.manifolds.push(createManifold());\n }\n}\n","/** The rigid-body world: gravity integration + a sequential-impulse contact\n * solver (Box2D-lite structure) with Coulomb friction and Baumgarte\n * de-penetration, plus a restitution term for bounce.\n *\n * Modeled as a plain object + free functions (no class) to match the house\n * style. The pool lives in a module-level `WeakMap` so `World` keeps its\n * frozen three-field public shape — one lookup per `step`, not per body. */\n\nimport type { Vec2 } from '@flare-engine/math';\nimport type { Body } from './body.js';\nimport { isStatic, writeAabb, writeBoxNormals, writeBoxVertices } from './body.js';\nimport { collideInto } from './manifold.js';\nimport { createPool, ensureBodyCapacity, ensureManifoldCapacity, type SolverPool } from './pool.js';\n\nexport interface WorldOptions {\n /** Acceleration applied to every dynamic body each step (px/s²). */\n gravity?: Vec2;\n /** Sequential-impulse iterations per step. More = stiffer stacks. */\n iterations?: number;\n}\n\nexport interface World {\n bodies: Body[];\n gravity: Vec2;\n iterations: number;\n}\n\nconst BAUMGARTE = 0.2;\nconst PENETRATION_SLOP = 0.5;\n\nconst pools = new WeakMap<World, SolverPool>();\n\nexport function createWorld(opts: WorldOptions = {}): World {\n const world: World = {\n bodies: [],\n gravity: opts.gravity ? { x: opts.gravity.x, y: opts.gravity.y } : { x: 0, y: 1800 },\n iterations: opts.iterations ?? 10,\n };\n pools.set(world, createPool());\n return world;\n}\n\nexport function addBody(world: World, body: Body): Body {\n world.bodies.push(body);\n return body;\n}\n\n/** Relative velocity at a contact projected onto (dx, dy). The vector never\n * escapes — its only use is a dot product, so it collapses to two scalars. */\nfunction relativeVelocityAlong(\n a: Body,\n b: Body,\n raX: number,\n raY: number,\n rbX: number,\n rbY: number,\n dx: number,\n dy: number,\n): number {\n const vax = a.velocity.x - a.angularVelocity * raY;\n const vay = a.velocity.y + a.angularVelocity * raX;\n const vbx = b.velocity.x - b.angularVelocity * rbY;\n const vby = b.velocity.y + b.angularVelocity * rbX;\n return (vbx - vax) * dx + (vby - vay) * dy;\n}\n\nfunction applyImpulse(body: Body, ix: number, iy: number, rx: number, ry: number): void {\n if (body.invMass === 0) return;\n body.velocity.x += ix * body.invMass;\n body.velocity.y += iy * body.invMass;\n body.angularVelocity += body.invInertia * (rx * iy - ry * ix);\n}\n\n/** Writes every body's world-space vertices, outward normals, and AABB into\n * `pool`, once per step, so the broadphase pair loop never recomputes them. */\nfunction writeGeometry(pool: SolverPool, bodies: Body[]): void {\n ensureBodyCapacity(pool, bodies.length);\n for (let i = 0; i < bodies.length; i++) {\n const b = bodies[i]!;\n const vOffset = i * 8;\n const aOffset = i * 4;\n writeBoxVertices(b, pool.verts, vOffset);\n writeBoxNormals(pool.verts, vOffset, b.position.x, b.position.y, pool.normals, vOffset);\n writeAabb(pool.verts, vOffset, pool.aabbs, aOffset);\n }\n}\n\n/** Broadphase (precomputed-AABB reject) + narrowphase (OBB manifold),\n * writing surviving contacts into `pool.manifolds[0..manifoldCount)`. */\nfunction collectManifolds(pool: SolverPool, bodies: Body[]): void {\n pool.manifoldCount = 0;\n const aabbs = pool.aabbs;\n for (let i = 0; i < bodies.length; i++) {\n const a = bodies[i]!;\n const aOff = i * 4;\n const aMinX = aabbs[aOff]!;\n const aMinY = aabbs[aOff + 1]!;\n const aMaxX = aabbs[aOff + 2]!;\n const aMaxY = aabbs[aOff + 3]!;\n for (let j = i + 1; j < bodies.length; j++) {\n const b = bodies[j]!;\n if (isStatic(a) && isStatic(b)) continue;\n const bOff = j * 4;\n const bMinX = aabbs[bOff]!;\n const bMinY = aabbs[bOff + 1]!;\n const bMaxX = aabbs[bOff + 2]!;\n const bMaxY = aabbs[bOff + 3]!;\n if (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) continue;\n ensureManifoldCapacity(pool, pool.manifoldCount + 1);\n const m = pool.manifolds[pool.manifoldCount]!;\n if (collideInto(a, b, pool.verts, pool.normals, i, j, m)) pool.manifoldCount++;\n }\n }\n}\n\n/** Derives per-point solver mass terms and the velocity/position bias for\n * every live manifold, writing them onto the manifold's own contact points. */\nfunction prepare(pool: SolverPool, gravity: Vec2, dt: number): void {\n const restThreshold = Math.hypot(gravity.x, gravity.y) * dt;\n for (let mi = 0; mi < pool.manifoldCount; mi++) {\n const m = pool.manifolds[mi]!;\n const { a, b } = m;\n const normalX = m.normalX;\n const normalY = m.normalY;\n const tangentX = m.tangentX;\n const tangentY = m.tangentY;\n const restitution = Math.min(a.restitution, b.restitution);\n\n for (let pi = 0; pi < m.pointCount; pi++) {\n const cp = m.points[pi]!;\n const raX = cp.x - a.position.x;\n const raY = cp.y - a.position.y;\n const rbX = cp.x - b.position.x;\n const rbY = cp.y - b.position.y;\n\n const rnA = raX * normalY - raY * normalX;\n const rnB = rbX * normalY - rbY * normalX;\n const kNormal = a.invMass + b.invMass + a.invInertia * rnA * rnA + b.invInertia * rnB * rnB;\n\n const rtA = raX * tangentY - raY * tangentX;\n const rtB = rbX * tangentY - rbY * tangentX;\n const kTangent = a.invMass + b.invMass + a.invInertia * rtA * rtA + b.invInertia * rtB * rtB;\n\n const vn = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, normalX, normalY);\n const restitutionBias = vn < -restThreshold ? -restitution * vn : 0;\n const baumgarte = (BAUMGARTE / dt) * Math.max(0, cp.penetration - PENETRATION_SLOP);\n\n cp.raX = raX;\n cp.raY = raY;\n cp.rbX = rbX;\n cp.rbY = rbY;\n cp.massNormal = kNormal > 0 ? 1 / kNormal : 0;\n cp.massTangent = kTangent > 0 ? 1 / kTangent : 0;\n cp.bias = restitutionBias + baumgarte;\n }\n }\n}\n\n/** One sequential-impulse sweep over every live manifold's contact points. */\nfunction solve(pool: SolverPool): void {\n for (let mi = 0; mi < pool.manifoldCount; mi++) {\n const m = pool.manifolds[mi]!;\n const { a, b, friction } = m;\n const normalX = m.normalX;\n const normalY = m.normalY;\n const tangentX = m.tangentX;\n const tangentY = m.tangentY;\n\n for (let pi = 0; pi < m.pointCount; pi++) {\n const cp = m.points[pi]!;\n const raX = cp.raX;\n const raY = cp.raY;\n const rbX = cp.rbX;\n const rbY = cp.rbY;\n\n // Normal impulse (clamped >= 0 so contacts push, never pull).\n const vn = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, normalX, normalY);\n let dPn = cp.massNormal * (-vn + cp.bias);\n const newNormal = Math.max(cp.normalImpulse + dPn, 0);\n dPn = newNormal - cp.normalImpulse;\n cp.normalImpulse = newNormal;\n const pnX = normalX * dPn;\n const pnY = normalY * dPn;\n applyImpulse(a, -pnX, -pnY, raX, raY);\n applyImpulse(b, pnX, pnY, rbX, rbY);\n\n // Friction impulse (clamped to the Coulomb cone mu*Pn).\n const vt = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, tangentX, tangentY);\n let dPt = cp.massTangent * -vt;\n const maxPt = friction * cp.normalImpulse;\n const newTangent = Math.max(-maxPt, Math.min(maxPt, cp.tangentImpulse + dPt));\n dPt = newTangent - cp.tangentImpulse;\n cp.tangentImpulse = newTangent;\n const ptX = tangentX * dPt;\n const ptY = tangentY * dPt;\n applyImpulse(a, -ptX, -ptY, raX, raY);\n applyImpulse(b, ptX, ptY, rbX, rbY);\n }\n }\n}\n\n/** Advance the world by `dt` seconds (fixed steps recommended).\n *\n * Resting contacts settle to a stable *position* but retain a small, bounded\n * velocity oscillation at the penetration-slop equilibrium — a sequential-\n * impulse solver with Baumgarte bias and no sleeping does not converge\n * velocity to exactly zero at rest, only the position does. This is\n * inherent to the algorithm, not a bug; sleeping and damping are\n * deliberately out of scope for v1. A consumer needing pixel-exact\n * stillness should clamp small resting velocities at its own layer. */\nexport function step(world: World, dt: number): void {\n if (dt <= 0) return;\n // `World` is a public interface with plain fields, so a consumer can hand\n // back a structurally valid value that never went through `createWorld`\n // (spread copy, `structuredClone`, state round-tripping) and is therefore\n // absent from `pools`. Lazily adopt it instead of crashing on a missing\n // entry; steady-state callers (worlds from `createWorld`) never pay this\n // branch since their pool is already registered.\n let pool = pools.get(world);\n if (!pool) {\n pool = createPool();\n pools.set(world, pool);\n }\n const { bodies } = world;\n\n // 1. Integrate velocities (apply gravity to dynamic bodies).\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (isStatic(body)) continue;\n body.velocity.x += world.gravity.x * dt;\n body.velocity.y += world.gravity.y * dt;\n }\n\n // 2. Broadphase (precomputed AABB reject) + narrowphase (OBB manifold).\n writeGeometry(pool, bodies);\n collectManifolds(pool, bodies);\n\n // 3. Solve velocity constraints.\n prepare(pool, world.gravity, dt);\n for (let iter = 0; iter < world.iterations; iter++) {\n solve(pool);\n }\n\n // 4. Integrate positions.\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (isStatic(body)) continue;\n body.position.x += body.velocity.x * dt;\n body.position.y += body.velocity.y * dt;\n body.angle += body.angularVelocity * dt;\n }\n}\n"],"mappings":"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"}