BB.Math.Transform2D (bb v0.27.0)

Copy Markdown View Source

A rigid transform within a plane: translation and rotation about the plane's normal.

This is the configuration of a :planar joint — two translations in the plane and one rotation about its normal, which is the joint's axis.

Not tensor-backed

Unlike BB.Math.Transform, this holds three plain floats rather than an Nx tensor. BB.Math.Transform needs a matrix because it composes through kinematic chains by matrix multiply; a planar configuration does not, because it is lifted into a BB.Math.Transform by to_transform/2 before it reaches forward kinematics.

Storing theta as an angle rather than as the sin/cos entries of a rotation matrix means there is no orthonormality to lose and no renormalisation on read.

Conventions

  • x and y are in metres, within the plane
  • theta is in radians, right-handed about the plane's normal

Examples

iex> t = BB.Math.Transform2D.new(1.0, 2.0, :math.pi() / 2)
iex> {t.x, t.y}
{1.0, 2.0}

Summary

Functions

Compose two planar transforms.

The identity planar transform.

Invert a planar transform.

Create a planar transform from in-plane translation and rotation.

The two in-plane axes for a given plane normal.

Lift into 3D, given the normal of the plane the transform is in.

Types

t()

@type t() :: %BB.Math.Transform2D{theta: float(), x: float(), y: float()}

Functions

compose(a, b)

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

Compose two planar transforms.

compose(a, b) returns the transform that applies a first, then b, matching BB.Math.Transform.compose/2.

Examples

iex> a = BB.Math.Transform2D.new(1.0, 0.0, :math.pi() / 2)
iex> b = BB.Math.Transform2D.new(1.0, 0.0, 0.0)
iex> c = BB.Math.Transform2D.compose(a, b)
iex> {Float.round(c.x, 6), Float.round(c.y, 6)}
{1.0, 1.0}

identity()

@spec identity() :: t()

The identity planar transform.

Examples

iex> BB.Math.Transform2D.identity()
%BB.Math.Transform2D{x: 0.0, y: 0.0, theta: 0.0}

inverse(t)

@spec inverse(t()) :: t()

Invert a planar transform.

Examples

iex> t = BB.Math.Transform2D.new(1.0, 2.0, 0.5)
iex> c = BB.Math.Transform2D.compose(t, BB.Math.Transform2D.inverse(t))
iex> {abs(c.x) < 1.0e-12, abs(c.y) < 1.0e-12, abs(c.theta) < 1.0e-12}
{true, true, true}

new(x, y, theta)

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

Create a planar transform from in-plane translation and rotation.

Examples

iex> BB.Math.Transform2D.new(1, 2, 0)
%BB.Math.Transform2D{x: 1.0, y: 2.0, theta: 0.0}

plane_basis(normal)

@spec plane_basis(BB.Math.Vec3.t()) :: {BB.Math.Vec3.t(), BB.Math.Vec3.t()}

The two in-plane axes for a given plane normal.

{u, v, normal} is right-handed with u × v == normal, u is the direction x measures along and v the direction y measures along. The canonical {0, 0, 1} normal reduces to the XY plane with u == x̂ and v == ŷ.

Exposed because a planar joint's Jacobian columns must be expressed in the same basis to_transform/2 lifts its configuration through, and the two disagreeing would be a silently wrong derivative.

Examples

iex> {u, v} = BB.Math.Transform2D.plane_basis(BB.Math.Vec3.unit_z())
iex> {BB.Math.Vec3.to_list(u), BB.Math.Vec3.to_list(v)}
{[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]}

to_transform(t, normal)

@spec to_transform(t(), BB.Math.Vec3.t()) :: BB.Math.Transform.t()

Lift into 3D, given the normal of the plane the transform is in.

The normal is the :planar joint's axis. It is taken as a parameter rather than carried on the struct so that the joint remains the single source of truth for its own plane — a configuration carrying a copy could disagree with the joint that owns it.

The plane is spanned by two axes perpendicular to normal, with x along the first and y along the second, and theta is a right-handed rotation about normal. For the canonical {0, 0, 1} normal this reduces to the XY plane with theta as yaw.

Examples

iex> t2d = BB.Math.Transform2D.new(1.0, 2.0, 0.0)
iex> t = BB.Math.Transform2D.to_transform(t2d, BB.Math.Vec3.unit_z())
iex> BB.Math.Transform.get_translation(t) |> BB.Math.Vec3.to_list()
[1.0, 2.0, 0.0]