@flare-engine/dynamics 0.4.5 → 0.4.7

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.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Vec2 } from "@flare-engine/math";
2
- //#region src/body.d.ts
3
2
  interface Body {
4
3
  /** Center of mass, world space. */
5
4
  position: Vec2;
@@ -33,10 +32,8 @@ interface BodyOptions {
33
32
  /** Static bodies have infinite mass and never move. */
34
33
  static?: boolean;
35
34
  }
36
- declare function createBody(opts: BodyOptions): Body;
37
- declare function isStatic(b: Body): boolean;
38
- //#endregion
39
- //#region src/world.d.ts
35
+ export declare function createBody(opts: BodyOptions): Body;
36
+ export declare function isStatic(b: Body): boolean;
40
37
  interface WorldOptions {
41
38
  /** Acceleration applied to every dynamic body each step (px/s²). */
42
39
  gravity?: Vec2;
@@ -48,8 +45,8 @@ interface World {
48
45
  gravity: Vec2;
49
46
  iterations: number;
50
47
  }
51
- declare function createWorld(opts?: WorldOptions): World;
52
- declare function addBody(world: World, body: Body): Body;
48
+ export declare function createWorld(opts?: WorldOptions): World;
49
+ export declare function addBody(world: World, body: Body): Body;
53
50
  /** Advance the world by `dt` seconds (fixed steps recommended).
54
51
  *
55
52
  * Resting contacts settle to a stable *position* but retain a small, bounded
@@ -59,7 +56,6 @@ declare function addBody(world: World, body: Body): Body;
59
56
  * inherent to the algorithm, not a bug; sleeping and damping are
60
57
  * deliberately out of scope for v1. A consumer needing pixel-exact
61
58
  * stillness should clamp small resting velocities at its own layer. */
62
- declare function step(world: World, dt: number): void;
63
- //#endregion
64
- export { type Body, type BodyOptions, type World, type WorldOptions, addBody, createBody, createWorld, isStatic, step };
59
+ export declare function step(world: World, dt: number): void;
60
+ export type { Body, BodyOptions, World, WorldOptions };
65
61
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/body.ts","../src/world.ts"],"mappings":";;UAOiB;;EAEf,UAAU;;EAEV,UAAU;;EAEV;;EAEA;;EAEA,MAAM;;EAEN;;EAEA;;EAEA;;EAEA;;UAGe;EACf,UAAU;EACV,MAAM;;EAEN;EACA,WAAW;EACX;EACA;EACA;EACA;;EAEA;;iBAGc,WAAW,MAAM,cAAc;iBA6B/B,SAAS,GAAG;;;UCzDX;;EAEf,UAAU;;EAEV;;UAGe;EACf,QAAQ;EACR,SAAS;EACT;;iBAQc,YAAY,OAAM,eAAoB;iBAUtC,QAAQ,OAAO,OAAO,MAAM,OAAO;;;;;;;;;;iBAwKnC,KAAK,OAAO,OAAO"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/body.ts","../src/world.ts"],"mappings":";UAOiB;;EAEf,UAAU;;EAEV,UAAU;;EAEV;;EAEA;;EAEA,MAAM;;EAEN;;EAEA;;EAEA;;EAEA;;UAGe;EACf,UAAU;EACV,MAAM;;EAEN;EACA,WAAW;EACX;EACA;EACA;EACA;;EAEA;;wBAGc,WAAW,MAAM,cAAc;wBA6B/B,SAAS,GAAG;UCzDX;;EAEf,UAAU;;EAEV;;UAGe;EACf,QAAQ;EACR,SAAS;EACT;;wBAQc,YAAY,OAAM,eAAoB;wBAUtC,QAAQ,OAAO,OAAO,MAAM,OAAO;;;;;;;;;;wBAwKnC,KAAK,OAAO,OAAO"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Vec2 } from "@flare-engine/math";
2
- //#region src/body.d.ts
3
2
  interface Body {
4
3
  /** Center of mass, world space. */
5
4
  position: Vec2;
@@ -33,10 +32,8 @@ interface BodyOptions {
33
32
  /** Static bodies have infinite mass and never move. */
34
33
  static?: boolean;
35
34
  }
36
- declare function createBody(opts: BodyOptions): Body;
37
- declare function isStatic(b: Body): boolean;
38
- //#endregion
39
- //#region src/world.d.ts
35
+ export declare function createBody(opts: BodyOptions): Body;
36
+ export declare function isStatic(b: Body): boolean;
40
37
  interface WorldOptions {
41
38
  /** Acceleration applied to every dynamic body each step (px/s²). */
42
39
  gravity?: Vec2;
@@ -48,8 +45,8 @@ interface World {
48
45
  gravity: Vec2;
49
46
  iterations: number;
50
47
  }
51
- declare function createWorld(opts?: WorldOptions): World;
52
- declare function addBody(world: World, body: Body): Body;
48
+ export declare function createWorld(opts?: WorldOptions): World;
49
+ export declare function addBody(world: World, body: Body): Body;
53
50
  /** Advance the world by `dt` seconds (fixed steps recommended).
54
51
  *
55
52
  * Resting contacts settle to a stable *position* but retain a small, bounded
@@ -59,7 +56,6 @@ declare function addBody(world: World, body: Body): Body;
59
56
  * inherent to the algorithm, not a bug; sleeping and damping are
60
57
  * deliberately out of scope for v1. A consumer needing pixel-exact
61
58
  * stillness should clamp small resting velocities at its own layer. */
62
- declare function step(world: World, dt: number): void;
63
- //#endregion
64
- export { type Body, type BodyOptions, type World, type WorldOptions, addBody, createBody, createWorld, isStatic, step };
59
+ export declare function step(world: World, dt: number): void;
60
+ export type { Body, BodyOptions, World, WorldOptions };
65
61
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/body.ts","../src/world.ts"],"mappings":";;UAOiB;;EAEf,UAAU;;EAEV,UAAU;;EAEV;;EAEA;;EAEA,MAAM;;EAEN;;EAEA;;EAEA;;EAEA;;UAGe;EACf,UAAU;EACV,MAAM;;EAEN;EACA,WAAW;EACX;EACA;EACA;EACA;;EAEA;;iBAGc,WAAW,MAAM,cAAc;iBA6B/B,SAAS,GAAG;;;UCzDX;;EAEf,UAAU;;EAEV;;UAGe;EACf,QAAQ;EACR,SAAS;EACT;;iBAQc,YAAY,OAAM,eAAoB;iBAUtC,QAAQ,OAAO,OAAO,MAAM,OAAO;;;;;;;;;;iBAwKnC,KAAK,OAAO,OAAO"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/body.ts","../src/world.ts"],"mappings":";UAOiB;;EAEf,UAAU;;EAEV,UAAU;;EAEV;;EAEA;;EAEA,MAAM;;EAEN;;EAEA;;EAEA;;EAEA;;UAGe;EACf,UAAU;EACV,MAAM;;EAEN;EACA,WAAW;EACX;EACA;EACA;EACA;;EAEA;;wBAGc,WAAW,MAAM,cAAc;wBA6B/B,SAAS,GAAG;UCzDX;;EAEf,UAAU;;EAEV;;UAGe;EACf,QAAQ;EACR,SAAS;EACT;;wBAQc,YAAY,OAAM,eAAoB;wBAUtC,QAAQ,OAAO,OAAO,MAAM,OAAO;;;;;;;;;;wBAwKnC,KAAK,OAAO,OAAO"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _flare_engine_math = require("@flare-engine/math");
3
- //#region src/body.ts
3
+ // src/body.ts
4
4
  function createBody(opts) {
5
5
  const half = opts.half;
6
6
  const density = opts.density ?? 1;
@@ -107,8 +107,7 @@ function writeAabb(verts, vOffset, out, offset) {
107
107
  out[offset + 2] = maxX;
108
108
  out[offset + 3] = maxY;
109
109
  }
110
- //#endregion
111
- //#region src/manifold.ts
110
+ // src/manifold.ts
112
111
  /** OBB-OBB collision: separating-axis test + reference/incident face clipping,
113
112
  * building a 1-2 point contact manifold with per-point penetration depth.
114
113
  *
@@ -297,8 +296,7 @@ function collideInto(a, b, verts, normals, ai, bi, out) {
297
296
  out.pointCount = written;
298
297
  return true;
299
298
  }
300
- //#endregion
301
- //#region src/pool.ts
299
+ // src/pool.ts
302
300
  function createManifoldPoint() {
303
301
  return {
304
302
  x: 0,
@@ -357,8 +355,7 @@ function ensureBodyCapacity(pool, count) {
357
355
  function ensureManifoldCapacity(pool, count) {
358
356
  while (pool.manifolds.length < count) pool.manifolds.push(createManifold());
359
357
  }
360
- //#endregion
361
- //#region src/world.ts
358
+ // src/world.ts
362
359
  const BAUMGARTE = .2;
363
360
  const PENETRATION_SLOP = .5;
364
361
  const pools = /* @__PURE__ */ new WeakMap();
@@ -545,7 +542,6 @@ function step(world, dt) {
545
542
  body.angle += body.angularVelocity * dt;
546
543
  }
547
544
  }
548
- //#endregion
549
545
  exports.addBody = addBody;
550
546
  exports.createBody = createBody;
551
547
  exports.createWorld = createWorld;
package/dist/index.js.map CHANGED
@@ -1 +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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"}
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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"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Vec2 } from "@flare-engine/math";
2
- //#region src/body.ts
2
+ // src/body.ts
3
3
  function createBody(opts) {
4
4
  const half = opts.half;
5
5
  const density = opts.density ?? 1;
@@ -106,8 +106,7 @@ function writeAabb(verts, vOffset, out, offset) {
106
106
  out[offset + 2] = maxX;
107
107
  out[offset + 3] = maxY;
108
108
  }
109
- //#endregion
110
- //#region src/manifold.ts
109
+ // src/manifold.ts
111
110
  /** OBB-OBB collision: separating-axis test + reference/incident face clipping,
112
111
  * building a 1-2 point contact manifold with per-point penetration depth.
113
112
  *
@@ -296,8 +295,7 @@ function collideInto(a, b, verts, normals, ai, bi, out) {
296
295
  out.pointCount = written;
297
296
  return true;
298
297
  }
299
- //#endregion
300
- //#region src/pool.ts
298
+ // src/pool.ts
301
299
  function createManifoldPoint() {
302
300
  return {
303
301
  x: 0,
@@ -356,8 +354,7 @@ function ensureBodyCapacity(pool, count) {
356
354
  function ensureManifoldCapacity(pool, count) {
357
355
  while (pool.manifolds.length < count) pool.manifolds.push(createManifold());
358
356
  }
359
- //#endregion
360
- //#region src/world.ts
357
+ // src/world.ts
361
358
  const BAUMGARTE = .2;
362
359
  const PENETRATION_SLOP = .5;
363
360
  const pools = /* @__PURE__ */ new WeakMap();
@@ -544,7 +541,6 @@ function step(world, dt) {
544
541
  body.angle += body.angularVelocity * dt;
545
542
  }
546
543
  }
547
- //#endregion
548
544
  export { addBody, createBody, createWorld, isStatic, step };
549
545
 
550
546
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","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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"}
1
+ {"version":3,"file":"index.mjs","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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flare-engine/dynamics",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "2D rigid bodies: oriented boxes, contact manifolds, sequential-impulse solver",
5
5
  "keywords": ["flare-engine", "physics", "rigid-body", "obb", "solver"],
6
6
  "license": "Apache-2.0",
@@ -37,7 +37,7 @@
37
37
  "typecheck": "tsc --noEmit"
38
38
  },
39
39
  "dependencies": {
40
- "@flare-engine/math": "^0.4.5"
40
+ "@flare-engine/math": "^0.4.7"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=20"