C# Welzl's Minimum Bounding Sphere

Hi,

I have been working on Welzl’s algorithm for smallest enclosing circles and spheres. I have been able to write a working C# script for the 2D case (circle), but have been having a lot of trouble extending that into 3D for the sphere.

I am not looking for approximations for the minimum bounding sphere, I already have that, I am looking for the exact minimum bounding sphere using Welzl’s algorithm, since that should be able to compute in linear time.

Here’s a result for the 2D case using the code I have for that:
welzlcircle
Note that it is tangent to 3 points, uniquely defining a circle.

What I get from my current best code for spheres, when it works:
welzlsphere3pts
What I usually get:
welzlsphereFail

It seems the biggest issue I have is that it fails when the sphere must be defined by 4 points. It appears to work when it is defined by 1 point (a 0 radius sphere centered on the point), 2 points (a sphere where the center is the midpoint between the points), and 3 points (a sphere with the same center and radius as the circumcircle of the points), but fails when it is to be defined by 4 points.

My latest attempt has been based on this webpage: https://www.gamedev.net/tutorials/programming/graphics/welzl-r2484/ but hasn’t worked out yet.

I find this hard to debug due to the recursiveness of the code, hopefully someone here can help me out! I am attaching all of my code for the 2D and 3D attempts I have made so far.

WelzlforForum.gh (16.5 KB)

3 Likes

James, I had a similar problem, I solved it with the help of geometry, iterating over the circles, given by all pairs and triples of points on the plane. This definition worked very slowly.

As a result, I began to master with с sharp))

Maybe these links will be useful to you

Have you tried this task through the grasshopper plugin? Moving the code to visual studio?

1 Like

Hi Alex,

Thanks for bringing this thread back from the brink of death and for your links.

If what you’ve done is brute force the min circle problem, you should download my definition and take a look at my working 2D welzl’s algorithm. It’s a lot faster than brute force trying all triplet combinations of points.

I have not tried it with Grasshopper since my purpose is to create a compiled C# component, and I don’t think this recursive algorithm would even be possible in plain GH anyways.

I do code it in Visual studio. The debugger hasn’t really helped me figure out whats going wrong. I just put the code into C# components in a GH definition so I could share it on the forum easily.

Maybe I’ll be able to get some ideas from your miniball link.
Thanks

1 Like

James, Thank you very much for your node, I am actively using it!
As I understand it, the node works in global coordinates and the minimum circles can be found only in the xy plane?
If we try to find the minimum circle for points lying in space on an arbitrary plane, then we still get a circle on the xy plane.
Maybe you can add the ability to add a local plane for each group of points to the code? We will all be infinitely grateful to you!

similar to this node for radial sort

WelzlforForum_plane.3dm (30.0 KB)
WelzlforForum_plane.gh (19.8 KB)
Radial Sort.ghuser (3.8 KB)

i took the liberty to add the plane parameter.

WelzlforForum_plane_fixed.gh (20.9 KB)

1 Like

rgr, Thank you very much, rays of gratitude !!!

Do I understand correctly that you do not need to submit planes to the nodes in the form of a list? do you need through an item?
And it turns out that the final circle is not much offset above or below the initial plane?

I’m not sure what you did exactly as I haven’t taken the time to really read and understand the changes to the code you made, but your changes do not work. I suspect it is an error in the MakeCircumcircle() function, but might be earlier, I didn’t study your changes closely.

Here is an example of your version using the YZ plane, you can clearly see it is not finding the minimum bounding circle. It should touch at least 2 points, likely 3, but only touches one, thus it is not the minimum.

I wrote this code a bit ago and I should update it to take planes I guess, but for right now what anyone who wants to can do is use the orient component to orient your points on a different plane to the XY plane, then orient the circle back. This will allow you to use my code for points with any plane. I will attach a gh file that shows this.

@AlexWer use this method for planes other than XY for now. I promise to revisit this code in the next couple months. I also plan to release a plugin which solves many tricky computational geometry problems such as mincircles, spheres, oriented bounding boxes, oriented bounding rectangles, largest empty circles/spheres, quickhull3D, minimum spanning trees, nearest neighbor graphs, etc etc.

