CGALDotNet - A C# computational Library built around CGAL

I would like to present CGALDotNet which is a C# wrapper around the C++ CGAL project.

This is a very niche topic and I dont expect much attention to it but its something I find interesting and enjoy working on.

CGAL is a computation geometry library. CGAL provides a solution to the precision issues which can plague computational geometry algorithms. CGAL provides 5 levels of precision with double being the minimum and the maximum being polynomials.

Heres a quote from the CGAL home page which explains what the project can do.

The library offers data structures and algorithms like triangulations, Voronoi diagrams, Boolean operations on polygons and polyhedra, point set processing, arrangements of curves, surface and volume mesh generation, geometry processing, alpha shapes, convex hull algorithms, shape reconstruction, AABB and a lot more.
Learn more about CGAL by browsing through the Package Overview.

This is very much a WIP and I will update as packages are complete. At the moment the geometry and polygon packages are complete.

Many of the other packages are complete but just need a bit of a clean up.

Heres a link to the Unity examples project.

Heres a link to the CGALDotNet project.

Heres a image of a polygon object made from the koch star algorithm.

Hers a image of some of the geometry intersections in CGAL.

9 Likes

Table of Contents

The Geometry Kernel.

The Polygon Kernel.

The Triangulation Kernel

- Triangulations 2D

