glemy/physics/collision
Values
pub const genuine_impact_velocity: Float
Reference closing speed (velocity_along_normal) at/above which a
collision’s impulse uses full restitution. Below it, the effective
restitution scales down proportionally
(restitution *. min(1.0, velocity_along_normal /. genuine_impact_velocity))
— not a hard “below X, absorb to 0; at/above X, full elastic” cutoff.
Same value as glemy/physics/bounds’s genuine_impact_velocity, for
consistency, and same reasoning, in full there: a hard cutoff was
tried and shipped first, then directly measured (a real, repeated,
multi-second headless-browser trace, not just gleam test passing)
to let two different-mass, stacked entities settle into a perfectly
periodic, never-decaying bounce with its impact speed sitting just
above the cutoff forever — a hard threshold is a discontinuity the
dynamics can balance on regardless of its exact value (both 3.0
and a Box2D-derived 10.0 exhibited this). A smooth ramp can’t be
balanced on: below genuine_impact_velocity, every bounce returns
strictly less energy than it received, forcing any resonance to
shrink every cycle instead of holding steady. See
docs/decisions.jsonl, decisions 0027 and 0028.
pub fn overlap(a: entity.Entity, b: entity.Entity) -> Float
How far two circular entities are overlapping — positive means they
overlap by that many world units, zero or negative means they aren’t
touching at all. Exposed on its own (not just inlined in resolve)
since a caller-supplied physics/collision_sweep interact function
often needs the same overlap test to decide whether two entities are
even touching, independent of whether they end up being
collision-resolved.
pub fn resolve(
a: entity.Entity,
b: entity.Entity,
) -> #(entity.Entity, entity.Entity)
Detects and resolves a collision between two circular entities:
separates them if they’re overlapping, and reflects their velocities
along the collision normal if they’re moving toward each other — at
full (elastic) strength for a genuine, higher-speed impact, or
proportionally softened for a gentle one (see
genuine_impact_velocity). If they aren’t overlapping, both are
returned unchanged.
Mass-aware (proportional to area — see mass) since decision 0018:
entities of very different sizes now collide realistically (a large
entity barely moves when struck by a tiny one) rather than assuming
equal mass regardless of radius (the original, now-superseded
decision 0007). The general unequal-mass elastic-impulse formula
this uses reduces exactly to the old equal-mass formula when
mass_a == mass_b — the textbook sanity check below still holds
unchanged: two equal circles meeting head-on with equal and opposite
velocities simply swap velocities. (Velocities of magnitude 20.0
here, not 1.0: velocity_along_normal needs to be at/above
genuine_impact_velocity for a full, undiminished elastic swap.)
resolve(
Entity(position: Vector2(0.0, 0.0), velocity: Vector2(20.0, 0.0), radius: 1.0, kind: 0),
Entity(position: Vector2(1.5, 0.0), velocity: Vector2(-20.0, 0.0), radius: 1.0, kind: 0),
)
// -> #(
// Entity(position: Vector2(-0.25, 0.0), velocity: Vector2(-20.0, 0.0), radius: 1.0, kind: 0),
// Entity(position: Vector2(1.75, 0.0), velocity: Vector2(20.0, 0.0), radius: 1.0, kind: 0),
// )
pub const restitution: Float
How much of a genuine, higher-speed impact’s closing velocity is
returned as bounce-back (see genuine_impact_velocity for how a
gentler closing speed gets proportionally less than even this). Not
1.0 (a fully elastic, energy-preserving bounce) deliberately: a
real headless-browser trace of a full-height drop with 1.0 showed
the entity rebounding off the floor by ~16 world units (16% of the
100-unit play area) on its very first bounce, then bouncing again –
nothing like the near-dead “plop” a merge-puzzle piece should have
on landing. 0.1 matches the restitution value used by a real,
working open-source clone of the reference game (TomboFry/suika-game,
Matter.js-based) rather than being guessed at – unlike a raw
gravity/velocity magnitude, a restitution coefficient is dimensionless
(a fraction of energy returned) and so is meaningfully comparable
across engines and world-unit scales. See decision 0029.