ArrayMesh: создать поверхность из массивов
Полный квадрат с нормалями, UV и индексами; форматы массивов, порядок граней и сохранение ресурса.
Запустить квадрат
Добавьте GeometryStage3D.cs и ArrayQuadLab3D.cs в C#-проект 1.0.0. Прикрепите ArrayQuadLab3D к корню Entity3D отдельного уровня, задайте viewport 640×420, соберите и нажмите F6. Над серым полом появится синяя квадратная поверхность. У неё одна поверхность, четыре вершины и шесть индексов.
GeometryStage3D.cs
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
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() });
}
}Собрать согласованный набор
Внешний Reckfell.Collections.Array имеет размер Mesh.ArrayType.Max. Его индексы — слоты атрибутов, а не номера вершин. Незаполненные слоты остаются Nil. Код передаёт типизированные Vector3[], Vector2[] и int[]; не заменяйте их массивом случайных Variant.
| Слот | Данные примера |
|---|---|
| Vertex | 4 позиции Vector3 |
| Normal | 4 нормали Vector3, здесь Up |
| TexUV | 4 координаты Vector2 |
| Index | 6 индексов int: 0, 1, 2, 0, 2, 3 |
Все включённые повершинные атрибуты должны соответствовать числу вершин. Каждый индекс должен быть в диапазоне 0..vertexCount−1. Для Triangles число индексов кратно 3. Нормали нужны корректной длины и направления; наличие UV само по себе не назначает текстуру.
Порядок примера задаёт лицевую сторону сверху: при расчёте нормали для этих трёх вершин (C−A).Cross(B−A) даёт Up. Геометрический обход, освещение и сторона нормали должны быть согласованы. Для normal map дополнительно нужны корректные tangents; их построение показано в SurfaceTool.
Добавление не равно замене
AddSurfaceFromArrays добавляет новую поверхность. Второй вызов не перезаписывает первую. Для полного обновления собственного ресурса сначала подготовьте и проверьте новые данные, затем создайте новый ArrayMesh либо очистите старые поверхности. Не очищайте общий ресурс, если другие объекты должны сохранить прежнюю геометрию.
Материал можно назначить поверхности либо через MaterialOverride показывающей MeshInstance3D. Несколько поверхностей или дополнительных проходов материала могут означать дополнительные отрисовки; один Mesh не обещает единственный draw call.
Сохранить результат
ResourceSaver.Save сохраняет готовый ArrayMesh как ресурс, например user://quad.rdata; проверяйте Error. В стенде выполнены сохранение и повторная загрузка с CacheMode.Ignore, после которой сохранились поверхность и четыре вершины. Для игрового проекта отдельно решите, генерировать меш при запуске или включить сохранённый ресурс в экспорт. Сохранение меша не сохраняет автоматически текущий уровень, камеру и C#-код.
Проверены массивы, лицевая сторона, AABB, добавление/очистка поверхностей, сохранение/загрузка и реальное отображение. Это геометрия без физического коллайдера; skin, blend shapes и произвольные повреждённые входные данные этим квадратом не проверены.