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

Unit quaternion for 3D rotations, backed by an Nx tensor.

Quaternions are stored in WXYZ order (scalar first): `[w, x, y, z]`.
All math operations use Nx for consistent performance and potential GPU acceleration.

All operations return normalised unit quaternions suitable for representing rotations.
The underlying tensor is always `{4}` shape with `:f64` type.

## Examples

    iex> q = BB.Math.Quaternion.identity()
    iex> BB.Math.Quaternion.w(q)
    1.0

    iex> q1 = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> q2 = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> q3 = BB.Math.Quaternion.multiply(q1, q2)
    iex> BB.Math.Quaternion.angular_distance(q3, BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi()))
    0.0

# `t`

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

# `angular_distance`

```elixir
@spec angular_distance(t(), t()) :: float()
```

Computes the angular distance between two quaternions in radians.

Returns a value between 0 and pi.

## Examples

    iex> q1 = BB.Math.Quaternion.identity()
    iex> q2 = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> Float.round(BB.Math.Quaternion.angular_distance(q1, q2), 6)
    1.570796

# `conjugate`

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

Returns the conjugate of a quaternion.

For unit quaternions, the conjugate equals the inverse.

## Examples

    iex> q = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> qc = BB.Math.Quaternion.conjugate(q)
    iex> Float.round(BB.Math.Quaternion.z(qc), 6)
    -0.707107

# `from_axis_angle`

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

Creates a quaternion from an axis-angle representation.

The axis should be a `BB.Math.Vec3` unit vector (it will be normalised if not).
The angle is in radians.

## Examples

    iex> q = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> Float.round(BB.Math.Quaternion.w(q), 6)
    0.707107

# `from_euler`

```elixir
@spec from_euler(number(), number(), number(), atom()) :: t()
```

Creates a quaternion from Euler angles (roll, pitch, yaw).

Angles are in radians. Default order is `:xyz` (roll around X, pitch around Y, yaw around Z).

Supported orders: `:xyz`, `:zyx`

## Examples

    iex> q = BB.Math.Quaternion.from_euler(0, 0, :math.pi() / 2, :xyz)
    iex> Float.round(BB.Math.Quaternion.z(q), 6)
    0.707107

# `from_list`

```elixir
@spec from_list([number()]) :: t()
```

Creates from a list in WXYZ order.

## Examples

    iex> q = BB.Math.Quaternion.from_list([1.0, 0.0, 0.0, 0.0])
    iex> BB.Math.Quaternion.w(q)
    1.0

# `from_rotation_matrix`

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

Creates a quaternion from a 3x3 rotation matrix.

Uses the Shepperd method for numerical stability.

