ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ccTrace.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 "ccTrace.h"
9 
10 #include <ecvDisplayTools.h>
11 
12 #include <bitset>
13 #include <queue>
14 
15 ccTrace::ccTrace(ccPointCloud* associatedCloud) : ccPolyline(associatedCloud) {
16  init(associatedCloud);
17 }
18 
19 ccTrace::ccTrace(ccPolyline* obj) : ccPolyline(obj->getAssociatedCloud()) {
20  ccPointCloud* cld = dynamic_cast<ccPointCloud*>(obj->getAssociatedCloud());
21  assert(cld != nullptr); // should never be null
22  init(cld);
23 
24  // load waypoints from metadata
25  if (obj->hasMetaData("waypoints")) {
26  QString waypoints = obj->getMetaData("waypoints").toString();
27  for (QString str : waypoints.split(",")) {
28  if (str != "") {
29  int pID = str.toInt();
30  m_waypoints.push_back(pID); // add waypoint
31  }
32  }
33 
34  // store waypoints metadata
35  QVariantMap* map = new QVariantMap();
36  map->insert("waypoints", waypoints);
37  setMetaData(*map, true);
38  }
39 
40  // load cost function from metadata
41  if (obj->hasMetaData("cost_function")) {
42  ccTrace::COST_MODE = obj->getMetaData("cost_function").toInt();
43  }
44 
45  setName(obj->getName());
46 
47  // copy polyline into trace points
48  std::deque<int> seg;
49  for (unsigned i = 0; i < obj->size(); i++) {
50  // copy into "trace" object
51  int pId = obj->getPointGlobalIndex(i); // get global point ID
52  seg.push_back(pId);
53 
54  // also copy into polyline object
55  addPointIndex(pId);
56  }
57  m_trace.push_back(seg);
58 
59  // recalculate trace if polyline data somehow lost [redundancy thing..]
60  if (obj->size() == 0) {
61  m_trace.clear();
62  optimizePath(); //[slooooow...!]
63  }
64 
65  // load SNE data from metadata (TODO)
66 
67  invalidateBoundingBox(); // update bounding box (for picking)
68 }
69 
70 void ccTrace::init(ccPointCloud* associatedCloud) {
71  setAssociatedCloud(associatedCloud); // the ccPolyline c'tor should do
72  // this, but just to be sure...
73  m_cloud = associatedCloud; // store pointer ourselves also
74  m_search_r = calculateOptimumSearchRadius(); // estimate the search radius
75  // we want to use
76 
77  // store these info as object attributes
78  updateMetadata();
79 }
80 
81 void ccTrace::updateMetadata() {
82  QVariantMap* map = new QVariantMap();
83  map->insert("ccCompassType", "Trace");
84  map->insert("search_r", m_search_r);
85  map->insert("cost_function", ccTrace::COST_MODE);
86 
87  // TODO - write metadata for structure normal estimates
88 
89  setMetaData(*map, true);
90 }
91 
92 int ccTrace::insertWaypoint(int pointId) {
93  if (m_waypoints.size() >= 2) {
94  // get location of point to add
95  // const CCVector3* Q = m_cloud->getPoint(pointId);
96  CCVector3 Q = *m_cloud->getPoint(pointId);
97  CCVector3 start, end;
98  // check if point is "inside" any segments
99  for (int i = 1; i < m_waypoints.size(); i++) {
100  // get start and end points
101  m_cloud->getPoint(m_waypoints[i - 1], start);
102  m_cloud->getPoint(m_waypoints[i], end);
103 
104  // are we are "inside" this segment
105  if (inCircle(&start, &end, &Q)) {
106  // insert waypoint
107  m_waypoints.insert(m_waypoints.begin() + i, pointId);
108  m_previous.push_back(i);
109  return i;
110  }
111  }
112 
113  // check if the point is closer to the start than the end -> i.e. the
114  // point should be 'pre-pended'
115  CCVector3 sp = Q - start;
116  CCVector3 ep = Q - end;
117  if (sp.norm2() < ep.norm2()) {
118  m_waypoints.insert(m_waypoints.begin(), pointId);
119  m_previous.push_back(0);
120  return 0;
121  }
122  }
123 
124  // add point to end of the trace
125  m_waypoints.push_back(pointId);
126  int id = static_cast<int>(m_waypoints.size()) - 1;
127  m_previous.push_back(id); // store for undo options
128  return id;
129 }
130 
131 // test if the query point is within a circle bound by segStart & segEnd
132 bool ccTrace::inCircle(const CCVector3* segStart,
133  const CCVector3* segEnd,
134  const CCVector3* query) {
135  // calculate vector Query->Start and Query->End
136  CCVector3 QS(segStart->x - query->x, segStart->y - query->y,
137  segStart->z - query->z);
138  CCVector3 QE(segEnd->x - query->x, segEnd->y - query->y,
139  segEnd->z - query->z);
140 
141  // is angle between these vectors obtuce (i.e. QS dot QE) < 0)? If so we are
142  // inside a circle between start&end, otherwise we are not
143  QS.normalize();
144  QE.normalize();
145 
146  return QS.dot(QE) < 0;
147 }
148 
149 bool ccTrace::optimizePath(int maxIterations) {
150  bool success = true;
151 
152  if (m_waypoints.size() < 2) {
153  m_trace.clear();
154  return false; // no segments...
155  }
156 
157 #ifdef DEBUG_PATH
159  "Search"); // look for scalar field to write search to
160  if (idx == -1) // doesn't exist - create
161  idx = m_cloud->addScalarField("Search");
163 #endif
164 
165  // update stored cost function etc.
166  updateMetadata();
167 
168  // update internal vars
169  m_maxIterations = maxIterations;
170 
171  // loop through segments and build/rebuild trace
172  int start, end, tID; // declare vars
173  for (unsigned i = 1; i < m_waypoints.size(); i++) {
174  // calculate indices
175  start = m_waypoints[i - 1]; // global point id for the start waypoint
176  end = m_waypoints[i]; // global point id for the end waypoint
177  tID = i - 1; // id of the trace segment id (in m_trace vector)
178 
179  // are we adding to the end of the trace?
180  if (tID >= m_trace.size()) {
181  std::deque<int> segment = optimizeSegment(
182  start, end, m_search_r); // calculate segment
183  m_trace.push_back(segment); // store segment
184  success = success &&
185  !segment.empty(); // if the queue is empty, we failed
186  } else // no... we're somewhere in the middle - update segment if
187  // necessary
188  {
189  if (!m_trace[tID].empty() && (m_trace[tID][0] == start) &&
190  (m_trace[tID][m_trace[tID].size() - 1] ==
191  end)) // valid trace and start/end match
192  continue; // this trace has already been calculated - we can
193  // skip! :)
194  else {
195  // calculate segment
196  std::deque<int> segment = optimizeSegment(
197  start, end, m_search_r); // calculate segment
198  success = success &&
199  !segment.empty(); // if the queue is empty, we failed
200 
201  // add trace
202  if (m_trace[tID][m_trace[tID].size() - 1] ==
203  end) // end matches - we can replace the current trace &
204  // things should be sweet (all prior traces will have
205  // been updated already)
206  m_trace[tID] = segment; // end is correct - overwrite this
207  // block, then hopefully we will
208  // match in the next one
209  else // end doesn't match - we need to insert
210  m_trace.insert(m_trace.begin() + tID, segment);
211  }
212  }
213  }
214 
215 #ifdef DEBUG_PATH
217  f->computeMinAndMax();
218 #endif
219 
220  // write control points to property (for reloading)
221  QVariantMap* map = new QVariantMap();
222  QString waypoints = "";
223 
224  for (unsigned i = 0; i < m_waypoints.size(); i++) {
225  waypoints += QString::number(m_waypoints[i]) + ",";
226  }
227 
228  map->insert("waypoints", waypoints);
229  setMetaData(*map, true);
230 
231  // push points onto underlying polyline object (for picking & save/load)
232  finalizePath();
233 
234  return success;
235 }
236 
238  // clear existing points in background "polyline"
239  clear();
240 
241  // push trace buffer to said polyline (for save/export etc.)
242  for (std::deque<int> seg : m_trace) {
243  for (int p : seg) {
244  addPointIndex(p);
245  }
246  }
247 
248  // invalidate bb
250 }
251 
253  m_trace.clear();
254  optimizePath();
255 }
256 
257 int ccTrace::COST_MODE = ccTrace::MODE::DARK; // set default cost mode
258 std::deque<int> ccTrace::optimizeSegment(int start, int end, int offset) {
259  // check handle to point cloud
260  if (!m_cloud) {
261  return std::deque<int>(); // error -> no cloud
262  }
263 
264  // retrieve and store start & end rgb
265  if (m_cloud->hasColors()) {
266  const ecvColor::Rgb& s = m_cloud->getPointColor(start);
267  const ecvColor::Rgb& e = m_cloud->getPointColor(end);
268  m_start_rgb[0] = s.r;
269  m_start_rgb[1] = s.g;
270  m_start_rgb[2] = s.b;
271  m_end_rgb[0] = e.r;
272  m_end_rgb[1] = e.g;
273  m_end_rgb[2] = e.b;
274  } else { // no colour... set to 0 just in case something tries to use these
275  // vars
276  m_start_rgb[0] = 0;
277  m_start_rgb[1] = 0;
278  m_start_rgb[2] = 0;
279  m_end_rgb[0] = 0;
280  m_end_rgb[1] = 0;
281  m_end_rgb[2] = 0;
282  }
283 
284  // get location of target node - used to optimise algorithm to stop
285  // searching paths leading away from the target
286  const CCVector3* end_v = m_cloud->getPoint(end);
287 
288  // code essentially taken from wikipedia page for Djikstra:
289  // https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
290  std::vector<bool>
291  visited; // an array of bits to check if node has been visited
292  std::priority_queue<Node*, std::vector<Node*>, Compare>
293  openQueue; // priority queue that stores nodes that haven't yet
294  // been explored/opened
295  std::vector<Node*> nodes; // list of visited nodes. Used to cleanup memory
296  // after re-constructing shortest path.
297 
298  // set size of visited such that there is one bit per point in the cloud
299  visited.resize(m_cloud->size(), false); // n.b. for 400 million points,
300  // this will still only be ~50Mb =)
301 
302  // declare variables used in the loop
303  Node* current = nullptr;
304  int current_idx = 0;
305  int cost = 0;
306  int smallest_cost = 999999;
307  int iter_count = 0;
308  float cur_d2, next_d2;
309 
310  // setup buffer for storing nodes
311  int bufferSize = 500000; // 500k node buffer (~1.5-2Mb). This should be
312  // enough for most small-medium size traces. More
313  // buffers will be created for bigger ones.
314  Node* node_buffer = new Node[bufferSize];
315  nodes.push_back(node_buffer); // store buffer in nodes list (for cleanup)
316  int nodeCount = 1; // start node will be added shortly
317 
318  // setup octree & values for nearest neighbour searches
320  if (!oct) {
321  oct = m_cloud->computeOctree(); // if the user clicked "no" when asked
322  // to compute the octree then tough....
323  }
324  unsigned char level =
325  oct->findBestLevelForAGivenNeighbourhoodSizeExtraction(m_search_r);
326 
327  // initialize start node on node_buffer and add to openQueue
328  node_buffer[0].set(start, 0, nullptr);
329  openQueue.push(&node_buffer[0]);
330 
331  // mark start node as visited
332  visited[start] = true;
333 
334  while (openQueue.size() > 0) // while unvisited nodes exist
335  {
336  // check if we excede max iterations
337  if (iter_count > m_maxIterations) {
338  // cleanup buffers
339  for (Node* n : nodes) {
340  delete[] n;
341  }
342 
343  // bail
344  return std::deque<int>(); // bail
345  }
346 
347  iter_count++;
348 
349  // get lowest cost node for expansion
350  current = openQueue.top();
351  current_idx = current->index;
352 
353  // remove node from open set
354  openQueue.pop(); // remove node from open set (queue)
355 
356  if (current_idx == end) // we've found it!
357  {
358  std::deque<int> path;
359  path.push_back(end); // add end node
360 
361  // traverse backwards to reconstruct path
362  while (current->index != start) {
363  current = current->previous;
364  path.push_front(current->index);
365  }
366 
367  path.push_front(start);
368 
369  // cleanup node buffers
370  for (Node* n : nodes) {
371  delete[] n;
372  }
373 
374  // return
375  return path;
376  }
377 
378  // calculate distance from current nodes parent to end -> avoid going
379  // backwards (in euclidean space) [essentially stops fracture turning >
380  // 90 degrees)
381  const CCVector3* cur = m_cloud->getPoint(current_idx);
382  cur_d2 = (cur->x - end_v->x) * (cur->x - end_v->x) +
383  (cur->y - end_v->y) * (cur->y - end_v->y) +
384  (cur->z - end_v->z) * (cur->z - end_v->z);
385 
386  // fill "neighbours" with nodes - essentially get results of a "sphere"
387  // search around active current point
388  m_neighbours.clear();
389 
390  oct->getPointsInSphericalNeighbourhood(
391  *cur, PointCoordinateType(m_search_r), m_neighbours, level);
392 
393  // loop through neighbours
394  for (size_t i = 0; i < m_neighbours.size(); i++) {
395  m_p = m_neighbours[i];
396 
397  if (visited[m_p.pointIndex]) // Has this node been visited before?
398  // If so then bail.
399  continue;
400 
401  // calculate (squared) distance from this neighbour to the end
402  next_d2 = (m_p.point->x - end_v->x) * (m_p.point->x - end_v->x) +
403  (m_p.point->y - end_v->y) * (m_p.point->y - end_v->y) +
404  (m_p.point->z - end_v->z) * (m_p.point->z - end_v->z);
405 
406  if (next_d2 >=
407  cur_d2) // Bigger than the original distance? If so then bail.
408  continue;
409 
410  // calculate cost to this neighbour
411  cost = getSegmentCost(current_idx, m_p.pointIndex);
412 
413 #ifdef DEBUG_PATH
415  m_p.pointIndex,
416  static_cast<ScalarType>(
417  cost)); // STORE VISITED NODES (AND COST) FOR DEBUG
418  // VISUALISATIONS
419 #endif
420 
421  // transform into cost from start node
422  cost += current->total_cost;
423 
424  // check that the node buffer isn't full
425  if (nodeCount == bufferSize) {
426  // buffer is full - create a new one
427  node_buffer = new Node[bufferSize];
428  nodes.push_back(node_buffer);
429  nodeCount = 0;
430  }
431 
432  // initialize node on node buffer
433  node_buffer[nodeCount].set(m_p.pointIndex, cost, current);
434 
435  // push node to open set
436  openQueue.push(&node_buffer[nodeCount]);
437 
438  // we now have one more node
439  nodeCount++;
440 
441  // mark node as visited
442  visited[m_p.pointIndex] = true;
443  }
444  }
445 
446  assert(false);
447  return std::deque<int>(); // shouldn't come here?
448 }
449 
450 int ccTrace::getSegmentCost(int p1, int p2) {
451  int cost = 1; // n.b. default value is 1 so that if no cost functions are
452  // used, the function doesn't crash (and returns the
453  // unweighted shortest path)
454  if (m_cloud->hasColors()) // check cloud has colour data
455  {
456  if (COST_MODE & MODE::RGB) cost += getSegmentCostRGB(p1, p2);
457  if (COST_MODE & MODE::DARK) cost += getSegmentCostDark(p1, p2);
458  if (COST_MODE & MODE::LIGHT) cost += getSegmentCostLight(p1, p2);
459  if (COST_MODE & MODE::GRADIENT)
460  cost += getSegmentCostGrad(p1, p2, m_search_r);
461  }
462  if (m_cloud->hasDisplayedScalarField()) // check cloud has scalar field
463  // data
464  {
465  if (COST_MODE & MODE::SCALAR) cost += getSegmentCostScalar(p1, p2);
466  if (COST_MODE & MODE::INV_SCALAR)
467  cost += getSegmentCostScalarInv(p1, p2);
468  }
469 
470  // these cost functions can be used regardless
471  if (COST_MODE & MODE::CURVE) cost += getSegmentCostCurve(p1, p2);
472  if (COST_MODE & MODE::DISTANCE) cost += getSegmentCostDist(p1, p2);
473 
474  return cost;
475 }
476 
477 int ccTrace::getSegmentCostRGB(int p1, int p2) {
478  // get colors
479  const ecvColor::Rgb& p1_rgb = m_cloud->getPointColor(p1);
480  const ecvColor::Rgb& p2_rgb = m_cloud->getPointColor(p2);
481 
482  // cost function: cost = |c1-c2| + 0.25 ( |c1-start| + |c1-end| + |c2-start|
483  // + |c2-end| ) IDEA: can we somehow optimize all the square roots here (for
484  // speed)?
485  return sqrt(
486  //|c1-c2|
487  (p1_rgb.r - p2_rgb.r) * (p1_rgb.r - p2_rgb.r) +
488  (p1_rgb.g - p2_rgb.g) * (p1_rgb.g - p2_rgb.g) +
489  (p1_rgb.b - p2_rgb.b) * (p1_rgb.b - p2_rgb.b)) +
490  0.25 *
491  (
492  //|c1-start|
493  sqrt((p1_rgb.r - m_start_rgb[0]) *
494  (p1_rgb.r - m_start_rgb[0]) +
495  (p1_rgb.g - m_start_rgb[1]) *
496  (p1_rgb.g - m_start_rgb[1]) +
497  (p1_rgb.b - m_start_rgb[2]) *
498  (p1_rgb.b - m_start_rgb[2])) +
499  //|c1-end|
500  sqrt((p1_rgb.r - m_end_rgb[0]) *
501  (p1_rgb.r - m_end_rgb[0]) +
502  (p1_rgb.g - m_end_rgb[1]) *
503  (p1_rgb.g - m_end_rgb[1]) +
504  (p1_rgb.b - m_end_rgb[2]) *
505  (p1_rgb.b - m_end_rgb[2])) +
506  //|c2-start|
507  sqrt((p2_rgb.r - m_start_rgb[0]) *
508  (p2_rgb.r - m_start_rgb[0]) +
509  (p2_rgb.g - m_start_rgb[1]) *
510  (p2_rgb.g - m_start_rgb[1]) +
511  (p2_rgb.b - m_start_rgb[2]) *
512  (p2_rgb.b - m_start_rgb[2])) +
513  //|c2-end|
514  sqrt((p2_rgb.r - m_end_rgb[0]) *
515  (p2_rgb.r - m_end_rgb[0]) +
516  (p2_rgb.g - m_end_rgb[1]) *
517  (p2_rgb.g - m_end_rgb[1]) +
518  (p2_rgb.b - m_end_rgb[2]) *
519  (p2_rgb.b - m_end_rgb[2]))) /
520  3.5; // N.B. the divide by 3.5 scales this cost function to
521  // range between 0 & 255
522 }
523 
524 int ccTrace::getSegmentCostDark(int p1, int p2) {
525  // return magnitude of the point p2
526  // const ColorCompType* p1_rgb = m_cloud->getPointColor(p1);
527  const ecvColor::Rgb& p2_rgb = m_cloud->getPointColor(p2);
528 
529  // return "taxi-cab" length
530  return (p2_rgb.r + p2_rgb.g +
531  p2_rgb.b); // note: this will naturally give a maximum of 765 (=255
532  // + 255 + 255)
533 }
534 
535 int ccTrace::getSegmentCostLight(int p1, int p2) {
536  // return the opposite of getCostDark
537  return 765 - getSegmentCostDark(p1, p2);
538 }
539 
540 int ccTrace::getSegmentCostCurve(int p1, int p2) {
542  "Curvature"); // look for pre-existing gradient SF
543  if (isCurvaturePrecomputed()) // scalar field found - return from
544  // precomputed cost]
545  {
546  // activate SF
548 
549  // return inverse of p2 value
550  return m_cloud->getScalarField(idx)->getMax() -
552  } else // scalar field not found - do slow calculation...
553  {
554  // put neighbourhood in a cloudViewer::Neighbourhood structure
555  if (m_neighbours.size() >
556  4) // need at least 4 points to calculate curvature....
557  {
558  m_neighbours.push_back(
559  m_p); // add center point onto end of neighbourhood
560 
561  // compute curvature
563  &m_neighbours, static_cast<unsigned>(m_neighbours.size()));
564  cloudViewer::Neighbourhood Z(&nCloud);
565  float c = Z.computeCurvature(
566  *nCloud.getPoint(0),
567  cloudViewer::Neighbourhood::CurvatureType::MEAN_CURV);
568 
569  m_neighbours.erase(m_neighbours.end() -
570  1); // remove center point from neighbourhood
571  // (so as not to screw up loops)
572 
573  // curvature tends to range between 0 (high cost) and 10 (low cost),
574  // though it can be greater than 10 in extreme cases hence we need
575  // to map to domain 0 - 10 and then transform that to the (integer)
576  // domain 0 - 884 to meet the cost function spec
577  if (c > 10) c = 10;
578 
579  // scale curvature to range 0, 765
580  c *= 76.5;
581 
582  // note that high curvature = low cost, hence subtract curvature
583  // from 765
584  return 765 - c;
585  }
586  return 765; // unknown curvature - this point is high cost.
587  }
588 }
589 
590 int ccTrace::getSegmentCostGrad(int p1, int p2, float search_r) {
592  "Gradient"); // look for pre-existing gradient SF
593  if (idx != -1) // found precomputed gradient
594  {
595  // activate SF
597 
598  // return inverse of p2 value
599  return m_cloud->getScalarField(idx)->getMax() -
601  } else // not found... do expensive calculation
602  {
603  CCVector3 p = *m_cloud->getPoint(p2);
604  const ecvColor::Rgb p2_rgb = m_cloud->getPointColor(p2);
605  int p_value = p2_rgb.r + p2_rgb.g + p2_rgb.b;
606 
607  if (m_neighbours.size() >
608  2) // need at least 2 points to calculate gradient....
609  {
610  // N.B. The following code is mostly stolen from the computeGradient
611  // function in CLOUDVIEWER
612  CCVector3d sum(0, 0, 0);
614  for (int i = 0; i < m_neighbours.size(); i++) {
615  n = m_neighbours[i];
616 
617  // vector from p1 to m_p
618  CCVector3 deltaPos = *n.point - p;
619  double norm2 = deltaPos.norm2d();
620 
621  // colour
623  int c_value = (static_cast<int>(c.r) + c.g) + c.b;
624 
625  // calculate gradient weighted by distance to the point (i.e.
626  // divide by distance^2)
627  if (cloudViewer::GreaterThanEpsilon(norm2)) {
628  // color magnitude difference
629  int deltaValue = p_value - c_value;
630  // divide by norm^2 to get distance-weighted gradient
631  deltaValue /=
632  norm2; // we divide by 'norm' to get the normalized
633  // direction, and by 'norm' again to get the
634  // gradient (hence we use the squared norm)
635  // sum stuff
636  sum.x += deltaPos.x *
637  deltaValue; // warning: here 'deltaValue'=
638  // deltaValue / squaredNorm(deltaPos)
639  // ;)
640  sum.y += deltaPos.y * deltaValue;
641  sum.z += deltaPos.z * deltaValue;
642  }
643  }
644 
645  float gradient = sum.norm() / m_neighbours.size();
646 
647  // ensure gradient is lass than a case-specific maximum gradient
648  // (colour change from white to black across a distance or search_r,
649  // giving a gradient of (255+255+255) / search_r)
650  gradient = std::min(gradient, 765 / search_r);
651  gradient *= search_r; // scale between 0 and 765
652  return 765 - gradient; // return inverse gradient (high gradient =
653  // low cost)
654  }
655  return 765; // no gradient = high cost
656  }
657 }
658 
659 int ccTrace::getSegmentCostDist(int p1, int p2) { return 255; }
660 
661 int ccTrace::getSegmentCostScalar(int p1, int p2) {
662  // m_cloud->getCurrentDisplayedScalarFieldIndex();
663  ccScalarField* sf = static_cast<ccScalarField*>(
665  return (sf->getValue(p2) - sf->getMin()) *
666  (765 / (sf->getMax() - sf->getMin())); // return scalar field value
667  // mapped to range 0 - 765
668 }
669 
670 int ccTrace::getSegmentCostScalarInv(int p1, int p2) {
671  ccScalarField* sf = static_cast<ccScalarField*>(
673  return (sf->getMax() - sf->getValue(p2)) *
674  (765 /
675  (sf->getMax() - sf->getMin())); // return inverted scalar field
676  // value mapped to range 0 - 765
677 }
678 
679 // functions for calculating cost SFs
680 void ccTrace::buildGradientCost(QWidget* parent) {
681  // is there already a gradient SF?
682  if (isGradientPrecomputed()) // already done :)
683  return;
684 
685  // create new SF for greyscale
686  int idx = m_cloud->addScalarField("Greyscale");
688 
689  // make colours greyscale and push to SF (otherwise copy active SF)
690  for (unsigned i = 0; i < m_cloud->size(); i++) {
691  const ecvColor::Rgb& col = m_cloud->getPointColor(i);
693  i, static_cast<ScalarType>((static_cast<int>(col.r) + col.g) +
694  col.b));
695  }
696  // compute min/max
698 
699  // calculate new SF with gradient of the old one
700  float roughnessKernelSize = m_search_r;
701  m_cloud->setCurrentOutScalarField(idx); // set out gradient field - values
702  // are read from here for calc
703 
704  // make gradient sf
705  int gIdx = m_cloud->addScalarField("Gradient");
707  gIdx); // set in scalar field - gradient is written here
708 
709  // get/build octree
710  ecvProgressDialog pDlg(true, parent);
711  pDlg.show();
712 
714  if (!octree) {
715  octree = m_cloud->computeOctree(&pDlg);
716  if (!octree) {
717  CVLog::Error("Failed to compute octree");
718  return;
719  }
720  }
721 
722  // calculate gradient
724  m_cloud,
725  m_search_r, // auto --> FIXME: should be properly set by the user!
726  false, false, &pDlg, octree.data());
727 
728  pDlg.close();
729 
730  if (result != 0) {
731  m_cloud->deleteScalarField(gIdx);
732  CVLog::Warning("Failed to compute the scalar field gradient");
733  return;
734  }
735 
736  // calculate bounds
738 
739  // normalize and log-transform
741  float logMax = log(m_cloud->getScalarField(gIdx)->getMax() + 10);
742  for (unsigned i = 0; i < m_cloud->size(); i++) {
743  int nVal = 765 * log(m_cloud->getPointScalarValue(i) + 10) / logMax;
744  if (nVal < 0) // this is caused by isolated points that were assigned
745  // "null" value gradients
746  nVal = 1; // set to low gradient by default
747  m_cloud->setPointScalarValue(i, nVal);
748  }
749 
750  // recompute min-max...
752 }
753 
754 void ccTrace::buildCurvatureCost(QWidget* parent) {
755  if (isCurvaturePrecomputed()) // already done
756  return;
757 
758  // create curvature SF
759  // make gradient sf
760  int idx = m_cloud->addScalarField("Curvature");
762  idx); // set in scalar field - curvature is written here
764 
765  // get/build octree
766  ecvProgressDialog pDlg(true, parent);
767  pDlg.show();
768 
770  if (!octree) {
771  octree = m_cloud->computeOctree(&pDlg);
772  }
773 
774  // calculate curvature
778  cloudViewer::Neighbourhood::CurvatureType::MEAN_CURV,
779  m_cloud, m_search_r, nullptr, &pDlg, octree.data());
780 
781  pDlg.close();
782 
785  CVLog::Warning("Failed to compute the curvature");
786  return;
787  }
788 
789  // calculate minmax
791 
792  // normalize and log-transform
793  float logMax = log(m_cloud->getScalarField(idx)->getMax() + 10);
794  for (unsigned i = 0; i < m_cloud->size(); i++) {
795  int nVal = 765 * log(m_cloud->getPointScalarValue(i) + 10) / logMax;
796  if (nVal < 0) // this is caused by isolated points that were assigned
797  // "null" value curvatures
798  nVal = 1; // set to low gradient by default
799  m_cloud->setPointScalarValue(i, nVal);
800  }
801 
802  // recompute min-max...
804 }
805 
808  "Gradient"); // look for pre-existing gradient SF
809  return idx != -1; // was something found?
810 }
813  "Curvature"); // look for pre-existing gradient SF
814  return idx != -1; // was something found?
815 }
816 
817 ccFitPlane* ccTrace::fitPlane(int surface_effect_tolerance,
818  float min_planarity) {
819  // put all "trace" points into the cloud
820  finalizePath();
821 
822  if (size() < 3) return 0; // need three points to fit a plane
823 
824  // check we are not trying to fit a plane to a line
826 
827  // calculate eigenvalues of neighbourhood
829  cloudViewer::SquareMatrixd eigVectors;
830  std::vector<double> eigValues;
831  if (Jacobi<double>::ComputeEigenValuesAndVectors(cov, eigVectors, eigValues,
832  true)) {
833  // sort eigenvalues into decending order
834  std::sort(eigValues.rbegin(), eigValues.rend());
835 
836  float y = eigValues[1]; // middle eigen
837  float z = eigValues[2]; // smallest eigen (parallel to plane normal)
838 
839  // calculate planarity (0 = line or random, 1 = plane)
840  float planarity = 1.0f - z / y;
841  if (planarity < min_planarity) {
842  return nullptr;
843  }
844  }
845 
846  // fit plane
847  double rms = 0.0; // output for rms
848  ccFitPlane* p = ccFitPlane::Fit(this, &rms);
849 
850  if (!p) {
851  return nullptr; // return null for invalid planes
852  }
853 
854  p->updateAttributes(rms, m_search_r);
855 
856  // test for 'surface effect'
857  if (m_cloud->hasNormals()) {
858  // calculate average normal of points on trace
859  CCVector3 n_avg;
860  for (unsigned i = 0; i < size(); i++) {
861  // get normal vector
863  m_cloud->getPointNormalIndex(this->getPointGlobalIndex(i)));
864  n_avg += n;
865  }
866  n_avg *= (PC_ONE / size()); // turn sum into average
867 
868  // compare to plane normal
869  CCVector3 n = p->getNormal();
870  if (acos(n_avg.dot(n)) <
871  0.01745329251 * surface_effect_tolerance) // 0.01745329251 converts
872  // deg to rad
873  {
874  // this is a false (surface) plane - reject
875  return 0; // don't return plane
876  }
877  }
878 
879  // all is good! Return the plane :)
880  return p;
881 }
882 
884  // bake points
885  int vertexCount = static_cast<int>(m_cloud->size());
886  for (const std::deque<int>& seg : m_trace) {
887  for (int p : seg) {
888  if (p >= 0 && p < vertexCount) {
889  m_cloud->setPointScalarValue(static_cast<unsigned>(p),
890  getUniqueID());
891  }
892  }
893  }
894 }
895 
898 
899  // setup octree & values for nearest neighbour searches
901  if (!oct) {
902  oct = m_cloud->computeOctree(); // if the user clicked "no" when asked
903  // to compute the octree then tough....
904  }
905 
906  // init vars needed for nearest neighbour search
907  unsigned char level = oct->findBestLevelForAGivenPopulationPerCell(2);
910 
911  // pick 15 random points
912  unsigned int npoints = m_cloud->size();
913  double dsum = 0;
914  srand(npoints); // set seed as n for repeatability
915  for (unsigned int i = 0; i < 30; i++) {
916  // int rn = rand() * rand(); //need to make bigger than rand max...
917  int r = (rand() * rand()) %
918  npoints; // random(ish) number between 0 and n
919 
920  // find nearest neighbour for point
921  nCloud->clear(false);
922  double d = -1.0;
923  oct->findPointNeighbourhood(m_cloud->getPoint(r), nCloud, 2, level, d);
924 
925  if (d != -1.0) // if a point was found
926  {
927  dsum += sqrt(d);
928  }
929  }
930 
931  // average nearest-neighbour distances
932  double d = dsum / 30;
933 
934  // return a number slightly larger than the average distance
935  return d * 1.5;
936 }
937 
938 static QSharedPointer<ccSphere> c_unitPointMarker(0);
940  if (!MACRO_Foreground(context)) // 2D foreground only
941  return; // do nothing
942 
943  if (MACRO_Draw3D(context)) {
944  if (m_waypoints.empty()) // no points -> bail!
945  return;
946 
947  // get the set of OpenGL functions (version 2.1)
948  if (ecvDisplayTools::GetCurrentScreen() == nullptr) {
949  assert(false);
950  return;
951  }
952 
953  // check sphere exists
954  if (!c_unitPointMarker) {
955  c_unitPointMarker = QSharedPointer<ccSphere>(
956  new ccSphere(1.0f, 0, "PointMarker", 6));
957 
958  c_unitPointMarker->showColors(true);
959  c_unitPointMarker->setVisible(true);
960  c_unitPointMarker->setEnabled(true);
961  c_unitPointMarker->showNormals(false);
962  }
963 
964  glDrawParams glParams;
965  getDrawingParameters(glParams);
966 
967  // not sure what this does, but it looks like fun
968  CC_DRAW_CONTEXT markerContext =
969  context; // build-up point maker own 'context'
970  // markerContext.display = 0;
971  markerContext.drawingFlags &=
972  (~CC_ENTITY_PICKING); // we must remove the 'push name flag'
973  // so that the sphere doesn't push its
974  // own!
975 
976  // get camera info
977  ccGLCameraParameters camera;
979  const ecvViewportParameters& viewportParams =
981 
982  // push name for picking
983  bool entityPickingMode = MACRO_EntityPicking(context);
984  if (entityPickingMode) {
985  // glFunc->glPushName(getUniqueIDForDisplay());
986  // minimal display for picking mode!
987  glParams.showNorms = false;
988  glParams.showColors = false;
989  }
990 
991  // set draw colour
993  c_unitPointMarker->setTempColor(color);
994 
995  // get point size for drawing
996  float pSize = markerContext.defaultPointSize;
997  // glFunc->glGetFloatv(GL_POINT_SIZE, &pSize);
998 
999  // draw key-points and structure normals (if assigned)
1000  if (m_isActive) {
1001  for (size_t i = 0; i < m_waypoints.size(); i++) {
1002  // glFunc->glMatrixMode(GL_MODELVIEW);
1003  // glFunc->glPushMatrix();
1004 
1005  const CCVector3* P = m_cloud->getPoint(m_waypoints[i]);
1006  // ccGL::Translate(glFunc, P->x, P->y, P->z);
1007  markerContext.transformInfo.setTranslationStart(
1008  CCVector3(P->x, P->y, P->z));
1009  float scale = context.labelMarkerSize * m_relMarkerScale * 0.3 *
1010  fmin(pSize, 4);
1011  if (viewportParams.perspectiveView && viewportParams.zFar > 0) {
1012  // in perspective view, the actual scale depends on the
1013  // distance to the camera!
1014  double d =
1015  (camera.modelViewMat * CCVector3d::fromArray(P->u))
1016  .norm();
1017  double unitD = viewportParams.zFar /
1018  2; // we consider that the 'standard' scale
1019  // is at half the depth
1020  scale = static_cast<float>(
1021  scale *
1022  sqrt(d /
1023  unitD)); // sqrt = empirical (probably because
1024  // the marker size is already partly
1025  // compensated by
1026  // ecvDisplayTools::computeActualPixelSize())
1027  }
1028  // glFunc->glScalef(scale, scale, scale);
1029  markerContext.transformInfo.setScale(
1030  CCVector3(scale, scale, scale));
1031  c_unitPointMarker->draw(markerContext);
1032  // glFunc->glPopMatrix();
1033  }
1034  } else // just draw lines
1035  {
1036  // draw lines
1037  for (const std::deque<int>& seg : m_trace) {
1038  if (m_width != 0) {
1039  // glFunc->glPushAttrib(GL_LINE_BIT);
1040  // glFunc->glLineWidth(static_cast<GLfloat>(m_width));
1041  markerContext.defaultLineWidth = m_width;
1042  }
1043  // glFunc->glBegin(GL_LINE_STRIP);
1044  // glFunc->glColor3f(color.r, color.g, color.b);
1045  // for (int p : seg)
1046  //{
1047  // ccGL::Vertex3v(glFunc, m_cloud->getPoint(p)->u);
1048  // }
1049  // glFunc->glEnd();
1050  // if (m_width != 0)
1051  //{
1052  // glFunc->glPopAttrib();
1053  // }
1054  }
1055  }
1056 
1057  // draw trace points if trace is active OR point size is large
1058  // (otherwise line gets hidden)
1059  if (m_isActive || pSize > 8) {
1060  for (const std::deque<int>& seg : m_trace) {
1061  for (int p : seg) {
1062  // glFunc->glMatrixMode(GL_MODELVIEW);
1063  // glFunc->glPushMatrix();
1064 
1065  const CCVector3* P = m_cloud->getPoint(p);
1066  // ccGL::Translate(glFunc, P->x, P->y, P->z);
1067  markerContext.transformInfo.setTranslationStart(
1068  CCVector3(P->x, P->y, P->z));
1069  float scale = context.labelMarkerSize * m_relMarkerScale *
1070  fmin(pSize, 4) * 0.2;
1071  if (viewportParams.perspectiveView &&
1072  viewportParams.zFar > 0) {
1073  // in perspective view, the actual scale depends on the
1074  // distance to the camera!
1075  const double* M = camera.modelViewMat.data();
1076  double d = (camera.modelViewMat *
1078  .norm();
1079  double unitD = viewportParams.zFar /
1080  2; // we consider that the 'standard'
1081  // scale is at half the depth
1082  scale = static_cast<float>(
1083  scale *
1084  sqrt(d /
1085  unitD)); // sqrt = empirical (probably
1086  // because the marker size is
1087  // already partly compensated by
1088  // ecvDisplayTools::computeActualPixelSize())
1089  }
1090 
1091  // glFunc->glScalef(scale, scale, scale);
1092  markerContext.transformInfo.setScale(
1093  CCVector3(scale, scale, scale));
1094  c_unitPointMarker->draw(markerContext);
1095  // glFunc->glPopMatrix();
1096  }
1097  }
1098  }
1099  }
1100 }
1101 
1103  ccHObject* object) // return true if object is a valid trace
1104  // [regardless of it's class type]
1105 {
1106  if (object->hasMetaData("ccCompassType")) {
1107  return object->getMetaData("ccCompassType")
1108  .toString()
1109  .contains("Trace");
1110  }
1111  return false;
1112 }
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int offset
math::float4 color
core::Tensor result
Definition: VtkUtils.cpp:76
static QSharedPointer< ccSphere > c_unitPointMarker(0)
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
Jacobi eigen vectors/values decomposition.
Definition: Jacobi.h:23
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 dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:417
double norm2d() const
Returns vector square norm (forces double precision output)
Definition: CVGeom.h:419
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
virtual void getDrawingParameters(glDrawParams &params) const
Returns main OpenGL parameters for this entity.
static ccFitPlane * Fit(cloudViewer::GenericIndexedCloudPersist *cloud, double *rms)
Definition: ccFitPlane.cpp:120
void updateAttributes(float rms, float search_r)
Definition: ccFitPlane.cpp:56
T * data()
Returns a pointer to internal data.
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
ecvColor::Rgb getMeasurementColour() const
Definition: ccMeasurement.h:36
static const CCVector3 & GetNormal(unsigned normIndex)
Static access to ccNormalVectors::getNormal.
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual unsigned getUniqueID() const
Returns object unique ID.
Definition: ecvObject.h:86
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.
bool hasMetaData(const QString &key) const
Returns whether a meta-data element with the given key exists or not.
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
CCVector3 getNormal() const override
Returns the entity normal.
Definition: ecvPlane.h:73
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
const CompressedNormType & getPointNormalIndex(unsigned pointIndex) const override
Returns compressed normal corresponding to a given point.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool hasNormals() const override
Returns whether normals are enabled or not.
bool hasDisplayedScalarField() const override
Returns whether an active scalar field is available or not.
bool hasColors() const override
Returns whether colors are enabled or not.
void deleteScalarField(int index) override
Deletes a specific scalar field.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
const ecvColor::Rgb & getPointColor(unsigned pointIndex) const override
Returns color corresponding to a given point.
Colored polyline.
Definition: ecvPolyline.h:24
PointCoordinateType m_width
Width of the line.
Definition: ecvPolyline.h:256
A scalar field associated to display-related parameters.
Sphere (primitive)
Definition: ecvSphere.h:16
int getSegmentCostScalar(int p1, int p2)
Definition: ccTrace.cpp:661
std::vector< int > m_previous
Definition: ccTrace.h:250
int getSegmentCostDist(int p1, int p2)
Definition: ccTrace.cpp:659
std::deque< int > optimizeSegment(int start, int end, int offset=0)
Definition: ccTrace.cpp:258
static int COST_MODE
Definition: ccTrace.h:203
std::vector< int > m_waypoints
Definition: ccTrace.h:249
float m_relMarkerScale
Definition: ccTrace.h:240
int getSegmentCostScalarInv(int p1, int p2)
Definition: ccTrace.cpp:670
ccPointCloud * m_cloud
Definition: ccTrace.h:241
int insertWaypoint(int pointId)
Definition: ccTrace.cpp:92
ccFitPlane * fitPlane(int surface_effect_tolerance=10, float min_planarity=0.75f)
Definition: ccTrace.cpp:817
void recalculatePath()
Definition: ccTrace.cpp:252
void bakePathToScalarField()
Definition: ccTrace.cpp:883
int getSegmentCostRGB(int p1, int p2)
Definition: ccTrace.cpp:477
static bool isTrace(ccHObject *object)
Definition: ccTrace.cpp:1102
void buildCurvatureCost(QWidget *parent)
Definition: ccTrace.cpp:754
bool isGradientPrecomputed()
Definition: ccTrace.cpp:806
int getSegmentCostLight(int p1, int p2)
Definition: ccTrace.cpp:535
void finalizePath()
Definition: ccTrace.cpp:237
int getSegmentCostGrad(int p1, int p2, float search_r)
Definition: ccTrace.cpp:590
int getSegmentCostDark(int p1, int p2)
Definition: ccTrace.cpp:524
int getSegmentCost(int p1, int p2)
Definition: ccTrace.cpp:450
virtual void drawMeOnly(CC_DRAW_CONTEXT &context) override
Draws the entity only (not its children)
Definition: ccTrace.cpp:939
std::vector< std::deque< int > > m_trace
Definition: ccTrace.h:246
void buildGradientCost(QWidget *parent)
Definition: ccTrace.cpp:680
ccTrace(ccPointCloud *associatedCloud)
Definition: ccTrace.cpp:15
float calculateOptimumSearchRadius()
Definition: ccTrace.cpp:896
bool isCurvaturePrecomputed()
Definition: ccTrace.cpp:811
bool optimizePath(int maxIterations=1000000)
Definition: ccTrace.cpp:149
int getSegmentCostCurve(int p1, int p2)
Definition: ccTrace.cpp:540
A kind of ReferenceCloud based on the DgmOctree::NeighboursSet structure.
const CCVector3 * getPoint(unsigned index) const override
Returns the ith point.
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
Definition: DgmOctree.h:133
static ErrorCode ComputeCharactersitic(GeomCharacteristic c, int subOption, GenericIndexedCloudPersist *cloud, PointCoordinateType kernelRadius, const CCVector3 *roughnessUpDir=nullptr, GenericProgressCallback *progressCb=nullptr, DgmOctree *inputOctree=nullptr)
Unified way to compute a geometric characteristic.
ScalarType computeCurvature(const CCVector3 &P, CurvatureType cType)
Computes the curvature of a set of point (by fitting a 2.5D quadric)
cloudViewer::SquareMatrixd computeCovarianceMatrix()
Computes the covariance matrix.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
void setCurrentOutScalarField(int index)
Sets the OUTPUT scalar field.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
void setPointScalarValue(unsigned pointIndex, ScalarType value) override
void setCurrentScalarField(int index)
Sets both the INPUT & OUTPUT scalar field.
unsigned size() const override
Definition: PointCloudTpl.h:38
ScalarType getPointScalarValue(unsigned pointIndex) const override
void setCurrentInScalarField(int index)
Sets the INPUT scalar field.
const CCVector3 * getPoint(unsigned index) const override
void clear(bool unusedParam=true) override
Clears the cloud.
Definition: Polyline.cpp:15
A very simple point cloud (no point duplication)
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
virtual GenericIndexedCloudPersist * getAssociatedCloud()
Returns the associated (source) cloud.
unsigned size() const override
Returns the number of points.
void invalidateBoundingBox()
Invalidates the bounding-box.
virtual unsigned getPointGlobalIndex(unsigned localIndex) const
virtual void clear(bool releaseMemory=false)
Clears the cloud.
virtual void setAssociatedCloud(GenericIndexedCloudPersist *cloud)
Sets the associated (source) cloud.
static int computeScalarFieldGradient(GenericIndexedCloudPersist *theCloud, PointCoordinateType radius, bool euclideanDistances, bool sameInAndOutScalarField=false, GenericProgressCallback *progressCb=nullptr, DgmOctree *theOctree=nullptr)
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
virtual void computeMinAndMax()
Determines the min and max values.
Definition: ScalarField.h:123
ScalarType getMin() const
Returns the minimum value.
Definition: ScalarField.h:72
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
ScalarType getMax() const
Returns the maximum value.
Definition: ScalarField.h:74
RGB color structure.
Definition: ecvColorTypes.h:49
static void GetGLCameraParameters(ccGLCameraParameters &params)
Returns the current OpenGL camera parameters.
static const ecvViewportParameters & GetViewportParameters()
static QWidget * GetCurrentScreen()
Graphical progress indicator (thread-safe)
Standard parameters for GL displays/viewports.
bool perspectiveView
Perspective view state.
double zFar
Actual perspective 'zFar' value.
int min(int a, int b)
Definition: cutil_math.h:53
#define MACRO_Draw3D(context)
#define MACRO_Foreground(context)
#define MACRO_EntityPicking(context)
@ CC_ENTITY_PICKING
ImGuiContext * context
Definition: Window.cpp:76
static const std::string path
Definition: PointCloud.cpp:59
bool GreaterThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:37
cloudViewer::DgmOctree * octree
void setScale(const CCVector3 &scale)
void setTranslationStart(const CCVector3 &trans)
OpenGL camera parameters.
ccGLMatrixd modelViewMat
Model view matrix (GL_MODELVIEW)
Display context.
int drawingFlags
Drawing options (see below)
unsigned char defaultLineWidth
TransformInfo transformInfo
unsigned char defaultPointSize
Structure used during nearest neighbour search.
Definition: DgmOctree.h:101
const CCVector3 * point
Point.
Definition: DgmOctree.h:103
Display parameters of a 3D entity.
bool showColors
Display colors.
bool showNorms
Display normals.