seam_edges.cpp 8.7 KB

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