mrhe
(Mariusz Hermansdorfer)
September 27, 2023, 1:01pm
1
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
menno
(Menno Deij - van Rijswijk)
September 27, 2023, 3:52pm
2
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.
piac
(Giulio Piacentino)
September 27, 2023, 5:21pm
3
Mariusz Hermansdorfer:
mesh
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.
mrhe
(Mariusz Hermansdorfer)
September 27, 2023, 5:32pm
4
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.
piac
(Giulio Piacentino)
September 27, 2023, 5:58pm
5
@mrhe sorry I’m AFK, but I think you have to have at least one vertex before switching.
mrhe
(Mariusz Hermansdorfer)
September 27, 2023, 6:37pm
6
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
mrhe
(Mariusz Hermansdorfer)
September 27, 2023, 7:24pm
8
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);
}
}
mrhe
(Mariusz Hermansdorfer)
September 28, 2023, 7:17am
9
@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.
piac
(Giulio Piacentino)
September 28, 2023, 5:54pm
10
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
piac
(Giulio Piacentino)
October 4, 2023, 8:49am
11
@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