WelzlforForum_planeByORIENT.gh (9.5 KB)

PS: Still looking for any help implementing Welzl’s Algorithm in 3D! Thanks everyone

Hello
after some attempts, here is a not too bad script to compute the Minimum Bounding Sphere. I put 2 approaches for computing this.
One is incremental, it take 2 points to make a first sphere, then try to add another point …Random points is a way to fasten calculation, as calculations are long just one one point is outside bounding sphere. This algorithm also outputs points used to make the sphere.

It take on my PC 0.48 s for 35 000 points (no random) 0.3 s if random points.

The other approach uses Welzl method, with a part of the script from there


BEWARE it crashes rhino if more than 300 points (recursions ?) are chosen.
This site seems more clean

One of the flaws of most algorithm description is that they used circumscribed circle (3 points) or sphere (4 points). On many cases it is not the minimum Bounding Sphere. You’ll see in my script that I used these methods but inside method that compute Minimum Bounding Sphere, for 1, 2, 3, 4 or 5 points.

bounding_sphere_LEGACY.gh (1.4 MB)

private void RunScript(List<Point3d> lst_points, bool random, ref object A, ref object B, ref object C, ref object D)
  {
    //Laurent Delrieu
    //18/12/2020
    //https://discourse.mcneel.com/t/c-welzls-minimum-bounding-sphere/109246/7

    // Use this one if you want to use Welzl method but beware there is a bug somewhere
    //Don't put more than 300 points
    //  A = MinimumBoundingSphereWelzl.GetMinimumBoundingSphere(lst_points);
    //  B = lst_points;
    List<Point3d> boundary_points;
    C = MinimumBoundingSphereIncrement.GetMinimumBoundingSphere(lst_points, out boundary_points, 1000000, random);
    D = boundary_points;
    Component.Message = "Minimum Bounding Sphere";
  }

  // <Custom additional code> 


  /// <summary>
  /// Class to calculate the minimum bounding sphere
  /// it takes the first and second points to make a sphere then add one point at each increment
  ///Laurent Delrieu, 18/12/2020
  /// </summary>
  public static class  MinimumBoundingSphereIncrement
  {
    //Tolerance to be inside the sphere
    static double epsilon = 1e-12;
    /// <summary>
    ///
    /// </summary>
    /// <param name="points">List of points</param>
    /// <param name="boundaryPoints">Points that are used to make the sphere</param>
    /// <param name="nMax">Max number of iterations, must be suppressed in future release</param>
    /// <returns>Minimum sphere</returns>
    public  static Sphere GetMinimumBoundingSphere(List<Point3d> points, out List<Point3d> boundaryPoints, int nMax, bool random)
    {
      Random rnd = new Random();
      boundaryPoints = new List<Point3d>();
      List<Point3d> allPoints = new List<Point3d>(points);

      Sphere sphere = Sphere.Unset;
      if (allPoints.Count == 1)
      {
        sphere = new Sphere(allPoints[0], 0);
        boundaryPoints.Add(allPoints[0]);
        allPoints.RemoveAt(0);
      }
      else if (points.Count > 1)
      {
        sphere = Sphere2Points(allPoints[0], allPoints[1]);
        boundaryPoints.Add(allPoints[0]);
        boundaryPoints.Add(allPoints[1]);

        allPoints.RemoveAt(1);
        allPoints.RemoveAt(0);
      }

      int count = 0;
      if (random)
      {
        while (allPoints.Count > 0 && count < nMax)
        {
          int next = rnd.Next(allPoints.Count);
          sphere = AddPoint(sphere, ref boundaryPoints, allPoints[next]);
          allPoints.RemoveAt(next);

          count++;
        }}
      else
      {

        while (allPoints.Count > 0 && count < nMax)
        {
          sphere = AddPoint(sphere, ref boundaryPoints, allPoints[0]);
          allPoints.RemoveAt(0);

          count++;
        }
      }
      return sphere;
    }

    /// <summary>
    /// Add a point to the minimum sphere and update boundary points
    /// </summary>
    /// <param name="initialSphere">Initial bounding sphere</param>
    /// <param name="boundaryPoints">List of boundary points as input and output</param>
    /// <param name="newPoint">New point to add</param>
    /// <returns>Updated or not minimum bounding sphere</returns>
    public static Sphere AddPoint(Sphere initialSphere, ref List<Point3d> boundaryPoints, Point3d newPoint)
    {
      //If new point is inside sphere
      Sphere output = initialSphere;
      //New Point is not on the sphere
      if (newPoint.DistanceTo(output.Center) > output.Radius)
      {
        // Print(" Ajout d'un point" + newPoint);
        boundaryPoints.Add(newPoint);
        output = Sphere5Points(boundaryPoints);

        for (int i = boundaryPoints.Count - 2; i >= 0; i--)
        {
          if (ContainsStrict(output, boundaryPoints[i], epsilon))
          {
            // Print(" Suppression : " + boundaryPoints[i]);
            boundaryPoints.RemoveAt(i);
          }
        }
        //Print(" Boundary cout = " + boundaryPoints.Count);
        if (boundaryPoints.Count >= 5)
        {
          boundaryPoints.RemoveAt(0);
        }
      }
      return output;
    }
  }



  /// <summary>
  /// Minimum bounding sphere of a maximum of 5 points
  /// </summary>
  /// <param name="lst_points">List of points, no more than 5 points</param>
  /// <returns>Bounding sphere</returns>
  public static Sphere Sphere5Points(List<Point3d> lst_points)
  {
    //Default output
    Sphere sphere = Sphere.Unset;
    //If there is 0, 1, 2 3 or 4 points
    if (lst_points.Count <= 4)
    {
      switch (lst_points.Count)
      {
        case 0: return Sphere.Unset;
        case 1: return new Sphere(lst_points[0], 0);
        case 2: return Sphere2Points(lst_points[0], lst_points[1]);
        case 3: return Sphere3Points(lst_points[0], lst_points[1], lst_points[2]);
        case 4: return Sphere4Points(lst_points[0], lst_points[1], lst_points[2], lst_points[3]);
      }
    }
    //All poissible spheres around the 4 among the 5 points
    Sphere sphere0 = Sphere4Points(lst_points[1], lst_points[2], lst_points[3], lst_points[4]);
    Sphere sphere1 = Sphere4Points(lst_points[0], lst_points[2], lst_points[3], lst_points[4]);
    Sphere sphere2 = Sphere4Points(lst_points[0], lst_points[1], lst_points[3], lst_points[4]);
    Sphere sphere3 = Sphere4Points(lst_points[0], lst_points[1], lst_points[2], lst_points[4]);
    Sphere sphere4 = Sphere4Points(lst_points[0], lst_points[1], lst_points[2], lst_points[3]);

    //Spheres that contains all 4 points
    List<Sphere> lst_spheres = new  List<Sphere>();
    if (Contains(sphere0, lst_points[0])) lst_spheres.Add(sphere0);
    if (Contains(sphere1, lst_points[1])) lst_spheres.Add(sphere1);
    if (Contains(sphere2, lst_points[2])) lst_spheres.Add(sphere2);
    if (Contains(sphere3, lst_points[3])) lst_spheres.Add(sphere3);
    if (Contains(sphere4, lst_points[4])) lst_spheres.Add(sphere4);

    //We keep the minimum sphere
    if (lst_spheres.Count > 0)
    {
      sphere = lst_spheres[0];
      for (int i = 1; i < lst_spheres.Count; i++)
      {
        if (lst_spheres[i].Radius < sphere.Radius)
        {
          sphere = lst_spheres[i];
        }
      }
    }
    return sphere;
  }




  /// <summary>
  /// Class to compute the minimum bounding sphere with Welzl method
  /// BEWARE if more than 300 points, there is a bug somewhere
  /// https://gamedev.stackexchange.com/questions/162731/welzl-algorithm-to-find-the-smallest-bounding-sphere
  /// </summary>
  public static class  MinimumBoundingSphereWelzl
  {
    public static List<Point3d> _boundaryPoints = null;
    static Random random = new Random();

    /// <summary>
    /// Main method to compute the Minimum Bounding Sphere using Welzl method
    /// </summary>
    /// <param name="points">List of points</param>
    /// <returns>Minimum Bounding Sphere</returns>
    public  static Sphere GetMinimumBoundingSphere(List<Point3d> points)
    {
      if (_boundaryPoints == null)
      {
        _boundaryPoints = new List<Point3d>(4);
      }
      else
      {
        _boundaryPoints.Clear();
      }
      return BallWithBounds(points);
    }

    /// <summary>
    /// Recursive method to calculate a minimum Bounding Sphere
    /// </summary>
    /// <param name="contained"></param>
    /// <returns></returns>
    private static Sphere BallWithBounds(List<Point3d> contained)
    {
      if (contained.Count == 0 || _boundaryPoints.Count == 4)
      {
        switch (_boundaryPoints.Count)
        {
          case 0: return Sphere.Unset;
          case 1: return new Sphere(_boundaryPoints[0], 0);
          case 2: return Sphere2Points(_boundaryPoints[0], _boundaryPoints[1]);
          case 3: return Sphere3Points(_boundaryPoints[0], _boundaryPoints[1], _boundaryPoints[2]);
          case 4: return Sphere4Points(_boundaryPoints[0], _boundaryPoints[1], _boundaryPoints[2], _boundaryPoints[3]);
        }
      }
      int last = contained.Count - 1;
      int removeAt = random.Next(0, contained.Count);

      Point3d removed = contained[removeAt];
      contained[removeAt] = contained[last];
      contained.RemoveAt(last);

      var ball = BallWithBounds(contained);

      if(!Contains(ball, removed))
      {
        _boundaryPoints.Add(removed);
        ball = BallWithBounds(contained);
        _boundaryPoints.RemoveAt(_boundaryPoints.Count - 1);
      }

      contained.Add(removed);
      return ball;
    }
  }

  /// <summary>
  /// Test if a point is strictly inside a sphere and not on the sphere
  /// </summary>
  /// <param name="sphere">Sphere</param>
  /// <param name="point">Point</param>
  /// <param name="epsilon">Precision or tolerance</param>
  /// <returns>Return true if point is inside</returns>
  public  static bool ContainsStrict(Sphere sphere, Point3d point, double epsilon)
  {
    bool output = false;

    if ((sphere.Radius - sphere.Center.DistanceTo(point)) >= epsilon)
    {
      output = true;
    }
    return output;
  }

  /// <summary>
  /// Return true if point is on or inside the sphere
  /// </summary>
  /// <param name="sphere">Sphere</param>
  /// <param name="point">Point</param>
  /// <returns>Return true if point is inside or on the sphere</returns>
  public  static bool Contains(Sphere sphere, Point3d point)
  {
    bool output = false;

    if (sphere.Center.DistanceTo(point) <= sphere.Radius)
    {
      output = true;
    }
    return output;
  }

  /// <summary>
  /// Minimum bounding sphere having 2 points
  /// </summary>
  /// <param name="a">First point</param>
  /// <param name="b">Second Point</param>
  /// <returns>Minimum bounding sphere</returns>
  public static Sphere Sphere2Points(Point3d a, Point3d b)
  {
    Sphere sphere = Sphere.Unset;
    double radius = a.DistanceTo(b) / 2;
    if (radius > 1e-12)
    {
      sphere = new Sphere((a + b) / 2, radius);
    }
    return  sphere;
  }

  /// <summary>
  ///  Minimum bounding sphere having 3 points
  /// </summary>
  /// <param name="a"></param>
  /// <param name="b"></param>
  /// <param name="c"></param>
  /// <returns>Minimum bounding sphere</returns>
  public static Sphere  Sphere3Points(Point3d a, Point3d b, Point3d c)
  {
    Sphere sphere3 = GetCircumSphere(a, b, c);

    Sphere sphere_ab = Sphere2Points(a, b);
    Sphere sphere_ac = Sphere2Points(a, c);
    Sphere sphere_bc = Sphere2Points(b, c);

    Sphere sphereOK = sphere3;
    if ((sphere_ab.IsValid) && sphere_ab.Center.DistanceTo(c) < sphereOK.Radius)
    {
      if ( sphere_ab.Radius < sphereOK.Radius)
      {
        sphereOK = sphere_ab;
      }
    }
    if ((sphere_ac.IsValid) && sphere_ac.Center.DistanceTo(b) < sphereOK.Radius)
    {
      if ( sphere_ac.Radius < sphereOK.Radius)
      {
        sphereOK = sphere_ac;
      }
    }
    if ((sphere_bc.IsValid) && sphere_bc.Center.DistanceTo(a) < sphereOK.Radius)
    {
      if ( sphere_bc.Radius < sphereOK.Radius)
      {
        sphereOK = sphere_bc;
      }
    }
    return sphereOK;
  }

  /// <summary>
  ///  Minimum bounding sphere having 4 points
  /// </summary>
  /// <param name="a"></param>
  /// <param name="b"></param>
  /// <param name="c"></param>
  /// <param name="d"></param>
  /// <returns>Minimum bounding sphere</returns>
  public static Sphere  Sphere4Points(Point3d a, Point3d b, Point3d c, Point3d d)
  {
    Sphere sphere4 = GetCircumSphere(a, b, c, d);

    Sphere sphere_abc = Sphere3Points(a, b, c);
    Sphere sphere_abd = Sphere3Points(a, b, d);
    Sphere sphere_acd = Sphere3Points(a, c, d);
    Sphere sphere_bcd = Sphere3Points(b, c, d);

    Sphere sphereOK = sphere4;

    if (sphere_abc.IsValid && sphere_abc.Center.DistanceTo(d) < sphereOK.Radius)
    {
      if ( sphere_abc.Radius < sphereOK.Radius)
      {
        sphereOK = sphere_abc;
      }
    }
    if (sphere_abd.IsValid && sphere_abd.Center.DistanceTo(c) < sphereOK.Radius)
    {
      if ( sphere_abd.Radius < sphereOK.Radius)
      {
        sphereOK = sphere_abd;
      }
    }
    if (sphere_acd.IsValid && sphere_acd.Center.DistanceTo(b) < sphereOK.Radius)
    {
      if ( sphere_acd.Radius < sphereOK.Radius)
      {
        sphereOK = sphere_acd;
      }
    }
    if (sphere_bcd.IsValid && sphere_bcd.Center.DistanceTo(a) < sphereOK.Radius)
    {
      if ( sphere_bcd.Radius < sphereOK.Radius)
      {
        sphereOK = sphere_bcd;
      }
    }
    return sphereOK;
  }
  /// <summary>
  ///Calculate the circumscribed sphere of 3 points, it is not the minimal bounding sphere
  /// https://gamedev.stackexchange.com/questions/60630/how-do-i-find-the-circumcenter-of-a-triangle-in-3d
  /// </summary>
  /// <param name="a">First point</param>
  /// <param name="b">Second point</param>
  /// <param name="c">Third point</param>
  /// <returns></returns>
  public static Sphere GetCircumSphere(Point3d a, Point3d b, Point3d c)
  {
    Vector3d ac = c - a;
    Vector3d ab = b - a;
    Vector3d abXac = Vector3d.CrossProduct(ab, ac);

    Sphere sphere = Sphere.Unset;
    if (abXac.Length > 1e-12)
    {
      // this is the vector from a TO the circumsphere center
      Vector3d toCircumsphereCenter = (Vector3d.CrossProduct(abXac, ab) * ac.SquareLength + Vector3d.CrossProduct(ac, abXac) * ab.SquareLength) / (2.0 * abXac.SquareLength);
      double circumsphereRadius = toCircumsphereCenter.Length;
      sphere = new Sphere(a + toCircumsphereCenter, circumsphereRadius);
    }
    else
    {
      Sphere sphere_ab = new Sphere((a + b) / 2, a.DistanceTo(b) / 2);
      Sphere sphere_ac = new Sphere((a + c) / 2, a.DistanceTo(c) / 2);
      Sphere sphere_bc = new Sphere((b + c) / 2, b.DistanceTo(c) / 2);
      sphere = sphere_ab;
      if (sphere_ac.Radius > sphere.Radius) sphere = sphere_ac;
      if (sphere_bc.Radius > sphere.Radius) sphere = sphere_bc;
    }
    return  sphere;
  }
  /// <summary>
  ///Calculate the circumscribed sphere of 4 points, it is not the minimal bounding sphere
  ///
  /// https://mathworld.wolfram.com/Circumsphere.html
  /// </summary>
  /// <param name="p1">First point</param>
  /// <param name="p2">Second Point</param>
  /// <param name="p3">Third Point</param>
  /// <param name="p4">Fourh Point</param>
  /// <returns>Minimum bounding sphere</returns>
  public static Sphere GetCircumSphere(Point3d p1, Point3d p2, Point3d p3, Point3d p4)
  {
    //TODO of point aligned or in a plane =>
    Sphere sphere = Sphere.Unset;

    Point3d center = Point3d.Origin;
    double radius = 1;


    //Matrix a = new Matrix(4, 4);
    Transform a = new Transform(1);
    //row colum,)
    a[0, 0] = p1.X;
    a[0, 1] = p1.Y;
    a[0, 2] = p1.Z;
    a[0, 3] = 1;

    a[1, 0] = p2.X;
    a[1, 1] = p2.Y;
    a[1, 2] = p2.Z;
    a[1, 3] = 1;

    a[2, 0] = p3.X;
    a[2, 1] = p3.Y;
    a[2, 2] = p3.Z;
    a[2, 3] = 1;

    a[3, 0] = p4.X;
    a[3, 1] = p4.Y;
    a[3, 2] = p4.Z;
    a[3, 3] = 1;

    //Matrix dx = new Matrix(4, 4);
    Transform dx = new Transform(1);
    dx[0, 0] = p1.X * p1.X + p1.Y * p1.Y + p1.Z * p1.Z;
    dx[0, 1] = p1.Y;
    dx[0, 2] = p1.Z;
    dx[0, 3] = 1;
    dx[1, 0] = p2.X * p2.X + p2.Y * p2.Y + p2.Z * p2.Z;
    dx[1, 1] = p2.Y;
    dx[1, 2] = p2.Z;
    dx[1, 3] = 1;
    dx[2, 0] = p3.X * p3.X + p3.Y * p3.Y + p3.Z * p3.Z;
    dx[2, 1] = p3.Y;
    dx[2, 2] = p3.Z;
    dx[2, 3] = 1;
    dx[3, 0] = p4.X * p4.X + p4.Y * p4.Y + p4.Z * p4.Z;
    dx[3, 1] = p4.Y;
    dx[3, 2] = p4.Z;
    dx[3, 3] = 1;
    Transform dy = new Transform(1);
    dy[0, 0] = p1.X * p1.X + p1.Y * p1.Y + p1.Z * p1.Z;
    dy[0, 1] = p1.X;
    dy[0, 2] = p1.Z;
    dy[0, 3] = 1;
    dy[1, 0] = p2.X * p2.X + p2.Y * p2.Y + p2.Z * p2.Z;
    dy[1, 1] = p2.X;
    dy[1, 2] = p2.Z;
    dy[1, 3] = 1;
    dy[2, 0] = p3.X * p3.X + p3.Y * p3.Y + p3.Z * p3.Z;
    dy[2, 1] = p3.X;
    dy[2, 2] = p3.Z;
    dy[2, 3] = 1;
    dy[3, 0] = p4.X * p4.X + p4.Y * p4.Y + p4.Z * p4.Z;
    dy[3, 1] = p4.X;
    dy[3, 2] = p4.Z;
    dy[3, 3] = 1;

    Transform dz = new Transform(1);
    dz[0, 0] = p1.X * p1.X + p1.Y * p1.Y + p1.Z * p1.Z;
    dz[0, 1] = p1.X;
    dz[0, 2] = p1.Y;
    dz[0, 3] = 1;
    dz[1, 0] = p2.X * p2.X + p2.Y * p2.Y + p2.Z * p2.Z;
    dz[1, 1] = p2.X;
    dz[1, 2] = p2.Y;
    dz[1, 3] = 1;
    dz[2, 0] = p3.X * p3.X + p3.Y * p3.Y + p3.Z * p3.Z;
    dz[2, 1] = p3.X;
    dz[2, 2] = p3.Y;
    dz[2, 3] = 1;
    dz[3, 0] = p4.X * p4.X + p4.Y * p4.Y + p4.Z * p4.Z;
    dz[3, 1] = p4.X;
    dz[3, 2] = p4.Y;
    dz[3, 3] = 1;

    Transform c = new Transform(1);
    c[0, 0] = p1.X * p1.X + p1.Y * p1.Y + p1.Z * p1.Z;
    c[0, 1] = p1.X;
    c[0, 2] = p1.Y;
    c[0, 3] = p1.Z;
    c[1, 0] = p2.X * p2.X + p2.Y * p2.Y + p2.Z * p2.Z;
    c[1, 1] = p2.X;
    c[1, 2] = p2.Y;
    c[1, 3] = p2.Z;
    c[2, 0] = p3.X * p3.X + p3.Y * p3.Y + p3.Z * p3.Z;
    c[2, 1] = p3.X;
    c[2, 2] = p3.Y;
    c[2, 3] = p3.Z;
    c[3, 0] = p4.X * p4.X + p4.Y * p4.Y + p4.Z * p4.Z;
    c[3, 1] = p4.X;
    c[3, 2] = p4.Y;
    c[3, 3] = p4.Z;
    center = new Point3d(dx.Determinant / (2 * a.Determinant), -dy.Determinant / (2 * a.Determinant), dz.Determinant / (2 * a.Determinant));
    double dx2 = dx.Determinant * dx.Determinant;
    double dy2 = dy.Determinant * dy.Determinant;
    double dz2 = dz.Determinant * dz.Determinant;
    double ac = a.Determinant * c.Determinant;
    radius = Math.Sqrt(dx2 + dy2 + dz2 - 4 * ac) / (2 * Math.Abs(a.Determinant));

    return new Sphere(center, radius);
  }

