Cauldron2D.Camera (Cauldron2D v0.1.0)

Copy Markdown View Source

Resolves which part of a world is on screen.

Cauldron2D.Camera.view(
  focus: {x, y},
  bounds: {width, height},
  size: {columns, rows},
  cell: fn {x, y} -> cell end
)

:cell is called exactly once per visible tile, with that tile's world coordinate, and returns a Cauldron2D.Surface.cell/0. It is the only thing the engine asks of a game.

With :bounds, the viewport is clamped so it never shows anything outside the world, and a world smaller than the viewport is centred in it. With :unbounded, the viewport is centred on :focus and world coordinates may be negative.

A tile occupies two terminal cells across and one down. columns_for/1 and rows_for/1 convert a pane's size in cells into a viewport size in tiles; zoomed/2 scales that size for a zoom — how many times larger than native a tile is drawn — and tile_at/3 and zoom_about/6 map cells back through it. A :size may be fractional: the origin centres the focus exactly, and the tiles read cover the fraction.

Summary

Functions

How many tiles wide a pane width terminal cells across can show; always at least 1.

Where a viewport size tiles across sits in world coordinates when focus is at its exact centre, as floats.

The view as glyphs and colours, for renderers that draw characters.

Where a viewport size tiles across sits in world coordinates when centred on focus.

How many tiles tall a pane height terminal cells high can show; always at least 1.

The world tile under {column, row}, given in terminal cells relative to the pane's top-left corner, for a viewport whose origin is the first argument.

tile_at/2 through a zoom: a tile drawn zoom times its native size spans 2 * zoom cells across and zoom down.

Resolve the viewport and read every tile in it.

The focus that keeps the world under cell where it is on screen when a pane of native size goes from from to to zoom, centred on focus before; bounds as view/1 takes them, so that a clamped view stays clamped.

The viewport in tiles of a pane size tiles across at native size, drawn at zoom.

Types

point()

@type point() :: {integer(), integer()}

t()

@type t() :: %Cauldron2D.Camera{
  cells: [[Cauldron2D.Surface.cell()]],
  columns: pos_integer(),
  origin: point(),
  rows: pos_integer(),
  scroll: {float(), float()}
}

Functions

columns_for(width)

@spec columns_for(non_neg_integer()) :: pos_integer()

How many tiles wide a pane width terminal cells across can show; always at least 1.

exact_origin(arg1, arg2, arg3)

@spec exact_origin(
  {number(), number()},
  {number(), number()},
  {pos_integer(), pos_integer()} | :unbounded
) :: {float(), float()}

Where a viewport size tiles across sits in world coordinates when focus is at its exact centre, as floats.

Clamped to the world like origin/3; a world smaller than the viewport is centred.

glyphs(atlas, camera)

The view as glyphs and colours, for renderers that draw characters.

Each cell becomes {glyph, {r, g, b}} with the tint already applied, in the same row and column order as :cells. A cell with overlays takes the glyph and colour of its topmost overlay; :void becomes two spaces in the atlas's void colour.

origin(focus, size, bounds \\ :unbounded)

@spec origin(
  point(),
  {pos_integer(), pos_integer()},
  {pos_integer(), pos_integer()} | :unbounded
) ::
  point()

Where a viewport size tiles across sits in world coordinates when centred on focus.

This is the :origin view/1 produces for the same arguments, so a caller mapping a screen position back to the world resolves against the same tiles that were drawn.

A fractional focus is rounded to the nearest tile first.

With {width, height} bounds the result is clamped to 0..width - columns horizontally and 0..height - rows vertically. When the world is narrower or shorter than the viewport, that axis is centred instead, which puts the origin at a negative coordinate.

rows_for(height)

@spec rows_for(non_neg_integer()) :: pos_integer()

How many tiles tall a pane height terminal cells high can show; always at least 1.

tile_at(arg1, arg2)

@spec tile_at(point(), {number(), number()}) :: {float(), float()}

The world tile under {column, row}, given in terminal cells relative to the pane's top-left corner, for a viewport whose origin is the first argument.

The result is fractional and resolves to the centre of the cell, so trunc/1 on either coordinate gives the whole tile the cell belongs to. The column is halved, since a tile is two cells wide: cells 0 and 1 both fall in tile 0, at 0.25 and 0.75.

Pair with origin/3 to map a pointer position back to the world it is over.

tile_at(arg1, arg2, zoom)

@spec tile_at({number(), number()}, {number(), number()}, number()) ::
  {float(), float()}

tile_at/2 through a zoom: a tile drawn zoom times its native size spans 2 * zoom cells across and zoom down.

view(opts)

@spec view(keyword()) :: t()

Resolve the viewport and read every tile in it.

Returns a t/0 whose :cells is a list of rows rows, each of columns cells, in top-to-bottom and left-to-right order, and whose :origin is the world coordinate of the top-left tile.

Options

  • :focus — the {x, y} world point to centre on, required
  • :size{columns, rows} in tiles, required, both positive, whole or not
  • :cellfn {x, y} -> cell end, required
  • :bounds{width, height} of the world in tiles, or :unbounded. Default :unbounded
  • :scrolltrue to keep the focus at the exact centre: :origin is then the exact origin rounded down, :cells holds one more column and one more row than :size, and :scroll is the fraction of a tile, {0.0..1.0, 0.0..1.0}, the cells are to be shifted left and up by. Default false: :origin is origin/3, :cells is exactly :size, :scroll is {0.0, 0.0}

zoom_about(focus, cell, size, from, to, bounds)

@spec zoom_about(
  {number(), number()},
  {number(), number()},
  {number(), number()},
  number(),
  number(),
  {pos_integer(), pos_integer()} | :unbounded
) :: {float(), float()}

The focus that keeps the world under cell where it is on screen when a pane of native size goes from from to to zoom, centred on focus before; bounds as view/1 takes them, so that a clamped view stays clamped.

zoomed(arg, zoom)

@spec zoomed({number(), number()}, number()) :: {float(), float()}

The viewport in tiles of a pane size tiles across at native size, drawn at zoom.