## Examples

    iex> m = Nx.tensor([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
    iex> q = BB.Math.Quaternion.from_rotation_matrix(m)
    iex> BB.Math.Quaternion.w(q)
    1.0

# `from_tensor`

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

Creates a quaternion from an existing `{4}` tensor.

The tensor should be in WXYZ order. It will be normalised.

# `from_two_vectors`

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

Creates a quaternion representing the shortest rotation from one vector to another.

Both vectors should be unit vectors (they will be normalised if not).
Returns the quaternion that rotates `from` to align with `to`.

Handles edge cases:
- Parallel vectors (from ≈ to): returns identity quaternion
- Anti-parallel vectors (from ≈ -to): returns 180° rotation around a perpendicular axis

## Examples

    iex> q = BB.Math.Quaternion.from_two_vectors(BB.Math.Vec3.unit_x(), BB.Math.Vec3.unit_y())
    iex> rotated = BB.Math.Quaternion.rotate_vector(q, BB.Math.Vec3.unit_x())
    iex> {Float.round(BB.Math.Vec3.x(rotated), 6), Float.round(BB.Math.Vec3.y(rotated), 6)}
    {0.0, 1.0}

    iex> q = BB.Math.Quaternion.from_two_vectors(BB.Math.Vec3.unit_z(), BB.Math.Vec3.unit_z())
    iex> BB.Math.Quaternion.w(q)
    1.0

# `from_xyzw_list`

```elixir
@spec from_xyzw_list([number()]) :: t()
```

Creates from a list in XYZW order (for ROS/external system compatibility).

## Examples

    iex> q = BB.Math.Quaternion.from_xyzw_list([0.0, 0.0, 0.0, 1.0])
    iex> BB.Math.Quaternion.w(q)
    1.0

# `identity`

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

Returns the identity quaternion (no rotation).

## Examples

    iex> q = BB.Math.Quaternion.identity()
    iex> {BB.Math.Quaternion.w(q), BB.Math.Quaternion.x(q), BB.Math.Quaternion.y(q), BB.Math.Quaternion.z(q)}
    {1.0, 0.0, 0.0, 0.0}

# `identity_tensor`

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

Returns an identity quaternion as a raw tensor (for batch operations).

# `inverse`

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

Returns the inverse of a quaternion.

For unit quaternions, this equals the conjugate.

## Examples

    iex> q = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> qi = BB.Math.Quaternion.inverse(q)
    iex> qr = BB.Math.Quaternion.multiply(q, qi)
    iex> Float.round(BB.Math.Quaternion.w(qr), 6)
    1.0

# `multiply`

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

Multiplies two quaternions (Hamilton product).

This composes the rotations: `multiply(q1, q2)` applies q2 first, then q1.

## Examples

    iex> q1 = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> q2 = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> q3 = BB.Math.Quaternion.multiply(q1, q2)
    iex> {_axis, angle} = BB.Math.Quaternion.to_axis_angle(q3)
    iex> Float.round(angle, 6)
    3.141593

# `new`

```elixir
@spec new(number(), number(), number(), number()) :: t()
```

Creates a new quaternion from w, x, y, z components.

The quaternion is automatically normalised.

## Examples

    iex> q = BB.Math.Quaternion.new(1, 0, 0, 0)
    iex> BB.Math.Quaternion.w(q)
    1.0

# `normalise`

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

Normalises a quaternion to unit length.

## Examples

    iex> q = %BB.Math.Quaternion{tensor: Nx.tensor([2.0, 0.0, 0.0, 0.0])}
    iex> qn = BB.Math.Quaternion.normalise(q)
    iex> BB.Math.Quaternion.w(qn)
    1.0

# `rotate_vector`

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

Rotates a 3D vector by a quaternion.

## Examples

    iex> q = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> v = BB.Math.Vec3.unit_x()
    iex> rotated = BB.Math.Quaternion.rotate_vector(q, v)
    iex> {Float.round(BB.Math.Vec3.x(rotated), 6), Float.round(BB.Math.Vec3.y(rotated), 6)}
    {0.0, 1.0}

# `slerp`

```elixir
@spec slerp(t(), t(), number()) :: t()
```

Spherical linear interpolation between two quaternions.

`t` should be between 0.0 and 1.0.

## Examples

    iex> q1 = BB.Math.Quaternion.identity()
    iex> q2 = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi())
    iex> q_mid = BB.Math.Quaternion.slerp(q1, q2, 0.5)
    iex> {_axis, angle} = BB.Math.Quaternion.to_axis_angle(q_mid)
    iex> Float.round(angle, 6)
    1.570796

# `tensor`

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

Returns the underlying `{4}` tensor.

# `to_axis_angle`

```elixir
@spec to_axis_angle(t()) :: {BB.Math.Vec3.t(), float()}
```

Converts a quaternion to axis-angle representation.

Returns `{axis, angle}` where axis is a `BB.Math.Vec3` unit vector
and angle is in radians (0 to pi).

## Examples

    iex> q = BB.Math.Quaternion.from_axis_angle(BB.Math.Vec3.unit_z(), :math.pi() / 2)
    iex> {axis, angle} = BB.Math.Quaternion.to_axis_angle(q)
    iex> Float.round(angle, 6)
    1.570796
    iex> Float.round(BB.Math.Vec3.z(axis), 1)
    1.0

# `to_euler`

```elixir
@spec to_euler(t(), atom()) :: {float(), float(), float()}
```

Converts a quaternion to Euler angles (roll, pitch, yaw).

Returns `{roll, pitch, yaw}` in radians. Default order is `:xyz`.

Note: Euler angles can have gimbal lock issues near pitch = ±90°.

## Examples

    iex> q = BB.Math.Quaternion.from_euler(0.1, 0.2, 0.3, :xyz)
    iex> {roll, pitch, yaw} = BB.Math.Quaternion.to_euler(q, :xyz)
    iex> Float.round(roll, 6)
    0.1

# `to_list`

```elixir
@spec to_list(t()) :: [float()]
```

Converts to a list in WXYZ order.

## Examples

    iex> q = BB.Math.Quaternion.identity()
    iex> BB.Math.Quaternion.to_list(q)
    [1.0, 0.0, 0.0, 0.0]

# `to_rotation_matrix`

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

Converts a quaternion to a 3x3 rotation matrix.

## Examples

    iex> q = BB.Math.Quaternion.identity()
    iex> m = BB.Math.Quaternion.to_rotation_matrix(q)
    iex> Nx.to_number(m[0][0])
    1.0

# `to_xyzw_list`

```elixir
@spec to_xyzw_list(t()) :: [float()]
```

Converts to a list in XYZW order (for ROS/external system compatibility).

## Examples

    iex> q = BB.Math.Quaternion.identity()
    iex> BB.Math.Quaternion.to_xyzw_list(q)
    [0.0, 0.0, 0.0, 1.0]

# `w`

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

Returns the W (scalar) component.

# `x`

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

Returns the X component.

# `y`

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

Returns the Y component.

# `z`

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

Returns the Z component.

---

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