public readonly record struct UiTransformThe 2D affine a composited group's rotate and scale place it under.
Remarks
⚠ This used to be six floats that deliberately could not express a projective map, and the argument for that was sound rather than mistaken. It ran: the third row of a 2D affine is always 0 0 1, storing it costs bytes on a type both executors compare by value, and — the real point — a perspective divide is exactly what the composite quad's linearly interpolated texture coordinates cannot survive, so a type that cannot express one is the type that says so.
⚠ What that argument did not reach is that the element is planar. A plane under a 3D transform and a perspective projects to a plane: the four corners of the border box land at four points with four ws, and that map is exactly a 2D homography. So rotateX and perspective need a 3×3 and not a 4×4, and the third column — M13, M23, M33 — is the whole of the difference. The texture coordinates still cannot survive a linear interpolation, and the answer to that is a w on the vertex rather than a type that refuses the matrix: a rasteriser divides by w because that is what a rasteriser does.
⚠ The affine case is bit-for-bit what it was, and that is a requirement rather than a nicety. Every reference image in Vixen.Graphics.Golden.Tests.UiCompositingTests was rendered through the six-float arithmetic, so a rounding that moved would have to be accepted into every one of them — and "both executors changed together" is precisely what a bug in a shared specification looks like. Then, Apply, Determinant and About are written so that their extra terms are exactly + 0 and × 1 on an affine, which IEEE-754 leaves untouched; Invert cannot be written that way and branches instead, keeping the old expression verbatim. UiTransformProjectiveTests asserts the equality rather than leaving it as reasoning.
⚠ And default is still what it always was, which is why the third column is stored offset. A homography's identity has M33 = 1, so a naively added field would make every zeroed struct divide by zero — turning today's defined-but-wrong collapse-to-the-origin into a NaN that propagates into a vertex buffer. The field behind M33 holds M33 − 1, so a zeroed UiTransform is affine and behaves exactly as it did before this type could be projective.
⚠ Expressed in absolute document space, with the transform origin already folded in. CSS defines rotate about transform-origin, which is the border box's centre by default, so the matrix that reaches here is already T(origin) · R · S · T(-origin). Carrying the origin separately would put the same composition in the geometry builder and in the hit test, which is the two-copies-of-the- arithmetic failure UiDocument.Accumulate's own remark warns about for translate. One matrix, composed once, read by both.
⚠ Identity is the absence, and default is not it. A zeroed struct collapses every point to the origin, so a consumer that read a default field as "no transform" would draw nothing at all. Transform and Transform are therefore nullable, exactly as UiColorMatrix is and for the identical reason.
Fields and properties (13)
public float M11The x contribution to x.
public float M12The x contribution to y.
public float M21The y contribution to x.
public float M22The y contribution to y.
public float DxThe x translation, in document pixels.
public float DyThe y translation, in document pixels.
public static UiTransform IdentityThe transform that moves nothing.
public float M13How much a point's x contributes to its w.
public float M23And its y.
public float M33The homogeneous scale at the origin: one for an affine, anything else for a homography.
public bool IsAffineWhether this is a plain affine, so no w anywhere can be anything but one.
public bool IsIdentityWhether this is Identity, to the tolerance a pixel can tell.
public float DeterminantThis transform's determinant.
Methods (10)
public UiTransform(float M11, float M12, float M21, float M22, float Dx, float Dy)The 2D affine a composited group's rotate and scale place it under.
public static UiTransform Rotation(float degrees, Vector2 about)A rotation about a point, in degrees clockwise.
public static UiTransform Scale(float x, float y, Vector2 about)A scale about a point.
public UiTransform About(Vector2 origin)This transform performed about rather than about zero.
public UiTransform Then(in UiTransform then)This transform followed by .
public Vector2 Apply(Vector2 point)Where a point lands, projected.
public Vector3 Project(Vector2 point)Where a point lands, before the divide: x, y and w.
public UiTransform? Invert()The transform that undoes this one, or null where there is none.
public Rectangle Bounds(Rectangle rectangle)The axis-aligned box that contains once transformed.
public bool TryBounds(Rectangle rectangle, out Rectangle bounds)The axis-aligned box that contains once transformed, or false where it has none.
Used by (12)
- DrawCommandVixen.Ui
- DrawListBuilderVixen.Ui
- OpeningVixen.Ui
- TransformReaderVixen.Ui
- TransformTestsVixen.Ui.Tests
- UiCompositingTestsVixen.Graphics.Golden.Tests
- UiDocumentVixen.Ui
- UiElementVixen.Ui
- UiGeometryBuilderVixen.Ui
- UiLayerVixen.Ui
- UiTransformProjectiveTestsVixen.Ui.Tests
- UtilityFamilySupportTestsVixen.Editor.Ui.Tests