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
xandyare in metres, within the planethetais 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
Functions
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}
@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}
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}
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}
@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]}
@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]