seam_edges.cpp 9.1 KB

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