ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
MeshSamplingTools.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 #include <MeshSamplingTools.h>
9 
10 // local
11 #include <CVMath.h>
12 #include <CVPointCloud.h>
13 #include <GenericIndexedMesh.h>
15 #include <GenericTriangle.h>
16 #include <ScalarField.h>
17 
18 // system
19 #include <random>
20 
21 using namespace cloudViewer;
22 
24  if (!mesh) {
25  assert(false);
26  return -1.0;
27  }
28 
29  // total area
30  double Stotal = 0.0;
31 
33  for (unsigned n = 0; n < mesh->size(); ++n) {
34  GenericTriangle* tri = mesh->_getNextTriangle();
35 
36  // vertices
37  const CCVector3* O = tri->_getA();
38  const CCVector3* A = tri->_getB();
39  const CCVector3* B = tri->_getC();
40 
41  // compute the area of the triangle (= half of the vector product norm)
42  CCVector3 OA = *A - *O;
43  CCVector3 OB = *B - *O;
44  Stotal += OA.cross(OB).norm();
45  }
46 
47  return Stotal / 2;
48 }
49 
51  if (!mesh) {
52  assert(false);
53  return -1.0;
54  }
55 
56  // total volume
57  double Vtotal = 0.0;
58 
59  CCVector3 origin, upperCorner;
60  mesh->getBoundingBox(origin, upperCorner);
61 
63  for (unsigned n = 0; n < mesh->size(); ++n) {
64  GenericTriangle* tri = mesh->_getNextTriangle();
65 
66  // vertices (expressed in the local mesh ref. so as to avoid numerical
67  // inaccuracies)
68  const CCVector3 A = *tri->_getA() - origin;
69  const CCVector3 B = *tri->_getB() - origin;
70  const CCVector3 C = *tri->_getC() - origin;
71 
72  // see "EFFICIENT FEATURE EXTRACTION FOR 2D/3D OBJECTS IN MESH
73  // REPRESENTATION" by Cha Zhang and Tsuhan Chen (2001) We compute the
74  // (signed) volume of the tetrahedron defined by each triangle and the
75  // origin
76  double signedVol = (-static_cast<double>(C.x * B.y * A.z) +
77  static_cast<double>(B.x * C.y * A.z) +
78  static_cast<double>(C.x * A.y * B.z) -
79  static_cast<double>(A.x * C.y * B.z) -
80  static_cast<double>(B.x * A.y * C.z) +
81  static_cast<double>(A.x * B.y * C.z)) /
82  6;
83 
84  Vtotal += signedVol;
85  }
86 
87  return std::abs(Vtotal); // in case the triangles are in the wrong order!
88 }
89 
90 unsigned long long MeshSamplingTools::ComputeEdgeKey(unsigned i1, unsigned i2) {
91  // build unique index
92  if (i1 > i2) std::swap(i1, i2);
93  return ((static_cast<unsigned long long>(i2) << 32) |
94  static_cast<unsigned long long>(i1));
95 }
96 
97 void MeshSamplingTools::DecodeEdgeKey(unsigned long long key,
98  unsigned& i1,
99  unsigned& i2) {
100  i1 = static_cast<unsigned>(key & 0x00000000FFFFFFFF);
101  i2 = static_cast<unsigned>((key >> 32) & 0x00000000FFFFFFFF);
102 }
103 
105  EdgeUsageMap& edgeMap) {
106  edgeMap.clear();
107 
108  if (!mesh) return false;
109 
110  try {
111  mesh->placeIteratorAtBeginning();
112  // for all triangles
113  for (unsigned n = 0; n < mesh->size(); ++n) {
115 
116  // for all edges
117  for (unsigned j = 0; j < 3; ++j) {
118  unsigned i1 = tri->i[j];
119  unsigned i2 = tri->i[(j + 1) % 3];
120  // build unique index
121  unsigned long long edgeKey = ComputeEdgeKey(i1, i2);
122  ++edgeMap[edgeKey];
123  }
124  }
125  } catch (const std::bad_alloc&) {
126  // not enough memory
127  return false;
128  }
129 
130  return true;
131 }
132 
135  stats = EdgeConnectivityStats();
136 
137  if (!mesh) return false;
138 
139  // count the number of triangles using each edge
140  EdgeUsageMap edgeCounters;
141  if (!buildMeshEdgeUsageMap(mesh, edgeCounters)) return false;
142 
143  // for all edges
144  stats.edgesCount = static_cast<unsigned>(edgeCounters.size());
145  for (std::map<unsigned long long, unsigned>::const_iterator edgeIt =
146  edgeCounters.begin();
147  edgeIt != edgeCounters.end(); ++edgeIt) {
148  assert(edgeIt->second != 0);
149  if (edgeIt->second == 1)
150  ++stats.edgesNotShared;
151  else if (edgeIt->second == 2)
152  ++stats.edgesSharedByTwo;
153  else
154  ++stats.edgesSharedByMore;
155  }
156 
157  return true;
158 }
159 
161  GenericIndexedMesh* mesh,
162  ScalarField* flags,
163  EdgeConnectivityStats* stats /*=0*/) {
164  if (!mesh || !flags || flags->currentSize() == 0) return false;
165 
166  //'non-processed' flag
167  flags->fill(NAN_VALUE);
168 
169  // count the number of triangles using each edge
170  EdgeUsageMap edgeCounters;
171  if (!buildMeshEdgeUsageMap(mesh, edgeCounters)) return false;
172 
173  // now scan all the edges and flag their vertices
174  {
175  if (stats)
176  stats->edgesCount = static_cast<unsigned>(edgeCounters.size());
177 
178  // for all edges
179  for (std::map<unsigned long long, unsigned>::const_iterator edgeIt =
180  edgeCounters.begin();
181  edgeIt != edgeCounters.end(); ++edgeIt) {
182  unsigned i1, i2;
183  DecodeEdgeKey(edgeIt->first, i1, i2);
184 
185  ScalarType flag = NAN_VALUE;
186  if (edgeIt->second == 1) {
187  // only one triangle uses this edge
188  flag = static_cast<ScalarType>(VERTEX_BORDER);
189  if (stats) ++stats->edgesNotShared;
190  } else if (edgeIt->second == 2) {
191  // two triangles use this edge
192  flag = static_cast<ScalarType>(VERTEX_NORMAL);
193  if (stats) ++stats->edgesSharedByTwo;
194  } else if (edgeIt->second > 2) {
195  // more than two triangles use this edge!
196  flag = static_cast<ScalarType>(VERTEX_NON_MANIFOLD);
197  if (stats) ++stats->edgesSharedByMore;
198  }
199  // else --> isolated vertex?
200 
201  flags->setValue(i1, flag);
202  flags->setValue(i2, flag);
203  }
204  }
205 
206  flags->computeMinAndMax();
207 
208  return true;
209 }
210 
212  GenericMesh* mesh,
213  unsigned numberOfPoints,
214  GenericProgressCallback* progressCb /*=0*/,
215  std::vector<unsigned>* triIndices /*=0*/) {
216  if (!mesh) return nullptr;
217 
218  // total mesh surface
219  double Stotal = computeMeshArea(mesh);
220 
221  if (LessThanEpsilon(Stotal)) return nullptr;
222 
223  double samplingDensity = numberOfPoints / Stotal;
224 
225  // no normal needs to be computed here
226  return samplePointsOnMesh(mesh, samplingDensity, numberOfPoints, progressCb,
227  triIndices);
228 }
229 
231  GenericMesh* mesh,
232  double samplingDensity,
233  GenericProgressCallback* progressCb /*=0*/,
234  std::vector<unsigned>* triIndices /*=0*/) {
235  if (!mesh) return nullptr;
236 
237  // we must compute the total area to deduce the number of points
238  double Stotal = computeMeshArea(mesh);
239 
240  unsigned theoreticNumberOfPoints =
241  static_cast<unsigned>(ceil(Stotal * samplingDensity));
242 
243  return samplePointsOnMesh(mesh, samplingDensity, theoreticNumberOfPoints,
244  progressCb, triIndices);
245 }
246 
248  GenericMesh* mesh,
249  double samplingDensity,
250  unsigned theoreticNumberOfPoints,
251  GenericProgressCallback* progressCb,
252  std::vector<unsigned>* triIndices /*=0*/) {
253  if (theoreticNumberOfPoints < 1) return nullptr;
254 
255  assert(mesh);
256  unsigned triCount = (mesh ? mesh->size() : 0);
257  if (triCount == 0) return nullptr;
258 
259  PointCloud* sampledCloud = new PointCloud();
260  if (!sampledCloud->reserve(theoreticNumberOfPoints)) // not enough memory
261  {
262  delete sampledCloud;
263  return nullptr;
264  }
265 
266  if (triIndices) {
267  triIndices->clear(); // just in case
268  try {
269  triIndices->reserve(theoreticNumberOfPoints);
270  } catch (const std::bad_alloc&) {
271  // not enough memory? DGM TODO: we should warn the caller
272  delete sampledCloud;
273  return nullptr;
274  }
275  }
276 
277  NormalizedProgress normProgress(progressCb, triCount);
278  if (progressCb) {
279  if (progressCb->textCanBeEdited()) {
280  progressCb->setMethodTitle("Mesh sampling");
281  char buffer[64];
282  snprintf(buffer, 64, "Triangles: %u\nPoints: %u", triCount,
283  theoreticNumberOfPoints);
284  progressCb->setInfo(buffer);
285  }
286  progressCb->update(0);
287  progressCb->start();
288  }
289 
290  unsigned addedPoints = 0;
291  std::random_device rd; // non-deterministic generator
292  std::mt19937 gen(rd()); // to seed mersenne twister.
293  std::uniform_real_distribution<double> dist(0, 1);
294 
295  // for each triangle
296  mesh->placeIteratorAtBeginning();
297  for (unsigned n = 0; n < triCount; ++n) {
298  const GenericTriangle* tri = mesh->_getNextTriangle();
299 
300  // vertices (OAB)
301  const CCVector3* O = tri->_getA();
302  const CCVector3* A = tri->_getB();
303  const CCVector3* B = tri->_getC();
304 
305  // edges (OA and OB)
306  CCVector3 u = *A - *O;
307  CCVector3 v = *B - *O;
308 
309  // we compute the (twice) the triangle area
310  CCVector3 N = u.cross(v);
311  double S = N.normd() / 2;
312 
313  // we deduce the number of points to generate on this face
314  double fPointsToAdd = S * samplingDensity;
315  unsigned pointsToAdd = static_cast<unsigned>(fPointsToAdd);
316 
317  // take care of the remaining fractional part
318  double fracPart = fPointsToAdd - static_cast<double>(pointsToAdd);
319  if (fracPart > 0) {
320  // we add a point with the same probability as its (relative) area
321  if (dist(gen) <= fracPart) pointsToAdd += 1;
322  }
323 
324  if (pointsToAdd) {
325  if (addedPoints + pointsToAdd >= theoreticNumberOfPoints) {
326  theoreticNumberOfPoints += pointsToAdd;
327  // reserve memory for the cloud
328  if (!sampledCloud->reserve(theoreticNumberOfPoints)) {
329  delete sampledCloud;
330  sampledCloud = nullptr;
331  if (triIndices) {
332  triIndices->resize(0);
333  }
334  break;
335  }
336  // reserve memory for the triangle indexes
337  if (triIndices &&
338  triIndices->capacity() <
339  theoreticNumberOfPoints) // not enough memory
340  {
341  try {
342  triIndices->reserve(theoreticNumberOfPoints);
343  } catch (const std::bad_alloc&) {
344  delete sampledCloud;
345  sampledCloud = nullptr;
346  if (triIndices) {
347  triIndices->resize(0);
348  }
349  break;
350  }
351  }
352  }
353 
354  for (unsigned i = 0; i < pointsToAdd; ++i) {
355  // we generate random points as in:
356  //'Greg Turk. Generating random points in triangles. In A. S.
357  // Glassner, editor, Graphics Gems, pages 24-28. Academic Press,
358  // 1990.'
359  double x = dist(gen);
360  double y = dist(gen);
361 
362  // we test if the generated point lies on the right side of (AB)
363  if (x + y > 1.0) {
364  x = 1.0 - x;
365  y = 1.0 - y;
366  }
367 
368  CCVector3 P = (*O) + static_cast<PointCoordinateType>(x) * u +
369  static_cast<PointCoordinateType>(y) * v;
370 
371  sampledCloud->addPoint(P);
372  if (triIndices) triIndices->push_back(n);
373  ++addedPoints;
374  }
375  }
376 
377  if (progressCb && !normProgress.oneStep()) break;
378  }
379 
380  if (sampledCloud) // can be in case of memory overflow!
381  {
382  if (addedPoints) {
383  sampledCloud->resize(
384  addedPoints); // should always be ok as addedPoints <
385  // theoreticNumberOfPoints
386  if (triIndices) triIndices->resize(addedPoints);
387  } else {
388  if (triIndices) triIndices->resize(0);
389  }
390  }
391 
392  return sampledCloud;
393 }
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
double normd() const
Returns vector norm (forces double precision output)
Definition: CVGeom.h:426
Type norm() const
Returns vector norm.
Definition: CVGeom.h:424
Vector3Tpl cross(const Vector3Tpl &v) const
Cross product.
Definition: CVGeom.h:412
A generic mesh with index-based vertex access.
virtual VerticesIndexes * getNextTriangleVertIndexes()=0
virtual unsigned size() const =0
Returns the number of triangles.
virtual void getBoundingBox(CCVector3 &bbMin, CCVector3 &bbMax)=0
Returns the mesh bounding-box.
virtual GenericTriangle * _getNextTriangle()=0
Returns the next triangle (relatively to the global iterator position)
virtual void placeIteratorAtBeginning()=0
Places the mesh iterator at the beginning.
virtual void setInfo(const char *infoStr)=0
Notifies some information about the ongoing process.
virtual void setMethodTitle(const char *methodTitle)=0
Notifies the algorithm title.
virtual bool textCanBeEdited() const
Returns whether the dialog title and info can be updated or not.
virtual void update(float percent)=0
Notifies the algorithm progress.
A generic triangle interface.
virtual const CCVector3 * _getA() const =0
Returns the first vertex (A)
virtual const CCVector3 * _getC() const =0
Returns the third vertex (C)
virtual const CCVector3 * _getB() const =0
Returns the second vertex (B)
static bool computeMeshEdgesConnectivity(GenericIndexedMesh *mesh, EdgeConnectivityStats &stats)
Computes some statistics on the edges connectivty of a mesh.
static bool flagMeshVerticesByType(GenericIndexedMesh *mesh, ScalarField *flags, EdgeConnectivityStats *stats=nullptr)
Flags the vertices of a mesh depending on their type.
static double computeMeshVolume(GenericMesh *mesh)
Computes the mesh volume.
static unsigned long long ComputeEdgeKey(unsigned i1, unsigned i2)
Computes the unique key corresponding to an edge.
static double computeMeshArea(GenericMesh *mesh)
Computes the mesh area.
std::map< unsigned long long, unsigned > EdgeUsageMap
Map used to count the number of triangles using each edge.
static PointCloud * samplePointsOnMesh(GenericMesh *mesh, double samplingDensity, GenericProgressCallback *progressCb=nullptr, std::vector< unsigned > *triIndices=nullptr)
Samples points on a mesh.
static bool buildMeshEdgeUsageMap(GenericIndexedMesh *mesh, EdgeUsageMap &edgeMap)
Creates a map to count the number of triangles using each edge.
static void DecodeEdgeKey(unsigned long long key, unsigned &i1, unsigned &i2)
Computes the edge vertex indexes from its unique key.
bool oneStep()
Increments total progress value of a single unit.
virtual bool reserve(unsigned newCapacity)
Reserves memory for the point database.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
bool resize(unsigned newCount) override
Resizes the point database.
Definition: CVPointCloud.h:41
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
void fill(ScalarType fillValue=0)
Fills the array with a particular value.
Definition: ScalarField.h:77
virtual void computeMinAndMax()
Determines the min and max values.
Definition: ScalarField.h:123
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
unsigned currentSize() const
Definition: ScalarField.h:100
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
::ccPointCloud PointCloud
Definition: PointCloud.h:19
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
Generic file read and write utility for python interface.
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370
Statistics on the edges connectivty of a mesh.
unsigned edgesSharedByTwo
Edges shared by exactly two triangles.
unsigned edgesNotShared
Edges not shared (i.e. used by only one triangle)
unsigned edgesSharedByMore
Edges shared by more than two triangles.
Triangle described by the indexes of its 3 vertices.