# `Cauldron2D.Projection.Volume`
[🔗](https://github.com/jaman/cauldron/blob/v0.2.2/cauldron_2d/lib/cauldron_2d/projection/volume.ex#L1)

A coloured region of space that tints whatever is seen through it, and the arithmetic
a projection draws one with.

    volume = Cauldron2D.Projection.Volume.new(
      at: {8.5, 6.5},
      shape: {:sphere, 1.5, 0.0},
      colour: {120, 232, 96},
      strength: 0.6
    )

`:at` is where the volume stands on the ground and `:shape` what it fills from there,
as `Cauldron2D.Projection.Volume.Shapes` lists the shapes the engine knows, or
`{YourModule, data}` for a shape of your own implementing
`Cauldron2D.Projection.Volume.Shape`. Distances are in tiles. Nothing below the ground
is drawn, whatever the shape says.

`:strength` is how much of the colour a viewer sees through the thickest part of the
shape, `0.0` to `1.0`. `:falloff` decides what a thinner crossing looks like:

  * `:depth` — the colour builds up along the ray, so the volume reads as something
    you look *through*: thin at the edges, full in the middle. The default
  * `:flat` — anything the ray touches at all is tinted by the full strength, which is
    how an area marked out in whole squares reads
  * `:shell` — strongest where the ray only grazes the shape, so the surface glows and
    the middle stays clear

A volume is never fully opaque.

A volume either stands where it was made or follows something that moves. `:follows`
names what it keeps up with — a term of the caller's choosing, a character, a thrown
light, itself — and `anchored/2` moves every such volume to where its subject is now,
turning a cone or a beam to face the way the subject faces and dropping a volume whose
subject is gone. A volume with no `:follows` is left where it stands. Which effects
follow what, and what may be followed at all, is the game's business; all this knows
is that a subject has a place and may have a heading.

`through/4` measures a ray against a volume and `mix/3` blends that much colour into a
pixel; the two together tint one pixel:

    Volume.mix(pixel, volume, Volume.through(volume, eye, ray, distance_to_surface))

A ray is a point and a direction of three numbers, `z` running up from the ground, and
how far along it the projection can see, or `:infinity` where nothing stops it. That
limit counts steps of the direction as it is given, so a projection may measure it
however suits it; the length `through/4` answers is always in the units the volume
itself is measured in, whatever size the direction is.

`eyeline/3` gives the direction the rays of an axonometric projection all run in,
`box/2` the rectangle of a picture a volume can reach and `reach/1` how far from `at`
it spreads across the ground.

`paint/4` tints a `Cauldron2D.Projection.Canvas` over a rectangle. It takes a plan
rather than a ray per pixel, because a projection's rays change by the same step from
one pixel to the next: `ground_plan/2` describes a projection whose rays start on the
ground and run parallel, `eye_plan/3` one whose rays start at the eye and fan out.

Painting is done by `Cauldron2D.Projection.Volume.Native` where it built, and in
Elixir where it did not or where the shape is a module of the caller's own. A plan
carrying `native: false` paints in Elixir wherever it is, which is how the two are
checked against each other; they agree to within one level of one channel, where
`:math.exp/1` and C's `exp` round differently.

# `falloff`

```elixir
@type falloff() :: :depth | :flat | :shell
```

# `plan`

```elixir
@type plan() :: %{
  :origin =&gt; point(),
  :origin_x =&gt; point(),
  :origin_y =&gt; point(),
  :direction =&gt; point(),
  :direction_x =&gt; point(),
  :direction_y =&gt; point(),
  :limits =&gt; binary() | nil,
  optional(:native) =&gt; boolean()
}
```

# `point`

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

# `shape`

```elixir
@type shape() :: tuple()
```

# `t`

```elixir
@type t() :: %{
  at: {number(), number()},
  shape: {module(), term()},
  colour: {byte(), byte(), byte()},
  strength: float(),
  falloff: falloff(),
  thickness: float(),
  density: float()
}
```

# `anchored`

```elixir
@spec anchored([map()], %{
  required(term()) =&gt; {number(), number()} | {{number(), number()}, number()}
}) ::
  [map()]
```

Every volume moved to where the thing it follows is now.

`subjects` maps what a volume's `:follows` names to where that subject is: a point, or
`{point, heading}` where the subject faces somewhere and the volume's shape should turn
with it. A volume with no `:follows` is returned untouched; one whose subject is absent
from `subjects` is dropped, so an effect outlives nothing it was tied to.

# `box`

```elixir
@spec box(t(), (point() -&gt; {number(), number()} | nil)) ::
  {integer(), integer(), integer(), integer()} | nil
```

The whole-pixel rectangle `{left, top, right, bottom}` of a picture the volume can
reach, or `nil` when none of it is in view.

`project` maps a world point to its place in the picture, or to `nil` for a point the
projection cannot show.

# `corners`

```elixir
@spec corners(t()) :: [point()]
```

The world points of the box the volume fits in, above the ground.

# `eye_plan`

```elixir
@spec eye_plan(point(), ({number(), number()} -&gt; point()), binary() | nil) :: plan()
```

A plan for a projection whose rays all start at the eye and fan out.

`along` answers the direction a picture pixel looks in, scaled so that one step along
it is one unit of depth, which is what `limits` counts. `limits` is one native float per picture pixel, how far that
pixel sees before something opaque stops it, or `nil` for nothing in the way.

# `eyeline`

```elixir
@spec eyeline({number(), number()}, {number(), number()}, number()) :: point()
```

The direction, towards the viewer, that every ray of an axonometric projection runs
in.

`across` and `down` are the picture vectors a step of one along the world's `x` and
`y` moves by, and `lift` how many pixels up the picture a step of one in `z` moves.
The three must not be flat against one another.

# `ground_plan`

```elixir
@spec ground_plan(({number(), number()} -&gt; {number(), number()}), point()) :: plan()
```

A plan for a projection whose rays start on the ground and all run the same way.

`at` answers the world point on the ground under a picture pixel, as a projection's
own `unproject` does, and `direction` is the way the rays run, as `eyeline/3` gives
it.

# `list`

```elixir
@spec list([keyword() | map()]) :: [t()]
```

Every volume of a list, as `new/1` builds one.

# `mix`

```elixir
@spec mix(binary(), t(), number()) :: binary()
```

`pixel` with as much of the volume's colour blended in as `length` inside it calls for,
by its falloff.

A length of nothing leaves the pixel untouched. The pixel's own opacity byte is kept.

# `new`

```elixir
@spec new(keyword() | map()) :: t()
```

A volume from its fields, a map or a keyword list, with `:at` required.

`:shape` defaults to a dome of one tile, `:colour` to white, `:strength` to
`0.5` and `:falloff` to `:depth`. Passing a volume back in leaves it
as it is, so a projection may normalise whatever it is handed.

# `paint`

```elixir
@spec paint(
  Cauldron2D.Projection.Canvas.t(),
  t(),
  {integer(), integer(), integer(), integer()} | nil,
  plan()
) :: Cauldron2D.Projection.Canvas.t()
```

Tint `canvas` with `volume` over `box`, clipped to the canvas, looking along `plan`.

# `reach`

```elixir
@spec reach(t()) :: float()
```

How far from `at` the volume can spread across the ground.

# `through`

```elixir
@spec through(t(), point(), point(), number() | :infinity) :: float()
```

How far a ray travels inside `volume` before `limit`, in the units the volume is
measured in.

The ray starts at `origin` and runs along `direction`, and `limit` is how many steps
of that direction it may take, so a projection whose step is a unit of depth rather
than a unit of distance passes its own depth. Only what lies above the ground, ahead
of the origin and nearer than `limit` counts; `:infinity` lets the ray run until it
leaves the shape.

---

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