Adding points to RhinoCommon pointcloud

I have 1500 000 doubles that I am converting to Points xyz coordinates to add to a PointCloud, same for normals and colors.

Can someone explain me why this takes 0.24 sec:

        int cc = 0;
        for (int i = 0; i < P.Length; i += 3) {
            newCloud.Add( new Point3d(P[i + 0], P[i + 1], P[i + 2])            );
            newCloud[cc].Color = System.Drawing.Color.FromArgb((int)C[i  + 0], (int)C[i + 1], (int)C[i  + 2]);
            cc++;
        }

        cc = 0;
        for (int i = 0; i < P.Length; i += 3) {
            newCloud[cc].Normal = new Vector3d(N[i + 0], N[i + 1], N[i + 2]);
            cc++;
        }

And this 5 sec:

        int cc = 0;
        for (int i = 0; i < P.Length; i += 3) {
            newCloud.Add(  new Point3d(P[i + 0], P[i + 1], P[i + 2])      );
            newCloud[cc].Normal = new Vector3d(N[i + 0], N[i  + 1], N[i + 2]);
            newCloud[cc].Color = System.Drawing.Color.FromArgb((int)C[i  + 0], (int)C[i + 1], (int)C[i  + 2]);
            cc++;
        }

Hi @Petras_Vestartas,

It’s all about memory allocation (and reallocation) for the internal arrays that make up the point cloud.

To be most efficient, use this version of PointCloud.Add, or better yet use this version of PointCloud.AddRange.

– Dale

2 Likes

Thanks,

The first one took 5 sec as well.
I ll try the second one tomorrow.

I imagine Pointcloud is allocated in C++ not .Net?
Every time I get element by index, does rhino interop with c++?

Yes. Use the overload of Add that adds the location, color, and normal all at once. You should see an improvement in speed.

Dale is right that AddRange could even give you faster results, but the simple change to use a single Add should help a lot.

You can see what is happening here

There is no improvement, something wicked is with normal with that constructor (position, normal, color) same 5 sec.

I ll try addrange, I hope it will be a bit faster than 0.2 sec.

Is it also possible to somehow get around this problem? :

  1. I get pointcloud in grasshopper
  2. convert positions, normals, colors to arrays of doubles
  3. I pass these arrays via Pinvoke to C++
  4. In C++ I construct 3rd party library pointcloud and do something with it
  5. I again convert points, normals, colors to double arrays to Pinvoke
  6. In C# I construct Pointcloud in Rhino .NET which is again sends data to Rhino C++.

What are you doing else to avoid 4 times conversion? Is there any better practices for PInvoke? Simply looping in .Net through pointcloud is very slow even with Parallel loops.

Is it possible to somehow construct Rhino Pointcloud in C++ and share it with .NET to avoid looping through the same points?

Writing your own pInvoke sounds like way too much work. If you have a sample that we can repeat this with, we should be able to figure out what is going wrong and make improvements

I will share the code tomorrow to make things clearer;)

Dear @stevebaer and @dale this is my source code, do you think it is possible to avoid conversion to doubles and directly pass C# PointCloud to C++ ? (Let me know if you need compiled dll, I can add it too)

The pointcloud processing methods I am using is fast and the main bottleneck is this data transfer via PInvoke.

Headers

C#:

    [DllImport(dllNameOpen3D, CallingConvention = CallingConvention.Cdecl)]
    internal static extern int Open3DDownsample(

      [MarshalAs(UnmanagedType.LPArray)] double[] p, ulong p_c,
      [MarshalAs(UnmanagedType.LPArray)] double[] n, ulong n_n,
      [MarshalAs(UnmanagedType.LPArray)] double[] c, ulong c_c,

      int numberOfPoints,

      ref IntPtr p_o, ref int p_c_o,
      ref IntPtr n_o, ref int n_n_o,
      ref IntPtr c_o, ref int c_c_o
  );

C++:

PINVOKE void Open3DDownsample (

    double* p, size_t p_c,
    double* n, size_t n_c,
    double* c, size_t c_c,

    int numberOfPoints,

    double*& p_o, int& p_c_o,
    double*& n_o, int& n_c_o,
    double*& c_o, int& c_c_o
);

Implementations:

C# :

