# `Cauldron3D.Bsp.Surfaces`
[🔗](https://github.com/jaman/cauldron/blob/v0.2.2/cauldron_3d/lib/cauldron_3d/bsp/surfaces.ex#L1)

Faces' textures with their light worked in, made once and kept until the light changes.

A surface covers its face's lightmap area at one mip level: each pixel is the texture's
colour taken through the light table at the light there, blended between the four
nearest lightmap samples. A face's light is the sum of its styles' samples, each scaled
by the style's value over 256.

    cache = Cauldron3D.Bsp.Surfaces.new()
    Cauldron3D.Bsp.Surfaces.fetch(cache, bsp, face_index, mip, light)

`light` is a map: `:table` (rows of 256 colours, brightest first), `:rows` (how many),
`:styles` (a map or tuple from style number to scale, 256 being normal; a missing style
is 256) and `:unlit` (the light of a face with no lightmap, default 255).

For a picture in full colour the map has `:rgba` instead of `:table` and `:rows`: a
palette as `Cauldron3D.Texture.colours/1` makes it, for textures without their own.
The surface is then an RGBA texture lit from the world's grey or red-green-blue
lightmaps (`Cauldron3D.Bsp`'s `light_channels`):

  * each lightmap sample's channel is the sum over the face's styles of its sample
    times `trunc(colour * scale)`, a style given in `:styles` as a scale or as
    `{scale, {r, g, b}}` (a grey lightmap's one sample serving all three channels); a
    face with no lightmap has `:unlit` in every channel (default 32768), and with
    `:fixed` every face has that in every channel instead
  * `:store`, a function of a sample's `{r, g, b}`, gives the value kept for it
    (default the sums themselves)
  * the kept values are blended between the four nearest samples at each texel's
    centre, and `:shade`, a function of the blended `{r, g, b}`, gives the three
    numbers the texture's channels are multiplied by (default each over 32768)

A face whose texture is of kind `:masked` is `{0, 0, 0, 0}` where its texture is
colour 255. `:scroll` moves the texture (not its light) that many of its own pixels
along `s`.

The cache is an ETS table owned by the process that made it, readable and writable by
any; one surface is kept per face and mip.

# `cache`

```elixir
@type cache() :: :ets.tid()
```

# `build`

```elixir
@spec build(
  Cauldron3D.Bsp.Face.t(),
  Cauldron3D.Texture.t(),
  binary() | nil,
  0..3,
  [number()],
  map()
) ::
  Cauldron3D.Texture.t()
```

A face's surface at `mip` from the texture at that mip and the face's lightmap
`samples` (every style's, in order; `nil` for an unlit face), its styles at `scales`.

# `cached`

```elixir
@spec cached(cache(), Cauldron3D.Bsp.t(), non_neg_integer(), 0..3, map()) ::
  {:ok, {non_neg_integer(), 0..3}, Cauldron3D.Texture.t()} | {:missing, map()}
```

The cached surface as `{:ok, {index, mip}, surface}`, or `{:missing, job}`: everything
`make/2` needs to make it, without the world, so it can be handed to another process;
`job.key` is `{index, mip}`.

# `clear`

```elixir
@spec clear(cache()) :: :ok
```

Forget every surface, as when a new world is loaded.

# `fetch`

```elixir
@spec fetch(cache(), Cauldron3D.Bsp.t(), non_neg_integer(), 0..3, map()) ::
  Cauldron3D.Texture.t()
```

The surface of face `index` at `mip`, made now if it is missing or its light has changed.

# `make`

```elixir
@spec make(cache(), map()) :: Cauldron3D.Texture.t()
```

Make the surface a `cached/5` job describes, and keep it in the cache.

# `new`

```elixir
@spec new() :: cache()
```

An empty cache.

---

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