Create Mesh Vertices/Faces via unsafe access

Hey developers!

We can modify existing mesh vertices in an unsafe way:

Point3d[] pointsArray = new Point3d[countX * countY];
unsafe
{
    using (var meshAccess = mesh.GetUnsafeLock(true))
    {
        Point3d* startOfVertexArray = meshAccess.VertexPoint3dArray(out int arrayLength);
        Parallel.For(0, arrayLength, i =>
        {
            (startOfVertexArray + i)->X = pointsArray[i].X;
            (startOfVertexArray + i)->Y = pointsArray[i].Y;
            (startOfVertexArray + i)->Z = pointsArray[i].Z;
        });
        mesh.ReleaseUnsafeLock(meshAccess);
    }
}

This works fine when our mesh already contains vertices.

Is it possible to add vertices and/or faces to an empty mesh without using the following methods?

mesh.Vertices.AddVertices(pointsArray);
mesh.Faces.AddFaces(facesArray);

I am trying to squeeze out maximum performance here and understand the risks involved.

1 Like

Would it maybe work if you set the MeshVertexList.Capacity first, then perform the unsafe operation?

Also, what happens when the point3d array gets garbage collected, or moved by the garbage collector? You may want to use a GCHandle to prevent the GC from pulling the rug from under your mesh.
Edit: sorry I read your code wrong, you’re assigning values in your unsafe bit.

MeshVertexList has settable Capacity and Count properties. Make sure you understand if you want to set the 3F or the 3D array. I’d go with setting 3D. Also, I suggest you time this. With unmanaged access, I doubt that this version of Parallel.For does much difference, or even, good.

Setting the correct Count value was the missing bit. Thanks @piac!

For whatever reason I can’t access the 3D array:

            Rhino.Geometry.Mesh mesh = new Rhino.Geometry.Mesh();
            mesh.Vertices.UseDoublePrecisionVertices = true;
            mesh.Vertices.Capacity = countX * countY;
            mesh.Vertices.Count = pointsArray.Length;

calling Point3d* startOfVertexArray = meshAccess.VertexPoint3dArray(out int arrayLength); returns an error that this mesh doesn’t use double precision vertices.

@mrhe sorry I’m AFK, but I think you have to have at least one vertex before switching.

Yep, adding a dummy Point3d does the job. mesh.Vertices.UseDoublePrecisionVertices = true; doesn’t seem to do anything.

With unmanaged access, I doubt that this version of Parallel.For does much difference, or even, good

You were right, for approx. 1M verts, Parallel.For doesn’t come with a measurable speed gain.

Final code if someone needs it:

            Rhino.Geometry.Mesh mesh = new Rhino.Geometry.Mesh();
            mesh.Vertices.Add(new Point3d()); // add dummy vertex to allow for double precision vertices in Rhino mesh
            mesh.Vertices.Capacity = pointsArray.Length;
            mesh.Vertices.Count = pointsArray.Length;

            mesh.Faces.Capacity = (countX - 1) * (countY - 1);
            mesh.Faces.Count = (countX - 1) * (countY - 1);

            unsafe
            {
                using (var meshAccess = mesh.GetUnsafeLock(true))
                {
                    Point3d* startOfVertexArray = meshAccess.VertexPoint3dArray(out int vertexArrayLength);
                    for (int i = 0; i < vertexArrayLength; i++)
                    {
                        (startOfVertexArray + i)->X = pointsArray[i].X;
                        (startOfVertexArray + i)->Y = pointsArray[i].Y;
                        (startOfVertexArray + i)->Z = pointsArray[i].Z;
                    }

                    MeshFace* startOfFacesArray = meshAccess.FacesArray(out int facesArrayLength);
                    for (int i = 0, j = 0; i < facesArrayLength; i++, j += 4)
                    {
                        (startOfFacesArray + i)->A = faces[j];
                        (startOfFacesArray + i)->B = faces[j + 1];
                        (startOfFacesArray + i)->C = faces[j + 2];
                        (startOfFacesArray + i)->D = faces[j + 3];
                    }
                    mesh.ReleaseUnsafeLock(meshAccess);
                }
            }
1 Like

Since the thread is specifically tagged windows and you’re willing to do things marked unsafe, if you’re working with 1M+ vertex meshes and really need performance, is there any chance of calling a C++ library to do a big memory copy instead of running your point by point for loop?

Edit: I forgot about
Buffer.MemoryCopy Method (System) | Microsoft Learn

Good point @Nathan_Bossett!

I tested it with the faces array - since the input data was already correctly laid out - and it runs at the same speed:

                    MeshFace* startOfFacesArray = meshAccess.FacesArray(out int facesArrayLength);
                    fixed (int* ptr = faces)
                    {
                        Buffer.MemoryCopy(ptr, startOfFacesArray, facesArrayLength * 4 * sizeof(int), faces.Length * sizeof(int));
                    }

[EDIT]
The whole thing looks like this:

            unsafe
            {
                using (var meshAccess = mesh.GetUnsafeLock(true))
                {
                    Point3d* startOfVertexArray = meshAccess.VertexPoint3dArray(out int vertexArrayLength);
                    MeshFace* startOfFacesArray = meshAccess.FacesArray(out int facesArrayLength);
                    fixed (Point3d* pointsPointer = pointsArray)
                    fixed (int* facesPointer = faces)
                    {
                        Buffer.MemoryCopy(pointsPointer, startOfVertexArray, vertexArrayLength * sizeof(Point3d), pointsArray.Length * sizeof(Point3d));
                        Buffer.MemoryCopy(facesPointer, startOfFacesArray, facesArrayLength * sizeof(MeshFace), faces.Length * sizeof(int));
                    }

                    mesh.ReleaseUnsafeLock(meshAccess);
                }
            }

@piac, is there a reason why the VertexColorsArray is not exposed in unmanaged access? We can modify vertices, vertex normals, and faces but not colors.

Is “Nobody asked” a good excuse? I’ve added a wish at RH-77392.
Also face normals, surface coordinates could probably get the same treatment.

2 Likes

@mrhe, the next release of Rhino 8 BETA will contain a function to access VertexColorsArray and FaceNormals.

Thanks,

Giulio

–
Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com

3 Likes

Thanks @piac!

That was fast :slight_smile:

1 Like