glemy/physics/rect
A plain axis-aligned rectangle and the circle-vs-rectangle overlap
math a game needs to collide a physics.Entity against a static
rectangular obstacle (a paddle, a brick, a platform). Originally
built game-local to glemy/games/breakout (decision 0052, when it
had exactly one real caller); promoted here once
glemy/games/platformer needed the identical detection math for
player-vs-platform collision – the same detection-vs-resolution
split glemy/physics/collision.overlap already draws against
collision.resolve: this module only ever answers “does a circle
overlap this rectangle, and along which axis” – what a game does
with that answer (reflect off a brick, stop dead on a platform) stays
entirely up to the game. See decision 0058 for the full reasoning
(a game may never import another game’s own games/<name>/ tree, so
once a second game needed this exact math, duplicating it a second
time was the only alternative to promoting it here).
A Rect is deliberately still its own type here, not
glemy/physics/bounds.Bounds reused: a Bounds is specifically the
physics world’s own containing box (what entities bounce inside of),
while a Rect is an inert obstacle a circle collides against –
different roles that happen to share a shape, not the same concept.
Types
Which axis a collision between ball and rect happened along –
whichever axis ball’s center has penetrated further past the
nearest point on rect, used by a game to decide which velocity
component its own resolution logic should act on (e.g.
glemy/games/breakout/brick.reflect flips it; a solid-landing game
zeroes it instead).
pub type Axis {
Horizontal
Vertical
}
Constructors
-
Horizontal -
Vertical
An axis-aligned rectangle, defined by its minimum and maximum
corners – see this module’s own doc comment for why it’s a distinct
type from glemy/physics/bounds.Bounds.
pub type Rect {
Rect(min: vector2.Vector2, max: vector2.Vector2)
}
Constructors
-
Rect(min: vector2.Vector2, max: vector2.Vector2)
Values
pub fn nearest_point(
circle_center: vector2.Vector2,
rect: Rect,
) -> vector2.Vector2
The point on rect’s boundary (or interior, if circle_center is
already inside it) closest to circle_center – clamping each axis
independently into rect’s range.
nearest_point(
Vector2(15.0, 5.0),
Rect(min: Vector2(0.0, 0.0), max: Vector2(10.0, 10.0)),
)
// -> Vector2(10.0, 5.0)
pub fn overlaps(ball: entity.Entity, rect: Rect) -> Bool
Whether ball overlaps rect – the standard clamp-to-nearest-point
circle-vs-AABB test: true if the nearest point on rect to ball’s
center is closer than ball.radius. Strict <., matching
glemy/physics/collision.overlap’s own convention elsewhere in this
project (>. 0.0, never >=.) – exactly touching doesn’t count as
overlapping.
pub fn penetration_axis(ball: entity.Entity, rect: Rect) -> Axis
The degenerate case (ball’s center exactly at the nearest point –
only reachable via tunneling on an unreasonably large dt) falls
back to Horizontal, arbitrarily but harmlessly – same category of
fallback as glemy/physics/collision.resolve’s own zero-distance
normal case.
pub fn to_css(
rect: Rect,
bounds: bounds.Bounds,
) -> #(Float, Float, Float, Float)
Converts a world-space rect into a CSS box within bounds, as
#(left_fraction, top_fraction, width_fraction, height_fraction) –
each 0.0-1.0, ready to multiply by 100 for a %-based CSS
position/size. Originally kept game-local to glemy/game_breakout
(exactly one real caller, reused there for both the paddle and every
brick); promoted here once glemy/game_platformer needed the
identical conversion for its own platform/goal overlay.
to_css(
Rect(min: Vector2(20.0, 10.0), max: Vector2(40.0, 30.0)),
Bounds(min: Vector2(0.0, 0.0), max: Vector2(100.0, 100.0)),
)
// -> #(0.2, 0.7, 0.2, 0.2)