bubbles / files / src / physics / RigidBody.ts
RigidBody.ts
dcc4757f 2026-04-09 Pavel Safronov | 1 | import { Vector2 } from './Vector2.js'; |
| 2 | ||
| 3 | export interface RigidBodyOptions { | |
| 4 | position: Vector2; | |
| 5 | radius: number; | |
| 6 | /** g/cm³ equivalent — water = 1.0; < 1 floats, > 1 sinks */ | |
| 7 | density: number; | |
| 8 | /** Display label — emoji or short string */ | |
| 9 | label: string; | |
fc6e1fbf 2026-04-09 Pavel Safronov | 10 | /** Optional pre-rendered texture drawn instead of label */ |
| 11 | texture?: HTMLCanvasElement; | |
| 12 | /** Permanent bodies are never evicted by the 12-body cap */ | |
| 13 | permanent?: boolean; | |
dcc4757f 2026-04-09 Pavel Safronov | 14 | } |
| 15 | ||
| 16 | export class RigidBody { | |
| 17 | position: Vector2; | |
| 18 | velocity: Vector2; | |
| 19 | readonly radius: number; | |
| 20 | readonly density: number; | |
| 21 | readonly label: string; | |
fc6e1fbf 2026-04-09 Pavel Safronov | 22 | readonly texture: HTMLCanvasElement | undefined; |
| 23 | readonly permanent: boolean; | |
dcc4757f 2026-04-09 Pavel Safronov | 24 | /** Rotation angle in radians, for visual wobble */ |
| 25 | angle: number = 0; | |
| 26 | angularVelocity: number = 0; | |
| 27 | /** Unique id for removal tracking */ | |
| 28 | readonly id: number; | |
| 29 | ||
| 30 | private static _nextId = 0; | |
| 31 | ||
| 32 | constructor(opts: RigidBodyOptions) { | |
| 33 | this.position = opts.position.clone(); | |
| 34 | this.velocity = Vector2.zero(); | |
| 35 | this.radius = opts.radius; | |
| 36 | this.density = opts.density; | |
| 37 | this.label = opts.label; | |
fc6e1fbf 2026-04-09 Pavel Safronov | 38 | this.texture = opts.texture; |
| 39 | this.permanent = opts.permanent ?? false; | |
dcc4757f 2026-04-09 Pavel Safronov | 40 | this.id = RigidBody._nextId++; |
| 41 | } | |
| 42 | ||
| 43 | get mass(): number { | |
| 44 | return this.density * Math.PI * this.radius * this.radius; | |
| 45 | } | |
| 46 | } |