BB.Math.Quaternion (bb v0.31.2)

Copy Markdown View Source

Unit quaternion for 3D rotations, held as four :f64 floats.

Components are named for WXYZ order (scalar first), and every operation returns a normalised unit quaternion suitable for representing a rotation.

Not tensor-backed

Like BB.Math.Vec3 and BB.Math.Transform2D, a single quaternion is far cheaper as four BEAM floats than as an Nx tensor. A Hamilton product is sixteen multiplies and twelve adds; dispatching those through eager Nx ops - or through a defn, which retraces its expression graph per call - costs orders of magnitude more than the arithmetic, and allocates every intermediate. Orientation is built and read on every estimator sample, so that lands squarely in the hot path. bb_estimator_ahrs used to carry its own scalar quaternion for exactly this reason.

tensor/1 and from_tensor/1 convert at the boundary of code that genuinely wants tensors, as do to_rotation_matrix/1 and from_rotation_matrix/1.

Examples

iex> BB.Math.Quaternion.new(1, 0, 0, 0)
BB.Math.Quaternion.new(1.0, 0.0, 0.0, 0.0)

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

Summary

Functions

Computes the angular distance between two quaternions in radians.

Returns the conjugate of a quaternion.

Creates a quaternion from an axis-angle representation.

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

Creates from a list in WXYZ order.

Creates a quaternion from a 3x3 rotation matrix.

Creates a quaternion from an existing {4} tensor.

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

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

Returns the identity quaternion (no rotation).

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

Returns the inverse of a quaternion.

Multiplies two quaternions (Hamilton product).

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

Normalises a quaternion to unit length.

Rotates a 3D vector by a quaternion.

Spherical linear interpolation between two quaternions.

Returns the quaternion as a {4} :f64 WXYZ tensor.

Converts a quaternion to axis-angle representation.

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

Converts to a list in WXYZ order.

Converts a quaternion to a 3x3 rotation matrix as a list of rows.

Converts a quaternion to a 3x3 rotation matrix as a {3, 3} :f64 tensor.

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

Returns the W (scalar) component.

Returns the X component.

Returns the Y component.

Returns the Z component.

Types

t()

@type t() :: %BB.Math.Quaternion{w: float(), x: float(), y: float(), z: float()}

Functions

angular_distance(a, b)

@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(quaternion)

@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(axis, angle)

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

Creates a quaternion from an axis-angle representation.

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(roll, pitch, yaw, order \\ :xyz)

@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(list)

@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(matrix)

@spec from_rotation_matrix(Nx.Tensor.t() | [[number()]]) :: t()

Creates a quaternion from a 3x3 rotation matrix.

Accepts a {3, 3} tensor or a list of three three-element rows. Uses the Shepperd method for numerical stability: the branch is chosen so the value under the square root is largest, which keeps the division well conditioned.

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(tensor)

@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(from, to)

@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(list)

@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()

@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()

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

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

inverse(q)

@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(a, b)

@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(w, x, y, z)

@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(quaternion)

@spec normalise(t()) :: t()

Normalises a quaternion to unit length.

Falls back to the identity quaternion when the input is near-zero, so the result is always a valid unit rotation.

Examples

iex> q = BB.Math.Quaternion.normalise(%BB.Math.Quaternion{w: 2.0, x: 0.0, y: 0.0, z: 0.0})
iex> BB.Math.Quaternion.w(q)
1.0

rotate_vector(quaternion, v)

@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(a, b, t)

@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(quaternion)

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

Returns the quaternion as a {4} :f64 WXYZ tensor.

to_axis_angle(quaternion)

@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(q, order \\ :xyz)

@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(quaternion)

@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_list(quaternion)

@spec to_rotation_list(t()) :: [[float()]]

Converts a quaternion to a 3x3 rotation matrix as a list of rows.

Examples

iex> BB.Math.Quaternion.to_rotation_list(BB.Math.Quaternion.identity())
[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]

to_rotation_matrix(q)

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

Converts a quaternion to a 3x3 rotation matrix as a {3, 3} :f64 tensor.

Use to_rotation_list/1 to avoid building a tensor.

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(quaternion)

@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(quaternion)

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

Returns the W (scalar) component.

x(quaternion)

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

Returns the X component.

y(quaternion)

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

Returns the Y component.

z(quaternion)

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

Returns the Z component.