|
@@ -11,161 +11,201 @@
|
|
|
#include <unordered_set>
|
|
|
#include <cassert>
|
|
|
|
|
|
-// I have verified that this function produces the exact same output as
|
|
|
+// Yotam has verified that this function produces the exact same output as
|
|
|
// `find_seam_fast.py` for `cow_triangled.obj`.
|
|
|
-template <typename DerivedV, typename DerivedF, typename DerivedT>
|
|
|
+template <
|
|
|
+ typename DerivedV,
|
|
|
+ typename DerivedTC,
|
|
|
+ typename DerivedF,
|
|
|
+ typename DerivedFTC,
|
|
|
+ typename Derivedseams,
|
|
|
+ typename Derivedboundaries,
|
|
|
+ typename Derivedfoldovers>
|
|
|
IGL_INLINE void igl::seam_edges(
|
|
|
- const Eigen::PlainObjectBase<DerivedV>& V,
|
|
|
- const Eigen::PlainObjectBase<DerivedT>& TC,
|
|
|
- const Eigen::PlainObjectBase<DerivedF>& F,
|
|
|
- const Eigen::PlainObjectBase<DerivedF>& FTC,
|
|
|
- Eigen::PlainObjectBase<DerivedF>& seams,
|
|
|
- Eigen::PlainObjectBase<DerivedF>& boundaries,
|
|
|
- Eigen::PlainObjectBase<DerivedF>& foldovers
|
|
|
- )
|
|
|
+ const Eigen::PlainObjectBase<DerivedV>& V,
|
|
|
+ const Eigen::PlainObjectBase<DerivedTC>& TC,
|
|
|
+ const Eigen::PlainObjectBase<DerivedF>& F,
|
|
|
+ const Eigen::PlainObjectBase<DerivedFTC>& FTC,
|
|
|
+ Eigen::PlainObjectBase<Derivedseams>& seams,
|
|
|
+ Eigen::PlainObjectBase<Derivedboundaries>& boundaries,
|
|
|
+ Eigen::PlainObjectBase<Derivedfoldovers>& foldovers)
|
|
|
{
|
|
|
- // Assume triangles.
|
|
|
- assert( F.cols() == 3 );
|
|
|
- assert( F.cols() == FTC.cols() );
|
|
|
- assert( F.rows() == FTC.rows() );
|
|
|
+ // Assume triangles.
|
|
|
+ assert( F.cols() == 3 );
|
|
|
+ assert( F.cols() == FTC.cols() );
|
|
|
+ assert( F.rows() == FTC.rows() );
|
|
|
|
|
|
- // Assume 2D texture coordinates (foldovers tests).
|
|
|
- assert( TC.cols() == 2 );
|
|
|
- typedef Eigen::Matrix< typename DerivedT::Scalar, 2, 1 > Vector2S;
|
|
|
- // Computes the orientation of `c` relative to the line between `a` and `b`.
|
|
|
- // Assumes 2D vector input.
|
|
|
- // Based on: https://www.cs.cmu.edu/~quake/robust.html
|
|
|
- const auto& Orientation = []( const Vector2S& a, const Vector2S& b, const Vector2S& c ) -> typename DerivedT::Scalar
|
|
|
- {
|
|
|
- const Vector2S row0 = a - c;
|
|
|
- const Vector2S row1 = b - c;
|
|
|
- return row0(0)*row1(1) - row1(0)*row0(1);
|
|
|
- };
|
|
|
+ // Assume 2D texture coordinates (foldovers tests).
|
|
|
+ assert( TC.cols() == 2 );
|
|
|
+ typedef Eigen::Matrix< typename DerivedTC::Scalar, 2, 1 > Vector2S;
|
|
|
+ // Computes the orientation of `c` relative to the line between `a` and `b`.
|
|
|
+ // Assumes 2D vector input.
|
|
|
+ // Based on: https://www.cs.cmu.edu/~quake/robust.html
|
|
|
+ const auto& Orientation = [](
|
|
|
+ const Vector2S& a,
|
|
|
+ const Vector2S& b,
|
|
|
+ const Vector2S& c ) -> typename DerivedTC::Scalar
|
|
|
+ {
|
|
|
+ const Vector2S row0 = a - c;
|
|
|
+ const Vector2S row1 = b - c;
|
|
|
+ return row0(0)*row1(1) - row1(0)*row0(1);
|
|
|
+ };
|
|
|
|
|
|
- seams .setZero( 3*F.rows(), 4 );
|
|
|
- boundaries.setZero( 3*F.rows(), 2 );
|
|
|
- foldovers .setZero( 3*F.rows(), 4 );
|
|
|
+ seams .setZero( 3*F.rows(), 4 );
|
|
|
+ boundaries.setZero( 3*F.rows(), 2 );
|
|
|
+ foldovers .setZero( 3*F.rows(), 4 );
|
|
|
|
|
|
- int num_seams = 0;
|
|
|
- int num_boundaries = 0;
|
|
|
- int num_foldovers = 0;
|
|
|
+ int num_seams = 0;
|
|
|
+ int num_boundaries = 0;
|
|
|
+ int num_foldovers = 0;
|
|
|
|
|
|
- // A map from a pair of vertex indices to the index (face and endpoints)
|
|
|
- // into face_position_indices.
|
|
|
- // The following should be true for every key, value pair:
|
|
|
- // key == face_position_indices[ value ]
|
|
|
- // This gives us a "reverse map" so that we can look up other face
|
|
|
- // attributes based on position edges.
|
|
|
- // The value are written in the format returned by numpy.where(),
|
|
|
- // which stores multi-dimensional indices such as array[a0,b0], array[a1,b1]
|
|
|
- // as ( (a0,a1), (b0,b1) ).
|
|
|
+ // A map from a pair of vertex indices to the index (face and endpoints)
|
|
|
+ // into face_position_indices.
|
|
|
+ // The following should be true for every key, value pair:
|
|
|
+ // key == face_position_indices[ value ]
|
|
|
+ // This gives us a "reverse map" so that we can look up other face
|
|
|
+ // attributes based on position edges.
|
|
|
+ // The value are written in the format returned by numpy.where(),
|
|
|
+ // which stores multi-dimensional indices such as array[a0,b0], array[a1,b1]
|
|
|
+ // as ( (a0,a1), (b0,b1) ).
|
|
|
|
|
|
- // We need to make a hash function for our directed edges.
|
|
|
- // We'll use i*V.rows() + j.
|
|
|
- typedef std::pair< typename DerivedF::Scalar, typename DerivedF::Scalar > directed_edge;
|
|
|
+ // We need to make a hash function for our directed edges.
|
|
|
+ // We'll use i*V.rows() + j.
|
|
|
+ typedef std::pair< typename DerivedF::Scalar, typename DerivedF::Scalar >
|
|
|
+ directed_edge;
|
|
|
const int numV = V.rows();
|
|
|
const int numF = F.rows();
|
|
|
- const auto& edge_hasher = [numV]( directed_edge const& e ) { return e.first*numV + e.second; };
|
|
|
- // When we pass a hash function object, we also need to specify the number of buckets.
|
|
|
- // The Euler characteristic says that the number of undirected edges is numV + numF -2*genus.
|
|
|
- std::unordered_map< directed_edge, std::pair< int, int >, decltype( edge_hasher ) > directed_position_edge2face_position_index( 2*( numV + numF ), edge_hasher );
|
|
|
- for( int fi = 0; fi < F.rows(); ++fi ) {
|
|
|
- for( int i = 0; i < 3; ++i ) {
|
|
|
- const int j = ( i+1 ) % 3;
|
|
|
- directed_position_edge2face_position_index[ std::make_pair( F(fi,i), F(fi,j) ) ] = std::make_pair( fi, i );
|
|
|
- }
|
|
|
+ const auto& edge_hasher =
|
|
|
+ [numV]( directed_edge const& e ) { return e.first*numV + e.second; };
|
|
|
+ // When we pass a hash function object, we also need to specify the number of
|
|
|
+ // buckets. The Euler characteristic says that the number of undirected edges
|
|
|
+ // is numV + numF -2*genus.
|
|
|
+ std::unordered_map<directed_edge,std::pair<int,int>,decltype(edge_hasher) >
|
|
|
+ directed_position_edge2face_position_index(2*( numV + numF ), edge_hasher);
|
|
|
+ for( int fi = 0; fi < F.rows(); ++fi )
|
|
|
+ {
|
|
|
+ for( int i = 0; i < 3; ++i )
|
|
|
+ {
|
|
|
+ const int j = ( i+1 ) % 3;
|
|
|
+ directed_position_edge2face_position_index[
|
|
|
+ std::make_pair( F(fi,i), F(fi,j) ) ] = std::make_pair( fi, i );
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // First find all undirected position edges (collect a canonical orientation of
|
|
|
- // the directed edges).
|
|
|
- std::unordered_set< directed_edge, decltype( edge_hasher ) > undirected_position_edges( numV + numF, edge_hasher );
|
|
|
- for( const auto& el : directed_position_edge2face_position_index ) {
|
|
|
- // The canonical orientation is the one where the smaller of
|
|
|
- // the two vertex indices is first.
|
|
|
- undirected_position_edges.insert( std::make_pair( std::min( el.first.first, el.first.second ), std::max( el.first.first, el.first.second ) ) );
|
|
|
- }
|
|
|
+ // First find all undirected position edges (collect a canonical orientation
|
|
|
+ // of the directed edges).
|
|
|
+ std::unordered_set< directed_edge, decltype( edge_hasher ) >
|
|
|
+ undirected_position_edges( numV + numF, edge_hasher );
|
|
|
+ for( const auto& el : directed_position_edge2face_position_index )
|
|
|
+ {
|
|
|
+ // The canonical orientation is the one where the smaller of
|
|
|
+ // the two vertex indices is first.
|
|
|
+ undirected_position_edges.insert( std::make_pair(
|
|
|
+ std::min( el.first.first, el.first.second ),
|
|
|
+ std::max( el.first.first, el.first.second ) ) );
|
|
|
+ }
|
|
|
|
|
|
- // Now we will iterate over all position edges.
|
|
|
- // Seam edges are the edges whose two opposite directed edges have different
|
|
|
- // texcoord indices (or one doesn't exist at all in the case of a mesh
|
|
|
- // boundary).
|
|
|
- for( const auto& vp_edge : undirected_position_edges ) {
|
|
|
- // We should only see canonical edges,
|
|
|
- // where the first vertex index is smaller.
|
|
|
- assert( vp_edge.first < vp_edge.second );
|
|
|
+ // Now we will iterate over all position edges.
|
|
|
+ // Seam edges are the edges whose two opposite directed edges have different
|
|
|
+ // texcoord indices (or one doesn't exist at all in the case of a mesh
|
|
|
+ // boundary).
|
|
|
+ for( const auto& vp_edge : undirected_position_edges )
|
|
|
+ {
|
|
|
+ // We should only see canonical edges,
|
|
|
+ // where the first vertex index is smaller.
|
|
|
+ assert( vp_edge.first < vp_edge.second );
|
|
|
|
|
|
- const auto vp_edge_reverse = std::make_pair( vp_edge.second, vp_edge.first );
|
|
|
- // If it and its opposite exist as directed edges, check if their
|
|
|
- // texture coordinate indices match.
|
|
|
- if( directed_position_edge2face_position_index.count( vp_edge ) &&
|
|
|
- directed_position_edge2face_position_index.count( vp_edge_reverse ) ) {
|
|
|
- const auto forwards = directed_position_edge2face_position_index[ vp_edge ];
|
|
|
- const auto backwards = directed_position_edge2face_position_index[ vp_edge_reverse ];
|
|
|
-
|
|
|
- // NOTE: They should never be equal.
|
|
|
- assert( forwards != backwards );
|
|
|
+ const auto vp_edge_reverse = std::make_pair(vp_edge.second, vp_edge.first);
|
|
|
+ // If it and its opposite exist as directed edges, check if their
|
|
|
+ // texture coordinate indices match.
|
|
|
+ if( directed_position_edge2face_position_index.count( vp_edge ) &&
|
|
|
+ directed_position_edge2face_position_index.count( vp_edge_reverse ) )
|
|
|
+ {
|
|
|
+ const auto forwards =
|
|
|
+ directed_position_edge2face_position_index[ vp_edge ];
|
|
|
+ const auto backwards =
|
|
|
+ directed_position_edge2face_position_index[ vp_edge_reverse ];
|
|
|
|
|
|
- // If the texcoord indices match (are similarly flipped),
|
|
|
- // this edge is not a seam. It could be a foldover.
|
|
|
- if( std::make_pair( FTC( forwards.first, forwards.second ), FTC( forwards.first, ( forwards.second+1 ) % 3 ) )
|
|
|
- ==
|
|
|
- std::make_pair( FTC( backwards.first, ( backwards.second+1 ) % 3 ), FTC( backwards.first, backwards.second ) )
|
|
|
- ) {
|
|
|
-
|
|
|
- // Check for foldovers in UV space.
|
|
|
- // Get the edge (a,b) and the two opposite vertices's texture coordinates.
|
|
|
- const Vector2S a = TC.row( FTC( forwards.first, forwards.second ) );
|
|
|
- const Vector2S b = TC.row( FTC( forwards.first, (forwards.second+1) % 3 ) );
|
|
|
- const Vector2S c_forwards = TC.row( FTC( forwards .first, (forwards .second+2) % 3 ) );
|
|
|
- const Vector2S c_backwards = TC.row( FTC( backwards.first, (backwards.second+2) % 3 ) );
|
|
|
- // If the opposite vertices' texture coordinates fall on the same side
|
|
|
- // of the edge, we have a UV-space foldover.
|
|
|
- const auto orientation_forwards = Orientation( a, b, c_forwards );
|
|
|
- const auto orientation_backwards = Orientation( a, b, c_backwards );
|
|
|
- if( ( orientation_forwards > 0 && orientation_backwards > 0 ) ||
|
|
|
- ( orientation_forwards < 0 && orientation_backwards < 0 )
|
|
|
- ) {
|
|
|
- foldovers( num_foldovers, 0 ) = forwards.first;
|
|
|
- foldovers( num_foldovers, 1 ) = forwards.second;
|
|
|
- foldovers( num_foldovers, 2 ) = backwards.first;
|
|
|
- foldovers( num_foldovers, 3 ) = backwards.second;
|
|
|
- num_foldovers += 1;
|
|
|
- }
|
|
|
- }
|
|
|
+ // NOTE: They should never be equal.
|
|
|
+ assert( forwards != backwards );
|
|
|
|
|
|
- // Otherwise, we have a non-matching seam edge.
|
|
|
- else {
|
|
|
- seams( num_seams, 0 ) = forwards.first;
|
|
|
- seams( num_seams, 1 ) = forwards.second;
|
|
|
- seams( num_seams, 2 ) = backwards.first;
|
|
|
- seams( num_seams, 3 ) = backwards.second;
|
|
|
- num_seams += 1;
|
|
|
- }
|
|
|
- }
|
|
|
- // Otherwise, the edge and its opposite aren't both in the directed
|
|
|
- // edges. One of them should be.
|
|
|
- else if( directed_position_edge2face_position_index.count( vp_edge ) ) {
|
|
|
- const auto forwards = directed_position_edge2face_position_index[ vp_edge ];
|
|
|
- boundaries( num_boundaries, 0 ) = forwards.first;
|
|
|
- boundaries( num_boundaries, 1 ) = forwards.second;
|
|
|
- num_boundaries += 1;
|
|
|
- }
|
|
|
- else if( directed_position_edge2face_position_index.count( vp_edge_reverse ) ) {
|
|
|
- const auto backwards = directed_position_edge2face_position_index[ vp_edge_reverse ];
|
|
|
- boundaries( num_boundaries, 0 ) = backwards.first;
|
|
|
- boundaries( num_boundaries, 1 ) = backwards.second;
|
|
|
- num_boundaries += 1;
|
|
|
- }
|
|
|
- else {
|
|
|
- // This should never happen! One of these two must have been seen.
|
|
|
- assert(
|
|
|
- directed_position_edge2face_position_index.count( vp_edge ) ||
|
|
|
- directed_position_edge2face_position_index.count( vp_edge_reverse )
|
|
|
- );
|
|
|
+ // If the texcoord indices match (are similarly flipped),
|
|
|
+ // this edge is not a seam. It could be a foldover.
|
|
|
+ if(
|
|
|
+ std::make_pair(
|
|
|
+ FTC( forwards.first, forwards.second ),
|
|
|
+ FTC( forwards.first, ( forwards.second+1 ) % 3 ) )
|
|
|
+ ==
|
|
|
+ std::make_pair(
|
|
|
+ FTC( backwards.first, ( backwards.second+1 ) % 3 ),
|
|
|
+ FTC( backwards.first, backwards.second ) ))
|
|
|
+ {
|
|
|
+ // Check for foldovers in UV space.
|
|
|
+ // Get the edge (a,b) and the two opposite vertices's texture
|
|
|
+ // coordinates.
|
|
|
+ const Vector2S a = TC.row( FTC( forwards.first, forwards.second ) );
|
|
|
+ const Vector2S b =
|
|
|
+ TC.row( FTC( forwards.first, (forwards.second+1) % 3 ) );
|
|
|
+ const Vector2S c_forwards =
|
|
|
+ TC.row( FTC( forwards .first, (forwards .second+2) % 3 ) );
|
|
|
+ const Vector2S c_backwards =
|
|
|
+ TC.row( FTC( backwards.first, (backwards.second+2) % 3 ) );
|
|
|
+ // If the opposite vertices' texture coordinates fall on the same side
|
|
|
+ // of the edge, we have a UV-space foldover.
|
|
|
+ const auto orientation_forwards = Orientation( a, b, c_forwards );
|
|
|
+ const auto orientation_backwards = Orientation( a, b, c_backwards );
|
|
|
+ if( ( orientation_forwards > 0 && orientation_backwards > 0 ) ||
|
|
|
+ ( orientation_forwards < 0 && orientation_backwards < 0 )
|
|
|
+ ) {
|
|
|
+ foldovers( num_foldovers, 0 ) = forwards.first;
|
|
|
+ foldovers( num_foldovers, 1 ) = forwards.second;
|
|
|
+ foldovers( num_foldovers, 2 ) = backwards.first;
|
|
|
+ foldovers( num_foldovers, 3 ) = backwards.second;
|
|
|
+ num_foldovers += 1;
|
|
|
}
|
|
|
+ }
|
|
|
+ // Otherwise, we have a non-matching seam edge.
|
|
|
+ else
|
|
|
+ {
|
|
|
+ seams( num_seams, 0 ) = forwards.first;
|
|
|
+ seams( num_seams, 1 ) = forwards.second;
|
|
|
+ seams( num_seams, 2 ) = backwards.first;
|
|
|
+ seams( num_seams, 3 ) = backwards.second;
|
|
|
+ num_seams += 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Otherwise, the edge and its opposite aren't both in the directed edges.
|
|
|
+ // One of them should be.
|
|
|
+ else if( directed_position_edge2face_position_index.count( vp_edge ) )
|
|
|
+ {
|
|
|
+ const auto forwards = directed_position_edge2face_position_index[vp_edge];
|
|
|
+ boundaries( num_boundaries, 0 ) = forwards.first;
|
|
|
+ boundaries( num_boundaries, 1 ) = forwards.second;
|
|
|
+ num_boundaries += 1;
|
|
|
+ } else if(
|
|
|
+ directed_position_edge2face_position_index.count( vp_edge_reverse ) )
|
|
|
+ {
|
|
|
+ const auto backwards =
|
|
|
+ directed_position_edge2face_position_index[ vp_edge_reverse ];
|
|
|
+ boundaries( num_boundaries, 0 ) = backwards.first;
|
|
|
+ boundaries( num_boundaries, 1 ) = backwards.second;
|
|
|
+ num_boundaries += 1;
|
|
|
+ } else {
|
|
|
+ // This should never happen! One of these two must have been seen.
|
|
|
+ assert(
|
|
|
+ directed_position_edge2face_position_index.count( vp_edge ) ||
|
|
|
+ directed_position_edge2face_position_index.count( vp_edge_reverse )
|
|
|
+ );
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- seams .conservativeResize( num_seams, Eigen::NoChange_t() );
|
|
|
- boundaries.conservativeResize( num_boundaries, Eigen::NoChange_t() );
|
|
|
- foldovers .conservativeResize( num_foldovers, Eigen::NoChange_t() );
|
|
|
+ seams .conservativeResize( num_seams, Eigen::NoChange_t() );
|
|
|
+ boundaries.conservativeResize( num_boundaries, Eigen::NoChange_t() );
|
|
|
+ foldovers .conservativeResize( num_foldovers, Eigen::NoChange_t() );
|
|
|
}
|
|
|
+
|
|
|
+#ifdef IGL_STATIC_LIBRARY
|
|
|
+// Explicit template specialization
|
|
|
+// generated by autoexplicit.sh
|
|
|
+template void igl::seam_edges<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&);
|
|
|
+#endif
|