ConformalMapGenus0.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. // David Eberly, Geometric Tools, Redmond WA 98052
  2. // Copyright (c) 1998-2020
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // https://www.boost.org/LICENSE_1_0.txt
  5. // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
  6. // Version: 4.0.2019.08.13
  7. #pragma once
  8. #include <Mathematics/Logger.h>
  9. #include <Mathematics/ETManifoldMesh.h>
  10. #include <Mathematics/LinearSystem.h>
  11. #include <Mathematics/Polynomial1.h>
  12. #include <Mathematics/Vector2.h>
  13. #include <Mathematics/Vector3.h>
  14. // Conformally map a 2-dimensional manifold mesh with the topology of a sphere
  15. // to a sphere. The algorithm is an implementation of the one in the paper
  16. // S.Haker, S.Angenent, A.Tannenbaum, R.Kikinis, G.Sapiro, and M.Halle.
  17. // Conformal surface parameterization for texture mapping,
  18. // IEEE Transactions on Visualization and Computer Graphics,
  19. // Volume 6, Number 2, pages 181–189, 2000
  20. // The paper is available at https://ieeexplore.ieee.org/document/856998 but
  21. // is not freely downloadable.
  22. namespace WwiseGTE
  23. {
  24. template <typename Real>
  25. class ConformalMapGenus0
  26. {
  27. public:
  28. // The input mesh should be a closed, manifold surface that has the
  29. // topology of a sphere (genus 0 surface).
  30. ConformalMapGenus0()
  31. :
  32. mSphereRadius(0.0f)
  33. {
  34. }
  35. ~ConformalMapGenus0()
  36. {
  37. }
  38. // The returned 'bool' value is 'true' whenever the conjugate gradient
  39. // algorithm converged. Even if it did not, the results might still
  40. // be acceptable.
  41. bool operator()(int numPositions, Vector3<Real> const* positions,
  42. int numTriangles, int const* indices, int punctureTriangle)
  43. {
  44. bool converged = true;
  45. mPlaneCoordinates.resize(numPositions);
  46. mSphereCoordinates.resize(numPositions);
  47. // Construct a triangle-edge representation of mesh.
  48. ETManifoldMesh graph;
  49. int const* currentIndex = indices;
  50. int t;
  51. for (t = 0; t < numTriangles; ++t)
  52. {
  53. int v0 = *currentIndex++;
  54. int v1 = *currentIndex++;
  55. int v2 = *currentIndex++;
  56. graph.Insert(v0, v1, v2);
  57. }
  58. auto const& emap = graph.GetEdges();
  59. // Construct the nondiagonal entries of the sparse matrix A.
  60. typename LinearSystem<Real>::SparseMatrix A;
  61. int v0, v1, v2, i;
  62. Vector3<Real> E0, E1;
  63. Real value;
  64. for (auto const& element : emap)
  65. {
  66. v0 = element.first.V[0];
  67. v1 = element.first.V[1];
  68. value = (Real)0;
  69. for (int j = 0; j < 2; ++j)
  70. {
  71. auto triangle = element.second->T[j].lock();
  72. for (i = 0; i < 3; ++i)
  73. {
  74. v2 = triangle->V[i];
  75. if (v2 != v0 && v2 != v1)
  76. {
  77. E0 = positions[v0] - positions[v2];
  78. E1 = positions[v1] - positions[v2];
  79. value += Dot(E0, E1) / Length(Cross(E0, E1));
  80. }
  81. }
  82. }
  83. value *= -(Real)0.5;
  84. std::array<int, 2> lookup = { v0, v1 };
  85. A[lookup] = value;
  86. }
  87. // Construct the diagonal entries of the sparse matrix A.
  88. std::vector<Real> tmp(numPositions, (Real)0);
  89. for (auto const& element : A)
  90. {
  91. tmp[element.first[0]] -= element.second;
  92. tmp[element.first[1]] -= element.second;
  93. }
  94. for (i = 0; i < numPositions; ++i)
  95. {
  96. std::array<int, 2> lookup = { i, i };
  97. A[lookup] = tmp[i];
  98. }
  99. LogAssert(static_cast<size_t>(numPositions) + emap.size() == A.size(), "Mismatched sizes.");
  100. // Construct the sparse column vector B.
  101. currentIndex = &indices[3 * punctureTriangle];
  102. v0 = *currentIndex++;
  103. v1 = *currentIndex++;
  104. v2 = *currentIndex++;
  105. Vector3<Real> V0 = positions[v0];
  106. Vector3<Real> V1 = positions[v1];
  107. Vector3<Real> V2 = positions[v2];
  108. Vector3<Real> E10 = V1 - V0;
  109. Vector3<Real> E20 = V2 - V0;
  110. Vector3<Real> E12 = V1 - V2;
  111. Vector3<Real> normal = Cross(E20, E10);
  112. Real len10 = Length(E10);
  113. Real invLen10 = (Real)1 / len10;
  114. Real twoArea = Length(normal);
  115. Real invLenNormal = (Real)1 / twoArea;
  116. Real invProd = invLen10 * invLenNormal;
  117. Real re0 = -invLen10;
  118. Real im0 = invProd * Dot(E12, E10);
  119. Real re1 = invLen10;
  120. Real im1 = invProd * Dot(E20, E10);
  121. Real re2 = (Real)0;
  122. Real im2 = -len10 * invLenNormal;
  123. // Solve the sparse system for the real parts.
  124. unsigned int const maxIterations = 1024;
  125. Real const tolerance = 1e-06f;
  126. std::fill(tmp.begin(), tmp.end(), (Real)0);
  127. tmp[v0] = re0;
  128. tmp[v1] = re1;
  129. tmp[v2] = re2;
  130. std::vector<Real> result(numPositions);
  131. unsigned int iterations = LinearSystem<Real>().SolveSymmetricCG(
  132. numPositions, A, tmp.data(), result.data(), maxIterations, tolerance);
  133. if (iterations >= maxIterations)
  134. {
  135. LogWarning("Conjugate gradient solver did not converge.");
  136. converged = false;
  137. }
  138. for (i = 0; i < numPositions; ++i)
  139. {
  140. mPlaneCoordinates[i][0] = result[i];
  141. }
  142. // Solve the sparse system for the imaginary parts.
  143. std::fill(tmp.begin(), tmp.end(), (Real)0);
  144. tmp[v0] = -im0;
  145. tmp[v1] = -im1;
  146. tmp[v2] = -im2;
  147. iterations = LinearSystem<Real>().SolveSymmetricCG(numPositions, A,
  148. tmp.data(), result.data(), maxIterations, tolerance);
  149. if (iterations >= maxIterations)
  150. {
  151. LogWarning("Conjugate gradient solver did not converge.");
  152. converged = false;
  153. }
  154. for (i = 0; i < numPositions; ++i)
  155. {
  156. mPlaneCoordinates[i][1] = result[i];
  157. }
  158. // Scale to [-1,1]^2 for numerical conditioning in later steps.
  159. Real fmin = mPlaneCoordinates[0][0], fmax = fmin;
  160. for (i = 0; i < numPositions; i++)
  161. {
  162. if (mPlaneCoordinates[i][0] < fmin)
  163. {
  164. fmin = mPlaneCoordinates[i][0];
  165. }
  166. else if (mPlaneCoordinates[i][0] > fmax)
  167. {
  168. fmax = mPlaneCoordinates[i][0];
  169. }
  170. if (mPlaneCoordinates[i][1] < fmin)
  171. {
  172. fmin = mPlaneCoordinates[i][1];
  173. }
  174. else if (mPlaneCoordinates[i][1] > fmax)
  175. {
  176. fmax = mPlaneCoordinates[i][1];
  177. }
  178. }
  179. Real halfRange = (Real)0.5 * (fmax - fmin);
  180. Real invHalfRange = (Real)1 / halfRange;
  181. for (i = 0; i < numPositions; ++i)
  182. {
  183. mPlaneCoordinates[i][0] = (Real)-1 + invHalfRange * (mPlaneCoordinates[i][0] - fmin);
  184. mPlaneCoordinates[i][1] = (Real)-1 + invHalfRange * (mPlaneCoordinates[i][1] - fmin);
  185. }
  186. // Map the plane coordinates to the sphere using inverse
  187. // stereographic projection. The main issue is selecting a
  188. // translation in (x,y) and a radius of the projection sphere.
  189. // Both factors strongly influence the final result.
  190. // Use the average as the south pole. The points tend to be
  191. // clustered approximately in the middle of the conformally
  192. // mapped punctured triangle, so the average is a good choice
  193. // to place the pole.
  194. Vector2<Real> origin{ (Real)0, (Real)0 };
  195. for (i = 0; i < numPositions; ++i)
  196. {
  197. origin += mPlaneCoordinates[i];
  198. }
  199. origin /= (Real)numPositions;
  200. for (i = 0; i < numPositions; ++i)
  201. {
  202. mPlaneCoordinates[i] -= origin;
  203. }
  204. mMinPlaneCoordinate = mPlaneCoordinates[0];
  205. mMaxPlaneCoordinate = mPlaneCoordinates[0];
  206. for (i = 1; i < numPositions; ++i)
  207. {
  208. if (mPlaneCoordinates[i][0] < mMinPlaneCoordinate[0])
  209. {
  210. mMinPlaneCoordinate[0] = mPlaneCoordinates[i][0];
  211. }
  212. else if (mPlaneCoordinates[i][0] > mMaxPlaneCoordinate[0])
  213. {
  214. mMaxPlaneCoordinate[0] = mPlaneCoordinates[i][0];
  215. }
  216. if (mPlaneCoordinates[i][1] < mMinPlaneCoordinate[1])
  217. {
  218. mMinPlaneCoordinate[1] = mPlaneCoordinates[i][1];
  219. }
  220. else if (mPlaneCoordinates[i][1] > mMaxPlaneCoordinate[1])
  221. {
  222. mMaxPlaneCoordinate[1] = mPlaneCoordinates[i][1];
  223. }
  224. }
  225. // Select the radius of the sphere so that the projected punctured
  226. // triangle has an area whose fraction of total spherical area is
  227. // the same fraction as the area of the punctured triangle to the
  228. // total area of the original triangle mesh.
  229. Real twoTotalArea = (Real)0;
  230. currentIndex = indices;
  231. for (t = 0; t < numTriangles; ++t)
  232. {
  233. V0 = positions[*currentIndex++];
  234. V1 = positions[*currentIndex++];
  235. V2 = positions[*currentIndex++];
  236. E0 = V1 - V0;
  237. E1 = V2 - V0;
  238. twoTotalArea += Length(Cross(E0, E1));
  239. }
  240. ComputeSphereRadius(v0, v1, v2, twoArea / twoTotalArea);
  241. Real sqrSphereRadius = mSphereRadius * mSphereRadius;
  242. // Inverse stereographic projection to obtain sphere coordinates.
  243. // The sphere is centered at the origin and has radius 1.
  244. for (i = 0; i < numPositions; i++)
  245. {
  246. Real rSqr = Dot(mPlaneCoordinates[i], mPlaneCoordinates[i]);
  247. Real mult = (Real)1 / (rSqr + sqrSphereRadius);
  248. Real x = (Real)2 * mult * sqrSphereRadius * mPlaneCoordinates[i][0];
  249. Real y = (Real)2 * mult * sqrSphereRadius * mPlaneCoordinates[i][1];
  250. Real z = mult * mSphereRadius * (rSqr - sqrSphereRadius);
  251. mSphereCoordinates[i] = Vector3<Real>{ x, y, z } / mSphereRadius;
  252. }
  253. return converged;
  254. }
  255. // Conformal mapping of mesh to plane. The array of coordinates has a
  256. // one-to-one correspondence with the input vertex array.
  257. inline std::vector<Vector2<Real>> const& GetPlaneCoordinates() const
  258. {
  259. return mPlaneCoordinates;
  260. }
  261. inline Vector2<Real> const& GetMinPlaneCoordinate() const
  262. {
  263. return mMinPlaneCoordinate;
  264. }
  265. inline Vector2<Real> const& GetMaxPlaneCoordinate() const
  266. {
  267. return mMaxPlaneCoordinate;
  268. }
  269. // Conformal mapping of mesh to sphere (centered at origin). The array
  270. // of coordinates has a one-to-one correspondence with the input vertex
  271. // array.
  272. inline std::vector<Vector3<Real>> const& GetSphereCoordinates() const
  273. {
  274. return mSphereCoordinates;
  275. }
  276. inline Real GetSphereRadius() const
  277. {
  278. return mSphereRadius;
  279. }
  280. private:
  281. void ComputeSphereRadius(int v0, int v1, int v2, Real areaFraction)
  282. {
  283. Vector2<Real> V0 = mPlaneCoordinates[v0];
  284. Vector2<Real> V1 = mPlaneCoordinates[v1];
  285. Vector2<Real> V2 = mPlaneCoordinates[v2];
  286. Real r0Sqr = Dot(V0, V0);
  287. Real r1Sqr = Dot(V1, V1);
  288. Real r2Sqr = Dot(V2, V2);
  289. Real diffR10 = r1Sqr - r0Sqr;
  290. Real diffR20 = r2Sqr - r0Sqr;
  291. Real diffX10 = V1[0] - V0[0];
  292. Real diffY10 = V1[1] - V0[1];
  293. Real diffX20 = V2[0] - V0[0];
  294. Real diffY20 = V2[1] - V0[1];
  295. Real diffRX10 = V1[0] * r0Sqr - V0[0] * r1Sqr;
  296. Real diffRY10 = V1[1] * r0Sqr - V0[1] * r1Sqr;
  297. Real diffRX20 = V2[0] * r0Sqr - V0[0] * r2Sqr;
  298. Real diffRY20 = V2[1] * r0Sqr - V0[1] * r2Sqr;
  299. Real c0 = diffR20 * diffRY10 - diffR10 * diffRY20;
  300. Real c1 = diffR20 * diffY10 - diffR10 * diffY20;
  301. Real d0 = diffR10 * diffRX20 - diffR20 * diffRX10;
  302. Real d1 = diffR10 * diffX20 - diffR20 * diffX10;
  303. Real e0 = diffRX10 * diffRY20 - diffRX20 * diffRY10;
  304. Real e1 = diffRX10 * diffY20 - diffRX20 * diffY10;
  305. Real e2 = diffX10 * diffY20 - diffX20 * diffY10;
  306. Polynomial1<Real> poly0(6);
  307. poly0[0] = (Real)0;
  308. poly0[1] = (Real)0;
  309. poly0[2] = e0 * e0;
  310. poly0[3] = c0 * c0 + d0 * d0 + (Real)2 * e0 * e1;
  311. poly0[4] = (Real)2 * (c0 * c1 + d0 * d1 + e0 * e1) + e1 * e1;
  312. poly0[5] = c1 * c1 + d1 * d1 + (Real)2 * e1 * e2;
  313. poly0[6] = e2 * e2;
  314. Polynomial1<Real> qpoly0(1), qpoly1(1), qpoly2(1);
  315. qpoly0[0] = r0Sqr;
  316. qpoly0[1] = (Real)1;
  317. qpoly1[0] = r1Sqr;
  318. qpoly1[1] = (Real)1;
  319. qpoly2[0] = r2Sqr;
  320. qpoly2[1] = (Real)1;
  321. Real tmp = areaFraction * static_cast<Real>(GTE_C_PI);
  322. Real amp = tmp * tmp;
  323. Polynomial1<Real> poly1 = amp * qpoly0;
  324. poly1 = poly1 * qpoly0;
  325. poly1 = poly1 * qpoly0;
  326. poly1 = poly1 * qpoly0;
  327. poly1 = poly1 * qpoly1;
  328. poly1 = poly1 * qpoly1;
  329. poly1 = poly1 * qpoly2;
  330. poly1 = poly1 * qpoly2;
  331. Polynomial1<Real> poly2 = poly1 - poly0;
  332. LogAssert(poly2.GetDegree() <= 8, "Expecting degree no larger than 8.");
  333. // Bound a root near zero and apply bisection to find t.
  334. Real tmin = (Real)0, fmin = poly2(tmin);
  335. Real tmax = (Real)1, fmax = poly2(tmax);
  336. LogAssert(fmin > (Real)0 && fmax < (Real)0, "Expecting opposite-signed extremes.");
  337. // Determine the number of iterations to get 'digits' of accuracy.
  338. int const digits = 6;
  339. Real tmp0 = std::log(tmax - tmin);
  340. Real tmp1 = (Real)digits * static_cast<Real>(GTE_C_LN_10);
  341. Real arg = (tmp0 + tmp1) * static_cast<Real>(GTE_C_INV_LN_2);
  342. int maxIterations = static_cast<int>(arg + (Real)0.5);
  343. Real tmid = (Real)0, fmid;
  344. for (int i = 0; i < maxIterations; ++i)
  345. {
  346. tmid = (Real)0.5 * (tmin + tmax);
  347. fmid = poly2(tmid);
  348. Real product = fmid * fmin;
  349. if (product < (Real)0)
  350. {
  351. tmax = tmid;
  352. fmax = fmid;
  353. }
  354. else
  355. {
  356. tmin = tmid;
  357. fmin = fmid;
  358. }
  359. }
  360. mSphereRadius = std::sqrt(tmid);
  361. }
  362. // Conformal mapping to a plane. The plane's (px,py) points
  363. // correspond to the mesh's (mx,my,mz) points.
  364. std::vector<Vector2<Real>> mPlaneCoordinates;
  365. Vector2<Real> mMinPlaneCoordinate, mMaxPlaneCoordinate;
  366. // Conformal mapping to a sphere. The sphere's (sx,sy,sz) points
  367. // correspond to the mesh's (mx,my,mz) points.
  368. std::vector<Vector3<Real>> mSphereCoordinates;
  369. Real mSphereRadius;
  370. };
  371. }