geometry-central

repository·master·Indexed 23 days ago

https://github.com/nmwsharp/geometry-central

A modern C++ library for geometry processing specializing in surface mesh data structures and discrete differential geometry algorithms. It features a SurfaceMesh class for efficient mesh modification, implementations of geometric quantities like normals and curvatures, and tools for surface distances and intrinsic Delaunay triangulations. The library includes sparse linear algebra utilities built on Eigen with support for SuiteSparse, direct solvers, and eigenvalue problem routines.

Tokens
69K
Snippets
120
Records
305
Agent score
79%

What's inside geometry-central

  1. Overview of Geometry Central features

    master

    Geometry-central is a modern C++ library designed for geometry processing, specifically focused on surface meshes. Key capabilities include:

    • Surface Mesh Management: A polished SurfaceMesh class supporting efficient mesh modification and data association via containers.
    • Geometric Quantities: Implementations for normals, curvatures, tangent vector bases, and discrete differential geometry operators.
    • Algorithms: Tools for computing surface distances, generating direction fields, and manipulating intrinsic Delaunay triangulations.
    • Linear Algebra: Sparse linear algebra tools built on Eigen, with automatic selection of optimized solvers if available on the system.
  2. Overview of Geometry Central

    master

    Geometry-central is a modern C++ library designed for geometry processing, specifically focused on surface meshes. It provides high-level data structures and algorithms for common geometric tasks.

    Key features include:

    • Surface Mesh Class: Efficient support for mesh modification and a container system for associating data with mesh elements (vertices, edges, faces).
    • Geometric Quantities: Implementations of normals, curvatures, tangent vector bases, and discrete differential geometry operators.
    • Geometric Algorithms: Tools for computing surface distances, generating direction fields, and manipulating intrinsic Delaunay triangulations.
    • Linear Algebra: Sparse linear algebra tools built on top of Eigen, with automatic selection of optimized solvers if available on the system.
  3. $\Delta$-complex support in HalfedgeMesh

    master

    The HalfedgeMesh class in geometry-central is explicitly designed to support $\Delta$-complexes. This support is built into the core formulation, but the library also ensures that specific operations maintain this capability:

    • Mutations: Operations that change the mesh topology (e.g., edge flips) are designed to handle the non-simplicial connectivity allowed by $\Delta$-complexes.
    • I/O: Data written to files via the library's utilities is designed to preserve the rich surface mesh data required for these structures.

    When using the API, look for notes regarding special properties or behaviors related to $\Delta$-complexes to ensure your algorithms remain robust when encountering self-edges or non-unique incidences.

  4. What is a BarycentricVector and how does it work?

    master

    A BarycentricVector is a vector that lies along a surface, representing a displacement between two points. It is currently only supported on triangle meshes.

    Unlike a ray, a BarycentricVector represents a constant vector field within a face. It can exist in three states, indicated by the BarycentricVector::type enum:

    • Face: The vector lies within a specific face. It is identified by BarycentricVector::face and its coordinates are stored in BarycentricVector::faceCoords (expressed in barycentric coordinates of the face).
    • Edge: The vector lies along a specific edge. It is identified by BarycentricVector::edge and its coordinates are stored in BarycentricVector::edgeCoords (ordered according to the edge's vertices).
    • Vertex: The vector is at a single vertex. It is identified by BarycentricVector::vertex and is always a zero-length vector.

    Barycentric vectors are useful for intrinsic geometry computations, such as inner products, because they allow for vector arithmetic that depends only on the surface's intrinsic properties.

    enum class BarycentricVectorType { Face = 0, Edge, Vertex };
  5. What is SimplePolygonMesh and when to use it

    master

    SimplePolygonMesh is a lightweight helper class used primarily for input/output (I/O) and as an intermediate data representation. It stores vertex positions, an indexed list of polygonal faces, and optional 2D texture/parameterization coordinates.

    Important Distinction: Do not use SimplePolygonMesh for implementing nontrivial geometric algorithms. For those tasks, use the SurfaceMesh class, which supports advanced traversals and geometric operations. Use SimplePolygonMesh only when you need to load/save data or pass simple mesh data between different parts of a pipeline.

  6. What is Signpost Intrinsic Triangulation?

    master

    The SignpostIntrinsicTriangulation is a data structure that encodes an intrinsic triangulation by storing "signposts" at mesh vertices. For every intrinsic edge, it stores the edge's length and its direction at the two incident vertices. This information allows the triangulation to be positioned above the input mesh.

    Tradeoffs

    Compared to the IntegerCoordinates representation, signposts have the following characteristics:

    • Pros:
      • Performance: Generally faster runtime than integer coordinates.
      • Tangent vector data: Naturally provides tangent space coordinate systems consistent with the input mesh, facilitating work with tangent-valued data.
    • Cons:
      • Robustness: Relies on tracing intrinsic edge paths across the input surface, which can be sensitive to floating-point errors. Reconstructing a common subdivision may fail if the input mesh contains degenerate triangles.
  7. Memory management pattern with std::unique_ptr

    master

    Geometry-central follows a specific memory management paradigm:

    1. Return by std::unique_ptr: Long-lived, allocated objects (like meshes) are returned from functions using std::unique_ptr. This ensures automatic deallocation when the pointer goes out of scope.
    2. Pass by Reference: When passing these objects to other functions or classes, you should pass them by reference (e.g., void processMesh(SurfaceMesh& inputMesh)) rather than moving or copying the pointer.

    To pass a unique_ptr to a function expecting a reference, dereference it with the * operator:

    processMesh(*mesh);
  8. How to interpret symmetric direction fields

    master

    When computing $n$-direction fields where $n > 1$ (such as line fields where $n=2$ or cross fields where $n=4$), geometry-central uses a "power" representation for efficiency. Instead of storing a single tangent vector, it stores a vector raised to the $n$-th power (using complex number exponentiation). This ensures that all $n$ symmetric directions map to the same representative vector.

    To recover the actual $n$ tangent direction vectors from a representative vector, you must take the $n$-th root and then rotate the resulting vector to find the other directions.

    // Compute a cross field
    int n = 4; 
    VertexData<Vector2> crossValues = computeSmoothestVertexDirectionField(*geometry, n);
    
    for(Vertex v : mesh->vertices()) {
      
      Vector2 representative = crossValues[v];
      // take the n'th root to get a base direction
      Vector2 crossDir = crossDir.pow(representative, 1. / n); 
    
      // loop over the n directions
      for(int rot = 0; rot < 4; rot++) {
        // crossDir is one of the four cross directions, as a tangent vector
        crossDir = crossDir.rot90();
      }
    }
  9. How block decomposition works for boundary conditions

    master

    Block decomposition allows you to decompose a square matrix into interleaved submatrix blocks (AA, AB, BA, BB). This is commonly used to extract boundary components of a finite element matrix to apply boundary conditions.

    1. Define Membership: Create a Vector<bool> where true indicates entries belonging to set 'A' (e.g., boundary nodes) and false indicates set 'B' (interior nodes).
    2. Decompose: Use blockDecomposeSquare to generate a BlockDecompositionResult.
    3. Partition Vectors: Use decomposeVector to split a global vector (like a RHS vector) into components for A and B.
    4. Solve and Reassemble: Solve the reduced system using the sub-blocks and reassembleVector to combine the interior solution and boundary values into a full vector.
    // Hypothetical input data
    SparseMatrix<double> mat = /* your square matrix */;
    size_t N = mat.rows();
    size_t NBoundary = /* ... */;
    Vector<double> rhsVals = Vector<double>::Zero(N);        // rhs for the system
    Vector<double> bcVals = Vector<double>::Ones(NBoundary); // boundary values at
                                                             // some nodes
    
    // Build the membership vector, which indicates which entries should be separated
    // in to set "A" (others are in "B")
    Vector<bool> setAMembership(N);
    for(size_t i = 0; i < N; i++) {
      if(/* element i is boundary */) {
        setAMembership(i) = true;
      } else {
        setAMembership(i) = false;
      }
    }
    
    // Construct the decomposition 
    BlockDecompositionResult<double> decomp = 
      blockDecomposeSquare(mat, setAMembership, true);
    
    // The four sub-blocks of the matrix are now in
    // decomp.AA, decomp.AB, decomp.BA, decomp.BB
    
    // Split up the rhs vector
    Vector<double> rhsValsA, rhsValsB;
    decomposeVector(decomp, rhsVals, rhsValsA, rhsValsB);
    
    // Solve problem
    Vector<double> combinedRHS = rhsValsA - decomp.AB * bcVals;
    Vector<double> Aresult = solve(decomp.AA, combinedRHS);
    
    // Combine the two boundary conditions and interior solution to a full vector
    Vector<double> result = reassembleVector(decomp, Aresult, bcVals);
  10. Understand the difference between SurfaceMesh and ManifoldSurfaceMesh

    master

    Geometry Central provides two primary mesh types for representing surfaces:

    1. SurfaceMesh: A general-purpose polygonal mesh structure. It can represent non-manifold meshes (e.g., three faces meeting at a single edge). Use this when your geometry does not satisfy manifold constraints.
    2. ManifoldSurfaceMesh: A specialized, more efficient version of SurfaceMesh that strictly enforces manifoldness and orientation. It inherits from SurfaceMesh, so it can be used anywhere a SurfaceMesh is required.

    Key Differences:

    • Manifoldness: ManifoldSurfaceMesh requires that the surface locally looks like a plane (no hourglass vertices or multiple faces per edge).
    • Orientation: ManifoldSurfaceMesh requires a consistent combinatorial orientation (clockwise ordering of halfedges around faces). It cannot represent non-orientable surfaces like a Klein bottles.
    • Operations: Some mutations that would make a mesh non-manifold should only be called on a SurfaceMesh. ManifoldSurfaceMesh guarantees its invariants are preserved during all operations.
    #include "geometrycentral/surface/surface_mesh.h"
    #include "geometrycentral/surface/manifold_surface_mesh.h"
  11. Associate data with mesh elements using MeshData containers

    master

    Geometry Central provides a system of MeshData<E, T> containers to associate data (scalars, vectors, etc.) with specific mesh elements like vertices, edges, or faces.

    A key feature is that these containers automatically adapt to mesh mutations. If you insert or delete elements in the underlying mesh, the containers resize themselves efficiently and remain valid.

    Commonly used typedefs include:

    • VertexData<T>: Data at vertices
    • HalfedgeData<T>: Data at halfedges
    • CornerData<T>: Data at corners
    • EdgeData<T>: Data at edges
    • FaceData<T>: Data at faces
    • BoundaryLoopData<T>: Data at boundary loops
    // on vertices
    VertexData<double> myVertexScalar(mesh);
    Vertex v = /* some vertex */;
    myVertexScalar[v] = 42.;
    
    // on faces
    FaceData<Vector3> myFaceVector(mesh);
    Face f = /* some face */;
    myFaceVector[f] = Vector3{1., 2., 3.};
  12. How mesh element handles work

    master

    In Geometry Central, element types like Vertex, Halfedge, Edge, Face, BoundaryLoop, and Corner are not the actual mesh data themselves, but rather lightweight "handles" (temporary references) to the underlying elements.

    Key behaviors:

    • Lifecycle: Creating a Vertex variable does not create a new vertex in the mesh; it just refers to one. Allowing a handle to go out of scope does not delete the element from the mesh.
    • Identity: Multiple handle variables can refer to the same underlying element.
    • Construction: You should not call constructors directly. Instead, obtain handles by:
      • Iterating through the mesh (e.g., for(Vertex v : mesh.vertices()))
      • Traversing from a neighbor (e.g., Face f = halfedge.face())
      • Iterating around an element (e.g., for(Halfedge he : vertex.outgoingHalfedges()))
    • Comparison & Hashing: All handles support equality checks (==, !=), comparisons (<, >, <=, >=) based on iteration order, and hashing (allowing them to be used as keys in std::unordered_map).