ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ccCompass.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 <array>
9 
10 // Qt
11 #include <QCheckBox>
12 #include <QFileDialog>
13 #include <QFileInfo>
14 #include <QIntValidator>
15 
16 // common
17 #include <QtCompat.h>
18 #include <ecvPickingHub.h>
19 
20 // CV_DB_LIB
21 #include <ecvBox.h>
22 #include <ecvDisplayTools.h>
23 #include <ecvProgressDialog.h>
24 #include <qcombobox.h>
25 
26 // LOCAL
27 #include "ccCompass.h"
28 #include "ccCompassDlg.h"
29 #include "ccCompassInfo.h"
30 #include "ccFitPlaneTool.h"
31 #include "ccGeoObject.h"
32 #include "ccLineationTool.h"
33 #include "ccMapDlg.h"
34 #include "ccNoteTool.h"
35 #include "ccPinchNodeTool.h"
36 #include "ccSNECloud.h"
37 #include "ccThicknessTool.h"
38 #include "ccTopologyTool.h"
39 #include "ccTraceTool.h"
40 
41 // initialize default static pars
42 bool ccCompass::drawName = false;
43 bool ccCompass::drawStippled = true;
44 bool ccCompass::drawNormals = true;
45 bool ccCompass::fitPlanes = true;
47 bool ccCompass::mapMode = false;
49 
50 ccCompass::ccCompass(QObject* parent)
51  : QObject(parent), ccStdPluginInterface(":/CC/plugin/qCompass/info.json") {
52  // initialize all tools
53  m_fitPlaneTool = new ccFitPlaneTool();
54  m_traceTool = new ccTraceTool();
55  m_lineationTool = new ccLineationTool();
56  m_thicknessTool = new ccThicknessTool();
57  m_topologyTool = new ccTopologyTool();
58  m_noteTool = new ccNoteTool();
59  m_pinchNodeTool = new ccPinchNodeTool();
60 }
61 
62 // deconstructor
63 ccCompass::~ccCompass() {
64  // delete all tools
65  delete m_fitPlaneTool;
66  delete m_traceTool;
67  delete m_lineationTool;
68  delete m_thicknessTool;
69  delete m_topologyTool;
70  delete m_noteTool;
71  delete m_pinchNodeTool;
72 }
73 
74 void ccCompass::onNewSelection(const ccHObject::Container& selectedEntities) {
75  // disable the main plugin icon if no entity is loaded
76  m_action->setEnabled(m_app && m_app->dbRootObject() &&
78 
79  if (!m_dlg | !m_mapDlg) {
80  return; // not initialized yet - ignore callback
81  }
82 
83  if (m_activeTool) {
85  selectedEntities); // pass on to the active tool
86  }
87 
88  // clear GeoObject selection & disable associated GUI
89  if (m_geoObject) {
90  m_geoObject->setActive(false);
91  }
92  m_geoObject = nullptr;
93  m_geoObject_id = -1;
94  if (m_mapDlg) {
95  m_mapDlg->setLowerButton->setEnabled(false);
96  m_mapDlg->setUpperButton->setEnabled(false);
97  m_mapDlg->setInteriorButton->setEnabled(false);
98  m_mapDlg->selectionLabel->setEnabled(false);
99  m_mapDlg->selectionLabel->setText(tr("No Selection"));
100  }
101  // has a GeoObject (or a child of one?) been selected?
102  for (ccHObject* obj : selectedEntities) {
103  // recurse upwards looking for geoObject & relevant part (interior,
104  // upper, lower)
105  ccHObject* o = obj;
106  bool interior = false;
107  bool upper = false;
108  bool lower = false;
109  while (o) {
110  interior = interior || ccGeoObject::isGeoObjectInterior(o);
111  upper = upper || ccGeoObject::isGeoObjectUpper(o);
112  lower = lower || ccGeoObject::isGeoObjectLower(o);
113 
114  // have we found a geoObject?
115  if (ccGeoObject::isGeoObject(o)) {
116  // found one!
117  m_geoObject = static_cast<ccGeoObject*>(o);
118  if (m_geoObject) // cast succeeded
119  {
120  m_geoObject_id = m_geoObject->getUniqueID(); // store id
121  m_geoObject->setActive(true); // display as "active"
122 
123  // activate GUI
125  m_mapDlg->setLowerButton->setEnabled(true);
126  m_mapDlg->setUpperButton->setEnabled(true);
127  m_mapDlg->setInteriorButton->setEnabled(true);
128  }
129  m_mapDlg->selectionLabel->setEnabled(true);
130  m_mapDlg->selectionLabel->setText(m_geoObject->getName());
131 
132  // set appropriate upper/lower/interior setting on gui
133  if (interior) {
134  writeToInterior();
135  } else if (upper) {
136  writeToUpper();
137  } else if (lower) {
138  writeToLower();
139  }
140 
141  // done!
142  return;
143  }
144  }
145 
146  // next parent
147  o = o->getParent();
148  }
149  }
150 }
151 
152 // Submit the action to launch ccCompass to CC
153 QList<QAction*> ccCompass::getActions() {
154  // default action (if it has not been already created, it's the moment to do
155  // it)
156  if (!m_action) // this is the action triggered by clicking the "Compass"
157  // button in the plugin menu
158  {
159  // here we use the default plugin name, description and icon,
160  // but each action can have its own!
161  m_action = new QAction(getName(), this);
162  m_action->setToolTip(getDescription());
163  m_action->setIcon(getIcon());
164 
165  // connect appropriate signal
166  connect(m_action, &QAction::triggered, this,
167  &ccCompass::doAction); // this binds the m_action to the
168  // ccCompass::doAction() function
169  }
170 
171  return QList<QAction*>{m_action};
172 }
173 
174 // Called by CC when the plugin should be activated - sets up the plugin and
175 // then calls startMeasuring()
177  // m_app should have already been initialized by CC when plugin is loaded!
178  //(--> pure internal check)
179  assert(m_app);
180 
181  // initialize tools (essentially give them a copy of m_app)
189 
190  // check valid window
191  if (!m_app->getActiveWindow()) {
192  m_app->dispToConsole(tr("[ccCompass] Could not find valid 3D window."),
194  return;
195  }
196 
197  // bind gui
198  if (!m_dlg) {
199  // bind GUI events
201 
202  // general
203  ccCompassDlg::connect(m_dlg->closeButton, SIGNAL(clicked()), this,
204  SLOT(onClose()));
205  ccCompassDlg::connect(m_dlg->acceptButton, SIGNAL(clicked()), this,
206  SLOT(onAccept()));
207  ccCompassDlg::connect(m_dlg->saveButton, SIGNAL(clicked()), this,
208  SLOT(onSave()));
209  ccCompassDlg::connect(m_dlg->undoButton, SIGNAL(clicked()), this,
210  SLOT(onUndo()));
211  ccCompassDlg::connect(m_dlg->infoButton, SIGNAL(clicked()), this,
212  SLOT(showHelp()));
213 
214  // modes
215  ccCompassDlg::connect(m_dlg->mapMode, SIGNAL(clicked()), this,
216  SLOT(enableMapMode()));
217  ccCompassDlg::connect(m_dlg->compassMode, SIGNAL(clicked()), this,
218  SLOT(enableMeasureMode()));
219 
220  // tools
221  ccCompassDlg::connect(m_dlg->pickModeButton, SIGNAL(clicked()), this,
222  SLOT(setPick()));
223  ccCompassDlg::connect(m_dlg->pairModeButton, SIGNAL(clicked()), this,
224  SLOT(setLineation()));
225  ccCompassDlg::connect(m_dlg->planeModeButton, SIGNAL(clicked()), this,
226  SLOT(setPlane()));
227  ccCompassDlg::connect(m_dlg->traceModeButton, SIGNAL(clicked()), this,
228  SLOT(setTrace()));
229 
230  // extra tools
231  ccCompassDlg::connect(m_dlg->m_pinchTool, SIGNAL(triggered()), this,
232  SLOT(addPinchNode()));
233  ccCompassDlg::connect(m_dlg->m_measure_thickness, SIGNAL(triggered()),
234  this, SLOT(setThickness()));
235  ccCompassDlg::connect(m_dlg->m_measure_thickness_twoPoint,
236  SIGNAL(triggered()), this, SLOT(setThickness2()));
237 
238  ccCompassDlg::connect(m_dlg->m_youngerThan, SIGNAL(triggered()), this,
239  SLOT(setYoungerThan()));
240  ccCompassDlg::connect(m_dlg->m_follows, SIGNAL(triggered()), this,
241  SLOT(setFollows()));
242  ccCompassDlg::connect(m_dlg->m_equivalent, SIGNAL(triggered()), this,
243  SLOT(setEquivalent()));
244 
245  ccCompassDlg::connect(m_dlg->m_mergeSelected, SIGNAL(triggered()), this,
246  SLOT(mergeGeoObjects()));
247  ccCompassDlg::connect(m_dlg->m_fitPlaneToGeoObject, SIGNAL(triggered()),
248  this, SLOT(fitPlaneToGeoObject()));
249  ccCompassDlg::connect(m_dlg->m_recalculateFitPlanes,
250  SIGNAL(triggered()), this,
251  SLOT(recalculateFitPlanes()));
252  ccCompassDlg::connect(m_dlg->m_toPointCloud, SIGNAL(triggered()), this,
253  SLOT(convertToPointCloud()));
254  ccCompassDlg::connect(m_dlg->m_distributeSelection, SIGNAL(triggered()),
255  this, SLOT(distributeSelection()));
256  ccCompassDlg::connect(m_dlg->m_estimateNormals, SIGNAL(triggered()),
257  this, SLOT(estimateStructureNormals()));
258  ccCompassDlg::connect(m_dlg->m_estimateP21, SIGNAL(triggered()), this,
259  SLOT(estimateP21()));
260  ccCompassDlg::connect(m_dlg->m_estimateStrain, SIGNAL(triggered()),
261  this, SLOT(estimateStrain()));
262  ccCompassDlg::connect(m_dlg->m_noteTool, SIGNAL(triggered()), this,
263  SLOT(setNote()));
264  ccCompassDlg::connect(m_dlg->m_loadFoliations, SIGNAL(triggered()),
265  this, SLOT(importFoliations()));
266  ccCompassDlg::connect(m_dlg->m_loadLineations, SIGNAL(triggered()),
267  this, SLOT(importLineations()));
268  ccCompassDlg::connect(m_dlg->m_toSVG, SIGNAL(triggered()), this,
269  SLOT(exportToSVG()));
270 
271  // settings menu
272  ccCompassDlg::connect(m_dlg->m_showNames, SIGNAL(toggled(bool)), this,
273  SLOT(toggleLabels(bool)));
274  ccCompassDlg::connect(m_dlg->m_showStippled, SIGNAL(toggled(bool)),
275  this, SLOT(toggleStipple(bool)));
276  ccCompassDlg::connect(m_dlg->m_showNormals, SIGNAL(toggled(bool)), this,
277  SLOT(toggleNormals(bool)));
278  ccCompassDlg::connect(m_dlg->m_recalculate, SIGNAL(triggered()), this,
279  SLOT(recalculateSelectedTraces()));
280  }
281 
282  if (!m_mapDlg) {
284 
285  ccCompassDlg::connect(m_mapDlg->m_create_geoObject, SIGNAL(triggered()),
286  this, SLOT(addGeoObject()));
287  ccCompassDlg::connect(m_mapDlg->m_create_geoObjectSS,
288  SIGNAL(triggered()), this,
289  SLOT(addGeoObjectSS()));
290  ccCompassDlg::connect(m_mapDlg->setInteriorButton, SIGNAL(clicked()),
291  this, SLOT(writeToInterior()));
292  ccCompassDlg::connect(m_mapDlg->setUpperButton, SIGNAL(clicked()), this,
293  SLOT(writeToUpper()));
294  ccCompassDlg::connect(m_mapDlg->setLowerButton, SIGNAL(clicked()), this,
295  SLOT(writeToLower()));
296  }
297 
300 
301  // load ccCompass objects
302  tryLoading();
303 
304  // start in measure mode
306 
307  // begin measuring
308  startMeasuring();
309 }
310 
311 // loop through DB tree looking for ccCompass objects that
312 // are not represented by our custom class. If any are found,
313 // replace them. Assuming not too many objects are found, this should be
314 // quite fast; hence we call it every time the selection changes.
316  // setup progress window
317  ecvProgressDialog prg(true, m_app->getMainWindow());
318  prg.setMethodTitle(tr("Compass"));
319  prg.setInfo(tr("Converting Compass types..."));
320  prg.start();
321 
322  // loop through DB_Tree and find any ccCompass objects
323  std::vector<int> originals; // ids of original objects
324  std::vector<ccHObject*> replacements; // pointers to objects that will
325  // replace the originals
326  unsigned nChildren = m_app->dbRootObject()->getChildrenNumber();
327  for (unsigned i = 0; i < nChildren; i++) {
328  prg.setValue(static_cast<int>((50 * i) / nChildren));
329  ccHObject* c = m_app->dbRootObject()->getChild(i);
330  tryLoading(c, &originals, &replacements);
331  }
332 
333  // replace all "originals" with their corresponding "duplicates"
334  for (size_t i = 0; i < originals.size(); i++) {
335  prg.setValue(static_cast<int>(50 + (50 * i) / originals.size()));
336 
337  ccHObject* original = m_app->dbRootObject()->find(originals[i]);
338  ccHObject* replacement = replacements[i];
339 
340  replacement->setVisible(original->isVisible());
341  replacement->setEnabled(original->isEnabled());
342 
343  if (!original) // can't find for some reason?
344  continue;
345  if (!replacement) // can't find for some reason?
346  continue;
347 
348  // steal all the children
349  for (unsigned c = 0; c < original->getChildrenNumber(); c++) {
350  replacement->addChild(original->getChild(c));
351  }
352 
353  // remove them from the orignal parent
354  original->detachAllChildren();
355 
356  // add new parent to scene graph
357  original->getParent()->addChild(replacement);
358 
359  // delete originals
360  m_app->removeFromDB(original);
361 
362  // add replacement to dbTree
363  m_app->addToDB(replacement, false, false, false, false);
364 
365  // is replacement a GeoObject? If so, "disactivate" it
366  if (ccGeoObject::isGeoObject(replacement)) {
367  ccGeoObject* g = static_cast<ccGeoObject*>(replacement);
368  g->setActive(false);
369  }
370  }
371 
372  prg.close();
373 }
374 
376  std::vector<int>* originals,
377  std::vector<ccHObject*>* replacements) {
378  // recurse on children
379  for (unsigned i = 0; i < obj->getChildrenNumber(); i++) {
380  tryLoading(obj->getChild(i), originals, replacements);
381  }
382 
383  // is object already represented by a ccCompass class?
384  if (dynamic_cast<ccFitPlane*>(obj) || dynamic_cast<ccTrace*>(obj) ||
385  dynamic_cast<ccPointPair*>(
386  obj) // n.b. several classes inherit from PointPair, so this
387  // cast will still succede for them
388  || dynamic_cast<ccGeoObject*>(obj) || dynamic_cast<ccSNECloud*>(obj)) {
389  return; // we need do nothing!
390  }
391 
392  // are we a geoObject
393  if (ccGeoObject::isGeoObject(obj)) {
394  ccHObject* geoObj = new ccGeoObject(obj, m_app);
395 
396  // add to originals/duplicates list [these are used later to overwrite
397  // the originals]
398  originals->push_back(obj->getUniqueID());
399  replacements->push_back(geoObj);
400 
401  return;
402  }
403 
404  // are we a fit plane?
405  if (ccFitPlane::isFitPlane(obj)) {
406  // cast to plane
407  ccPlane* p = dynamic_cast<ccPlane*>(obj);
408  if (p) {
409  // create equivalent fit plane object
410  ccHObject* plane = new ccFitPlane(p);
411 
412  // add to originals/duplicates list [these are used later to
413  // overwrite the originals]
414  originals->push_back(obj->getUniqueID());
415  replacements->push_back(plane);
416  return;
417  }
418  }
419 
420  // are we a SNE cloud?
421  if (ccSNECloud::isSNECloud(obj)) {
422  ccHObject* sneCloud = new ccSNECloud(static_cast<ccPointCloud*>(obj));
423  originals->push_back(obj->getUniqueID());
424  replacements->push_back(sneCloud);
425  return;
426  }
427 
428  // is the HObject a polyline? (this will be the case for lineations &
429  // traces)
430  ccPolyline* p = dynamic_cast<ccPolyline*>(obj);
431  if (p) {
432  // are we a trace?
433  if (ccTrace::isTrace(obj)) {
434  ccTrace* trace = new ccTrace(p);
435  trace->setWidth(2);
436  // add to originals/duplicates list [these are used later to
437  // overwrite the originals]
438  originals->push_back(obj->getUniqueID());
439  replacements->push_back(trace);
440  return;
441  }
442 
443  // are we a lineation?
444  if (ccLineation::isLineation(obj)) {
445  ccHObject* lin = new ccLineation(p);
446  originals->push_back(obj->getUniqueID());
447  replacements->push_back(lin);
448  return;
449  }
450 
451  // are we a thickness?
452  if (ccThickness::isThickness(obj)) {
453  ccHObject* t = new ccThickness(p);
454  originals->push_back(obj->getUniqueID());
455  replacements->push_back(t);
456  return;
457  }
458 
459  // are we a topology relation?
460  // todo
461 
462  // are we a pinchpiont
463  if (ccPinchNode::isPinchNode(obj)) {
464  ccHObject* n = new ccPinchNode(p);
465  originals->push_back(obj->getUniqueID());
466  replacements->push_back(n);
467  return;
468  }
469 
470  // are we a note?
471  if (ccNote::isNote(obj)) {
472  ccHObject* n = new ccNote(p);
473  originals->push_back(obj->getUniqueID());
474  replacements->push_back(n);
475  return;
476  }
477  }
478 }
479 
480 // Begin measuring
482  // check valid gl window
483  if (!m_app->getActiveWindow()) {
484  // invalid pointer error
485  m_app->dispToConsole(tr("Error: ccCompass could not find the "
486  "ACloudViewer window. Abort!"),
488  return false;
489  }
490 
491  // setup listener for mouse events
492  m_app->getActiveWindow()->installEventFilter(this);
493 
494  // refresh window
495  ecvDisplayTools::RedrawDisplay(true, false);
496 
497  // start GUI
498  m_app->registerOverlayDialog(m_dlg, Qt::TopRightCorner);
499  m_dlg->start();
500 
501  // activate active tool
502  if (m_activeTool) {
504  }
505 
506  m_active = true;
507  return true;
508 }
509 
510 // Exits measuring
511 bool ccCompass::stopMeasuring(bool finalStop /*=false*/) {
512  // remove click listener
513  if (m_app->getActiveWindow()) {
514  m_app->getActiveWindow()->removeEventFilter(this);
515  }
516 
517  // reset gui
518  cleanupBeforeToolChange(!finalStop);
519 
520  // stop picking
521  stopPicking();
522 
523  // set active tool to null (avoids tools "doing stuff" when the gui isn't
524  // shown)
525  m_activeTool = nullptr;
526 
527  // remove overlay GUI
528  if (m_dlg) {
529  m_dlg->stop(true);
531  }
532 
533  if (m_mapDlg) {
534  m_mapDlg->stop(true);
536  }
537 
538  // forget last measurement
539  if (m_activeTool) {
540  m_activeTool->cancel();
542  }
543 
544  // redraw
545  if (m_app->getActiveWindow()) {
546  m_app->refreshAll(true, false);
547  }
548 
549  m_active = false;
550 
551  return true;
552 }
553 
554 // registers this plugin with the picking hub
556  if (m_picking) // already picking... don't need to add again
557  return true;
558 
559  // activate "point picking mode"
560  if (!m_app->pickingHub()) // no valid picking hub
561  {
562  m_app->dispToConsole(tr("[ccCompass] Could not retrieve valid picking "
563  "hub. Measurement aborted."),
565  return false;
566  }
567 
568  if (!m_app->pickingHub()->addListener(this, true, true)) {
569  m_app->dispToConsole(tr("Another tool is already using the picking "
570  "mechanism. Stop it first"),
572  return false;
573  }
574 
575  m_picking = true;
576  return true;
577 }
578 
579 // removes this plugin from the picking hub
581  // stop picking
582  if (m_app->pickingHub()) {
583  m_app->pickingHub()->removeListener(this);
584  }
585 
586  m_picking = false;
587 }
588 
589 // Get the place/object that new measurements or interpretation should be stored
591  // check if there is an active GeoObject or we are in mapMode
593  // check there is an active GeoObject
594  if (!m_geoObject) {
595  m_app->dispToConsole(tr("[ccCompass] Error: Please select a "
596  "GeoObject to digitize to."),
598  }
599 
600  // check it actually exists/hasn't been deleted
602  // object has been deleted
603  m_geoObject = nullptr;
604  m_geoObject_id = -1;
605  m_app->dispToConsole(tr("[ccCompass] Error: Please select a "
606  "GeoObject to digitize to."),
608  } else {
609  // object exists - we can use it to find the insert point
611  if (!insertPoint) // something went wrong?
612  {
614  tr("[ccCompass] Warning: Could not retrieve valid "
615  "mapping region for the active GeoObject."),
617  } else {
618  return insertPoint; // :)
619  }
620  }
621  } else {
622  // otherwise, we're in "Compass" mode, so...
623  // find/create a group called "measurements"
624  ccHObject* measurement_group = nullptr;
625 
626  // search for a "measurements" group
627  for (unsigned i = 0; i < m_app->dbRootObject()->getChildrenNumber();
628  i++) {
629  if (m_app->dbRootObject()->getChild(i)->getName() ==
630  tr("measurements")) {
631  measurement_group = m_app->dbRootObject()->getChild(i);
632  } else {
633  // also search first-level children of root node (when files are
634  // re-loaded this is where things will sit)
635  for (unsigned c = 0;
636  c <
637  m_app->dbRootObject()->getChild(i)->getChildrenNumber();
638  c++) {
639  if (m_app->dbRootObject()
640  ->getChild(i)
641  ->getChild(c)
642  ->getName() == tr("measurements")) {
643  measurement_group =
644  m_app->dbRootObject()->getChild(i)->getChild(c);
645  break;
646  }
647  }
648  }
649 
650  // found a valid group :)
651  if (measurement_group) {
652  break;
653  }
654  }
655 
656  // didn't find it - create a new one!
657  if (!measurement_group) {
658  measurement_group = new ccHObject(tr("measurements"));
659  m_app->dbRootObject()->addChild(measurement_group);
660  m_app->addToDB(measurement_group, false, true, false, false);
661  }
662 
663  return measurement_group; // this is the insert point
664  }
665  return nullptr; // no valid insert point
666 }
667 
668 // This function is called when a point is picked (through the picking hub)
670  pointPicked(pi.entity, pi.itemIndex, pi.clickPoint.x(), pi.clickPoint.y(),
671  pi.P3D); // map straight to pointPicked function
672 }
673 
674 // Process point picks
676  ccHObject* entity, unsigned itemIdx, int x, int y, const CCVector3& P) {
677  if (!entity) // null pick
678  {
679  return;
680  }
681 
682  // no active tool (i.e. picking mode) - set selected object as active
683  if (!m_activeTool) {
684  m_app->setSelectedInDB(entity, true);
685  return;
686  }
687 
688  // find relevant node to add data to
689  ccHObject* parentNode = getInsertPoint();
690 
691  if (parentNode == nullptr) // could not get insert point for some reason
692  {
693  return; // bail
694  }
695 
696  // ensure what we are writing too is visible (avoids confusion if it is
697  // turned off...)
698  parentNode->setEnabled(true);
699 
700  // call generic "point-picked" function of active tool
701  m_activeTool->pointPicked(parentNode, itemIdx, entity, P);
702 
703  // have we picked a point cloud?
704  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
705  // get point cloud
706  ccPointCloud* cloud =
707  static_cast<ccPointCloud*>(entity); // cast to point cloud
708 
709  if (!cloud) {
711  tr("[Item picking] Shit's fubar (Picked point is not in "
712  "pickable entities DB?)!"));
713  return;
714  }
715 
716  // pass picked point, cloud & insert point to relevant tool
717  m_activeTool->pointPicked(parentNode, itemIdx, cloud, P);
718  }
719 
720  // redraw
721  m_app->updateUI();
723 }
724 
725 bool ccCompass::eventFilter(QObject* obj, QEvent* event) {
726  // update cost mode (just in case it has changed) & fit plane params
730 
731  if (event->type() == QEvent::MouseButtonDblClick) {
732  QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
733  if (mouseEvent->buttons() == Qt::RightButton) {
734  stopMeasuring();
735  return true;
736  }
737  }
738  return false;
739 }
740 
741 // exit this tool
743  // cancel current action
744  if (m_activeTool) {
745  m_activeTool->cancel();
746  }
747 
748  // finish measuring
749  stopMeasuring();
750 }
751 
753  if (m_activeTool) {
754  m_activeTool->accept();
755  }
756 }
757 
758 // returns true if object was created by ccCompass
760  // return isFitPlane(object) | isTrace(object) | isLineation(object);
761  return object->hasMetaData(tr("ccCompassType"));
762 }
763 
764 // undo last plane
766  if (m_activeTool) {
767  m_activeTool->undo();
768  }
769 }
770 
771 // called to cleanup pointers etc. before changing the active tool
772 void ccCompass::cleanupBeforeToolChange(bool autoRestartPicking /*=true*/) {
773  // finish current tool
774  if (m_activeTool) {
776  }
777 
778  // clear m_hiddenObjects buffer
779  if (!m_hiddenObjects.empty()) {
780  for (int i : m_hiddenObjects) {
781  ccHObject* o = m_app->dbRootObject()->find(i);
782  if (o) {
783  o->setVisible(true);
784  }
785  }
786  m_hiddenObjects.clear();
787  ecvDisplayTools::RedrawDisplay(false, true);
788  }
789 
790  // uncheck/disable gui components (the relevant ones will be activated
791  // later)
792  if (m_dlg) {
793  m_dlg->pairModeButton->setChecked(false);
794  m_dlg->planeModeButton->setChecked(false);
795  m_dlg->traceModeButton->setChecked(false);
796  m_dlg->pickModeButton->setChecked(false);
797  m_dlg->extraModeButton->setChecked(false);
798  m_dlg->undoButton->setEnabled(false);
799  m_dlg->acceptButton->setEnabled(false);
800  }
801 
802  if (autoRestartPicking) {
803  // check picking is engaged
804  startPicking();
805  }
806 }
807 
808 // activate lineation mode
810  // cleanup
812 
813  // activate lineation tool
816 
817  // trigger selection changed
818  onNewSelection(m_app->getSelectedEntities());
819 
820  // update GUI
821  m_dlg->undoButton->setEnabled(false);
822  m_dlg->pairModeButton->setChecked(true);
823  ecvDisplayTools::RedrawDisplay(true, true);
824 }
825 
826 // activate plane mode
828  // cleanup
830 
831  // activate plane tool
834 
835  // trigger selection changed
836  onNewSelection(m_app->getSelectedEntities());
837 
838  // update GUI
839  m_dlg->undoButton->setEnabled(m_fitPlaneTool->canUndo());
840  m_dlg->planeModeButton->setChecked(true);
841  ecvDisplayTools::RedrawDisplay(true, true);
842 }
843 
844 // activate trace mode
846  // cleanup
848 
849  // activate trace tool
852 
853  // trigger selection changed
854  onNewSelection(m_app->getSelectedEntities());
855 
856  // update GUI
857  m_dlg->traceModeButton->setChecked(true);
858  m_dlg->undoButton->setEnabled(m_traceTool->canUndo());
859  m_dlg->acceptButton->setEnabled(true);
860  ecvDisplayTools::RedrawDisplay(true, true);
861 }
862 
863 // activate the paint tool
866 
867  m_activeTool = nullptr; // picking tool is default - so no tool class
868  stopPicking(); // let CC handle picks now
869 
870  // hide point clouds
872 
873  m_dlg->pickModeButton->setChecked(true);
874  m_dlg->undoButton->setEnabled(false);
875  m_dlg->acceptButton->setEnabled(false);
876  ecvDisplayTools::RedrawDisplay(true, true);
877 }
878 
879 // activate the pinch-node tool
882 
883  // activate thickness tool
886 
887  // update GUI
888  m_dlg->extraModeButton->setChecked(true);
889  m_dlg->undoButton->setEnabled(m_activeTool->canUndo());
890  m_dlg->acceptButton->setEnabled(false);
891  ecvDisplayTools::RedrawDisplay(true, true);
892 }
893 // activates the thickness tool
896 
897  // activate thickness tool
901  false; // one-point mode (unless changed later)
902 
903  // trigger selection changed
904  onNewSelection(m_app->getSelectedEntities());
905 
906  // update GUI
907  m_dlg->extraModeButton->setChecked(true);
908  m_dlg->undoButton->setEnabled(m_activeTool->canUndo());
909  m_dlg->acceptButton->setEnabled(true);
910  ecvDisplayTools::RedrawDisplay(true, true);
911 }
912 
913 // activates the thickness tool in two-point mode
915  setThickness();
917  true; // now set the tool to operate in two-point mode
918 }
919 
920 void ccCompass::setYoungerThan() // activates topology tool in "older-than"
921  // mode
922 {
924 
925  m_activeTool = m_topologyTool; // activate topology tool
926  stopPicking(); // let CC handle picks now - this tool only needs "selection
927  // changed" callbacks
928 
929  // hide point clouds
931 
932  // update gui
933  m_dlg->undoButton->setEnabled(false);
934  m_dlg->acceptButton->setEnabled(false);
935  ecvDisplayTools::RedrawDisplay(true, true);
936 
937  // set topology tool mode
939 }
940 
941 void ccCompass::setFollows() // activates topology tool in "follows" mode
942 {
943  setYoungerThan();
944  // set topology tool mode
946 }
947 
948 void ccCompass::setEquivalent() // activates topology mode in "equivalent" mode
949 {
950  setYoungerThan();
951  // set topology tool mode
953 }
954 
955 // activates note mode
958 
959  // activate thickness tool
962 
963  // update GUI
964  m_dlg->extraModeButton->setChecked(true);
965  m_dlg->undoButton->setEnabled(m_activeTool->canUndo());
966  m_dlg->acceptButton->setEnabled(false);
967  ecvDisplayTools::RedrawDisplay(true, true);
968 }
969 
970 // merges the selected GeoObjects
972  // get selected GeoObjects
973  std::vector<ccGeoObject*> objs;
974 
975  for (ccHObject* o : m_app->getSelectedEntities()) {
976  if (ccGeoObject::isGeoObject(o)) {
977  ccGeoObject* g = dynamic_cast<ccGeoObject*>(o);
978  if (g) // could possibly be null if non-loaded geo-objects exist
979  {
980  objs.push_back(g);
981  }
982  }
983  }
984 
985  if (objs.size() < 2) // not enough geoObjects
986  {
988  tr("[Compass] Select several GeoObjects to merge."),
990  return; // nothing to merge
991  }
992 
993  // merge geo-objects with first one
994  ccGeoObject* dest = objs[0];
995  ccHObject* d_interior = dest->getRegion(ccGeoObject::INTERIOR);
998  for (int i = 1; i < objs.size(); i++) {
999  ccHObject* interior = objs[i]->getRegion(ccGeoObject::INTERIOR);
1000  ccHObject* upper = objs[i]->getRegion(ccGeoObject::UPPER_BOUNDARY);
1001  ccHObject* lower = objs[i]->getRegion(ccGeoObject::LOWER_BOUNDARY);
1002 
1003  // add children to destination
1004  interior->transferChildren(*d_interior, true);
1005  upper->transferChildren(*d_upper, true);
1006  lower->transferChildren(*d_lower, true);
1007 
1008  // delete un-needed objects
1009  objs[i]->removeChild(interior);
1010  objs[i]->removeChild(upper);
1011  objs[i]->removeChild(lower);
1012  objs[i]->getParent()->removeChild(objs[i]);
1013 
1014  // delete
1015  m_app->removeFromDB(objs[i]);
1016  m_app->removeFromDB(upper);
1017  m_app->removeFromDB(lower);
1018  m_app->removeFromDB(interior);
1019  }
1020 
1021  m_app->setSelectedInDB(dest, true);
1022  m_app->refreshAll(true); // redraw gui + 3D view
1023 
1025  tr("[Compass] Merged selected GeoObjects to ") + dest->getName(),
1027 }
1028 
1029 // calculates best-fit plane for the upper and lower surfaces of the selected
1030 // GeoObject
1032  m_app->dispToConsole(tr("[Compass] fitPlane"),
1034 
1035  // loop selected GeoObject
1037  if (!o) {
1038  m_geoObject_id = -1;
1039  return; // invalid id
1040  }
1041 
1042  ccGeoObject* obj = static_cast<ccGeoObject*>(o); // get as geoObject
1043 
1044  // fit upper plane
1046  ccPointCloud* points =
1047  new ccPointCloud(); // create point cloud for storing points
1048  double rms; // float for storing rms values
1049  for (unsigned i = 0; i < upper->getChildrenNumber(); i++) {
1050  if (ccTrace::isTrace(upper->getChild(i))) {
1051  ccTrace* t = dynamic_cast<ccTrace*>(upper->getChild(i));
1052 
1053  if (t != nullptr) // can in rare cases be a null ptr (dynamic cast
1054  // will fail for traces that haven't been
1055  // converted to ccTrace objects)
1056  {
1057  points->reserve(points->size() + t->size()); // make space
1058 
1059  for (unsigned p = 0; p < t->size(); p++) {
1060  points->addPoint(*t->getPoint(p)); // add point to
1061  }
1062  }
1063  }
1064  }
1065 
1066  // calculate and store upper fitplane
1067  if (points->size() > 0) {
1068  ccFitPlane* p = ccFitPlane::Fit(points, &rms);
1069  if (p) {
1070  QVariantMap map;
1071  map.insert("RMS", rms);
1072  p->setMetaData(map, true);
1073  upper->addChild(p);
1074  m_app->addToDB(p, false, false, false, false);
1075  } else {
1076  m_app->dispToConsole(tr("[Compass] Not enough 3D information to "
1077  "generate sensible fit plane."),
1079  }
1080  }
1081 
1082  // rinse and repeat for lower (assuming normal GeoObject; skip this step for
1083  // single-surface object)
1085  points->clear();
1087  for (unsigned i = 0; i < lower->getChildrenNumber(); i++) {
1088  if (ccTrace::isTrace(lower->getChild(i))) {
1089  ccTrace* t = dynamic_cast<ccTrace*>(lower->getChild(i));
1090 
1091  if (t != nullptr) // can in rare cases be a null ptr (dynamic
1092  // cast will fail for traces that haven't
1093  // been converted to ccTrace objects)
1094  {
1095  points->reserve(points->size() + t->size()); // make space
1096 
1097  for (unsigned p = 0; p < t->size(); p++) {
1098  points->addPoint(*t->getPoint(p)); // add point to
1099  // cloud
1100  }
1101  }
1102  }
1103  }
1104 
1105  // calculate and store lower fitplane
1106  if (points->size() > 0) {
1107  ccFitPlane* p = ccFitPlane::Fit(points, &rms);
1108  if (p) {
1109  QVariantMap map;
1110  map.insert("RMS", rms);
1111  p->setMetaData(map, true);
1112  lower->addChild(p);
1113  m_app->addToDB(p, false, false, false, true);
1114  } else {
1115  m_app->dispToConsole(tr("[Compass] Not enough 3D information "
1116  "to generate sensible fit plane."),
1118  }
1119  }
1120  }
1121  // clean up point cloud
1122  delete (points);
1123 }
1124 
1125 // recalculates all fit planes in the DB Tree, except those generated using the
1126 // Plane Tool
1128  // get all plane objects
1129  ccHObject::Container planes;
1130  m_app->dbRootObject()->filterChildren(planes, true, CV_TYPES::PLANE, true);
1131 
1132  std::vector<ccHObject*> garbage; // planes that need to be deleted
1133  for (ccHObject::Container::iterator it = planes.begin(); it != planes.end();
1134  it++) {
1135  if (!ccFitPlane::isFitPlane((*it)))
1136  continue; // only deal with FitPlane objects
1137 
1138  // is parent of the plane a trace object?
1139  ccHObject* parent = (*it)->getParent();
1140 
1141  if (ccTrace::isTrace(parent)) // add to recalculate list
1142  {
1143  // recalculate the fit plane
1144  ccTrace* t = static_cast<ccTrace*>(parent);
1145  ccFitPlane* p = t->fitPlane();
1146  if (p) {
1147  t->addChild(p); // add the new fit-plane
1148  m_app->addToDB(p, false, false, false, false);
1149  }
1150 
1151  // add the old plane to the garbage list (to be deleted later)
1152  garbage.push_back((*it));
1153 
1154  continue; // next
1155  }
1156 
1157  // otherwise - does the plane have a child that is a trace object (i.e.
1158  // it was created in Compass mode)
1159  for (unsigned c = 0; c < (*it)->getChildrenNumber(); c++) {
1160  ccHObject* child = (*it)->getChild(c);
1161  if (ccTrace::isTrace(child)) // add to recalculate list
1162  {
1163  // recalculate the fit plane
1164  ccTrace* t = static_cast<ccTrace*>(child);
1165  ccFitPlane* p = t->fitPlane();
1166 
1167  if (p) {
1168  //... do some jiggery pokery
1169  parent->addChild(
1170  p); // add fit-plane to the original fit-plane's
1171  // parent (as we are replacing it)
1172  m_app->addToDB(p, false, false, false, false);
1173 
1174  // remove the trace from the original fit-plane
1175  (*it)->detachChild(t);
1176 
1177  // add it to the new one
1178  p->addChild(t);
1179 
1180  // add the old plane to the garbage list (to be deleted
1181  // later)
1182  garbage.push_back((*it));
1183 
1184  break;
1185  }
1186  }
1187  }
1188  }
1189 
1190  // delete all the objects in the garbage
1191  for (int i = 0; i < garbage.size(); i++) {
1192  garbage[i]->getParent()->removeChild(garbage[i]);
1193  }
1194 }
1195 
1196 // prior distribution for orientations (depends on outcrop orientation)
1197 inline double prior(double phi, double theta, double nx, double ny, double nz) {
1198  // check normal points down
1199  if (nz > 0) {
1200  nx *= -1;
1201  ny *= -1;
1202  nz *= -1;
1203  }
1204 
1205  // calculate angle between normal vector and the normal estimate(phi, theta)
1206  double alpha = acos(nx * sin(phi) * cos(theta) +
1207  ny * cos(phi) * cos(theta) - nz * sin(theta));
1208  return sin(alpha) /
1209  (2 * M_PI); // n.b. 2pi is normalising factor so that function
1210  // integrates to one over all phi,theta
1211 }
1212 
1213 // calculate log scale-factor for wishart dist. This only needs to be done once
1214 // per X, so is pulled out of the wish function for performance
1215 inline double logWishSF(cloudViewer::SquareMatrixd X, int nobserved) {
1216  // calculate determinant of X
1217  double detX = X.m_values[0][0] * ((X.m_values[1][1] * X.m_values[2][2]) -
1218  (X.m_values[2][1] * X.m_values[1][2])) -
1219  X.m_values[0][1] * (X.m_values[1][0] * X.m_values[2][2] -
1220  X.m_values[2][0] * X.m_values[1][2]) +
1221  X.m_values[0][2] * (X.m_values[1][0] * X.m_values[2][1] -
1222  X.m_values[2][0] * X.m_values[1][1]);
1223 
1224  return (nobserved - 4.0) * 0.5 * log(detX) -
1225  (nobserved * 3. / 2.) *
1226  log(2.0) - // parts of gamma function that do not depend on
1227  // the scale matrix
1228  ((3.0 / 2.0) * log(M_PI) + lgamma(nobserved / 2.0) +
1229  lgamma((nobserved / 2.0) - 0.5) +
1230  lgamma((nobserved / 2.0) - 1.0)); // log(gamma3(nobserved/2))
1231 }
1232 
1233 // calculate log wishart probability density
1235  int nobserved,
1236  double phi,
1237  double theta,
1238  double alpha,
1239  double e1,
1240  double e2,
1241  double e3,
1242  double lsf) {
1243  //--------------------------------------------------
1244  // Derive scale matrix eigenvectors (basis matrix)
1245  //--------------------------------------------------
1246  double e[3][3];
1247  double i[3][3];
1248 
1249  // eigenvector 3 (normal to plane defined by theta->phi)
1250  e[0][2] = sin(phi) * cos(theta);
1251  e[1][2] = cos(phi) * cos(theta);
1252  e[2][2] = -sin(theta);
1253  // eigenvector 2 (normal of theta->phi projected into horizontal plane and
1254  // rotated by angle alpha)
1255  e[0][1] = sin(phi) * sin(theta) * sin(alpha) - cos(phi) * cos(alpha);
1256  e[1][1] = sin(phi) * cos(alpha) + sin(theta) * cos(phi) * sin(alpha);
1257  e[2][1] = sin(alpha) * cos(theta);
1258  // eigenvector 1 (calculate using cross product)
1259  e[0][0] = e[1][2] * e[2][1] - e[2][2] * e[1][1];
1260  e[1][0] = e[2][2] * e[0][1] - e[0][2] * e[2][1];
1261  e[2][0] = e[0][2] * e[1][1] - e[1][2] * e[0][1];
1262 
1263  // calculate determinant of the scale matrix by multiplying it's eigens
1264  double D = e1 * e2 * e3;
1265 
1266  // calculate the inverse of the scale matrix (we don't actually need to
1267  // compute the scale matrix)
1268  e1 = 1.0 / e1; // N.B. Note that by inverting the eigenvalues we compute
1269  // the inverse scale matrix
1270  e2 = 1.0 / e2;
1271  e3 = 1.0 / e3;
1272 
1273  // calculate unique components of I from the eigenvectors and inverted
1274  // eigenvalues
1275  i[0][0] = e1 * e[0][0] * e[0][0] + e2 * e[0][1] * e[0][1] +
1276  e3 * e[0][2] * e[0][2]; // diagonal component
1277  i[1][1] = e1 * e[1][0] * e[1][0] + e2 * e[1][1] * e[1][1] +
1278  e3 * e[1][2] * e[1][2];
1279  i[2][2] = e1 * e[2][0] * e[2][0] + e2 * e[2][1] * e[2][1] +
1280  e3 * e[2][2] * e[2][2];
1281  i[0][1] = e1 * e[0][0] * e[1][0] + e2 * e[0][1] * e[1][1] +
1282  e3 * e[0][2] * e[1][2]; // off-axis component
1283  i[0][2] = e1 * e[0][0] * e[2][0] + e2 * e[0][1] * e[2][1] +
1284  e3 * e[0][2] * e[2][2];
1285  i[1][2] = e1 * e[1][0] * e[2][0] + e2 * e[1][1] * e[2][1] +
1286  e3 * e[1][2] * e[2][2];
1287 
1288  // compute the trace of I times X
1289  double trIX = (i[0][0] * X.m_values[0][0] + i[0][1] * X.m_values[1][0] +
1290  i[0][2] * X.m_values[2][0]) +
1291  (i[0][1] * X.m_values[0][1] + i[1][1] * X.m_values[1][1] +
1292  i[1][2] * X.m_values[2][1]) +
1293  (i[0][2] * X.m_values[0][2] + i[1][2] * X.m_values[1][2] +
1294  i[2][2] * X.m_values[2][2]);
1295 
1296  // return the log wishart probability density
1297  return lsf - 0.5 * (trIX + nobserved * log(D));
1298 }
1299 
1300 // Estimate the normal vector to the structure this trace represents at each
1301 // point in this trace. declare variables for the dlg used by the below function
1302 // as statics, so they are remembered between uses (for convenience)
1303 static unsigned int minsize = 500; // these are the defaults
1304 static unsigned int maxsize = 1000;
1305 static double tcDistance =
1306  10.0; // the square of the maximum distance to compute thicknesses for
1307 static unsigned int oversample = 30;
1308 static double likPower = 1.0;
1309 static bool calcThickness = true;
1310 static double stride = 0.025;
1311 static int dof = 10;
1313  //******************************************
1314  // build dialog to get input properties
1315  //******************************************
1316  QDialog dlg(m_app->getMainWindow());
1317  QVBoxLayout* vbox = new QVBoxLayout();
1318  QLabel minSizeLabel(tr("Minimum trace size (points):"));
1319  QLineEdit minSizeText(QString::number(minsize));
1320  minSizeText.setValidator(
1321  new QIntValidator(5, std::numeric_limits<int>::max()));
1322  QLabel maxSizeLabel(tr("Maximum trace size (points):"));
1323  QLineEdit maxSizeText(QString::number(maxsize));
1324  maxSizeText.setValidator(
1325  new QIntValidator(50, std::numeric_limits<int>::max()));
1326  QLabel dofLabel(tr("Wishart Degrees of Freedom:"));
1327  QLineEdit dofText(QString::number(dof));
1328  dofText.setValidator(new QIntValidator(3, std::numeric_limits<int>::max()));
1329  QLabel likPowerLabel(tr("Likelihood power:"));
1330  QLineEdit likPowerText(QString::number(likPower));
1331  likPowerText.setValidator(
1332  new QDoubleValidator(0.01, std::numeric_limits<double>::max(), 6));
1333  QLabel calcThickLabel(tr("Calculate thickness:"));
1334  QCheckBox calcThickChk(tr("Calculate thickness"));
1335  calcThickChk.setChecked(calcThickness);
1336  QLabel distanceLabel(tr("Distance cutoff (m):"));
1337  QLineEdit distanceText(QString::number(tcDistance));
1338  distanceText.setValidator(
1339  new QDoubleValidator(0, std::numeric_limits<double>::max(), 6));
1340  QLabel sampleLabel(tr("Samples:"));
1341  QLineEdit sampleText(QString::number(oversample));
1342  sampleText.setValidator(
1343  new QIntValidator(1, 10000)); //>10000 samples per point will break
1344  // even the best computer!
1345  QLabel strideLabel(tr("MCMC Stride (radians):"));
1346  QLineEdit strideText(QString::number(stride));
1347  strideText.setValidator(new QDoubleValidator(0.0000001, 0.5, 6));
1348 
1349  // tooltips
1350  minSizeText.setToolTip(
1351  tr("The minimum size of the normal-estimation window."));
1352  maxSizeText.setToolTip(
1353  tr("The maximum size of the normal-estimation window."));
1354  dofText.setToolTip(
1355  tr("Sets the degrees of freedom parameter for the Wishart "
1356  "distribution. Due to non-independent data/errors in traces, "
1357  "this should be low (~10). Higher give more confident results - "
1358  "use with care!"));
1359  distanceText.setToolTip(
1360  tr("The furthest distance to search for points on the opposite "
1361  "surface of a GeoObject during thickness calculations."));
1362  sampleText.setToolTip(
1363  tr("Sample n orientation estimates at each point in each trace to "
1364  "quantify uncertainty."));
1365  likPowerText.setToolTip(tr(
1366  "Fudge factor to change the balance between the prior and "
1367  "likelihood functions. Advanced use only - see docs for details."));
1368  strideText.setToolTip(
1369  tr("Standard deviation of the normal distribution used to "
1370  "calculate monte-carlo jumps during sampling. Larger numbers "
1371  "sample more widely but are slower to run."));
1372 
1373  QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
1374  QObject::connect(&buttonBox, SIGNAL(accepted()), &dlg, SLOT(accept()));
1375  QObject::connect(&buttonBox, SIGNAL(rejected()), &dlg, SLOT(reject()));
1376 
1377  vbox->addWidget(&minSizeLabel);
1378  vbox->addWidget(&minSizeText);
1379  vbox->addWidget(&maxSizeLabel);
1380  vbox->addWidget(&maxSizeText);
1381  vbox->addWidget(&dofLabel);
1382  vbox->addWidget(&dofText);
1383  vbox->addWidget(&likPowerLabel);
1384  vbox->addWidget(&likPowerText);
1385  vbox->addWidget(&sampleLabel);
1386  vbox->addWidget(&sampleText);
1387  vbox->addWidget(&strideLabel);
1388  vbox->addWidget(&strideText);
1389  vbox->addWidget(&calcThickLabel);
1390  vbox->addWidget(&calcThickChk);
1391  vbox->addWidget(&distanceLabel);
1392  vbox->addWidget(&distanceText);
1393  vbox->addWidget(&buttonBox);
1394 
1395  dlg.setLayout(vbox);
1396 
1397  // execute dialog and get results
1398  int result = dlg.exec();
1399  if (result == QDialog::Rejected) {
1400  return; // bail!
1401  }
1402 
1403  // get values
1404  minsize = minSizeText.text().toInt(); // these are the defaults
1405  maxsize = maxSizeText.text().toInt();
1406  dof = dofText.text().toInt();
1407  tcDistance = distanceText.text()
1408  .toDouble(); // the square of the maximum distance to
1409  // compute thicknesses for
1410  oversample = sampleText.text().toInt();
1411  likPower = likPowerText.text().toDouble();
1412  calcThickness = calcThickChk.isChecked();
1413  stride = strideText.text().toDouble();
1414 
1415  // cleanup
1416  dlg.close();
1417  delete vbox;
1418 
1419  // someone is an idiot
1420  if (maxsize < minsize) {
1421  m_app->dispToConsole(tr("[ccCompass] Error - provided maxsize is less "
1422  "than minsize? Get your shit together..."),
1424  return;
1425  }
1426 
1427  m_app->dispToConsole(tr("[ccCompass] Estimating structure normals. This "
1428  "may take a while..."),
1430 
1431  // declare some variables used in the loops
1432  double d = 0.0;
1433  double cx = 0.0;
1434  double cy = 0.0;
1435  double cz = 0.0;
1436  int iid = 0;
1437  cloudViewer::SquareMatrixd eigVectors;
1438  std::vector<double> eigValues;
1439  bool hasNormals = true;
1440  bool broken = false; // assume normals exist until check later on
1441 
1442  // setup progress dialog
1443  ecvProgressDialog prg(true, m_app->getMainWindow());
1444  prg.setMethodTitle(tr("Estimating Structure Normals"));
1445  prg.setInfo(tr("Gathering data..."));
1446  prg.start();
1447  prg.update(0.0);
1448 
1449  // gather objects to process
1450  std::vector<std::array<ccHObject*, 2>>
1451  datasets; // upper/lower surfaces will be put into this array
1452  std::vector<ccPointCloud*> pinchClouds;
1453  for (ccHObject* o : m_app->getSelectedEntities()) {
1454  // option 1 - selected object is a GeoObject or has GeoObject children
1455  ccHObject::Container objs;
1456  if (ccGeoObject::isGeoObject(o)) { // selected object is a geoObject
1457  objs.push_back(o);
1458  } else // otherwise search for all GeoObjects
1459  {
1460  o->filterChildren(
1461  objs, true,
1462  CV_TYPES::HIERARCHY_OBJECT); // n.b. geoObjects are simpy
1463  // considered to be hierarchy
1464  // objects by CC
1465  }
1466 
1467  bool foundGeoObject = false;
1468  for (ccHObject* o2 : objs) {
1469  if (ccGeoObject::isGeoObject(o2)) {
1470  ccGeoObject* g = dynamic_cast<ccGeoObject*>(o2);
1471  if (g) { // could possibly be null if non-loaded geo-objects
1472  // exist
1473  foundGeoObject = true; // use to escape to next object
1474  // later
1475 
1476  // store upper and lower regions
1477  std::array<ccHObject*, 2> data = {
1481  g)) { // special case - single surface
1482  // geoboject (upper and lower regions
1483  // will be the same). Set upper to null
1484  data[1] = nullptr;
1485  }
1486  datasets.push_back(data);
1487 
1488  // build empty point cloud for pinch nodes to go in
1489  ccPointCloud* cloud =
1490  new ccPointCloud(); // points will be written here
1491  // if the object is a GeoObject
1492  // and if it contains pinch
1493  // nodes
1494  pinchClouds.push_back(cloud); // store it
1495 
1496  // gather pinch-nodes from GeoObject
1497  ccHObject::Container objs;
1498  g->filterChildren(
1499  objs, true,
1500  CV_TYPES::POLY_LINE); // pinch nodes inherit the
1501  // polyline clas
1502  for (ccHObject* c : objs) {
1504  c)) { // is it a pinch node?
1505  ccPinchNode* p = dynamic_cast<ccPinchNode*>(c);
1506  if (p != nullptr) // can in rare cases fail
1507  {
1508  cloud->reserve(
1509  cloud->size() +
1510  1); // pinch nodes only have one point
1511  cloud->addPoint(
1512  *p->getPoint(0)); // get this point
1513  }
1514  }
1515  }
1516  }
1517  }
1518  }
1519  if (foundGeoObject) {
1520  continue; // skip to next object if we found one (or more!)
1521  // GeoObjects
1522  }
1523 
1524  // option 2 - selected object is a trace or has children that are traces
1525  objs.clear();
1526  if (ccTrace::isTrace(o)) { // selected object is a trace
1527  objs.push_back(o);
1528  } else { // otherwise search for all GeoObjects
1529  o->filterChildren(objs, true,
1530  CV_TYPES::POLY_LINE); // n.b. geoObjects are
1531  // simpy considered to be
1532  // hierarchy objects by CC
1533  }
1534  for (ccHObject* o2 : objs) {
1535  if (ccTrace::isTrace(o2) && o2->isEnabled()) { // is it a trace?
1536  ccTrace* t = dynamic_cast<ccTrace*>(o2);
1537  if (t != nullptr) { // can in rare cases be a null ptr (dynamic
1538  // cast will fail for traces that haven't
1539  // been converted to ccTrace objects)
1540  std::array<ccHObject*, 2> data = {t, nullptr};
1541  datasets.push_back(data); // store data for processing
1542  pinchClouds.push_back(
1543  new ccPointCloud()); // push empty cloud (no pinch
1544  // nodes).
1545  }
1546  }
1547  }
1548  }
1549 
1550  if (datasets.empty()) { // no data found
1552  tr("[ccCompass] No GeoObjects or Traces could be found to "
1553  "estimate structure normals for. Please select some!"),
1555  }
1556 
1557  // process datasets std::array<ccHObject*, 2> regions : datasets
1558  for (int _d = 0; _d < datasets.size(); _d++) {
1559  // update progress dialog
1560  prg.setInfo(
1561  tr("Processing %1 of %2 datasets: Calculating fit planes...")
1562  .arg(_d + 1)
1563  .arg(datasets.size()));
1564  prg.update(0.0f);
1565  if (prg.isCancelRequested()) {
1566  break;
1567  }
1568 
1569  // get regions and pinchNodes to work on this step
1570  std::array<ccHObject*, 2> regions = datasets[_d];
1571  ccPointCloud* pinchNodes = pinchClouds[_d];
1572 
1573  //************************************************
1574  // LOAD POINT DATA FROM TRACESS IN REGIONS
1575  //************************************************
1576  ccPointCloud* points[] = {
1577  new ccSNECloud(), // Lower Boundary Points
1578  new ccSNECloud()}; // Upper Boundary Points (will remain empty
1579  // for everything execept multi-surface
1580  // GeoObjects)
1581  ccPointCloud* samples[] = {
1582  nullptr,
1583  nullptr}; // lower and upper boundary samples (will be
1584  // populated later if samples are generated).
1585 
1586  // for lower,upper in the case of a GeoObject, otherwise regions[1] will
1587  // be null and will be ignored
1588  for (unsigned r = 0; r < 2; r++) {
1589  if (regions[r] == nullptr) {
1590  delete points[r];
1591  continue; // skip null regions
1592  }
1593 
1594  // search for traces in this region
1595  ccHObject::Container objs;
1596  if (ccTrace::isTrace(regions[r])) { // given object is a trace
1597  objs.push_back(regions[r]);
1598  } else { // otherwise search for child traces (this is a GeoObject
1599  // region so traces need to be joined together)
1600  regions[r]->filterChildren(objs, true, CV_TYPES::POLY_LINE);
1601  }
1602  for (ccHObject* c : objs) {
1603  if (ccTrace::isTrace(c) && c->isEnabled()) // is it a trace?
1604  {
1605  ccTrace* t = dynamic_cast<ccTrace*>(c);
1606  if (t !=
1607  nullptr) // can in rare cases be a null ptr (dynamic
1608  // cast will fail for traces that haven't been
1609  // converted to ccTrace objects)
1610  {
1611  // copy points from this trace across into the relevant
1612  // point cloud for future access
1613  points[r]->reserve(points[r]->size() +
1614  t->size()); // make space
1615  points[r]->reserveTheNormsTable(); // make space for
1616  // normals
1617  points[r]->setGlobalScale(
1618  t->getGlobalScale()); // copy global shift &
1619  // scale onto new point
1620  // cloud
1621  points[r]->setGlobalShift(t->getGlobalShift());
1622  for (unsigned p = 0; p < t->size(); p++) {
1623  points[r]->addPoint(*t->getPoint(
1624  p)); // add point to relevant surface
1625  points[r]->addNorm(
1626  t->getPointNormal(p)); // add point normal
1627  }
1628  }
1629  }
1630  }
1631 
1632  // skip if there are not enough points!
1633  if (points[r]->size() < minsize) {
1635  tr("[ccCompass] Warning: Region %1 contains less than "
1636  "minsize points. Region ignored.")
1637  .arg(regions[r]->getUniqueID()),
1639  delete points[r];
1640  points[r] = nullptr;
1641  regions[r] = nullptr;
1642  continue;
1643  }
1644 
1645  //*********************************************************
1646  // SORT GATHERED POINTS INTO ORDER ALONG LONG-AXIS OF TRACE
1647  //*********************************************************
1649  points[r]); // put points for this surface into a
1650  // neighbourhood and get the sorting direction
1651  // (principal eigenvector)
1652  const CCVector3* longAxis =
1653  Z.getLSPlaneX(); // n.b. this is a normal vector
1654  if (longAxis == nullptr) {
1655  // fail friendly if eigens could not be computed
1657  tr("[ccCompass] Warning: Could not compute eigensystem "
1658  "for region %1. Region ignored.")
1659  .arg(regions[r]->getUniqueID()),
1661  continue; // skip to next region
1662  }
1663 
1664  // now sort points along this vector
1665  std::vector<unsigned> pid; // store link to point id in original
1666  // cloud (for later data storage)
1667  std::vector<double> dist, px, py, pz, nx, ny, nz;
1668 
1669  // add first point
1670  pid.push_back(0);
1671  dist.push_back(points[r]->getPoint(0)->dot(*longAxis));
1672  px.push_back(points[r]->getPoint(0)->x);
1673  py.push_back(points[r]->getPoint(0)->y);
1674  pz.push_back(points[r]->getPoint(0)->z);
1675  nx.push_back(points[r]->getPointNormal(0).x);
1676  ny.push_back(points[r]->getPointNormal(0).y);
1677  nz.push_back(points[r]->getPointNormal(0).z);
1678 
1679  for (unsigned p = 0; p < points[r]->size(); p++) {
1680  // calculate distance along the longAxis
1681  d = points[r]->getPoint(p)->dot(*longAxis);
1682 
1683  // quick-check to see if point can just be pushed to end of the
1684  // list
1685  if (dist[dist.size() - 1] <= d) {
1686  pid.push_back(p);
1687  dist.push_back(d);
1688  px.push_back(points[r]->getPoint(p)->x);
1689  py.push_back(points[r]->getPoint(p)->y);
1690  pz.push_back(points[r]->getPoint(p)->z);
1691  nx.push_back(points[r]->getPointNormal(p).x);
1692  ny.push_back(points[r]->getPointNormal(p).y);
1693  nz.push_back(points[r]->getPointNormal(p).z);
1694  } else {
1695  // find insert point
1696  for (int n = 0; n < dist.size(); n++) {
1697  // check id = n
1698  if (dist[n] > d) // found an insert point from the left
1699  {
1700  iid = n;
1701  break;
1702  } // TODO - could optimise this by searching backwards
1703  // from the end also?
1704  }
1705 
1706  // do inserts
1707  dist.insert(dist.begin() + iid, d);
1708  pid.insert(pid.begin() + iid, p);
1709  px.insert(px.begin() + iid, points[r]->getPoint(p)->x);
1710  py.insert(py.begin() + iid, points[r]->getPoint(p)->y);
1711  pz.insert(pz.begin() + iid, points[r]->getPoint(p)->z);
1712  nx.insert(nx.begin() + iid, points[r]->getPointNormal(p).x);
1713  ny.insert(ny.begin() + iid, points[r]->getPointNormal(p).y);
1714  nz.insert(nz.begin() + iid, points[r]->getPointNormal(p).z);
1715  }
1716  }
1717 
1718  //**************************************************************************************************
1719  // CREATE BREAKS AT PINCH NODES (these prevent planes including
1720  // points from two sides of a pinch node
1721  //**************************************************************************************************
1722  std::vector<bool> breaks(
1723  px.size(), false); // if point n is a break (closest point
1724  // to a pinch node), breaks[n] == True.
1726 
1727  // build octree over points in combined trace
1728  ccOctree::Shared oct = points[r]->computeOctree();
1729  unsigned char level = oct->findBestLevelForAGivenPopulationPerCell(
1730  2); // init vars needed for nearest neighbour search
1731  cloudViewer::ReferenceCloud* nCloud =
1733  d = -1.0; // re-use the d variable rather than re-declaring another
1734  for (unsigned p = 0; p < pinchNodes->size(); p++) {
1735  // get closest point in combined trace to this pinch node
1736  nCloud->clear(false);
1737  oct->findPointNeighbourhood(pinchNodes->getPoint(p), nCloud, 1,
1738  level, d);
1739  breaks[nCloud->getPointGlobalIndex(0)] = true; // assign
1740  }
1741 
1742  //***********************************************************************************************
1743  // RECURSE THROUGH ALL POSSIBLE COMBINATIONS OF POINTS TO FIND THE
1744  // BEST STRUCTURE NORMAL ESTIMATE
1745  //***********************************************************************************************
1746  // declare variables used in nested loops below
1747  int n;
1748  double mnx, mny, mnz, lpd, lsf, phi, theta, alpha, len;
1749  bool hasValidSNE =
1750  false; // becomes true once a valid plane is found
1751  std::vector<double> bestPd(
1752  px.size(),
1753  std::numeric_limits<double>::
1754  lowest()); // best (log) probability density
1755  // observed for each point
1756  std::vector<double> bestPhi(px.size(), 0);
1757  std::vector<double> bestTheta(px.size(), 0);
1758  std::vector<double> bestAlpha(px.size(), 0);
1759  std::vector<double> bestE1(px.size(), 0);
1760  std::vector<double> bestE2(px.size(), 0);
1761  std::vector<double> bestE3(px.size(), 0);
1762  std::vector<cloudViewer::SquareMatrixd> bestX(
1763  px.size()); // keep track of best COV matrix for each trace
1764  // (for oversampling later)
1765  std::vector<CCVector3> sne(
1766  px.size()); // list of the best surface normal estimates
1767  // found for each point (corresponds with the
1768  // MAP above)
1769  std::vector<int> start(px.size(),
1770  0); // index of start point for best planes
1771  std::vector<int> end(px.size(),
1772  0); // index of end point for best planes
1773  std::vector<int> segmentID(
1774  px.size(), -1); // unique id for each point segment.
1775  std::vector<CCVector3> normal(
1776  px.size()); // list of the surface normals (as opposed to
1777  // structure normals stored in SNE).
1778 
1779  // check if valid normals have been retrieved
1780  if (hasNormals) {
1781  if (abs(nx[0]) <= 0.000001 && abs(ny[0]) <= 0.0000001 &&
1782  abs(nz[0]) <= 0.00000001) // zero normal vector means
1783  // normals not computed
1784  {
1786  tr("[ccCompass] Warning: Cannot compensate for "
1787  "outcrop-surface bias as point cloud has no "
1788  "normals. Structure normal estimates may be "
1789  "misleading or incorrect."),
1791  hasNormals = false; // don't bother checking again - if
1792  // normals are computed they will exist
1793  // for all points
1794  }
1795  }
1796 
1797  // loop through all possible continuous subsets of the combined
1798  // trace with minsize < length < maxsize.
1799  for (unsigned _min = 0; _min < px.size() - minsize; _min++) {
1800  // update progress bar
1801  prg.update(100 * _min /
1802  static_cast<float>(px.size() - minsize));
1803 
1804  if (prg.isCancelRequested()) {
1805  // cleanup
1806  delete points[r];
1807  for (int i = 0; i < pinchClouds.size(); i++) {
1808  delete pinchClouds[i];
1809  }
1810  return;
1811  }
1812 
1813  // do inner loop
1814  for (unsigned _max = _min + minsize;
1815  _max <
1816  std::min(static_cast<unsigned>(px.size()), _min + maxsize);
1817  _max++) {
1818  // size of the current subset
1819  n = _max - _min + 1;
1820 
1821  //-------------------------------------------------------------------------------------------------------------------------------------
1822  // compute centroid of points between min and max (and the
1823  // average normal). Also check if break-point exists (if so
1824  // skip this subset)
1825  //-------------------------------------------------------------------------------------------------------------------------------------
1826  cx = 0.0;
1827  cy = 0.0;
1828  cz = 0.0;
1829  mnx = 0.0;
1830  mny = 0.0;
1831  mnz = 0.0;
1832  broken = false;
1833  for (unsigned p = _min; p <= _max; p++) {
1834  cx += px[p];
1835  cy += py[p];
1836  cz += pz[p]; // average point positions
1837  if (hasNormals) {
1838  mnx += nx[p];
1839  mny += ny[p];
1840  mnz += nz[p]; // average point normals
1841  }
1842  if (breaks[pid[p]]) { // is this a breakpoint
1843  broken = true;
1844  break; // skip to next plane!
1845  }
1846  }
1847  if (broken) {
1848  break; // skip to next _min point
1849  }
1850 
1851  cx /= n;
1852  cy /= n;
1853  cz /= n; // position vector of subset centroid
1854 
1855  if (hasNormals) {
1856  mnx /= n;
1857  mny /= n;
1858  mnz /= n; // average normal vector of subset centroid
1859  len = sqrt(mnx * mnx + mny * mny +
1860  mnz * mnz); // normalise
1861  mnx /= len;
1862  mny /= len;
1863  mnz /= len;
1864  }
1865 
1866  hasValidSNE =
1867  true; // we have now found at least one valid plane
1868 
1869  //-----------------------------------------------------------------------------
1870  // compute the scatter and covariance matrices of this
1871  // section of the trace
1872  //-----------------------------------------------------------------------------
1873  cloudViewer::SquareMatrixd X(3); // scale matrix
1874  for (unsigned p = _min; p <= _max; p++) {
1875  X.m_values[0][0] += (px[p] - cx) * (px[p] - cx); // mXX
1876  X.m_values[1][1] += (py[p] - cy) * (py[p] - cy); // mYY
1877  X.m_values[2][2] += (pz[p] - cz) * (pz[p] - cz); // mZZ
1878  X.m_values[0][1] += (px[p] - cx) * (py[p] - cy); // mXY
1879  X.m_values[0][2] += (px[p] - cx) * (pz[p] - cz); // mXZ
1880  X.m_values[1][2] += (py[p] - cy) * (pz[p] - cz); // mYZ
1881  }
1882 
1884  3); // convert to covariance matrix
1885  cov.m_values[0][0] = X.m_values[0][0] / n;
1886  cov.m_values[1][1] = X.m_values[1][1] / n;
1887  cov.m_values[2][2] = X.m_values[2][2] / n;
1888  cov.m_values[0][1] = X.m_values[0][1] / n;
1889  cov.m_values[0][2] = X.m_values[0][2] / n;
1890  cov.m_values[1][2] = X.m_values[1][2] / n;
1891 
1892  // update X to reflect the dof (rather than the true number
1893  // of samples, as these are not truly independent due to
1894  // spatial covariance)
1895  X.m_values[0][0] = cov.m_values[0][0] * dof;
1896  X.m_values[1][1] = cov.m_values[1][1] * dof;
1897  X.m_values[2][2] = cov.m_values[2][2] * dof;
1898  X.m_values[0][1] = cov.m_values[0][1] * dof;
1899  X.m_values[0][2] = cov.m_values[0][2] * dof;
1900  X.m_values[1][2] = cov.m_values[1][2] * dof;
1901 
1902  // fill symmetric parts
1903  X.m_values[1][0] = X.m_values[0][1];
1904  cov.m_values[1][0] = cov.m_values[0][1];
1905  X.m_values[2][0] = X.m_values[0][2];
1906  cov.m_values[2][0] = cov.m_values[0][2];
1907  X.m_values[2][1] = X.m_values[1][2];
1908  cov.m_values[2][1] = cov.m_values[1][2];
1909 
1910  // compute and sort eigens
1912  cov, eigVectors, eigValues, true); // get eigens
1914  eigVectors,
1915  eigValues); // sort into decreasing order
1916 
1917  //----------------------------------------------------------------------------------------------------
1918  // Compute the trend and plunge of the best-fit plane (based
1919  // entirely on the eigensystem). These values will be the
1920  // maxima of the wishart likelihood distribution and are
1921  // used to efficiently estimate the maxima a-postiori. This
1922  // will be incorrect where we are at the low-point in the
1923  // prior, but it doesn't matter that much....
1924  //----------------------------------------------------------------------------------------------------
1925 
1926  // calculate trend and plunge of 3rd eigenvector (this
1927  // represents the "best-fit-plane").
1928  phi = atan2(
1929  eigVectors.m_values[0][2],
1930  eigVectors.m_values[1][2]); // trend of the third
1931  // eigenvector
1932  theta = -asin(
1933  eigVectors
1934  .m_values[2][2]); // plunge of the
1935  // principal eigenvector
1936 
1937  // ensure phi and theta are in the correct domain
1938  if (theta < 0) // ensure dip angle is positive
1939  {
1940  phi = phi + (M_PI);
1941  theta = -theta;
1942  }
1943  while (phi < 0) // ensure phi ranges between 0 and 2 pi
1944  {
1945  phi += 2 * M_PI;
1946  }
1947  while (phi > 2 * M_PI) {
1948  phi -= 2 * M_PI;
1949  }
1950 
1951  // calculate third angle (alpha) defining the orientation of
1952  // the eigensystem
1953  alpha = asin(eigVectors.m_values[2][1] /
1954  cos(theta)); // alpha = arcsin(eigVector2.z /
1955  // cos(theta))
1956 
1957  // map alpha to correct domain (0 to 180 degrees)
1958  while (alpha < 0) {
1959  alpha += M_PI;
1960  }
1961  while (alpha > M_PI) {
1962  alpha -= M_PI;
1963  }
1964 
1965  // compute log-likelihood of this plane estimate
1966  // dof = _max - _min - 1;
1967  lsf = logWishSF(X, dof);
1968  lpd = likPower * logWishart(X, dof, phi, theta, alpha,
1969  eigValues[0], eigValues[1],
1970  eigValues[2], lsf);
1971 
1972  // multiply by prior
1973  if (hasNormals) {
1974  lpd += log(prior(phi, theta, mnx, mny, mnz));
1975  }
1976 
1977  //----------------------------------------------------------------------------
1978  // Check if this is the best observed posterior probability
1979  //----------------------------------------------------------------------------
1980  for (unsigned p = _min; p <= _max; p++) {
1981  if (lpd > bestPd[p]) // this is a better Pd
1982  {
1983  bestPd[p] = lpd;
1984  bestPhi[p] = phi;
1985  bestTheta[p] = theta;
1986  bestAlpha[p] = alpha;
1987  bestE1[p] = eigValues[0];
1988  bestE2[p] = eigValues[1];
1989  bestE3[p] = eigValues[2];
1990  sne[p] = CCVector3(eigVectors.m_values[0][2],
1991  eigVectors.m_values[1][2],
1992  eigVectors.m_values[2][2]);
1993  start[p] = _min;
1994  end[p] = _max;
1995  segmentID[p] =
1996  _max * static_cast<int>(px.size()) + _min;
1997  bestX[p] = X;
1998  normal[p] = CCVector3(mnx, mny, mnz);
1999  }
2000  }
2001  }
2002  }
2003 
2004  if (!hasValidSNE) { // if segments between pinch nodes are too
2005  // small, then we will not get any valid
2006  // fit-planes
2008  tr("[ccCompass] Warning: Region %1 contains no valid "
2009  "points (PinchNodes break the trace into small "
2010  "segments?). Region ignored.")
2011  .arg(regions[r]->getUniqueID()),
2013  delete points[r];
2014  points[r] = nullptr;
2015  regions[r] = nullptr;
2016  continue;
2017  }
2018 
2019  // #################################################################################################################
2020  // STORE SNE ESTIMATES ON CLOUD
2021  // ##################################################################################################################
2022  points[r]->setName("SNE");
2023 
2024  // build scalar fields
2025  cloudViewer::ScalarField* startSF = points[r]->getScalarField(
2026  points[r]->addScalarField(new ccScalarField("StartPoint")));
2027  cloudViewer::ScalarField* endSF = points[r]->getScalarField(
2028  points[r]->addScalarField(new ccScalarField("EndPoint")));
2029  cloudViewer::ScalarField* idSF = points[r]->getScalarField(
2030  points[r]->addScalarField(new ccScalarField("SegmentID")));
2031  cloudViewer::ScalarField* weightSF = points[r]->getScalarField(
2032  points[r]->addScalarField(new ccScalarField("Weight")));
2033  cloudViewer::ScalarField* trend = points[r]->getScalarField(
2034  points[r]->addScalarField(new ccScalarField("Trend")));
2035  cloudViewer::ScalarField* plunge = points[r]->getScalarField(
2036  points[r]->addScalarField(new ccScalarField("Plunge")));
2037  cloudViewer::ScalarField* pointID = points[r]->getScalarField(
2038  points[r]->addScalarField(new ccScalarField(
2039  "PointID"))); // used for linking samples
2040  // representing the same point
2041 
2042  weightSF->reserve(px.size());
2043  startSF->reserve(px.size());
2044  endSF->reserve(px.size());
2045  idSF->reserve(px.size());
2046  trend->reserve(px.size());
2047  plunge->reserve(px.size());
2048  pointID->reserve(px.size());
2049 
2050  // store best-guess (maximum a-postiori) surface normal estimates
2051  for (unsigned p = 0; p < px.size(); p++) {
2052  points[r]->setPointNormal(pid[p], sne[p]);
2053  weightSF->setValue(pid[p], bestPd[p]);
2054  startSF->setValue(pid[p], start[p]);
2055  endSF->setValue(pid[p], end[p]);
2056  idSF->setValue(pid[p], segmentID[p]);
2057  trend->setValue(pid[p], bestPhi[p] * 180.0 / M_PI);
2058  plunge->setValue(pid[p], bestTheta[p] * 180.0 / M_PI);
2059  pointID->setValue(pid[p], pid[p]);
2060  }
2061 
2062  // compute range
2063  weightSF->computeMinAndMax();
2064  startSF->computeMinAndMax();
2065  endSF->computeMinAndMax();
2066  idSF->computeMinAndMax();
2067  trend->computeMinAndMax();
2068  plunge->computeMinAndMax();
2069  pointID->computeMinAndMax();
2070 
2071  // set weight to visible
2072  points[r]->setCurrentDisplayedScalarField(0);
2073  points[r]->showSF(true);
2074 
2075  // add cloud to object
2076  regions[r]->addChild(points[r]);
2077  m_app->addToDB(points[r], false, false, false, false);
2078 
2079  //*************************************************************************************
2080  // SAMPLE ORIENTATIONS FROM POSTERIORS FOR EACH POINTS SNE TO
2081  // PROPAGATE UNCERTAINTY
2082  //*************************************************************************************
2083  if (oversample > 1) {
2084  // build point cloud to store MCMC samples in and associated
2085  // scalar fields
2086  samples[r] = new ccSNECloud();
2087  samples[r]->setName("SNE_Samples");
2088  samples[r]->setGlobalScale(
2089  points[r]->getGlobalScale()); // copy global shift &
2090  // scale onto new point
2091  // cloud
2092  samples[r]->setGlobalShift(points[r]->getGlobalShift());
2093  samples[r]->reserve(
2094  static_cast<unsigned int>(px.size() * oversample));
2095  samples[r]->reserveTheNormsTable();
2096  cloudViewer::ScalarField* startSF =
2097  samples[r]->getScalarField(samples[r]->addScalarField(
2098  new ccScalarField("StartPoint")));
2099  cloudViewer::ScalarField* endSF =
2100  samples[r]->getScalarField(samples[r]->addScalarField(
2101  new ccScalarField("EndPoint")));
2102  cloudViewer::ScalarField* idSF =
2103  samples[r]->getScalarField(samples[r]->addScalarField(
2104  new ccScalarField("SegmentID")));
2105  cloudViewer::ScalarField* weightSF =
2106  samples[r]->getScalarField(samples[r]->addScalarField(
2107  new ccScalarField("Weight")));
2108  cloudViewer::ScalarField* trend = samples[r]->getScalarField(
2109  samples[r]->addScalarField(new ccScalarField("Trend")));
2110  cloudViewer::ScalarField* plunge =
2111  samples[r]->getScalarField(samples[r]->addScalarField(
2112  new ccScalarField("Plunge")));
2113  cloudViewer::ScalarField* pointID =
2114  samples[r]->getScalarField(samples[r]->addScalarField(
2115  new ccScalarField("PointID")));
2116  weightSF->reserve(px.size() * oversample);
2117  startSF->reserve(px.size() * oversample);
2118  endSF->reserve(px.size() * oversample);
2119  idSF->reserve(px.size() * oversample);
2120  trend->reserve(px.size() * oversample);
2121  plunge->reserve(px.size() * oversample);
2122  pointID->reserve(px.size() * oversample);
2123 
2124  // init random number generators
2125  std::random_device rd;
2126  std::default_random_engine generator(rd());
2127  std::normal_distribution<double> N(
2128  0.0, stride); // construct random samplers
2129  std::uniform_real_distribution<double> U(0.0, 1.0);
2130 
2131  // loop through points
2132  for (unsigned p = 0; p < px.size(); p++) {
2133  // update progress dialog
2134  prg.setInfo(tr("Processing %1 of %2 datasets: Sampling "
2135  "points...")
2136  .arg(_d + 1)
2137  .arg(datasets.size()));
2138  prg.update(100.0f * p / static_cast<float>(px.size()));
2139  if (prg.isCancelRequested()) {
2140  // cleanup
2141  delete points[r];
2142  for (int i = 0; i < pinchClouds.size(); i++) {
2143  delete pinchClouds[i];
2144  if (samples[0] != nullptr) {
2145  delete samples[0];
2146  }
2147  if (samples[1] != nullptr) {
2148  delete samples[1];
2149  }
2150  }
2151  return;
2152  }
2153 
2154  // skip stand-alone points (with no co-variance matrix)
2155  if (bestX[p].m_values == nullptr) {
2156  continue;
2157  }
2158 
2159  // calculate log scale factor for wish distribution
2160  lsf = logWishSF(bestX[p], dof);
2161 
2162  // initialise MCMC sampler at likelihood maxima (to avoid
2163  // need for burn-in period)
2164  double lpdProposed;
2165  double lpdCurrent = bestPd[p];
2166  double phi = bestPhi[p];
2167  double theta = bestTheta[p];
2168  double alpha = bestAlpha[p];
2169  double e1 = bestE1[p];
2170  double e2 = bestE2[p];
2171  double e3 = bestE3[p];
2172  double _phi, _theta, _alpha; // propsals
2173 
2174  // generate chain
2175  unsigned count = 0;
2176  unsigned iter = 0;
2177  while (count < oversample) {
2178  // generate proposed sample
2179  _phi = phi + N(generator);
2180  _theta = theta + N(generator);
2181  _alpha = alpha + N(generator);
2182 
2183  // evaluate log-likelihood of proposal
2184  lpdProposed = likPower * logWishart(bestX[p], dof, _phi,
2185  _theta, _alpha, e1,
2186  e2, e3, lsf);
2187 
2188  // apply prior?
2189  if (hasNormals) {
2190  lpdProposed += log(prior(_phi, _theta, normal[p].x,
2191  normal[p].y, normal[p].z));
2192  }
2193 
2194  // accept or reject
2195  if (log(U(generator)) <= lpdProposed - lpdCurrent) {
2196  // accept - update chain
2197  phi = _phi;
2198  theta = _theta;
2199  alpha = _alpha;
2200 
2201  // calculate normal vector
2202  CCVector3 norm(sin(phi) * cos(theta),
2203  cos(phi) * cos(theta), -sin(theta));
2204 
2205  // store sample in point cloud
2206  samples[r]->addPoint(
2207  CCVector3(px[p], py[p], pz[p]));
2208  samples[r]->addNorm(norm);
2209  weightSF->addElement(lpdProposed);
2210  startSF->addElement(start[p]);
2211  endSF->addElement(end[p]);
2212  idSF->addElement(segmentID[p]);
2213  trend->addElement(phi * 180.0 / M_PI);
2214  plunge->addElement(theta * 180.0 / M_PI);
2215  pointID->addElement(pid[p]);
2216 
2217  // move to next point
2218  lpdCurrent = lpdProposed;
2219  count++;
2220  }
2221 
2222  if (iter > 1000000 * oversample) {
2224  tr("[ccCompass] Warning - MCMC sampler "
2225  "failed so sampling will be "
2226  "incomplete."),
2228  break;
2229  }
2230  iter++;
2231  }
2232  }
2233 
2234  // get rid of excess space in arrays (if samples were not
2235  // generated in sections)
2236  samples[r]->shrinkToFit();
2237  samples[r]->normalsHaveChanged();
2238  weightSF->shrink_to_fit();
2239  startSF->shrink_to_fit();
2240  idSF->shrink_to_fit();
2241  trend->shrink_to_fit();
2242  plunge->shrink_to_fit();
2243  pointID->shrink_to_fit();
2244 
2245  // compute range
2246  weightSF->computeMinAndMax();
2247  startSF->computeMinAndMax();
2248  endSF->computeMinAndMax();
2249  idSF->computeMinAndMax();
2250  trend->computeMinAndMax();
2251  plunge->computeMinAndMax();
2252  pointID->computeMinAndMax();
2253 
2254  // set weight to visible
2255  samples[r]->setCurrentDisplayedScalarField(0);
2256  samples[r]->showSF(true);
2257 
2258  // add cloud to object
2259  regions[r]->addChild(samples[r]);
2260  m_app->addToDB(samples[r], false, false, false, false);
2261  // samples[r]->setEnabled(false); //disable by default
2262  }
2263  }
2264 
2265  // compute thicknesses if upper + lower surfaces are defined
2266  if (regions[0] != nullptr && regions[1] != nullptr &&
2267  calcThickness) // have both surfaces been defined?
2268  {
2269  if (points[0]->size() > 0 &&
2270  points[1]->size() >
2271  0) { // do both surfaces have points in them?
2272  prg.setInfo(tr("Processing %1 of %2 datasets: Estimating "
2273  "thickness...")
2274  .arg(_d + 1)
2275  .arg(datasets.size()));
2276  for (int r = 0; r < 2; r++) {
2277  // make scalar field
2278  cloudViewer::ScalarField* thickSF =
2279  points[r]->getScalarField(points[r]->addScalarField(
2280  new ccScalarField("Thickness")));
2281  thickSF->reserve(points[r]->size());
2282 
2283  // set thickness to visible scalar field
2284  points[r]->setCurrentDisplayedScalarField(
2285  points[r]->getScalarFieldIndexByName("Thickness"));
2286  points[r]->showSF(true);
2287 
2288  // create scalar field in samples point cloud
2289  cloudViewer::ScalarField* thickSF_sample = nullptr;
2290  cloudViewer::ScalarField* idSF_sample = nullptr;
2291  if (samples[r] != nullptr) {
2292  thickSF_sample = samples[r]->getScalarField(
2293  samples[r]->addScalarField(
2294  new ccScalarField("Thickness")));
2295  thickSF_sample->reserve(samples[r]->size());
2296  idSF_sample = samples[r]->getScalarField(
2297  samples[r]->getScalarFieldIndexByName(
2298  "PointID"));
2299  samples[r]->setCurrentDisplayedScalarField(
2300  samples[r]->getScalarFieldIndexByName(
2301  "Thickness"));
2302  samples[r]->showSF(true);
2303  }
2304 
2305  // figure out id of the compared surface (opposite to the
2306  // current one)
2307  int compID = 0;
2308  if (r == 0) {
2309  compID = 1;
2310  }
2311 
2312  // get octree for the picking and build picking data
2313  // structures
2314  ccOctree::Shared oct = points[compID]->getOctree();
2315  cloudViewer::ReferenceCloud* nCloud =
2316  new cloudViewer::ReferenceCloud(points[compID]);
2317  unsigned char level =
2318  oct->findBestLevelForAGivenNeighbourhoodSizeExtraction(
2319  tcDistance / 2);
2320 
2322  d = -1.0;
2323 
2324  // loop through points in this surface
2325  for (unsigned p = 0; p < points[r]->size(); p++) {
2326  // keep progress bar up to date
2327  if (r == 0) {
2328  prg.update((50.0f * p) /
2329  points[r]->size()); // first 50% from
2330  // lower surface
2331  } else {
2332  prg.update(50.0f +
2333  (50.0f *
2334  p) / points[r]->size()); // second 50%
2335  // from upper
2336  // surface
2337  }
2338  if (prg.isCancelRequested()) {
2339  // cleanup
2340  for (int i = 0; i < pinchClouds.size(); i++) {
2341  delete pinchClouds[i];
2342  }
2343  return;
2344  }
2345 
2346  // pick nearest point in opposite surface closest to
2347  // this one
2348  nCloud->clear();
2349  oct->findPointNeighbourhood(points[r]->getPoint(p),
2350  nCloud, 1, level, d);
2351 
2352  // skip points that are a long way from their opposite
2353  // neighbours
2354  if (d > tcDistance * tcDistance) {
2355  thickSF->setValue(p, -1.0);
2356 
2357  if (samples[r] != nullptr) {
2358  for (unsigned s = 0; s < samples[r]->size();
2359  s++) {
2360  if (idSF_sample->getValue(s) ==
2361  p) // find samples matching this point
2362  {
2363  thickSF_sample->setValue(s, -1.0);
2364  }
2365  }
2366  }
2367 
2368  continue;
2369  }
2370 
2371  // calculate thickness for this point pair in sne cloud
2372  // build equation of the plane
2373  PointCoordinateType pEq[4];
2374  pEq[0] = points[r]->getPointNormal(p).x;
2375  pEq[1] = points[r]->getPointNormal(p).y;
2376  pEq[2] = points[r]->getPointNormal(p).z;
2377  pEq[3] = points[r]->getPoint(p)->dot(
2378  points[r]->getPointNormal(p));
2379 
2380  // calculate point to plane distance
2383  pEq);
2384 
2385  // write thickness scalar field
2386  thickSF->setValue(p, abs(d));
2387 
2388  // flip normals so that it points in the correct
2389  // direction
2390  points[r]->setPointNormal(
2391  p, points[r]->getPointNormal(p) * (d / abs(d)));
2392 
2393  // if samples have been generated, also calculate
2394  // thicknesses for matching sets of points
2395  if (samples[r] != nullptr) {
2396  for (unsigned s = 0; s < samples[r]->size(); s++) {
2397  if (idSF_sample->getValue(s) ==
2398  p) // find samples matching this point
2399  {
2400  // calculate and store thickness
2401  PointCoordinateType pEq[4];
2402  pEq[0] = samples[r]->getPointNormal(s).x;
2403  pEq[1] = samples[r]->getPointNormal(s).y;
2404  pEq[2] = samples[r]->getPointNormal(s).z;
2405  pEq[3] = samples[r]->getPoint(s)->dot(
2406  samples[r]->getPointNormal(s));
2409  nCloud->getPoint(0), pEq);
2410  thickSF_sample->setValue(s, abs(d));
2411  samples[r]->setPointNormal(
2412  s, samples[r]->getPointNormal(s) *
2413  (d / abs(d)));
2414  }
2415  }
2416  }
2417  }
2418 
2419  // compute min and max of thickness scalar fields
2420  thickSF->computeMinAndMax();
2421  if (thickSF_sample != nullptr) {
2422  thickSF_sample->computeMinAndMax();
2423  }
2424  }
2425  }
2426  }
2427  }
2428 
2429  // cleanup
2430  for (int i = 0; i < pinchClouds.size(); i++) {
2431  delete pinchClouds[i];
2432  }
2433 
2434  // notify finish
2435  prg.stop();
2437  tr("[ccCompass] Structure normal estimation complete."),
2439 
2440  // redraw
2441  m_app->refreshAll();
2442 }
2443 
2444 // Estimate strain from Mode-I dykes and veins
2445 static double binSize = 50;
2446 static bool useExternalSNE = true;
2447 static bool buildGraphics = true;
2448 static double exag = 2.0f;
2450  //******************************
2451  // gather structure traces
2452  //******************************
2453  std::vector<ccPolyline*> lines;
2454  for (ccHObject* o : m_app->getSelectedEntities()) {
2455  // Is selected object a trace?
2456  if (ccTrace::isTrace(o) && o->isEnabled()) {
2457  lines.push_back(static_cast<ccPolyline*>(o));
2458  continue;
2459  }
2460 
2461  // Clearly not... what about it's children?
2462  ccHObject::Container objs;
2463  o->filterChildren(objs, true, CV_TYPES::POLY_LINE); // look for SNE
2464  for (ccHObject* c : objs) {
2465  if (ccTrace::isTrace(c) && c->isEnabled()) {
2466  lines.push_back(static_cast<ccPolyline*>(c));
2467  }
2468  }
2469  }
2470 
2471  // calculate bounding box of all traces
2472  float minx = std::numeric_limits<float>::max(),
2473  maxx = std::numeric_limits<float>::lowest();
2474  float miny = std::numeric_limits<float>::max(),
2475  maxy = std::numeric_limits<float>::lowest();
2476  float minz = std::numeric_limits<float>::max(),
2477  maxz = std::numeric_limits<float>::lowest();
2478 
2479  if (lines.empty()) {
2480  m_app->dispToConsole(tr("[ccCompass] Error - no traces or SNEs found "
2481  "to compute estimate strain with."),
2483  return;
2484  }
2485 
2486  // check bounds
2487  for (ccPolyline* poly : lines) {
2488  CCVector3 bbMin, bbMax;
2489  if (poly->size() > 0) // avoid (0,0,0),(0,0,0) bounding boxes...
2490  {
2491  poly->getBoundingBox(bbMin, bbMax);
2492  minx = std::min(bbMin.x, minx);
2493  maxx = std::max(bbMax.x, maxx);
2494  miny = std::min(bbMin.y, miny);
2495  maxy = std::max(bbMax.y, maxy);
2496  minz = std::min(bbMin.z, minz);
2497  maxz = std::max(bbMax.z, maxz);
2498  }
2499  }
2500 
2501  //******************************
2502  // get bin-size from user
2503  //******************************
2504  QDialog dlg(m_app->getMainWindow());
2505  QVBoxLayout* vbox = new QVBoxLayout();
2506  QLabel boxSizeLabel(tr("Voxel Size:"));
2507  QLineEdit boxSizeText(QString::number(binSize));
2508  boxSizeText.setValidator(new QDoubleValidator(
2509  0.00001, std::numeric_limits<double>::max(), 6));
2510  QCheckBox externalSNEChk(tr("Use external SNE:"));
2511  externalSNEChk.setChecked(useExternalSNE);
2512  QCheckBox buildBlocksChk(tr("Build graphics:"));
2513  buildBlocksChk.setChecked(buildGraphics);
2514  QLabel exagLabel(tr("Shape exaggeration factor:"));
2515  QLineEdit exagText(QString::number(exag));
2516  boxSizeText.setValidator(new QDoubleValidator(
2517  0.00001, std::numeric_limits<double>::max(), 6));
2518 
2519  boxSizeText.setToolTip(
2520  tr("The voxel size for computing strain. This should be large "
2521  "enough that most boxes contain SNEs."));
2522  externalSNEChk.setToolTip(
2523  tr("Use SNE orientation estimates for outside the current cell if "
2524  "none are avaliable within it."));
2525  buildBlocksChk.setToolTip(
2526  tr("Build graphic strain ellipses and grid domains. Useful for "
2527  "validation."));
2528  exagText.setToolTip(
2529  tr("Exaggerate the shape of strain ellipses for easier "
2530  "visualisation."));
2531 
2532  QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
2533  QObject::connect(&buttonBox, SIGNAL(accepted()), &dlg, SLOT(accept()));
2534  QObject::connect(&buttonBox, SIGNAL(rejected()), &dlg, SLOT(reject()));
2535  vbox->addWidget(&boxSizeLabel);
2536  vbox->addWidget(&boxSizeText);
2537  vbox->addWidget(&buildBlocksChk);
2538  vbox->addWidget(&exagLabel);
2539  vbox->addWidget(&exagText);
2540  vbox->addWidget(&externalSNEChk);
2541  vbox->addWidget(&buttonBox);
2542  dlg.setLayout(vbox);
2543 
2544  // execute dialog and get results
2545  int result = dlg.exec();
2546  if (result == QDialog::Rejected) {
2547  return; // bail!
2548  }
2549 
2550  // get values
2551  binSize = boxSizeText.text().toDouble();
2552  useExternalSNE = externalSNEChk.isChecked();
2553  buildGraphics = buildBlocksChk.isChecked();
2554  exag = exagText.text().toDouble();
2555 
2556  // cleanup
2557  dlg.close();
2558  delete vbox;
2559 
2560  // setup progress window
2561  ecvProgressDialog prg(true, m_app->getMainWindow());
2562  prg.setMethodTitle(tr("Computing strain estimates"));
2563  prg.start();
2564 
2565  //***************************************
2566  // build grid for evaluating strain within
2567  //***************************************
2568  // pad out by a bin size on each side to avoid gaps due to rounding
2569  minx -= binSize;
2570  miny -= binSize;
2571  minz -= binSize;
2572  maxx += binSize;
2573  maxy += binSize;
2574  maxz += binSize;
2575 
2576  int nx = (maxx - minx) / binSize;
2577  int ny = (maxy - miny) / binSize;
2578  int nz = (maxz - minz) / binSize;
2579 
2580  //*********************************************
2581  // Map geo-objects onto cells
2582  //*********************************************
2583  prg.setInfo(tr("Gathering GeoObjects..."));
2584  std::vector<std::unordered_set<ccGeoObject*>> geoObjectBins(
2585  nx * ny * nz, std::unordered_set<ccGeoObject*>());
2586  for (int i = 0; i < lines.size(); i++) {
2587  prg.update(100.0 * i / float(lines.size()));
2588  if (prg.isCancelRequested()) {
2589  return;
2590  }
2591 
2593  if (g != nullptr) {
2594  for (unsigned p = 0; p < lines[i]->size(); p++) {
2595  CCVector3 V = *lines[i]->getPoint(p);
2596 
2597  // compute cell this point falls in
2598  int x = (V.x - minx) / binSize;
2599  int y = (V.y - miny) / binSize;
2600  int z = (V.z - minz) / binSize;
2601  int idx = x + nx * (y + ny * z);
2602 
2603  // store reference to this geoobject
2604  if (idx < geoObjectBins.size()) {
2605  geoObjectBins[idx].insert(g);
2606  } else {
2607  // n.b. this *should* never happen!
2608  const QString message =
2609  tr("[ccCompass] Error: cell %1 is outside of mesh "
2610  "bounds (with total size = %2 [%3,%4,%5]).")
2611  .arg(idx)
2612  .arg(geoObjectBins.size())
2613  .arg(nx)
2614  .arg(ny)
2615  .arg(nz);
2616 
2619  return;
2620  }
2621  }
2622  }
2623  }
2624 
2625  //*********************************************
2626  // Loop through cells and compute strain tensor
2627  //*********************************************
2628  std::vector<int> nStructures(nx * ny * nz,
2629  0); // number of structures used to compute
2630  // the strain tensor per cell
2631  std::vector<int> nIgnored(
2632  nx * ny * nz,
2633  0); // number of structured ignored during the above calculation
2634  // (as they did not have orientation/thickness estimates)
2635  std::vector<ccPointCloud*> dataInCell(nx * ny * nz, nullptr);
2636 
2637  // init object to store blocks in
2638  ccHObject* blocks = nullptr;
2639  if (buildGraphics) {
2640  blocks = new ccHObject(tr("Blocks"));
2641  }
2642 
2643  // init strain tensors
2645  I.toIdentity();
2646  std::vector<cloudViewer::SquareMatrixd> F(
2647  nx * ny * nz,
2648  cloudViewer::SquareMatrixd(I)); // deformation gradient tensors
2649 
2650  int validCells = 0;
2651  prg.setInfo(tr("Calculating strain tensors..."));
2652  for (int x = 0; x < nx; x++) {
2653  for (int y = 0; y < ny; y++) {
2654  for (int z = 0; z < nz; z++) {
2655  int idx = x + nx * (y + ny * z);
2656  prg.update((100.0f * idx) / (nx * ny * nz));
2657  if (prg.isCancelRequested()) {
2658  delete blocks;
2659  return;
2660  }
2661 
2662  // build graphics objects. These are deleted later if no
2663  // graphics were built.
2664  dataInCell[idx] = new ccSNECloud();
2665  dataInCell[idx]->setName(tr("DataInCell"));
2666  ccScalarField* thickness = new ccScalarField("Thickness");
2667  dataInCell[idx]->addScalarField(thickness);
2668 
2669  for (ccGeoObject* g : geoObjectBins[idx]) {
2670  // calculate average dilation vector for this structure
2671  // (based on the SNEs within this cell)
2672  CCVector3 average_direction; // dilation direction
2673  double average_thickness = 0.0; // amount of dilation
2674 
2675  // TODO - write code that also tracks error/variability in
2676  // orientation/length of dilation vectors?
2677 
2678  int n_lower = 0, n_upper = 0;
2679  ccHObject::Container objs;
2680  g->filterChildren(objs, true, CV_TYPES::POINT_CLOUD,
2681  true); // look for SNE
2682  for (ccHObject* c : objs) {
2683  if (ccSNECloud::isSNECloud(c)) {
2684  ccSNECloud* s = dynamic_cast<ccSNECloud*>(c);
2685  if (s != nullptr) {
2686  // check that a thickness scalar field exists
2687  int thickSF = s->getScalarFieldIndexByName(
2688  "Thickness");
2689  if (thickSF != -1) {
2690  s->setCurrentOutScalarField(thickSF);
2691  int region =
2693  if (!(region ==
2695  region == ccGeoObject::
2696  UPPER_BOUNDARY)) {
2697  continue;
2698  }
2699 
2700  // loop through points and only pick those
2701  // that fall in this bin
2702  for (unsigned p = 0; p < s->size(); p++) {
2703  CCVector3 V = *s->getPoint(p);
2704 
2705  // compute voxel that last vertex of
2706  // this segment falls in
2707  int _x = (V.x - minx) / binSize;
2708  int _y = (V.y - miny) / binSize;
2709  int _z = (V.z - minz) / binSize;
2710  int _idx = _x + nx * (_y + ny * _z);
2711 
2712  if (_idx == idx) {
2713  // compute averages
2714  CCVector3 normal =
2715  s->getPointNormal(p);
2716  if (average_direction.norm2() ==
2717  0.0) {
2718  average_direction = normal;
2719  } else if (
2720  normal.dot(
2721  average_direction) <
2722  0) {
2723  // avoid vectors pointing in
2724  // opposite directions (as
2725  // opposite normal directions
2726  // are equivalent)
2727  average_direction +=
2728  -1 * normal;
2729  } else {
2730  average_direction += normal;
2731  }
2732  average_thickness +=
2733  s->getPointScalarValue(p);
2734 
2735  // increment counters
2736  if (region ==
2738  n_upper++;
2739  } else {
2740  n_lower++;
2741  }
2742 
2743  // write to point cloud
2744  if (buildGraphics) {
2745  dataInCell[idx]->reserve(1);
2746  thickness->reserve(1);
2747  thickness->addElement(
2749  p));
2750  dataInCell[idx]->addPoint(V);
2751  dataInCell[idx]
2752  ->reserveTheNormsTable();
2753  dataInCell[idx]->addNorm(
2754  s->getPointNormal(p));
2755  }
2756  }
2757  }
2758 
2759  if (buildGraphics) {
2760  thickness->computeMinAndMax();
2761  dataInCell[idx]
2762  ->setCurrentDisplayedScalarField(
2763  0);
2764  dataInCell[idx]->showSF(true);
2765  }
2766  }
2767  }
2768  }
2769  }
2770 
2771  // check that an upper SNE has been observed, otherwise we
2772  // ignore this structure
2773  if (n_upper == 0 && n_lower == 0) {
2774  nIgnored[idx]++;
2775  continue; // skip this structure
2776  }
2777 
2778  // compute average dilation vector
2779  average_direction.normalize();
2780  average_thickness /= (n_lower + n_upper);
2781 
2782  // increment number of structures that have contributed to
2783  // the strain in this cell and number of valid cells for
2784  // which strain can be estimated
2785  validCells++;
2786  nStructures[idx]++;
2787 
2788  // build local coordinate system relative to the dyke
2789  // opening vector (opening vector N, strike vector S, dip
2790  // vector D)
2791  CCVector3 N = average_direction;
2792  CCVector3 S = N.cross(CCVector3(0.0f, 0.0f, 1.0f));
2793  CCVector3 D = N.cross(S);
2794 
2795  // define basis matrix
2797  B.setValue(0, 0, N.x);
2798  B.setValue(1, 0, N.y);
2799  B.setValue(2, 0, N.z);
2800  B.setValue(0, 1, S.x);
2801  B.setValue(1, 1, S.y);
2802  B.setValue(2, 1, S.z);
2803  B.setValue(0, 2, D.x);
2804  B.setValue(1, 2, D.y);
2805  B.setValue(2, 2, D.z);
2806 
2807  // compute transform matrix that apply's a
2808  // scaling/stretching equal to the dyke thickness and in the
2809  // direction of its normal
2811  e.toIdentity();
2812  e.setValue(0, 0,
2813  (binSize + average_thickness) /
2814  binSize); // stretch matrix in local
2815  // coordinates
2816  cloudViewer::SquareMatrixd F_increment =
2817  B *
2818  (e * B.transposed()); // transform to global coords
2819 
2820  // apply this (multiply with) the deformation gradient
2821  // tensor N.B. The order here is important, but we don't
2822  // know the timing! Hence we need to somehow bootstrap this
2823  // to try all possibilites.
2824  F[idx] = F_increment * F[idx];
2825 
2826  // build blocks for visualisation?
2827  if (buildGraphics) {
2828  double gl16[16];
2829  B.toGlMatrix(gl16);
2830  gl16[12] = minx + (x + 0.5) * binSize;
2831  gl16[13] = miny + (y + 0.5) * binSize;
2832  gl16[14] = minz + (z + 0.5) * binSize;
2833  gl16[15] = 1.0;
2834  ccGLMatrix gl(gl16);
2835  ccGenericPrimitive* box =
2836  new ccBox(CCVector3(average_thickness,
2837  binSize / 2, binSize / 2),
2838  &gl, "BlockStrain");
2839  dataInCell[idx]->addChild(box);
2840  }
2841  }
2842  }
2843  }
2844  }
2845 
2846  // remove progress bar
2847  prg.close();
2848 
2849  //**************************************************************
2850  // store strain tensors on point cloud and build graphics
2851  //**************************************************************
2852  ccPointCloud* points = new ccPointCloud(tr("Strain"));
2853  points->setGlobalScale(
2854  lines[0]->getGlobalScale()); // copy global shift & scale from one
2855  // of the polylines (N.B. we assume
2856  // here that all features have the
2857  // same shift/scale)
2858  points->setGlobalShift(lines[0]->getGlobalShift());
2859 
2860  points->reserve(validCells);
2861  ccScalarField* nValidSF = new ccScalarField("nValid");
2862  ccScalarField* nIgnoredSF = new ccScalarField("nIgnored");
2863  ccScalarField* JSF = new ccScalarField("J");
2864  points->addScalarField(nValidSF);
2865  points->addScalarField(nIgnoredSF);
2866  points->addScalarField(JSF);
2867  nValidSF->reserve(validCells);
2868  nIgnoredSF->reserve(validCells);
2869  JSF->reserve(validCells);
2870  ccScalarField* eSF[3][3];
2871  for (int i = 0; i < 3; i++) {
2872  for (int j = 0; j < 3; j++) {
2873  eSF[i][j] =
2874  new ccScalarField(QString::asprintf("E%d%d", i + 1, j + 1)
2875  .toStdString()
2876  .c_str());
2877  eSF[i][j]->reserve(validCells);
2878  points->addScalarField(eSF[i][j]);
2879  }
2880  }
2881 
2882  ccHObject* ellipses = nullptr;
2883  ccHObject* grid = nullptr;
2884  if (buildGraphics) {
2885  ellipses = new ccHObject(tr("Ellipses"));
2886  grid = new ccHObject(tr("Grid"));
2887  points->addChild(ellipses);
2888  points->addChild(grid);
2889  points->addChild(blocks);
2890  }
2891 
2892  // loop through bins and build points/graphics where strain has been
2893  // estimated
2894  for (int x = 0; x < nx; x++) {
2895  for (int y = 0; y < ny; y++) {
2896  for (int z = 0; z < nz; z++) {
2897  int idx = x + nx * (y + ny * z);
2898  if (nStructures[idx] > 0) {
2899  // build point
2900  CCVector3 p(minx + ((x + 0.5) * binSize),
2901  miny + ((y + 0.5) * binSize),
2902  minz + ((z + 0.5) * binSize));
2903  points->addPoint(p);
2904  nValidSF->addElement(nStructures[idx]);
2905  nIgnoredSF->addElement(nIgnored[idx]);
2906 
2907  // decompose into the rotation and right-stretch
2908  cloudViewer::SquareMatrixd eigVectors;
2909  std::vector<double> eigValues;
2910  cloudViewer::SquareMatrixd B = F[idx] * F[idx].transposed();
2912  B, eigVectors, eigValues, true); // get eigens
2913 
2914  cloudViewer::SquareMatrixd U_local(3);
2915  U_local.toIdentity(); // calculate stretch matrix in local
2916  // (un-rotated coordinates)
2917  U_local.setValue(0, 0, sqrt(eigValues[0]));
2918  U_local.setValue(1, 1, sqrt(eigValues[1]));
2919  U_local.setValue(2, 2, sqrt(eigValues[2]));
2921  eigVectors.transposed() *
2922  (U_local * eigVectors); // transform back into
2923  // global coordinates
2924 
2925  // compute jacobian (volumetric strain)
2926  double J = eigValues[0] * eigValues[1] *
2927  eigValues[2]; // F[idx].computeDet();
2928  JSF->addElement(J);
2929 
2930  // store strain tensor
2931  for (int i = 0; i < 3; i++) {
2932  for (int j = 0; j < 3; j++) {
2933  eSF[i][j]->addElement(U.getValue(i, j));
2934  }
2935  }
2936 
2937  if (buildGraphics) {
2938  // compute eigens of F
2939  eigVectors.clear();
2940  eigValues.clear();
2942  F[idx], eigVectors, eigValues,
2943  true); // get eigens
2945  eigValues);
2946 
2947  // apply exaggeration to eigenvalues (exaggerate shape
2948  // of the strain ellipse)
2949  cloudViewer::SquareMatrixd transMat(3);
2950  transMat.setValue(
2951  0, 0, pow(eigValues[0] / eigValues[1], exag));
2952  transMat.setValue(
2953  1, 1, pow(eigValues[1] / eigValues[1], exag));
2954  transMat.setValue(
2955  2, 2, pow(eigValues[2] / eigValues[1], exag));
2956 
2957  // transform back into global coords
2958  transMat = eigVectors *
2959  (transMat * eigVectors.transposed());
2960  double gl16[16];
2961  transMat.toGlMatrix(gl16);
2962  gl16[12] = p.x;
2963  gl16[13] = p.y;
2964  gl16[14] = p.z;
2965  gl16[15] = 1.0; // add translation to GL matrix
2966  ccGLMatrix gl(gl16);
2967  ccGenericPrimitive* ellipse =
2968  new ccSphere(binSize / 3, &gl, "StrainEllipse");
2969  ellipse->setColor(ecvColor::blue);
2970  ellipse->showColors(true);
2971  ellipses->addChild(ellipse);
2972 
2973  // store strain tensor on the graphic for reference
2974  QVariantMap* map = new QVariantMap();
2975  map->insert("Exx", U.getValue(0, 0) - 1.0);
2976  map->insert("Exy", U.getValue(0, 1));
2977  map->insert("Exz", U.getValue(0, 2));
2978  map->insert("Eyx", U.getValue(1, 0));
2979  map->insert("Eyy", U.getValue(1, 1) - 1.0);
2980  map->insert("Eyz", U.getValue(1, 2));
2981  map->insert("Ezx", U.getValue(2, 0));
2982  map->insert("Ezy", U.getValue(2, 1));
2983  map->insert("Ezz", U.getValue(2, 2) - 1.0);
2984  map->insert("J", J);
2985  ellipse->setMetaData(*map, true);
2986 
2987  // create cubes to highlight gridding
2988  ccGLMatrix T;
2989  T.setTranslation(p);
2990  ccGenericPrimitive* box =
2992  &T, "GridCell");
2993  grid->addChild(box);
2994  box->showWired(true);
2995 
2996  // add points to this grid cell
2997  box->addChild(dataInCell[idx]);
2998  }
2999  } else // no strain estimate here - cleanup
3000  {
3001  delete dataInCell[idx];
3002  }
3003  }
3004  }
3005  }
3006 
3007  // finalize scalar fields
3008  nValidSF->computeMinAndMax();
3009  nIgnoredSF->computeMinAndMax();
3010  JSF->computeMinAndMax();
3011  for (int i = 0; i < 3; i++) {
3012  for (int j = 0; j < 3; j++) {
3013  eSF[i][j]->computeMinAndMax();
3014  }
3015  }
3016 
3017  // store & display mesh
3019  m_app->addToDB(points);
3020  points->setCurrentDisplayedScalarField(0);
3021  points->showSF(true);
3022 }
3023 
3024 // Estimate P21 intensity of selected structures
3025 static double searchR = 10;
3026 static unsigned subsample = 25;
3028  // setup point cloud to store data in
3029  ccPointCloud* cloud = new ccPointCloud();
3030  ccScalarField* weight = new ccScalarField("weight");
3031  cloud->addScalarField(weight);
3032  cloud->setCurrentScalarField(0);
3033 
3034  //******************************
3035  // gather polylines and SNEs
3036  //******************************
3037  std::vector<ccPolyline*> lines;
3038  std::vector<ccSNECloud*> sne;
3039  for (ccHObject* o : m_app->getSelectedEntities()) {
3040  // Is selected object a trace?
3041  if (ccTrace::isTrace(o)) {
3042  lines.push_back(static_cast<ccPolyline*>(o));
3043  continue;
3044  }
3045  // What about an SNE cloud?
3046  else if (ccSNECloud::isSNECloud(o)) {
3047  ccSNECloud* s = dynamic_cast<ccSNECloud*>(o);
3048  if (s != nullptr) {
3049  sne.push_back(s);
3050  }
3051  continue;
3052  }
3053 
3054  // Clearly not... what about it's children?
3055  ccHObject::Container objs;
3056  o->filterChildren(objs, true, CV_TYPES::POINT_CLOUD); // look for SNE
3057  for (ccHObject* c : objs) {
3058  if (ccSNECloud::isSNECloud(c)) {
3059  ccSNECloud* s = dynamic_cast<ccSNECloud*>(c);
3060  if (s != nullptr) {
3061  sne.push_back(s);
3062  }
3063  }
3064  }
3065 
3066  objs.clear();
3067  o->filterChildren(objs, true, CV_TYPES::POLY_LINE); // look for SNE
3068  for (ccHObject* c : objs) {
3069  if (ccTrace::isTrace(c)) {
3070  lines.push_back(static_cast<ccPolyline*>(c));
3071  }
3072  }
3073  }
3074 
3075  if (lines.empty()) {
3076  m_app->dispToConsole(tr("[ccCompass] Error - no polylines or traces "
3077  "found to compute P21."),
3079  return;
3080  }
3081 
3082  // get points from polylines
3083  ccPointCloud* outcrop = nullptr;
3084  for (ccPolyline* p : lines) {
3085  // if unknown, find the point cloud features have been digitised on
3086  if (outcrop == nullptr) {
3087  outcrop = dynamic_cast<ccPointCloud*>(p->getAssociatedCloud());
3088  }
3089 
3090  // check that all features have been digitised on the same feature...
3091  if (outcrop != p->getAssociatedCloud()) {
3093  tr("[ccCompass] Error - cannot calculate P21 intensity for "
3094  "structures digitised from different point clouds."),
3096  return;
3097  }
3098 
3100  p); // get the region of any associated geo-object this
3101  // polyline relates too.
3102  double w = 1.0;
3104  w = 0.5; // upper/lower boundaries only count for 0.5 as they
3105  // should be represented/counted twice.
3106  }
3107 
3108  cloud->reserve(p->size());
3109  weight->reserve(p->size());
3110  for (unsigned i = 0; i < p->size(); i++) {
3111  cloud->addPoint(*p->getPoint(i));
3112  weight->addElement(w);
3113  }
3114  }
3115 
3116  // compute octree for this cloud (for future picking)
3117  cloud->computeOctree();
3118 
3119  //******************************
3120  // get search radius from user
3121  //******************************
3122  QDialog dlg(m_app->getMainWindow());
3123  QVBoxLayout* vbox = new QVBoxLayout();
3124  QLabel boxSizeLabel(tr("Search Radius:"));
3125  QLineEdit boxSizeText(QString::number(searchR));
3126  boxSizeText.setValidator(new QDoubleValidator(
3127  0.00001, std::numeric_limits<double>::max(), 6));
3128  QLabel subsampleLabel(tr("Subsample:"));
3129  QLineEdit subsampleText(QString::number(subsample));
3130  boxSizeText.setValidator(
3131  new QIntValidator(1, std::numeric_limits<int>::max()));
3132 
3133  boxSizeText.setToolTip(
3134  tr("The search radius used to define the region to compute P21 "
3135  "within."));
3136  subsampleText.setToolTip(
3137  tr("Only sample P21 on the each n'th point in the original outcrop "
3138  "model (decreases calculation time)."));
3139 
3140  QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
3141  QObject::connect(&buttonBox, SIGNAL(accepted()), &dlg, SLOT(accept()));
3142  QObject::connect(&buttonBox, SIGNAL(rejected()), &dlg, SLOT(reject()));
3143  vbox->addWidget(&boxSizeLabel);
3144  vbox->addWidget(&boxSizeText);
3145  vbox->addWidget(&subsampleLabel);
3146  vbox->addWidget(&subsampleText);
3147  vbox->addWidget(&buttonBox);
3148  dlg.setLayout(vbox);
3149 
3150  // execute dialog and get results
3151  int result = dlg.exec();
3152  if (result == QDialog::Rejected) {
3153  return; // bail!
3154  }
3155 
3156  // get values
3157  searchR = boxSizeText.text().toDouble();
3158  subsample = subsampleText.text().toUInt();
3159  m_app->dispToConsole(tr("[ccCompass] Estimating P21 Intensity using a "
3160  "search radius of of %1.")
3161  .arg(searchR),
3163 
3164  // cleanup
3165  dlg.close();
3166  delete vbox;
3167 
3168  //***************************************************************************
3169  // Setup output cloud
3170  //***************************************************************************
3171  // subsample outcrop cloud
3172  ccPointCloud* outputCloud = new ccPointCloud(tr("P21 Intensity"));
3173  outputCloud->reserve(outcrop->size() / subsample);
3174  for (unsigned p = 0; p < outcrop->size(); p += subsample) {
3175  outputCloud->addPoint(*outcrop->getPoint(p));
3176  }
3177  // copy global shift
3178  outputCloud->setGlobalScale(outcrop->getGlobalScale()); // copy global
3179  // scale
3180  outputCloud->setGlobalShift(outcrop->getGlobalShift()); // copy global
3181  // shift
3182 
3183  // setup scalar fields etc
3184  ccScalarField* P21 = new ccScalarField("P21");
3185  outputCloud->addScalarField(P21);
3186  P21->reserve(outputCloud->size());
3187 
3188  //*****************************************************************************
3189  // Loop through points on outcrop and calculate trace points / outcrop
3190  // points
3191  //*****************************************************************************
3192 
3193  // get octree for the picking and build picking data structures
3194  ccOctree::Shared trace_oct = cloud->computeOctree();
3195  unsigned char trace_level =
3196  trace_oct->findBestLevelForAGivenNeighbourhoodSizeExtraction(
3197  searchR);
3198 
3199  // structure for nearest neighbors search
3201 
3202  // setup progress dialog
3203  ecvProgressDialog prg(true, m_app->getMainWindow());
3204  prg.setMethodTitle(tr("Estimating P21 Intensity"));
3205  prg.setInfo(tr("Sampling structures..."));
3206  prg.start();
3207  prg.update(0.0);
3208 
3209  // loop through points in the output cloud
3210  for (unsigned p = 0; p < outputCloud->size(); p++) {
3211  // keep progress bar up to date
3212  prg.update(100.0 * p / static_cast<float>(outputCloud->size()));
3213  if (prg.isCancelRequested()) {
3214  // cleanup
3215  delete cloud;
3216  return;
3217  }
3218 
3219  // get number of structure points in this neighbourhood
3220  region.clear();
3221  trace_oct->getPointsInSphericalNeighbourhood(
3222  *outputCloud->getPoint(p), searchR, region, trace_level);
3223 
3224  // calculate total weight (think length) of structure points by summing
3225  // weights
3226  float sum = 0;
3227  for (int i = 0; i < region.size(); i++) {
3228  sum += weight->getValue(region[i].pointIndex);
3229  }
3230 
3231  // calculate and store number of trace points within this domain
3232  P21->setValue(p, sum);
3233  }
3234 
3235  // loop through points in output cloud again, but this time calculate
3236  // surface area in regions where n_trace wasn't zero
3237  prg.setInfo(tr("Calculating patch areas..."));
3238  ccOctree::Shared outcrop_oct = outputCloud->computeOctree();
3239  int n_outcrop = 0;
3240  for (unsigned p = 0; p < outputCloud->size(); p++) {
3241  float sum = P21->getValue(p);
3242  if (sum > 0) // this domain has at least some structures, so is worth
3243  // computing patch area
3244  {
3245  // keep progress bar up to date
3246  prg.update(100.0 * p / static_cast<float>(outputCloud->size()));
3247  if (prg.isCancelRequested()) {
3248  // cleanup
3249  delete cloud;
3250  return;
3251  }
3252 
3253  // get number of structure points in this neighbourhood
3254  region.clear();
3255  n_outcrop = outcrop_oct->getPointsInSphericalNeighbourhood(
3256  *outputCloud->getPoint(p), searchR, region, trace_level);
3257 
3258  // update scalar field
3259  P21->setValue(p, sum / (n_outcrop * subsample));
3260  }
3261  }
3262 
3263  delete cloud;
3264 
3265  // finish
3266  P21->computeMinAndMax();
3267  outputCloud->setCurrentDisplayedScalarField(0);
3268  outputCloud->showSF(true);
3269  m_app->dbRootObject()->addChild(outputCloud);
3270  m_app->addToDB(outputCloud);
3271 }
3272 
3273 // converts selected traces or geoObjects to point clouds
3275  // get selected objects
3276  std::vector<ccGeoObject*> objs;
3277  std::vector<ccPolyline*> lines;
3278 
3279  for (ccHObject* o : m_app->getSelectedEntities()) {
3280  if (ccGeoObject::isGeoObject(o)) {
3281  ccGeoObject* g = dynamic_cast<ccGeoObject*>(o);
3282  if (g) // could possibly be null if non-loaded geo-objects exist
3283  {
3284  objs.push_back(g);
3285  }
3286  } else if (o->isA(CV_TYPES::POLY_LINE)) {
3287  lines.push_back(static_cast<ccPolyline*>(o));
3288  } else {
3289  // search children for geo-objects and polylines
3290  ccHObject::Container objs;
3291  o->filterChildren(objs, true,
3293  for (ccHObject* c : objs) {
3294  if (ccGeoObject::isGeoObject(c)) {
3295  ccGeoObject* g = dynamic_cast<ccGeoObject*>(c);
3296  if (g) // could possibly be null if non-loaded geo-objects
3297  // exist
3298  {
3299  objs.push_back(g);
3300  }
3301  }
3302  if (c->isA(CV_TYPES::POLY_LINE)) {
3303  lines.push_back(static_cast<ccPolyline*>(c));
3304  }
3305  }
3306  }
3307  }
3308 
3309  // convert GeoObjects
3310  for (ccGeoObject* o : objs) {
3311  // get regions
3312  ccHObject* regions[3] = {o->getRegion(ccGeoObject::INTERIOR),
3313  o->getRegion(ccGeoObject::LOWER_BOUNDARY),
3314  o->getRegion(ccGeoObject::UPPER_BOUNDARY)};
3315 
3316  // make point cloud
3318  tr("ConvertedLines")); // create point cloud for storing points
3319  int sfid = points->addScalarField(new ccScalarField(
3320  "Region")); // add scalar field containing region info
3321  cloudViewer::ScalarField* sf = points->getScalarField(sfid);
3322 
3323  // convert traces in each region
3324  int nRegions = 3;
3326  nRegions = 1; // single surface objects only have one region
3327  }
3328  for (int i = 0; i < nRegions; i++) {
3329  ccHObject* region = regions[i];
3330 
3331  // get polylines/traces
3332  ccHObject::Container poly;
3333  region->filterChildren(poly, true, CV_TYPES::POLY_LINE);
3334 
3335  for (ccHObject::Container::const_iterator it = poly.begin();
3336  it != poly.end(); it++) {
3337  ccPolyline* t = static_cast<ccPolyline*>(*it);
3338  points->setGlobalScale(
3339  t->getGlobalScale()); // copy global scale
3340  points->setGlobalShift(
3341  t->getGlobalShift()); // copy global shift
3342  points->reserve(points->size() + t->size()); // make space
3343  sf->reserve(points->size() + t->size());
3344  for (unsigned int p = 0; p < t->size(); p++) {
3345  points->addPoint(*t->getPoint(p)); // add point to cloud
3346  sf->addElement(i);
3347  }
3348  }
3349  }
3350 
3351  // save
3352  if (points->size() > 0) {
3353  sf->computeMinAndMax();
3354  points->setCurrentDisplayedScalarField(sfid);
3355  points->showSF(true);
3356 
3357  regions[2]->addChild(points);
3358  m_app->addToDB(points, false, true, false, false);
3359  } else {
3360  m_app->dispToConsole(tr("[Compass] No polylines or traces "
3361  "converted - none found."),
3363  delete points;
3364  }
3365  }
3366 
3367  // convert traces not associated with a GeoObject
3368  if (objs.empty()) {
3369  // make point cloud
3371  tr("ConvertedLines")); // create point cloud for storing points
3372  int sfid = points->addScalarField(new ccScalarField(
3373  "Region")); // add scalar field containing region info
3374  cloudViewer::ScalarField* sf = points->getScalarField(sfid);
3375  int number = 0;
3376  for (ccPolyline* t : lines) {
3377  number++;
3378  points->reserve(points->size() + t->size()); // make space
3379  sf->reserve(points->size() + t->size());
3380  for (unsigned p = 0; p < t->size(); p++) {
3381  points->addPoint(*t->getPoint(p)); // add point to cloud
3382  sf->addElement(number);
3383  }
3384  }
3385  if (points->size() > 0) {
3386  sf->computeMinAndMax();
3387  points->setCurrentDisplayedScalarField(sfid);
3388  points->showSF(true);
3389 
3391  m_app->addToDB(points, false, true, false, true);
3392  } else {
3393  delete points;
3394  }
3395  }
3396 }
3397 
3398 // distributes selected objects into GeoObjects with the same name
3400  // get selection
3402  if (selection.empty()) {
3403  m_app->dispToConsole(tr("[Compass] No objects selected."),
3405  }
3406 
3407  // build list of GeoObjects
3408  std::vector<ccGeoObject*> geoObjs;
3409  ccHObject::Container search;
3410  m_app->dbRootObject()->filterChildren(search, true,
3412  for (ccHObject* obj : search) {
3413  if (ccGeoObject::isGeoObject(obj)) {
3414  ccGeoObject* g = dynamic_cast<ccGeoObject*>(obj);
3415  if (g) {
3416  geoObjs.push_back(g);
3417  }
3418  }
3419  }
3420 
3421  // loop through selection and try to match with a GeoObject
3422  for (ccHObject* obj : selection) {
3423  // try to match name
3424  ccGeoObject* bestMatch = nullptr;
3425  int matchingChars = 0; // size of match
3426  for (ccGeoObject* g : geoObjs) {
3427  // find geoObject with biggest matching name (this avoids issues
3428  // with Object_1 and Object_11 matching)
3429  if (obj->getName().contains(
3430  g->getName())) // object name contains a GeoObject name
3431  {
3432  if (g->getName().size() > matchingChars) {
3433  matchingChars = g->getName().size();
3434  bestMatch = g;
3435  }
3436  }
3437  }
3438 
3439  // was a match found?
3440  if (bestMatch) {
3441  // detach child from parent and DB Tree
3442  m_app->removeFromDB(obj, false);
3443 
3444  // look for upper or low (otherwise put in interior)
3445  if (obj->getName().contains("upper")) {
3447  ->addChild(obj); // add to GeoObject upper
3448  } else if (obj->getName().contains("lower")) {
3450  ->addChild(obj); // add to GeoObject lower
3451  } else {
3452  bestMatch->getRegion(ccGeoObject::INTERIOR)
3453  ->addChild(obj); // add to GeoObject interior
3454  }
3455 
3456  // deselect and update
3457  obj->setSelected(false);
3458  m_app->addToDB(obj, false, true, false, false);
3459  } else // a best match was not found...
3460  {
3461  m_app->dispToConsole(tr("[Compass] Warning: No GeoObject could be "
3462  "found that matches %1.")
3463  .arg(obj->getName()),
3465  }
3466  }
3467 
3468  m_app->updateUI();
3469  m_app->refreshAll();
3470 }
3471 
3472 // recompute entirely each selected trace (useful if the cost function has
3473 // changed)
3475  ccTrace::COST_MODE = m_dlg->getCostMode(); // update cost mode
3476 
3477  for (ccHObject* obj : m_app->getSelectedEntities()) {
3478  if (ccTrace::isTrace(obj)) {
3479  ccTrace* trc = static_cast<ccTrace*>(obj);
3480  trc->recalculatePath();
3481  }
3482  }
3483 
3484  ecvDisplayTools::RedrawDisplay(); // repaint window
3485 }
3486 
3487 // recurse and hide visisble point clouds
3489  if (o->isKindOf(CV_TYPES::POINT_CLOUD) & o->isVisible()) {
3490  o->setVisible(false);
3491  m_hiddenObjects.push_back(o->getUniqueID());
3492  return;
3493  }
3494 
3495  for (unsigned i = 0; i < o->getChildrenNumber(); i++) {
3497  }
3498 }
3499 
3500 // toggle stippling
3501 void ccCompass::toggleStipple(bool checked) {
3503  checked; // change stippling for newly created planes
3505  checked); // change stippling for existing planes
3506  ecvDisplayTools::RedrawDisplay(); // redraw
3507 }
3508 
3509 void ccCompass::recurseStipple(ccHObject* object, bool checked) {
3510  // check this object
3511  if (ccFitPlane::isFitPlane(object)) {
3512  ccPlane* p = static_cast<ccPlane*>(object);
3513  p->enableStippling(checked);
3514  }
3515 
3516  // recurse
3517  for (unsigned i = 0; i < object->getChildrenNumber(); i++) {
3518  ccHObject* o = object->getChild(i);
3519  recurseStipple(o, checked);
3520  }
3521 }
3522 
3523 // toggle labels
3524 void ccCompass::toggleLabels(bool checked) {
3526  checked); // change labels for existing planes
3527  ccCompass::drawName = checked; // change labels for newly created planes
3528  ecvDisplayTools::RedrawDisplay(); // redraw
3529 }
3530 
3531 void ccCompass::recurseLabels(ccHObject* object, bool checked) {
3532  // check this object
3533  if (ccFitPlane::isFitPlane(object) | ccPointPair::isPointPair(object)) {
3534  object->showNameIn3D(checked);
3535  }
3536 
3537  // recurse
3538  for (unsigned i = 0; i < object->getChildrenNumber(); i++) {
3539  ccHObject* o = object->getChild(i);
3540  recurseLabels(o, checked);
3541  }
3542 }
3543 
3544 // toggle plane normals
3545 void ccCompass::toggleNormals(bool checked) {
3547  checked); // change labels for existing planes
3548  ccCompass::drawNormals = checked; // change labels for newly created planes
3549  ecvDisplayTools::RedrawDisplay(); // redraw
3550 }
3551 
3552 void ccCompass::recurseNormals(ccHObject* object, bool checked) {
3553  // check this object
3554  if (ccFitPlane::isFitPlane(object)) {
3555  ccPlane* p = static_cast<ccPlane*>(object);
3556  p->showNormalVector(checked);
3557  }
3558 
3559  // recurse
3560  for (unsigned i = 0; i < object->getChildrenNumber(); i++) {
3561  ccHObject* o = object->getChild(i);
3562  recurseNormals(o, checked);
3563  }
3564 }
3565 
3566 // displays the info dialog
3568  // create new qt window
3570  info.exec();
3571 }
3572 
3573 // enter or turn off map mode
3574 void ccCompass::enableMapMode() // turns on/off map mode
3575 {
3576  // m_app->dispToConsole("ccCompass: Changing to Map mode. Measurements will
3577  // be associated with GeoObjects.",
3578  // ecvMainAppInterface::STD_CONSOLE_MESSAGE);
3579  m_dlg->mapMode->setChecked(true);
3580  m_dlg->compassMode->setChecked(false);
3581 
3582  ccCompass::mapMode = true;
3583 
3584  // start gui
3585  m_app->registerOverlayDialog(m_mapDlg, Qt::Corner::TopLeftCorner);
3586  m_mapDlg->start();
3588  ecvDisplayTools::RedrawDisplay(true, true);
3589 }
3590 
3591 // enter or turn off map mode
3592 void ccCompass::enableMeasureMode() // turns on/off map mode
3593 {
3594  // m_app->dispToConsole("ccCompass: Changing to Compass mode. Measurements
3595  // will be stored in the \"Measurements\" folder.",
3596  // ecvMainAppInterface::STD_CONSOLE_MESSAGE);
3597  m_dlg->mapMode->setChecked(false);
3598  m_dlg->compassMode->setChecked(true);
3599  ccCompass::mapMode = false;
3600  ecvDisplayTools::RedrawDisplay(true, true);
3601 
3602  // turn off map mode dialog
3603  m_mapDlg->stop(true);
3606 }
3607 
3608 void ccCompass::addGeoObject(bool singleSurface) // creates a new GeoObject
3609 {
3610  // calculate default name
3611  QString name = m_lastGeoObjectName;
3612  int number = 0;
3613  if (name.contains("_")) {
3614  number = name.split("_")[1].toInt(); // counter
3615  name = name.split("_")[0]; // initial part
3616  }
3617  number++;
3618  name += QString::asprintf("_%d", number);
3619 
3620  // get name
3621  name = QInputDialog::getText(m_app->getMainWindow(), tr("New GeoObject"),
3622  tr("GeoObject Name:"), QLineEdit::Normal,
3623  name);
3624  if (name == "") // user clicked cancel
3625  {
3626  return;
3627  }
3629 
3630  // search for a "interpretation" group [where the new unit will be added]
3631  ccHObject* interp_group = nullptr;
3632  for (unsigned i = 0; i < m_app->dbRootObject()->getChildrenNumber(); i++) {
3633  if (m_app->dbRootObject()->getChild(i)->getName() ==
3634  tr("interpretation")) {
3635  interp_group = m_app->dbRootObject()->getChild(i);
3636  } else {
3637  // also search first-level children of root node (when files are
3638  // re-loaded this is where things will sit)
3639  for (unsigned c = 0;
3640  c < m_app->dbRootObject()->getChild(i)->getChildrenNumber();
3641  c++) {
3642  if (m_app->dbRootObject()
3643  ->getChild(i)
3644  ->getChild(c)
3645  ->getName() == tr("interpretation")) {
3646  interp_group =
3647  m_app->dbRootObject()->getChild(i)->getChild(c);
3648  break;
3649  }
3650  }
3651  }
3652  if (interp_group) // found one :)
3653  {
3654  break;
3655  }
3656  }
3657 
3658  // didn't find it - create a new one!
3659  if (!interp_group) {
3660  interp_group = new ccHObject(tr("interpretation"));
3661  m_app->dbRootObject()->addChild(interp_group);
3662  m_app->addToDB(interp_group, false, true, false, false);
3663  }
3664 
3665  // create the new GeoObject
3666  ccGeoObject* newGeoObject = new ccGeoObject(name, m_app, singleSurface);
3667  interp_group->addChild(newGeoObject);
3668  m_app->addToDB(newGeoObject, false, true, false, false);
3669 
3670  // set it to selected (this will then make it "active" via the selection
3671  // change callback)
3672  m_app->setSelectedInDB(newGeoObject, true);
3673 }
3674 
3676 
3677 void ccCompass::writeToInterior() // new digitization will be added to the
3678  // GeoObjects interior
3679 {
3681  m_mapDlg->setInteriorButton->setChecked(true);
3682  m_mapDlg->setUpperButton->setChecked(false);
3683  m_mapDlg->setLowerButton->setChecked(false);
3684 }
3685 
3686 void ccCompass::writeToUpper() // new digitization will be added to the
3687  // GeoObjects upper boundary
3688 {
3690  m_mapDlg->setInteriorButton->setChecked(false);
3691  m_mapDlg->setUpperButton->setChecked(true);
3692  m_mapDlg->setLowerButton->setChecked(false);
3693 }
3694 
3695 void ccCompass::writeToLower() // new digitiziation will be added to the
3696  // GeoObjects lower boundary
3697 {
3699  m_mapDlg->setInteriorButton->setChecked(false);
3700  m_mapDlg->setUpperButton->setChecked(false);
3701  m_mapDlg->setLowerButton->setChecked(true);
3702 }
3703 
3704 // convert a point cloud containing field points (x,y,z) and dip+dip-direction
3705 // scalar fields to planes for visualisation.
3707  // get selected point cloud
3708  std::vector<ccHObject*> sel = m_app->getSelectedEntities();
3709  if (sel.empty()) {
3711  tr("Please select a point cloud containing your field data "
3712  "(this can be loaded from a text file)"),
3714  return;
3715  }
3716 
3717  if (!sel[0]->isA(CV_TYPES::POINT_CLOUD)) {
3719  tr("Please select a point cloud containing your field data "
3720  "(this can be loaded from a text file)"),
3722  return;
3723  }
3724 
3725  // get point cloud object
3726  ccPointCloud* cld = static_cast<ccPointCloud*>(sel[0]);
3727 
3728  // get user to select scalar field for dip & dip-directon
3729  QDialog dlg(m_app->getMainWindow());
3730  QVBoxLayout* vbox = new QVBoxLayout();
3731  QLabel dipLabel(tr("Dip Field:"));
3732  QLabel dipDirLabel(tr("Dip-Direction Field:"));
3733  QLabel sizeLabel(tr("Plane Size"));
3734  QComboBox dipDirCombo(m_app->getMainWindow());
3735  QComboBox dipCombo(m_app->getMainWindow());
3736  QLineEdit planeSize("2.0");
3737  planeSize.setValidator(
3738  new QDoubleValidator(0.01, std::numeric_limits<double>::max(), 6));
3739 
3740  // fill combo boxes with field names
3741  // std::vector<QString> fields;
3742  // std::vector<int> idx;
3743  for (unsigned i = 0; i < cld->getNumberOfScalarFields(); i++) {
3744  dipDirCombo.addItem(cld->getScalarFieldName(i));
3745  dipCombo.addItem(cld->getScalarFieldName(i));
3746  // fields.push_back(cld->getScalarFieldName(i));
3747  // idx.push_back(i);
3748  }
3749 
3750  QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
3751  QObject::connect(&buttonBox, SIGNAL(accepted()), &dlg, SLOT(accept()));
3752  QObject::connect(&buttonBox, SIGNAL(rejected()), &dlg, SLOT(reject()));
3753 
3754  vbox->addWidget(&dipLabel);
3755  vbox->addWidget(&dipCombo);
3756  vbox->addWidget(&dipDirLabel);
3757  vbox->addWidget(&dipDirCombo);
3758  vbox->addWidget(&buttonBox);
3759  vbox->addWidget(&sizeLabel);
3760  vbox->addWidget(&planeSize);
3761  dlg.setLayout(vbox);
3762 
3763  // execute dialog and get results
3764  int result = dlg.exec();
3765  if (result == QDialog::Rejected) {
3766  return; // bail!
3767  }
3768 
3769  // get values
3770  int dipSF = cld->getScalarFieldIndexByName(
3771  dipCombo.currentText().toStdString().c_str());
3772  int dipDirSF = cld->getScalarFieldIndexByName(
3773  dipDirCombo.currentText().toStdString().c_str());
3774  double size = planeSize.text().toDouble();
3775  if (dipSF == dipDirSF) {
3776  m_app->dispToConsole(tr("Error: Dip and Dip-Direction scalar fields "
3777  "must be different!"),
3779  return;
3780  }
3781 
3782  // loop through points
3783  for (unsigned p = 0; p < cld->size(); p++) {
3784  float dip = cld->getScalarField(dipSF)->at(p);
3785  float dipdir = cld->getScalarField(dipDirSF)->at(p);
3786  CCVector3 Cd = *cld->getPoint(p);
3787 
3788  // build plane and get its orientation
3789  ccPlane* plane = new ccPlane(
3790  QString("%1/%2")
3791  .arg(static_cast<int>(dip), 2, 10, QChar('0'))
3792  .arg(static_cast<int>(dipdir), 3, 10, QChar('0')));
3793  plane->showNameIn3D(true);
3794  cld->addChild(plane);
3795  m_app->addToDB(plane, false, true, false, false);
3796  CCVector3 N = plane->getNormal();
3797  CCVector3 C = plane->getCenter();
3798 
3799  // figure out transform (blatantly stolen from
3800  // ccPlaneEditDlg::updatePlane())
3801  CCVector3 Nd =
3803  ccGLMatrix trans;
3804  bool needToApplyTrans = false;
3805  bool needToApplyRot = false;
3806 
3807  needToApplyRot = (fabs(N.dot(Nd) - PC_ONE) >
3808  std::numeric_limits<PointCoordinateType>::epsilon());
3809  needToApplyTrans = needToApplyRot || ((C - Cd).norm2d() != 0);
3810 
3811  if (needToApplyTrans) {
3812  trans.setTranslation(-C);
3813  needToApplyTrans = true;
3814  }
3815  if (needToApplyRot) {
3816  ccGLMatrix rotation;
3817  // special case: plane parallel to XY
3818  if (fabs(N.z) >
3819  PC_ONE - std::numeric_limits<PointCoordinateType>::epsilon()) {
3820  ccGLMatrix rotX;
3822  CCVector3(1, 0, 0),
3823  CCVector3(0, 0, 0)); // plunge
3824  ccGLMatrix rotZ;
3826  CCVector3(0, 0, -1),
3827  CCVector3(0, 0, 0));
3828  rotation = rotZ * rotX;
3829  } else // general case
3830  {
3831  rotation = ccGLMatrix::FromToRotation(N, Nd);
3832  }
3833  trans = rotation * trans;
3834  }
3835  if (needToApplyTrans) {
3836  trans.setTranslation(trans.getTranslationAsVec3D() + Cd);
3837  }
3838  if (needToApplyRot || needToApplyTrans) {
3839  plane->applyGLTransformation_recursive(&trans);
3840 
3841  // CVLog::Print("[Plane edit] Applied transformation matrix:");
3842  // CVLog::Print(trans.toString(12, ' ')); //full precision
3843  }
3844  plane->setXWidth(size, false);
3845  plane->setYWidth(size, true);
3846  }
3847 }
3848 
3849 // convert a point cloud containing field points (x,y,z) and trend + plunge
3850 // scalar fields to lineation vectors for visualisation.
3852  // get selected point cloud
3853  std::vector<ccHObject*> sel = m_app->getSelectedEntities();
3854  if (sel.empty()) {
3856  tr("Please select a point cloud containing your field data "
3857  "(this can be loaded from a text file)"),
3859  return;
3860  }
3861 
3862  if (!sel[0]->isA(CV_TYPES::POINT_CLOUD)) {
3864  tr("Please select a point cloud containing your field data "
3865  "(this can be loaded from a text file)"),
3867  return;
3868  }
3869 
3870  // get point cloud object
3871  ccPointCloud* cld = static_cast<ccPointCloud*>(sel[0]);
3872 
3873  // get user to select scalar field for dip & dip-directon
3874  QDialog dlg(m_app->getMainWindow());
3875  QVBoxLayout* vbox = new QVBoxLayout();
3876  QLabel dipLabel(tr("Trend Field:"));
3877  QLabel dipDirLabel(tr("Plunge Field:"));
3878  QLabel sizeLabel(tr("Display Length"));
3879  QComboBox dipDirCombo(m_app->getMainWindow());
3880  QComboBox dipCombo(m_app->getMainWindow());
3881  QLineEdit planeSize("2.0");
3882  planeSize.setValidator(
3883  new QDoubleValidator(0.01, std::numeric_limits<double>::max(), 6));
3884 
3885  // fill combo boxes with field names
3886  for (unsigned i = 0; i < cld->getNumberOfScalarFields(); i++) {
3887  dipDirCombo.addItem(cld->getScalarFieldName(i));
3888  dipCombo.addItem(cld->getScalarFieldName(i));
3889  }
3890 
3891  QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
3892  QObject::connect(&buttonBox, SIGNAL(accepted()), &dlg, SLOT(accept()));
3893  QObject::connect(&buttonBox, SIGNAL(rejected()), &dlg, SLOT(reject()));
3894 
3895  vbox->addWidget(&dipLabel);
3896  vbox->addWidget(&dipCombo);
3897  vbox->addWidget(&dipDirLabel);
3898  vbox->addWidget(&dipDirCombo);
3899  vbox->addWidget(&buttonBox);
3900  vbox->addWidget(&sizeLabel);
3901  vbox->addWidget(&planeSize);
3902  dlg.setLayout(vbox);
3903 
3904  // execute dialog and get results
3905  int result = dlg.exec();
3906  if (result == QDialog::Rejected) {
3907  return; // bail!
3908  }
3909 
3910  // get values
3911  int dipSF = cld->getScalarFieldIndexByName(
3912  dipCombo.currentText().toStdString().c_str());
3913  int dipDirSF = cld->getScalarFieldIndexByName(
3914  dipDirCombo.currentText().toStdString().c_str());
3915  double size = planeSize.text().toDouble();
3916  if (dipSF == dipDirSF) {
3918  tr("Error: Trend and plunge scalar fields must be different!"),
3920  return;
3921  }
3922 
3923  // loop through points
3924  for (unsigned p = 0; p < cld->size(); p++) {
3925  float trend = cld->getScalarField(dipSF)->at(p);
3926  float plunge = cld->getScalarField(dipDirSF)->at(p);
3927  CCVector3 Cd = *cld->getPoint(p);
3928 
3929  // build lineation vector
3931  cos(cloudViewer::DegreesToRadians(plunge)),
3932  cos(cloudViewer::DegreesToRadians(trend)) *
3933  cos(cloudViewer::DegreesToRadians(plunge)),
3934  -sin(cloudViewer::DegreesToRadians(plunge)));
3935 
3936  // create new point cloud to associate with lineation graphic
3937  ccPointCloud* points = new ccPointCloud();
3938  points->setGlobalScale(
3939  cld->getGlobalScale()); // copy global shift & scale onto new
3940  // point cloud
3941  points->setGlobalShift(cld->getGlobalShift());
3942  points->reserve(2);
3943  points->addPoint(Cd);
3944  points->addPoint(Cd + l * size);
3945  points->setName(tr("verts"));
3946 
3947  // create lineation graphic
3948  ccLineation* lineation = new ccLineation(points);
3949  lineation->addChild(points);
3950  lineation->addPointIndex(0);
3951  lineation->addPointIndex(1);
3952  lineation->updateMetadata();
3953  lineation->setName(QStringLiteral("%1->%2")
3954  .arg(qRound(plunge))
3955  .arg(qRound(trend)));
3956  cld->addChild(lineation);
3957  m_app->addToDB(lineation, false, true, false, false);
3958  }
3959 }
3960 
3961 // save the current view to an SVG file
3963  float zoom = 2.0f; // TODO: create popup box
3964 
3965  // get filename for the svg file
3966  QString filename = QFileDialog::getSaveFileName(
3967  m_dlg, tr("SVG Output file"), "", tr("SVG files (*.svg)"));
3968  if (filename.isEmpty()) {
3969  // process cancelled by the user
3970  return;
3971  }
3972 
3973  if (QFileInfo(filename).suffix() != "svg") {
3974  filename += ".svg";
3975  }
3976 
3977  // set all objects except the point clouds invisible
3978  std::vector<ccHObject*>
3979  hidden; // store objects we hide so we can turn them back on after!
3980  ccHObject::Container objects;
3982  false); // get list of all children!
3983  for (ccHObject* o : objects) {
3984  if (!o->isA(CV_TYPES::POINT_CLOUD)) {
3985  if (o->isVisible()) {
3986  hidden.push_back(o);
3987  o->setVisible(false);
3988  }
3989  }
3990  }
3991 
3992  // render the scene
3993  // QImage img = m_app->getActiveWindow()->renderToImage(zoom);
3994 
3995  // restore visibility
3996  for (ccHObject* o : hidden) {
3997  o->setVisible(true);
3998  }
3999 
4000  // convert image to base64 (png format) to write in svg file
4001  QByteArray ba;
4002  QBuffer bu(&ba);
4003  bu.open(QIODevice::WriteOnly);
4004  // img.save(&bu, "PNG");
4005  bu.close();
4006 
4007  // create .svg file
4008  QFile svg_file(filename);
4009  // open file & create text stream
4010  if (svg_file.open(QIODevice::WriteOnly)) {
4011  QTextStream svg_stream(&svg_file);
4012 
4013  int width = std::abs(static_cast<int>(
4015  zoom)); // glWidth and glHeight are negative on some machines??
4016  int height =
4017  std::abs(static_cast<int>(ecvDisplayTools::GlHeight() * zoom));
4018 
4019  // write svg header
4020  svg_stream << QString::asprintf("<svg width=\"%d\" height=\"%d\">",
4021  width, height)
4022  << QtCompat::endl;
4023 
4024  // write the image
4025  svg_stream << QString::asprintf(
4026  "<image height = \"%d\" width = \"%d\" "
4027  "xlink:href = \"data:image/png;base64,",
4028  height, width)
4029  << ba.toBase64() << "\"/>" << QtCompat::endl;
4030 
4031  // recursively write traces
4032  int count = writeTracesSVG(m_app->dbRootObject(), &svg_stream, height,
4033  zoom);
4034 
4035  // TODO: write scale bar
4036 
4037  // write end tag for svg file
4038  svg_stream << "</svg>" << QtCompat::endl;
4039 
4040  // close file
4041  svg_stream.flush();
4042  svg_file.close();
4043 
4044  if (count > 0) {
4045  m_app->dispToConsole(tr("[ccCompass] Successfully saved %1 "
4046  "polylines to .svg file.")
4047  .arg(count));
4048  } else {
4049  // remove file
4050  svg_file.remove();
4051  m_app->dispToConsole(tr("[ccCompass] Could not write polylines to "
4052  ".svg - no polylines found!"),
4054  }
4055  }
4056 }
4057 
4059  QTextStream* out,
4060  int height,
4061  float zoom) {
4062  int n = 0;
4063 
4064  // is this a drawable polyline?
4065  if (object->isA(CV_TYPES::POLY_LINE) || ccTrace::isTrace(object)) {
4066  // get polyline object
4067  ccPolyline* line = static_cast<ccPolyline*>(object);
4068 
4069  if (!line->isVisible()) {
4070  return 0; // as soon as something is not visible we bail
4071  }
4072 
4073  // write polyline header
4074  *out << "<polyline fill=\"none\" stroke=\"black\" points=\"";
4075 
4076  // get projection params
4079  if (params.perspective) {
4081  // m_app->getActiveWindow()->redraw(false, false); //not sure if
4082  // this is needed or not?
4084  params); // get updated params
4085  }
4086 
4087  // write point string
4088  for (unsigned i = 0; i < line->size(); i++) {
4089  // get point in world coordinates
4090  CCVector3 P = *line->getPoint(i);
4091 
4092  // project 3D point into 2D
4093  CCVector3d coords2D;
4094  params.project(P, coords2D);
4095 
4096  // write point
4097  *out << QString::asprintf(
4098  "%.3f,%.3f ", coords2D.x * zoom,
4099  height - (coords2D.y *
4100  zoom)); // n.b. we need to flip y-axis
4101  }
4102 
4103  // end polyline
4104  *out << "\"/>" << QtCompat::endl;
4105 
4106  n++; // a polyline has been written
4107  }
4108 
4109  // recurse on children
4110  for (unsigned i = 0; i < object->getChildrenNumber(); i++) {
4111  n += writeTracesSVG(object->getChild(i), out, height, zoom);
4112  }
4113 
4114  return n;
4115 }
4116 
4117 // export interpretations to csv or xml
4119  // get output file path
4120  QString filename = QFileDialog::getSaveFileName(
4121  m_dlg, tr("Output file"), "",
4122  tr("CSV files (*.csv *.txt);XML (*.xml)"));
4123  if (filename.isEmpty()) {
4124  // process cancelled by the user
4125  return;
4126  }
4127 
4128  // is this an xml file?
4129  QFileInfo fi(filename);
4130  if (fi.suffix() == "xml") {
4131  writeToXML(filename); // write xml file
4132  return;
4133  }
4134 
4135  // otherwise write a whole bunch of .csv files
4136 
4137  int planes = 0; // keep track of how many objects are being written (used
4138  // to delete empty files)
4139  int traces = 0;
4140  int lineations = 0;
4141  int thicknesses = 0;
4142 
4143  /*
4144  QString filename = QFileDialog::getSaveFileName(m_dlg, tr("Output file"),
4145  "", tr("XML files (*.xml *.txt)"));
4146  */
4147  // build filenames
4148 
4149  QString baseName = fi.absolutePath() + "/" + fi.completeBaseName();
4150  QString ext = fi.suffix();
4151  if (!ext.isEmpty()) {
4152  ext.prepend('.');
4153  }
4154  QString plane_fn = baseName + "_planes" + ext;
4155  QString trace_fn = baseName + "_traces" + ext;
4156  QString lineation_fn = baseName + "_lineations" + ext;
4157  QString thickness_fn = baseName + "_thickness" + ext;
4158 
4159  // create files
4160  QFile plane_file(plane_fn);
4161  QFile trace_file(trace_fn);
4162  QFile lineation_file(lineation_fn);
4163  QFile thickness_file(thickness_fn);
4164 
4165  // open files
4166  if (plane_file.open(QIODevice::WriteOnly) &&
4167  trace_file.open(QIODevice::WriteOnly) &&
4168  lineation_file.open(QIODevice::WriteOnly) &&
4169  thickness_file.open(QIODevice::WriteOnly)) {
4170  // create text streams for each file
4171  QTextStream plane_stream(&plane_file);
4172  QTextStream trace_stream(&trace_file);
4173  QTextStream lineation_stream(&lineation_file);
4174  QTextStream thickness_stream(&thickness_file);
4175 
4176  // write headers
4177  plane_stream << "Name,Strike,Dip,Dip_Dir,Cx,Cy,Cz,Nx,Ny,Nz,Sample_"
4178  "Radius,RMS,Gx,Gy,Gz,Length"
4179  << QtCompat::endl;
4180  trace_stream << "Name,Trace_id,Point_id,Start_x,Start_y,Start_z,End_x,"
4181  "End_y,End_z,Cost,Cost_Mode"
4182  << QtCompat::endl;
4183  lineation_stream << "Name,Sx,Sy,Sz,Ex,Ey,Ez,Trend,Plunge,Length"
4184  << QtCompat::endl;
4185  thickness_stream << "Name,Sx,Sy,Sz,Ex,Ey,Ez,Trend,Plunge,Thickness"
4186  << QtCompat::endl;
4187 
4188  // write data for all objects in the db tree (n.b. we loop through the
4189  // dbRoots children rathern than just passing db_root so the naming is
4190  // correct)
4191  for (unsigned i = 0; i < m_app->dbRootObject()->getChildrenNumber();
4192  i++) {
4193  ccHObject* o = m_app->dbRootObject()->getChild(i);
4194  planes += writePlanes(o, &plane_stream);
4195  traces += writeTraces(o, &trace_stream);
4196  lineations +=
4197  writeLineations(o, &lineation_stream, QString(), false);
4198  thicknesses +=
4199  writeLineations(o, &thickness_stream, QString(), true);
4200  }
4201 
4202  // cleanup
4203  plane_stream.flush();
4204  plane_file.close();
4205  trace_stream.flush();
4206  trace_file.close();
4207  lineation_stream.flush();
4208  lineation_file.close();
4209  thickness_stream.flush();
4210  thickness_file.close();
4211 
4212  // ensure data has been written (and if not, delete the file)
4213  if (planes) {
4215  tr("[ccCompass] Successfully exported plane data."),
4217  } else {
4218  m_app->dispToConsole(tr("[ccCompass] No plane data found."),
4220  plane_file.remove();
4221  }
4222  if (traces) {
4224  tr("[ccCompass] Successfully exported trace data."),
4226  } else {
4227  m_app->dispToConsole(tr("[ccCompass] No trace data found."),
4229  trace_file.remove();
4230  }
4231  if (lineations) {
4233  tr("[ccCompass] Successfully exported lineation data."),
4235  } else {
4236  m_app->dispToConsole(tr("[ccCompass] No lineation data found."),
4238  lineation_file.remove();
4239  }
4240  if (thicknesses) {
4242  tr("[ccCompass] Successfully exported thickness data."),
4244  } else {
4245  m_app->dispToConsole(tr("[ccCompass] No thickness data found."),
4247  thickness_file.remove();
4248  }
4249  } else {
4250  m_app->dispToConsole(tr("[ccCompass] Could not open output files... "
4251  "ensure CC has write access to this location."),
4253  }
4254 }
4255 
4256 // write plane data
4258  QTextStream* out,
4259  const QString& parentName) {
4260  // get object name
4261  QString name;
4262  if (parentName.isEmpty()) {
4263  name = QString("%1").arg(object->getName());
4264  } else {
4265  name = QString("%1.%2").arg(parentName, object->getName());
4266  }
4267 
4268  // find point cloud (biggest in project) to pull global shift & scale from
4269  // n.b. ccPlanes do not store a global shift/scale like point clouds do,
4270  // hence this hack. Will only cause issues if CC is being used with multiple
4271  // point clouds that have different underlying coordinate systems (and hence
4272  // shift and scales) - which I can't see happening too often. (in any case,
4273  // 99% of the time the biggest point cloud in the project will be the model
4274  // being interpreted)
4275  ccPointCloud* ss = nullptr;
4276  if (object->isKindOf(CV_TYPES::PLANE) | ccFitPlane::isFitPlane(object)) {
4277  std::vector<ccHObject*> clouds;
4278  m_app->dbRootObject()->filterChildren(clouds, true,
4279  CV_TYPES::POINT_CLOUD, false);
4280  unsigned int npoints = 0;
4281  for (ccHObject* o : clouds) {
4282  ccPointCloud* p = static_cast<ccPointCloud*>(o);
4283  if (npoints <= p->size()) {
4284  npoints = p->size();
4285  ss = p;
4286  }
4287  }
4288  }
4289 
4290  // is object a plane made by ccCompass?
4291  int n = 0;
4292  if (ccFitPlane::isFitPlane(object)) {
4293  // write global position
4294  ccPlane* P = static_cast<ccPlane*>(object);
4295 
4296  // Write object as
4297  // Name,Strike,Dip,Dip_Dir,Cx,Cy,Cz,Nx,Ny,Nz,Radius,RMS,Gx,Gy,Gz,Length
4298  *out << name << ",";
4299  *out << object->getMetaData("Strike").toString() << ","
4300  << object->getMetaData("Dip").toString() << ","
4301  << object->getMetaData("DipDir").toString() << ",";
4302  *out << object->getMetaData("Cx").toString() << ","
4303  << object->getMetaData("Cy").toString() << ","
4304  << object->getMetaData("Cz").toString() << ",";
4305  *out << object->getMetaData("Nx").toString() << ","
4306  << object->getMetaData("Ny").toString() << ","
4307  << object->getMetaData("Nz").toString() << ",";
4308  *out << object->getMetaData("Radius").toString() << ","
4309  << object->getMetaData("RMS").toString() << ",";
4310 
4311  if (ss != nullptr) {
4313  CCVector3d G = ss->toGlobal3d(L);
4314 
4315  *out << G.x << "," << G.y << "," << G.z << ",";
4316  }
4317 
4318  // write length of trace associated with this plane
4319  *out << std::max(P->getXWidth(), P->getYWidth()) << QtCompat::endl;
4320  n++;
4321  } else if (object->isKindOf(
4322  CV_TYPES::PLANE)) // not one of our planes, but a plane
4323  // anyway (so we'll export it)
4324  {
4325  // calculate plane orientation
4326  // get plane normal vector
4327  ccPlane* P = static_cast<ccPlane*>(object);
4328  CCVector3 N(P->getNormal());
4330 
4331  // We always consider the normal with a positive 'Z' by default!
4332  if (N.z < 0.0) N *= -1.0;
4333 
4334  // calculate strike/dip/dip direction
4335  float strike, dip, dipdir;
4338 
4339  // export
4340  *out << name << ",";
4341  *out << strike << "," << dip << "," << dipdir
4342  << ","; // write orientation
4343  *out << L.x << "," << L.y << "," << L.z << ","; // write location
4344  *out << N.x << "," << N.y << "," << N.z << ","; // write normal
4345  *out << "NA"
4346  << ","
4347  << "UNK"
4348  << ","; // the "radius" and "RMS" are unknown
4349 
4350  // write global position
4351  if (ss != nullptr) {
4352  CCVector3d G = ss->toGlobal3d(L);
4353  *out << G.x << "," << G.y << "," << G.z << QtCompat::endl;
4354  } else {
4355  *out << QtCompat::endl;
4356  }
4357 
4358  n++;
4359  }
4360 
4361  // write all children
4362  for (unsigned i = 0; i < object->getChildrenNumber(); i++) {
4363  ccHObject* o = object->getChild(i);
4364  n += writePlanes(o, out, name);
4365  }
4366  return n;
4367 }
4368 
4369 // write trace data
4371  QTextStream* out,
4372  const QString& parentName) {
4373  // get object name
4374  QString name;
4375  if (parentName.isEmpty()) {
4376  name = QString("%1").arg(object->getName());
4377  } else {
4378  name = QString("%1.%2").arg(parentName, object->getName());
4379  }
4380 
4381  // is object a polyline
4382  int n = 0;
4383  if (ccTrace::isTrace(object)) // ensure this is a trace
4384  {
4385  ccTrace* p = static_cast<ccTrace*>(object);
4386 
4387  // loop through points
4388  CCVector3 start, end;
4389  int cost;
4390  int tID = object->getUniqueID();
4391  if (p->size() >= 2) {
4392  // set cost function
4393  ccTrace::COST_MODE = p->getMetaData("cost_function").toInt();
4394 
4395  // loop through segments
4396  for (unsigned i = 1; i < p->size(); i++) {
4397  // get points
4398  p->getPoint(i - 1, start);
4399  p->getPoint(i, end);
4400 
4401  // calculate segment cost
4402  cost = p->getSegmentCost(p->getPointGlobalIndex(i - 1),
4403  p->getPointGlobalIndex(i));
4404 
4405  // write data
4406  // n.b. csv columns are
4407  // name,trace_id,seg_id,start_x,start_y,start_z,end_x,end_y,end_z,
4408  // cost, cost_mode
4409  *out << name << ","; // name
4410  *out << tID << ",";
4411  *out << i - 1 << ",";
4412  *out << p->toGlobal3d(start).x << ",";
4413  *out << p->toGlobal3d(start).y << ",";
4414  *out << p->toGlobal3d(start).z << ",";
4415  *out << p->toGlobal3d(end).x << ",";
4416  *out << p->toGlobal3d(end).y << ",";
4417  *out << p->toGlobal3d(end).z << ",";
4418  *out << cost << ",";
4420  }
4421  }
4422  n++;
4423  }
4424 
4425  // write all children
4426  for (unsigned i = 0; i < object->getChildrenNumber(); i++) {
4427  ccHObject* o = object->getChild(i);
4428  n += writeTraces(o, out, name);
4429  }
4430  return n;
4431 }
4432 
4433 // write lineation data
4435  QTextStream* out,
4436  const QString& parentName,
4437  bool thicknesses) {
4438  // get object name
4439  QString name;
4440  if (parentName.isEmpty()) {
4441  name = QString("%1").arg(object->getName());
4442  } else {
4443  name = QString("%1.%2").arg(parentName, object->getName());
4444  }
4445 
4446  // is object a lineation made by ccCompass?
4447  int n = 0;
4448  if (((thicknesses == false) &&
4449  ccLineation::isLineation(object)) | // lineation measurement
4450  ((thicknesses == true) &&
4451  ccThickness::isThickness(object))) // or thickness measurement
4452  {
4453  // Write object as Name,Sx,Sy,Sz,Ex,Ey,Ez,Trend,Plunge
4454  *out << name << ",";
4455  *out << object->getMetaData("Sx").toString() << ","
4456  << object->getMetaData("Sy").toString() << ","
4457  << object->getMetaData("Sz").toString() << ",";
4458  *out << object->getMetaData("Ex").toString() << ","
4459  << object->getMetaData("Ey").toString() << ","
4460  << object->getMetaData("Ez").toString() << ",";
4461  *out << object->getMetaData("Trend").toString() << ","
4462  << object->getMetaData("Plunge").toString() << ","
4463  << object->getMetaData("Length").toString() << QtCompat::endl;
4464  n++;
4465  }
4466 
4467  // write all children
4468  for (unsigned i = 0; i < object->getChildrenNumber(); i++) {
4469  ccHObject* o = object->getChild(i);
4470  n += writeLineations(o, out, name, thicknesses);
4471  }
4472  return n;
4473 }
4474 
4475 int ccCompass::writeToXML(const QString& filename) {
4476  int n = 0;
4477 
4478  // open output stream
4479  QFile file(filename);
4480  if (file.open(QIODevice::WriteOnly)) // open the file
4481  {
4482  // QTextStream plane_stream(&plane_file);
4483  QXmlStreamWriter xmlWriter(&file); // open xml stream;
4484 
4485  xmlWriter.setAutoFormatting(true);
4486  xmlWriter.writeStartDocument();
4487 
4488  // find root node
4489  ccHObject* root = m_app->dbRootObject();
4490  if (root->getChildrenNumber() == 1) {
4491  root = root->getChild(
4492  0); // HACK - often the root only has one child (a .bin
4493  // file); if so, move down a level
4494  }
4495 
4496  /*ccHObject::Container pointClouds;
4497  m_app->dbRootObject()->filterChildren(&pointClouds, true,
4498  CV_TYPES::POINT_CLOUD, true);*/
4499 
4500  // write data tree
4501  n += writeObjectXML(root, &xmlWriter);
4502 
4503  // write end of document
4504  xmlWriter.writeEndDocument();
4505 
4506  // close
4507  file.flush();
4508  file.close();
4509 
4511  tr("[ccCompass] Successfully exported data-tree to xml."),
4513  } else {
4514  m_app->dispToConsole(tr("[ccCompass] Could not open output files... "
4515  "ensure CC has write access to this location."),
4517  }
4518 
4519  return n;
4520 }
4521 
4522 // recursively write the provided ccHObject and its children
4523 int ccCompass::writeObjectXML(ccHObject* object, QXmlStreamWriter* out) {
4524  int n = 1;
4525  // write object header based on type
4526  if (ccGeoObject::isGeoObject(object)) {
4527  // write GeoObject
4528  out->writeStartElement("GEO_OBJECT");
4529  } else if (object->isA(CV_TYPES::PLANE)) {
4530  // write fitPlane
4531  out->writeStartElement("PLANE");
4532  } else if (ccTrace::isTrace(object)) {
4533  // write trace
4534  out->writeStartElement("TRACE");
4535  } else if (ccThickness::isThickness(object)) {
4536  // write thickness
4537  out->writeStartElement("THICKNESS");
4538  } else if (ccSNECloud::isSNECloud(object)) {
4539  out->writeStartElement("SNE");
4540  } else if (ccLineation::isLineation(object)) {
4541  // write lineation
4542  out->writeStartElement("LINEATION");
4543  } else if (object->isA(CV_TYPES::POINT_CLOUD)) {
4544  out->writeStartElement("CLOUD");
4545  } else if (object->isA(CV_TYPES::POLY_LINE)) {
4546  // write polyline (note that this will ignore "trace" polylines as they
4547  // have been grabbed earlier)
4548  out->writeStartElement("POLYLINE");
4549  } else if (object->isA(CV_TYPES::HIERARCHY_OBJECT)) {
4550  // write container
4551  out->writeStartElement(
4552  "CONTAINER"); // QString::asprintf("CONTAINER name = '%s' id =
4553  // %d", object->getName(), object->getUniqueID())
4554  } else // we don't care about this object
4555  {
4556  return 0;
4557  }
4558 
4559  // write name and oid attributes
4560  out->writeAttribute("name", object->getName());
4561  out->writeAttribute("id", QString::asprintf("%d", object->getUniqueID()));
4562 
4563  // write metadata tags (these contain the data)
4564  for (QMap<QString, QVariant>::const_iterator it =
4565  object->metaData().begin();
4566  it != object->metaData().end(); it++) {
4567  out->writeTextElement(it.key(), it.value().toString());
4568  }
4569 
4570  // special case - we can calculate all metadata from a plane
4571  if (object->isA(CV_TYPES::PLANE)) {
4572  ccPlane* P = static_cast<ccPlane*>(object);
4573 
4574  // write length
4575  out->writeTextElement(
4576  "Length", QString::asprintf("%f", std::max(P->getXWidth(),
4577  P->getYWidth())));
4578 
4579  // if this is just an ordinary plane, make a corresponding fitplane
4580  // object and then steal metadata
4581  if (!ccFitPlane::isFitPlane(P)) {
4582  // build fitplane object
4583  ccFitPlane* temp = new ccFitPlane(P);
4584 
4585  // write metadata
4586  for (QMap<QString, QVariant>::const_iterator it =
4587  temp->metaData().begin();
4588  it != temp->metaData().end(); it++) {
4589  out->writeTextElement(it.key(), it.value().toString());
4590  }
4591 
4592  // cleanup
4593  delete temp;
4594  }
4595  }
4596 
4597  // if object is a polyline object (or a trace) write trace points and
4598  // normals
4599  if (object->isA(CV_TYPES::POLY_LINE)) {
4600  ccPolyline* poly = static_cast<ccPolyline*>(object);
4601  ccTrace* trace = nullptr;
4602  if (ccTrace::isTrace(object)) {
4603  trace = static_cast<ccTrace*>(object);
4604  }
4605 
4606  QString x, y, z, nx, ny, nz, cost, wIDs, w_local_ids;
4607 
4608  // loop through points
4609  CCVector3 p1, p2; // position
4610  CCVector3 n1, n2; // normal vector (if defined)
4611 
4612  // becomes true if any valid normals are recieved
4613  bool hasNormals = false;
4614 
4615  if (poly->size() >= 2) {
4616  // loop through segments
4617  for (unsigned i = 1; i < poly->size(); i++) {
4618  // get points
4619  poly->getPoint(i - 1, p1); // segment start point
4620  poly->getPoint(i, p2); // segment end point
4621 
4622  // store data to buffers
4623  x += QString::asprintf("%f,", p1.x);
4624  y += QString::asprintf("%f,", p1.y);
4625  z += QString::asprintf("%f,", p1.z);
4626 
4627  // write data specific to traces
4628  if (trace) {
4629  int c = trace->getSegmentCost(
4630  trace->getPointGlobalIndex(i - 1),
4631  trace->getPointGlobalIndex(i));
4632  cost += QString::asprintf("%d,", c);
4633 
4634  // write point normals (if this is a trace)
4635  n2 = trace->getPointNormal(i);
4636  nx += QString::asprintf("%f,", n1.x);
4637  ny += QString::asprintf("%f,", n1.y);
4638  nz += QString::asprintf("%f,", n1.z);
4639  if (!hasNormals && !(n1.x == 0 && n1.y == 0 && n1.z == 0)) {
4640  hasNormals =
4641  true; // this was a non-null normal estimate -
4642  // we will write normals now
4643  }
4644  }
4645  }
4646 
4647  // store last point
4648  x += QString::asprintf("%f", p2.x);
4649  y += QString::asprintf("%f", p2.y);
4650  z += QString::asprintf("%f", p2.z);
4651  if (hasNormals) // normal
4652  {
4653  nx += QString::asprintf("%f", n2.x);
4654  ny += QString::asprintf("%f", n2.y);
4655  nz += QString::asprintf("%f", n2.z);
4656  }
4657  if (trace) // cost
4658  {
4659  cost += "0";
4660  }
4661 
4662  // if this is a trace also write the waypoints
4663  if (trace) {
4664  // get ids (on the cloud) for waypoints
4665  for (int w = 0; w < trace->waypoint_count(); w++) {
4666  wIDs += QString::asprintf("%d,", trace->getWaypoint(w));
4667  }
4668 
4669  // get ids (vertex # in polyline) for waypoints
4670  for (int w = 0; w < trace->waypoint_count(); w++) {
4671  // get id of waypoint in cloud
4672  int globalID = trace->getWaypoint(w);
4673 
4674  // find corresponding point in trace
4675  unsigned i = 0;
4676  for (; i < trace->size(); i++) {
4677  if (trace->getPointGlobalIndex(i) == globalID) {
4678  break; // found it!;
4679  }
4680  }
4681 
4682  // write this points local index
4683  w_local_ids += QString::asprintf("%d,", i);
4684  }
4685  }
4686  // write points
4687  out->writeStartElement("POINTS");
4688  out->writeAttribute("count", QString::asprintf("%d", poly->size()));
4689 
4690  if (hasNormals) {
4691  out->writeAttribute("normals", "True");
4692  } else {
4693  out->writeAttribute("normals", "False");
4694  }
4695 
4696  out->writeTextElement("x", x);
4697  out->writeTextElement("y", y);
4698  out->writeTextElement("z", z);
4699 
4700  if (hasNormals) {
4701  out->writeTextElement("nx", nx);
4702  out->writeTextElement("ny", ny);
4703  out->writeTextElement("nz", nz);
4704  }
4705 
4706  if (trace) {
4707  // write waypoints
4708  out->writeTextElement("cost", cost);
4709  out->writeTextElement("control_point_cloud_ids", wIDs);
4710  out->writeTextElement("control_point_local_ids", w_local_ids);
4711  }
4712 
4713  // fin!
4714  out->writeEndElement();
4715  }
4716  }
4717 
4718  // if object is a point cloud write global shift and scale
4719  if (object->isA(CV_TYPES::POINT_CLOUD)) {
4720  ccPointCloud* cloud = static_cast<ccPointCloud*>(object);
4721  out->writeTextElement("GLOBAL_SCALE",
4722  QString::asprintf("%f", cloud->getGlobalScale()));
4723  out->writeTextElement(
4724  "GLOBAL_X", QString::asprintf("%f", cloud->getGlobalShift().x));
4725  out->writeTextElement(
4726  "GLOBAL_Y", QString::asprintf("%f", cloud->getGlobalShift().y));
4727  out->writeTextElement(
4728  "GLOBAL_Z", QString::asprintf("%f", cloud->getGlobalShift().z));
4729 
4730  // for SNE clouds write all points, point normals and scalar fields
4731  if (ccSNECloud::isSNECloud(object)) {
4732  // write header for point data
4733  out->writeStartElement("POINTS");
4734  out->writeAttribute("count",
4735  QString::asprintf("%d", cloud->size()));
4736 
4737  // gather data strings
4738  QString x, y, z, nx, ny, nz, thickness, weight, trend, plunge;
4740  cloud->getScalarFieldIndexByName("Weight"));
4741  cloudViewer::ScalarField* trendSF = cloud->getScalarField(
4742  cloud->getScalarFieldIndexByName("Trend"));
4743  cloudViewer::ScalarField* plungeSF = cloud->getScalarField(
4744  cloud->getScalarFieldIndexByName("Plunge"));
4745 
4747  cloud->getScalarFieldIndexByName("Thickness"));
4748  for (unsigned p = 0; p < cloud->size(); p++) {
4749  x += QString::asprintf("%f,", cloud->getPoint(p)->x);
4750  y += QString::asprintf("%f,", cloud->getPoint(p)->y);
4751  z += QString::asprintf("%f,", cloud->getPoint(p)->z);
4752  nx += QString::asprintf("%f,", cloud->getPointNormal(p).x);
4753  ny += QString::asprintf("%f,", cloud->getPointNormal(p).y);
4754  nz += QString::asprintf("%f,", cloud->getPointNormal(p).z);
4755  weight += QString::asprintf("%f,", wSF->getValue(p));
4756  trend += QString::asprintf("%f,", trendSF->getValue(p));
4757  plunge += QString::asprintf("%f,", plungeSF->getValue(p));
4758 
4759  if (tSF !=
4760  nullptr) // can be null if no thickness was estimated!
4761  {
4762  thickness += QString::asprintf("%f,", tSF->getValue(p));
4763  }
4764  }
4765 
4766  // write
4767  out->writeTextElement("x", x);
4768  out->writeTextElement("y", y);
4769  out->writeTextElement("z", z);
4770  out->writeTextElement("nx", nx);
4771  out->writeTextElement("ny", ny);
4772  out->writeTextElement("nz", nz);
4773  out->writeTextElement("weight", weight);
4774  out->writeTextElement("trend", trend);
4775  out->writeTextElement("plunge", plunge);
4776  if (tSF != nullptr) {
4777  out->writeTextElement("thickness", thickness);
4778  }
4779 
4780  // fin
4781  out->writeEndElement();
4782  }
4783  }
4784 
4785  // write children
4786  for (unsigned i = 0; i < object->getChildrenNumber(); i++) {
4787  n += writeObjectXML(object->getChild(i), out);
4788  }
4789 
4790  // close this object
4791  out->writeEndElement();
4792 
4793  return n;
4794 }
MouseEvent event
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
constexpr double M_PI
Pi.
Definition: CVConst.h:19
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
std::string filename
double normal[3]
int width
int size
std::string name
int height
int count
int points
void * X
Definition: SmallVector.cpp:45
cmdLineReadable * params[]
core::Tensor result
Definition: VtkUtils.cpp:76
static int dof
Definition: ccCompass.cpp:1311
static unsigned subsample
Definition: ccCompass.cpp:3026
static bool buildGraphics
Definition: ccCompass.cpp:2447
double prior(double phi, double theta, double nx, double ny, double nz)
Definition: ccCompass.cpp:1197
static unsigned int oversample
Definition: ccCompass.cpp:1307
static double likPower
Definition: ccCompass.cpp:1308
double logWishart(cloudViewer::SquareMatrixd X, int nobserved, double phi, double theta, double alpha, double e1, double e2, double e3, double lsf)
Definition: ccCompass.cpp:1234
static unsigned int maxsize
Definition: ccCompass.cpp:1304
static unsigned int minsize
Definition: ccCompass.cpp:1303
static double stride
Definition: ccCompass.cpp:1310
static double exag
Definition: ccCompass.cpp:2448
static double binSize
Definition: ccCompass.cpp:2445
static bool useExternalSNE
Definition: ccCompass.cpp:2446
static double tcDistance
Definition: ccCompass.cpp:1305
static double searchR
Definition: ccCompass.cpp:3025
double logWishSF(cloudViewer::SquareMatrixd X, int nobserved)
Definition: ccCompass.cpp:1215
static bool calcThickness
Definition: ccCompass.cpp:1309
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool ComputeEigenValuesAndVectors(const SquareMatrix &matrix, SquareMatrix &eigenVectors, EigenValues &eigenValues, bool absoluteValues=true, unsigned maxIterationCount=50)
Computes eigen vectors (and values) with the Jacobian method.
Definition: Jacobi.h:183
static bool SortEigenValuesAndVectors(SquareMatrix &eigenVectors, EigenValues &eigenValues)
Definition: Jacobi.h:333
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:417
Vector3Tpl cross(const Vector3Tpl &v) const
Cross product.
Definition: CVGeom.h:412
Box (primitive)
Definition: ecvBox.h:16
QAction * m_equivalent
Definition: ccCompassDlg.h:62
QAction * m_showStippled
Definition: ccCompassDlg.h:49
QAction * m_fitPlaneToGeoObject
Definition: ccCompassDlg.h:65
QAction * m_measure_thickness_twoPoint
Definition: ccCompassDlg.h:57
QAction * m_toPointCloud
Definition: ccCompassDlg.h:73
QAction * m_distributeSelection
Definition: ccCompassDlg.h:74
QAction * m_measure_thickness
Definition: ccCompassDlg.h:56
QAction * m_estimateNormals
Definition: ccCompassDlg.h:68
QAction * m_showNormals
Definition: ccCompassDlg.h:50
QAction * m_estimateP21
Definition: ccCompassDlg.h:69
QAction * m_mergeSelected
Definition: ccCompassDlg.h:72
QAction * m_estimateStrain
Definition: ccCompassDlg.h:71
QAction * m_toSVG
Definition: ccCompassDlg.h:82
QAction * m_follows
Definition: ccCompassDlg.h:60
QAction * m_noteTool
Definition: ccCompassDlg.h:79
QAction * m_loadLineations
Definition: ccCompassDlg.h:81
QAction * m_youngerThan
Definition: ccCompassDlg.h:59
QAction * m_pinchTool
Definition: ccCompassDlg.h:55
QAction * m_showNames
Definition: ccCompassDlg.h:51
bool planeFitMode()
QAction * m_loadFoliations
Definition: ccCompassDlg.h:80
QAction * m_recalculateFitPlanes
Definition: ccCompassDlg.h:67
QAction * m_recalculate
Definition: ccCompassDlg.h:52
void addPinchNode()
Definition: ccCompass.cpp:880
void toggleStipple(bool checked)
Definition: ccCompass.cpp:3501
void cleanupBeforeToolChange(bool autoRestartPicking=true)
Definition: ccCompass.cpp:772
bool m_picking
Definition: ccCompass.h:183
void showHelp()
Definition: ccCompass.cpp:3567
void addGeoObject(bool singleSurface=false)
Definition: ccCompass.cpp:3608
ccTool * m_activeTool
Definition: ccCompass.h:191
virtual bool eventFilter(QObject *obj, QEvent *event) override
Definition: ccCompass.cpp:725
static int costMode
Definition: ccCompass.h:264
void exportToSVG()
Definition: ccCompass.cpp:3962
bool m_active
Definition: ccCompass.h:184
void estimateStructureNormals()
Definition: ccCompass.cpp:1312
bool startMeasuring()
Definition: ccCompass.cpp:481
ccFitPlaneTool * m_fitPlaneTool
Definition: ccCompass.h:192
void setLineation()
Definition: ccCompass.cpp:809
void recurseStipple(ccHObject *object, bool checked)
Definition: ccCompass.cpp:3509
void onUndo()
Definition: ccCompass.cpp:765
ccCompassDlg * m_dlg
Definition: ccCompass.h:187
bool madeByMe(ccHObject *object)
Definition: ccCompass.cpp:759
void writeToInterior()
Definition: ccCompass.cpp:3677
static bool drawNormals
Definition: ccCompass.h:260
void pointPicked(ccHObject *entity, unsigned itemIdx, int x, int y, const CCVector3 &P)
Definition: ccCompass.cpp:675
void toggleLabels(bool checked)
Definition: ccCompass.cpp:3524
bool stopMeasuring(bool finalStop=false)
Definition: ccCompass.cpp:511
void setPlane()
Definition: ccCompass.cpp:827
void mergeGeoObjects()
Definition: ccCompass.cpp:971
void doAction()
Definition: ccCompass.cpp:176
void setThickness()
Definition: ccCompass.cpp:894
int writeToXML(const QString &filename)
Definition: ccCompass.cpp:4475
static int mapTo
Definition: ccCompass.h:268
static bool fitPlanes
Definition: ccCompass.h:263
void tryLoading()
Definition: ccCompass.cpp:315
ccHObject * getInsertPoint()
Definition: ccCompass.cpp:590
void addGeoObjectSS()
Definition: ccCompass.cpp:3675
void distributeSelection()
Definition: ccCompass.cpp:3399
void importLineations()
Definition: ccCompass.cpp:3851
ccLineationTool * m_lineationTool
Definition: ccCompass.h:194
void recalculateFitPlanes()
Definition: ccCompass.cpp:1127
int m_geoObject_id
Definition: ccCompass.h:203
ccTopologyTool * m_topologyTool
Definition: ccCompass.h:196
void setTrace()
Definition: ccCompass.cpp:845
void recurseLabels(ccHObject *object, bool checked)
Definition: ccCompass.cpp:3531
void hideAllPointClouds(ccHObject *o)
Definition: ccCompass.cpp:3488
void setFollows()
Definition: ccCompass.cpp:941
void setNote()
Definition: ccCompass.cpp:956
void writeToUpper()
Definition: ccCompass.cpp:3686
void enableMapMode()
Definition: ccCompass.cpp:3574
void stopPicking()
Definition: ccCompass.cpp:580
QAction * m_action
Definition: ccCompass.h:177
void onSave()
Definition: ccCompass.cpp:4118
int writeObjectXML(ccHObject *object, QXmlStreamWriter *out)
Definition: ccCompass.cpp:4523
void writeToLower()
Definition: ccCompass.cpp:3695
std::vector< int > m_hiddenObjects
Definition: ccCompass.h:204
void estimateP21()
Definition: ccCompass.cpp:3027
void estimateStrain()
Definition: ccCompass.cpp:2449
void recurseNormals(ccHObject *object, bool checked)
Definition: ccCompass.cpp:3552
void importFoliations()
Definition: ccCompass.cpp:3706
int writeTracesSVG(ccHObject *object, QTextStream *out, int height, float zoom)
Definition: ccCompass.cpp:4058
void toggleNormals(bool checked)
Definition: ccCompass.cpp:3545
ccGeoObject * m_geoObject
Definition: ccCompass.h:201
int writeLineations(ccHObject *object, QTextStream *out, const QString &parentName=QString(), bool thickness=false)
Definition: ccCompass.cpp:4434
void setEquivalent()
Definition: ccCompass.cpp:948
void setThickness2()
Definition: ccCompass.cpp:914
void convertToPointCloud()
Definition: ccCompass.cpp:3274
void enableMeasureMode()
Definition: ccCompass.cpp:3592
ccThicknessTool * m_thicknessTool
Definition: ccCompass.h:195
int writeTraces(ccHObject *object, QTextStream *out, const QString &parentName=QString())
Definition: ccCompass.cpp:4370
ccPinchNodeTool * m_pinchNodeTool
Definition: ccCompass.h:198
QString m_lastGeoObjectName
Definition: ccCompass.h:207
static bool drawName
Definition: ccCompass.h:258
virtual void onItemPicked(const ccPickingListener::PickedItem &pi) override
Definition: ccCompass.cpp:669
bool startPicking()
Definition: ccCompass.cpp:555
ccTraceTool * m_traceTool
Definition: ccCompass.h:193
static bool mapMode
Definition: ccCompass.h:267
ccNoteTool * m_noteTool
Definition: ccCompass.h:197
void setPick()
Definition: ccCompass.cpp:864
void fitPlaneToGeoObject()
Definition: ccCompass.cpp:1031
void onAccept()
Definition: ccCompass.cpp:752
ccMapDlg * m_mapDlg
Definition: ccCompass.h:188
static bool drawStippled
Definition: ccCompass.h:259
void setYoungerThan()
Definition: ccCompass.cpp:920
void onClose()
Definition: ccCompass.cpp:742
void recalculateSelectedTraces()
Definition: ccCompass.cpp:3474
int writePlanes(ccHObject *object, QTextStream *out, const QString &parentName=QString())
Definition: ccCompass.cpp:4257
virtual QString getName() const override
Returns (short) name (for menu entry, etc.)
virtual QString getDescription() const override
Returns long name/description (for tooltip, etc.)
virtual QIcon getIcon() const override
Returns icon.
virtual bool isVisible() const
Returns whether entity is visible or not.
virtual void setVisible(bool state)
Sets entity visibility.
virtual void showNameIn3D(bool state)
Sets whether name should be displayed in 3D.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
static ccFitPlane * Fit(cloudViewer::GenericIndexedCloudPersist *cloud, double *rms)
Definition: ccFitPlane.cpp:120
static bool isFitPlane(ccHObject *object)
Definition: ccFitPlane.cpp:99
Vector3Tpl< T > getTranslationAsVec3D() const
Returns a copy of the translation as a CCVector3.
static ccGLMatrixTpl< float > FromToRotation(const Vector3Tpl< float > &from, const Vector3Tpl< float > &to)
Creates a transformation matrix that rotates a vector to another.
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
void initFromParameters(T alpha_rad, const Vector3Tpl< T > &axis3D, const Vector3Tpl< T > &t3D)
Inits transformation from a rotation axis, an angle and a translation.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
virtual void showWired(bool state)
Sets whether mesh should be displayed as a wire or with plain facets.
void enableStippling(bool state)
Enables polygon stippling.
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
Generic primitive interface.
virtual void setColor(const ecvColor::Rgb &col)
Sets primitive color (shortcut)
virtual ccGLMatrix & getTransformation()
Returns the transformation that is currently applied to the vertices.
void setActive(bool active)
static ccGeoObject * getGeoObjectParent(ccHObject *object)
static bool isGeoObjectInterior(ccHObject *object)
static const int INTERIOR
Definition: ccGeoObject.h:54
static const int UPPER_BOUNDARY
Definition: ccGeoObject.h:55
static int getGeoObjectRegion(ccHObject *object)
ccHObject * getRegion(int mappingRegion)
Definition: ccGeoObject.cpp:73
static bool isGeoObjectLower(ccHObject *object)
static bool isGeoObject(ccHObject *object)
static bool isSingleSurfaceGeoObject(ccHObject *object)
static const int LOWER_BOUNDARY
Definition: ccGeoObject.h:56
static bool isGeoObjectUpper(ccHObject *object)
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
ccHObject * find(unsigned uniqueID)
Finds an entity in this object hierarchy.
void transferChildren(ccHObject &newParent, bool forceFatherDependent=false)
Transfer all children to another parent.
void applyGLTransformation_recursive(const ccGLMatrix *trans=nullptr)
Applies the active OpenGL transformation to the entity (recursive)
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
void detachAllChildren()
Removes a specific child.
unsigned filterChildren(Container &filteredChildren, bool recursive=false, CV_CLASS_ENUM filter=CV_TYPES::OBJECT, bool strict=false) const
Collects the children corresponding to a certain pattern.
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
void updateMetadata() override
Definition: ccLineation.cpp:20
static bool isLineation(ccHObject *obj)
QAction * m_create_geoObject
Definition: ccMapDlg.h:34
QAction * m_create_geoObjectSS
Definition: ccMapDlg.h:35
static CCVector3 ConvertDipAndDipDirToNormal(PointCoordinateType dip_deg, PointCoordinateType dipDir_deg, bool upward=true)
static void ConvertNormalToStrikeAndDip(const CCVector3 &N, PointCoordinateType &strike_deg, PointCoordinateType &dip_deg)
static void ConvertNormalToDipAndDipDir(const CCVector3 &N, PointCoordinateType &dip_deg, PointCoordinateType &dipDir_deg)
Converts a normal vector to geological 'dip direction & dip' parameters.
Definition: ccNote.h:17
static bool isNote(ccHObject *obj)
Definition: ccNote.cpp:30
const QVariantMap & metaData() const
Returns meta-data map (const only)
Definition: ecvObject.h:184
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.
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
virtual bool isEnabled() const
Returns whether the object is enabled or not.
Definition: ecvObject.h:97
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
virtual void stop(bool accepted)
Stops process/dialog.
virtual bool start()
Starts process.
virtual bool linkWith(QWidget *win)
Links the overlay dialog with a MDI window.
void removeListener(ccPickingListener *listener, bool autoStopPickingIfLast=true)
Removes a listener.
bool addListener(ccPickingListener *listener, bool exclusive=false, bool autoStartPicking=true, ecvDisplayTools::PICKING_MODE mode=ecvDisplayTools::POINT_OR_TRIANGLE_PICKING)
Adds a listener.
static bool isPinchNode(ccHObject *obj)
Definition: ccPinchNode.cpp:34
void showNormalVector(bool state)
Show normal vector.
Plane (primitive)
Definition: ecvPlane.h:18
CCVector3 getCenter() const
Returns the center.
Definition: ecvPlane.h:56
CCVector3 getNormal() const override
Returns the entity normal.
Definition: ecvPlane.h:73
void setXWidth(PointCoordinateType w, bool autoUpdate=true)
Sets 'X' width.
Definition: ecvPlane.h:61
PointCoordinateType getXWidth() const
Returns 'X' width.
Definition: ecvPlane.h:50
PointCoordinateType getYWidth() const
Returns 'Y' width.
Definition: ecvPlane.h:53
void setYWidth(PointCoordinateType h, bool autoUpdate=true)
Sets 'Y' width.
Definition: ecvPlane.h:67
virtual bool start()
Starts the plugin.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
void addNorm(const CCVector3 &N)
Pushes a normal vector on stack (shortcut)
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
void normalsHaveChanged()
Notify a modification of normals display parameters or contents.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool reserveTheNormsTable()
Reserves memory to store the compressed normals.
void clear() override
Clears the entity from all its points and features.
void setPointNormal(size_t pointIndex, const CCVector3 &N)
Sets a particular point normal (shortcut)
void shrinkToFit()
Removes unused capacity.
const CCVector3 & getPointNormal(unsigned pointIndex) const override
Returns normal corresponding to a given point.
static bool isPointPair(ccHObject *object)
Colored polyline.
Definition: ecvPolyline.h:24
void setWidth(PointCoordinateType width)
Sets the width of the line.
static bool isSNECloud(ccHObject *obj)
Definition: ccSNECloud.cpp:36
A scalar field associated to display-related parameters.
void computeMinAndMax() override
Determines the min and max values.
virtual void setGlobalScale(double scale)
CCVector3d toGlobal3d(const Vector3Tpl< T > &Plocal) const
Returns the point back-projected into the original coordinates system.
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
virtual double getGlobalScale() const
Returns the scale applied to original coordinates.
Sphere (primitive)
Definition: ecvSphere.h:16
Standard ECV plugin interface.
ecvMainAppInterface * m_app
Main application interface.
static bool TWO_POINT_MODE
static bool isThickness(ccHObject *obj)
virtual bool canUndo()
Definition: ccTool.h:58
virtual void onNewSelection(const ccHObject::Container &selectedEntities)
Definition: ccTool.h:48
virtual void toolActivated()
Definition: ccTool.h:28
virtual void undo()
Definition: ccTool.h:61
virtual void accept()
Definition: ccTool.h:52
virtual void pointPicked(ccHObject *insertPoint, unsigned itemIdx, ccHObject *pickedObject, const CCVector3 &P)
Definition: ccTool.h:35
virtual void toolDisactivated()
Definition: ccTool.h:31
virtual void cancel()
Definition: ccTool.h:55
void initializeTool(ecvMainAppInterface *app)
Definition: ccTool.h:22
static const int EQUIVALENCE
static const int YOUNGER_THAN
static const int IMMEDIATELY_FOLLOWS
static int RELATIONSHIP
bool canUndo() override
static int COST_MODE
Definition: ccTrace.h:203
ccFitPlane * fitPlane(int surface_effect_tolerance=10, float min_planarity=0.75f)
Definition: ccTrace.cpp:817
void recalculatePath()
Definition: ccTrace.cpp:252
size_t waypoint_count() const
Definition: ccTrace.h:130
static bool isTrace(ccHObject *object)
Definition: ccTrace.cpp:1102
@ DARK
Definition: ccTrace.h:195
CCVector3f getPointNormal(int pointIdx)
Definition: ccTrace.h:110
int getSegmentCost(int p1, int p2)
Definition: ccTrace.cpp:450
int getWaypoint(int n)
Definition: ccTrace.h:93
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
Definition: DgmOctree.h:133
static ScalarType computePoint2PlaneDistance(const CCVector3 *P, const PointCoordinateType *planeEquation)
Computes the (signed) distance between a point and a plane.
const CCVector3 * getLSPlaneX()
Returns best interpolating plane (Least-square) 'X' base vector.
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.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
void setCurrentScalarField(int index)
Sets both the INPUT & OUTPUT scalar field.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
const char * getScalarFieldName(int index) const
Returns the name of a specific scalar field.
ScalarType getPointScalarValue(unsigned pointIndex) const override
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
unsigned size() const override
Returns the number of points.
virtual unsigned getPointGlobalIndex(unsigned localIndex) const
virtual void clear(bool releaseMemory=false)
Clears the cloud.
const CCVector3 * getPoint(unsigned index) const override
Returns the ith point.
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
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
void toIdentity()
Sets matrix to identity.
Definition: SquareMatrix.h:452
Scalar ** m_values
The matrix rows.
Definition: SquareMatrix.h:157
void clear()
Sets all elements to 0.
Definition: SquareMatrix.h:319
SquareMatrixTpl transposed() const
Returns the transposed version of this matrix.
Definition: SquareMatrix.h:311
void setValue(unsigned row, unsigned column, Scalar value)
Sets a particular matrix value.
Definition: SquareMatrix.h:163
void toGlMatrix(float M16f[]) const
Converts a 3*3 or 4*4 matrix to an OpenGL-style float matrix (float[16])
Definition: SquareMatrix.h:601
Scalar getValue(unsigned row, unsigned column) const
Returns a particular matrix value.
Definition: SquareMatrix.h:168
static void GetGLCameraParameters(ccGLCameraParameters &params)
Returns the current OpenGL camera parameters.
static int GlWidth()
Returns the OpenGL context width.
static int GlHeight()
Returns the OpenGL context height.
static QWidget * GetCurrentScreen()
static void SetPerspectiveState(bool state, bool objectCenteredView)
Set perspective state/mode.
static void RedrawDisplay(bool only2D=false, bool forceRedraw=true)
virtual void updateUI()=0
virtual ccHObject * dbRootObject()=0
Returns DB root (as a ccHObject)
virtual QMainWindow * getMainWindow()=0
Returns main window.
virtual QWidget * getActiveWindow()=0
virtual void updateOverlayDialogsPlacement()=0
Forces the update of all registered MDI 'overlay' dialogs.
virtual void refreshAll(bool only2D=false, bool forceRedraw=true)=0
Redraws all GL windows that have the 'refresh' flag on.
virtual const ccHObject::Container & getSelectedEntities() const =0
Returns currently selected entities ("read only")
virtual void setSelectedInDB(ccHObject *obj, bool selected)=0
Selects or unselects an entity (in db tree)
virtual void addToDB(ccHObject *obj, bool updateZoom=false, bool autoExpandDBTree=true, bool checkDimensions=false, bool autoRedraw=true)=0
virtual void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE)=0
virtual void removeFromDB(ccHObject *obj, bool autoDelete=true)=0
Removes an entity from main db tree.
virtual ccPickingHub * pickingHub()
virtual void registerOverlayDialog(ccOverlayDialog *dlg, Qt::Corner pos)=0
Registers a MDI area 'overlay' dialog.
virtual void unregisterOverlayDialog(ccOverlayDialog *dlg)=0
Unregisters a MDI area 'overlay' dialog.
Graphical progress indicator (thread-safe)
virtual void stop() override
Notifies the fact that the process has ended.
virtual void update(float percent) override
Notifies the algorithm progress.
virtual void start() override
virtual void setInfo(const char *infoStr) override
Notifies some information about the ongoing process.
virtual bool isCancelRequested() override
Checks if the process should be canceled.
virtual void setMethodTitle(const char *methodTitle) override
Notifies the algorithm title.
__host__ __device__ float dot(float2 a, float2 b)
Definition: cutil_math.h:1119
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
int min(int a, int b)
Definition: cutil_math.h:53
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
int max(int a, int b)
Definition: cutil_math.h:48
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
@ HIERARCHY_OBJECT
Definition: CVTypes.h:103
@ POINT_CLOUD
Definition: CVTypes.h:104
@ POLY_LINE
Definition: CVTypes.h:112
@ PLANE
Definition: CVTypes.h:120
@ OBJECT
Definition: CVTypes.h:102
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
float DegreesToRadians(int degrees)
Convert degrees to radians.
Definition: CVMath.h:98
constexpr Rgb blue(0, 0, MAX)
OpenGL camera parameters.