2 Likes

Laurent,

Thank you for your reply. I enjoyed your approximate minimum bounding sphere approach that you took. Too bad that the Welzl true minimum bounding sphere doesn’t work over 300 points. I will have to play around.

Sometimes the approximate code does leave a few points outside of the sphere I’ve noticed, by playing around with the points seed and the random boolean.

Just for comparison’s sake and you may be interested, I am attaching a file containing two approximate minimum bounding sphere c# codes that approach it from different ways. One is the Bouncing Bubble algorithm, it is very simple code and it can do 35,000 points in nearly half the time of yours so you might like it! And the way it works it generally guarantees that no points are left outside the sphere.

The other is an iterative approach like yours, not as fast as yours but doesn’t leave any outside the sphere as far as I’ve noticed.

ApproximateBoundingSpheres_Comparison.gh (18.0 KB)

Actually reading your comments on the Welzl portion of your code has led me to a realization. I think I did not account for cases where the minimum sphere for 3 and 4 points is not the circumsphere. Of course when the 3 points form an obtuse triangle the circumcircle is not the minimum circle, and same for sphere! I don’t think I accounted for that… You see I appreciate the approximations for minimum bounding spheres, but I really want to get THE minimum bounding sphere, not close to it. Thanks again for your help and the links, I will have to read through.

