Populate geometry in rhinocommon?

rhinocommon

(Riccardo Majewski) #1

Hi all.
I need to populate a mesh with points in a c# script.
(just on the surface, not the volume)
There is a way to access the “Populate geometry” function from a script?

Thanks in advance

Riccardo


(David Rutten) #2

Sorry, that code is internal to the Vector GHa lib, but it relies mostly on classes available in Grasshopper itself.

Here’s the relevant method which slowly builds up a population over time:

    Public Function Populate(count As Int32, existing As Point3dList) As Point3dList
      If (existing Is Nothing) Then existing = New Point3dList()

      Dim box As BoundingBox = Me.BoundingBox
      box.Union(existing.BoundingBox)

      Dim tree As New Node3d(Of Point3d)(AddressOf TreeDelegates.Point3dCoordinates, box, 150)
      tree.AddRange(existing)

      Dim population As New Point3dList()
      Dim distanceThreshold As Double = Double.MaxValue

      For i As Int32 = 1 To count
        If (population.Count = 0) AndAlso (existing.Count = 0) Then
          Dim pt As Point3d = NextPoint()
          tree.Add(pt)
          population.Add(pt)
        Else
          Dim attempts As Int32 = Math.Max(50, Convert.ToInt32(Math.Sqrt(i + existing.Count)))
          Dim maxd As Double = Double.MinValue
          Dim maxp As Point3d

          For k As Int32 = 1 To attempts
            If (k > 100) AndAlso (maxd > distanceThreshold * 0.9) Then
              Exit For
            ElseIf (k > 50) AndAlso (maxd > distanceThreshold * 0.92) Then
              Exit For
            ElseIf (k > 25) AndAlso (maxd > distanceThreshold * 0.95) Then
              Exit For
            End If

            Dim pt As Point3d = NextPoint()
            Dim index As Index3d(Of Point3d) = tree.NearestItem(pt, 0.0, Double.MaxValue)

            Dim d As Double = pt.DistanceTo(index.Item)
            If (d = Double.MaxValue) Then Exit For
            If (d > maxd) Then
              maxd = d
              maxp = pt
              If (maxd > distanceThreshold * 1.25) Then Exit For
            End If
          Next

          distanceThreshold = maxd
          tree.Add(maxp)
          population.Add(maxp)
        End If
      Next
      Return population
    End Function

(Laurent Delrieu) #3

To populate a mesh you could
Make an array with the sum of faces area
S[I] = S[I-1] +area of face i
Then make random number between 0 and S[number if faces -1]
this random number will allow you to choose a face.
Then you place a random point on the face.
If needed I could make the script


(David Rutten) #4

Yes, that’s the code that goes into the NextPoint() call in my code. The rest of the logic is to avoid you place two random points too close together.


(Riccardo Majewski) #5

Thank you guys for the tips.
But still I didn’t understood… both the code supplied by David and the formula from Laurent… :sweat:

I’m working on a finite element method to simulate magnetic field and their interactions.
I was looking around for a way to uniformly populate a geometry as otherwise results are less precise.
I need a Volume populating method, not surface as I said in first post… but I was thinking of populate multiple shells done via offset…
Bad idea; now i’m thinking of some fast iteration of sphere collide after a raw populating.

I did a c# method to populate the surface of a mesh.
This is 10 times faster (or more) than “Populate Geometry” component in grasshopper, the result is less uniform in small portions, but more uniform in the overall geometry (a ready-er situation to start some loop with sphere collide).
(“Populate Geometry” seems, to me, to be influenced by vertices density in the geometry; I’ts good, indeed, way better than mine. Just… I needed a code version and this is a step to volume populating, so speed is better than local density uniformity for now).

The logic is to iterate through each face adding random points in proportion to its area… just that.


RawPopulate.gh (37.7 KB)

