How to create procedural generated geometry using MeshAccessor?

I am trying to procedurally generate large amount of geometry. According to the tutorial (here) and other sources I should be using the MeshAccessor class to achieve this. I am wondering how I am supposed to do this?

Between extensive reading of the API docs and various AI hallucinations pointing me in hopefully the right direction I have come up with the following proof of concept to learn the API. Note that none of the following code was written by AI, it was only used as reference material.

public class TrackBuilder : Script
{
    public Actor trackHolder;
    public Material material;

    public override void OnStart()
    {
        //create and instanciante the new object
        StaticModel actor = new StaticModel();
        actor.Parent = trackHolder;
        //do the thing magic thing to allow dynamic geometery
        actor.Model = Content.CreateVirtualAsset<Model>();
        //create a list of LODs each containing n meshes. In out case 1 LOD containing 1 mesh.
        //also maybe initialise the mesh coresponding to the LOD?
        actor.Model.SetupLODs(new[] { 1 });
        //create the Mesh accessesor
        MeshAccessor accessor = new MeshAccessor();

        //create the data
        Float3[] verticies = new[]
        {
            /*0*/  new Float3(-1.5f, 0.0f, 0.0f),
            /*1*/  new Float3(-1.0f, 0.5f, 0.0f),
            /*2*/  new Float3( 1.0f, 0.5f, 0.0f),
            /*3*/  new Float3( 1.5f, 0.0f, 0.0f),

            /*4*/  new Float3(-1.5f, 0.0f, 1.0f),
            /*5*/  new Float3(-1.0f, 0.5f, 1.0f),
            /*6*/  new Float3( 1.0f, 0.5f, 1.0f),
            /*7*/  new Float3( 1.5f, 0.0f, 1.0f),

            /*8*/  new Float3(-1.5f, 0.0f, 2.0f),
            /*9*/  new Float3(-1.0f, 0.5f, 2.0f),
            /*10*/ new Float3( 1.0f, 0.5f, 2.0f),
            /*11*/ new Float3( 1.5f, 0.0f, 2.0f),
        };
        Int32[] indicies = new[]
        {
            //front face
            0, 1, 2,
            0, 2, 3,
            
            //first "cube"
            //left face
            4, 5, 1,
            4, 1, 0,
            //right face
            3, 2, 6,
            3, 6, 7,
            //top face
            1, 5, 6,
            1, 6, 2,
            //bottom face
            4, 0, 3,
            4, 3, 7,

            //second "cube"
            //left face
            8, 9, 5,
            8, 5, 4,
            //right face
            7, 6, 10,
            7, 10, 11,
            //top face
            5, 9, 10,
            5, 10, 6,
            //bottom face
            8, 4, 7,
            8, 7, 11,

            //back face
            11, 10, 9,
            11, 9, 8,
        };
        Float3[] normals = new[]
        {
            ((verticies[1] - verticies[0]) + (verticies[3] - verticies[0]) + (verticies[4] - verticies[0])).Normalized,
            ((verticies[2] - verticies[1]) + (verticies[0] - verticies[1]) + (verticies[5] - verticies[1])).Normalized,
            ((verticies[3] - verticies[2]) + (verticies[1] - verticies[2]) + (verticies[6] - verticies[2])).Normalized,
            ((verticies[0] - verticies[3]) + (verticies[2] - verticies[3]) + (verticies[7] - verticies[3])).Normalized,

            ((verticies[5] - verticies[4]) + (verticies[7] - verticies[4]) + (verticies[8] - verticies[4]) + (verticies[0] - verticies[4])).Normalized,
            ((verticies[6] - verticies[5]) + (verticies[4] - verticies[5]) + (verticies[9] - verticies[5]) + (verticies[1] - verticies[5])).Normalized,
            ((verticies[7] - verticies[6]) + (verticies[5] - verticies[6]) + (verticies[10] - verticies[6]) + (verticies[2] - verticies[6])).Normalized,
            ((verticies[4] - verticies[7]) + (verticies[6] - verticies[7]) + (verticies[11] - verticies[7]) + (verticies[3] - verticies[7])).Normalized,

            ((verticies[9] - verticies[8]) + (verticies[11] - verticies[8]) + (verticies[4] - verticies[8])).Normalized,
            ((verticies[10] - verticies[9]) + (verticies[8] - verticies[9]) + (verticies[5] - verticies[9])).Normalized,
            ((verticies[11] - verticies[10]) + (verticies[9] - verticies[10]) + (verticies[6] - verticies[10])).Normalized,
            ((verticies[8] - verticies[11]) + (verticies[10] - verticies[11]) + (verticies[7] - verticies[11])).Normalized,
        };
        //give the accessor the changes
        MeshAccessor.Stream vertexStream = accessor.Position();
        for(int i = 0; i < verticies.Length; i++)
        {
            vertexStream.SetFloat3(i, verticies[i]);
        }
        MeshAccessor.Stream indexStream = accessor.Index();
        for(int i = 0; i < indicies.Length; i++)
        {
            vertexStream.SetInt(i, indicies[i]);
        }
        MeshAccessor.Stream normalStream = accessor.Normal();
        for(int i = 0; i < normals.Length; i++)
        {
            vertexStream.SetFloat3(i, normals[i]);
        }
        //write the chcnages to the GPU
        accessor.UpdateMesh(actor.Model.LODs[0].Meshes[0]);
        //boring things to make the mesh visable
        actor.SetMaterial(0, material);
        actor.Scale = new Vector3(100,100,100);
    }
}

