Browse Source

Added propagate_winding_numbers(...)

Former-commit-id: 8ee0db57c7bde44ee433f22f34729c06d18aba5f
Qingnan Zhou 9 years ago
parent
commit
83cd38bfaa

+ 330 - 0
include/igl/cgal/closest_facet.cpp

@@ -0,0 +1,330 @@
+#include "closest_facet.h"
+
+#include <vector>
+#include <stdexcept>
+
+#include <CGAL/AABB_tree.h>
+#include <CGAL/AABB_traits.h>
+#include <CGAL/AABB_triangle_primitive.h>
+#include <CGAL/intersections.h>
+#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
+
+#include "order_facets_around_edge.h"
+
+template<
+    typename DerivedV,
+    typename DerivedF,
+    typename DerivedI,
+    typename DerivedP,
+    typename DerivedR,
+    typename DerivedS >
+IGL_INLINE void igl::cgal::closest_facet(
+        const Eigen::PlainObjectBase<DerivedV>& V,
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        const Eigen::PlainObjectBase<DerivedI>& I,
+        const Eigen::PlainObjectBase<DerivedP>& P,
+        Eigen::PlainObjectBase<DerivedR>& R,
+        Eigen::PlainObjectBase<DerivedS>& S) {
+    typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
+    typedef Kernel::Point_3 Point_3;
+    typedef Kernel::Plane_3 Plane_3;
+    typedef Kernel::Segment_3 Segment_3;
+    typedef Kernel::Triangle_3 Triangle;
+    typedef std::vector<Triangle>::iterator Iterator;
+    typedef CGAL::AABB_triangle_primitive<Kernel, Iterator> Primitive;
+    typedef CGAL::AABB_traits<Kernel, Primitive> AABB_triangle_traits;
+    typedef CGAL::AABB_tree<AABB_triangle_traits> Tree;
+
+    if (F.rows() <= 0 || I.rows() <= 0) {
+        throw std::runtime_error(
+                "Closest facet cannot be computed on empty mesh.");
+    }
+
+    const size_t num_faces = I.rows();
+    std::vector<Triangle> triangles;
+    for (size_t i=0; i<num_faces; i++) {
+        const Eigen::Vector3i f = F.row(I(i, 0));
+        triangles.emplace_back(
+                Point_3(V(f[0], 0), V(f[0], 1), V(f[0], 2)),
+                Point_3(V(f[1], 0), V(f[1], 1), V(f[1], 2)),
+                Point_3(V(f[2], 0), V(f[2], 1), V(f[2], 2)));
+        if (triangles.back().is_degenerate()) {
+            throw std::runtime_error(
+                    "Input facet components contains degenerated triangles");
+        }
+    }
+    Tree tree(triangles.begin(), triangles.end());
+    tree.accelerate_distance_queries();
+
+    auto on_the_positive_side = [&](size_t fid, const Point_3& p) {
+        const auto& f = F.row(fid).eval();
+        Point_3 v0(V(f[0], 0), V(f[0], 1), V(f[0], 2));
+        Point_3 v1(V(f[1], 0), V(f[1], 1), V(f[1], 2));
+        Point_3 v2(V(f[2], 0), V(f[2], 1), V(f[2], 2));
+        auto ori = CGAL::orientation(v0, v1, v2, p);
+        switch (ori) {
+            case CGAL::POSITIVE:
+                return true;
+            case CGAL::NEGATIVE:
+                return false;
+            case CGAL::COPLANAR:
+                throw std::runtime_error(
+                        "It seems input mesh contains self intersection");
+            default:
+                throw std::runtime_error("Unknown CGAL state.");
+        }
+        return false;
+    };
+
+    auto get_orientation = [&](size_t fid, size_t s, size_t d) -> bool {
+        const auto& f = F.row(fid);
+        if (f[0] == s && f[1] == d) return false;
+        else if (f[1] == s && f[2] == d) return false;
+        else if (f[2] == s && f[0] == d) return false;
+        else if (f[0] == d && f[1] == s) return true;
+        else if (f[1] == d && f[2] == s) return true;
+        else if (f[2] == d && f[0] == s) return true;
+        else {
+            throw std::runtime_error(
+                    "Cannot compute orientation due to incorrect connectivity");
+            return false;
+        }
+    };
+    auto index_to_signed_index = [&](size_t index, bool ori) -> int{
+        return (index+1) * (ori? 1:-1);
+    };
+    auto signed_index_to_index = [&](int signed_index) -> size_t {
+        return abs(signed_index) - 1;
+    };
+
+    enum ElementType { VERTEX, EDGE, FACE };
+    auto determine_element_type = [&](const Point_3& p, const size_t fid,
+            size_t& element_index) {
+        const auto& tri = triangles[fid];
+        const Point_3 p0 = tri[0];
+        const Point_3 p1 = tri[1];
+        const Point_3 p2 = tri[2];
+
+        if (p == p0) { element_index = 0; return VERTEX; }
+        if (p == p1) { element_index = 1; return VERTEX; }
+        if (p == p2) { element_index = 2; return VERTEX; }
+        if (CGAL::collinear(p0, p1, p)) { element_index = 2; return EDGE; }
+        if (CGAL::collinear(p1, p2, p)) { element_index = 0; return EDGE; }
+        if (CGAL::collinear(p2, p0, p)) { element_index = 1; return EDGE; }
+
+        element_index = 0;
+        return FACE;
+    };
+
+    auto process_edge_case = [&](
+            size_t query_idx,
+            const size_t s, const size_t d,
+            bool& orientation) {
+
+        Point_3 mid_edge_point(
+                (V(s,0) + V(d,0)) * 0.5,
+                (V(s,1) + V(d,1)) * 0.5,
+                (V(s,2) + V(d,2)) * 0.5);
+        Point_3 query_point(
+                P(query_idx, 0),
+                P(query_idx, 1),
+                P(query_idx, 2));
+
+        std::vector<Tree::Primitive_id> intersected_faces;
+        tree.all_intersected_primitives(Segment_3(mid_edge_point, query_point),
+                std::back_inserter(intersected_faces));
+
+        const size_t num_intersected_faces = intersected_faces.size();
+        std::vector<size_t> intersected_face_indices(num_intersected_faces);
+        std::vector<int> intersected_face_signed_indices(num_intersected_faces);
+        std::transform(intersected_faces.begin(),
+                intersected_faces.end(),
+                intersected_face_indices.begin(),
+                [&](const Tree::Primitive_id& itr) -> size_t
+                { return I(itr-triangles.begin(), 0); });
+        std::transform(
+                intersected_face_indices.begin(),
+                intersected_face_indices.end(),
+                intersected_face_signed_indices.begin(),
+                [&](size_t index) {
+                    return index_to_signed_index(
+                        index, get_orientation(index, s,d));
+                });
+
+        Eigen::VectorXi order;
+        DerivedP pivot = P.row(query_idx).eval();
+        auto get_opposite_vertex = [&](size_t fid) {
+            const auto& f = F.row(fid);
+            if (f[0] != s && f[0] != d) return V.row(f[0]).eval();
+            if (f[1] != s && f[1] != d) return V.row(f[1]).eval();
+            if (f[2] != s && f[2] != d) return V.row(f[2]).eval();
+        };
+        igl::cgal::order_facets_around_edge(V, F, s, d,
+                intersected_face_signed_indices,
+                pivot, order);
+        orientation = intersected_face_signed_indices[order[0]] < 0;
+        return intersected_face_indices[order[0]];
+    };
+
+    auto process_face_case = [&](
+            const size_t query_idx, const size_t fid, bool& orientation) {
+        const auto& f = F.row(I(fid, 0));
+        return process_edge_case(query_idx, f[0], f[1], orientation);
+    };
+
+    auto process_vertex_case = [&](const size_t query_idx, size_t s,
+            bool& orientation) {
+        Point_3 closest_point(V(s, 0), V(s, 1), V(s, 2));
+        Point_3 query_point(P(query_idx, 0), P(query_idx, 1), P(query_idx, 2));
+
+        std::vector<Tree::Primitive_id> intersected_faces;
+        tree.all_intersected_primitives(Segment_3(closest_point, query_point),
+                std::back_inserter(intersected_faces));
+
+        const size_t num_intersected_faces = intersected_faces.size();
+        std::vector<size_t> intersected_face_indices(num_intersected_faces);
+        std::transform(intersected_faces.begin(),
+                intersected_faces.end(),
+                intersected_face_indices.begin(),
+                [&](const Tree::Primitive_id& itr) -> size_t
+                { return I(itr-triangles.begin(), 0); });
+
+        std::set<size_t> adj_vertices_set;
+        for (auto fid : intersected_face_indices) {
+            const auto& f = F.row(fid);
+            if (f[0] != s) adj_vertices_set.insert(f[0]);
+            if (f[1] != s) adj_vertices_set.insert(f[1]);
+            if (f[2] != s) adj_vertices_set.insert(f[2]);
+        }
+        const size_t num_adj_vertices = adj_vertices_set.size();
+        std::vector<size_t> adj_vertices(num_adj_vertices);
+        std::copy(adj_vertices_set.begin(), adj_vertices_set.end(),
+                adj_vertices.begin());
+
+        std::vector<Point_3> adj_points;
+        for (size_t vid : adj_vertices) {
+            adj_points.emplace_back(V(vid,0), V(vid,1), V(vid,2));
+        }
+
+        // A plane is on the exterior if all adj_points lies on or to
+        // one side of the plane.
+        auto is_on_exterior = [&](const Plane_3& separator) {
+            size_t positive=0;
+            size_t negative=0;
+            size_t coplanar=0;
+            for (const auto& point : adj_points) {
+                switch(separator.oriented_side(point)) {
+                    case CGAL::ON_POSITIVE_SIDE:
+                        positive++;
+                        break;
+                    case CGAL::ON_NEGATIVE_SIDE:
+                        negative++;
+                        break;
+                    case CGAL::ON_ORIENTED_BOUNDARY:
+                        coplanar++;
+                        break;
+                    default:
+                        throw "Unknown plane-point orientation";
+                }
+            }
+            auto query_orientation = separator.oriented_side(query_point);
+            bool r = (positive == 0 && query_orientation == CGAL::POSITIVE)
+                || (negative == 0 && query_orientation == CGAL::NEGATIVE);
+            return r;
+        };
+
+        size_t d = std::numeric_limits<size_t>::max();
+        for (size_t i=0; i<num_adj_vertices; i++) {
+            const size_t vi = adj_vertices[i];
+            for (size_t j=i+1; j<num_adj_vertices; j++) {
+                Plane_3 separator(closest_point, adj_points[i], adj_points[j]);
+                if (separator.is_degenerate()) {
+                    throw std::runtime_error(
+                            "Input mesh contains degenerated faces");
+                }
+                if (is_on_exterior(separator)) {
+                    d = vi;
+                    assert(!CGAL::collinear(
+                                query_point, adj_points[i], closest_point));
+                    break;
+                }
+            }
+        }
+        assert(d != std::numeric_limits<size_t>::max());
+
+        return process_edge_case(query_idx, s, d, orientation);
+    };
+
+    const size_t num_queries = P.rows();
+    R.resize(num_queries, 1);
+    S.resize(num_queries, 1);
+    for (size_t i=0; i<num_queries; i++) {
+        const Point_3 query(P(i,0), P(i,1), P(i,2));
+        auto projection = tree.closest_point_and_primitive(query);
+        const Point_3 closest_point = projection.first;
+        size_t fid = projection.second - triangles.begin();
+        bool fid_ori = false;
+
+        // Gether all facets sharing the closest point.
+        std::vector<Tree::Primitive_id> intersected_faces;
+        tree.all_intersected_primitives(Segment_3(closest_point, query),
+                std::back_inserter(intersected_faces));
+        const size_t num_intersected_faces = intersected_faces.size();
+        std::vector<size_t> intersected_face_indices(num_intersected_faces);
+        std::transform(intersected_faces.begin(),
+                intersected_faces.end(),
+                intersected_face_indices.begin(),
+                [&](const Tree::Primitive_id& itr) -> size_t
+                { return I(itr-triangles.begin(), 0); });
+
+        size_t element_index;
+        auto element_type = determine_element_type(closest_point, fid,
+                element_index);
+        switch(element_type) {
+            case VERTEX:
+                {
+                    const auto& f = F.row(I(fid, 0));
+                    const size_t s = f[element_index];
+                    fid = process_vertex_case(i, s, fid_ori);
+                }
+                break;
+            case EDGE:
+                {
+                    const auto& f = F.row(I(fid, 0));
+                    const size_t s = f[(element_index+1)%3];
+                    const size_t d = f[(element_index+2)%3];
+                    fid = process_edge_case(i, s, d, fid_ori);
+                }
+                break;
+            case FACE:
+                {
+                    fid = process_face_case(i, fid, fid_ori);
+                }
+                break;
+            default:
+                throw std::runtime_error("Unknown element type.");
+        }
+
+
+        R(i,0) = fid;
+        S(i,0) = fid_ori;
+    }
+}
+
+template<
+    typename DerivedV,
+    typename DerivedF,
+    typename DerivedP,
+    typename DerivedR,
+    typename DerivedS >
+IGL_INLINE void igl::cgal::closest_facet(
+        const Eigen::PlainObjectBase<DerivedV>& V,
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        const Eigen::PlainObjectBase<DerivedP>& P,
+        Eigen::PlainObjectBase<DerivedR>& R,
+        Eigen::PlainObjectBase<DerivedS>& S) {
+    const size_t num_faces = F.rows();
+    Eigen::VectorXi I(num_faces);
+    I.setLinSpaced(num_faces, 0, num_faces-1);
+    igl::cgal::closest_facet(V, F, I, P, R, S);
+}