private void RunScript(Mesh M, int N, ref object A)
  {
    A = RawPopulate(M, N);
  }

  // <Custom additional code> 
  public List<Rhino.Geometry.Point3d> RawPopulate(Mesh M, int N){
    Rhino.Geometry.Collections.MeshFaceList faces = M.Faces;
    faces.ConvertQuadsToTriangles();
    Rhino.Geometry.Collections.MeshVertexList vertices = M.Vertices;
    // Calculating area for each face
    // code by David Rutten from https://www.grasshopper3d.com/forum/topics/what-is-the-most-efficient-way-to-convert-mesh-faces-into-breps
    List<double> areas = new List<double>(faces.Count);
    for (int i = 0; i < faces.Count; i++){
      Rhino.Geometry.MeshFace face = faces[i];
      double area = 0;
      if (face.IsTriangle){
        Point3d p0 = vertices[face.A];
        Point3d p1 = vertices[face.B];
        Point3d p2 = vertices[face.C];
        Vector3d v0 = p0 - p1;
        Vector3d v1 = p2 - p1;
        Vector3d cp = Vector3d.CrossProduct(v0, v1);
        area = 0.5 * cp.Length;
      }
      else{
        if(face.IsQuad){
          Point3d p0 = vertices[face.A];
          Point3d p1 = vertices[face.B];
          Point3d p2 = vertices[face.C];
          Point3d p3 = vertices[face.D];
          Vector3d v0 = p0 - p1;
          Vector3d v1 = p2 - p1;
          Vector3d v2 = p0 - p3;
          Vector3d v3 = p2 - p3;
          Vector3d cp0 = Vector3d.CrossProduct(v0, v1);
          Vector3d cp1 = Vector3d.CrossProduct(v2, v3);
          area = 0.5 * cp0.Length + 0.5 * cp1.Length;
        }
      }
      areas.Add(area);
    }
    Random rnd = new Random();
    List<Rhino.Geometry.Point3d> pts = new List<Rhino.Geometry.Point3d>(N);
    double totalarea = 0.999999999 * (Rhino.Geometry.AreaMassProperties.Compute(M, true, false, false, false)).Area;
    double fraction = totalarea / N;
    double resto = 0;
    for(int i = 0;i < faces.Count;i++){
      double nn = Math.Floor((areas[i] + resto) / fraction);
      for(int j = 0;j < nn;j++){
        double par0 = Math.Pow(rnd.NextDouble(), 1.5);
        double par1 = Math.Pow(rnd.NextDouble(), 1.5);
        double par2 = Math.Pow(rnd.NextDouble(), 1.5);
        double tot = par0 + par1 + par2;
        par0 = par0 / tot;
        par1 = par1 / tot;
        par2 = par2 / tot;
        Rhino.Geometry.Point3d p = M.PointAt(i, par0, par1, par2, 0.0);
        pts.Add(p);
      }
      resto = (areas[i] + resto) - nn * fraction;
    }
    return pts;
  }

Thanks again.
I’ll keep this updated.


(Laurent Delrieu) #6

Hello
it is normal that David’s component is slower as it searches to place points not too near from each others. It is like a Poisson Sampling. So recursion is involved and distance calculations which is long with many points.

Your approach is very good. I made an example with 10100 points, with a mesh with a face with a surface of 10000 and the other 100. So one must have 10 000 points and the other 100. Your code gives the exact number, mine is fluctuating between the good number. David’s script gives too much points on the little surface surely because it pushes points on the edges. No approach is perfect !

Your method is not good for the placement of point on triangle.
You must use something like that
Point3d p0 = M.Vertices[M.Faces[lastIndexFace].A];
Point3d p1 = M.Vertices[M.Faces[lastIndexFace].B];
Point3d p2 = M.Vertices[M.Faces[lastIndexFace].C];

  double r1 = rnd.NextDouble();
  double r2 = rnd.NextDouble();
  if ((r1 + r2) > 1)
  {
    r1 = 1.0 - r1;
    r2 = 1.0 - r2;
  }
  pts.Add(p0 * (1 - r1 - r2) + p1 * r1 + p2 * r2);

See this :
http://mathworld.wolfram.com/TrianglePointPicking.html

Here your placement

RandomPopulateMesh_LD.gh (18.9 KB)


(Daniel Piker) #7

If you’re actually distributing points in a volume, here’s a way to do it with sphere packing:
populate_volume.gh (11.8 KB)

and another quick way to fill a volume with points, making sure none are closer than some distance apart is to use a random distribution to start with, then cull duplicates with a distance tolerance:
populate_volume2.gh (22.1 KB)