Well … just for once you dont need recursion for that.

Points_MinEnclosingSphere_V1.gh (165.7 KB)

I hear you: that’s a dirty and not 100% accurate solution (and has nothing to do with Welzl). Indeed but it does 2K points in 2 milliseconds (max) … and … er … if you want total accuracy apply at NASA.

1 Like

I don’t understand why you say approximate and on the same time you were forgetting some obvious cases. I can have made some errors but I take all the points into account.
between 2 points it is exact
between 3 points also
Between 4 points or it is circumscribed or 3 points are enough (previous case)

So what are the cases that didn’t work. I am curious.
For sure my code (not so different from Welzl) can be optimized.

I say that because it isn’t the Exact Minimum Bounding Sphere. Your code produces a result that is close, but not that. Mine does too, at this stage, so I’m not meaning to insult you. Your code has helped me out by studying it and I am grateful for it.

The cases that didn’t work, I am not so sure I can pinpoint whether there is a specific condition for it, but sometimes it leaves points outside of the sphere.

I don’t think I am insulted, I am just curious to understand were is the flaw. I will look more in depth.

And Point Is Inside is probably using mesh so it could be less exact. Just A guess.

1 Like

Honestly I get it. I think I should have made clear, I know a 100% accurate solution is not feasible for real world applications. I was just quite interested in the algorithm.