+ 55 - 0
include/igl/cgal/closest_facet.h

@@ -0,0 +1,55 @@
+#ifndef CLOSEST_FACET_H
+#define CLOSEST_FACET_H
+
+#include "../igl_inline.h"
+#include <Eigen/Core>
+
+namespace igl {
+    namespace cgal {
+
+        // Determine the closest facet for each of the input points.
+        //
+        // Inputs:
+        //   V  #V by 3 array of vertices.
+        //   F  #F by 3 array of faces.
+        //   I  #I list of triangle indices to consider.
+        //   P  #P by 3 array of query points.
+        //
+        // Outputs:
+        //   R  #P list of closest facet indices.
+        //   S  #P list of bools indicating on which side of the closest facet
+        //      each query point lies.
+        template<
+            typename DerivedV,
+            typename DerivedF,
+            typename DerivedI,
+            typename DerivedP,
+            typename DerivedR,
+            typename DerivedS >
+        IGL_INLINE void closest_facet(
+                const Eigen::PlainObjectBase<DerivedV>& V,
+                const Eigen::PlainObjectBase<DerivedF>& F,
+                const Eigen::PlainObjectBase<DerivedI>& I,
+                const Eigen::PlainObjectBase<DerivedP>& P,
+                Eigen::PlainObjectBase<DerivedR>& R,
+                Eigen::PlainObjectBase<DerivedS>& S);
+
+        template<
+            typename DerivedV,
+            typename DerivedF,
+            typename DerivedP,
+            typename DerivedR,
+            typename DerivedS >
+        IGL_INLINE void closest_facet(
+                const Eigen::PlainObjectBase<DerivedV>& V,
+                const Eigen::PlainObjectBase<DerivedF>& F,
+                const Eigen::PlainObjectBase<DerivedP>& P,
+                Eigen::PlainObjectBase<DerivedR>& R,
+                Eigen::PlainObjectBase<DerivedS>& S);
+    }
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#include "closest_facet.cpp"
+#endif
+#endif

+ 459 - 0
include/igl/cgal/propagate_winding_numbers.cpp

@@ -0,0 +1,459 @@
+#include "propagate_winding_numbers.h"
+#include "../extract_manifold_patches.h"
+#include "../extract_non_manifold_edge_curves.h"
+#include "../facet_components.h"
+#include "../unique_edge_map.h"
+#include "order_facets_around_edge.h"
+#include "outer_facet.h"
+#include "closest_facet.h"
+
+#include <stdexcept>
+#include <limits>
+#include <vector>
+
+namespace propagate_winding_numbers_helper {
+    template<typename DerivedW >
+    bool winding_number_assignment_is_consistent(
+            const std::vector<Eigen::VectorXi>& orders,
+            const std::vector<std::vector<bool> >& orientations,
+            const Eigen::PlainObjectBase<DerivedW>& per_patch_winding_number) {
+        const size_t num_edge_curves = orders.size();
+        const size_t num_labels = per_patch_winding_number.cols() / 2;
+        for (size_t i=0; i<num_edge_curves; i++) {
+            const auto& order = orders[i];
+            const auto& orientation = orientations[i];
+            assert(order.size() == orientation.size());
+            const size_t order_size = order.size();
+            for (size_t j=0; j<order_size; j++) {
+                const size_t curr = j;
+                const size_t next = (j+1) % order_size;
+
+                for (size_t k=0; k<num_labels; k++) {
+                    // Retrieve the winding numbers of the current partition from
+                    // the current patch and the next patch.  If the patches forms
+                    // the boundary of a 3D volume, the winding number assignments
+                    // should be consistent.
+                    int curr_winding_number = per_patch_winding_number(
+                            order[curr], k*2+(orientation[curr]? 0:1));
+                    int next_winding_number = per_patch_winding_number(
+                            order[next], k*2+(orientation[next]? 1:0));
+                    if (curr_winding_number != next_winding_number) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+}
+
+template<
+    typename DerivedV,
+    typename DerivedF,
+    typename DeriveduE,
+    typename uE2EType,
+    typename DerivedC,
+    typename DerivedP,
+    typename DerivedW >
+IGL_INLINE bool igl::cgal::propagate_winding_numbers_single_component_patch_wise(
+        const Eigen::PlainObjectBase<DerivedV>& V,
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        const Eigen::PlainObjectBase<DeriveduE>& uE,
+        const std::vector<std::vector<uE2EType> >& uE2E,
+        const Eigen::PlainObjectBase<DerivedC>& C,
+        const Eigen::PlainObjectBase<DerivedP>& P,
+        const std::vector<std::vector<size_t> >& intersection_curves,
+        Eigen::PlainObjectBase<DerivedW>& patch_W) {
+    const size_t num_faces = F.rows();
+    const size_t num_patches = P.maxCoeff() + 1;
+    assert(C.size() == num_patches);
+    // Utility functions.
+    auto edge_index_to_face_index = [&](size_t ei) { return ei % num_faces; };
+    auto edge_index_to_corner_index = [&](size_t ei) { return ei / num_faces; };
+    auto face_and_corner_index_to_edge_index = [&](size_t fi, size_t ci) {
+        return ci*num_faces + fi;
+    };
+    auto get_edge_end_points = [&](size_t ei, size_t& s, size_t& d) {
+        const size_t fi = edge_index_to_face_index(ei);
+        const size_t ci = edge_index_to_corner_index(ei);
+        s = F(fi, (ci+1)%3);
+        d = F(fi, (ci+2)%3);
+    };
+    auto is_positively_orientated =
+        [&](size_t fi, size_t s, size_t d) -> bool{
+            const Eigen::Vector3i f = F.row(fi);
+            if (f[0] == d && f[1] == s) return true;
+            if (f[1] == d && f[2] == s) return true;
+            if (f[2] == d && f[0] == s) return true;
+            if (f[0] == s && f[1] == d) return false;
+            if (f[1] == s && f[2] == d) return false;
+            if (f[2] == s && f[0] == d) return false;
+            throw "Edge does not belong to face.";
+            return false;
+        };
+
+    auto compute_signed_index = [&](size_t fi, size_t s, size_t d) -> int{
+        return int(fi+1) * (is_positively_orientated(fi, s, d) ? 1:-1);
+    };
+    auto compute_unsigned_index = [&](int signed_idx) -> size_t {
+        return abs(signed_idx) - 1;
+    };
+
+    // Order patches around each intersection curve.
+    const size_t num_edge_curves = intersection_curves.size();
+    std::vector<Eigen::VectorXi> orders(num_edge_curves);
+    std::vector<std::vector<bool> > orientations(num_edge_curves);
+    std::vector<std::vector<size_t> > patch_curve_adjacency(num_patches);
+    for (size_t i=0; i<num_edge_curves; i++) {
+        const auto& curve = intersection_curves[i];
+        const size_t uei = curve[0];
+        size_t s = uE(uei, 0);
+        size_t d = uE(uei, 1);
+        std::vector<int> adj_faces;
+        for (size_t ei : uE2E[uei]) {
+            const size_t fi = edge_index_to_face_index(ei);
+            const size_t signed_fi =
+                compute_signed_index(fi, s, d);
+            adj_faces.push_back(signed_fi);
+            patch_curve_adjacency[P[fi]].push_back(i);
+        }
+
+        auto& order = orders[i];
+        igl::cgal::order_facets_around_edge(
+                V, F, s, d, adj_faces, order);
+        assert(order.minCoeff() == 0);
+        assert(order.maxCoeff() == adj_faces.size() - 1);
+
+        auto& orientation = orientations[i];
+        orientation.resize(order.size());
+        std::transform(order.data(), order.data()+order.size(),
+                orientation.begin(), 
+                [&](size_t index) { return adj_faces[index] > 0; });
+        std::transform(order.data(), order.data()+order.size(),
+                order.data(), [&](size_t index) {
+                return P[compute_unsigned_index(adj_faces[index])];
+                } );
+    }
+
+    // Propagate winding number from infinity.
+    // Assuming infinity has winding number 0.
+    const size_t num_labels = C.maxCoeff() + 1;
+    const int INVALID = std::numeric_limits<int>::max();
+    patch_W.resize(num_patches, 2*num_labels);
+    patch_W.setConstant(INVALID);
+
+    size_t outer_facet_idx;
+    bool outer_facet_is_flipped;
+    Eigen::VectorXi face_indices(num_faces);
+    face_indices.setLinSpaced(num_faces, 0, num_faces-1);
+    igl::cgal::outer_facet(V, F, face_indices,
+            outer_facet_idx, outer_facet_is_flipped);
+    size_t outer_patch_idx = P[outer_facet_idx];
+    size_t outer_patch_label = C[outer_patch_idx];
+    patch_W.row(outer_patch_idx).setZero();
+    if (outer_facet_is_flipped) {
+        patch_W(outer_patch_idx, outer_patch_label*2) = -1;
+    } else {
+        patch_W(outer_patch_idx, outer_patch_label*2+1) = 1;
+    }
+
+    auto winding_num_assigned = [&](size_t patch_idx) -> bool{
+        return (patch_W.row(patch_idx).array() != INVALID).all();
+    };
+
+    std::queue<size_t> Q;
+    Q.push(outer_patch_idx);
+    while (!Q.empty()) {
+        const size_t curr_patch_idx = Q.front();
+        const size_t curr_patch_label = C[curr_patch_idx];
+        Q.pop();
+
+        const auto& adj_curves = patch_curve_adjacency[curr_patch_idx];
+        for (size_t curve_idx : adj_curves) {
+            const auto& order = orders[curve_idx];
+            const auto& orientation = orientations[curve_idx];
+            const size_t num_adj_patches = order.size();
+            assert(num_adj_patches == orientation.size());
+
+            size_t curr_i = std::numeric_limits<size_t>::max();
+            for (size_t i=0; i<num_adj_patches; i++) {
+                if (order[i] == curr_patch_idx) {
+                    curr_i = i;
+                    break;
+                }
+            }
+            assert(curr_i < num_adj_patches);
+            const bool curr_ori = orientation[curr_i];
+
+            const size_t next_i = curr_ori ? (curr_i+1) % num_adj_patches
+                : (curr_i+num_adj_patches-1)%num_adj_patches;
+            const size_t prev_i = curr_ori ?
+                (curr_i+num_adj_patches-1)%num_adj_patches
+                : (curr_i+1) % num_adj_patches;
+            const size_t next_patch_idx = order[next_i];
+            const size_t prev_patch_idx = order[prev_i];
+
+            if (!winding_num_assigned(next_patch_idx)) {
+                const bool next_ori = orientation[next_i];
+                const bool next_cons = next_ori != curr_ori;
+                const size_t next_patch_label = C[next_patch_idx];
+                for (size_t i=0; i<num_labels; i++) {
+                    const int shared_winding_number =
+                        patch_W(curr_patch_idx, i*2);
+
+                    if (i == next_patch_label) {
+                        // Truth table:
+                        // curr_ori  next_ori  wind_# inc
+                        // True      True      -1
+                        // True      False      1
+                        // False     True       1
+                        // False     False     -1
+
+                        patch_W(next_patch_idx, i*2+(next_cons ?0:1)) =
+                            shared_winding_number;
+                        patch_W(next_patch_idx, i*2+(next_cons ?1:0)) = 
+                            shared_winding_number + (next_cons ? 1:-1);
+                    } else {
+                        patch_W(next_patch_idx, i*2  ) = shared_winding_number;
+                        patch_W(next_patch_idx, i*2+1) = shared_winding_number;
+                    }
+                }
+                Q.push(next_patch_idx);
+            }
+            if (!winding_num_assigned(prev_patch_idx)) {
+                const bool prev_ori = orientation[prev_i];
+                const bool prev_cons = prev_ori != curr_ori;
+                const size_t prev_patch_label = C[prev_patch_idx];
+
+                for (size_t i=0; i<num_labels; i++) {
+                    const int shared_winding_number =
+                        patch_W(curr_patch_idx, i*2+1);
+
+                    if (i == prev_patch_label) {
+                        // Truth table:
+                        // curr_ori  next_ori  wind_# inc
+                        // True      True       1
+                        // True      False     -1
+                        // False     True      -1
+                        // False     False      1
+
+                        patch_W(prev_patch_idx, i*2+(prev_cons ?1:0)) =
+                            shared_winding_number;
+                        patch_W(prev_patch_idx, i*2+(prev_cons ?0:1)) =
+                            shared_winding_number + (prev_cons ? -1:1);
+                    } else {
+                        patch_W(prev_patch_idx, i*2  ) = shared_winding_number; 
+                        patch_W(prev_patch_idx, i*2+1) = shared_winding_number; 
+                    }
+                }
+                Q.push(prev_patch_idx);
+            }
+        }
+    }
+
+    using namespace propagate_winding_numbers_helper;
+    return winding_number_assignment_is_consistent(orders, orientations, patch_W);
+}
+
+template<
+typename DerivedV,
+typename DerivedF,
+typename DerivedC,
+typename DerivedW>
+IGL_INLINE bool igl::cgal::propagate_winding_numbers_single_component(
+        const Eigen::PlainObjectBase<DerivedV>& V,
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        const Eigen::PlainObjectBase<DerivedC>& C,
+        Eigen::PlainObjectBase<DerivedW>& W) {
+    typedef typename DerivedF::Scalar Index;
+    const size_t num_faces = F.rows();
+
+    // Extract unique edges.
+    std::vector<std::vector<size_t> > uE2E;
+    Eigen::Matrix<Index, Eigen::Dynamic, Eigen::Dynamic> E, uE;
+    Eigen::Matrix<Index, Eigen::Dynamic, 1> EMAP;
+    igl::unique_edge_map(F, E, uE, EMAP, uE2E);
+
+    // Extract manifold patches and intersection curves.
+    Eigen::VectorXi P;
+    std::vector<std::vector<size_t> > intersection_curves;
+    size_t num_patches =
+        igl::extract_manifold_patches(F, EMAP, uE2E, P);
+    igl::extract_non_manifold_edge_curves(F, EMAP, uE2E,
+            intersection_curves);
+    assert(P.size() == num_faces);
+    assert(P.maxCoeff() + 1 == num_patches);
+
+    Eigen::VectorXi labels(num_patches);
+    const int INVALID = std::numeric_limits<int>::max();
+    labels.setConstant(INVALID);
+    for (size_t i=0; i<num_faces; i++) {
+        if (labels[P[i]] == INVALID) {
+            labels[P[i]] = C[i];
+        } else {
+            assert(labels[P[i]] == C[i]);
+        }
+    }
+    assert((labels.array() != INVALID).all());
+    const size_t num_labels = labels.maxCoeff()+1;
+
+    Eigen::MatrixXi winding_numbers;
+    bool is_consistent =
+        igl::cgal::propagate_winding_numbers_single_component_patch_wise(
+            V, F, uE, uE2E, labels, P, intersection_curves, winding_numbers);
+    assert(winding_numbers.rows() == num_patches);
+    assert(winding_numbers.cols() == 2 * num_labels);
+
+    W.resize(num_faces, 2*num_labels);
+    for (size_t i=0; i<num_faces; i++) {
+        W.row(i) = winding_numbers.row(P[i]);
+    }
+
+    return is_consistent;
+}
+
+template<
+typename DerivedV,
+typename DerivedF,
+typename DerivedW>
+IGL_INLINE bool igl::cgal::propagate_winding_numbers_single_component(
+        const Eigen::PlainObjectBase<DerivedV>& V,
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        Eigen::PlainObjectBase<DerivedW>& W) {
+    const size_t num_faces = F.rows();
+    Eigen::VectorXi C(num_faces);
+    C.setZero();
+    return igl::cgal::propagate_winding_numbers_single_component(V, F, C, W);
+}
+
+template<
+typename DerivedV,
+typename DerivedF,
+typename DerivedL,
+typename DerivedW>
+IGL_INLINE void igl::cgal::propagate_winding_numbers(
+        const Eigen::PlainObjectBase<DerivedV>& V,
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        const Eigen::PlainObjectBase<DerivedL>& labels,
+        Eigen::PlainObjectBase<DerivedW>& W) {
+    typedef typename DerivedF::Scalar Index;
+    const size_t num_faces = F.rows();
+
+    // Extract unique edges.
+    std::vector<std::vector<size_t> > uE2E;
+    Eigen::Matrix<Index, Eigen::Dynamic, Eigen::Dynamic> E, uE;
+    Eigen::Matrix<Index, Eigen::Dynamic, 1> EMAP;
+    igl::unique_edge_map(F, E, uE, EMAP, uE2E);
+
+    // Check to make sure there is no boundaries and no non-manifold edges with
+    // odd number of adjacent faces.
+    for (const auto& adj_faces : uE2E) {
+        if (adj_faces.size() % 2 == 1) {
+            std::stringstream err_msg;
+            err_msg << "Input mesh contains odd number of faces "
+                << "sharing a single edge" << std::endl;
+            err_msg << "Indicating the input mesh does not represent a valid volume"
+                << ", and winding number cannot be propagated." << std::endl;
+            throw std::runtime_error(err_msg.str());
+        }
+    }
+
+    // Gather connected components.
+    std::vector<std::vector<std::vector<Index > > > TT,_1;
+    triangle_triangle_adjacency(E,EMAP,uE2E,false,TT,_1);
+    Eigen::VectorXi counts;
+    Eigen::VectorXi C;
+    igl::facet_components(TT,C,counts);
+
+    const size_t num_components = counts.size();
+    std::vector<std::vector<size_t> > components(num_components);
+    for (size_t i=0; i<num_faces; i++) {
+        components[C[i]].push_back(i);
+    }
+    std::vector<Eigen::MatrixXi> comp_faces(num_components);
+    std::vector<Eigen::VectorXi> comp_labels(num_components);
+    for (size_t i=0; i<num_components; i++) {
+        const auto& comp = components[i];
+        auto& faces = comp_faces[i];
+        auto& c_labels = comp_labels[i];
+        const size_t comp_size = comp.size();
+        faces.resize(comp_size, 3);
+        c_labels.resize(comp_size);
+        for (size_t j=0; j<comp_size; j++) {
+            faces.row(j) = F.row(comp[j]);
+            c_labels[j] = labels[comp[j]];
+        }
+    }
+
+    // Compute winding number for each component.
+    const size_t num_labels = labels.maxCoeff()+1;
+    W.resize(num_faces, 2*num_labels);
+    W.setZero();
+    std::vector<Eigen::MatrixXi> Ws(num_components);
+    for (size_t i=0; i<num_components; i++) {
+        bool is_consistent =
+            propagate_winding_numbers_single_component(V, comp_faces[i], comp_labels[i], Ws[i]);
+        const size_t num_labels_in_i = comp_labels[i].maxCoeff()+1;
+        const size_t num_faces_in_comp = comp_faces[i].rows();
+        assert(Ws[i].cols() == num_labels_in_i*2);
+        assert(Ws[i].rows() == num_faces_in_comp);
+        const auto& comp = components[i];
+        for (size_t j=0; j<num_faces_in_comp; j++) {
+            const size_t fid = comp[j];
+            W.block(fid, 0, 1, num_labels_in_i*2) = Ws[i].row(j);
+        }
+
+        if (!is_consistent) {
+            std::stringstream err_msg;
+            err_msg << "Component " << i
+                << " has inconsistant winding number assignment." << std::endl;
+            throw std::runtime_error(err_msg.str());
+        }
+    }
+
+    auto sample_component = [&](size_t cid) {
+        const auto& f = comp_faces[cid].row(0).eval();
+        return ((V.row(f[0]) + V.row(f[1]) + V.row(f[2])) / 3.0).eval();
+    };
+
+    std::vector<Eigen::MatrixXi> nested_Ws = Ws;
+    Eigen::MatrixXi ambient_correction(num_components, 2*num_labels);
+    ambient_correction.setZero();
+    for (size_t i=0; i<num_components; i++) {
+        DerivedV samples(num_components-1, 3);
+        Eigen::VectorXi is_inside;
+        auto index_without_i = [&](size_t index) {
+            return index < i ? index:index-1;
+        };
+        for (size_t j=0; j<num_components; j++) {
+            if (i == j) continue;
+            samples.row(index_without_i(j)) = sample_component(j);
+        }
+        Eigen::VectorXi fids;
+        Eigen::Matrix<bool, Eigen::Dynamic, 1> orientation;
+        igl::cgal::closest_facet(V, comp_faces[i], samples,
+                fids, orientation);
+
+        const auto& comp = components[i];
+        for (size_t j=0; j<num_components; j++) {
+            if (i == j) continue;
+            const size_t index = index_without_i(j);
+            const size_t fid = fids(index, 0);
+            const bool ori = orientation(index, 0);
+            for (size_t k=0; k<num_labels; k++) {
+                const int correction = W(comp[fid], k*2+(ori?0:1));
+                ambient_correction(j, k*2  ) += correction;
+                ambient_correction(j, k*2+1) += correction;
+            }
+        }
+    }
+
+    for (size_t i=0; i<num_components; i++) {
+        const auto& comp = components[i];
+        const auto& correction = ambient_correction.row(i).eval();
+        for (const auto& fid : comp) {
+            W.row(fid) += correction;
+        }
+    }
+}
+

+ 123 - 0
include/igl/cgal/propagate_winding_numbers.h

@@ -0,0 +1,123 @@
+#ifndef IGL_CGAL_PROPAGATE_WINDING_NUMBERS_H
+#define IGL_CGAL_PROPAGATE_WINDING_NUMBERS_H
+#include "../igl_inline.h"
+#include <Eigen/Core>
+#include <vector>
+
+namespace igl {
+    namespace cgal {
+        // Computer winding number on each side of each patch.
+        //
+        // Inputs:
+        //   V  #V by 3 list of vertices.
+        //   F  #F by 3 list of trinalges.
+        //   uE #uE by 2 list of undirected edges.
+        //   uE2E  #uE list of lists mapping each undirected edge to directed
+        //         edges.
+        //   C  #P list of component indices.
+        //   P  #F list of patch indices.
+        //   intersection_curves  A list of non-manifold curves that separates
+        //                        the mesh into patches.
+        //
+        // Outputs:
+        //   patch_W  #P by k*2 list of winding numbers on each side of a
+        //            patch for each component.
+        template<
+            typename DerivedV,
+            typename DerivedF,
+            typename DeriveduE,
+            typename uE2EType,
+            typename DerivedC,
+            typename DerivedP,
+            typename DerivedW >
+        IGL_INLINE bool propagate_winding_numbers_single_component_patch_wise(
+                const Eigen::PlainObjectBase<DerivedV>& V,
+                const Eigen::PlainObjectBase<DerivedF>& F,
+                const Eigen::PlainObjectBase<DeriveduE>& uE,
+                const std::vector<std::vector<uE2EType> >& uE2E,
+                const Eigen::PlainObjectBase<DerivedC>& C,
+                const Eigen::PlainObjectBase<DerivedP>& P,
+                const std::vector<std::vector<size_t> >& intersection_curves,
+                Eigen::PlainObjectBase<DerivedW>& patch_W);
+
+        // Compute winding number on each side of the face.
+        //
+        // This method assumes the input mesh (V, F, I) forms a single connected
+        // component.
+        //
+        // Inputs:
+        //   V  #V by 3 list of vertex positions.
+        //   F  #F by 3 list of triangle indices into V.
+        //   C  #F list of effective face indices ranging from 0 to k-1.
+        //
+        // Output:
+        //   W  #F by 2*k list of winding numbers.  W(i,2*j) is the winding
+        //   number of submesh j on the positive side of facet i, and
+        //   W(i, 2*j+1) is the winding number of submesh j on the negative
+        //   side of facet i.
+        //
+        // Returns:
+        //   True iff integer winding number can be consistently assigned.
+        template<
+            typename DerivedV,
+            typename DerivedF,
+            typename DerivedC,
+            typename DerivedW>
+        IGL_INLINE bool propagate_winding_numbers_single_component(
+                const Eigen::PlainObjectBase<DerivedV>& V,
+                const Eigen::PlainObjectBase<DerivedF>& F,
+                const Eigen::PlainObjectBase<DerivedC>& C,
+                Eigen::PlainObjectBase<DerivedW>& W);
+
+        // Compute the winding number on each side of the face.
+        // This method assumes the input mesh (V, F) forms a single connected
+        // component.
+        //
+        // Inputs:
+        //   V  #V by 3 list of vertex positions.
+        //   F  #F by 3 list of triangle indices into V.
+        //
+        // Output:
+        //   W  #F by 2 list of winding numbers.  W(i,0) is the winding number
+        //   on the positive side of facet i, and W(i, 1) is the winding
+        //   number on the negative side of facet I[i].
+        template<
+            typename DerivedV,
+            typename DerivedF,
+            typename DerivedW>
+        IGL_INLINE bool propagate_winding_numbers_single_component(
+                const Eigen::PlainObjectBase<DerivedV>& V,
+                const Eigen::PlainObjectBase<DerivedF>& F,
+                Eigen::PlainObjectBase<DerivedW>& W);
+
+        // Compute winding number on each side of the face.  The input mesh
+        // could contain multiple connected components.  The input mesh must
+        // representa the boundary of a valid 3D volume, which means it is
+        // closed, consistently oriented and induces integer winding numbers.
+        //
+        // Inputs:
+        //   V  #V by 3 list of vertex positions.
+        //   F  #F by 3 list of triangle indices into V.
+        //   C  #F list of facet labels.
+        //
+        // Output:
+        //   W  #I by 2 list of winding numbers.  W(i,0) is the winding number
+        //   on the positive side of facet I[i], and W(i, 1) is the winding
+        //   number on the negative side of facet I[i].
+        template<
+            typename DerivedV,
+            typename DerivedF,
+            typename DerivedL,
+            typename DerivedW>
+        IGL_INLINE void propagate_winding_numbers(
+                const Eigen::PlainObjectBase<DerivedV>& V,
+                const Eigen::PlainObjectBase<DerivedF>& F,
+                const Eigen::PlainObjectBase<DerivedL>& labels,
+                Eigen::PlainObjectBase<DerivedW>& W);
+    }
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "propagate_winding_numbers.cpp"
+#endif
+#endif

+ 68 - 0
include/igl/extract_manifold_patches.cpp

@@ -0,0 +1,68 @@
+#include "extract_manifold_patches.h"
+#include <cassert>
+#include <limits>
+#include <queue>
+
+template<
+typename DerivedF,
+typename DerivedEMAP,
+typename uE2EType,
+typename DerivedP>
+IGL_INLINE size_t igl::extract_manifold_patches(
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        const Eigen::PlainObjectBase<DerivedEMAP>& EMAP,
+        const std::vector<std::vector<uE2EType> >& uE2E,
+        Eigen::PlainObjectBase<DerivedP>& P) {
+    assert(F.cols() == 3);
+    const size_t num_faces = F.rows();
+
+    auto edge_index_to_face_index = [&](size_t ei) { return ei % num_faces; };
+    auto face_and_corner_index_to_edge_index = [&](size_t fi, size_t ci) {
+        return ci*num_faces + fi;
+    };
+    auto is_manifold_edge = [&](size_t fi, size_t ci) {
+        const size_t ei = face_and_corner_index_to_edge_index(fi, ci);
+        return uE2E[EMAP(ei, 0)].size() == 2;
+    };
+    auto get_adj_face_index = [&](size_t fi, size_t ci) -> size_t {
+        const size_t ei = face_and_corner_index_to_edge_index(fi, ci);
+        const auto& adj_faces = uE2E[EMAP(ei, 0)];
+        assert(adj_faces.size() == 2);
+        if (adj_faces[0] == ei) {
+            return edge_index_to_face_index(adj_faces[1]);
+        } else {
+            assert(adj_faces[1] == ei);
+            return edge_index_to_face_index(adj_faces[0]);
+        }
+    };
+
+    typedef typename DerivedP::Scalar Scalar;
+    const Scalar INVALID = std::numeric_limits<Scalar>::max();
+    P.resize(num_faces,1);
+    P.setConstant(INVALID);
+    size_t num_patches = 0;
+    for (size_t i=0; i<num_faces; i++) {
+        if (P(i,0) != INVALID) continue;
+
+        std::queue<size_t> Q;
+        Q.push(i);
+        P(i,0) = num_patches;
+        while (!Q.empty()) {
+            const size_t fid = Q.front();
+            Q.pop();
+            for (size_t j=0; j<3; j++) {
+                if (is_manifold_edge(fid, j)) {
+                    const size_t adj_fid = get_adj_face_index(fid, j);
+                    if (P(adj_fid,0) == INVALID) {
+                        Q.push(adj_fid);
+                        P(adj_fid,0) = num_patches;
+                    }
+                }
+            }
+        }
+        num_patches++;
+    }
+    assert((P.array() != INVALID).all());
+
+    return num_patches;
+}

+ 42 - 0
include/igl/extract_manifold_patches.h

@@ -0,0 +1,42 @@
+#ifndef IGL_EXTRACT_MANIFOLD_PATCHES
+#define IGL_EXTRACT_MANIFOLD_PATCHES
+
+#include "igl_inline.h"
+#include <Eigen/Dense>
+#include <vector>
+
+namespace igl {
+    // Extract a set of maximal manifold patches from a given mesh.
+    // A maximal manifold patch is a subset of the input faces that is
+    // connected and manifold, and it cannot be expanded further by
+    // including more faces.
+    //
+    // Assumes the input mesh have all self-intersection resolved.  See
+    // ``igl::cgal::remesh_self_intersection`` for more details.
+    //
+    // Inputs:
+    //   F  #F by 3 list representing triangles.
+    //   EMAP  #F*3 list of indices of unique undirected edges.
+    //   uE2E  #uE list of lists of indices into E of coexisting edges.
+    //
+    // Output:
+    //   P  #F list of patch incides.
+    //
+    // Returns:
+    //   number of manifold patches.
+    template <
+        typename DerivedF,
+        typename DerivedEMAP,
+        typename uE2EType,
+        typename DerivedP>
+    IGL_INLINE size_t extract_manifold_patches(
+            const Eigen::PlainObjectBase<DerivedF>& F,
+            const Eigen::PlainObjectBase<DerivedEMAP>& EMAP,
+            const std::vector<std::vector<uE2EType> >& uE2E,
+            Eigen::PlainObjectBase<DerivedP>& P);
+}
+#ifndef IGL_STATIC_LIBRARY
+#  include "extract_manifold_patches.cpp"
+#endif
+
+#endif

+ 116 - 0
include/igl/extract_non_manifold_edge_curves.cpp

@@ -0,0 +1,116 @@
+#include "extract_non_manifold_edge_curves.h"
+#include <algorithm>
+#include <cassert>
+#include <list>
+#include <vector>
+#include <unordered_map>
+
+template<
+typename DerivedF,
+typename DerivedEMAP,
+typename uE2EType >
+IGL_INLINE void igl::extract_non_manifold_edge_curves(
+        const Eigen::PlainObjectBase<DerivedF>& F,
+        const Eigen::PlainObjectBase<DerivedEMAP>& EMAP,
+        const std::vector<std::vector<uE2EType> >& uE2E,
+        std::vector<std::vector<size_t> >& curves) {
+    const size_t num_faces = F.rows();
+    assert(F.cols() == 3);
+    typedef std::pair<size_t, size_t> Edge;
+    auto edge_index_to_face_index = [&](size_t ei) { return ei % num_faces; };
+    auto edge_index_to_corner_index = [&](size_t ei) { return ei / num_faces; };
+    auto get_edge_end_points = [&](size_t ei, size_t& s, size_t& d) {
+        const size_t fi = edge_index_to_face_index(ei);
+        const size_t ci = edge_index_to_corner_index(ei);
+        s = F(fi, (ci+1)%3);
+        d = F(fi, (ci+2)%3);
+    };
+
+    curves.clear();
+    const size_t num_unique_edges = uE2E.size();
+    std::unordered_multimap<size_t, size_t> vertex_edge_adjacency;
+    std::vector<size_t> non_manifold_edges;
+    for (size_t i=0; i<num_unique_edges; i++) {
+        const auto& adj_edges = uE2E[i];
+        if (adj_edges.size() == 2) continue;
+
+        const size_t ei = adj_edges[0];
+        size_t s,d;
+        get_edge_end_points(ei, s, d);
+        vertex_edge_adjacency.insert({{s, i}, {d, i}});
+        non_manifold_edges.push_back(i);
+        assert(vertex_edge_adjacency.count(s) > 0);
+        assert(vertex_edge_adjacency.count(d) > 0);
+    }
+
+    auto expand_forward = [&](std::list<size_t>& edge_curve,
+            size_t& front_vertex, size_t& end_vertex) {
+        while(vertex_edge_adjacency.count(front_vertex) == 2 &&
+                front_vertex != end_vertex) {
+            auto adj_edges = vertex_edge_adjacency.equal_range(front_vertex);
+            for (auto itr = adj_edges.first; itr!=adj_edges.second; itr++) {
+                const size_t uei = itr->second;
+                assert(uE2E.at(uei).size() > 2);
+                const size_t ei = uE2E[uei][0];
+                if (uei == edge_curve.back()) continue;
+                size_t s,d;
+                get_edge_end_points(ei, s, d);
+                edge_curve.push_back(uei);
+                if (s == front_vertex) {
+                    front_vertex = d;
+                } else if (d == front_vertex) {
+                    front_vertex = s;
+                } else {
+                    throw "Invalid vertex/edge adjacency!";
+                }
+                break;
+            }
+        }
+    };
+
+    auto expand_backward = [&](std::list<size_t>& edge_curve,
+            size_t& front_vertex, size_t& end_vertex) {
+        while(vertex_edge_adjacency.count(front_vertex) == 2 &&
+                front_vertex != end_vertex) {
+            auto adj_edges = vertex_edge_adjacency.equal_range(front_vertex);
+            for (auto itr = adj_edges.first; itr!=adj_edges.second; itr++) {
+                const size_t uei = itr->second;
+                assert(uE2E.at(uei).size() > 2);
+                const size_t ei = uE2E[uei][0];
+                if (uei == edge_curve.front()) continue;
+                size_t s,d;
+                get_edge_end_points(ei, s, d);
+                edge_curve.push_front(uei);
+                if (s == front_vertex) {
+                    front_vertex = d;
+                } else if (d == front_vertex) {
+                    front_vertex = s;
+                } else {
+                    throw "Invalid vertex/edge adjcency!";
+                }
+                break;
+            }
+        }
+    };
+
+    std::vector<bool> visited(num_unique_edges, false);
+    for (const size_t i : non_manifold_edges) {
+        if (visited[i]) continue;
+        std::list<size_t> edge_curve;
+        edge_curve.push_back(i);
+
+        const auto& adj_edges = uE2E[i];
+        assert(adj_edges.size() != 2);
+        const size_t ei = adj_edges[0];
+        size_t s,d;
+        get_edge_end_points(ei, s, d);
+
+        expand_forward(edge_curve, d, s);
+        expand_backward(edge_curve, s, d);
+        curves.emplace_back(edge_curve.begin(), edge_curve.end());
+        for (auto itr = edge_curve.begin(); itr!=edge_curve.end(); itr++) {
+            visited[*itr] = true;
+        }
+    }
+
+}

+ 40 - 0
include/igl/extract_non_manifold_edge_curves.h

@@ -0,0 +1,40 @@
+#ifndef IGL_CGAL_EXTRACT_NON_MANIFOLD_EDGE_CURVES
+#define IGL_CGAL_EXTRACT_NON_MANIFOLD_EDGE_CURVES
+
+#include "igl_inline.h"
+#include <Eigen/Dense>
+#include <vector>
+
+namespace igl {
+    // Extract non-manifold curves from a given mesh.
+    // A non-manifold curves are a set of connected non-manifold edges that
+    // does not touch other non-manifold edges except at the end points.
+    // They are also maximal in the sense that they cannot be expanded by
+    // including more edges.
+    //
+    // Assumes the input mesh have all self-intersection resolved.  See
+    // ``igl::cgal::remesh_self_intersection`` for more details.
+    //
+    // Inputs:
+    //   F  #F by 3 list representing triangles.
+    //   EMAP  #F*3 list of indices of unique undirected edges.
+    //   uE2E  #uE list of lists of indices into E of coexisting edges.
+    //
+    // Output:
+    //   curves  An array of arries of unique edge indices.
+    template<
+        typename DerivedF,
+        typename DerivedEMAP,
+        typename uE2EType>
+    IGL_INLINE void extract_non_manifold_edge_curves(
+            const Eigen::PlainObjectBase<DerivedF>& F,
+            const Eigen::PlainObjectBase<DerivedEMAP>& EMAP,
+            const std::vector<std::vector<uE2EType> >& uE2E,
+            std::vector<std::vector<size_t> >& curves);
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "extract_non_manifold_edge_curves.cpp"
+#endif
+
+#endif