# `Color.Material`

A physically-based-rendering (PBR) material wrapper around a
base colour.

`%Color.SRGB{}` describes a colour stimulus. `%Color.Material{}`
describes a *surface* — a colour plus the appearance parameters
(metallic, roughness, clearcoat) that determine whether the
surface reads as plastic, painted metal, glossy varnish, or
matte ceramic. Two materials can share the same base colour yet
look entirely different because of how light interacts with
them.

The parameter set deliberately matches the Disney Principled
BSDF / glTF 2.0 `pbrMetallicRoughness` convention so values
from those sources can be imported directly.

## Why this exists

When you sort a palette that mixes plastic swatches with
polished-metal swatches, any single-axis colour sort puts
identical base colours next to each other regardless of
finish — a red plastic and a red-anodized aluminium end up
visually adjacent even though users file them into different
mental categories. `Color.Palette.Sort`'s `:material_pbr`
strategy consumes this struct and produces an ordering that
respects the metallic-vs-dielectric cliff first, then colour,
then gloss.

## Parameters

* `:base_color` — a `%Color.SRGB{}` struct. The "albedo" for
  dielectrics or the "specular colour" for metals.

* `:metallic` — `0.0`–`1.0`. `0.0` is a pure dielectric
  (plastic, paint, wood, skin); `1.0` is a pure conductor
  (gold, copper, aluminium). Values in between model partial
  metallic coatings such as metallic automotive paint.

* `:roughness` — `0.0`–`1.0`. `0.0` is a perfect mirror;
  `1.0` is a fully diffuse (Lambertian) surface.

* `:clearcoat` — `0.0`–`1.0`. Strength of an optional
  dielectric varnish layer on top. Defaults to `0.0`
  (no clearcoat).

* `:clearcoat_roughness` — `0.0`–`1.0`. Roughness of the
  clearcoat layer. Ignored when `:clearcoat` is `0.0`.
  Defaults to `0.03` (near-mirror, matching automotive paint).

* `:name` — optional string label stored with the material.

## Example

    iex> {:ok, red} = Color.new("#ff0000")
    iex> mat = Color.Material.new(red, metallic: 0.0, roughness: 0.85, name: "Matte Red PC")
    iex> mat.name
    "Matte Red PC"
    iex> mat.metallic
    0.0

# `t`

```elixir
@type t() :: %Color.Material{
  base_color: Color.SRGB.t(),
  clearcoat: float(),
  clearcoat_roughness: float(),
  metallic: float(),
  name: binary() | nil,
  roughness: float()
}
```

# `base_color`

```elixir
@spec base_color(t()) :: Color.SRGB.t()
```

Returns the underlying base colour.

### Arguments

* `material` is a `%Color.Material{}` struct.

### Returns

* A `%Color.SRGB{}` struct.

### Examples

    iex> mat = Color.Material.new("#ff0000")
    iex> Color.to_hex(Color.Material.base_color(mat))
    "#ff0000"

# `new`

```elixir
@spec new(
  Color.input(),
  keyword()
) :: t()
```

Builds a `%Color.Material{}` struct.

### Arguments

* `color_input` is anything accepted by `Color.new/1` — a hex
  string, a CSS named colour, an `%Color.SRGB{}` struct, an
  Oklch struct, etc. The input is normalised to `%Color.SRGB{}`
  and stored in `:base_color`.

### Options

* `:metallic` is the metallic parameter in `[0.0, 1.0]`.
  Default `0.0` (dielectric).

* `:roughness` is the roughness parameter in `[0.0, 1.0]`.
  Default `0.5`.

* `:clearcoat` is the clearcoat strength in `[0.0, 1.0]`.
  Default `0.0`.

* `:clearcoat_roughness` is the clearcoat roughness in
  `[0.0, 1.0]`. Default `0.03`.

* `:name` is an optional string label.

### Returns

* A `%Color.Material{}` struct.

### Examples

    iex> mat = Color.Material.new("#c0c0c0", metallic: 1.0, roughness: 0.2)
    iex> mat.metallic
    1.0
    iex> mat.roughness
    0.2

    iex> mat = Color.Material.new("red")
    iex> Color.to_hex(mat.base_color)
    "#ff0000"
    iex> mat.metallic
    0.0

# `to_pbr_tuple`

```elixir
@spec to_pbr_tuple(
  t(),
  keyword()
) :: {non_neg_integer(), float(), float(), float()}
```

Returns a tuple suitable for tuple-based sorting.

The tuple is `{metallic_bucket, hue, lightness, roughness}`
where `metallic_bucket` is `0` for dielectrics and `1` for
metals, computed from `metallic >= threshold`.

Used by `Color.Palette.Sort`'s `:material_pbr` strategy and
exposed so third parties can compose their own sort keys.

### Arguments

* `material` is a `%Color.Material{}` struct.

### Options

* `:metallic_threshold` is the cutoff between dielectric and
  metallic buckets, in `(0.0, 1.0]`. Default `0.5`.

### Returns

* A `{integer, float, float, float}` tuple.

### Examples

    iex> mat = Color.Material.new("#c0c0c0", metallic: 1.0, roughness: 0.2)
    iex> {bucket, _h, _l, rough} = Color.Material.to_pbr_tuple(mat)
    iex> bucket
    1
    iex> rough
    0.2

---

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