seam_edges.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // This file is part of libigl, a simple c++ geometry processing library.
  2. //
  3. // Copyright (C) 2016 Yotam Gingold <yotam@yotamgingold.com>
  4. //
  5. // This Source Code Form is subject to the terms of the Mozilla Public License
  6. // v. 2.0. If a copy of the MPL was not distributed with this file, You can
  7. // obtain one at http://mozilla.org/MPL/2.0/.
  8. #include "seam_edges.h"
  9. #include <unordered_map>
  10. #include <unordered_set>
  11. #include <cassert>
  12. // I have verified that this function produces the exact same output as
  13. // `find_seam_fast.py` for `cow_triangled.obj`.
  14. template <typename DerivedV, typename DerivedF, typename DerivedT>
  15. IGL_INLINE void igl::seam_edges(
  16. const Eigen::PlainObjectBase<DerivedV>& V,
  17. const Eigen::PlainObjectBase<DerivedT>& TC,
  18. const Eigen::PlainObjectBase<DerivedF>& F,
  19. const Eigen::PlainObjectBase<DerivedF>& FTC,
  20. Eigen::PlainObjectBase<DerivedF>& seams,
  21. Eigen::PlainObjectBase<DerivedF>& boundaries,
  22. Eigen::PlainObjectBase<DerivedF>& foldovers
  23. )
  24. {
  25. // Assume triangles.
  26. assert( F.cols() == 3 );
  27. assert( F.cols() == FTC.cols() );
  28. assert( F.rows() == FTC.rows() );
  29. // Assume 2D texture coordinates (foldovers tests).
  30. assert( TC.cols() == 2 );
  31. typedef Eigen::Matrix< typename DerivedT::Scalar, 2, 1 > Vector2S;
  32. // Computes the orientation of `c` relative to the line between `a` and `b`.
  33. // Assumes 2D vector input.
  34. // Based on: https://www.cs.cmu.edu/~quake/robust.html
  35. const auto& Orientation = []( const Vector2S& a, const Vector2S& b, const Vector2S& c ) -> typename DerivedT::Scalar
  36. {
  37. const Vector2S row0 = a - c;
  38. const Vector2S row1 = b - c;
  39. return row0(0)*row1(1) - row1(0)*row0(1);
  40. };
  41. seams .setZero( 3*F.rows(), 4 );
  42. boundaries.setZero( 3*F.rows(), 2 );
  43. foldovers .setZero( 3*F.rows(), 4 );
  44. int num_seams = 0;
  45. int num_boundaries = 0;
  46. int num_foldovers = 0;
  47. // A map from a pair of vertex indices to the index (face and endpoints)
  48. // into face_position_indices.
  49. // The following should be true for every key, value pair:
  50. // key == face_position_indices[ value ]
  51. // This gives us a "reverse map" so that we can look up other face
  52. // attributes based on position edges.
  53. // The value are written in the format returned by numpy.where(),
  54. // which stores multi-dimensional indices such as array[a0,b0], array[a1,b1]
  55. // as ( (a0,a1), (b0,b1) ).
  56. // We need to make a hash function for our directed edges.
  57. // We'll use i*V.rows() + j.
  58. typedef std::pair< typename DerivedF::Scalar, typename DerivedF::Scalar > directed_edge;
  59. const int numV = V.rows();
  60. const int numF = F.rows();
  61. const auto& edge_hasher = [numV]( directed_edge const& e ) { return e.first*numV + e.second; };
  62. // When we pass a hash function object, we also need to specify the number of buckets.
  63. // The Euler characteristic says that the number of undirected edges is numV + numF -2*genus.
  64. std::unordered_map< directed_edge, std::pair< int, int >, decltype( edge_hasher ) > directed_position_edge2face_position_index( 2*( numV + numF ), edge_hasher );
  65. for( int fi = 0; fi < F.rows(); ++fi ) {
  66. for( int i = 0; i < 3; ++i ) {
  67. const int j = ( i+1 ) % 3;
  68. directed_position_edge2face_position_index[ std::make_pair( F(fi,i), F(fi,j) ) ] = std::make_pair( fi, i );
  69. }
  70. }
  71. // First find all undirected position edges (collect a canonical orientation of
  72. // the directed edges).
  73. std::unordered_set< directed_edge, decltype( edge_hasher ) > undirected_position_edges( numV + numF, edge_hasher );
  74. for( const auto& el : directed_position_edge2face_position_index ) {
  75. // The canonical orientation is the one where the smaller of
  76. // the two vertex indices is first.
  77. undirected_position_edges.insert( std::make_pair( std::min( el.first.first, el.first.second ), std::max( el.first.first, el.first.second ) ) );
  78. }
  79. // Now we will iterate over all position edges.
  80. // Seam edges are the edges whose two opposite directed edges have different
  81. // texcoord indices (or one doesn't exist at all in the case of a mesh
  82. // boundary).
  83. for( const auto& vp_edge : undirected_position_edges ) {
  84. // We should only see canonical edges,
  85. // where the first vertex index is smaller.
  86. assert( vp_edge.first < vp_edge.second );
  87. const auto vp_edge_reverse = std::make_pair( vp_edge.second, vp_edge.first );
  88. // If it and its opposite exist as directed edges, check if their
  89. // texture coordinate indices match.
  90. if( directed_position_edge2face_position_index.count( vp_edge ) &&
  91. directed_position_edge2face_position_index.count( vp_edge_reverse ) ) {
  92. const auto forwards = directed_position_edge2face_position_index[ vp_edge ];
  93. const auto backwards = directed_position_edge2face_position_index[ vp_edge_reverse ];
  94. // NOTE: They should never be equal.
  95. assert( forwards != backwards );
  96. // If the texcoord indices match (are similarly flipped),
  97. // this edge is not a seam. It could be a foldover.
  98. if( std::make_pair( FTC( forwards.first, forwards.second ), FTC( forwards.first, ( forwards.second+1 ) % 3 ) )
  99. ==
  100. std::make_pair( FTC( backwards.first, ( backwards.second+1 ) % 3 ), FTC( backwards.first, backwards.second ) )
  101. ) {
  102. // Check for foldovers in UV space.
  103. // Get the edge (a,b) and the two opposite vertices's texture coordinates.
  104. const Vector2S a = TC.row( FTC( forwards.first, forwards.second ) );
  105. const Vector2S b = TC.row( FTC( forwards.first, (forwards.second+1) % 3 ) );
  106. const Vector2S c_forwards = TC.row( FTC( forwards .first, (forwards .second+2) % 3 ) );
  107. const Vector2S c_backwards = TC.row( FTC( backwards.first, (backwards.second+2) % 3 ) );
  108. // If the opposite vertices' texture coordinates fall on the same side
  109. // of the edge, we have a UV-space foldover.
  110. const auto orientation_forwards = Orientation( a, b, c_forwards );
  111. const auto orientation_backwards = Orientation( a, b, c_backwards );
  112. if( ( orientation_forwards > 0 && orientation_backwards > 0 ) ||
  113. ( orientation_forwards < 0 && orientation_backwards < 0 )
  114. ) {
  115. foldovers( num_foldovers, 0 ) = forwards.first;
  116. foldovers( num_foldovers, 1 ) = forwards.second;
  117. foldovers( num_foldovers, 2 ) = backwards.first;
  118. foldovers( num_foldovers, 3 ) = backwards.second;
  119. num_foldovers += 1;
  120. }
  121. }
  122. // Otherwise, we have a non-matching seam edge.
  123. else {
  124. seams( num_seams, 0 ) = forwards.first;
  125. seams( num_seams, 1 ) = forwards.second;
  126. seams( num_seams, 2 ) = backwards.first;
  127. seams( num_seams, 3 ) = backwards.second;
  128. num_seams += 1;
  129. }
  130. }
  131. // Otherwise, the edge and its opposite aren't both in the directed
  132. // edges. One of them should be.
  133. else if( directed_position_edge2face_position_index.count( vp_edge ) ) {
  134. const auto forwards = directed_position_edge2face_position_index[ vp_edge ];
  135. boundaries( num_boundaries, 0 ) = forwards.first;
  136. boundaries( num_boundaries, 1 ) = forwards.second;
  137. num_boundaries += 1;
  138. }
  139. else if( directed_position_edge2face_position_index.count( vp_edge_reverse ) ) {
  140. const auto backwards = directed_position_edge2face_position_index[ vp_edge_reverse ];
  141. boundaries( num_boundaries, 0 ) = backwards.first;
  142. boundaries( num_boundaries, 1 ) = backwards.second;
  143. num_boundaries += 1;
  144. }
  145. else {
  146. // This should never happen! One of these two must have been seen.
  147. assert(
  148. directed_position_edge2face_position_index.count( vp_edge ) ||
  149. directed_position_edge2face_position_index.count( vp_edge_reverse )
  150. );
  151. }
  152. }
  153. seams .conservativeResize( num_seams, Eigen::NoChange_t() );
  154. boundaries.conservativeResize( num_boundaries, Eigen::NoChange_t() );
  155. foldovers .conservativeResize( num_foldovers, Eigen::NoChange_t() );
  156. }