# `BB.Math.Transform`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/math/transform.ex#L5)

Homogeneous transformation matrices for 3D transformations, backed by an Nx tensor.

All transforms are represented as 4x4 matrices in row-major order:

```
| R11 R12 R13 Tx |
| R21 R22 R23 Ty |
| R31 R32 R33 Tz |
|  0   0   0   1 |
```

Where the upper-left 3x3 is the rotation matrix and the rightmost column
is the translation vector.

## Conventions

- All angles are in radians
- All distances are in metres
- Rotations use XYZ Euler angles (roll-pitch-yaw)
- Coordinate frame follows right-hand rule

## Examples

    iex> t = BB.Math.Transform.identity()
    iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
    [0.0, 0.0, 0.0]

    iex> t = BB.Math.Transform.translation(BB.Math.Vec3.new(1, 2, 3))
    iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
    [1.0, 2.0, 3.0]

# `t`

```elixir
@type t() :: %BB.Math.Transform{tensor: Nx.Tensor.t()}
```

# `apply_to_point`

```elixir
@spec apply_to_point(t(), BB.Math.Vec3.t()) :: BB.Math.Vec3.t()
```

Apply a transform to a 3D point, returning the transformed point.

## Examples

    iex> t = BB.Math.Transform.translation(BB.Math.Vec3.new(1, 2, 3))
    iex> p = BB.Math.Transform.apply_to_point(t, BB.Math.Vec3.zero())
    iex> BB.Math.Vec3.to_list(p)
    [1.0, 2.0, 3.0]

# `compose`

```elixir
@spec compose(t(), t()) :: t()
```

Compose (multiply) two transformation matrices.

`compose(a, b)` returns the transform that applies `a` first, then `b`.

## Examples

    iex> t1 = BB.Math.Transform.translation(BB.Math.Vec3.new(1, 0, 0))
    iex> t2 = BB.Math.Transform.translation(BB.Math.Vec3.new(0, 2, 0))
    iex> t = BB.Math.Transform.compose(t1, t2)
    iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
    [1.0, 2.0, 0.0]

# `compose_all`

```elixir
@spec compose_all([t()]) :: t()
```

Compose a list of transforms in order.

## Examples

    iex> transforms = [
    ...>   BB.Math.Transform.translation(BB.Math.Vec3.new(1, 0, 0)),
    ...>   BB.Math.Transform.translation(BB.Math.Vec3.new(0, 1, 0)),
    ...>   BB.Math.Transform.translation(BB.Math.Vec3.new(0, 0, 1))
    ...> ]
    iex> t = BB.Math.Transform.compose_all(transforms)
    iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
    [1.0, 1.0, 1.0]

# `from_axis_angle`

```elixir
@spec from_axis_angle(BB.Math.Vec3.t(), float()) :: t()
```

Create a rotation transform around an arbitrary axis using the axis-angle representation.

Uses Rodrigues' rotation formula to compute the rotation matrix.

## Parameters

- `axis`: normalised axis Vec3
- `angle`: rotation angle in radians

## Examples

    iex> axis = BB.Math.Vec3.unit_z()
    iex> t = BB.Math.Transform.from_axis_angle(axis, :math.pi() / 2)
    iex> p = BB.Math.Transform.apply_to_point(t, BB.Math.Vec3.unit_x())
    iex> {Float.round(BB.Math.Vec3.x(p), 6), Float.round(BB.Math.Vec3.y(p), 6)}
    {0.0, 1.0}

# `from_origin`

```elixir
@spec from_origin(%{
  position: {float(), float(), float()},
  orientation: {float(), float(), float()}
}) ::
  t()
```

Create a transformation matrix from position and orientation.

The origin map should have:
- `position`: {x, y, z} in metres
- `orientation`: {roll, pitch, yaw} in radians

Rotation is applied in XYZ order (roll around X, then pitch around Y,
then yaw around Z).

## Examples

    iex> origin = %{position: {1.0, 2.0, 3.0}, orientation: {0.0, 0.0, 0.0}}
    iex> t = BB.Math.Transform.from_origin(origin)
    iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
    [1.0, 2.0, 3.0]

# `from_position_quaternion`

```elixir
@spec from_position_quaternion(BB.Math.Vec3.t(), BB.Math.Quaternion.t()) :: t()
```

Create a 4x4 transformation matrix from position and quaternion orientation.

## Examples

    iex> pos = BB.Math.Vec3.new(1, 2, 3)
    iex> q = BB.Math.Quaternion.identity()
    iex> t = BB.Math.Transform.from_position_quaternion(pos, q)
    iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
    [1.0, 2.0, 3.0]

# `from_quaternion`

```elixir
@spec from_quaternion(BB.Math.Quaternion.t()) :: t()
```

Create a 4x4 transformation matrix from a quaternion (rotation only).

The resulting matrix has the quaternion's rotation in the upper-left 3x3
and zero translation.

