GenerateMeshUV.h 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  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/Vector2.h>
  9. #include <Mathematics/Vector3.h>
  10. #include <Mathematics/ETManifoldMesh.h>
  11. #include <cstdint>
  12. #include <cstring>
  13. #include <functional>
  14. #include <thread>
  15. // This class is an implementation of the barycentric mapping algorithm
  16. // described in Section 5.3 of the book
  17. // Polygon Mesh Processing
  18. // Mario Botsch, Leif Kobbelt, Mark Pauly, Pierre Alliez, Bruno Levy
  19. // AK Peters, Ltd., Natick MA, 2010
  20. // It uses the mean value weights described in Section 5.3.1 to allow the mesh
  21. // geometry to influence the texture coordinate generation, and it uses
  22. // Gauss-Seidel iteration to solve the sparse linear system. The authors'
  23. // advice is that the Gauss-Seidel approach works well for at most about 5000
  24. // vertices, presumably the convergence rate degrading as the number of
  25. // vertices increases.
  26. //
  27. // The algorithm implemented here has an additional preprocessing step that
  28. // computes a topological distance transform of the vertices. The boundary
  29. // texture coordinates are propagated inward by updating the vertices in
  30. // topological distance order, leading to fast convergence for large numbers
  31. // of vertices.
  32. namespace WwiseGTE
  33. {
  34. template <typename Real>
  35. class GenerateMeshUV
  36. {
  37. public:
  38. // Construction and destruction. Set the number of threads to 0 when
  39. // you want the code to run in the main thread of the applications.
  40. // Set the number of threads to a positive number when you want the
  41. // code to run multithreaded on the CPU. Derived classes that use the
  42. // GPU ignore the number of threads, setting the constructor input to
  43. // std::numeric_limits<uint32_t>::max(). Provide a callback when you
  44. // want to monitor each iteration of the uv-solver. The input to the
  45. // progress callback is the current iteration; it starts at 1 and
  46. // increases to the numIterations input to the operator() member
  47. // function.
  48. GenerateMeshUV(uint32_t numThreads,
  49. std::function<void(uint32_t)> const* progress = nullptr)
  50. :
  51. mNumThreads(numThreads),
  52. mProgress(progress),
  53. mNumVertices(0),
  54. mVertices(nullptr),
  55. mTCoords(nullptr),
  56. mNumBoundaryEdges(0),
  57. mBoundaryStart(0)
  58. {
  59. }
  60. virtual ~GenerateMeshUV() = default;
  61. // The incoming mesh must be edge-triangle manifold and have rectangle
  62. // topology (simply connected, closed polyline boundary). The arrays
  63. // 'vertices' and 'tcoords' must both have 'numVertices' elements.
  64. // Set 'useSquareTopology' to true for the generated coordinates to
  65. // live in the uv-square [0,1]^2. Set it to false for the generated
  66. // coordinates to live in a convex polygon that inscribes the uv-disk
  67. // of center (1/2,1/2) and radius 1/2.
  68. void operator()(uint32_t numIterations, bool useSquareTopology,
  69. int numVertices, Vector3<Real> const* vertices, int numIndices,
  70. int const* indices, Vector2<Real>* tcoords)
  71. {
  72. // Ensure that numIterations is even, which avoids having a memory
  73. // copy from the temporary ping-pong buffer to 'tcoords'.
  74. if (numIterations & 1)
  75. {
  76. ++numIterations;
  77. }
  78. mNumVertices = numVertices;
  79. mVertices = vertices;
  80. mTCoords = tcoords;
  81. // The linear system solver has a first pass to initialize the
  82. // texture coordinates to ensure the Gauss-Seidel iteration
  83. // converges rapidly. This requires the texture coordinates all
  84. // start as (-1,-1).
  85. for (int i = 0; i < numVertices; ++i)
  86. {
  87. mTCoords[i][0] = (Real)-1;
  88. mTCoords[i][1] = (Real)-1;
  89. }
  90. // Create the manifold mesh data structure.
  91. mGraph.Clear();
  92. int const numTriangles = numIndices / 3;
  93. for (int t = 0; t < numTriangles; ++t)
  94. {
  95. int v0 = *indices++;
  96. int v1 = *indices++;
  97. int v2 = *indices++;
  98. mGraph.Insert(v0, v1, v2);
  99. }
  100. TopologicalVertexDistanceTransform();
  101. if (useSquareTopology)
  102. {
  103. AssignBoundaryTextureCoordinatesSquare();
  104. }
  105. else
  106. {
  107. AssignBoundaryTextureCoordinatesDisk();
  108. }
  109. ComputeMeanValueWeights();
  110. SolveSystem(numIterations);
  111. }
  112. protected:
  113. // A CPU-based implementation is provided by this class. The derived
  114. // classes using the GPU override this function.
  115. virtual void SolveSystemInternal(uint32_t numIterations)
  116. {
  117. if (mNumThreads > 1)
  118. {
  119. SolveSystemCPUMultiple(numIterations);
  120. }
  121. else
  122. {
  123. SolveSystemCPUSingle(numIterations);
  124. }
  125. }
  126. // Constructor inputs.
  127. uint32_t mNumThreads;
  128. std::function<void(uint32_t)> const* mProgress;
  129. // Convenience members that store the input parameters to operator().
  130. int mNumVertices;
  131. Vector3<Real> const* mVertices;
  132. Vector2<Real>* mTCoords;
  133. // The edge-triangle manifold graph, where each edge is shared by at
  134. // most two triangles.
  135. ETManifoldMesh mGraph;
  136. // The mVertexInfo array stores -1 for the interior vertices. For a
  137. // boundary edge <v0,v1> that is counterclockwise,
  138. // mVertexInfo[v0] = v1, which gives us an orded boundary polyline.
  139. enum { INTERIOR_VERTEX = -1 };
  140. std::vector<int> mVertexInfo;
  141. int mNumBoundaryEdges, mBoundaryStart;
  142. typedef ETManifoldMesh::Edge Edge;
  143. std::set<std::shared_ptr<Edge>> mInteriorEdges;
  144. // The vertex graph required to set up a sparse linear system of
  145. // equations to determine the texture coordinates.
  146. struct Vertex
  147. {
  148. // The topological distance from the boundary of the mesh.
  149. int distance;
  150. // The value range0 is the index into mVertexGraphData for the
  151. // first adjacent vertex. The value range1 is the number of
  152. // adjacent vertices.
  153. int range0, range1;
  154. // Unused. The padding is necessary for the GLSL program in the
  155. // derived class for GL45.
  156. int padding;
  157. };
  158. std::vector<Vertex> mVertexGraph;
  159. std::vector<std::pair<int, Real>> mVertexGraphData;
  160. // The vertices are listed in the order determined by a topological
  161. // distance transform. Boundary vertices have 'distance' 0. Any
  162. // vertices that are not boundary vertices but are edge-adjacent to
  163. // boundary vertices have 'distance' 1. Neighbors of those have
  164. // distance '2', and so on. The mOrderedVertices array stores
  165. // distance-0 vertices first, distance-1 vertices second, and so on.
  166. std::vector<int> mOrderedVertices;
  167. private:
  168. void TopologicalVertexDistanceTransform()
  169. {
  170. // Initialize the graph information.
  171. mVertexInfo.resize(mNumVertices);
  172. std::fill(mVertexInfo.begin(), mVertexInfo.end(), INTERIOR_VERTEX);
  173. mVertexGraph.resize(mNumVertices);
  174. mVertexGraphData.resize(2 * mGraph.GetEdges().size());
  175. std::pair<int, Real> initialData = std::make_pair(-1, (Real)-1);
  176. std::fill(mVertexGraphData.begin(), mVertexGraphData.end(), initialData);
  177. mOrderedVertices.resize(mNumVertices);
  178. mInteriorEdges.clear();
  179. mNumBoundaryEdges = 0;
  180. mBoundaryStart = std::numeric_limits<int>::max();
  181. // Count the number of adjacent vertices for each vertex. For
  182. // data sets with a large number of vertices, this is a
  183. // preprocessing step to avoid a dynamic data structure that has
  184. // a large number of std:map objects that take a very long time
  185. // to destroy when a debugger is attached to the executable.
  186. // Instead, we allocate a single array that stores all the
  187. // adjacency information. It is also necessary to bundle the
  188. // data this way for a GPU version of the algorithm.
  189. std::vector<int> numAdjacencies(mNumVertices);
  190. std::fill(numAdjacencies.begin(), numAdjacencies.end(), 0);
  191. for (auto const& element : mGraph.GetEdges())
  192. {
  193. ++numAdjacencies[element.first.V[0]];
  194. ++numAdjacencies[element.first.V[1]];
  195. if (element.second->T[1].lock())
  196. {
  197. // This is an interior edge.
  198. mInteriorEdges.insert(element.second);
  199. }
  200. else
  201. {
  202. // This is a boundary edge. Determine the ordering of the
  203. // vertex indices to make the edge counterclockwise.
  204. ++mNumBoundaryEdges;
  205. int v0 = element.second->V[0], v1 = element.second->V[1];
  206. auto tri = element.second->T[0].lock();
  207. int i;
  208. for (i = 0; i < 3; ++i)
  209. {
  210. int v2 = tri->V[i];
  211. if (v2 != v0 && v2 != v1)
  212. {
  213. // The vertex is opposite the boundary edge.
  214. v0 = tri->V[(i + 1) % 3];
  215. v1 = tri->V[(i + 2) % 3];
  216. mVertexInfo[v0] = v1;
  217. mBoundaryStart = std::min(mBoundaryStart, v0);
  218. break;
  219. }
  220. }
  221. }
  222. }
  223. // Set the range data for each vertex.
  224. for (int vIndex = 0, aIndex = 0; vIndex < mNumVertices; ++vIndex)
  225. {
  226. int numAdjacent = numAdjacencies[vIndex];
  227. mVertexGraph[vIndex].range0 = aIndex;
  228. mVertexGraph[vIndex].range1 = numAdjacent;
  229. aIndex += numAdjacent;
  230. mVertexGraph[vIndex].padding = 0;
  231. }
  232. // Compute a topological distance transform of the vertices.
  233. std::set<int> currFront;
  234. for (auto const& element : mGraph.GetEdges())
  235. {
  236. int v0 = element.second->V[0], v1 = element.second->V[1];
  237. for (int i = 0; i < 2; ++i)
  238. {
  239. if (mVertexInfo[v0] == INTERIOR_VERTEX)
  240. {
  241. mVertexGraph[v0].distance = -1;
  242. }
  243. else
  244. {
  245. mVertexGraph[v0].distance = 0;
  246. currFront.insert(v0);
  247. }
  248. // Insert v1 into the first available slot of the
  249. // adjacency array.
  250. int range0 = mVertexGraph[v0].range0;
  251. int range1 = mVertexGraph[v0].range1;
  252. for (int j = 0; j < range1; ++j)
  253. {
  254. std::pair<int, Real>& data = mVertexGraphData[range0 + j];
  255. if (data.second == (Real)-1)
  256. {
  257. data.first = v1;
  258. data.second = (Real)0;
  259. break;
  260. }
  261. }
  262. std::swap(v0, v1);
  263. }
  264. }
  265. // Use a breadth-first search to propagate the distance
  266. // information.
  267. int nextDistance = 1;
  268. size_t numFrontVertices = currFront.size();
  269. std::copy(currFront.begin(), currFront.end(), mOrderedVertices.begin());
  270. while (currFront.size() > 0)
  271. {
  272. std::set<int> nextFront;
  273. for (auto v : currFront)
  274. {
  275. int range0 = mVertexGraph[v].range0;
  276. int range1 = mVertexGraph[v].range1;
  277. auto* current = &mVertexGraphData[range0];
  278. for (int j = 0; j < range1; ++j, ++current)
  279. {
  280. int a = current->first;
  281. if (mVertexGraph[a].distance == -1)
  282. {
  283. mVertexGraph[a].distance = nextDistance;
  284. nextFront.insert(a);
  285. }
  286. }
  287. }
  288. std::copy(nextFront.begin(), nextFront.end(), mOrderedVertices.begin() + numFrontVertices);
  289. numFrontVertices += nextFront.size();
  290. currFront = std::move(nextFront);
  291. ++nextDistance;
  292. }
  293. }
  294. void AssignBoundaryTextureCoordinatesSquare()
  295. {
  296. // Map the boundary of the mesh to the unit square [0,1]^2. The
  297. // selection of square vertices is such that the relative
  298. // distances between boundary vertices and the relative distances
  299. // between polygon vertices is preserved, except that the four
  300. // corners of the square are required to have boundary points
  301. // mapped to them. The first boundary point has an implied
  302. // distance of zero. The value distance[i] is the length of the
  303. // boundary polyline from vertex 0 to vertex i+1.
  304. std::vector<Real> distance(mNumBoundaryEdges);
  305. Real total = (Real)0;
  306. int v0 = mBoundaryStart, v1, i;
  307. for (i = 0; i < mNumBoundaryEdges; ++i)
  308. {
  309. v1 = mVertexInfo[v0];
  310. total += Length(mVertices[v1] - mVertices[v0]);
  311. distance[i] = total;
  312. v0 = v1;
  313. }
  314. Real invTotal = (Real)1 / total;
  315. for (auto& d : distance)
  316. {
  317. d *= invTotal;
  318. }
  319. auto begin = distance.begin(), end = distance.end();
  320. int endYMin = (int)(std::lower_bound(begin, end, (Real)0.25) - begin);
  321. int endXMax = (int)(std::lower_bound(begin, end, (Real)0.50) - begin);
  322. int endYMax = (int)(std::lower_bound(begin, end, (Real)0.75) - begin);
  323. int endXMin = (int)distance.size() - 1;
  324. // The first polygon vertex is (0,0). The remaining vertices are
  325. // chosen counterclockwise around the square.
  326. v0 = mBoundaryStart;
  327. mTCoords[v0][0] = (Real)0;
  328. mTCoords[v0][1] = (Real)0;
  329. for (i = 0; i < endYMin; ++i)
  330. {
  331. v1 = mVertexInfo[v0];
  332. mTCoords[v1][0] = distance[i] * (Real)4;
  333. mTCoords[v1][1] = (Real)0;
  334. v0 = v1;
  335. }
  336. v1 = mVertexInfo[v0];
  337. mTCoords[v1][0] = (Real)1;
  338. mTCoords[v1][1] = (Real)0;
  339. v0 = v1;
  340. for (++i; i < endXMax; ++i)
  341. {
  342. v1 = mVertexInfo[v0];
  343. mTCoords[v1][0] = (Real)1;
  344. mTCoords[v1][1] = distance[i] * (Real)4 - (Real)1;
  345. v0 = v1;
  346. }
  347. v1 = mVertexInfo[v0];
  348. mTCoords[v1][0] = (Real)1;
  349. mTCoords[v1][1] = (Real)1;
  350. v0 = v1;
  351. for (++i; i < endYMax; ++i)
  352. {
  353. v1 = mVertexInfo[v0];
  354. mTCoords[v1][0] = (Real)3 - distance[i] * (Real)4;
  355. mTCoords[v1][1] = (Real)1;
  356. v0 = v1;
  357. }
  358. v1 = mVertexInfo[v0];
  359. mTCoords[v1][0] = (Real)0;
  360. mTCoords[v1][1] = (Real)1;
  361. v0 = v1;
  362. for (++i; i < endXMin; ++i)
  363. {
  364. v1 = mVertexInfo[v0];
  365. mTCoords[v1][0] = (Real)0;
  366. mTCoords[v1][1] = (Real)4 - distance[i] * (Real)4;
  367. v0 = v1;
  368. }
  369. }
  370. void AssignBoundaryTextureCoordinatesDisk()
  371. {
  372. // Map the boundary of the mesh to a convex polygon. The selection
  373. // of convex polygon vertices is such that the relative distances
  374. // between boundary vertices and the relative distances between
  375. // polygon vertices is preserved. The first boundary point has an
  376. // implied distance of zero. The value distance[i] is the length
  377. // of the boundary polyline from vertex 0 to vertex i+1.
  378. std::vector<Real> distance(mNumBoundaryEdges);
  379. Real total = (Real)0;
  380. int v0 = mBoundaryStart;
  381. for (int i = 0; i < mNumBoundaryEdges; ++i)
  382. {
  383. int v1 = mVertexInfo[v0];
  384. total += Length(mVertices[v1] - mVertices[v0]);
  385. distance[i] = total;
  386. v0 = v1;
  387. }
  388. // The convex polygon lives in [0,1]^2 and inscribes a circle with
  389. // center (1/2,1/2) and radius 1/2. The polygon center is not
  390. // necessarily the circle center! This is the case when a
  391. // boundary edge has length larger than half the total length of
  392. // the boundary polyline; we do not expect such data for our
  393. // meshes. The first polygon vertex is (1/2,0). The remaining
  394. // vertices are chosen counterclockwise around the polygon.
  395. Real multiplier = (Real)GTE_C_TWO_PI / total;
  396. v0 = mBoundaryStart;
  397. mTCoords[v0][0] = (Real)1;
  398. mTCoords[v0][1] = (Real)0.5;
  399. for (int i = 1; i < mNumBoundaryEdges; ++i)
  400. {
  401. int v1 = mVertexInfo[v0];
  402. Real angle = multiplier * distance[i - 1];
  403. mTCoords[v1][0] = (std::cos(angle) + (Real)1) * (Real)0.5;
  404. mTCoords[v1][1] = (std::sin(angle) + (Real)1) * (Real)0.5;
  405. v0 = v1;
  406. }
  407. }
  408. void ComputeMeanValueWeights()
  409. {
  410. for (auto const& edge : mInteriorEdges)
  411. {
  412. int v0 = edge->V[0], v1 = edge->V[1];
  413. for (int i = 0; i < 2; ++i)
  414. {
  415. // Compute the direction from X0 to X1 and compute the
  416. // length of the edge (X0,X1).
  417. Vector3<Real> X0 = mVertices[v0];
  418. Vector3<Real> X1 = mVertices[v1];
  419. Vector3<Real> X1mX0 = X1 - X0;
  420. Real x1mx0length = Normalize(X1mX0);
  421. Real weight;
  422. if (x1mx0length > (Real)0)
  423. {
  424. // Compute the weight for X0 associated with X1.
  425. weight = (Real)0;
  426. for (int j = 0; j < 2; ++j)
  427. {
  428. // Find the vertex of triangle T[j] opposite edge
  429. // <X0,X1>.
  430. auto tri = edge->T[j].lock();
  431. int k;
  432. for (k = 0; k < 3; ++k)
  433. {
  434. int v2 = tri->V[k];
  435. if (v2 != v0 && v2 != v1)
  436. {
  437. Vector3<Real> X2 = mVertices[v2];
  438. Vector3<Real> X2mX0 = X2 - X0;
  439. Real x2mx0Length = Normalize(X2mX0);
  440. if (x2mx0Length > (Real)0)
  441. {
  442. Real dot = Dot(X2mX0, X1mX0);
  443. Real cs = std::min(std::max(dot, (Real)-1), (Real)1);
  444. Real angle = std::acos(cs);
  445. weight += std::tan(angle * (Real)0.5);
  446. }
  447. else
  448. {
  449. weight += (Real)1;
  450. }
  451. break;
  452. }
  453. }
  454. }
  455. weight /= x1mx0length;
  456. }
  457. else
  458. {
  459. weight = (Real)1;
  460. }
  461. int range0 = mVertexGraph[v0].range0;
  462. int range1 = mVertexGraph[v0].range1;
  463. for (int j = 0; j < range1; ++j)
  464. {
  465. std::pair<int, Real>& data = mVertexGraphData[range0 + j];
  466. if (data.first == v1)
  467. {
  468. data.second = weight;
  469. }
  470. }
  471. std::swap(v0, v1);
  472. }
  473. }
  474. }
  475. void SolveSystem(uint32_t numIterations)
  476. {
  477. // On the first pass, average only neighbors whose texture
  478. // coordinates have been computed. This is a good initial guess
  479. // for the linear system and leads to relatively fast convergence
  480. // of the Gauss-Seidel iterates.
  481. Real zero = (Real)0;
  482. for (int i = mNumBoundaryEdges; i < mNumVertices; ++i)
  483. {
  484. int v0 = mOrderedVertices[i];
  485. int range0 = mVertexGraph[v0].range0;
  486. int range1 = mVertexGraph[v0].range1;
  487. auto const* current = &mVertexGraphData[range0];
  488. Vector2<Real> tcoord{ zero, zero };
  489. Real weight, weightSum = zero;
  490. for (int j = 0; j < range1; ++j, ++current)
  491. {
  492. int v1 = current->first;
  493. if (mTCoords[v1][0] != -1.0f)
  494. {
  495. weight = current->second;
  496. weightSum += weight;
  497. tcoord += weight * mTCoords[v1];
  498. }
  499. }
  500. tcoord /= weightSum;
  501. mTCoords[v0] = tcoord;
  502. }
  503. SolveSystemInternal(numIterations);
  504. }
  505. void SolveSystemCPUSingle(uint32_t numIterations)
  506. {
  507. // Use ping-pong buffers for the texture coordinates.
  508. std::vector<Vector2<Real>> tcoords(mNumVertices);
  509. size_t numBytes = mNumVertices * sizeof(Vector2<Real>);
  510. std::memcpy(&tcoords[0], mTCoords, numBytes);
  511. Vector2<Real>* inTCoords = mTCoords;
  512. Vector2<Real>* outTCoords = &tcoords[0];
  513. // The value numIterations is even, so we always swap an even
  514. // number of times. This ensures that on exit from the loop,
  515. // outTCoords is tcoords.
  516. for (uint32_t i = 1; i <= numIterations; ++i)
  517. {
  518. if (mProgress)
  519. {
  520. (*mProgress)(i);
  521. }
  522. for (int j = mNumBoundaryEdges; j < mNumVertices; ++j)
  523. {
  524. int v0 = mOrderedVertices[j];
  525. int range0 = mVertexGraph[v0].range0;
  526. int range1 = mVertexGraph[v0].range1;
  527. auto const* current = &mVertexGraphData[range0];
  528. Vector2<Real> tcoord{ (Real)0, (Real)0 };
  529. Real weight, weightSum = (Real)0;
  530. for (int k = 0; k < range1; ++k, ++current)
  531. {
  532. int v1 = current->first;
  533. weight = current->second;
  534. weightSum += weight;
  535. tcoord += weight * inTCoords[v1];
  536. }
  537. tcoord /= weightSum;
  538. outTCoords[v0] = tcoord;
  539. }
  540. std::swap(inTCoords, outTCoords);
  541. }
  542. }
  543. void SolveSystemCPUMultiple(uint32_t numIterations)
  544. {
  545. // Use ping-pong buffers for the texture coordinates.
  546. std::vector<Vector2<Real>> tcoords(mNumVertices);
  547. size_t numBytes = mNumVertices * sizeof(Vector2<Real>);
  548. std::memcpy(&tcoords[0], mTCoords, numBytes);
  549. Vector2<Real>* inTCoords = mTCoords;
  550. Vector2<Real>* outTCoords = &tcoords[0];
  551. // Partition the data for multiple threads.
  552. int numV = mNumVertices - mNumBoundaryEdges;
  553. int numVPerThread = numV / mNumThreads;
  554. std::vector<int> vmin(mNumThreads), vmax(mNumThreads);
  555. for (uint32_t t = 0; t < mNumThreads; ++t)
  556. {
  557. vmin[t] = mNumBoundaryEdges + t * numVPerThread;
  558. vmax[t] = vmin[t] + numVPerThread - 1;
  559. }
  560. vmax[mNumThreads - 1] = mNumVertices - 1;
  561. // The value numIterations is even, so we always swap an even
  562. // number of times. This ensures that on exit from the loop,
  563. // outTCoords is tcoords.
  564. for (uint32_t i = 1; i <= numIterations; ++i)
  565. {
  566. if (mProgress)
  567. {
  568. (*mProgress)(i);
  569. }
  570. // Execute Gauss-Seidel iterations in multiple threads.
  571. std::vector<std::thread> process(mNumThreads);
  572. for (uint32_t t = 0; t < mNumThreads; ++t)
  573. {
  574. process[t] = std::thread([this, t, &vmin, &vmax, inTCoords,
  575. outTCoords]()
  576. {
  577. for (int j = vmin[t]; j <= vmax[t]; ++j)
  578. {
  579. int v0 = mOrderedVertices[j];
  580. int range0 = mVertexGraph[v0].range0;
  581. int range1 = mVertexGraph[v0].range1;
  582. auto const* current = &mVertexGraphData[range0];
  583. Vector2<Real> tcoord{ (Real)0, (Real)0 };
  584. Real weight, weightSum = (Real)0;
  585. for (int k = 0; k < range1; ++k, ++current)
  586. {
  587. int v1 = current->first;
  588. weight = current->second;
  589. weightSum += weight;
  590. tcoord += weight * inTCoords[v1];
  591. }
  592. tcoord /= weightSum;
  593. outTCoords[v0] = tcoord;
  594. }
  595. });
  596. }
  597. // Wait for all threads to finish.
  598. for (uint32_t t = 0; t < mNumThreads; ++t)
  599. {
  600. process[t].join();
  601. }
  602. std::swap(inTCoords, outTCoords);
  603. }
  604. }
  605. };
  606. }