reckfellENGINE

MeshDataTool: изменить вершины и пересчитать нормали

Топология треугольной поверхности, поднятый край, материал и замена вместо лишнего Commit.

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

Запустить редактирование

Добавьте три файла: GeometryStage3D.cs, ArrayQuadLab3D.cs и MeshEditLab3D.cs. В отдельном C#-уровне 1.0.0 прикрепите только MeshEditLab3D к корню Entity3D. Задайте viewport 640×420, соберите и нажмите F6. Один край квадратной поверхности поднимется на 0,9; нормали будут пересчитаны. Показывающая Entity разворачивает готовую поверхность к камере.

Поверхность после изменения вершин и пересчёта нормалей

GeometryStage3D.cs
C#
using Reckfell;

public static class GeometryStage3D
{
    public static StandardMaterial3D Material(bool vertexColors = false, bool unshaded = false)
    {
        return new StandardMaterial3D
        {
            AlbedoColor = vertexColors ? Colors.White : new Color(0.24f, 0.44f, 1),
            VertexColorUseAsAlbedo = vertexColors,
            ShadingMode = unshaded ? BaseMaterial3D.ShadingModeEnum.Unshaded : BaseMaterial3D.ShadingModeEnum.PerPixel,
            Roughness = 0.8f
        };
    }

    public static void Setup(Entity3D root, string ru, string en)
    {
        root.AddChild(new WorldEnvironment { Environment = new Reckfell.Environment
        {
            BackgroundMode = Reckfell.Environment.BGMode.Color,
            BackgroundColor = new Color(0.035f, 0.04f, 0.05f),
            AmbientLightSource = Reckfell.Environment.AmbientSource.Color,
            AmbientLightColor = Colors.White, AmbientLightEnergy = 0.5f
        }});
        root.AddChild(new DirectionalLight3D { RotationDegrees = new Vector3(-55, -25, 0), LightEnergy = 1.2f });
        root.AddChild(new MeshInstance3D
        {
            Mesh = new BoxMesh { Size = new Vector3(8, 0.05f, 6) }, Position = new Vector3(0, -0.12f, 0),
            MaterialOverride = new StandardMaterial3D { AlbedoColor = new Color(0.10f, 0.11f, 0.13f), Roughness = 1 }
        });
        var camera = new Camera3D { Position = new Vector3(4, 4.8f, 6), Current = true, Fov = 45 };
        root.AddChild(camera); camera.LookAt(new Vector3(0, 0.35f, 0), Vector3.Up);
        var canvas = new CanvasLayer(); root.AddChild(canvas);
        var label = new Label { Position = new Vector2(24, 20), Text = TranslationServer.GetLocale().StartsWith("ru") ? ru : en };
        label.AddThemeFontSizeOverride("font_size", 22); canvas.AddChild(label);
    }
}
ArrayQuadLab3D.cs
C#
using Reckfell;

public partial class ArrayQuadLab3D : Entity3D
{
    public ArrayMesh Generated { get; private set; } = null!;

    public static ArrayMesh CreateQuad()
    {
        Vector3[] vertices = [new(-1, 0, -1), new(1, 0, -1), new(1, 0, 1), new(-1, 0, 1)];
        Vector3[] normals = [Vector3.Up, Vector3.Up, Vector3.Up, Vector3.Up];
        Vector2[] uv = [new(0, 0), new(1, 0), new(1, 1), new(0, 1)];
        int[] indices = [0, 1, 2, 0, 2, 3];
        using var arrays = new Reckfell.Collections.Array();
        arrays.Resize((int)Mesh.ArrayType.Max);
        arrays[(int)Mesh.ArrayType.Vertex] = vertices;
        arrays[(int)Mesh.ArrayType.Normal] = normals;
        arrays[(int)Mesh.ArrayType.TexUV] = uv;
        arrays[(int)Mesh.ArrayType.Index] = indices;
        var mesh = new ArrayMesh();
        mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays);
        return mesh;
    }

    public override void _Ready()
    {
        GeometryStage3D.Setup(this, "ArrayMesh: 4 вершины, 2 треугольника", "ArrayMesh: 4 vertices, 2 triangles");
        Generated = CreateQuad();
        AddChild(new MeshInstance3D { Mesh = Generated, MaterialOverride = GeometryStage3D.Material() });
    }
}
MeshEditLab3D.cs
C#
using System;
using Reckfell;

