Camera

Pure 2D camera settings for world-space drawing.

A camera is a value, not a host-owned resource. Build the camera you want for the current frame and pass it to Draw.with_camera!, which applies it for the duration of a nested scope.

camera = Camera.centered(model.player, Math.vec2(800, 600))
frame.with_camera!(camera, |world| {
    world.circle!({ center: model.player, radius: 12, style: Draw.filled(Color.white) })
    Ok({})
})?

Every constructor and builder here is total: it answers with a Camera2D rather than a Try. A camera has exactly two invariants -- every transform field is finite, and the zoom is non-zero so screen_to_world can invert it -- and the only inputs that can break them are inputs no caller means to pass. Rather than make every app carry an unreachable Err branch for those, they are sanitized on the way in. A target or offset component that is not finite becomes 0, as does a rotation that is not finite; a zoom that is zero or not finite becomes fallback_zoom, keeping its sign so a mirrored camera stays mirrored.

Nothing else is touched. Sanitizing only ever rewrites a value a validating API would have refused outright, so a camera built from ordinary numbers comes back with its fields unchanged -- negative, axis-mirroring zooms included.

fallback_zoom : F32

The zoom substituted for a zoom that cannot be used, meaning zero (which has no inverse) or a non-finite one (which has no meaning).

Deliberately tiny rather than 1: a camera that had to be rescued collapses the world to a dot at its offset, which reads on screen as the mistake it is, instead of quietly looking plausible.

default : Camera2D

Identity camera: no offset or rotation, with unit zoom.

new : Settings -> Camera2D

Construct a camera from explicit settings.

Total: non-finite target, offset, and rotation components become 0, and a zoom of zero or one that is not finite becomes fallback_zoom with the same sign. Every usable setting is kept exactly.

centered : Vec2, Vec2 -> Camera2D

Place target at the center of a screen with unit zoom.

Total: a non-finite component of either vector becomes 0.

follow : Vec2, { screen : Vec2, zoom : F32 } -> Camera2D

A common player-follow camera with a configurable zoom.

Total: a non-finite target or screen component becomes 0, and a zoom of zero or one that is not finite becomes fallback_zoom with the same sign.

Settings : {
    target : Vec2,
    offset : Vec2,
    rotation : F32,
    zoom : F32,
}

Target, screen offset, clockwise rotation in degrees, and zoom factor.

Camera2D

Camera.Camera2D :: # (opaque)

Immutable camera value accepted by drawing and coordinate transforms. Camera.Camera2D and Draw.CameraMode on the platform are this same type, re-exported.

Its representation is opaque so non-finite transform fields, or a zero zoom that has no inverse, cannot bypass the sanitizing every constructor applies. The module header states what is rewritten and what is left alone.

target : Camera2D -> Vec2

World-space point placed at the camera offset.

offset : Camera2D -> Vec2

Logical screen-space offset of the target.

rotation : Camera2D -> F32

Clockwise camera rotation in degrees.

zoom : Camera2D -> F32

Non-zero camera zoom factor. Negative values intentionally mirror axes.

with_target : Camera2D, Vec2 -> Camera2D

Return a copy focused on new_target.

Total: a component of new_target that is not finite becomes 0, and every finite coordinate is kept exactly.

with_offset : Camera2D, Vec2 -> Camera2D

Return a copy with new_offset as its logical screen-space offset.

Total: a component of new_offset that is not finite becomes 0, and every finite coordinate is kept exactly.

with_rotation : Camera2D, F32 -> Camera2D

Return a copy rotated new_rotation degrees clockwise.

Total: a new_rotation that is not finite becomes 0. Any finite angle is kept exactly, including one outside 0 to 360 -- degrees wrap through the sine and cosine, so there is no range to clamp to.

with_zoom : Camera2D, F32 -> Camera2D

Return a copy zoomed by new_zoom.

Total: a new_zoom of zero has no inverse and one that is not finite has no meaning, so either becomes fallback_zoom with the same sign. Every other zoom is kept exactly, so a negative zoom still mirrors both axes.

clamp_zoom : Camera2D, { min : F32, max : F32 } -> Camera2D

Clamp zoom to inclusive limits.

Total: limits that produce a zero or non-finite zoom fall back to fallback_zoom, exactly as with_zoom does.

world_to_screen : Camera2D, Vec2 -> Vec2

Convert a world-space point to logical screen coordinates. Rotation uses degrees, matching raylib and Draw.with_camera!.

screen_to_world : Camera2D, Vec2 -> Vec2

Convert logical screen coordinates back to world space. Every Camera2D is invertible because no constructor lets a zoom of zero through.

viewport : Camera2D, Vec2 -> Rect

World-space axis-aligned bounds visible through this camera. This handles non-centered offsets, rotation, and mirrored (negative) zoom.