Currently this code does not work producing the following error, Cannot marshal 'parameter #2': Cannot marshal managed types when the runtime marshalling system is disabled. According to the stack trade the error is caused by this line vertexStream.SetFloat3(i, verticies[i]);. Based on my limited understanding of C# the only way to make a non-primiative type like Float3 unmanaged would be to delve into unsafe code which is unrecommended. Am I misusing MeshAccessor.Stream, or is unsafe required, or is my entire approach floored?

I am new to both the Flax Engine and this forum so apologies in advance if I get anything wrong.

Any advice would be greatly appreciated,

Ebony

Thanks for reporting this issue. I’ll investigate it and fix the bug.

Hey, I’m back with some feedback and fixes. Few things:

  1. Typo
vertexStream.SetInt(i, indicies[i]);
// should be
indexStream .SetInt(i, indicies[i]);
  1. Use in-built utilities for faster data copies (via memcpy)
accessor.Positions = verticies;
accessor.Normals = normals;
accessor.Triangles = indicies;

Using Normals setter instead of manual loop properly sets normal vectors that are encoded from [-1;1] space to normalized [0;1] (via MeshAccessor.PackNormal). Old Mesh.UpdateMesh api operates on Float3 vectors that are pure normals/tangents, MeshAccessor is designed for more low-level memory access where different things can be encoded.

  1. Those mesh buffers were not allocated, I added check for this with a proper error message to guide users:

You need to add the following code before setting data to accessor:

var vertexLayout = new VertexElement[]
{
    new VertexElement(VertexElement.Types.Position, PixelFormat.R32G32B32_Float),
    new VertexElement(VertexElement.Types.Normal, PixelFormat.R32G32B32_Float),
};
accessor.AllocateBuffer(MeshBufferType.Index, indicies.Length, PixelFormat.R32_UInt);
accessor.AllocateBuffer(MeshBufferType.Vertex0, verticies.Length, GPUVertexLayout.Get(vertexLayout));

This tells the engine the data format and layout in memory.

  1. That error message you get was fixed in: Fix PixelFormatSampler.Write` in C# to actually work · FlaxEngine/FlaxEngine@f1b6c13 · GitHub
    (API was invalid, thanks for spotting this)

  2. Engine needs both normals and tangents for correct shading. I added utility to auto-calculate tangents based on input normals for efficiency:

// add Tangent to the layout of the vertex structure
new VertexElement(VertexElement.Types.Tangent, PixelFormat.R32G32B32A32_Float),

//  calculate tangets with utility:
accessor.ComputeTangents();

// or manually for each one with:
// RenderTools.CalculateTangentFrame(out var packedNormal, out var packedTangent, ref normal)
  1. Use compact vertex format, for example, Normal and Tangent elements can use R10G10B10A2_UNorm that is way smaller and offers good quality (engine uses it by default). In that way normals array can stay as Float3 it’s the MeshAccessor that will do all the magic of format conversion.

(need to use the latest master as I pushed some improvements to API thanks to your feedback)

1 Like