public partial class MeshEditLab3D : Entity3D
{
    public ArrayMesh Generated { get; private set; } = null!;

    public static Error RaiseFarEdge(ArrayMesh mesh, float height)
    {
        if (mesh.GetSurfaceCount() != 1 || mesh.SurfaceGetPrimitiveType(0) != Mesh.PrimitiveType.Triangles ||
            !float.IsFinite(height) || height < 0 || height > 2) return Error.InvalidParameter;
        using var data = new MeshDataTool();
        Error error = data.CreateFromSurface(mesh, 0);
        if (error != Error.Ok) return error;
        for (int i = 0; i < data.GetVertexCount(); i++)
        {
            Vector3 point = data.GetVertex(i);
            if (point.Z > 0) point.Y = height;
            data.SetVertex(i, point);
        }
        Vector3[] normals = new Vector3[data.GetVertexCount()];
        for (int face = 0; face < data.GetFaceCount(); face++)
        {
            int a = data.GetFaceVertex(face, 0), b = data.GetFaceVertex(face, 1), c = data.GetFaceVertex(face, 2);
            Vector3 normal = (data.GetVertex(c) - data.GetVertex(a)).Cross(data.GetVertex(b) - data.GetVertex(a));
            normals[a] += normal; normals[b] += normal; normals[c] += normal;
        }
        for (int i = 0; i < normals.Length; i++)
        {
            if (normals[i].IsZeroApprox()) return Error.InvalidData;
            data.SetVertexNormal(i, normals[i].Normalized());
        }
        Material material = data.GetMaterial();
        mesh.ClearSurfaces();
        error = data.CommitToSurface(mesh, compressionFlags: 0);
        if (error == Error.Ok) mesh.SurfaceSetMaterial(0, material);
        return error;
    }

    public override void _Ready()
    {
        GeometryStage3D.Setup(this, "MeshDataTool: новая форма и нормали", "MeshDataTool: updated shape and normals");
        Generated = ArrayQuadLab3D.CreateQuad();
        Error error = RaiseFarEdge(Generated, 0.9f);
        if (error != Error.Ok) throw new InvalidOperationException("Mesh edit failed: " + error);
        AddChild(new MeshInstance3D { Mesh = Generated, RotationDegrees = new Vector3(0, 180, 0), MaterialOverride = GeometryStage3D.Material() });
    }
}

Что хранит инструмент

CreateFromSurface принимает ArrayMesh и индекс поверхности Triangles. Проверяйте Error. Инструмент строит связи вершин, рёбер и граней в собственной рабочей копии; изменение SetVertex не обновляет отображаемый Mesh до Commit.

У исходного квадрата четыре вершины, две грани и пять рёбер. Одно внутреннее ребро принадлежит двум граням. GetFaceVertex возвращает индекс вершины данной грани; GetEdgeFaces — связанные грани. Не путайте номер ребра с индексом массива позиций.

Изменить геометрию согласованно

Код поднимает вершины с Z > 0 и заново накапливает нормали по треугольникам. Для текущего порядка граней используется (C−A).Cross(B−A), после суммирования результат нормализуется. Это сглаженные нормали для данного простого меша, а не универсальное решение жёстких рёбер и UV-швов.

Исходный квадрат не содержит tangents, skin, blend shapes, LOD или custom-каналов. Если они есть в вашей модели, проверяйте их сохранение и обновление отдельно. Изменённая форма может потребовать новых tangents, AABB и коллайдера; пересчёт нормалей не делает это автоматически.

Commit не перезаписывает источник

CommitToSurface добавляет поверхность. В стенде отдельный вызов увеличивает число поверхностей с одной до двух. В примере исходные поверхности очищаются только после подготовки допустимой рабочей копии; затем записывается одна новая поверхность и сохраняется её материал. GetMaterial — метод MeshDataTool, не свойство Material этого класса в C# SDK 1.0.0.

RaiseFarEdge предназначен для нашего собственного квадратного ресурса. Это не транзакционный редактор любого импортированного меша: если нужна гарантия сохранения старой модели при любой ошибке, сначала подготовьте новый ArrayMesh и замените ссылку лишь после успеха. Не очищайте ресурс, общий для нескольких объектов, без намерения изменить их все.

Проверены реальные связи топологии, поднятые позиции, перпендикулярность новых нормалей, сохранение одной поверхности и визуальный результат. Редакторский Undo/Redo, универсальное сохранение всех каналов модели и физическая форма здесь не реализованы.

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

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

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

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