But now I have gotten two different approaches from two of the best c# guys on the forum! So even if it is not Welzl’s algorithm, I am happy.

You’re right, I switched to checking if any point is further from the center than the radius, and guess what: my iterative approach also sometimes leaves points outside the sphere. Thank you for your help, I think I must be satisfied with an approximate approach, and leave Welzl’s algorithm for an educational pursuit.

For sure precision is not all, I still have some difficulties to find the flaws. It could be floating precision … There lots of Determinant, divisions … I also choose some sphere that are minimal but they could contains previous spheres …

I don’t need much precision but the subject is interesting. I learned some methods and also some tricks for statics class and some problem I can’t solve, the crash surely due tor the recursion.

1 Like

Get the trad update. Tested in a I9 K and for 2K points the elapsed is ~0.3 milliseconds with an average accuracy of 1-3% (Moral: speed is everything, accuracy is nothing).

Points_MinEnclosingSphere_V1A.gh (168.7 KB)

1 Like

I just tested clicking multiple time on the toggle for the random button.
I sometime have a 0 distance for 3 boundary points and so for 3 points of the cluster.
I understand that if 3 points are on the sphere it must be the minimal sphere. Because 3 points are on the equator. I hope not being wrong. Logic is not always logic.


And sometimes there are errors. Difference must not be negative So I have a flaw somewhere.

