Access "MeshPoint.T" returned by "rg.Mesh.ClosestMeshPoint()" Fast

Hi,

i am working with Meshes and Points, where I need to determine in which Element (FaceIndex) a Point is located. This is realised with the Mesh/Analysis/Mesh Closest Point Component of Grasshopper.

The Component returns 3 values.

  1. Closest Mesh Point (in my 2D case the Query Point is the Closest Point as all Points are in the Mesh)
  2. Index (the Element the Query Point is located in)
  3. Parameter as List of Strings of the following anatomy: $FaceIndex[$a;$b;$c;$d]

I am interested in the variables $a, $b, $c, $d and came up with 4 different variants to access the values. However none of them seem to be satisfying as for my application speed is crucial.

The Grasshopper Component can query 5000 Points against a Mesh with 5600 Faces in 16ms.

Variant 1:
Parsing of the Parameter variable of the Grasshopper Component. This is - as expected - the slowest method and also the least accurate, as the Parameter seems to be truncated with regard to precision.

Variant 2:
Basically this piece of code:

import Rhino.Geometry as rg

MeshPoint = rg.Mesh.ClosestMeshPoint(Mesh,Point,0)
a,b,c,d = MeshPoint.T[0], MeshPoint.T[1], MeshPoint.T[2], MeshPoint.T[3]

Variant 3:
equivalent to Variant 2 but SDK Mode and compiled.

Variant 4:
C# Version of Variant 2. I expected this to be the fastest, but to be honest I am not proficient at C# at all, so this was the slowest.



  • I’d like to know, how the MeshPoint.T[i] values can be accessed in approximately the same runtime as the Grasshopper Component (16ms on my hardware) is able to. My closest implementation is 5 times slower. Please find the *.gh with internalized data attached.

  • Could the way the Parameter value is formated be changed in the future? I do not see any benefit of returning a string and the FaceIndex is also redundant. Or am I missing something?

Thanks.
Dan

mwe_MeshPointParameter.gh (157.1 KB)


Rhino 7 SR29 2023-4-17 (Rhino 7, 7.29.23107.03001, Git hash:master @ 5f05ef3a2eea3d910c4c2fb79ccc75b413d33ca7)

I haven’t inspected the file yet (on my phone), but try to implement these performance tips if you haven’t already:

And if it’s still too slow you can consider threading the code, see a recent example here:

1 Like

If you make it a native component it should go much faster, but like your compiled python component, you need to ship it with the gh file for it to work on other machines.

1 Like

This is when native, and could probably be optimized with list input instead of item.

Let me know if you want the file, I don’t really know how to share these ‘native component’ files.

The file would be great. Even better would be if you could explain to me how to get to “native” grasshopper components. Here, unlike with Python, there is no possibility to compile via the editor.

I assume that I need Visual Studio for this, as described here: Project Setup Guide (C#) or is there a simpler variant?

I think this is the best high level tutorial to dive in this topic: parametricCamp

He explains everything from setup to development workflows and (now) more complex async components.

anyway here is the main functions for the native component, which is basically what you’ve used but within the ‘c# grasshopper plugin’ template.

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            // add input parameters.
            pManager.AddPointParameter("Point", "P", "Point to test", GH_ParamAccess.item);
            pManager.AddMeshParameter("Mesh", "M", "Mesh to test", GH_ParamAccess.item);
        }


        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            pManager.AddNumberParameter("a", "a", "barycentric a", GH_ParamAccess.list);
            pManager.AddNumberParameter("b", "b", "barycentric b", GH_ParamAccess.list);
            pManager.AddNumberParameter("c", "c", "barycentric c", GH_ParamAccess.list);
            pManager.AddNumberParameter("d", "d", "barycentric d", GH_ParamAccess.list);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            // get the point and mesh from the input parameters
            Point3d point = new Point3d();
            Mesh mesh = new Mesh();

            if (!DA.GetData(0, ref point)) return;
            if (!DA.GetData(1, ref mesh)) return;

            MeshPoint mp = mesh.ClosestMeshPoint(point, 0.0);

            // set the output parameters
            DA.SetData(0, mp.T[0]);
            DA.SetData(1, mp.T[1]);
            DA.SetData(2, mp.T[2]);
            DA.SetData(3, mp.T[3]);

        }
1 Like

In case you’re still wondering about Python solutions, the tips I pointed to above also help quite a bit. Or at least dropping type hints and wrapping the output in Grasshopper types did:


230613_ClosestMeshPoints_Anders_0.gh (154.5 KB)

1 Like

the c# variant is not the slowest - it actually processes in about 1.5x time of the native GH. When you are editing the code, the compile time is added to the ms measurement. Thats why it shows 200+ ms. Recompute the GH solution to obtain the real values. (23 ms on my machine)

In any case - you can process the input in C# as list - this makes it a bit faster:
mwe_MeshPointParameter.gh (157.9 KB)

As far as native plugins / compiling goes - don’t expect wonders - the current c# JIT compiler does a much better job than in earlier times… If you are lucky you might get double the performance.

You can also use Multithreading - but then you need to keep track of the indices, as the concurrentbag does not: mwe_MeshPointParameter_para+.gh (155.6 KB) This gets the time down to 12ms - the gains will be greater with bigger datasets.

One more thing: Depending on the GH / Rhino Version - you might want to triangulate your mesh always, as the MeshClosestPoint Method has some nasty bugs with quads. Not sure if they are fixed yet, but its always better because you dont have to keep the logic for both cases (tri and quad)

1 Like

I just checked and you are right. I only checked the corners that were always (1,0,0,0) and the sum of all 4 parameters, which were always 1. Probably need to work on a different approach.

I would question the use of Grasshopper then. Python has a reputation of being slow, but GH is probably worse with the GUI overhead. What sort of data are you starting with? A mesh and points could be treated with scientific libraries like Numpy for example. Or even a C program if you want to optimize further.

While that is true, the two solutions on the right are still the fastest that have been uploaded so far:


230615_ClosestMeshPoints_Anders_0.gh (148.4 KB)

We probably need more explicit metrics/requirements/context to advice further. ~25 ms has certainly never been a bottleneck in my pipelines :man_shrugging:

Oh sure don’t get me wrong, Python is great for the kind of things we do in Rhino and a well-written script can be perfectly acceptable.

Looking at the question, I was just wondering how much data needs to be processed. If the amount of data is huge, then performance can be an issue and the tools available in GH are probably not best suited.

If the amount of data is not huge then performance is not so much of an issue and the question of waiting a second or two may be less relevant.

Just thought it was worth throwing a curve ball in!

Maybe @dn.aur can shed a bit more light on the performance requirements and why the solutions he’s found so far are not good enough?

1 Like