pub struct Transform2D(/* private fields */);Expand description
A transform of the plane that may carry perspective.
A three-by-three homography acting on a point written (x, y, 1), which is
what dart:ui’s four-by-four reduces to for content on the z = 0 plane.
An affine is the case whose bottom row is (0, 0, 1), and it is the common
one — is_affine is how the fast paths ask.
§Why this is a newtype and not a Mat3
Because glam::Mat3::transform_point2 exists, compiles here, reads
correctly, and is wrong. It documents itself as assuming a valid affine
transform: it computes x_axis * x + y_axis * y + z_axis and stops, never
dividing by the w it just produced. Anyone converting a call site would
reach for it, and what comes out is a picture that is plausible and is not
of the transform that was asked for — the failure this renderer is built to
refuse.
So the divide is not optional here. This type offers
project_point2, which divides, and
project_homogeneous, which hands back the
undivided triple for the one caller that wants it — the vertex path, where
the rasterizer does the divide and needs w to clip against first. There is
no method that maps a point without doing one or the other.
§Storage
glam’s Mat3 is column-major, so z_axis.xy is the translation and the
bottom row — the one that makes this projective — is spread across the three
columns as (x_axis.z, y_axis.z, z_axis.z).
Implementations§
Source§impl Transform2D
impl Transform2D
pub const IDENTITY: Self
Sourcepub fn from_affine(affine: Affine2) -> Self
pub fn from_affine(affine: Affine2) -> Self
Lift an affine into a homography, which is exact and free.
Sourcepub fn to_affine(self) -> Option<Affine2>
pub fn to_affine(self) -> Option<Affine2>
The affine this is, or None if it carries perspective.
Sourcepub fn is_affine(self) -> bool
pub fn is_affine(self) -> bool
Whether the bottom row is (0, 0, 1), so that w is one everywhere.
Tested exactly rather than against a tolerance, and the distinction is
worth stating because it looks like an oversight. The bottom row’s first
two entries have units of inverse length, so there is no scale-free
threshold to compare them against: whether 0.001 is negligible depends
entirely on how far across the plane the geometry reaches. What this
answers is “was perspective asked for”, which is a question about how the
transform was built, and a transform built from an affine has exact
zeros there. Composition preserves them exactly, since the product’s
bottom row is (0, 0, 1) times the other matrix.
A caller wanting “close enough to affine over this region” is asking a different question, and it needs the region.
Sourcepub fn from_column_major_4x4(m: &[f32; 16]) -> Self
pub fn from_column_major_4x4(m: &[f32; 16]) -> Self
Reduce the dart:ui four-by-four, which is column-major and sixteen long.
Exact for the content this renderer draws rather than an approximation of
it. Applying a four-by-four to (x, y, 0, 1) never reads the z column,
and the z row only produces a depth nothing here has a use for — so
what is left, rows and columns zero, one and three, is the whole of what
the matrix means on the plane.
Sourcepub fn to_column_major_4x4(self) -> [f32; 16]
pub fn to_column_major_4x4(self) -> [f32; 16]
The four-by-four this reduces from, with the z axis left alone.
Sourcepub fn project_point2(self, point: Vec2) -> Vec2
pub fn project_point2(self, point: Vec2) -> Vec2
The point mapped and divided.
A point on the vanishing line has no image, and the divide reports that
as an infinity or a NaN rather than a wrong finite answer — which is what
every caller here already guards for with is_finite.
Sourcepub fn project_homogeneous(self, point: Vec2) -> Vec3
pub fn project_homogeneous(self, point: Vec2) -> Vec3
The point mapped and not divided.
What a vertex carries. The rasterizer divides, and it needs w first in
order to clip against the plane where w reaches zero.
Sourcepub fn inverse(self) -> Option<Self>
pub fn inverse(self) -> Option<Self>
The inverse, or None if there is not one.
An affine inverts as an affine, through Affine2 rather than through the
general path, which is cheaper and — the part worth writing down — makes
the exactness this code’s property instead of a dependency’s.
Everything downstream that asks is_affine is asking
about an exact zero, and an inverse taken the general way arrives at that
zero through the same adjugate arithmetic as every other entry. It does
in fact land on it today: glam returns a bottom row of exactly
(0, -0, 1) for a lifted affine, and negative zero compares equal, so
routing affines through the general path would work. It would work
because of how a dependency happens to arrange one division, which is not
a thing this can check and not a thing a version bump would announce. The
branch costs a comparison and removes the question.
Sourcepub fn is_finite(self) -> bool
pub fn is_finite(self) -> bool
Whether every entry is a number, which a caller checks before handing this to a shader that would otherwise spread a NaN across a draw.
Sourcepub fn determinant(self) -> f32
pub fn determinant(self) -> f32
The determinant, which is zero exactly when the plane has collapsed.
Sourcepub fn transformed_bounds_of(self, min: Vec2, max: Vec2) -> Option<(Vec2, Vec2)>
pub fn transformed_bounds_of(self, min: Vec2, max: Vec2) -> Option<(Vec2, Vec2)>
transformed_bounds, for a transform already in this form.
Sourcepub fn to_cols_array(self) -> [f32; 9]
pub fn to_cols_array(self) -> [f32; 9]
The nine floats, in column order.
Sourcepub fn max_scale_over(self, min: Vec2, max: Vec2) -> Option<f32>
pub fn max_scale_over(self, min: Vec2, max: Vec2) -> Option<f32>
The largest factor by which this can stretch a direction anywhere in a
box, or None if the box reaches the vanishing line.
max_scale answers this for an affine with one number, because an
affine stretches every part of the plane alike. A homography does not:
writing it as (A p + b) / (R · p + s), its derivative at p is
(A - N(p) Rᵀ) / w(p) for N the mapped point and w the divisor, so
the stretch grows as roughly one over w squared and the near end of a
shape needs finer flattening than the far end.
Bounding it over a box rather than solving for the worst point: w is
affine, so its smallest value over a box is at a corner, and while w
stays positive the image of the box is the convex hull of the mapped
corners, so the largest ‖N‖ is at a corner too. Four evaluations, the
same four transformed_bounds already makes.
Taking the largest is conservative in the direction that costs triangles rather than correctness: the far end of a shape is flattened more finely than it needs, and the near end is flattened finely enough.
For an affine this reduces to max_scale exactly — R is zero and
w is one, leaving the longer basis vector — so no shape already being
drawn is tessellated any differently than it was.
Trait Implementations§
Source§impl Clone for Transform2D
impl Clone for Transform2D
impl Copy for Transform2D
Source§impl Debug for Transform2D
impl Debug for Transform2D
Source§impl Default for Transform2D
impl Default for Transform2D
Source§impl From<&Affine2> for Transform2D
So that a caller holding an affine by reference need not name the lift.
impl From<&Affine2> for Transform2D
So that a caller holding an affine by reference need not name the lift.
Source§impl From<Affine2> for Transform2D
impl From<Affine2> for Transform2D
Source§impl Mul for Transform2D
impl Mul for Transform2D
Source§impl Mul<Affine2> for Transform2D
So that composing with an affine reads the way composing two of these does.
impl Mul<Affine2> for Transform2D
So that composing with an affine reads the way composing two of these does.
Most of what a transform is composed with here is affine — a projection, a translation to a paint’s origin, a rotation folded into a gradient’s space — and lifting each of those at the call site would bury the composition it is there to express.
Source§impl Mul<Transform2D> for Affine2
impl Mul<Transform2D> for Affine2
Source§type Output = Transform2D
type Output = Transform2D
* operator.Source§fn mul(self, rhs: Transform2D) -> Transform2D
fn mul(self, rhs: Transform2D) -> Transform2D
* operation. Read moreSource§impl PartialEq for Transform2D
impl PartialEq for Transform2D
impl StructuralPartialEq for Transform2D
Auto Trait Implementations§
impl Freeze for Transform2D
impl RefUnwindSafe for Transform2D
impl Send for Transform2D
impl Sync for Transform2D
impl Unpin for Transform2D
impl UnsafeUnpin for Transform2D
impl UnwindSafe for Transform2D
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more