# The Arrangements Kernel
[

# The Polyhedra Kernel

2 Likes

The Eigen, polygon Boolean and Minkowski sum packages are complete.

Below is a image of the Minkowski sum between a polygon with holes and a circle shape polygon.

Minkowski sums are used to create navigation areas and path finding.

2 Likes

Nice!

1 Like

The polygon offset, simplification, visibility and partition packages are now complete

Below is a image of a polygon skeleton created with the offset algorithm.

Below is a image of the visibility region from the point. The red polygon is what the point can see.

1 Like

Triangulation 2D package complete

Below is a image a Delaunay triangulation and its Voronoi diagram.

The arrangement kernel now complete. Think of arrangement’s like triangulations but with any points, segments and polygons allowed.

Here’s a image of a sweep line in that arrangement kernel.

The polyhedron mesh is completed. This mesh is based on a half edge data structure and supports polygon faces of any size.

Helper functions are provided up to hexagons in CGALDotNet. Below is a image of some of the mesh provided through a factory class.

Mesh processing package is done. This package provides a set of utility functions to process meshes.

MeshProcessingBoolean

  • Union
  • Difference
  • Intersection

MeshProcessingConnections

  • UnconnectedComponents
  • ConnectedFaces
  • SplitUnconnectedComponents
  • KeepLargeComponents
  • KeepLargestComponents

MeshProcessingFeatures

  • DetectSharpEdges
  • EdgeLengthMinMaxAvg
  • FaceAreaMinMaxAvg
  • SharpEdgesSegmentation

MeshProcessingLocate

  • RandomLocationOnMesh
  • LocateFace
  • ClosestFace

MeshProcessingMeshing

  • Extrude
  • Fair
  • Refine
  • IsotropicRemeshing
  • RandomPerturbation
  • SmoothMeshByAngle
  • SplitLongEdges
  • TriangulateFace
  • TriangulateFaces

MeshProcessingOrientation

  • DoesBoundAVolume
  • IsOutwardOriented
  • Orient
  • OrientToBoundAVolume
  • ReverseFaceOrientations

MeshProcessingRepair

  • DegenerateEdgeCount
  • DegenerateTriangleCount
  • NeedleTriangleCount
  • NonManifoldVertexCount
  • RepairPolygonSoup
  • StitchBoundaryCycles
  • StitchBorders
  • RemoveIsolatedVertices

MeshProcessingSlicer

  • Slice

Below is a image of the bunny being sliced. The output is a set of polylines.

Below is a image of a mesh being remeshed with the isotropic remesher.

2 Likes

Excellent work! I want to know how you wrapper cgal from C++ to C#, and how I get it work on macOS and iOS.

2 Likes

Hi there! I’m working with your CGAL wrapper (fantastic resource- thanks!), and I’m having some issues with the boolean operations. I think I might just be defining the issue wrong, but was hoping to ask your advice?

I’m trying to find the unique volumes of a set of gameobjects. In order to do this I take the union of all of them, then one by one I intersect the other gameobjects with each other, then finish by taking the difference between the union and the intersected components.

However, for some reason I need to translate the intersections by a small amount to make the DIFFERENCE code work? I wonder if I’m doing something wrong, or there’s an issue with the underlying algorithm?

Any help would be really appreciated!

    public void PerformVolumetricBooleanOperations(GameObject[] inputGos)
    {
        // in this function we find the union of all the gameobject volumes.
        // we then find the volume that is unique to those volumes.
       
        var boolean = MeshProcessingBoolean<EEK>.Instance;

        // we also need to convert all the gameobjects to cgal polyhedra.
        var polyhedra = new Polyhedron3<EEK>[inputGos.Length];
        for (int i = 0; i < inputGos.Length; i++)
        {
            polyhedra[i] = ConvertGoToPolyhedron(inputGos[i]);
        }


        // now, let's produce a union of all the polyhedra.
        var unionPoly = new Polyhedron3<EEK>();
        for (int i = 0; i < polyhedra.Length; i++)
        {
            if (!boolean.Op(POLYHEDRA_BOOLEAN.UNION, unionPoly, polyhedra[i], out unionPoly))
            {
                Debug.Log("BooleanOperation has failed");// this is probably because one mesh completely encompasses/equals another.
            }
        }
     

        // now we can produce the bits which are unique to each gameobject.
        // to do this, we take the union of all the overlaps between every pairwise combination of gameobjects.
        // once we have this, we can take the difference between this and the union object we calculated above.
        var intersectionsUnion = new Polyhedron3<EEK>();
        for (int i = 0; i < polyhedra.Length; i++)
        {
            var tempPoly = new Polyhedron3<EEK>();
            for (int ii = i + 1; ii < polyhedra.Length; ii++)
            {
                if (boolean.Op(POLYHEDRA_BOOLEAN.INTERSECT, polyhedra[i], polyhedra[ii], out tempPoly))
                {
                    boolean.Op(POLYHEDRA_BOOLEAN.UNION, intersectionsUnion, tempPoly, out intersectionsUnion);
                }
            }
        }
        // there is a bit of an issue with taking the difference between union and intersections. i think it might be because there's too perfect an overlap?. this seems to solve it, but i don't like it.
        intersectionsUnion.Translate(new Vector3d(0.0000001));


        // as a check, you can uncomment the below, and you should see gameobjects appear that are the union, and the union of intersections
        //unionPoly.ToUnityMesh("union of all gameobjects", new Material(Shader.Find("Diffuse")), false);
        //intersectionsUnion.ToUnityMesh("union of all intersections", new Material(Shader.Find("Diffuse")), false);

        var intersectionsUnionInversion = new Polyhedron3<EEK>();
        boolean.Op(POLYHEDRA_BOOLEAN.DIFFERENCE, unionPoly, intersectionsUnion, out intersectionsUnionInversion);
        intersectionsUnionInversion.ToUnityMesh("All unique volumes", new Material(Shader.Find("Diffuse")), false);
    }



    Polyhedron3<EEK> ConvertGoToPolyhedron(GameObject go)
    {
        // this takes a unity gameobject, and turns it into an EEK polyhedron (middle accuracy, middle speed).
        // to convert back, just call .toUnityMesh() on the polyhedron itself.
        Vector3[] unityMeshPoints = go.GetComponent<MeshFilter>().mesh.vertices;
        Point3d[] cgalMeshPoints = new Point3d[unityMeshPoints.Length];
        for (int i = 0; i < unityMeshPoints.Length; i++)
        {
            cgalMeshPoints[i] = unityMeshPoints[i].ToCGALPoint3d();
        }
        var polyhedron = new Polyhedron3<EEK>(cgalMeshPoints, go.GetComponent<MeshFilter>().mesh.triangles);
        polyhedron.Translate(new Point3d(go.transform.position.x, go.transform.position.y, go.transform.position.z));

        return polyhedron;
    }

Hi scrawk,
thank you so much for your wrapper and C# integration. I have been using it and really appreciating it, it’s fantastic.

@AShipman

While using the boolean operations repeatedly i did notice something. If two vertices in the Unity mesh are too close together it will crash the library, i think because of Point3 being float there is limited resolution and two vertices too close get merged or some other issue related to floating point precision.

In my experiment i have noticed that the threshold for issues is >3.3e-6 and from my tests < 1e-5.
So I recommend that before performing boolean operations, you check vertex distances.

public static bool checkIfRedundantVertex(Mesh mesh) {

        var vertices = mesh.vertices;
        var posVertices = new HashSet<Vector3>();

        for(int i = 0; i < vertices.Length; i++) {
            var rounded = RoundVector(vertices[i], 0.00001f);
            if(posVertices.Contains(rounded)) {
                Debug.Log("Redundant vertex");
                return true;
            } else {
                posVertices.Add(rounded);
            }
        }
        return false;
    }

public static Vector3 RoundVector(Vector3 input, float precision) {
        float invPrecision = 1.0f / precision;
        return new Vector3(
            Mathf.Round(input.x * invPrecision) / invPrecision,
            Mathf.Round(input.y * invPrecision) / invPrecision,
            Mathf.Round(input.z * invPrecision) / invPrecision
        );
    }