reckfellENGINE

Transforms: points, directions and inverse matrices

Local/world coordinates, multiplication order and AffineInverse under scale.

On this page

What a transform stores

Transform2D contains two axes and an Origin; Transform3D contains a three-axis Basis and an Origin. Axes describe rotation, scale and possible skew; Origin supplies translation. Identity changes nothing. Vector3.Zero is a zero point; default(Basis), with zero axes, is not Basis.Identity.

OperationResult
transform × pointPoint affected by rotation/scale and translation
transform.Basis × direction in 3DVector affected by rotation/scale, without translation
parent × childChild in the parent's space; the right operand acts first
transform.AffineInverse() × worldPointInverse point conversion with invertible scale

In 2D, axes (2, 0), (0, 3), Origin = (10, 20) and local point (4, 5) yield world point (18, 35). Multiply the first column by X, the second by Y, then add Origin. The inverse conversion must return (4, 5).

Distinguish inverse operations

Inverse uses a transpose and assumes an orthonormal basis: perpendicular axes of unit length. It is not a general inverse for scaled matrices; use AffineInverse for those. In C#, point × transform also assumes orthonormality and does not replace AffineInverse under scale.

A zero determinant means no inverse exists. A nearly degenerate basis can amplify float errors. Uniform scale s has determinant s² in 2D and s³ in 3D: s = 2 gives 4 and 8. A negative determinant reverses basis orientation. IsFinite does not check these properties.

Check the Entity hierarchy

Attach TransformRig3D.cs to an Entity3D root in a separate 1.0.0 C# level. Use a 640×420 window/viewport. Build and press F6 to see scaled local axes, the blue cube and its projection. The TRANSFORM line should return a local point near (1, 1, 0); numeric formatting depends on locale.

A local point transformed through a rotated and scaled parent

TransformRig3D.cs
C#
using System;
using Reckfell;

public partial class TransformRig3D : Entity3D
{
    public Entity3D Pivot { get; private set; } = null!;
    public MeshInstance3D Marker { get; private set; } = null!;
    public Plane GroundPlane => new(Vector3.Up, 0);
    public Vector3 ProjectedPoint => GroundPlane.Project(Marker.GlobalPosition);

    private static MeshInstance3D Box(Vector3 size, Color color, Vector3 position)
    {
        return new MeshInstance3D
        {
            Mesh = new BoxMesh { Size = size }, Position = position,
            MaterialOverride = new StandardMaterial3D
            {
                AlbedoColor = color, ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded
            }
        };
    }

    public override void _Ready()
    {
        AddChild(new WorldEnvironment { Environment = new Reckfell.Environment
        {
            BackgroundMode = Reckfell.Environment.BGMode.Color,
            BackgroundColor = new Color(0.035f, 0.04f, 0.05f)
        }});
        AddChild(Box(new Vector3(8, 0.05f, 6), new Color(0.10f, 0.11f, 0.13f), new Vector3(0, -0.025f, 0)));
        Pivot = new Entity3D
        {
            Position = new Vector3(1, 0, 0), Rotation = new Vector3(0, Mathf.Pi / 4, 0),
            Scale = new Vector3(1.5f, 1, 0.6f)
        };
        AddChild(Pivot);
        Pivot.AddChild(Box(new Vector3(1.6f, 0.045f, 0.045f), new Color(0.25f, 0.46f, 1), new Vector3(0.8f, 0, 0)));
        Pivot.AddChild(Box(new Vector3(0.045f, 1.5f, 0.045f), Colors.White, new Vector3(0, 0.75f, 0)));
        Pivot.AddChild(Box(new Vector3(0.045f, 0.045f, 1.5f), new Color(0.45f, 0.48f, 0.55f), new Vector3(0, 0, 0.75f)));
        Marker = Box(new Vector3(0.2f, 0.2f, 0.2f), new Color(0.25f, 0.46f, 1), new Vector3(1, 1, 0));
        Pivot.AddChild(Marker);
        AddChild(Box(new Vector3(0.25f, 0.035f, 0.25f), Colors.White, ProjectedPoint + Vector3.Up * 0.025f));
        var camera = new Camera3D { Position = new Vector3(5, 4.5f, 7), Current = true, Fov = 48 };
        AddChild(camera); camera.LookAt(new Vector3(0.7f, 0.4f, 0), Vector3.Up);
        var canvas = new CanvasLayer(); AddChild(canvas);
        bool ru = TranslationServer.GetLocale().StartsWith("ru");
        var title = new Label { Position = new Vector2(24, 20), Text = ru
            ? "Локальная точка и мировая плоскость" : "A local point and a world-space plane" };
        title.AddThemeFontSizeOverride("font_size", 22); canvas.AddChild(title);
        canvas.AddChild(new Label { Position = new Vector2(24, 58), Text = ru
            ? "Синий куб: local (1,1,0). Белая метка: проекция на Y = 0."
            : "Blue cube: local (1,1,0). White marker: projection onto Y = 0." });
        ReckfellApi.Print("TRANSFORM local=" + Pivot.ToLocal(Marker.GlobalPosition));
    }

    public static bool TryTransformNormal(Basis basis, Vector3 normal, out Vector3 result)
    {
        result = Vector3.Zero;
        if (!basis.IsFinite() || !normal.IsFinite() || normal.LengthSquared() < 0.000001f ||
            MathF.Abs(basis.Determinant()) < 0.000001f) return false;
        Vector3 transformed = basis.Inverse().Transposed() * normal;
        if (!transformed.IsFinite() || transformed.LengthSquared() < 0.000001f) return false;
        result = transformed.Normalized();
        return true;
    }
}

A child's GlobalTransform = Parent.GlobalTransform × Transform. ToGlobal converts a point from an Entity's local space to world space; ToLocal reverses it. These are not arbitrary-normal operations: under nonuniform scale, transform a normal with the inverse-transposed Basis and normalize again.

TryTransformNormal demonstrates that conversion, rejecting zero/nonfinite input and a determinant below its chosen threshold. The 0.000001 threshold belongs to this example, not universal world-scale validity. Ordinary basis × normal may cease to be perpendicular to the surface under nonuniform scale.

Updating C# values

Vector3, Basis and Transform are structs. Copy Position/Transform, edit the required field and assign the struct back. Modifying a separate copy does not automatically update an Entity. For movement, decide explicitly whether to retain scaled-axis magnitudes or normalize direction. Continue with Entity3D practice for rotations.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.