reckfellENGINE

MultiMeshInstance3D: many instances of one mesh

A 64-cube field, allocation order, colors and the editor Populate Surface tool.

On this page

Run the field

Add GeometryStage3D.cs and MultiMeshFieldLab3D.cs. Attach MultiMeshFieldLab3D to a separate Entity3D-root 1.0.0 C# level, set a 640×420 viewport, build and press F6. It displays 64 instances of shared cube geometry with individual height/color. UseBulkUpload starts disabled; see the buffer guide for that alternative.

64 shared BoxMesh instances with individual transforms and colors

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);
    }
}
MultiMeshFieldLab3D.cs
C#
using System;
using Reckfell;

public partial class MultiMeshFieldLab3D : Entity3D
{
    [Export] public int Count { get; set; } = 64;
    [Export] public bool UseBulkUpload { get; set; }
    public MultiMesh Field { get; private set; } = null!;
    private MultiMeshInstance3D _visual = null!;

    public static Transform3D TransformAt(int index)
    {
        float height = 0.35f + (index % 5) * 0.12f;
        var basis = Basis.Identity.Scaled(new Vector3(0.32f, height, 0.32f));
        return new Transform3D(basis, new Vector3((index % 8 - 3.5f) * 0.5f, height / 2, (index / 8 - 3.5f) * 0.5f));
    }

    public static Color ColorAt(int index) => index % 4 == 0 ? Colors.White : new Color(0.24f, 0.44f, 1);
    public static Color DataAt(int index) => new(index / 255.0f, 0, 0, 1);

    public static float[] BuildBuffer(int count)
    {
        if (count < 1 || count > 256) throw new ArgumentOutOfRangeException(nameof(count));
        float[] buffer = new float[count * 20];
        for (int i = 0; i < count; i++)
        {
            Transform3D t = TransformAt(i); Color color = ColorAt(i), data = DataAt(i); int p = i * 20;
            buffer[p] = t.Basis.Row0.X; buffer[p + 1] = t.Basis.Row0.Y; buffer[p + 2] = t.Basis.Row0.Z; buffer[p + 3] = t.Origin.X;
            buffer[p + 4] = t.Basis.Row1.X; buffer[p + 5] = t.Basis.Row1.Y; buffer[p + 6] = t.Basis.Row1.Z; buffer[p + 7] = t.Origin.Y;
            buffer[p + 8] = t.Basis.Row2.X; buffer[p + 9] = t.Basis.Row2.Y; buffer[p + 10] = t.Basis.Row2.Z; buffer[p + 11] = t.Origin.Z;
            buffer[p + 12] = color.R; buffer[p + 13] = color.G; buffer[p + 14] = color.B; buffer[p + 15] = color.A;
            buffer[p + 16] = data.R; buffer[p + 17] = data.G; buffer[p + 18] = data.B; buffer[p + 19] = data.A;
        }
        return buffer;
    }

    public Error Rebuild(int count, bool bulk)
    {
        if (_visual is null) return Error.Unconfigured;
        if (count < 1 || count > 256) return Error.InvalidParameter;
        _visual.Multimesh = null;
        Field?.Dispose();
        Field = new MultiMesh
        {
            TransformFormat = MultiMesh.TransformFormatEnum.Transform3D,
            UseColors = true, UseCustomData = true,
            Mesh = new BoxMesh { Size = Vector3.One, Material = GeometryStage3D.Material(vertexColors: true) },
            InstanceCount = count
        };
        if (bulk) Field.Buffer = BuildBuffer(count);
        else for (int i = 0; i < count; i++)
        {
            Field.SetInstanceTransform(i, TransformAt(i));
            Field.SetInstanceColor(i, ColorAt(i));
            Field.SetInstanceCustomData(i, DataAt(i));
        }
        int rows = (count + 7) / 8;
        Field.CustomAabb = new Aabb(new Vector3(-2, -0.05f, -2), new Vector3(4, 1.5f, Math.Max(4, rows * 0.5f)));
        Field.VisibleInstanceCount = -1;
        _visual.Multimesh = Field;
        Count = count; UseBulkUpload = bulk;
        return Error.Ok;
    }

    public override void _Ready()
    {
        GeometryStage3D.Setup(this, "MultiMesh: общая геометрия, свои данные", "MultiMesh: shared geometry, per-instance data");
        _visual = new MultiMeshInstance3D(); AddChild(_visual);
        if (Rebuild(Count, UseBulkUpload) != Error.Ok) ReckfellApi.PrintErr("MultiMesh count must be 1..256");
    }
}

MultiMesh stores instance data. MultiMeshInstance3D displays it through the C# property Multimesh. Elements are not individual Entities and do not automatically gain scripts, physics or separate Mesh resources. Split different geometries/materials into groups.

Configuration order

Set TransformFormat, UseColors and UseCustomData while InstanceCount = 0, then assign Mesh and allocate InstanceCount. The engine rejects those format changes after allocation. VisibleInstanceCount does not change capacity: −1 shows all, 0 none, N the first N.

SetInstanceTransform uses MultiMeshInstance3D local space; SetInstanceColor assigns color. The example material enables VertexColorUseAsAlbedo. INSTANCE_CUSTOM supplies values to a custom shader; the standard material does not turn them into animation automatically.

CustomAabb must cover all local-space instances, including mesh extents. The code conservatively bounds its grid. A small bound can hide visible geometry; an oversized one weakens culling. Elements do not receive individual Entity camera culling: partition large areas into spatial groups.

Populate a surface in the editor

The checked editor source exposes MultiMesh → Populate Surface for a selected MultiMeshInstance3D, opening Populate MultiMesh. Prepare a MeshInstance3D target surface and another MeshInstance3D containing the source mesh. Select them in Target Surface and Source Mesh. Paths are relative to the selected MultiMeshInstance3D, for example ../Ground and ../Source. A hidden Source can supply the mesh without displaying an extra copy.

Mesh Up Axis selects the source up axis, ordinarily Y for BoxMesh. Amount sets count and Scale sets scale; Random Rotation/Tilt/Scale add variation. Start with a small Amount and zero variation. The target needs triangle faces; empty meshes or invalid paths fail. Without Source Mesh, the tool attempts to use the current MultiMesh Mesh.

Populate distributes instances by triangle area and assigns a new MultiMesh to the selected Entity. The checked method does not register its own Undo/Redo action; save the previous resource separately when it must be retained. This is not saved-seed deterministic generation or physics spawning. Menu steps are source-checked; native editor mouse actions remain unverified here. The programmatic example is runtime- and render-checked.

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.