ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
UVUnwrapping.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 // clang-format off
9 // Include standard library headers before DirectX/UVAtlas headers to avoid
10 // macro conflicts. DirectX headers define macros that interfere with libstdc++
11 // internals (e.g., template syntax in <bits/parse_numbers.h>).
12 #include <thread>
13 #include <chrono>
14 #include <type_traits>
15 
16 // Include TBB before any Qt headers to avoid macro clashes with `emit`.
17 #ifdef emit
18 # pragma push_macro("emit")
19 # undef emit
20 # define CV_RESTORE_EMIT_MACRO
21 #endif
22 #include <tbb/parallel_for.h>
23 #include <UVAtlas.h>
24 // SAL macros from UVAtlas/DirectX headers may conflict with libstdc++ internals
25 // (e.g., variable names like __out in <locale_conv.h>). Undefine them immediately
26 // after including UVAtlas to avoid breaking standard headers on GCC/Clang.
27 #ifdef __out
28 #undef __out
29 #endif
30 #ifdef __in
31 #undef __in
32 #endif
33 #ifdef __inout
34 #undef __inout
35 #endif
36 // Some headers may accidentally clobber NULL; ensure it's defined for C++
37 #ifdef NULL
38 #undef NULL
39 #endif
40 #define NULL 0
41 #ifdef CV_RESTORE_EMIT_MACRO
42 # pragma pop_macro("emit")
43 # undef CV_RESTORE_EMIT_MACRO
44 #endif
45 // clang-format on
46 
48 
49 namespace cloudViewer {
50 namespace t {
51 namespace geometry {
52 namespace kernel {
53 namespace uvunwrapping {
54 
55 namespace {
56 using namespace DirectX;
57 
58 struct UVAtlasPartitionOutput {
59  std::vector<int64_t> original_face_idx;
60  std::vector<UVAtlasVertex> vb;
61  // raw index buffer. Elements are uint32_t for DXGI_FORMAT_R32_UINT.
62  std::vector<uint8_t> ib;
63  std::vector<uint32_t> partition_result_adjacency;
66 };
67 
68 void ComputeUVAtlasPartition(TriangleMesh mesh,
69  const float max_stretch,
70  bool fast_mode,
71  UVAtlasPartitionOutput& output) {
72  const int64_t num_verts = mesh.GetVertexPositions().GetLength();
73 
74  std::unique_ptr<XMFLOAT3[]> pos(new XMFLOAT3[num_verts]);
75  {
76  core::Tensor vertices = mesh.GetVertexPositions()
77  .To(core::Device(), core::Float32)
78  .Contiguous();
79  const float* vertices_ptr = vertices.GetDataPtr<float>();
80  for (int64_t i = 0; i < num_verts; ++i) {
81  pos[i].x = vertices_ptr[i * 3 + 0];
82  pos[i].y = vertices_ptr[i * 3 + 1];
83  pos[i].z = vertices_ptr[i * 3 + 2];
84  }
85  }
86 
87  core::Tensor triangles = mesh.GetTriangleIndices()
88  .To(core::Device(), core::UInt32)
89  .Contiguous();
90  const uint32_t* triangles_ptr = triangles.GetDataPtr<uint32_t>();
91  const int64_t num_triangles = triangles.GetLength();
92 
93  typedef uint64_t Edge_t;
94  typedef std::pair<uint32_t, uint32_t> AdjTriangles_t;
95  const uint32_t INVALID = static_cast<uint32_t>(-1);
96  auto MakeEdge = [](uint32_t idx1, uint32_t idx2) {
97  return (uint64_t(std::min(idx1, idx2)) << 32) | std::max(idx1, idx2);
98  };
99 
100  // Compute adjacency as described here
101  // https://github.com/microsoft/DirectXMesh/wiki/DirectXMesh
102  std::vector<uint32_t> adj(triangles.NumElements());
103  {
104  std::unordered_map<Edge_t, AdjTriangles_t> edge_adjtriangle_map;
105  for (int64_t i = 0; i < num_triangles; ++i) {
106  const uint32_t* t_ptr = triangles_ptr + i * 3;
107 
108  for (int j = 0; j < 3; ++j) {
109  auto e = MakeEdge(t_ptr[j], t_ptr[(j + 1) % 3]);
110  auto it = edge_adjtriangle_map.find(e);
111  if (it != edge_adjtriangle_map.end()) {
112  it->second.second = i;
113  } else {
114  edge_adjtriangle_map[e] = AdjTriangles_t(i, INVALID);
115  }
116  }
117  }
118 
119  // second pass filling the adj array
120  int64_t linear_idx = 0;
121  for (int64_t i = 0; i < num_triangles; ++i) {
122  const uint32_t* t_ptr = triangles_ptr + i * 3;
123  for (int j = 0; j < 3; ++j, ++linear_idx) {
124  auto e = MakeEdge(t_ptr[j], t_ptr[(j + 1) % 3]);
125  auto& adjacent_tri = edge_adjtriangle_map[e];
126  if (adjacent_tri.first != i) {
127  adj[linear_idx] = adjacent_tri.first;
128  } else {
129  adj[linear_idx] = adjacent_tri.second;
130  }
131  }
132  }
133  }
134 
135  // Output vertex buffer for positions and uv coordinates.
136  // Note that the positions will be modified during the atlas computation
137  // and don't represent the original mesh anymore.
138  std::vector<UVAtlasVertex> vb;
139  // raw index buffer. Elements are uint32_t for DXGI_FORMAT_R32_UINT.
140  std::vector<uint8_t> ib;
141  // UVAtlas will create new vertices. remap stores the original vertex id for
142  // all created vertices.
143  std::vector<uint32_t> remap;
144  std::vector<uint32_t> face_partitioning;
145  std::vector<uint32_t> partition_result_adjacency;
146 
147  HRESULT hr = UVAtlasPartition(
148  pos.get(), num_verts, triangles_ptr, DXGI_FORMAT_R32_UINT,
149  num_triangles, 0, max_stretch, adj.data(), nullptr, nullptr,
150  nullptr, UVATLAS_DEFAULT_CALLBACK_FREQUENCY,
151  fast_mode ? UVATLAS_GEODESIC_FAST : UVATLAS_DEFAULT, vb, ib,
152  &face_partitioning, &remap, partition_result_adjacency,
153  &output.max_stretch_out, &output.num_charts_out);
154 
155  if (FAILED(hr)) {
156  if (hr == static_cast<HRESULT>(0x8007000DL)) {
157  utility::LogError("UVAtlasPartition: Non-manifold mesh");
158  } else if (hr == static_cast<HRESULT>(0x80070216L)) {
159  utility::LogError("UVAtlasPartition: Arithmetic overflow");
160  } else if (hr == static_cast<HRESULT>(0x80070032L)) {
161  utility::LogError("UVAtlasPartition: Not supported");
162  }
163  utility::LogError("UVAtlasPartition failed with code 0x{:X}",
164  static_cast<uint32_t>(hr));
165  }
166 
167  output.original_face_idx =
168  mesh.GetTriangleAttr("original_idx").ToFlatVector<int64_t>();
169  output.ib = std::move(ib);
170  output.vb = std::move(vb);
171  output.partition_result_adjacency = std::move(partition_result_adjacency);
172 }
173 } // namespace
174 
175 std::tuple<float, int, int> ComputeUVAtlas(TriangleMesh& mesh,
176  const size_t width,
177  const size_t height,
178  const float gutter,
179  const float max_stretch,
180  int parallel_partitions,
181  int nthreads) {
182  const int64_t num_verts = mesh.GetVertexPositions().GetLength();
183 
184  // create temporary mesh for partitioning
185  TriangleMesh mesh_tmp(
188  if (parallel_partitions > 1) {
189  const int max_points_per_partition =
190  (num_verts - 1) / (parallel_partitions - 1);
191  parallel_partitions = mesh_tmp.PCAPartition(max_points_per_partition);
192  }
193  utility::LogInfo("actual parallel_partitions {}", parallel_partitions);
194  mesh_tmp.SetTriangleAttr(
195  "original_idx",
197  1, core::Int64));
198 
199  std::vector<TriangleMesh> mesh_partitions;
200  if (parallel_partitions > 1) {
201  for (int i = 0; i < parallel_partitions; ++i) {
202  core::Tensor mask = mesh_tmp.GetTriangleAttr("partition_ids").Eq(i);
203  mesh_partitions.emplace_back(mesh_tmp.SelectFacesByMask(mask));
204  }
205  } else {
206  mesh_partitions.emplace_back(mesh_tmp);
207  }
208 
209  std::vector<UVAtlasPartitionOutput> uvatlas_partitions(parallel_partitions);
210 
211  // By default UVAtlas uses a fast mode if there are more than 25k faces.
212  // This makes sure that we always use fast mode if we parallelize.
213  const bool fast_mode = parallel_partitions > 1;
214  auto LoopFn = [&](const tbb::blocked_range<size_t>& range) {
215  for (size_t i = range.begin(); i < range.end(); ++i) {
216  auto& output = uvatlas_partitions[i];
217  ComputeUVAtlasPartition(mesh_partitions[i], max_stretch, fast_mode,
218  output);
219  }
220  };
221 
222  if (parallel_partitions > 1) {
223  if (nthreads > 0) {
224  tbb::task_arena arena(nthreads);
225  arena.execute([&]() {
226  tbb::parallel_for(
227  tbb::blocked_range<size_t>(0, parallel_partitions),
228  LoopFn);
229  });
230  } else {
231  tbb::parallel_for(
232  tbb::blocked_range<size_t>(0, parallel_partitions), LoopFn);
233  }
234  } else {
235  LoopFn(tbb::blocked_range<size_t>(0, parallel_partitions));
236  }
237 
238  // merge outputs for the packing algorithm
239  UVAtlasPartitionOutput& combined_output = uvatlas_partitions.front();
240  for (int i = 1; i < parallel_partitions; ++i) {
241  auto& output = uvatlas_partitions[i];
242 
243  // append vectors and update indices
244  const uint32_t vidx_offset = combined_output.vb.size();
245  uint32_t* indices_ptr = reinterpret_cast<uint32_t*>(output.ib.data());
246  const int64_t num_indices = output.ib.size() / sizeof(uint32_t);
247  for (int64_t indices_i = 0; indices_i < num_indices; ++indices_i) {
248  indices_ptr[indices_i] += vidx_offset;
249  }
250  combined_output.vb.insert(combined_output.vb.end(), output.vb.begin(),
251  output.vb.end());
252 
253  const uint32_t fidx_offset =
254  combined_output.ib.size() / (sizeof(uint32_t) * 3);
255  const uint32_t invalid = std::numeric_limits<uint32_t>::max();
256  for (auto& x : output.partition_result_adjacency) {
257  if (x != invalid) {
258  x += fidx_offset;
259  }
260  }
261  combined_output.ib.insert(combined_output.ib.end(), output.ib.begin(),
262  output.ib.end());
263  combined_output.partition_result_adjacency.insert(
264  combined_output.partition_result_adjacency.end(),
265  output.partition_result_adjacency.begin(),
266  output.partition_result_adjacency.end());
267 
268  combined_output.original_face_idx.insert(
269  combined_output.original_face_idx.end(),
270  output.original_face_idx.begin(),
271  output.original_face_idx.end());
272 
273  // update stats
274  combined_output.max_stretch_out = std::max(
275  combined_output.max_stretch_out, output.max_stretch_out);
276  combined_output.num_charts_out += output.num_charts_out;
277 
278  // free memory
279  output = UVAtlasPartitionOutput();
280  }
281 
282  HRESULT hr = UVAtlasPack(combined_output.vb, combined_output.ib,
283  DXGI_FORMAT_R32_UINT, width, height, gutter,
284  combined_output.partition_result_adjacency,
285  nullptr, UVATLAS_DEFAULT_CALLBACK_FREQUENCY);
286 
287  if (FAILED(hr)) {
288  if (hr == static_cast<HRESULT>(0x8007000DL)) {
289  utility::LogError("UVAtlasPack: Non-manifold mesh");
290  } else if (hr == static_cast<HRESULT>(0x80070216L)) {
291  utility::LogError("UVAtlasPack: Arithmetic overflow");
292  } else if (hr == static_cast<HRESULT>(0x80070032L)) {
293  utility::LogError("UVAtlasPack: Not supported");
294  }
295  utility::LogError("UVAtlasPack failed with code 0x{:X}",
296  static_cast<uint32_t>(hr));
297  }
298 
299  auto& ib = combined_output.ib;
300  auto& vb = combined_output.vb;
301  auto& original_face_idx = combined_output.original_face_idx;
302  const int64_t num_triangles = mesh.GetTriangleIndices().GetLength();
303  const uint32_t* indices_ptr = reinterpret_cast<uint32_t*>(ib.data());
304  if (ib.size() != sizeof(uint32_t) * 3 * num_triangles) {
306  "Unexpected output index buffer size. Got {} expected {}.",
307  ib.size(), sizeof(uint32_t) * 3 * num_triangles);
308  }
309  core::Tensor texture_uvs({num_triangles, 3, 2}, core::Float32);
310  {
311  // copy uv coords
312  float* texture_uvs_ptr = texture_uvs.GetDataPtr<float>();
313  for (int64_t i = 0; i < num_triangles; ++i) {
314  int64_t original_i = original_face_idx[i];
315  for (int j = 0; j < 3; ++j) {
316  const uint32_t& vidx = indices_ptr[i * 3 + j];
317  const auto& vert = vb[vidx];
318  texture_uvs_ptr[original_i * 6 + j * 2 + 0] = vert.uv.x;
319  texture_uvs_ptr[original_i * 6 + j * 2 + 1] = vert.uv.y;
320  }
321  }
322  }
323 
324  texture_uvs = texture_uvs.To(mesh.GetDevice());
325  mesh.SetTriangleAttr("texture_uvs", texture_uvs);
326 
327  return std::tie(combined_output.max_stretch_out,
328  combined_output.num_charts_out, parallel_partitions);
329 }
330 
331 } // namespace uvunwrapping
332 } // namespace kernel
333 } // namespace geometry
334 } // namespace t
335 } // namespace cloudViewer
int width
int height
@ INVALID
std::vector< UVAtlasVertex > vb
std::vector< int64_t > original_face_idx
std::vector< uint32_t > partition_result_adjacency
std::vector< uint8_t > ib
size_t num_charts_out
float max_stretch_out
Tensor Contiguous() const
Definition: Tensor.cpp:772
static Tensor Arange(const Scalar start, const Scalar stop, const Scalar step=1, const Dtype dtype=core::Int64, const Device &device=core::Device("CPU:0"))
Create a 1D tensor with evenly spaced values in the given interval.
Definition: Tensor.cpp:436
int64_t GetLength() const
Definition: Tensor.h:1125
Tensor To(Dtype dtype, bool copy=false) const
Definition: Tensor.cpp:739
A triangle mesh contains vertices and triangles.
Definition: TriangleMesh.h:98
TriangleMesh SelectFacesByMask(const core::Tensor &mask) const
core::Device GetDevice() const override
Returns the device of the geometry.
Definition: TriangleMesh.h:721
void SetTriangleAttr(const std::string &key, const core::Tensor &value)
Definition: TriangleMesh.h:285
const TensorMap & GetTriangleAttr() const
Getter for triangle_attr_ TensorMap. Used in Pybind.
Definition: TriangleMesh.h:159
#define LogInfo(...)
Definition: Logging.h:81
#define LogError(...)
Definition: Logging.h:60
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
const Dtype Int64
Definition: Dtype.cpp:47
const Dtype UInt32
Definition: Dtype.cpp:50
const Dtype Float32
Definition: Dtype.cpp:42
std::tuple< float, int, int > ComputeUVAtlas(TriangleMesh &mesh, const size_t width, const size_t height, const float gutter, const float max_stretch, int parallel_partitions, int nthreads)
Generic file read and write utility for python interface.