Skip to main content

Transform2D

Struct Transform2D 

Source
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

Source

pub const IDENTITY: Self

Source

pub fn from_affine(affine: Affine2) -> Self

Lift an affine into a homography, which is exact and free.

Source

pub fn to_affine(self) -> Option<Affine2>

The affine this is, or None if it carries perspective.

Source

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.

Source

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.

Source

pub fn to_column_major_4x4(self) -> [f32; 16]

The four-by-four this reduces from, with the z axis left alone.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn determinant(self) -> f32

The determinant, which is zero exactly when the plane has collapsed.

Source

pub fn transformed_bounds_of(self, min: Vec2, max: Vec2) -> Option<(Vec2, Vec2)>

transformed_bounds, for a transform already in this form.

Source

pub fn to_cols_array(self) -> [f32; 9]

The nine floats, in column order.

Source

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

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Transform2D

Source§

impl Debug for Transform2D

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Transform2D

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl From<&Affine2> for Transform2D

So that a caller holding an affine by reference need not name the lift.

Source§

fn from(affine: &Affine2) -> Self

Converts to this type from the input type.
Source§

impl From<Affine2> for Transform2D

Source§

fn from(affine: Affine2) -> Self

Converts to this type from the input type.
Source§

impl Mul for Transform2D

Source§

type Output = Transform2D

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Self) -> Self

Performs the * operation. Read more
Source§

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§

type Output = Transform2D

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Affine2) -> Self

Performs the * operation. Read more
Source§

impl Mul<Transform2D> for Affine2

Source§

type Output = Transform2D

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Transform2D) -> Transform2D

Performs the * operation. Read more
Source§

impl PartialEq for Transform2D

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Transform2D

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.