This algorithm takes as input a path (or loop/network of paths) along the edges of a triangle mesh, and as output straightens that path to be a _geodesic_ (i.e. a straight line, or equivalently a locally-shortest path along a surface). The procedure runs in milliseconds, is quite robust, comes with a strong guarantee that no new crossings will be created in the path, and as an added benefit also generates a triangulation on the surface which conforms to the geodesic.  Additionally, it even enables the construction of Bézier curves on a surface.

![shorten path to geodesic](/media/flip_geodesic.jpg)

See [this repo](https://github.com/nmwsharp/flip-geodesics-demo) for a demo application which wraps this implementation as a standalone application with a GUI.

This is an implementation of the algorithm described in [You Can Find Geodesic Paths in Triangle Meshes by Just Flipping Edges](https://nmwsharp.com/research/flip-geodesics/).

`#include "geometrycentral/surface/flip_geodesics.h"`

**Example:**

```cpp
#include "geometrycentral/surface/flip_geodesics.h"
#include "geometrycentral/surface/meshio.h"

using namespace geometrycentral;
using namespace geometrycentral::surface;

// Load a mesh
std::unique_ptr<ManifoldSurfaceMesh> mesh;
std::unique_ptr<VertexPositionGeometry> geometry;
std::tie(mesh, geometry) = readManifoldSurfaceMesh("my_mesh.obj");

// Create a path network as a Dijkstra path between endpoints
std::unique_ptr<FlipEdgeNetwork> edgeNetwork;
Vertex vStart = mesh->vertex(0);
Vertex vEnd = mesh->vertex(7);
edgeNetwork = FlipEdgeNetwork::constructFromDijkstraPath(*mesh, *geometry, vStart, vEnd);

// Make the path a geodesic
edgeNetwork->iterativeShorten();

// Extract the result as a polyline along the surface
edgeNetwork->posGeom = geometry.get();
std::vector<std::vector<Vector3>> polyline = edgeNetwork->getPathPolyline3D();
```


## Constructing a path network

The class stateful class `FlipEdgeNetwork` encapsulates all functionality in the algorithm; all of the methods below are members functions. This class wraps a `SignpostIntrinsicTriangulation` (as member `FlipEdgeNetwork::tri`), and also manages a path network along the edges of that triangulation.

The most direct construct takes as input a sequence of halfedges along the surface which comprise the path.

??? func "`#!cpp FlipEdgeNetwork::FlipEdgeNetwork(ManifoldSurfaceMesh& mesh, IntrinsicGeometryInterface& inputGeom, std::vector<std::vector<Halfedge>> paths, VertexData<bool> extraMarkedVerts = VertexData<bool>())`"

    Construct a network of paths along the surface.

    - `mesh`: the input manifold triangle mesh
    - `inputGeom`: the input geometry (as always, a `VertexPositionGeometry` is valid input)
    - `paths`: a collection of paths along the surface, each given as an ordered sequence of halfedges
    - `extraMarkedVerts`: if given, boolean vector of vertices where the path is pinned, and should not be straightened

Additionally, several factory methods are provided which assist in constructing a path network from common input data.

??? func "`#!cpp static std::unique_ptr<FlipEdgeNetwork> FlipEdgeNetwork::constructFromDijkstraPath(ManifoldSurfaceMesh& mesh, IntrinsicGeometryInterface& geom, Vertex startVert, Vertex endVert)`"
    
    Construct a network consisting of a single path, generated by running Dijkstra's algorithm between two point.

    - `mesh`: the input manifold triangle mesh
    - `inputGeom`: the input geometry (as always, a `VertexPositionGeometry` is valid input)
    - `startVert`, `endVert`: the source and target vertices from which to initialize a Dijkstra path along edges

??? func "`#!cpp static std::unique_ptr<FlipEdgeNetwork> FlipEdgeNetwork::constructFromPiecewiseDijkstraPath(ManifoldSurfaceMesh& mesh, IntrinsicGeometryInterface& geom, std::vector<Vertex> points, bool closed = false, bool markInterior = false)`" 

    Construct a network consisting of a single path, generated by running Dijkstra's algorithm between consecutive pairs of vertices.

    - `mesh`: the input manifold triangle mesh
    - `inputGeom`: the input geometry (as always, a `VertexPositionGeometry` is valid input)
    - `points`: the not-necessarily-adjacent vertices from which to initialize the Dijkstra path, by running Dijkstra's algorithm between each i'th and (i+1)'th points.
    - `closed`: if true, the first point will be connected to the last to create a closed loop
    - `markInterior`: if true, the path will be pinned at the specified `points` and not shortened there

??? func "`#!cpp static std::unique_ptr<FlipEdgeNetwork> FlipEdgeNetwork::constructFromEdgeSet(ManifoldSurfaceMesh& mesh, IntrinsicGeometryInterface& geom, const EdgeData<bool>& inPath, const VertexData<bool>& extraMarkedVertices)`"

    Construct a network from a marked set of edges on the mesh. Endpoints, loops, etc are heuristically inferred (e.g. if a path ends at the same vertex it starts at, it is assumed to be a loop).

    - `mesh`: the input manifold triangle mesh
    - `inputGeom`: the input geometry (as always, a `VertexPositionGeometry` is valid input)
    - `inPath`: a boolean array indicating edges which are in the path
    - `extraMarkedVerts`: if given, boolean vector of vertices where the path is pinned, and should not be straightened

## Manipulating paths

The key routine is `FlipEdgeNetwork::iterativeShorten()`, which shortens the path until it is a geodesic by flipping edges in the underlying intrinsic triangulation.

??? func "`#!cpp void FlipEdgeNetwork::iterativeShorten(size_t maxIterations = INVALID_IND, double maxRelativeLengthDecrease = 0.)`"

    Shorten the network to be geodesic.

    Additional termination conditions can be set to potentially stop the process before the path is a geodesic:

    - `maxIterations` if set to something other than `INVALID_IND`, limits the number of iterations
    - `maxRelativeLengthDecrease` if set to something other than `0`, limits the maximum decrease in length for the network. E.g. `0.5` would mean the resulting network is at at `0.5 * L`, where `L` is the initial length.


Importantly, after calling `iterativeShorten()`, the underlying intrinsic triangulation `FlipEdgeNetwork::tri` is a triangulation which _conforms_ to the generated geodesics. This triangulation can then be used for subsequent computation, such as solving a PDE which has geodesics as boundary conditions.

### Geodesic Bézier curves 

Once we can shorten curves to geodesics, we can use a de Casteljau-style subdivision scheme to construct geodesic Bézier curves along a mesh,
using a procedure described in "Modeling on triangulations with geodesic curves" by Morera et al. (2008).

??? func "`#!cpp void FlipEdgeNetwork::bezierSubdivide(size_t nRounds)`"

    Construct a Bézier curve from an input control polygon path via iterative subdivision.

    - `nRounds` specifies the number of subdivision rounds to perform; the curve converges to an exact geodesics as `nRounds` increases. Setting `nRounds=3` is often a good initial choice.

    Note that the control points of the curve should be _marked vertices_, as described in the construction section. Additionally, this function should be applied to a network consisting of a _single path_, not distinct paths between multiple control points.  Calling the factory method `FlipEdgeNetwork::constructFromPiecewiseDijkstraPath(mesh, geom, /* vector of constrol vertices */, false, true)` above will construct a path ready for Bézier subdivision.
 
    The algorithm used here is essentially the one described in "Modeling on triangulations with geodesic curves" by Morera et al. (2008), but using flip-based geodesics to straighten paths.


<figure>
  <img src="/media/flip_bezier.jpg"/>
  <figcaption>
  A geodesic Bézier curve generated with this method.
  </figcaption>
</figure>


### Improving the triangulation

As noted above, we can additionally generate a triangulation which conforms to geodesic curves, but this triangulation will often contain extremely skinny triangles. Performing Delaunay refinement improves the triangle quality for subsequent computation.

??? func "`#!cpp void FlipEdgeNetwork::delaunayRefine(double areaThresh = std::numeric_limits<double>::infinity(), size_t maxInsertions = INVALID_IND, double angleBound = 25.)`"

    Perform intrinsic Delaunay refinement on the underlying triangulation to improve its numerical quality while preserving the geodesics.
 
    Additional arguments are as in `SignpostIntrinsicTriangulation::delaunayRefine()`.
  
<figure>
  <img src="/media/flip_cathead_refine.jpg"/>
  <figcaption>
  A geodesic path is introduced, then the triangulation is refined to remove skinny triangles.
  </figcaption>
</figure>


## Output and utilities

After running `iterativeShorten()`, the resulting geodesic paths are represented as a collection edges in an updated intrinsic triangulation. The most common operation is to extract the resulting path network as a polyline along the surface. Note that these routines return a double list; the outer list represents the paths/loops in the network, while the inner list are the points in each path/loop.

One option is to get the output as a sequence of [surface points](/surface/utilities/surface_point), where each is a vertex or a point along some edge in the mesh. 

??? func "`#!cpp std::vector<std::vector<SurfacePoint>> FlipEdgeNetwork::getPathPolyline()`"

    Get the path network as a sequence of surface points along the input mesh.

??? func "`#!cpp std::vector<std::vector<SurfacePoint>> FlipEdgeNetwork::getAllEdgePolyline()`"
    
    Get all edges in the underlying triangulation as a sequence of surface points along the input mesh.

A simpler approach is to simply extract the path as a sequence of points in 3D space.

**To use this approach, you must first set your 3D mesh geometry as the `posGeom` member.** This extra step is necessary because in general, the `FlipEdgeNetwork` can be applied to meshes which have only edge lengths, but extracting 3D points requires your mesh to have 3D positions. (see the example at the top.)

??? func "`#!cpp std::vector<std::vector<Vector3>> FlipEdgeNetwork::getPathPolyline3D()`"
    
    Get the path network as a sequence of points in 3D space. 

    **To use this approach, you must first set your 3D mesh geometry as the `posGeom` member.** 

??? func "`#!cpp std::vector<std::vector<Vector3>> FlipEdgeNetwork::getAllEdgePolyline3D()`"
    
    Get all edges in the underlying intrinsic triangulation as a sequence of points in 3D space. 

    **To use this approach, you must first set your 3D mesh geometry as the `posGeom` member.** 

Additionally, a collection of useful utility functions are provided (see `flip_geodesic.h` the complete listing).

??? func "`#!cpp bool FlipEdgeNetwork::edgeInPath(Edge e)`"

    Test whether a given edge from the intrinsic triangulation appears in the path.

??? func "`#!cpp bool FlipEdgeNetwork::halfedgeInPath(Halfedge he)`"
    
    Test whether a given halfedge from the intrinsic triangulation appears in the path.

??? func "`#!cpp double FlipEdgeNetwork::length()`"
    
    Measure the total length of the path network.

??? func "`#!cpp double FlipEdgeNetwork::minAngleIsotopy()`"
    
    Measure the smallest angle in the path (not blocked by isotopy, aka the constraint that curves do not cross).

??? func "`#!cpp void FlipEdgeNetwork::savePathOBJLine(std::string filenamePrefix, bool withAll = false)`"
    
    Write the path to file as line entries in an obj file.

## Citation

If these routines contribute to academic work, please cite:

```bib
@article{sharp2020you,
  title={You can find geodesic paths in triangle meshes by just flipping edges},
  author={Sharp, Nicholas and Crane, Keenan},
  journal={ACM Transactions on Graphics (TOG)},
  volume={39},
  number={6},
  pages={1--15},
  year={2020},
  publisher={ACM New York, NY, USA}
}
```
