ArrayMesh: create a surface from arrays
A complete quad with normals, UVs and indices; array layout, winding and resource saving.
Run the quad
Add GeometryStage3D.cs and ArrayQuadLab3D.cs to a 1.0.0 C# project. Attach ArrayQuadLab3D to a separate Entity3D-root level, set a 640×420 viewport, build and press F6. A blue quad appears above the gray floor. It has one surface, four vertices and six indices.
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() });
}
}Build a consistent attribute set
The outer Reckfell.Collections.Array has Mesh.ArrayType.Max slots. Its indices identify attributes, not individual vertices. Unused slots stay Nil. The example supplies typed Vector3[], Vector2[] and int[] data rather than arbitrary Variant values.
| Slot | Example data |
|---|---|
| Vertex | 4 Vector3 positions |
| Normal | 4 Vector3 normals, Up here |
| TexUV | 4 Vector2 coordinates |
| Index | 6 int indices: 0, 1, 2, 0, 2, 3 |
Enabled per-vertex attributes must match the vertex count. Every index must lie in 0..vertexCount−1. Triangle index counts must be divisible by 3. Normals need correct length/orientation; UV data alone does not assign a texture.
The example faces upward: (C−A).Cross(B−A) for its triangle gives Up. Winding, lighting and normal direction must agree. A normal map also needs correct tangents; see SurfaceTool for their generation.
Appending is not replacing
AddSurfaceFromArrays appends a surface. A second call does not overwrite the first. For a full update of an owned resource, prepare/validate new data, then create another ArrayMesh or clear old surfaces. Do not clear a shared resource when other objects must retain the previous geometry.
Assign a surface material or use the displaying MeshInstance3D MaterialOverride. Multiple surfaces or extra material passes may require extra draws; one Mesh does not promise one draw call.
Save the result
ResourceSaver.Save stores the completed ArrayMesh, for example user://quad.rdata; check Error. The fixture saves and reloads with CacheMode.Ignore, retaining its surface and four vertices. Decide separately whether to regenerate at startup or include a saved resource in export. Saving a mesh does not automatically save the level, camera or C# code.
Checks cover attributes, front face, AABB, append/clear behavior, save/load and real rendering. This is geometry without a physics collider; the quad does not establish skin, blend-shape or malformed-input support.