## Examples

    iex> q = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> t = BB.Math.Transform.from_quaternion(q)
    iex> p = BB.Math.Transform.apply_to_point(t, BB.Math.Vec3.unit_x())
    iex> {Float.round(BB.Math.Vec3.x(p), 6), Float.round(BB.Math.Vec3.y(p), 6)}
    {0.0, 1.0}

# `from_tensor`

```elixir
@spec from_tensor(Nx.Tensor.t()) :: t()
```

Creates a transform from an existing `{4, 4}` tensor.

# `get_forward_vector`

```elixir
@spec get_forward_vector(t()) :: BB.Math.Vec3.t()
```

Get the forward vector (Z-axis) from a transformation matrix.

The forward vector is the third column of the rotation matrix,
representing the direction the local Z-axis points in world coordinates.

## Examples

    iex> t = BB.Math.Transform.identity()
    iex> fwd = BB.Math.Transform.get_forward_vector(t)
    iex> BB.Math.Vec3.to_list(fwd)
    [0.0, 0.0, 1.0]

# `get_quaternion`

```elixir
@spec get_quaternion(t()) :: BB.Math.Quaternion.t()
```

Extract a quaternion from a transform.

Extracts the 3x3 rotation portion and converts it to a unit quaternion.

## Examples

    iex> t = BB.Math.Transform.rotation_z(:math.pi() / 2)
    iex> q = BB.Math.Transform.get_quaternion(t)
    iex> {_axis, angle} = BB.Math.Quaternion.to_axis_angle(q)
    iex> Float.round(angle, 6)
    1.570796

# `get_right_vector`

```elixir
@spec get_right_vector(t()) :: BB.Math.Vec3.t()
```

Get the right vector (X-axis) from a transformation matrix.

The right vector is the first column of the rotation matrix,
representing the direction the local X-axis points in world coordinates.

## Examples

    iex> t = BB.Math.Transform.identity()
    iex> right = BB.Math.Transform.get_right_vector(t)
    iex> BB.Math.Vec3.to_list(right)
    [1.0, 0.0, 0.0]

# `get_rotation`

```elixir
@spec get_rotation(t()) :: Nx.Tensor.t()
```

Get the rotation matrix (3x3) from a transform.

# `get_translation`

```elixir
@spec get_translation(t()) :: BB.Math.Vec3.t()
```

Get the translation component of a transform as a Vec3.

# `get_up_vector`

```elixir
@spec get_up_vector(t()) :: BB.Math.Vec3.t()
```

Get the up vector (Y-axis) from a transformation matrix.

The up vector is the second column of the rotation matrix,
representing the direction the local Y-axis points in world coordinates.

## Examples

    iex> t = BB.Math.Transform.identity()
    iex> up = BB.Math.Transform.get_up_vector(t)
    iex> BB.Math.Vec3.to_list(up)
    [0.0, 1.0, 0.0]

# `identity`

```elixir
@spec identity() :: t()
```

Create a 4x4 identity transformation matrix.

## Examples

    iex> t = BB.Math.Transform.identity()
    iex> BB.Math.Transform.tensor(t) |> Nx.to_list()
    [[1.0, 0.0, 0.0, 0.0],
     [0.0, 1.0, 0.0, 0.0],
     [0.0, 0.0, 1.0, 0.0],
     [0.0, 0.0, 0.0, 1.0]]

# `inverse`

```elixir
@spec inverse(t()) :: t()
```

Compute the inverse of a transformation matrix.

For a valid transformation matrix, this computes the inverse transform.

# `rotation_x`

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

Create a rotation matrix around the X axis (roll).

## Examples

    iex> t = BB.Math.Transform.rotation_x(:math.pi() / 2)
    iex> v = BB.Math.Transform.apply_to_point(t, BB.Math.Vec3.new(0, 1, 0))
    iex> Float.round(BB.Math.Vec3.z(v), 6)
    1.0

# `rotation_y`

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

Create a rotation matrix around the Y axis (pitch).

# `rotation_z`

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

Create a rotation matrix around the Z axis (yaw).

# `tensor`

```elixir
@spec tensor(t()) :: Nx.Tensor.t()
```

Returns the underlying `{4, 4}` tensor.

# `translation`

```elixir
@spec translation(BB.Math.Vec3.t()) :: t()
```

Create a pure translation matrix from a Vec3.

## Examples

    iex> t = BB.Math.Transform.translation(BB.Math.Vec3.new(1, 2, 3))
    iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
    [1.0, 2.0, 3.0]

# `translation_along`

```elixir
@spec translation_along(BB.Math.Vec3.t(), float()) :: t()
```

Create a translation transform along an arbitrary axis.

## Parameters

- `axis`: normalised axis Vec3
- `distance`: translation distance in metres

## Examples

    iex> axis = BB.Math.Vec3.unit_x()
    iex> t = BB.Math.Transform.translation_along(axis, 2.5)
    iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
    [2.5, 0.0, 0.0]

---

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