public static PointCloud Downsample(PointCloud cloud, int numberOfPoints = 5000) {

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Conversion from Rhino PointCloud to Flat Array
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////




    double[] p = new double[cloud.Count * 3];
    ulong p_c = (ulong)cloud.Count;

    double[] n =new double[cloud.Count * 3] ;
    ulong n_c = p_c;

    double[] c = new double[cloud.Count * 3];
    ulong c_c = p_c;

    bool hasColors = cloud.ContainsColors;

    System.Threading.Tasks.Parallel.For(0, cloud.Count, i =>
    {
    //for (int i = 0; i < cloud.Count; i++) {
        p[i * 3 + 0] = cloud[i].Location.X;
        p[i * 3 + 1] = cloud[i].Location.Y;
        p[i * 3 + 2] = cloud[i].Location.Z;
        if (cloud.ContainsNormals) {
            n[i * 3 + 0] = cloud[i].Normal.X;
            n[i * 3 + 1] = cloud[i].Normal.Y;
            n[i * 3 + 2] = cloud[i].Normal.Z;
        }
        if (cloud.ContainsColors) {
            c[i * 3 + 0] = cloud[i].Color.R;
            c[i * 3 + 1] = cloud[i].Color.G;
            c[i * 3 + 2] = cloud[i].Color.B;
        }
   
    });


    //watch.Stop();
   // Rhino.RhinoApp.WriteLine("Convert PointCloud to double array "+watch.ElapsedMilliseconds.ToString());


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Call C++
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    IntPtr p_pointer = IntPtr.Zero;
    int p_pointer_c = 0;
    IntPtr n_pointer = IntPtr.Zero;
    int n_pointer_c = 0;
    IntPtr c_pointer = IntPtr.Zero;
    int c_pointer_c = 0;


    UnsafeOpen3D.Open3DDownsample(
        p, p_c,
        n, n_c,
        c, c_c,
        numberOfPoints,
        ref p_pointer, ref p_pointer_c,
        ref n_pointer, ref n_pointer_c,
        ref c_pointer, ref c_pointer_c
        );

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Convert Pointers to double arrays
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Convert faceIndicesPointer to C# int[]


    double[] P = new double[p_pointer_c ];
    Marshal.Copy(p_pointer, P, 0, P.Length);

    double[] N = new double[n_pointer_c ];
    Marshal.Copy(n_pointer, N, 0, P.Length);

    double[] C = new double[c_pointer_c ];
    Marshal.Copy(c_pointer, C, 0, P.Length);


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Release C++ memory
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    UnsafeOpen3D.ReleaseDouble(p_pointer, true);
    UnsafeOpen3D.ReleaseDouble(n_pointer, true);
    UnsafeOpen3D.ReleaseDouble(c_pointer, true);


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Convert C++ to C# Pointcloud
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    PointCloud newCloud = new PointCloud();


    for (int i = 0; i < P.Length; i += 3) {
        newCloud.Add(new Point3d(P[i + 0], P[i + 1], P[i + 2]));

        if (C[i + 0] == 0 && C[i + 1] == 0 && C[i + 2] == 0) continue;
        newCloud[(int)Math.Round((i / 3.0))].Color = System.Drawing.Color.FromArgb((int)C[i + 0], (int)C[i + 1], (int)C[i + 2]);

    }


    for (int i = 0; i < P.Length; i += 3) {
        if (N[i + 0] == 0 && N[i + 1] == 0 && N[i + 2] == 0) continue;
        newCloud[(int)Math.Round((i / 3.0))].Normal = new Vector3d(N[i + 0], N[i + 1], N[i + 2]);
    }




    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Output
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    return newCloud;

}  

C++:

PINVOKE void Open3DDownsample (
double* p, size_t p_c, 
double* n, size_t n_c, 
double* c, size_t c_c,

int numberOfPoints,

double*& p_o, int& p_c_o, 
double*& n_o, int& n_c_o, 
double*& c_o, int& c_c_o) {
 

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Convert Input to Open3D PointCloud
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
shared_ptr<PC> Open3DCloud (new PC);

Open3DCloud->points_.resize (p_c);
Open3DCloud->normals_.resize (p_c);
Open3DCloud->colors_.resize (p_c);



for ( size_t i = 0; i < p_c; i++ ) {

    Open3DCloud->points_[i] = Eigen::Vector3d (p[3 * i + 0], p[3 * i + 1], p[3 * i + 2]);
    Open3DCloud->normals_[i] = Eigen::Vector3d (n[3 * i + 0], n[3 * i + 1], n[3 * i + 2]);
    Open3DCloud->colors_[i] = Eigen::Vector3d (c[3 * i + 0], c[3 * i + 1], c[3 * i + 2]);

}








////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Run Open3D Method
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int nOfPoints = numberOfPoints;
int b = (int)(Open3DCloud->points_.size () * (1.0 / numberOfPoints *1.0));
int nth = max (1,b );

Open3DCloud = Open3DCloud->UniformDownSample (nth);



////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Run Open3D Method
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

p_c_o = Open3DCloud->points_.size ()*3;
p_o = new double[p_c_o];

n_c_o = Open3DCloud->normals_.size ()*3 ;
n_o = new double[n_c_o];

c_c_o = Open3DCloud->colors_.size ()*3 ;
c_o = new double[c_c_o];

int i = 0;
for ( auto& p : Open3DCloud->points_ ) {
    p_o[i++] = p.x ();
    p_o[i++] = p.y ();
    p_o[i++] = p.z ();


}

i = 0;
for ( auto& p : Open3DCloud->normals_ ) {
    n_o[i++] = p.x ();
    n_o[i++] = p.y ();
    n_o[i++] = p.z ();


}

i = 0;
for ( auto& p : Open3DCloud->colors_ ) {
    c_o[i++] = p.x ();
    c_o[i++] = p.y ();
    c_o[i++] = p.z ();

}

}

Something is just wrong with Add and AddRange constructor when 3 values are given, look at the performance below for 5000 points for creating a pointcloud in .NET. And yes I refereshed several times components to get a correct time stamp:

I’m sorry, this one slipped through the cracks. We need to add functionality to our PointCloud class to directly expose the underlying arrays in order to give you much faster access. We have already written this type of functionality on the Mesh class so it shouldn’t be too hard to do.
https://mcneel.myjetbrains.com/youtrack/issue/RH-64318

Thank you. Would it be possible to update me after this addition?

Yes; this post is referenced in our bugtracking system. When that is done you should see a post from Brian here that mentions this issue being fixed

1 Like