# `Cauldron2D.Camera`
[🔗](https://github.com/jaman/cauldron/blob/v0.1.3/cauldron_2d/lib/cauldron_2d/camera.ex#L1)

Resolves which part of a world is on screen.

    Cauldron2D.Camera.view(
      focus: {x, y},
      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 `t:Cauldron2D.Surface.cell/0`. It is the only thing the engine asks of a game.

The viewport is always centred on `:focus`: at a world's edge, or zoomed out past it, the
coordinates asked for run outside the world and `:cell` answers for them (`:void`, or a
wrapped tile) — the focus never moves off the centre to keep the world on screen.

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.

# `point`

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

# `t`

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

# `columns_for`

```elixir
@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`

```elixir
@spec exact_origin({number(), number()}, {number(), number()}) :: {float(), float()}
```

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

# `glyphs`

```elixir
@spec glyphs(Cauldron2D.Atlas.name() | Cauldron2D.Atlas.t(), t()) :: [
  [{String.t(), Cauldron2D.Atlas.rgb()}]
]
```

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`

```elixir
@spec origin(point(), {pos_integer(), pos_integer()}) :: 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.

# `rows_for`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

Resolve the viewport and read every tile in it.

Returns a `t: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
  * `:cell` — `fn {x, y} -> cell end`, required
  * `:scroll` — `true` 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`

```elixir
@spec zoom_about(
  {number(), number()},
  {number(), number()},
  {number(), number()},
  number(),
  number()
) ::
  {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. Zooming back the same
way about the same cell returns to the focus it started from.

# `zoomed`

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

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