@PeterFotiadis Peter stop the Vodka :wink:

ApproximateBoundingSpheres_Comparison_LD.gh (995.6 KB)

1 Like

Alright guys I have put Peter’s, Laurent’s, and mine into a gh file for comparison, and for anyone searching for this topic in the future. They all work relatively well and provide satisfactory results a majority of the time.

You will have to weigh the importance to you of accuracy, speed, configurability, whether fully enclosing all the points is important 100% of the time, and perhaps simplicity of the code may be important to you.

In my tests of these 3 each has been the fastest and slowest, and each has had times when it leaves a point outside the sphere (probably floating precision/tolerance reasons). So there’s tradeoffs with each of them.

Points_MinEnclosingSphere_UltimateComparison.gh (127.7 KB)

4 Likes

OK, since this is resolved (or “resolved” or whatever) here’s the 1Z dollars challenge for the brave:

Given a collection of rnd pts (say: 2 to 10K) find the closest pairs where the pair distances are as “homogenous” as possible (or their graph is as smooth as possible).

For instance if you use the Point3dList.Closest (even on some sort of recursive oct Tree basis blah, blah) approach meaning exclution of the taken pair from the next search collection [classic iterative proximity] … you’ll end up with several pairs having a rather big distance (a big spike in some distances graph like in the attached image).


The classic Steiner pair problem.

Like maybe minimise the sum of the distance cubed across all pairs …? Or a higher power to really penalise large distances?

1 Like