ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
distanceMapGenerationTool.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 
9 
10 // qCC
11 #include <ecvMainAppInterface.h>
12 
13 // CV_DB_LIB
14 #include <ecvMesh.h>
15 #include <ecvPointCloud.h>
16 #include <ecvPolyline.h>
17 #include <ecvProgressDialog.h>
18 #include <ecvScalarField.h>
19 
20 // cloudViewer
21 #include <Delaunay2dMesh.h>
22 
23 // Qt
24 #include <QFile>
25 #include <QMainWindow>
26 // Qt5/Qt6 Compatibility
27 #include <QtCompat.h>
28 
29 // Meta-data key for profile (polyline) origin
30 const char PROFILE_ORIGIN_KEY[] = "ProfileOrigin";
31 // Meta-data key for profile (polyline) axis
32 const char REVOLUTION_AXIS_KEY[] = "RevolutionAxis";
33 // Meta-data key for the profile (polyline) height shift
34 const char PROFILE_HEIGHT_SHIFT_KEY[] = "ProfileHeightShift";
35 
36 // shortcuts
37 static const double M_PI_DIV_2 = M_PI / 2;
38 static const double M_PI_DIV_4 = M_PI / 4;
39 
40 // helper
44  double r = static_cast<double>(x * x + y * y);
45 
46  if (r < 1.0e-8) {
47  return z < 0.0 ? -M_PI_DIV_2 : M_PI_DIV_2;
48  }
49 
50  return atan(z / sqrt(static_cast<double>(r)));
51 }
52 
53 // helper
54 static bool GetPolylineMetaVector(const ccPolyline* polyline,
55  const QString& key,
56  CCVector3& P) {
57  assert(polyline);
58  if (polyline) {
59  // we try to get the right meta-data 'vector'
60  QVariant x = polyline->getMetaData(key + QString(".x"));
61  QVariant y = polyline->getMetaData(key + QString(".y"));
62  QVariant z = polyline->getMetaData(key + QString(".z"));
63  if (x.isValid() && y.isValid() && z.isValid()) {
64  bool ok[3] = {true, true, true};
65  P.x = static_cast<PointCoordinateType>(x.toDouble(ok));
66  P.y = static_cast<PointCoordinateType>(y.toDouble(ok + 1));
67  P.z = static_cast<PointCoordinateType>(z.toDouble(ok + 2));
68  return (ok[0] && ok[1] && ok[2]);
69  }
70  }
71 
72  return false;
73 }
74 
75 // helper
76 static void SetPoylineMetaVector(ccPolyline* polyline,
77  const QString& key,
78  const CCVector3& P) {
79  assert(polyline);
80  if (polyline) {
81  // add revolution axis as meta-data
82  polyline->setMetaData(key + QString(".x"), QVariant(P.x));
83  polyline->setMetaData(key + QString(".y"), QVariant(P.y));
84  polyline->setMetaData(key + QString(".z"), QVariant(P.z));
85  }
86 }
87 
88 // helper
91  const {
92  ccGLMatrix cloudToSurfaceOrigin;
93  cloudToSurfaceOrigin.setTranslation(-origin);
94 
95  // we must take the axis of the Surface of Revolution into account (if any
96  // and if it is not colinear with X, Y or Z)
97  if (hasAxis &&
98  axis.u[revolDim] + std::numeric_limits<PointCoordinateType>::epsilon() <
99  1.0) {
100  ccGLMatrix rotation;
101  CCVector3 Z(0, 0, 0);
102  Z.u[revolDim] = PC_ONE;
103  rotation = ccGLMatrix::FromToRotation(axis, Z);
104  cloudToSurfaceOrigin = rotation * cloudToSurfaceOrigin;
105  }
106 
107  return cloudToSurfaceOrigin;
108 }
109 
110 // helper
113  const {
114  ccGLMatrix cloudToPolylineOrigin = computeCloudToSurfaceOriginTrans();
115 
116  // add the height shift along the revolution axis (if any)
117  cloudToPolylineOrigin.getTranslation()[revolDim] -= heightShift;
118 
119  return cloudToPolylineOrigin;
120 }
121 
123  int revolDim) {
124  assert(polyline);
125  if (polyline) {
126  // add revolution dimension as meta-data
127  QVariant dim(revolDim);
128  polyline->setMetaData(REVOLUTION_AXIS_KEY, dim);
129  }
130 }
131 
133  assert(polyline);
134  if (polyline) {
135  // we try to get the revolution dimension from the polyline meta-data
136  QVariant axis = polyline->getMetaData(REVOLUTION_AXIS_KEY);
137  if (axis.isValid()) {
138  bool ok = true;
139  int dim = axis.toInt(&ok);
140  if (ok && dim >= 0 && dim <= 2) return dim;
141  }
142  }
143 
144  return -1;
145 }
146 
148  const CCVector3& origin) {
149  SetPoylineMetaVector(polyline, PROFILE_ORIGIN_KEY, origin);
150 }
151 
153  CCVector3& origin) {
154  // we try to get the profile origin from the polyline meta-data
155  return GetPolylineMetaVector(polyline, PROFILE_ORIGIN_KEY, origin);
156 }
157 
159  const CCVector3& axis) {
160  SetPoylineMetaVector(polyline, REVOLUTION_AXIS_KEY, axis);
161 }
162 
164  CCVector3& axis) {
165  // we try to get the profile origin from the polyline meta-data
166  return GetPolylineMetaVector(polyline, REVOLUTION_AXIS_KEY, axis);
167 }
168 
170  ccPolyline* polyline, PointCoordinateType heightShift) {
171  assert(polyline);
172  if (polyline) {
173  // add 'height shift' as meta-data
174  polyline->setMetaData(PROFILE_HEIGHT_SHIFT_KEY, QVariant(heightShift));
175  }
176 }
177 
179  const ccPolyline* polyline, PointCoordinateType& heightShift) {
180  assert(polyline);
181  if (polyline) {
182  // retrieve the right meta-data
183  QVariant shift = polyline->getMetaData(PROFILE_HEIGHT_SHIFT_KEY);
184  if (shift.isValid()) {
185  bool ok;
186  heightShift = static_cast<PointCoordinateType>(shift.toDouble(&ok));
187  return ok;
188  }
189  }
190 
191  return false;
192 }
193 
195  ProfileMetaData& data) {
196  if (!polyline) {
197  assert(false);
198  return false;
199  }
200 
201  data.revolDim = GetPoylineRevolDim(polyline);
202  if (data.revolDim < 0 || data.revolDim > 2) {
203  return false;
204  }
205 
206  if (!GetPoylineOrigin(polyline, data.origin)) {
207  return false;
208  }
209 
210  if (!GetPolylineHeightShift(polyline, data.heightShift)) {
211  data.heightShift = 0;
212  }
213 
214  data.hasAxis = GetPoylineAxis(polyline, data.axis);
215 
216  return true;
217 }
218 
220  ccPointCloud* cloud,
222  bool storeRadiiAsSF /*=false*/,
223  ecvMainAppInterface* app /*=0*/) {
224  // check input cloud and profile/polyline
225  if (!cloud || !profile) {
226  if (app)
227  app->dispToConsole(
228  QString("Internal error: invalid input parameters"),
230  return false;
231  }
232  assert(cloud && profile);
233 
234  // number of vertices for the profile
236  profile->getAssociatedCloud();
237  unsigned vertexCount = vertices->size();
238  if (vertexCount < 2) {
239  if (app)
240  app->dispToConsole(
241  QString("Invalid polyline (not enough vertices)"),
243  return false;
244  }
245 
246  // profile meta-data
247  ProfileMetaData profileDesc;
248  if (!GetPoylineMetaData(profile, profileDesc)) {
249  if (app)
250  app->dispToConsole(
251  QString("Invalid polyline (bad or missing meta-data)"),
253  return false;
254  }
255 
256  // reserve a new scalar field (or take the old one if it already exists)
258  if (sfIdx < 0) sfIdx = cloud->addScalarField(RADIAL_DIST_SF_NAME);
259  if (sfIdx < 0) {
260  if (app)
261  app->dispToConsole(
262  QString("Failed to allocate a new scalar field for "
263  "computing distances! Try to free some memory ..."),
265  return false;
266  }
267  ccScalarField* sf =
268  static_cast<ccScalarField*>(cloud->getScalarField(sfIdx));
269  unsigned pointCount = cloud->size();
270  sf->resize(pointCount); // should always be ok
271  assert(sf);
272 
273  ccScalarField* radiiSf = 0;
274  if (storeRadiiAsSF) {
275  int sfIdxRadii = cloud->getScalarFieldIndexByName(RADII_SF_NAME);
276  if (sfIdxRadii < 0) sfIdxRadii = cloud->addScalarField(RADII_SF_NAME);
277  if (sfIdxRadii < 0) {
278  if (app)
279  app->dispToConsole(
280  QString("Failed to allocate a new scalar field for "
281  "storing radii! You should try to free some "
282  "memory ..."),
284  // return false;
285  } else {
286  radiiSf = static_cast<ccScalarField*>(
287  cloud->getScalarField(sfIdxRadii));
288  radiiSf->resize(pointCount); // should always be ok
289  }
290  }
291 
292  bool success = true;
293 
294  // now compute the distance between the cloud and the (implicit) surface of
295  // revolution
296  {
297  ccGLMatrix cloudToProfile =
298  profileDesc.computeCloudToProfileOriginTrans();
299 
300  // we deduce the horizontal dimensions from the revolution axis
301  const unsigned char dim1 = static_cast<unsigned char>(
302  profileDesc.revolDim < 2 ? profileDesc.revolDim + 1 : 0);
303  const unsigned char dim2 = (dim1 < 2 ? dim1 + 1 : 0);
304 
305  ecvProgressDialog dlg(true, app ? app->getMainWindow() : 0);
306  dlg.setMethodTitle(QObject::tr("Cloud to profile radial distance"));
307  dlg.setInfo(QObject::tr("Polyline: %1 vertices\nCloud: %2 points")
308  .arg(vertexCount)
309  .arg(pointCount));
310  dlg.start();
312  static_cast<cloudViewer::GenericProgressCallback*>(&dlg),
313  pointCount);
314 
315  for (unsigned i = 0; i < pointCount; ++i) {
316  const CCVector3* P = cloud->getPoint(i);
317 
318  // relative point position
319  CCVector3 Prel = cloudToProfile * (*P);
320 
321  // deduce point height and radius (i.e. in profile 2D coordinate
322  // system)
323  double height = Prel.u[profileDesc.revolDim];
324  // TODO FIXME: we assume the surface of revolution is smooth!
325  double radius = sqrt(Prel.u[dim1] * Prel.u[dim1] +
326  Prel.u[dim2] * Prel.u[dim2]);
327 
328  if (radiiSf) {
329  ScalarType radiusVal = static_cast<ScalarType>(radius);
330  radiiSf->setValue(i, radiusVal);
331  }
332 
333  // search nearest "segment" in polyline
334  ScalarType minDist = NAN_VALUE;
335  for (unsigned j = 1; j < vertexCount; ++j) {
336  const CCVector3* A = vertices->getPoint(j - 1);
337  const CCVector3* B = vertices->getPoint(j);
338 
339  double alpha = (height - A->y) / (B->y - A->y);
340  if (alpha >= 0.0 && alpha <= 1.0) {
341  // we deduce the right radius by linear interpolation
342  double radius_th = A->x + alpha * (B->x - A->x);
343  double dist = radius - radius_th;
344 
345  // we look at the closest segment (if the polyline is
346  // concave!)
347  if (!cloudViewer::ScalarField::ValidValue(minDist) ||
348  dist * dist < minDist * minDist) {
349  minDist = static_cast<ScalarType>(dist);
350  }
351  }
352  }
353 
354  sf->setValue(i, minDist);
355 
356  if (!nProgress.oneStep()) {
357  // cancelled by user
358  for (unsigned j = i; j < pointCount; ++j)
359  sf->setValue(j, NAN_VALUE);
360 
361  success = false;
362  break;
363  }
364 
365  // TEST
366  //*const_cast<CCVector3*>(P) = Prel;
367  }
368 
369  // TEST
370  // cloud->invalidateBoundingBox();
371  }
372 
373  sf->computeMinAndMax();
374  cloud->setCurrentDisplayedScalarField(sfIdx);
375  cloud->showSF(true);
376 
377  return success;
378 }
379 
381  ccPointCloud* cloud,
382  double& minLat_rad,
383  double& maxLat_rad,
384  const ccGLMatrix& cloudToSurfaceOrigin, // e.g. translation to the
385  // revolution origin
386  unsigned char revolutionAxisDim) {
387  minLat_rad = maxLat_rad = 0.0;
388 
389  assert(cloud);
390  // invalid parameters?
391  if (!cloud || revolutionAxisDim > 2) return false;
392 
393  unsigned count = cloud->size();
394  if (count == 0) return true;
395 
396  // revolution axis
397  const unsigned char Z = revolutionAxisDim;
398  // we deduce the 2 other ('horizontal') dimensions from the revolution axis
399  const unsigned char X = (Z < 2 ? Z + 1 : 0);
400  const unsigned char Y = (X < 2 ? X + 1 : 0);
401 
402  for (unsigned n = 0; n < count; ++n) {
403  const CCVector3* P = cloud->getPoint(n);
404  CCVector3 relativePos = cloudToSurfaceOrigin * (*P);
405 
406  // latitude between 0 and pi/2
407  double lat_rad = ComputeLatitude_rad(relativePos.u[X], relativePos.u[Y],
408  relativePos.u[Z]);
409 
410  if (n) {
411  if (lat_rad < minLat_rad)
412  minLat_rad = lat_rad;
413  else if (lat_rad > maxLat_rad)
414  maxLat_rad = lat_rad;
415  } else {
416  minLat_rad = maxLat_rad = lat_rad;
417  }
418  }
419 
420  return true;
421 }
422 
423 double DistanceMapGenerationTool::ConicalProjectN(double phi1, double phi2) {
424  if (phi1 >= phi2) return 1.0;
425 
426  assert(fabs(phi1) < M_PI_DIV_2);
427  assert(fabs(phi2) < M_PI_DIV_2);
428 
429  double tan_pl1 = tan(M_PI_DIV_4 - phi1 / 2);
430  double tan_pl2 = tan(M_PI_DIV_4 - phi2 / 2);
431 
432  return (log(cos(phi1)) - log(cos(phi2))) / (log(tan_pl1) - log(tan_pl2));
433 }
434 
436  double phi1,
437  double n) {
438  assert(phi1 >= -M_PI_DIV_2 && phi1 < M_PI_DIV_2);
439 
440  double tan_pl1 = tan(M_PI_DIV_4 - phi1 / 2);
441  double tan_pl = tan(M_PI_DIV_4 - phi / 2);
442 
443  return cos(phi1) * pow(tan_pl / tan_pl1, n) / n;
444 }
445 
446 QSharedPointer<DistanceMapGenerationTool::Map>
448  ccScalarField* sf,
449  const ccGLMatrix& cloudToSurface,
450  unsigned char revolutionAxisDim,
451  double xStep_rad,
452  double yStep,
453  double yMin,
454  double yMax,
455  bool conical,
456  bool counterclockwise,
457  FillStrategyType fillStrategy,
458  EmptyCellFillOption emptyCellfillOption,
459  ecvMainAppInterface* app /*=0*/) {
460  assert(cloud && sf);
461  if (!cloud || !sf) {
462  if (app)
463  app->dispToConsole(QString("[DistanceMapGenerationTool] Internal "
464  "error: invalid input structures!"),
466  return QSharedPointer<Map>(0);
467  }
468 
469  // invalid parameters?
470  if (xStep_rad <= 0.0 || yStep <= 0.0 || yMax <= yMin ||
471  revolutionAxisDim > 2) {
472  if (app)
473  app->dispToConsole(QString("[DistanceMapGenerationTool] Internal "
474  "error: invalid grid parameters!"),
476  return QSharedPointer<Map>(0);
477  }
478 
479  unsigned count = cloud->size();
480  if (count == 0) {
481  if (app)
482  app->dispToConsole(QString("[DistanceMapGenerationTool] Cloud is "
483  "empty! Nothing to do!"),
485  return QSharedPointer<Map>(0);
486  }
487 
488  // revolution axis
489  const unsigned char Z = revolutionAxisDim;
490  // we deduce the 2 other ('horizontal') dimensions from the revolution axis
491  const unsigned char X = (Z < 2 ? Z + 1 : 0);
492  const unsigned char Y = (X < 2 ? X + 1 : 0);
493 
494  // grid dimensions
495  unsigned xSteps = 0;
496  {
497  if (xStep_rad > 0)
498  xSteps = static_cast<unsigned>(ceil((2 * M_PI) / xStep_rad));
499  if (xSteps == 0) {
500  if (app)
501  app->dispToConsole(QString("[DistanceMapGenerationTool] "
502  "Invalid longitude step/boundaries! "
503  "Can't generate a proper map!"),
505  return QSharedPointer<Map>(0);
506  }
507  }
508 
509  unsigned ySteps = 0;
510  {
511  if (yStep > 0)
512  ySteps = static_cast<unsigned>(ceil((yMax - yMin) / yStep));
513  if (ySteps == 0) {
514  if (app)
515  app->dispToConsole(QString("[DistanceMapGenerationTool] "
516  "Invalid latitude step/boundaries! "
517  "Can't generate a proper map!"),
519  return QSharedPointer<Map>(0);
520  }
521  }
522 
523  unsigned cellCount = xSteps * ySteps;
524  if (app)
525  app->dispToConsole(QString("[DistanceMapGenerationTool] Projected map "
526  "size: %1 x %2 (%3 cells)")
527  .arg(xSteps)
528  .arg(ySteps)
529  .arg(cellCount),
531 
532  // reserve memory for the output matrix
533  QSharedPointer<Map> grid(new Map);
534  try {
535  grid->resize(cellCount);
536  } catch (const std::bad_alloc&) {
537  if (app)
538  app->dispToConsole(
539  QString("[DistanceMapGenerationTool] Not enough memory!"),
541  return QSharedPointer<Map>(0);
542  }
543 
544  // update grid info ("for the records")
545  grid->xSteps = xSteps;
546  grid->xMin = 0.0;
547  grid->xMax = 2 * M_PI;
548  grid->xStep = xStep_rad;
549  grid->ySteps = ySteps;
550  grid->yMin = yMin;
551  grid->yMax = yMax;
552  grid->yStep = yStep;
553  grid->conical = conical;
554 
555  // motion direction
556  grid->counterclockwise = counterclockwise;
557  double ccw = (counterclockwise ? -1.0 : 1.0);
558 
559  for (unsigned n = 0; n < count; ++n) {
560  // we skip invalid values
561  const ScalarType& val = sf->getValue(n);
562  if (!cloudViewer::ScalarField::ValidValue(val)) continue;
563 
564  const CCVector3* P = cloud->getPoint(n);
565  CCVector3 relativePos = cloudToSurface * (*P);
566 
567  // convert to cylindrical or conical (spherical) coordinates
568  double x =
569  ccw * atan2(relativePos.u[X], relativePos.u[Y]); // longitude
570  if (x < 0.0) {
571  x += 2 * M_PI;
572  }
573 
574  double y = 0.0;
575  if (conical) {
577  relativePos.u[X], relativePos.u[Y],
578  relativePos.u[Z]); // latitude between 0 and pi/2
579  } else {
580  y = relativePos.u[Z]; // height
581  }
582 
583  int i = static_cast<int>((x - grid->xMin) / grid->xStep);
584  int j = static_cast<int>((y - grid->yMin) / grid->yStep);
585 
586  // if we fall exactly on the max corner of the grid box
587  if (i == static_cast<int>(grid->xSteps)) --i;
588  if (j == static_cast<int>(grid->ySteps)) --j;
589 
590  // we skip points outside the box!
591  if (i < 0 || i >= static_cast<int>(grid->xSteps) || j < 0 ||
592  j >= static_cast<int>(grid->ySteps)) {
593  continue;
594  }
595  assert(i >= 0 && j >= 0);
596 
597  MapCell& cell = (*grid)[j * static_cast<int>(grid->xSteps) + i];
598  if (cell.count) // if there's already values projected in this cell
599  {
600  switch (fillStrategy) {
601  case FILL_STRAT_MIN_DIST:
602  // Set the minimum SF value
603  if (val < cell.value) cell.value = val;
604  break;
605  case FILL_STRAT_AVG_DIST:
606  // Sum the values
607  cell.value += static_cast<double>(val);
608  break;
609  case FILL_STRAT_MAX_DIST:
610  // Set the maximum SF value
611  if (val > cell.value) cell.value = val;
612  break;
613  default:
614  assert(false);
615  break;
616  }
617  } else {
618  // for the first point, we simply have to store its associated value
619  // (whatever the case)
620  cell.value = val;
621  }
622  ++cell.count;
623 
624  // if (progressCb)
625  // progressCb->update(30.0 * (float)n / (float)cloud->size());
626  }
627 
628  // we need to finish the average values computation
629  if (fillStrategy == FILL_STRAT_AVG_DIST) {
630  MapCell* cell = &grid->at(0);
631  for (unsigned i = 0; i < cellCount; ++i, ++cell)
632  if (cell->count > 1)
633  cell->value /= static_cast<double>(cell->count);
634  }
635 
636  // fill empty cells with zero?
637  if (emptyCellfillOption == FILL_WITH_ZERO) {
638  MapCell* cell = &grid->at(0);
639  for (unsigned i = 0; i < cellCount; ++i, ++cell) {
640  if (cell->count == 0) {
641  cell->value = 0.0;
642  cell->count = 1;
643  }
644  }
645  } else if (emptyCellfillOption == FILL_INTERPOLATE) {
646  // convert the non-empty cells to a 2D point cloud
647  unsigned fillCount = 0;
648  {
649  MapCell* cell = &grid->at(0);
650  for (unsigned i = 0; i < cellCount; ++i, ++cell)
651  if (cell->count != 0) ++fillCount;
652  }
653 
654  // do we really need to interpolate empty grid cells?
655  if (fillCount) {
656  std::vector<CCVector2> the2DPoints;
657  try {
658  the2DPoints.reserve(fillCount);
659  } catch (...) {
660  // out of memory
661  if (app)
662  app->dispToConsole(
663  QString("[DistanceMapGenerationTool] Not enough "
664  "memory to interpolate!"),
666  }
667 
668  if (the2DPoints.capacity() == fillCount) {
669  // fill 2D vector with non-empty cell indexes
670  {
671  const MapCell* cell = &grid->at(0);
672  for (unsigned j = 0; j < grid->ySteps; ++j)
673  for (unsigned i = 0; i < grid->xSteps; ++i, ++cell)
674  if (cell->count)
675  the2DPoints.push_back(CCVector2(
676  static_cast<PointCoordinateType>(i),
677  static_cast<PointCoordinateType>(j)));
678  }
679 
680  // mesh the '2D' points
683  std::string errorStr;
684  if (!dm->buildMesh(the2DPoints,
686  errorStr)) {
687  if (app)
688  app->dispToConsole(
689  QString("[DistanceMapGenerationTool] "
690  "Interpolation failed: Triangle lib. "
691  "said '%1'")
692  .arg(QString::fromStdString(errorStr)),
694  } else {
695  unsigned triNum = dm->size();
696  MapCell* cells = &grid->at(0);
697  // now we are going to 'project' all triangles on the grid
699  for (unsigned k = 0; k < triNum; ++k) {
700  const cloudViewer::VerticesIndexes* tsi =
702  // get the triangle bounding box (in grid coordinates)
703  int P[3][2];
704  int xMin = 0, yMin = 0, xMax = 0, yMax = 0;
705  {
706  for (unsigned j = 0; j < 3; ++j) {
707  const CCVector2& P2D = the2DPoints[tsi->i[j]];
708  P[j][0] = static_cast<int>(P2D.x);
709  P[j][1] = static_cast<int>(P2D.y);
710  }
711  xMin = std::min(std::min(P[0][0], P[1][0]),
712  P[2][0]);
713  yMin = std::min(std::min(P[0][1], P[1][1]),
714  P[2][1]);
715  xMax = std::max(std::max(P[0][0], P[1][0]),
716  P[2][0]);
717  yMax = std::max(std::max(P[0][1], P[1][1]),
718  P[2][1]);
719  }
720  // now scan the cells
721  {
722  // pre-computation for barycentric coordinates
723  const double& valA =
724  cells[P[0][0] + P[0][1] * grid->xSteps]
725  .value;
726  const double& valB =
727  cells[P[1][0] + P[1][1] * grid->xSteps]
728  .value;
729  const double& valC =
730  cells[P[2][0] + P[2][1] * grid->xSteps]
731  .value;
732  int det =
733  (P[1][1] - P[2][1]) * (P[0][0] - P[2][0]) +
734  (P[2][0] - P[1][0]) * (P[0][1] - P[2][1]);
735 
736  for (int j = yMin; j <= yMax; ++j) {
737  MapCell* cell =
738  cells +
739  static_cast<unsigned>(j) * grid->xSteps;
740 
741  for (int i = xMin; i <= xMax; ++i) {
742  // if the cell is empty
743  if (!cell[i].count) {
744  // we test if it's included or not in
745  // the current triangle Point Inclusion
746  // in Polygon Test (inspired from W.
747  // Randolph Franklin - WRF)
748  bool inside = false;
749  for (int ti = 0; ti < 3; ++ti) {
750  const int* P1 = P[ti];
751  const int* P2 = P[(ti + 1) % 3];
752  if ((P2[1] <= j && j < P1[1]) ||
753  (P1[1] <= j && j < P2[1])) {
754  int t = (i - P2[0]) * (P1[1] -
755  P2[1]) -
756  (P1[0] - P2[0]) *
757  (j - P2[1]);
758  if (P1[1] < P2[1]) t = -t;
759  if (t < 0) inside = !inside;
760  }
761  }
762  // can we interpolate?
763  if (inside) {
764  double l1 =
765  static_cast<double>(
766  (P[1][1] -
767  P[2][1]) *
768  (i -
769  P[2][0]) +
770  (P[2][0] -
771  P[1][0]) *
772  (j -
773  P[2][1])) /
774  det;
775  double l2 =
776  static_cast<double>(
777  (P[2][1] -
778  P[0][1]) *
779  (i -
780  P[2][0]) +
781  (P[0][0] -
782  P[2][0]) *
783  (j -
784  P[2][1])) /
785  det;
786  double l3 = 1.0 - l1 - l2;
787 
788  cell[i].count = 1;
789  cell[i].value = l1 * valA +
790  l2 * valB +
791  l3 * valC;
792  }
793  }
794  }
795  }
796  }
797  }
798  }
799 
800  delete dm;
801  dm = 0;
802  }
803  }
804  }
805 
806  // update min and max values
807  {
808  const MapCell* cell = &grid->at(0);
809  grid->minVal = grid->maxVal = cell->value;
810  ++cell;
811  for (unsigned i = 1; i < cellCount; ++i, ++cell) {
812  if (cell->value < grid->minVal)
813  grid->minVal = cell->value;
814  else if (cell->value > grid->maxVal)
815  grid->maxVal = cell->value;
816  }
817  }
818 
819  // end of process
820  return grid;
821 }
822 
824  double lat_rad,
825  double latMin_rad,
826  double nProj,
827  bool counterclockwise) {
828  double theta = nProj * (lon_rad - M_PI);
829  double r = ConicalProject(lat_rad, latMin_rad, nProj);
830 
831  CCVector3 P(static_cast<PointCoordinateType>((counterclockwise ? -r : r) *
832  sin(theta)),
833  static_cast<PointCoordinateType>(-r * cos(theta)), 0);
834 
835  return P;
836 }
837 
839  const QSharedPointer<Map>& map,
840  bool counterclockwise,
841  QImage mapTexture /*=QImage()*/) {
842  if (!map) return 0;
843 
844  unsigned meshVertCount = map->xSteps * map->ySteps;
845  unsigned meshFaceCount = (map->xSteps - 1) * (map->ySteps - 1) * 2;
846  ccPointCloud* cloud = new ccPointCloud();
847  ccMesh* mesh = new ccMesh(cloud);
848  mesh->addChild(cloud);
849  if (!cloud->reserve(meshVertCount) || !mesh->reserve(meshFaceCount)) {
850  // not enough memory
851  delete mesh;
852  return 0;
853  }
854 
855  // compute projection constant
856  double nProj =
857  ConicalProjectN(map->yMin, map->yMax) * map->conicalSpanRatio;
858  assert(nProj >= -1.0 && nProj <= 1.0);
859 
860  // create vertices
861  {
862  double cwSign = (counterclockwise ? -1.0 : 1.0);
863  for (unsigned j = 0; j < map->xSteps; ++j) {
864  // longitude
865  double lon_rad =
866  static_cast<double>(j) / map->xSteps * (2.0 * M_PI);
867 
868  double theta = nProj *
869  (lon_rad -
870  M_PI); //-Pi shift so that the map is well centered
871  double sin_theta = sin(theta);
872  double cos_theta = cos(theta);
873 
874  for (unsigned i = 0; i < map->ySteps; ++i) {
875  double lat_rad =
876  map->yMin + static_cast<double>(i) * map->yStep;
877  double r = ConicalProject(lat_rad, map->yMin, nProj);
878 
879  CCVector3 Pxyz(static_cast<PointCoordinateType>(cwSign * r *
880  sin_theta),
881  static_cast<PointCoordinateType>(-r * cos_theta),
882  0);
883  cloud->addPoint(Pxyz);
884  }
885  }
886  }
887 
888  // create facets
889  {
890  for (unsigned j = 0; j + 1 < map->xSteps; ++j) {
891  for (unsigned i = 0; i + 1 < map->ySteps; ++i) {
892  unsigned vertA = j * map->ySteps + i;
893  unsigned vertB = vertA + map->ySteps;
894  unsigned vertC = vertB + 1;
895  unsigned vertD = vertA + 1;
896 
897  mesh->addTriangle(vertB, vertC, vertD);
898  mesh->addTriangle(vertB, vertD, vertA);
899  }
900  }
901  }
902 
903  // do we have a texture as well?
904  if (true ) // we force tex. coordinates and indexes
905  // creation!
906  {
907  // texture coordinates
909  if (!texCoords->reserveSafe(meshVertCount)) {
910  // not enough memory to finish the job!
911  delete texCoords;
912  return mesh;
913  }
914 
915  // create default texture coordinates
916  for (unsigned j = 0; j < map->xSteps; ++j) {
917  TexCoords2D T(static_cast<float>(j) / (map->xSteps - 1), 0.0f);
918  for (unsigned i = 0; i < map->ySteps; ++i) {
919  T.ty = static_cast<float>(i) / (map->ySteps - 1);
920  texCoords->addElement(T);
921  }
922  }
923 
924  if (!mesh->reservePerTriangleTexCoordIndexes()) {
925  // not enough memory to finish the job!
926  delete texCoords;
927  return mesh;
928  }
929 
930  // set texture indexes
931  {
932  for (unsigned j = 0; j + 1 < map->xSteps; ++j) {
933  for (unsigned i = 0; i + 1 < map->ySteps; ++i) {
934  unsigned vertA = j * map->ySteps + i;
935  unsigned vertB = vertA + map->ySteps;
936  unsigned vertC = vertB + 1;
937  unsigned vertD = vertA + 1;
938 
939  mesh->addTriangleTexCoordIndexes(vertB, vertC, vertD);
940  mesh->addTriangleTexCoordIndexes(vertB, vertD, vertA);
941  }
942  }
943  }
944 
945  // set material indexes
946  if (!mesh->reservePerTriangleMtlIndexes()) {
947  // not enough memory to finish the job!
948  delete texCoords;
950  return mesh;
951  }
952  for (unsigned i = 0; i < meshFaceCount; ++i) {
953  mesh->addTriangleMtlIndex(0);
954  }
955 
956  // set material
957 #if 0
958  {
959  ccMaterial::Shared material(new ccMaterial("texture"));
960  material->setTexture(mapTexture, QString(), false);
961 
962  ccMaterialSet* materialSet = new ccMaterialSet();
963  materialSet->addMaterial(material);
964 
965  mesh->setMaterialSet(materialSet);
966  }
967 #endif
968 
969  mesh->setTexCoordinatesTable(texCoords);
970  mesh->showMaterials(true);
971  mesh->setVisible(true);
972  }
973 
974  return mesh;
975 }
976 
978  const QSharedPointer<Map>& map,
980  Measures& surface,
981  Measures& volume) {
982  if (!map || !profile)
983  // invalid input!
984  return false;
985 
987  profile->getAssociatedCloud();
988  unsigned vertexCount = vertices ? vertices->size() : 0;
989  if (vertexCount < 2) {
990  // invalid profile!
991  return false;
992  }
993 
994  const ccPointCloud* pcVertices =
995  dynamic_cast<ccPointCloud*>(profile->getAssociatedCloud());
996  if (!pcVertices) {
997  return false;
998  }
999 
1000  // surface measures
1001  surface = Measures();
1002  // volume measures
1003  volume = Measures();
1004 
1005  // theoretical surface and volumes
1006  {
1007  double surfaceProd = 0.0;
1008  double volumeProd = 0.0;
1009  const double yMax = map->yMin + map->yStep * map->ySteps;
1010  for (unsigned i = 1; i < pcVertices->size(); ++i) {
1011  const CCVector3* P0 = pcVertices->getPoint(i - 1);
1012  const CCVector3* P1 = pcVertices->getPoint(i);
1013 
1014  // polyline: X = radius, Y = height
1015  double r0 = P0->x;
1016  double y0 = P0->y;
1017  double r1 = P1->x;
1018  double y1 = P1->y;
1019 
1020  // without loss of generality ;)
1021  if (y0 > y1) {
1022  std::swap(y0, y1);
1023  std::swap(r0, r1);
1024  }
1025 
1026  // segment is totally outside the map?
1027  if (y1 < map->yMin || y0 > yMax) {
1028  // we skip it
1029  continue;
1030  }
1031 
1032  if (y0 < map->yMin) {
1033  // interpolate r0 @ map->yMin
1034  double alpha = (map->yMin - y0) / (y1 - y0);
1035  assert(alpha >= 0.0 && alpha <= 1.0);
1036  r0 = r0 + alpha * (r1 - r0);
1037  y0 = map->yMin;
1038  } else if (y1 > yMax) {
1039  // interpolate r1 @ map->yMax
1040  double alpha = (yMax - y0) / (y1 - y0);
1041  assert(alpha >= 0.0 && alpha <= 1.0);
1042  r1 = r0 + alpha * (r1 - r0);
1043  y1 = yMax;
1044  }
1045 
1046  // product for truncated cone surface (see
1047  // http://en.wikipedia.org/wiki/Frustum)
1048  double segmentLength =
1049  sqrt((r1 - r0) * (r1 - r0) + (y1 - y0) * (y1 - y0));
1050  surfaceProd += (r0 + r1) * segmentLength;
1051 
1052  // product for truncated cone volume (see
1053  // http://en.wikipedia.org/wiki/Frustum)
1054  volumeProd += (y1 - y0) * (r0 * r0 + r1 * r1 + r0 * r1);
1055  }
1056 
1057  surface.theoretical = M_PI * surfaceProd;
1058  volume.theoretical = M_PI / 3.0 * volumeProd;
1059  }
1060 
1061  int revolDim = GetPoylineRevolDim(profile);
1062  if (revolDim < 0) return false;
1063 
1064  // constant factors
1065  const double surfPart =
1066  map->xStep /
1067  2.0; // perimeter of a portion of circle of angle alpha = alpha * r
1068  // (* height to get the external surface)
1069  const double volPart = map->yStep * map->xStep /
1070  6.0; // area of a portion of circle of angle alpha =
1071  // alpha/2 * r^2 (* height to get the volume)
1072 
1073  const MapCell* cell = &map->at(0);
1074  // for each row
1075  for (unsigned j = 0; j < map->ySteps; ++j) {
1076  // corresponding heights
1077  double height1 = map->yMin + j * map->yStep;
1078  double height2 = height1 + map->yStep;
1079  double r_th1 = -1.0;
1080  double r_th2 = -1.0;
1081 
1082  // search nearest "segment" in polyline
1083  double height_middle = (height1 + height2) / 2.0;
1084  for (unsigned k = 1; k < vertexCount; ++k) {
1085  const CCVector3* A = vertices->getPoint(k - 1);
1086  const CCVector3* B = vertices->getPoint(k);
1087 
1088  double alpha = (height_middle - A->y) / (B->y - A->y);
1089  if (alpha >= 0.0 && alpha <= 1.0) {
1090  r_th1 = A->x + (height1 - A->y) / (B->y - A->y) * (B->x - A->x);
1091  r_th2 = A->x + (height2 - A->y) / (B->y - A->y) * (B->x - A->x);
1092  break; // FIXME: we hope that there's only one segment facing
1093  // this particular height?!
1094  }
1095  }
1096 
1097  if (r_th1 >= 0.0 /* && r_th2 >= 0.0*/) {
1098  // for each column
1099  for (unsigned i = 0; i < map->xSteps; ++i, ++cell) {
1100  // deviation from theory
1101  double d = (cell->count != 0 ? cell->value : 0.0);
1102 
1103  //"real" radius
1104  double r1 = r_th1 + d; // see ComputeRadialDist --> << double
1105  // dist = radius - radius_th; >>
1106  double r2 = r_th2 +
1107  d; // FIXME: works only if the "scalar field" used
1108  // for map creation was radial distances!!!
1109 
1110  // surface of the element (truncated cone external face)
1111  {
1112  double s = sqrt((r2 - r1) * (r2 - r1) +
1113  map->yStep * map->yStep);
1114  double externalSurface = /*surfPart * */ (r1 + r2) * s;
1115  surface.total += externalSurface;
1116  // dispatch in 'positive' and 'negative' surface
1117  if (d >= 0.0)
1118  surface.positive += externalSurface;
1119  else
1120  surface.negative += externalSurface;
1121  }
1122 
1123  // volume of the element
1124  {
1125  volume.total +=
1126  /*volPart * */ (r1 * r1 + r2 * r2 + r1 * r2);
1127  // volume of the gain (or loss) of matter
1128  double diffVolume = /*volPart * */ fabs(
1129  3.0 * d *
1130  (r_th1 + r_th2 +
1131  d)); // = (r*r) * part - (r_th*r_th) * part =
1132  // [(r_th+d)*(r_th+d)-r_th*r_th] * part
1133  if (d >= 0.0)
1134  volume.positive += diffVolume;
1135  else
1136  volume.negative += diffVolume;
1137  }
1138  }
1139  } else {
1140  cell += map->xSteps;
1141  }
1142  }
1143 
1144  // don't forget to mult. by constants
1145  surface.total *= surfPart;
1146  surface.positive *= surfPart;
1147  surface.negative *= surfPart;
1148 
1149  volume.total *= volPart;
1150  volume.positive *= volPart;
1151  volume.negative *= volPart;
1152 
1153  return true;
1154 }
1155 
1157  ccPointCloud* cloud,
1158  const ccGLMatrix&
1159  cloudToSurface, // e.g. translation to the revolution origin
1160  unsigned char revolutionAxisDim,
1161  bool counterclockwise /*=false*/) {
1162  assert(cloud);
1163  if (!cloud || cloud->size() == 0) return false;
1164 
1165  // revolution axis
1166  const unsigned char Z = revolutionAxisDim;
1167  // we deduce the 2 other ('horizontal') dimensions from the revolution axis
1168  const unsigned char X = (Z < 2 ? Z + 1 : 0);
1169  const unsigned char Y = (X < 2 ? X + 1 : 0);
1170 
1171  // motion direction
1172  PointCoordinateType ccw = (counterclockwise ? -PC_ONE : PC_ONE);
1173 
1174  // get projection height
1175  for (unsigned n = 0; n < cloud->size(); ++n) {
1176  CCVector3* P = const_cast<CCVector3*>(cloud->getPoint(n));
1177  CCVector3 relativePos = cloudToSurface * (*P);
1178 
1179  // convert to cylindrical coordinates
1180  double lon_rad =
1181  ccw * atan2(relativePos.u[X], relativePos.u[Y]); // longitude
1182  if (lon_rad < 0.0) {
1183  lon_rad += 2 * M_PI;
1184  }
1185 
1186  PointCoordinateType height = relativePos.u[Z];
1187 
1188  P->x = static_cast<PointCoordinateType>(lon_rad);
1189  P->y = height;
1190  P->z = 0;
1191  }
1192 
1193  cloud->refreshBB();
1194  if (cloud->getOctree()) {
1195  cloud->deleteOctree();
1196  }
1197  // TODO FIXME: and kd-trees? etc. We need a better way to handle those
1198  // cases...
1199 
1200  return true;
1201 }
1202 
1204  ccPointCloud* cloud,
1205  const ccGLMatrix&
1206  cloudToSurface, // e.g. translation to the revolution origin
1207  unsigned char revolutionAxisDim,
1208  double latMin_rad,
1209  double latMax_rad,
1210  double conicalSpanRatio /*=1.0*/,
1211  bool counterclockwise /*=false*/) {
1212  assert(cloud);
1213  if (!cloud || cloud->size() == 0) return false;
1214 
1215  // revolution axis
1216  const unsigned char Z = revolutionAxisDim;
1217  // we deduce the 2 other ('horizontal') dimensions from the revolution axis
1218  const unsigned char X = (Z < 2 ? Z + 1 : 0);
1219  const unsigned char Y = (X < 2 ? X + 1 : 0);
1220 
1221  // motion direction
1222  PointCoordinateType ccw = (counterclockwise ? -PC_ONE : PC_ONE);
1223  // projection factor
1224  double nProj = ConicalProjectN(latMin_rad, latMax_rad) * conicalSpanRatio;
1225 
1226  // get projection height
1227  for (unsigned n = 0; n < cloud->size(); ++n) {
1228  CCVector3* P = const_cast<CCVector3*>(cloud->getPoint(n));
1229  CCVector3 relativePos = cloudToSurface * (*P);
1230 
1231  // convert to cylindrical coordinates
1232  PointCoordinateType ang_rad =
1233  ccw * atan2(relativePos.u[X], relativePos.u[Y]);
1234  if (ang_rad < 0.0)
1235  ang_rad += static_cast<PointCoordinateType>(2 * M_PI);
1236 
1237  double lat_rad =
1238  ComputeLatitude_rad(relativePos.u[X], relativePos.u[Y],
1239  relativePos.u[Z]); // between 0 and pi/2
1240 
1241  *P = ProjectPointOnCone(ang_rad, lat_rad, latMin_rad, nProj,
1242  counterclockwise);
1243  }
1244 
1245  cloud->refreshBB();
1246  if (cloud->getOctree()) {
1247  cloud->deleteOctree();
1248  }
1249  // TODO FIXME: and kd-trees? etc. We need a better way to handle those
1250  // cases...
1251 
1252  return true;
1253 }
1254 
1256  const QSharedPointer<Map>& map,
1257  QString filename,
1258  QString xUnit,
1259  QString yUnit,
1260  double xConversionFactor /*=1.0*/,
1261  double yConversionFactor /*=1.0*/,
1262  ecvMainAppInterface* app /*=0*/) {
1263  if (!map) {
1264  if (app)
1265  app->dispToConsole(QString("[SaveMapAsCSVMatrix] Internal error: "
1266  "invalid input map!"),
1268  return false;
1269  }
1270 
1271  // write file
1272  QFile file(filename);
1273  if (!file.open(QFile::WriteOnly | QFile::Text)) {
1274  if (app)
1275  app->dispToConsole(QString("[SaveMapAsCSVMatrix] Failed to open "
1276  "file for writing! Check access rights"),
1278  return false;
1279  }
1280  QTextStream stream(&file);
1281 
1282  // write CSV header
1283  {
1284  // min and max height (for all lines)
1285  stream << QString("Height min (%1);").arg(yUnit);
1286  stream << QString("Height max (%1);").arg(yUnit);
1287 
1288  // for each column
1289  for (unsigned i = 0; i < map->xSteps; ++i) {
1290  // min and max angle for the current column
1291  double minX = xConversionFactor * (map->xMin + i * map->xStep);
1292  double maxX =
1293  xConversionFactor * (map->xMin + (i + 1) * map->xStep);
1294  stream << QString("%1-%2 (%3);").arg(minX).arg(maxX).arg(xUnit);
1295  }
1296 
1297  // eol
1298  stream << QtCompat::endl;
1299  }
1300 
1301  // for each line
1302  for (unsigned j = 0; j < map->ySteps; ++j) {
1303  // min and max height (for the current line)
1304  double minY = yConversionFactor *
1305  (map->yMin + (map->ySteps - 1 - j) * map->yStep);
1306  double maxY = yConversionFactor *
1307  (map->yMin + (map->ySteps - j) * map->yStep);
1308  stream << QString::number(minY) << QString(";");
1309  stream << QString::number(maxY) << QString(";");
1310 
1311  // for each column
1312  for (unsigned i = 0; i < map->xSteps; ++i) {
1313  // write the grid value
1314  stream << QString::number(map->at(i + j * map->xSteps).value)
1315  << QString(";");
1316  }
1317  // eol
1318  stream << QString("\n");
1319  }
1320 
1321  file.close();
1322 
1323  return true;
1324 }
1325 
1328  const ccGLMatrix& cloudToProfile,
1329  bool counterclockwise,
1330  unsigned angularSteps /*=36*/,
1331  QImage mapTexture /*=QImage()*/) {
1332  if (!profile || angularSteps < 3) {
1333  return 0;
1334  }
1335 
1336  // profile vertices
1337  cloudViewer::GenericIndexedCloudPersist* profileVertices =
1338  profile->getAssociatedCloud();
1339  unsigned profVertCount = profileVertices->size();
1340  if (profVertCount < 2) {
1341  return 0;
1342  }
1343 
1344  // profile meta-data
1345  ProfileMetaData profileDesc;
1346  if (!GetPoylineMetaData(profile, profileDesc)) {
1347  assert(false);
1348  return 0;
1349  }
1350 
1351  unsigned char Z = static_cast<unsigned char>(profileDesc.revolDim);
1352  // we deduce the 2 other ('horizontal') dimensions
1353  const unsigned char X = (Z < 2 ? Z + 1 : 0);
1354  const unsigned char Y = (X < 2 ? X + 1 : 0);
1355 
1356  unsigned meshVertCount = profVertCount * angularSteps;
1357  unsigned meshFaceCount = (profVertCount - 1) * angularSteps * 2;
1358  ccPointCloud* cloud = new ccPointCloud("vertices");
1359  ccMesh* mesh = new ccMesh(cloud);
1360  if (!cloud->reserve(meshVertCount) || !mesh->reserve(meshFaceCount)) {
1361  // not enough memory
1362  delete cloud;
1363  delete mesh;
1364  return 0;
1365  }
1366 
1367  ccGLMatrix profileToCloud = cloudToProfile.inverse();
1368 
1369  // create vertices
1370  {
1371  double cwSign = (counterclockwise ? -1.0 : 1.0);
1372  for (unsigned j = 0; j < angularSteps; ++j) {
1373  double angle_rad =
1374  static_cast<double>(j) / angularSteps * (2 * M_PI);
1375 
1376  CCVector3d N(sin(angle_rad) * cwSign, cos(angle_rad), 0);
1377 
1378  for (unsigned i = 0; i < profVertCount; ++i) {
1379  const CCVector3* P = profileVertices->getPoint(i);
1380  double radius = static_cast<double>(P->x);
1381 
1382  CCVector3 Pxyz;
1383  Pxyz.u[X] = static_cast<PointCoordinateType>(radius * N.x);
1384  Pxyz.u[Y] = static_cast<PointCoordinateType>(radius * N.y);
1385  Pxyz.u[Z] = P->y;
1386 
1387  profileToCloud.apply(Pxyz);
1388 
1389  cloud->addPoint(Pxyz);
1390  }
1391  }
1392  mesh->addChild(cloud);
1393  }
1394 
1395  PointCoordinateType h0 = profileVertices->getPoint(0)->y;
1396  PointCoordinateType dH =
1397  profileVertices->getPoint(profVertCount - 1)->y - h0;
1398  bool invertedHeight = (dH < 0);
1399 
1400  // create facets
1401  {
1402  for (unsigned j = 0; j < angularSteps; ++j) {
1403  unsigned nextJ = ((j + 1) % angularSteps);
1404  for (unsigned i = 0; i + 1 < profVertCount; ++i) {
1405  unsigned vertA = j * profVertCount + i;
1406  unsigned vertB = nextJ * profVertCount + i;
1407  unsigned vertC = vertB + 1;
1408  unsigned vertD = vertA + 1;
1409 
1410  if (invertedHeight) {
1411  mesh->addTriangle(vertB, vertC, vertD);
1412  mesh->addTriangle(vertB, vertD, vertA);
1413  } else {
1414  mesh->addTriangle(vertB, vertD, vertC);
1415  mesh->addTriangle(vertB, vertA, vertD);
1416  }
1417  }
1418  }
1419  }
1420 
1421  // do we have a texture as well?
1422  if (!mapTexture.isNull()) {
1423  // texture coordinates
1425  mesh->addChild(texCoords);
1426  if (!texCoords->reserveSafe(
1427  meshVertCount +
1428  profVertCount)) // we add a column for correct wrapping!
1429  {
1430  // not enough memory to finish the job!
1431  return mesh;
1432  }
1433 
1434  // create default texture coordinates
1435  for (unsigned j = 0; j <= angularSteps; ++j) {
1436  TexCoords2D T(static_cast<float>(j) / angularSteps, 0.0f);
1437  for (unsigned i = 0; i < profVertCount; ++i) {
1438  T.ty = (profileVertices->getPoint(i)->y - h0) / dH;
1439  if (invertedHeight) T.ty = 1.0f - T.ty;
1440  texCoords->addElement(T);
1441  }
1442  }
1443 
1444  if (!mesh->reservePerTriangleTexCoordIndexes()) {
1445  // not enough memory to finish the job!
1446  return mesh;
1447  }
1448 
1449  // set texture indexes
1450  {
1451  for (unsigned j = 0; j < angularSteps; ++j) {
1452  unsigned nextJ = ((j + 1) /*% angularSteps*/);
1453  for (unsigned i = 0; i + 1 < profVertCount; ++i) {
1454  unsigned vertA = j * profVertCount + i;
1455  unsigned vertB = nextJ * profVertCount + i;
1456  unsigned vertC = vertB + 1;
1457  unsigned vertD = vertA + 1;
1458 
1459  if (invertedHeight) {
1460  mesh->addTriangleTexCoordIndexes(vertB, vertC, vertD);
1461  mesh->addTriangleTexCoordIndexes(vertB, vertD, vertA);
1462  } else {
1463  mesh->addTriangleTexCoordIndexes(vertB, vertD, vertC);
1464  mesh->addTriangleTexCoordIndexes(vertB, vertA, vertD);
1465  }
1466  }
1467  }
1468  }
1469 
1470  // set material indexes
1471  if (!mesh->reservePerTriangleMtlIndexes()) {
1472  // not enough memory to finish the job!
1473  mesh->removeChild(texCoords);
1475  return mesh;
1476  }
1477  for (unsigned i = 0; i < meshFaceCount; ++i) {
1478  mesh->addTriangleMtlIndex(0);
1479  }
1480 
1481  // set material
1482 #if 0
1483  {
1484  ccMaterial::Shared material(new ccMaterial("texture"));
1485  material->setTexture(mapTexture, QString(), false);
1486 
1487  ccMaterialSet* materialSet = new ccMaterialSet();
1488  materialSet->addMaterial(material);
1489 
1490  mesh->setMaterialSet(materialSet);
1491  }
1492 #endif
1493 
1494  mesh->setTexCoordinatesTable(texCoords);
1495  mesh->showMaterials(true);
1496  mesh->setVisible(true);
1497  cloud->setVisible(false);
1498  }
1499 
1500  return mesh;
1501 }
1502 
1504  const QSharedPointer<Map>& map,
1506  double baseRadius /*=1.0*/,
1507  bool keepNaNPoints /*=true*/) {
1508  if (!map || !profile) return 0;
1509 
1510  unsigned count = map->ySteps * map->xSteps;
1511 
1512  ccPointCloud* cloud = new ccPointCloud("map");
1513  ccScalarField* sf = new ccScalarField("values");
1514  if (!cloud->reserve(count) || !sf->reserveSafe(count)) {
1515  // not enough memory
1516  delete cloud;
1517  sf->release();
1518  return nullptr;
1519  }
1520 
1521  // number of vertices
1523  profile->getAssociatedCloud();
1524  unsigned polyVertCount = polyVertices->size();
1525  if (polyVertCount < 2) {
1526  delete cloud;
1527  sf->release();
1528  return nullptr;
1529  }
1530 
1531  // profile meta-data
1532  ProfileMetaData profileDesc;
1533  if (!GetPoylineMetaData(profile, profileDesc)) {
1534  delete cloud;
1535  sf->release();
1536  return nullptr;
1537  }
1538 
1539  unsigned char Z = static_cast<unsigned char>(profileDesc.revolDim);
1540  // we deduce the 2 other ('horizontal') dimensions
1541  const unsigned char X = (Z < 2 ? Z + 1 : 0);
1542  const unsigned char Y = (X < 2 ? X + 1 : 0);
1543 
1544  const double xStep =
1545  baseRadius * (2 * M_PI) / static_cast<double>(map->xSteps);
1546 
1547  const MapCell* cell = &map->at(0);
1548  for (unsigned j = 0; j < map->ySteps; ++j) {
1549  CCVector3 P(0, 0, 0);
1550  P.u[Z] = static_cast<PointCoordinateType>(map->yMin +
1551  (j + 0.5) * map->yStep);
1552 
1553  // for each column
1554  for (unsigned i = 0; i < map->xSteps; ++i, ++cell) {
1555  if (keepNaNPoints || cell->count != 0) {
1556  P.u[X] = static_cast<PointCoordinateType>(map->xMin +
1557  (i + 0.5) * xStep);
1558 
1559  // search nearest "segment" in polyline
1560  for (unsigned k = 1; k < polyVertCount; ++k) {
1561  const CCVector3* A = polyVertices->getPoint(k - 1);
1562  const CCVector3* B = polyVertices->getPoint(k);
1563 
1564  double alpha = (P.u[Z] - profileDesc.heightShift - A->y) /
1565  (B->y - A->y);
1566  if (alpha >= 0.0 && alpha <= 1.0) {
1567  // we deduce the right radius by linear interpolation
1568  double radius_th = A->x + alpha * (B->x - A->x);
1569  // TODO: we take the first radius (even if there are
1570  // other segments at this particular height, because we
1571  // can't guess which one is the 'right' one!
1572  P.u[Y] = static_cast<PointCoordinateType>(radius_th);
1573  break;
1574  }
1575  }
1576 
1577  cloud->addPoint(profileDesc.origin + P);
1578 
1579  ScalarType val = cell->count
1580  ? static_cast<ScalarType>(cell->value)
1581  : NAN_VALUE;
1582  sf->addElement(val);
1583  }
1584  }
1585  }
1586 
1587  sf->computeMinAndMax();
1588  int sfIdx = cloud->addScalarField(sf);
1589  cloud->setCurrentDisplayedScalarField(sfIdx);
1590  cloud->showSF(true);
1591  cloud->resize(cloud->size()); // if we have skipped NaN values!
1592 
1593  return cloud;
1594 }
1595 
1597  const QSharedPointer<Map>& map,
1598  ccColorScale::Shared colorScale,
1599  unsigned colorScaleSteps /*=ccColorScale::MAX_STEPS*/) {
1600  if (!map || !colorScale) return QImage();
1601 
1602  // create image
1603  QImage image(QSize(map->xSteps, map->ySteps), QImage::Format_ARGB32);
1604  if (image.isNull()) {
1605  // not enough memory!
1606  return QImage();
1607  }
1608 
1609  // convert map cells to pixels
1610  {
1611  bool csIsRelative = colorScale->isRelative();
1612 
1613  const MapCell* cell = &map->at(0);
1614  for (unsigned j = 0; j < map->ySteps; ++j) {
1615  // for each column
1616  for (unsigned i = 0; i < map->xSteps; ++i, ++cell) {
1617  const ecvColor::Rgb* rgb = &ecvColor::lightGrey;
1618 
1619  if (cell->count != 0) {
1620  double relativePos =
1621  csIsRelative ? (cell->value - map->minVal) /
1622  (map->maxVal - map->minVal)
1623  : colorScale->getRelativePosition(
1624  cell->value);
1625  if (relativePos < 0.0)
1626  relativePos = 0.0;
1627  else if (relativePos > 1.0)
1628  relativePos = 1.0;
1629  rgb = colorScale->getColorByRelativePos(
1630  relativePos, colorScaleSteps, &ecvColor::lightGrey);
1631  }
1632 
1633  // DGM FIXME: QImage::sePixel is quite slow!
1634  image.setPixel(static_cast<int>(i), static_cast<int>(j),
1635  qRgb(rgb->r, rgb->g, rgb->b));
1636  }
1637  }
1638  }
1639 
1640  return image;
1641 }
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
constexpr double M_PI
Pi.
Definition: CVConst.h:19
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
Vector2Tpl< PointCoordinateType > CCVector2
Default 2D Vector.
Definition: CVGeom.h:780
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
std::string filename
std::shared_ptr< core::Tensor > image
int height
int count
CloudViewerScene::LightingProfile profile
void * X
Definition: SmallVector.cpp:45
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
EmptyCellFillOption
Option for handling empty cells.
static bool SaveMapAsCSVMatrix(const QSharedPointer< Map > &map, QString filename, QString xUnit, QString yUnit, double xConversionFactor=1.0, double yConversionFactor=1.0, ecvMainAppInterface *app=0)
Saves a map as a CSV matrix.
static void SetPoylineAxis(ccPolyline *polyline, const CCVector3 &axis)
Sets the revolution axis of a given polyline.
static ccPointCloud * ConvertMapToCloud(const QSharedPointer< Map > &map, ccPolyline *profile, double baseRadius=1.0, bool keepNaNPoints=true)
Converts map to a point cloud.
static bool GetPoylineOrigin(const ccPolyline *polyline, CCVector3 &origin)
Returns the origin associated to a given polyline/profile.
static void SetPoylineOrigin(ccPolyline *polyline, const CCVector3 &origin)
Sets the origin of a given polyline/profile.
static bool ComputeRadialDist(ccPointCloud *cloud, ccPolyline *profile, bool storeRadiiAsSF=false, ecvMainAppInterface *app=0)
Computes radial distance between cloud and a profile.
static ccMesh * ConvertProfileToMesh(ccPolyline *profile, const ccGLMatrix &cloudToProfile, bool counterclockwise, unsigned angularSteps=36, QImage mapTexture=QImage())
Converts profile to a (textured) mesh.
static QImage ConvertMapToImage(const QSharedPointer< Map > &map, ccColorScale::Shared colorScale, unsigned colorScaleSteps=ccColorScale::MAX_STEPS)
Converts map to a QImage.
static double ConicalProject(double phi, double phi1, double n)
static void SetPoylineRevolDim(ccPolyline *polyline, int revolDim)
Sets the revolution dimension of a given polyline.
static bool ComputeMinAndMaxLatitude_rad(ccPointCloud *cloud, double &minLat_rad, double &maxLat_rad, const ccGLMatrix &cloudToSurfaceOrigin, unsigned char revolutionAxisDim)
static int GetPoylineRevolDim(const ccPolyline *polyline)
static bool GetPoylineAxis(const ccPolyline *polyline, CCVector3 &axis)
Returns the revolution axis associated to a given profile (polyline)
static bool ConvertCloudToConical(ccPointCloud *cloud, const ccGLMatrix &cloudToSurface, unsigned char revolutionAxisDim, double latMin_rad, double latMax_rad, double conicalSpanRatio=1.0, bool counterclockwise=false)
Converts a point cloud coordinates to "conical" ones (in place)
static double ConicalProjectN(double phi1, double phi2)
static void SetPolylineHeightShift(ccPolyline *polyline, PointCoordinateType heightShift)
Sets the profile 'height shift' (i.e. along the revolution axis)
static bool GetPolylineHeightShift(const ccPolyline *polyline, PointCoordinateType &heightShift)
Returns the profile 'height shift' (i.e. along the revolution axis)
static bool ComputeSurfacesAndVolumes(const QSharedPointer< Map > &map, ccPolyline *profile, Measures &surfaces, Measures &volumes)
FillStrategyType
Grid filling strategy.
static bool ConvertCloudToCylindrical(ccPointCloud *cloud, const ccGLMatrix &cloudToSurface, unsigned char revolutionAxisDim, bool counterclockwise=false)
Converts a point cloud coordinates to "cylindrical" ones (in place)
static QSharedPointer< Map > CreateMap(ccPointCloud *cloud, ccScalarField *sf, const ccGLMatrix &cloudToSurface, unsigned char revolutionAxisDim, double angStep_rad, double yStep, double yMin, double yMax, bool conical, bool counterclockwise, FillStrategyType fillStrategy, EmptyCellFillOption emptyCellfillOption, ecvMainAppInterface *app=0)
static bool GetPoylineMetaData(const ccPolyline *polyline, ProfileMetaData &data)
static CCVector3 ProjectPointOnCone(double lon_rad, double lat_rad, double latMin_rad, double nProj, bool counterclockwise)
Projects a (longitude,r) couple to a 2D map.
static ccMesh * ConvertConicalMapToMesh(const QSharedPointer< Map > &map, bool counterclockwise, QImage mapTexture=QImage())
Creates a conical projection (textured) mesh.
Array of 2D texture coordinates.
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:36
Type y
Definition: CVGeom.h:36
bool reserveSafe(size_t count)
Reserves memory (no exception thrown)
Definition: ecvArray.h:56
void addElement(const Type &value)
Definition: ecvArray.h:105
QSharedPointer< ccColorScale > Shared
Shared pointer type.
Definition: ecvColorScale.h:74
virtual void setVisible(bool state)
Sets entity visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
static ccGLMatrixTpl< float > FromToRotation(const Vector3Tpl< float > &from, const Vector3Tpl< float > &to)
Creates a transformation matrix that rotates a vector to another.
T * getTranslation()
Retruns a pointer to internal translation.
ccGLMatrixTpl< T > inverse() const
Returns inverse transformation.
void apply(float vec[3]) const
Applies transformation to a 3D vector (in place) - float version.
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
virtual void showMaterials(bool state)
Sets whether textures should be displayed or not.
virtual void deleteOctree()
Erases the octree.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
void removeChild(ccHObject *child)
Mesh (triangle) material.
int addMaterial(ccMaterial::CShared mat, bool allowDuplicateNames=false)
Adds a material.
Mesh (triangle) material.
Definition: ecvMaterial.h:28
QSharedPointer< ccMaterial > Shared
Shared type.
Definition: ecvMaterial.h:33
Triangular mesh.
Definition: ecvMesh.h:35
bool reservePerTriangleMtlIndexes()
Reserves memory to store per-triangle material index.
void addTriangleMtlIndex(int mtlIndex)
Adds triangle material index for next triangle.
void setMaterialSet(ccMaterialSet *materialSet, bool autoReleaseOldMaterialSet=true)
Sets associated material set (may be shared)
bool reserve(std::size_t n)
Reserves the memory to store the vertex indexes (3 per triangle)
void addTriangleTexCoordIndexes(int i1, int i2, int i3)
Adds a triplet of tex coords indexes for next triangle.
void addTriangle(unsigned i1, unsigned i2, unsigned i3)
Adds a triangle to the mesh.
void removePerTriangleTexCoordIndexes()
Remove per-triangle tex coords indexes.
void setTexCoordinatesTable(TextureCoordsContainer *texCoordsTable, bool autoReleaseOldTable=true)
Sets per-triangle texture coordinates array (may be shared)
bool reservePerTriangleTexCoordIndexes()
Reserves memory to store per-triangle triplets of tex coords indexes.
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
void refreshBB() override
Forces bounding-box update.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
Colored polyline.
Definition: ecvPolyline.h:24
A scalar field associated to display-related parameters.
void computeMinAndMax() override
Determines the min and max values.
A class to compute and handle a Delaunay 2D mesh on a subset of points.
virtual unsigned size() const override
Returns the number of triangles.
void placeIteratorAtBeginning() override
Places the mesh iterator at the beginning.
virtual bool buildMesh(const std::vector< CCVector2 > &points2D, std::size_t pointCountToUse, std::string &outputErrorStr)
Build the Delaunay mesh on top a set of 2D points.
VerticesIndexes * getNextTriangleVertIndexes() override
static constexpr int USE_ALL_POINTS
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based and presistent access to points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
bool oneStep()
Increments total progress value of a single unit.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
const CCVector3 * getPoint(unsigned index) const override
void addElement(ScalarType value)
Definition: ScalarField.h:99
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
bool reserveSafe(std::size_t count)
Reserves memory (no exception thrown)
Definition: ScalarField.cpp:71
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
Definition: ScalarField.h:61
RGB color structure.
Definition: ecvColorTypes.h:49
Main application interface (for plugins)
virtual QMainWindow * getMainWindow()=0
Returns main window.
virtual void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE)=0
Graphical progress indicator (thread-safe)
virtual void start() override
virtual void setInfo(const char *infoStr) override
Notifies some information about the ongoing process.
virtual void setMethodTitle(const char *methodTitle) override
Notifies the algorithm title.
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
const char REVOLUTION_AXIS_KEY[]
static const double M_PI_DIV_2
static double ComputeLatitude_rad(PointCoordinateType x, PointCoordinateType y, PointCoordinateType z)
const char PROFILE_HEIGHT_SHIFT_KEY[]
static const double M_PI_DIV_4
static void SetPoylineMetaVector(ccPolyline *polyline, const QString &key, const CCVector3 &P)
const char PROFILE_ORIGIN_KEY[]
static bool GetPolylineMetaVector(const ccPolyline *polyline, const QString &key, CCVector3 &P)
const char RADIAL_DIST_SF_NAME[]
const char RADII_SF_NAME[]
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
constexpr Rgb lightGrey(static_cast< ColorCompType >(MAX *0.8), static_cast< ColorCompType >(MAX *0.8), static_cast< ColorCompType >(MAX *0.8))
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370
cloudViewer::NormalizedProgress * nProgress
unsigned count
Number of values projected in this cell (for average computation)
int revolDim
revolution axis (X=0, Y=1, Z=2)
CCVector3 origin
origin of the surface of revolution
2D texture coordinates
Triangle described by the indexes of its 3 vertices.