reckfellENGINE

Преобразования: точка, направление и обратная матрица

Локальные и мировые координаты, порядок умножения и AffineInverse при масштабе.

На этой странице

Что хранит преобразование

Transform2D содержит две оси и Origin; Transform3D — Basis с тремя осями и Origin. Оси описывают поворот, масштаб и возможный сдвиг осей относительно друг друга; Origin задаёт перенос. Identity ничего не меняет. Vector3.Zero — нулевая точка, а default(Basis) с нулевыми осями не заменяет Basis.Identity.

ОперацияРезультат
transform × pointТочка с поворотом/масштабом и переносом
transform.Basis × direction в 3DВектор с поворотом/масштабом, без переноса
parent × childПреобразование ребёнка в пространстве родителя; правое применяется первым
transform.AffineInverse() × worldPointОбратный перевод точки при обратимом масштабе

В 2D оси (2, 0) и (0, 3), Origin = (10, 20), локальная точка (4, 5) дают мировую (18, 35). Здесь первый столбец умножается на X, второй на Y, затем прибавляется Origin. Обратный перевод должен вернуть (4, 5).

Не перепутать обратные операции

Inverse использует транспонирование и предполагает ортонормированный базис: взаимно перпендикулярные оси длины 1. Он не является общей обратной операцией для масштабированной матрицы. Для неё нужен AffineInverse. В C# обратная запись point × transform также использует предположение об ортонормированности; она не заменяет AffineInverse для масштаба.

Определитель 0 означает, что обратной матрицы нет. Очень близкий к вырожденному базис может усиливать ошибки float. Для равномерного масштаба s определитель равен s² в 2D и s³ в 3D: при s = 2 получаются 4 и 8. Отрицательный определитель меняет ориентацию базиса. IsFinite не проверяет эти свойства.

Проверить в дереве Entity

Добавьте TransformRig3D.cs к корню Entity3D отдельного C#-уровня 1.0.0. Окно/viewport — 640×420. После сборки и F6 появятся масштабированные локальные оси, синий куб и его проекция. Строка TRANSFORM должна вернуть локальную точку около (1, 1, 0); формат чисел зависит от локали.

Преобразование локальной точки через повёрнутого и масштабированного родителя

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;
    }
}

У ребёнка GlobalTransform = Parent.GlobalTransform × Transform. ToGlobal переводит точку из локального пространства Entity в мировое, ToLocal — обратно. Это не операции для произвольной нормали: при неравномерном масштабе нормаль переносится обратным транспонированным Basis и снова нормализуется.

TryTransformNormal демонстрирует такой перенос и отвергает нулевые/нефинитные данные и слишком маленький по выбранному порогу определитель. Порог 0,000001 относится к примеру, не к универсальной допустимости любого масштаба мира. Обычное basis × normal при неравномерном масштабе может перестать быть перпендикулярным поверхности.

Изменение C#-значения

Vector3, Basis и Transform — структуры. Возьмите копию Position/Transform, измените нужное поле и присвойте структуру обратно свойству. Нельзя рассчитывать, что изменение отдельной копии автоматически обновит Entity. Для направления движения явно решите, учитывать ли длины масштабированных осей или нормализовать направление. Для вращений продолжите практикой Entity3D.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.