ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecv2DLabel.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 "ecv2DLabel.h"
9 
10 #include "ecvBasicTypes.h"
11 #include "ecvDisplayTools.h"
12 #include "ecvFacet.h"
13 #include "ecvGenericPointCloud.h"
14 #include "ecvHObjectCaster.h"
15 #include "ecvPointCloud.h"
16 #include "ecvPolyline.h"
17 #include "ecvScalarField.h"
18 #include "ecvSphere.h"
19 
20 // Qt
21 #include <QApplication>
22 #include <QScreen>
23 #include <QSharedPointer>
24 
25 // Qt5/Qt6 Compatibility
26 #include <QtCompat.h>
27 
28 // System
29 #include <assert.h>
30 #include <string.h>
31 
32 #include <algorithm> // For std::max, std::min
33 
34 //'Delta' character
35 // static const QChar MathSymbolDelta(0x0394);
36 static const QString MathSymbolDelta = "D";
37 static const QString SEPARATOR = "-";
38 
39 // unit point marker
40 static QSharedPointer<ccSphere> c_unitPointMarker(nullptr);
41 static QSharedPointer<ccFacet> c_unitTriMarker(nullptr);
42 
43 static const QString CENTER_STRING = QObject::tr("Center");
44 static const char POINT_INDEX_0[] = "pi0";
45 static const char POINT_INDEX_1[] = "pi1";
46 static const char POINT_INDEX_2[] = "pi2";
47 static const char ENTITY_INDEX_0[] = "ei0";
48 static const char ENTITY_INDEX_1[] = "ei1";
49 static const char ENTITY_INDEX_2[] = "ei2";
50 
52  if (entityCenterPoint) {
53  QString title = CENTER_STRING;
54  if (entity()) title += QString("@%1").arg(entity()->getUniqueID());
55  return title;
56  } else {
57  return QString::number(index, 10);
58  }
59 }
60 
61 QString cc2DLabel::PickedPoint::prefix(const char* pointTag) const {
62  if (entityCenterPoint) {
63  return CENTER_STRING;
64  } else if (cloud) {
65  return QString("Point #") + pointTag;
66  } else if (mesh) {
67  return QString("Point@Tri#") + pointTag;
68  }
69 
70  assert(false);
71  return QString();
72 }
73 
75  CCVector3 P;
76 
77  if (cloud) {
78  if (entityCenterPoint) {
79  return cloud->getOwnBB().getCenter();
80  } else {
81  P = *cloud->getPointPersistentPtr(index);
82  }
83  } else if (mesh) {
84  if (entityCenterPoint) {
85  return mesh->getOwnBB().getCenter();
86  } else {
87  mesh->computePointPosition(index, uv, P);
88  }
89  } else {
90  assert(false);
91  }
92 
93  return P;
94 }
95 
97  if (cloud) return cloud->getUniqueID();
98  if (mesh) return mesh->getUniqueID();
99 
100  assert(false);
101  return 0;
102 }
103 
105  if (cloud) return cloud;
106  if (mesh) return mesh->getAssociatedCloud();
107 
108  assert(false);
109  return nullptr;
110 }
111 
113  if (cloud) return cloud;
114  if (mesh) return mesh;
115 
116  assert(false);
117  return nullptr;
118 }
119 
120 cc2DLabel::cc2DLabel(QString name /*=QString()*/)
121  : ccHObject(name.isEmpty() ? "label" : name),
122  m_showFullBody(true),
123  m_dispPointsLegend(false),
124  m_dispIn2D(true),
125  m_relMarkerScale(0.15f), // Reduced from 1.0f for better visualization -
126  // prevents sphere from obscuring points
127  m_historyMessage(QStringList()) {
128  m_screenPos[0] = m_screenPos[1] = 0.05f;
129 
130  clear(false);
131 
132  m_lineID = "labelLine-" + this->getViewId();
133  if (c_unitPointMarker) {
135  c_unitPointMarker->getViewId();
136  }
137 
138  if (c_unitTriMarker) {
139  m_surfaceIdfix = this->getViewId() + SEPARATOR +
140  c_unitTriMarker->getPolygon()->getViewId();
141  m_contourIdfix = this->getViewId() + SEPARATOR +
142  c_unitTriMarker->getContour()->getViewId();
143  }
144 
145  lockVisibility(false);
146  setEnabled(true);
147 }
148 
149 QString cc2DLabel::GetSFValueAsString(const LabelInfo1& info, int precision) {
150  if (info.hasSF) {
151  if (!ccScalarField::ValidValue(info.sfValue)) {
152  return "NaN";
153  } else {
154  QString sfVal = QString::number(info.sfValue, 'f', precision);
155  if (info.sfValueIsShifted) {
156  sfVal = QString::number(info.sfShiftedValue, 'f', precision) +
157  QString(" (shifted: %1)").arg(sfVal);
158  }
159  return sfVal;
160  }
161  } else {
162  return QString();
163  }
164 }
165 
166 QString cc2DLabel::getTitle(int precision) const {
167  QString title;
168  size_t count = m_pickedPoints.size();
169  if (count == 1) {
170  title = m_name;
171  title.replace(POINT_INDEX_0, QString::number(m_pickedPoints[0].index));
172 
173  // if available, we display the point SF value
174  LabelInfo1 info;
175  getLabelInfo1(info);
176  if (info.hasSF) {
177  QString sfVal = GetSFValueAsString(info, precision);
178  title = QString("%1 = %2").arg(info.sfName, sfVal);
179  }
180  } else if (count == 2) {
181  LabelInfo2 info;
182  getLabelInfo2(info);
183  // display distance by default
184  double dist = info.diff.normd();
185  title = QString("Distance: %1").arg(dist, 0, 'f', precision);
186  } else if (count == 3) {
187  LabelInfo3 info;
188  getLabelInfo3(info);
189  // display area by default
190  title = QString("Area: %1").arg(info.area, 0, 'f', precision);
191  }
192 
193  return title;
194 }
195 
196 QString cc2DLabel::getName() const {
197  QString processedName = m_name;
198 
199  size_t count = m_pickedPoints.size();
200  if (count > 0) {
201  processedName.replace(POINT_INDEX_0,
202  QString::number(m_pickedPoints[0].index));
203  if (count > 1) {
204  processedName.replace(POINT_INDEX_1,
205  QString::number(m_pickedPoints[1].index));
206  if (m_pickedPoints[0].cloud)
207  processedName.replace(ENTITY_INDEX_0,
208  m_pickedPoints[0].cloud->getViewId());
209  if (m_pickedPoints[1].cloud)
210  processedName.replace(ENTITY_INDEX_1,
211  m_pickedPoints[1].cloud->getViewId());
212  if (count > 2) {
213  processedName.replace(POINT_INDEX_2,
214  QString::number(m_pickedPoints[2].index));
215  if (m_pickedPoints[2].cloud)
216  processedName.replace(ENTITY_INDEX_2,
217  m_pickedPoints[2].cloud->getViewId());
218  }
219  }
220  }
221 
222  return processedName;
223 }
224 
225 void cc2DLabel::setPosition(float x, float y) {
226  m_screenPos[0] = x;
227  m_screenPos[1] = y;
228 }
229 
231  int x, int y, int dx, int dy, int screenWidth, int screenHeight) {
232  assert(screenHeight > 0 && screenWidth > 0);
233 
234  m_screenPos[0] += static_cast<float>(dx) / screenWidth;
235  m_screenPos[1] += static_cast<float>(dy) / screenHeight;
236 
237  return true;
238 }
239 
240 void cc2DLabel::clear(bool ignoreDependencies, bool ignoreCaption) {
241  // clear history
242  clearLabel(ignoreCaption);
243 
244  if (ignoreDependencies) {
245  m_pickedPoints.resize(0);
246  } else {
247  // remove all dependencies first!
248  while (!m_pickedPoints.empty()) {
249  m_pickedPoints.back().cloud->removeDependencyWith(this);
250  m_pickedPoints.pop_back();
251  }
252  }
253 
254  m_lastScreenPos[0] = m_lastScreenPos[1] = -1;
255  m_labelROI = QRect(0, 0, 0, 0);
256  setVisible(false);
257  setName("Label");
258 
260 }
261 
265 
266  if (c_unitPointMarker) {
267  for (int i = 0; i < 3; ++i) {
268  // ecvDisplayTools::RemoveWidgets(
269  // WIDGETS_PARAMETER(WIDGETS_TYPE::WIDGET_POLYGONMESH,
270  // QString::number(i) + m_sphereIdfix));
273  QString::number(i) + m_sphereIdfix));
274  }
275  }
276 
277  if (c_unitTriMarker) {
282  }
283 }
284 
286  if (!m_historyMessage.isEmpty()) {
287  for (const QString& text : m_historyMessage) {
288  // no more valid? we delete the message
293  }
294  m_historyMessage.clear();
295  }
296 
301 }
302 
303 void cc2DLabel::clearLabel(bool ignoreCaption) {
304  clear3Dviews();
305  clear2Dviews();
306  if (!ignoreCaption) {
309  }
310 }
311 
315  update3DLabelView(context, false);
316  update2DLabelView(context, false);
318 }
319 
321  bool updateScreen /* = true */) {
322  context.drawingFlags = CC_DRAW_3D | CC_DRAW_FOREGROUND;
324  if (updateScreen) {
326  }
327 }
328 
330  bool updateScreen /* = true */) {
331  context.drawingFlags = CC_DRAW_2D | CC_DRAW_FOREGROUND;
333  if (updateScreen) {
335  }
336 }
337 
339  ccHObject::onDeletionOf(obj); // remove dependencies, etc.
340 
341  // check that associated clouds are not about to be deleted!
342  size_t pointsToRemove = 0;
343  {
344  for (size_t i = 0; i < m_pickedPoints.size(); ++i)
345  if (m_pickedPoints[i].cloud == obj) ++pointsToRemove;
346  }
347 
348  if (pointsToRemove == 0) return;
349 
350  if (pointsToRemove == m_pickedPoints.size()) {
351  clear(true); // don't call clear as we don't want/need to update input
352  // object's dependencies!
353  } else {
354  // remove only the necessary points
355  size_t j = 0;
356  for (size_t i = 0; i < m_pickedPoints.size(); ++i) {
357  if (m_pickedPoints[i].cloud != obj) {
358  if (i != j) std::swap(m_pickedPoints[i], m_pickedPoints[j]);
359  j++;
360  }
361  }
362  assert(j != 0);
363  m_pickedPoints.resize(j);
364  }
365 
366  updateName();
367 }
368 
370  switch (m_pickedPoints.size()) {
371  case 0:
372  setName("Label");
373  break;
374  case 1:
375  setName(QString("Point #") + POINT_INDEX_0);
376  break;
377  case 2:
378  if (m_pickedPoints[0].cloud == m_pickedPoints[1].cloud)
379  setName(QString("Vector #") + POINT_INDEX_0 + QString(" - #") +
380  POINT_INDEX_1);
381  else
382  setName(QString("Vector #") + POINT_INDEX_0 + QString("@") +
383  ENTITY_INDEX_0 + QString(" - #") + POINT_INDEX_1 +
384  QString("@") + ENTITY_INDEX_1);
385  break;
386  case 3:
387  if (m_pickedPoints[0].cloud == m_pickedPoints[2].cloud &&
388  m_pickedPoints[1].cloud == m_pickedPoints[2].cloud)
389  setName(QString("Triplet #") + POINT_INDEX_0 + QString(" - #") +
390  POINT_INDEX_1 + QString(" - #") + POINT_INDEX_2);
391  else
392  setName(QString("Triplet #") + POINT_INDEX_0 + QString("@") +
393  ENTITY_INDEX_0 + QString(" - #") + POINT_INDEX_1 +
394  QString("@") + ENTITY_INDEX_1 + QString(" - #") +
395  POINT_INDEX_2 + QString("@") + ENTITY_INDEX_2);
396  break;
397  }
398 }
399 
401  unsigned pointIndex,
402  bool entityCenter /*=false*/) {
403  if (!cloud || pointIndex >= cloud->size()) return false;
404 
405  PickedPoint pp;
406  pp.cloud = cloud;
407  pp.index = pointIndex;
408  pp.entityCenterPoint = entityCenter;
409 
410  return addPickedPoint(pp);
411 }
412 
414  unsigned triangleIndex,
415  const CCVector2d& uv,
416  bool entityCenter) {
417  if (!mesh || triangleIndex >= mesh->size()) return false;
418 
419  PickedPoint pp;
420  pp.mesh = mesh;
421  pp.index = triangleIndex;
422  pp.uv = uv;
423  pp.entityCenterPoint = entityCenter;
424 
425  return addPickedPoint(pp);
426 }
427 
429  if (m_pickedPoints.size() == 3) {
430  return false;
431  }
432 
433  try {
434  m_pickedPoints.resize(m_pickedPoints.size() + 1);
435  } catch (const std::bad_alloc&) {
436  // not enough memory
437  return false;
438  }
439 
440  m_pickedPoints.back() = pp;
441 
442  // we want to be notified whenever an associated mesh is deleted (in which
443  // case we'll automatically clear the label)
444  if (pp.entity())
446  // we must also warn the cloud or mesh whenever we delete this label
447  //--> DGM: automatically done by the previous call to addDependency!
448 
449  updateName();
450 
451  return true;
452 }
453 
454 bool cc2DLabel::toFile_MeOnly(QFile& out, short dataVersion) const {
455  assert(out.isOpen() && (out.openMode() & QIODevice::WriteOnly));
456  if (dataVersion < 50) {
457  assert(false);
458  return false;
459  }
460 
461  if (!ccHObject::toFile_MeOnly(out, dataVersion)) return false;
462 
463  // points count (dataVersion >= 20)
464  uint32_t count = (uint32_t)m_pickedPoints.size();
465  if (out.write((const char*)&count, 4) < 0) return WriteError();
466 
467  // points & associated cloud ID (dataVersion >= 20)
468  for (std::vector<PickedPoint>::const_iterator it = m_pickedPoints.begin();
469  it != m_pickedPoints.end(); ++it) {
470  // point index
471  uint32_t index = (uint32_t)it->index;
472  if (out.write((const char*)&index, 4) < 0) return WriteError();
473  // cloud ID (will be retrieved later --> make sure that the cloud is
474  // saved alongside!)
475  uint32_t cloudID = (uint32_t)it->cloud->getUniqueID();
476  if (out.write((const char*)&cloudID, 4) < 0) return WriteError();
477  }
478 
479  // Relative screen position (dataVersion >= 20)
480  if (out.write((const char*)m_screenPos, sizeof(float) * 2) < 0)
481  return WriteError();
482 
483  // Collapsed state (dataVersion >= 20)
484  if (out.write((const char*)&m_showFullBody, sizeof(bool)) < 0)
485  return WriteError();
486 
487  // Show in 2D boolean (dataVersion >= 21)
488  if (out.write((const char*)&m_dispIn2D, sizeof(bool)) < 0)
489  return WriteError();
490 
491  // Show point(s) legend boolean (dataVersion >= 21)
492  if (out.write((const char*)&m_dispPointsLegend, sizeof(bool)) < 0)
493  return WriteError();
494 
495  // Relative marker scale (dataVersion >= 49) - IMPORTANT for sphere size!
496  // This is always written when saving, but only read when dataVersion >= 49
497  // to maintain backward compatibility with version 48 and earlier
498  if (out.write((const char*)&m_relMarkerScale, sizeof(float)) < 0)
499  return WriteError();
500 
501  return true;
502 }
503 
505  return std::max(static_cast<short>(50),
507 }
508 
510  short dataVersion,
511  int flags,
512  LoadedIDMap& oldToNewIDMap) {
513  if (!ccHObject::fromFile_MeOnly(in, dataVersion, flags, oldToNewIDMap))
514  return false;
515 
516  // points count (dataVersion >= 20)
517  uint32_t count = 0;
518  if (in.read((char*)&count, 4) < 0) return ReadError();
519 
520  // points & associated cloud ID (dataVersion >= 20)
521  assert(m_pickedPoints.empty());
522  for (uint32_t i = 0; i < count; ++i) {
523  // point index
524  uint32_t index = 0;
525  if (in.read((char*)&index, 4) < 0) return ReadError();
526  // cloud ID (will be retrieved later --> make sure that the cloud is
527  // saved alongside!)
528  uint32_t cloudID = 0;
529  if (in.read((char*)&cloudID, 4) < 0) return ReadError();
530 
531  //[DIRTY] WARNING: temporarily, we set the cloud unique ID in the
532  //'PickedPoint::cloud' pointer!!!
533  PickedPoint pp;
534  pp.index = (unsigned)index;
535  *(uint32_t*)(&pp.cloud) = cloudID;
536  m_pickedPoints.push_back(pp);
537  if (m_pickedPoints.size() != i + 1) return MemoryError();
538  }
539 
540  // Relative screen position (dataVersion >= 20)
541  if (in.read((char*)m_screenPos, sizeof(float) * 2) < 0) return ReadError();
542 
543  // Collapsed state (dataVersion >= 20)
544  if (in.read((char*)&m_showFullBody, sizeof(bool)) < 0) return ReadError();
545 
546  if (dataVersion > 20) {
547  // Show in 2D boolean (dataVersion >= 21)
548  if (in.read((char*)&m_dispIn2D, sizeof(bool)) < 0) return ReadError();
549 
550  // Show point(s) legend boolean (dataVersion >= 21)
551  if (in.read((char*)&m_dispPointsLegend, sizeof(bool)) < 0)
552  return ReadError();
553  }
554 
555  if (dataVersion > 48) {
556  // Relative marker scale (dataVersion >= 49) - IMPORTANT for sphere
557  // size! Read the saved value to preserve custom sphere sizes
558  if (in.read((char*)&m_relMarkerScale, sizeof(float)) < 0)
559  return ReadError();
560  }
561  // else: use constructor default value (0.15f) for old files (version <= 48)
562  // This automatically fixes sphere size for old labels
563 
564  return true;
565 }
566 
567 void AddPointCoordinates(QStringList& body,
568  unsigned pointIndex,
569  ccGenericPointCloud* cloud,
570  int precision,
571  QString pointName = QString()) {
572  assert(cloud);
573  const CCVector3* P = cloud->getPointPersistentPtr(pointIndex);
574  bool isShifted = cloud->isShifted();
575 
576  QString coordStr = QString("P#%0:").arg(pointIndex);
577  if (!pointName.isEmpty())
578  coordStr = QString("%1 (%2)").arg(pointName, coordStr);
579  if (isShifted) {
580  body << coordStr;
581  coordStr = QString(" [shifted]");
582  }
583 
584  coordStr += QString(" (%1;%2;%3)")
585  .arg(P->x, 0, 'f', precision)
586  .arg(P->y, 0, 'f', precision)
587  .arg(P->z, 0, 'f', precision);
588  body << coordStr;
589 
590  if (isShifted) {
591  CCVector3d Pg = cloud->toGlobal3d(*P);
592  QString globCoordStr = QString(" [original] (%1;%2;%3)")
593  .arg(Pg.x, 0, 'f', precision)
594  .arg(Pg.y, 0, 'f', precision)
595  .arg(Pg.z, 0, 'f', precision);
596  body << globCoordStr;
597  }
598 }
599 
601  info.cloud = 0;
602  if (m_pickedPoints.size() != 1) return;
603 
604  // cloud and point index
605  info.cloud = m_pickedPoints[0].cloud;
606  if (!info.cloud) {
607  assert(false);
608  return;
609  }
610  info.pointIndex = m_pickedPoints[0].index;
611  // normal
612  info.hasNormal = info.cloud->hasNormals();
613  if (info.hasNormal) {
614  info.normal = info.cloud->getPointNormal(info.pointIndex);
615  }
616  // color
617  info.hasRGB = info.cloud->hasColors();
618  if (info.hasRGB) {
619  info.rgb = info.cloud->getPointColor(info.pointIndex);
620  }
621  // scalar field
622  info.hasSF = info.cloud->hasDisplayedScalarField();
623  if (info.hasSF) {
624  info.sfValue = info.cloud->getPointScalarValue(info.pointIndex);
625 
626  info.sfName = "Scalar";
627  // fetch the real scalar field name if possible
628  if (info.cloud->isA(CV_TYPES::POINT_CLOUD)) {
629  ccPointCloud* pc = static_cast<ccPointCloud*>(info.cloud);
630  if (pc->getCurrentDisplayedScalarField()) {
632  info.sfName = QString(sf->getName());
634  sf->getGlobalShift() != 0) {
635  info.sfShiftedValue = sf->getGlobalShift() + info.sfValue;
636  info.sfValueIsShifted = true;
637  }
638  }
639  }
640  }
641 }
642 
644  info.cloud1 = info.cloud2 = 0;
645  if (m_pickedPoints.size() != 2) return;
646 
647  // 1st point
648  info.cloud1 = m_pickedPoints[0].cloud;
649  info.point1Index = m_pickedPoints[0].index;
650  const CCVector3* P1 = info.cloud1->getPointPersistentPtr(info.point1Index);
651  // 2nd point
652  info.cloud2 = m_pickedPoints[1].cloud;
653  info.point2Index = m_pickedPoints[1].index;
654  const CCVector3* P2 = info.cloud2->getPointPersistentPtr(info.point2Index);
655 
656  info.diff = *P2 - *P1;
657 }
658 
660  info.cloud1 = info.cloud2 = info.cloud3 = 0;
661  if (m_pickedPoints.size() != 3) return;
662  // 1st point
663  info.cloud1 = m_pickedPoints[0].cloud;
664  info.point1Index = m_pickedPoints[0].index;
665  const CCVector3* P1 = info.cloud1->getPointPersistentPtr(info.point1Index);
666  // 2nd point
667  info.cloud2 = m_pickedPoints[1].cloud;
668  info.point2Index = m_pickedPoints[1].index;
669  const CCVector3* P2 = info.cloud2->getPointPersistentPtr(info.point2Index);
670  // 3rd point
671  info.cloud3 = m_pickedPoints[2].cloud;
672  info.point3Index = m_pickedPoints[2].index;
673  const CCVector3* P3 = info.cloud3->getPointPersistentPtr(info.point3Index);
674 
675  // area
676  CCVector3 P1P2 = *P2 - *P1;
677  CCVector3 P1P3 = *P3 - *P1;
678  CCVector3 P2P3 = *P3 - *P2;
679  CCVector3 N = P1P2.cross(P1P3); // N = ABxAC
680  info.area = N.norm() / 2;
681 
682  // normal
683  N.normalize();
684  info.normal = N;
685 
686  // edges length
687  info.edges.u[0] = P1P2.normd(); // edge 1-2
688  info.edges.u[1] = P2P3.normd(); // edge 2-3
689  info.edges.u[2] = P1P3.normd(); // edge 3-1
690 
691  // angle
692  info.angles.u[0] =
693  cloudViewer::RadiansToDegrees(P1P2.angle_rad(P1P3)); // angleAtP1
694  info.angles.u[1] =
695  cloudViewer::RadiansToDegrees(P2P3.angle_rad(-P1P2)); // angleAtP2
697  P1P3.angle_rad(P2P3)); // angleAtP3 (should be equal to 180-a1-a2!)
698 }
699 
700 QStringList cc2DLabel::getLabelContent(int precision) const {
701  QStringList body;
702 
703  switch (m_pickedPoints.size()) {
704  case 0:
705  // can happen if the associated cloud(s) has(ve) been deleted!
706  body << "Deprecated";
707  break;
708 
709  case 1: // point
710  {
711  LabelInfo1 info;
712  getLabelInfo1(info);
713  if (!info.cloud) break;
714 
715  // coordinates
716  AddPointCoordinates(body, info.pointIndex, info.cloud, precision);
717 
718  // normal
719  if (info.hasNormal) {
720  QString normStr =
721  QString("Normal: (%1;%2;%3)")
722  .arg(info.normal.x, 0, 'f', precision)
723  .arg(info.normal.y, 0, 'f', precision)
724  .arg(info.normal.z, 0, 'f', precision);
725  body << normStr;
726  }
727  // color
728  if (info.hasRGB) {
729  QString colorStr = QString("Color: (%1;%2;%3)")
730  .arg(info.rgb.r)
731  .arg(info.rgb.g)
732  .arg(info.rgb.b);
733  body << colorStr;
734  }
735  // scalar field
736  if (info.hasSF) {
737  QString sfVal = GetSFValueAsString(info, precision);
738  QString sfStr = QString("%1 = %2").arg(info.sfName, sfVal);
739  body << sfStr;
740  }
741  } break;
742 
743  case 2: // vector
744  {
745  LabelInfo2 info;
746  getLabelInfo2(info);
747  if (!info.cloud1 || !info.cloud2) break;
748 
749  // distance is now the default label title
750  // PointCoordinateType dist = info.diff.norm();
751  // QString distStr = QString("Distance =
752  // %1").arg(dist,0,'f',precision); body << distStr;
753 
754  QString vecStr =
756  QString("X: %1\t").arg(info.diff.x, 0, 'f', precision) +
758  QString("Y: %1\t").arg(info.diff.y, 0, 'f', precision) +
760  QString("Z: %1").arg(info.diff.z, 0, 'f', precision);
761 
762  body << vecStr;
763 
764  PointCoordinateType dXY =
765  sqrt(info.diff.x * info.diff.x + info.diff.y * info.diff.y);
766  PointCoordinateType dXZ =
767  sqrt(info.diff.x * info.diff.x + info.diff.z * info.diff.z);
768  PointCoordinateType dZY =
769  sqrt(info.diff.z * info.diff.z + info.diff.y * info.diff.y);
770 
771  vecStr = MathSymbolDelta +
772  QString("XY: %1\t").arg(dXY, 0, 'f', precision) +
774  QString("XZ: %1\t").arg(dXZ, 0, 'f', precision) +
776  QString("ZY: %1").arg(dZY, 0, 'f', precision);
777  body << vecStr;
778 
779  AddPointCoordinates(body, info.point1Index, info.cloud1, precision);
780  AddPointCoordinates(body, info.point2Index, info.cloud2, precision);
781  } break;
782 
783  case 3: // triangle/plane
784  {
785  LabelInfo3 info;
786  getLabelInfo3(info);
787 
788  // area
789  QString areaStr =
790  QString("Area = %1").arg(info.area, 0, 'f', precision);
791  body << areaStr;
792 
793  // coordinates
794  AddPointCoordinates(body, info.point1Index, info.cloud1, precision,
795  "A");
796  AddPointCoordinates(body, info.point2Index, info.cloud2, precision,
797  "B");
798  AddPointCoordinates(body, info.point3Index, info.cloud3, precision,
799  "C");
800 
801  // normal
802  QString normStr = QString("Normal: (%1;%2;%3)")
803  .arg(info.normal.x, 0, 'f', precision)
804  .arg(info.normal.y, 0, 'f', precision)
805  .arg(info.normal.z, 0, 'f', precision);
806  body << normStr;
807 
808  // angles
809  QString angleStr =
810  QString("Angles: A=%1 - B=%2 - C=%3 deg.")
811  .arg(info.angles.u[0], 0, 'f', precision)
812  .arg(info.angles.u[1], 0, 'f', precision)
813  .arg(info.angles.u[2], 0, 'f', precision);
814  body << angleStr;
815 
816  // edges
817  QString edgesStr = QString("Edges: AB=%1 - BC=%2 - CA=%3")
818  .arg(info.edges.u[0], 0, 'f', precision)
819  .arg(info.edges.u[1], 0, 'f', precision)
820  .arg(info.edges.u[2], 0, 'f', precision);
821  body << edgesStr;
822  } break;
823 
824  default:
825  assert(false);
826  break;
827  }
828 
829  return body;
830 }
831 
832 bool cc2DLabel::acceptClick(int x, int y, Qt::MouseButton button) {
833  if (button == Qt::MiddleButton) {
834  QRect rect = QRect(0, 0, m_labelROI.width(), m_labelROI.height());
835  if (rect.contains(x - m_lastScreenPos[0], y - m_lastScreenPos[1])) {
836  // toggle collapse state
840  update2DLabelView(context, true);
841  return true;
842  }
843  }
844 
845  return false;
846 }
847 
849  if (m_pickedPoints.empty()) return;
850 
851  // 2D foreground only
852  if (!MACRO_Foreground(context)) return;
853 
854  // Not compatible with virtual transformation (see
855  // ccDrawableObject::enableGLTransformation)
856  if (MACRO_VirtualTransEnabled(context)) return;
857 
858  if (!isRedraw()) {
859  return;
860  }
861 
862  if (MACRO_Draw3D(context))
864  else if (MACRO_Draw2D(context))
866 }
867 
869  // clear history
870  clear3Dviews();
871  if (!isVisible() || !isEnabled()) {
872  return;
873  }
874 
875  size_t count = m_pickedPoints.size();
876  if (count == 0) {
877  return;
878  }
879 
880  if (ecvDisplayTools::GetCurrentScreen() == nullptr) {
881  assert(false);
882  return;
883  }
884 
885  // standard case: list names pushing
886  bool entityPickingMode = MACRO_EntityPicking(context);
887  if (entityPickingMode) {
888  // not particularly fast
889  if (MACRO_FastEntityPicking(context)) return;
890  }
891 
892  // bool loop = false;
893  switch (count) {
894  case 3: {
895  // display point marker as spheres
896  {
897  if (!c_unitTriMarker) {
899  {
900  ccPointCloud* m_polyVertices =
901  new ccPointCloud("vertices");
902  m_polyVertices->resize(3);
903  CCVector3* A = const_cast<CCVector3*>(
904  m_polyVertices->getPointPersistentPtr(0));
905  CCVector3* B = const_cast<CCVector3*>(
906  m_polyVertices->getPointPersistentPtr(1));
907  CCVector3* C = const_cast<CCVector3*>(
908  m_polyVertices->getPointPersistentPtr(2));
909 
910  *A = *(m_pickedPoints[0].cloud->getPoint(
911  m_pickedPoints[0].index));
912  *B = *(m_pickedPoints[1].cloud->getPoint(
913  m_pickedPoints[1].index));
914  *C = *(m_pickedPoints[2].cloud->getPoint(
915  m_pickedPoints[2].index));
916 
917  ccGenericPointCloud* gencloud =
919  m_polyVertices);
920  if (gencloud) {
921  cloud = static_cast<
923  gencloud);
924  }
925  }
926 
928  QSharedPointer<ccFacet>(ccFacet::Create(cloud));
929 
930  if (c_unitTriMarker) {
931  c_unitTriMarker->getPolygon()->setOpacity(0.5);
932  c_unitTriMarker->getPolygon()->setTempColor(
934  c_unitTriMarker->getPolygon()->setVisible(true);
935  c_unitTriMarker->getContour()->setColor(ecvColor::red);
936  c_unitTriMarker->getContour()->showColors(true);
937  c_unitTriMarker->getContour()->setVisible(true);
938  c_unitTriMarker->setTempColor(ecvColor::darkGrey);
939  c_unitTriMarker->showColors(true);
940  c_unitTriMarker->setVisible(true);
941  c_unitTriMarker->setEnabled(true);
943  this->getViewId() + SEPARATOR +
944  c_unitTriMarker->getPolygon()->getViewId();
946  this->getViewId() + SEPARATOR +
947  c_unitTriMarker->getContour()->getViewId();
948  c_unitTriMarker->setFixedId(true);
949  c_unitTriMarker->getContour()->setFixedId(true);
950  c_unitTriMarker->getPolygon()->setFixedId(true);
951  }
952 
953  if (m_surfaceIdfix == "") {
955  this->getViewId() + SEPARATOR +
956  c_unitTriMarker->getPolygon()->getViewId();
957  }
958  if (m_contourIdfix == "") {
960  this->getViewId() + SEPARATOR +
961  c_unitTriMarker->getContour()->getViewId();
962  }
963  }
964 
965  CCVector3* A = const_cast<CCVector3*>(
966  c_unitTriMarker->getContourVertices()
967  ->getPointPersistentPtr(0));
968  CCVector3* B = const_cast<CCVector3*>(
969  c_unitTriMarker->getContourVertices()
970  ->getPointPersistentPtr(1));
971  CCVector3* C = const_cast<CCVector3*>(
972  c_unitTriMarker->getContourVertices()
973  ->getPointPersistentPtr(2));
974 
975  *A = *(m_pickedPoints[0].cloud->getPoint(
976  m_pickedPoints[0].index));
977  *B = *(m_pickedPoints[1].cloud->getPoint(
978  m_pickedPoints[1].index));
979  *C = *(m_pickedPoints[2].cloud->getPoint(
980  m_pickedPoints[2].index));
981 
982  // build-up point maker own 'context'
983  CC_DRAW_CONTEXT markerContext = context;
984  // we must remove the 'push name flag' so that the sphere
985  // doesn't push its own!
986  markerContext.drawingFlags &= (~CC_ENTITY_PICKING);
987 
988  // draw triangle contour
989  markerContext.viewID = m_contourIdfix;
990  c_unitTriMarker->getContour()->setRedraw(true);
991  c_unitTriMarker->getContour()->draw(markerContext);
992  // draw triangle mesh surface
993  markerContext.viewID = m_surfaceIdfix;
994  c_unitTriMarker->getPolygon()->setRedraw(true);
995  c_unitTriMarker->getPolygon()->draw(markerContext);
996  }
997  }
998 
999  case 2: {
1000  if (count == 2) {
1001  // it's may not displayed on top of the entities
1002  // line width
1003  const float c_sizeFactor = 4.0f;
1004  // contour segments (before the labels!)
1005  ecvColor::Rgb lineColor =
1006  isSelected()
1007  ? ecvColor::red
1008  : context.labelDefaultMarkerCol /*ecvColor::green.rgba*/
1009  ;
1010  float lineWidth = c_sizeFactor * context.renderZoom;
1011 
1012  const CCVector3* lineSt = m_pickedPoints[0].cloud->getPoint(
1013  m_pickedPoints[0].index);
1014  const CCVector3* lineEd = m_pickedPoints[1].cloud->getPoint(
1015  m_pickedPoints[1].index);
1016 
1017  // we draw the line
1019  param.setLineWidget(
1020  LineWidget(*lineSt, *lineEd, lineWidth, lineColor));
1022  }
1023  }
1024 
1025  case 1: {
1026  // display point marker as spheres
1027  {
1028  if (!c_unitPointMarker) {
1029  c_unitPointMarker = QSharedPointer<ccSphere>(
1030  new ccSphere(1.0f, 0, "PointMarker", 12));
1031  c_unitPointMarker->showColors(true);
1032  c_unitPointMarker->setVisible(true);
1033  c_unitPointMarker->setEnabled(true);
1034  c_unitPointMarker->setFixedId(true);
1035  m_sphereIdfix = SEPARATOR + this->getViewId() + SEPARATOR +
1036  c_unitPointMarker->getViewId();
1037  }
1038 
1039  if (m_sphereIdfix == "") {
1040  m_sphereIdfix = SEPARATOR + this->getViewId() + SEPARATOR +
1041  c_unitPointMarker->getViewId();
1042  }
1043 
1044  // build-up point maker own 'context'
1045  CC_DRAW_CONTEXT markerContext = context;
1046  // we must remove the 'push name flag' so that the sphere
1047  // doesn't push its own!
1048  markerContext.drawingFlags &= (~CC_ENTITY_PICKING);
1049 
1050  if (isSelected() && !entityPickingMode)
1051  c_unitPointMarker->setTempColor(ecvColor::red);
1052  else
1053  c_unitPointMarker->setTempColor(
1054  context.labelDefaultMarkerCol);
1055 
1056  const ecvViewportParameters& viewportParams =
1058  for (size_t i = 0; i < count; i++) {
1059  const CCVector3* P = m_pickedPoints[i].cloud->getPoint(
1060  m_pickedPoints[i].index);
1061  float scale = context.labelMarkerSize * m_relMarkerScale;
1062  if (viewportParams.perspectiveView &&
1063  viewportParams.zFar > 0) {
1064  // we always project the points in 2D (maybe useful
1065  // later, even when displaying the label during the 2D
1066  // pass!)
1067  ccGLCameraParameters camera;
1068  // we can't use the context 'ccGLCameraParameters'
1069  // (viewport, modelView matrix, etc. ) because it
1070  // doesn't take the temporary 'GL transformation' into
1071  // account!
1073 
1074  // in perspective view, the actual scale depends on the
1075  // distance to the camera!
1076  double d = (camera.modelViewMat *
1078  .norm();
1079  // we consider that the 'standard' scale is at half the
1080  // depth sqrt = empirical (probably because the marker
1081  // size is
1082  // already partly compensated by
1083  // ecvDisplayTools::computeActualPixelSize())
1084  double unitD = viewportParams.zFar / 2;
1085  scale = static_cast<float>(scale * sqrt(d / unitD));
1086  }
1087 
1089  QString::number(i) + m_sphereIdfix);
1090  param.radius =
1091  scale * m_relMarkerScale; // Use relative scale for
1092  // consistent sphere size
1093  m_pickedPoints[i].markerScale = scale * m_relMarkerScale;
1094  param.center = CCVector3(P->x, P->y, P->z);
1096  ecvDisplayTools::DrawWidgets(param, false);
1097  // markerContext.transformInfo.setScale(CCVector3(scale,
1098  // scale, scale)); markerContext.viewID = QString::number(i)
1099  // + m_sphereIdfix; c_unitPointMarker->setRedraw(true);
1100  // c_unitPointMarker->draw(markerContext);
1101  }
1102  }
1103  }
1104  }
1105 }
1106 
1107 // display parameters
1108 static const int c_margin = 5;
1109 static const int c_tabMarginX = 5; // default: 5
1110 static const int c_tabMarginY = 2;
1111 static const int c_arrowBaseSize = 3;
1112 // static const int c_buttonSize = 10;
1113 
1114 static const ecvColor::Rgb c_darkGreen(0, 200, 0);
1115 
1117 struct Tab {
1119  Tab(int _maxBlockPerRow = 2)
1120  : maxBlockPerRow(_maxBlockPerRow),
1121  blockCount(0),
1122  rowCount(0),
1123  colCount(0) {}
1124 
1126 
1128  void setMaxBlockPerRow(int maxBlock) { maxBlockPerRow = maxBlock; }
1129 
1131  int add2x3Block() {
1132  // add columns (if necessary)
1133  if (colCount < maxBlockPerRow * 2) {
1134  colCount += 2;
1135  colContent.resize(colCount);
1136  colWidth.resize(colCount, 0);
1137  }
1138  int blockCol = (blockCount % maxBlockPerRow);
1139  // add new row
1140  if (blockCol == 0) rowCount += 3;
1141  ++blockCount;
1142 
1143  // return the first column index of the block
1144  return blockCol * 2;
1145  }
1146 
1148 
1150  int updateColumnsWidthTable(const QFontMetrics& fm) {
1151  // compute min width of each column
1152  int totalWidth = 0;
1153  for (int i = 0; i < colCount; ++i) {
1154  int maxWidth = 0;
1155  for (int j = 0; j < colContent[i].size(); ++j) {
1156 #if (QT_VERSION <= QT_VERSION_CHECK(5, 0, 0))
1157  maxWidth = std::max(maxWidth, fm.width(colContent[i][j]));
1158 #else
1159  maxWidth = std::max(maxWidth,
1160  fm.horizontalAdvance(colContent[i][j]));
1161 #endif
1162  }
1163  colWidth[i] = maxWidth;
1164  totalWidth += maxWidth;
1165  }
1166  return totalWidth;
1167  }
1168 
1178  std::vector<int> colWidth;
1180  std::vector<QStringList> colContent;
1181 };
1182 
1185  assert(false);
1186  return;
1187  }
1188 
1189  // clear history
1190  clear2Dviews();
1191  if (!isVisible() || !isEnabled()) {
1192  clearLabel(false);
1193  return;
1194  }
1195 
1196  if (m_pickedPoints.empty()) {
1197  return;
1198  }
1199 
1200  // standard case: list names pushing
1201  bool entityPickingMode = MACRO_EntityPicking(context);
1202 
1203  size_t count = m_pickedPoints.size();
1204  assert(count != 0);
1205 
1206  // hack: we display the label connecting 'segments' and the point(s) legend
1207  // in 2D so that they always appear above the entities
1208  {
1209  // don't do this in picking mode!
1210  if (!entityPickingMode) {
1211  // we always project the points in 2D (maybe useful later, even when
1212  // displaying the label during the 2D pass!)
1213  ccGLCameraParameters camera;
1214  // we can't use the context 'ccGLCameraParameters' (viewport,
1215  // modelView matrix, etc. ) because it doesn't take the temporary
1216  // 'GL transformation' into account!
1218  for (size_t i = 0; i < count; i++) {
1219  // project the point in 2D
1220  const CCVector3* P3D = m_pickedPoints[i].cloud->getPoint(
1221  m_pickedPoints[i].index);
1222  camera.project(*P3D, m_pickedPoints[i].pos2D);
1223  }
1224  }
1225 
1226  // test if the label points are visible
1227  size_t visibleCount = 0;
1228  for (unsigned j = 0; j < count; ++j) {
1229  if (m_pickedPoints[j].pos2D.z >= 0.0 &&
1230  m_pickedPoints[j].pos2D.z <= 1.0) {
1231  ++visibleCount;
1232  }
1233  }
1234 
1235  if (visibleCount) {
1236  // no need to display the point(s) legend in picking mode
1237  if (m_dispPointsLegend && !entityPickingMode) {
1238  QFont font(ecvDisplayTools::
1239  GetTextDisplayFont()); // takes rendering
1240  // zoom into account!
1241  // font.setPointSize(font.pointSize() + 2);
1242  font.setBold(true);
1243  static const QChar ABC[3] = {'A', 'B', 'C'};
1244 
1245  // draw the label 'legend(s)'
1246  for (size_t j = 0; j < count; j++) {
1247  QString title;
1248  if (count == 1)
1249  title = getName(); // for single-point labels we prefer
1250  // the name
1251  else if (count == 3)
1252  title = ABC[j]; // for triangle-labels, we only display
1253  // "A","B","C"
1254  else
1255  title = QString("P#%0").arg(m_pickedPoints[j].index);
1256 
1258  title,
1259  static_cast<int>(m_pickedPoints[j].pos2D.x) +
1260  context.labelMarkerTextShift_pix,
1261  static_cast<int>(m_pickedPoints[j].pos2D.y) +
1262  context.labelMarkerTextShift_pix,
1264  context.labelOpacity / 100.0f, ecvColor::white.rgb,
1265  &font, this->getViewId());
1266  }
1267  }
1268  } else {
1269  return;
1270  }
1271  }
1272 
1273  // only display lengend other than 2D display
1274  if (!m_dispIn2D) {
1277  return;
1278  }
1279 
1280  // label title
1281  const int precision = context.dispNumberPrecision;
1282  QString title = getTitle(precision);
1283 
1284 #define DRAW_CONTENT_AS_TAB
1285 #ifdef DRAW_CONTENT_AS_TAB
1286  // draw contents as an array
1287  Tab tab(4);
1288  int rowHeight = 0;
1289 #else
1290  // simply display the content as text
1291  QStringList body;
1292 #endif
1293 
1294  // render zoom
1295  int margin = static_cast<int>(c_margin * context.renderZoom);
1296  int tabMarginX = static_cast<int>(c_tabMarginX * context.renderZoom);
1297  int tabMarginY = static_cast<int>(c_tabMarginY * context.renderZoom);
1298  int arrowBaseSize = static_cast<int>(c_arrowBaseSize * context.renderZoom);
1299 
1300  int titleHeight = 0;
1301  QFont bodyFont, titleFont;
1302  if (!entityPickingMode) {
1303  /*** label border ***/
1304  bodyFont =
1305  ecvDisplayTools::GetLabelDisplayFont(); // takes rendering zoom
1306  // into account!
1307  titleFont = bodyFont; // takes rendering zoom into account!
1308 
1309  // CRITICAL FIX: Adaptively increase title font size on macOS for better
1310  // visibility Only affects eCV2DLabel caption text, not other fonts
1311  // Adjust based on screen resolution and DPI for compatibility across
1312  // different displays CRITICAL: Always get current screen info (not
1313  // cached) to handle display changes
1314 #ifdef Q_OS_MAC
1315  int currentSize = titleFont.pointSize();
1316  float scaleFactor = 1.0f;
1317 
1318  // CRITICAL: Get the screen where the label is actually displayed
1319  // This ensures correct scaling when window moves between displays
1320  // Use the widget's screen if available, otherwise fall back to primary
1321  // screen
1322  QWidget* win = ecvDisplayTools::GetMainWindow();
1323  QScreen* screen = nullptr;
1324  int dpiScale = 1;
1325 
1326  if (win) {
1327  // Get the screen where the window is currently displayed
1328  // This is critical for multi-monitor setups and display changes
1329  screen = win->screen();
1330  if (!screen) {
1331  screen = QApplication::primaryScreen();
1332  }
1333  dpiScale = win->devicePixelRatio();
1334  } else {
1335  screen = QApplication::primaryScreen();
1336  }
1337 
1338  if (screen) {
1339  QSize screenSize = screen->size();
1340  int screenWidth = screenSize.width();
1341  int screenHeight = screenSize.height();
1342  int screenDPI = screen->physicalDotsPerInch();
1343 
1344  // Adaptive scaling based on resolution and DPI
1345  // User requested: increase all scaling by 50% (multiply by 1.5)
1346  if (screenWidth >= 3840 || screenHeight >= 2160) {
1347  // 4K and above: Use moderate scaling (1.3x * 1.5 = 1.95x)
1348  scaleFactor = 1.95f;
1349  } else if (screenWidth >= 2560 || screenHeight >= 1440) {
1350  // 2K resolution: Use higher scaling (1.5x * 1.5 = 2.25x)
1351  scaleFactor = 2.25f;
1352  } else if (screenWidth >= 1920 && screenHeight >= 1080) {
1353  // 1080p: Use higher scaling (1.6x * 1.5 = 2.4x) for better
1354  // visibility
1355  scaleFactor = 2.4f;
1356  } else {
1357  // Lower resolution: Use even higher scaling (1.8x * 1.5 = 2.7x)
1358  scaleFactor = 2.7f;
1359  }
1360 
1361  // Adjust for Retina displays (dpiScale > 1)
1362  // Retina displays have higher pixel density, so they need less
1363  // scaling For Retina (dpiScale = 2), reduce scaling by ~20% For
1364  // non-Retina (dpiScale = 1), keep full scaling
1365  if (dpiScale >= 2) {
1366  // Retina display: reduce scaling since text is already sharper
1367  scaleFactor *= 0.85f; // Reduce by 15% for Retina
1368  } else if (dpiScale > 1) {
1369  // Partial scaling (rare case)
1370  scaleFactor *= (1.0f - (dpiScale - 1.0f) * 0.15f);
1371  }
1372 
1373  // Adjust for DPI: Higher DPI screens need less scaling
1374  // This provides fine-tuning based on physical DPI
1375  if (screenDPI > 150) {
1376  scaleFactor *= 0.95f; // Reduce by 5% for very high DPI
1377  } else if (screenDPI < 100) {
1378  scaleFactor *= 1.05f; // Increase by 5% for lower DPI
1379  }
1380  }
1381 
1382  // Apply the calculated scale factor
1383  int newSize = static_cast<int>(currentSize * scaleFactor);
1384  titleFont.setPointSize(newSize);
1385 #endif
1386  // titleFont.setBold(true);
1387 
1388  QFontMetrics titleFontMetrics(titleFont);
1389  titleHeight = titleFontMetrics.height();
1390 
1391  QFontMetrics bodyFontMetrics(bodyFont);
1392  rowHeight = bodyFontMetrics.height();
1393 
1394  // get label box dimension
1395  int dx = 100;
1396  int dy = 0;
1397  {
1398  // base box dimension
1399  dx = std::max(dx,
1400  QTCOMPAT_FONTMETRICS_WIDTH(titleFontMetrics, title));
1401 
1402  dy += margin; // top vertical margin
1403  dy += titleHeight; // title
1404 
1405  if (m_showFullBody) {
1406 #ifdef DRAW_CONTENT_AS_TAB
1407  try {
1408  if (count == 1) {
1409  LabelInfo1 info;
1410  getLabelInfo1(info);
1411 
1412  bool isShifted = info.cloud->isShifted();
1413  // 1st block: X, Y, Z (local)
1414  {
1415  int c = tab.add2x3Block();
1416  QChar suffix = ' ';
1417  if (isShifted) {
1418  suffix = 'l'; //'l' for local
1419  }
1420  const CCVector3* P =
1421  info.cloud->getPoint(info.pointIndex);
1422  tab.colContent[c] << QString("X") + suffix;
1423  tab.colContent[c + 1]
1424  << QString::number(P->x, 'f', precision);
1425  tab.colContent[c] << QString("Y") + suffix;
1426  tab.colContent[c + 1]
1427  << QString::number(P->y, 'f', precision);
1428  tab.colContent[c] << QString("Z") + suffix;
1429  tab.colContent[c + 1]
1430  << QString::number(P->z, 'f', precision);
1431  }
1432  // next block: X, Y, Z (global)
1433  if (isShifted) {
1434  int c = tab.add2x3Block();
1435  CCVector3d P = info.cloud->toGlobal3d(
1436  *info.cloud->getPoint(info.pointIndex));
1437  tab.colContent[c] << "Xg ";
1438  tab.colContent[c + 1]
1439  << QString::number(P.x, 'f', precision);
1440  tab.colContent[c] << "Yg ";
1441  tab.colContent[c + 1]
1442  << QString::number(P.y, 'f', precision);
1443  tab.colContent[c] << "Zg ";
1444  tab.colContent[c + 1]
1445  << QString::number(P.z, 'f', precision);
1446  }
1447  // next block: normal
1448  if (info.hasNormal) {
1449  int c = tab.add2x3Block();
1450  tab.colContent[c] << "Nx ";
1451  tab.colContent[c + 1] << QString::number(
1452  info.normal.x, 'f', precision);
1453  tab.colContent[c] << "Ny ";
1454  tab.colContent[c + 1] << QString::number(
1455  info.normal.y, 'f', precision);
1456  tab.colContent[c] << "Nz ";
1457  tab.colContent[c + 1] << QString::number(
1458  info.normal.z, 'f', precision);
1459  }
1460 
1461  // next block: RGB color
1462  if (info.hasRGB) {
1463  int c = tab.add2x3Block();
1464  tab.colContent[c] << " R ";
1465  tab.colContent[c + 1]
1466  << QString::number(info.rgb.r);
1467  tab.colContent[c] << " G ";
1468  tab.colContent[c + 1]
1469  << QString::number(info.rgb.g);
1470  tab.colContent[c] << " B ";
1471  tab.colContent[c + 1]
1472  << QString::number(info.rgb.b);
1473  }
1474  } else if (count == 2) {
1475  LabelInfo2 info;
1476  getLabelInfo2(info);
1477 
1478  // 1st block: dX, dY, dZ
1479  {
1480  int c = tab.add2x3Block();
1481  tab.colContent[c]
1482  << MathSymbolDelta + QString("X ");
1483  tab.colContent[c + 1] << QString::number(
1484  info.diff.x, 'f', precision);
1485  tab.colContent[c]
1486  << MathSymbolDelta + QString("Y ");
1487  tab.colContent[c + 1] << QString::number(
1488  info.diff.y, 'f', precision);
1489  tab.colContent[c]
1490  << MathSymbolDelta + QString("Z ");
1491  tab.colContent[c + 1] << QString::number(
1492  info.diff.z, 'f', precision);
1493  }
1494  // 2nd block: dXY, dXZ, dZY
1495  {
1496  int c = tab.add2x3Block();
1497  PointCoordinateType dXY =
1498  sqrt(info.diff.x * info.diff.x +
1499  info.diff.y * info.diff.y);
1500  PointCoordinateType dXZ =
1501  sqrt(info.diff.x * info.diff.x +
1502  info.diff.z * info.diff.z);
1503  PointCoordinateType dZY =
1504  sqrt(info.diff.z * info.diff.z +
1505  info.diff.y * info.diff.y);
1506  tab.colContent[c]
1507  << " " + MathSymbolDelta + QString("XY ");
1508  tab.colContent[c + 1]
1509  << QString::number(dXY, 'f', precision);
1510  tab.colContent[c]
1511  << " " + MathSymbolDelta + QString("XZ ");
1512  tab.colContent[c + 1]
1513  << QString::number(dXZ, 'f', precision);
1514  tab.colContent[c]
1515  << " " + MathSymbolDelta + QString("ZY ");
1516  tab.colContent[c + 1]
1517  << QString::number(dZY, 'f', precision);
1518  }
1519  } else if (count == 3) {
1520  LabelInfo3 info;
1521  getLabelInfo3(info);
1522  tab.setMaxBlockPerRow(2); // square tab (2x2 blocks)
1523 
1524  // next block: indexes
1525  {
1526  int c = tab.add2x3Block();
1527  tab.colContent[c] << "index.A ";
1528  tab.colContent[c + 1]
1529  << QString::number(info.point1Index);
1530  tab.colContent[c] << "index.B ";
1531  tab.colContent[c + 1]
1532  << QString::number(info.point2Index);
1533  tab.colContent[c] << "index.C ";
1534  tab.colContent[c + 1]
1535  << QString::number(info.point3Index);
1536  }
1537  // next block: edges length
1538  {
1539  int c = tab.add2x3Block();
1540  tab.colContent[c] << " AB ";
1541  tab.colContent[c + 1] << QString::number(
1542  info.edges.u[0], 'f', precision);
1543  tab.colContent[c] << " BC ";
1544  tab.colContent[c + 1] << QString::number(
1545  info.edges.u[1], 'f', precision);
1546  tab.colContent[c] << " CA ";
1547  tab.colContent[c + 1] << QString::number(
1548  info.edges.u[2], 'f', precision);
1549  }
1550  // next block: angles
1551  {
1552  int c = tab.add2x3Block();
1553  tab.colContent[c] << "angle.A ";
1554  tab.colContent[c + 1] << QString::number(
1555  info.angles.u[0], 'f', precision);
1556  tab.colContent[c] << "angle.B ";
1557  tab.colContent[c + 1] << QString::number(
1558  info.angles.u[1], 'f', precision);
1559  tab.colContent[c] << "angle.C ";
1560  tab.colContent[c + 1] << QString::number(
1561  info.angles.u[2], 'f', precision);
1562  }
1563  // next block: normal
1564  {
1565  int c = tab.add2x3Block();
1566  tab.colContent[c] << " Nx ";
1567  tab.colContent[c + 1] << QString::number(
1568  info.normal.x, 'f', precision);
1569  tab.colContent[c] << " Ny ";
1570  tab.colContent[c + 1] << QString::number(
1571  info.normal.y, 'f', precision);
1572  tab.colContent[c] << " Nz ";
1573  tab.colContent[c + 1] << QString::number(
1574  info.normal.z, 'f', precision);
1575  }
1576  }
1577  } catch (const std::bad_alloc&) {
1578  // not enough memory
1579  assert(!entityPickingMode);
1580  return;
1581  }
1582 
1583  // compute min width of each column
1584  int totalWidth = tab.updateColumnsWidthTable(bodyFontMetrics);
1585 
1586  int tabWidth =
1587  totalWidth +
1588  tab.colCount * (2 * tabMarginX); // add inner margins
1589  dx = std::max(dx, tabWidth);
1590  dy += tab.rowCount *
1591  (rowHeight + 2 * tabMarginY); // add inner margins
1592  // we also add a margin every 3 rows
1593  dy += std::max(0, (tab.rowCount / 3) - 1) * margin;
1594  dy += margin; // bottom vertical margin
1595 #else
1596  body = getLabelContent(precision);
1597  if (!body.empty()) {
1598  dy += margin; // vertical margin above separator
1599  for (int j = 0; j < body.size(); ++j) {
1600  dx = std::max(dx, QTCOMPAT_FONTMETRICS_WIDTH(
1601  bodyFontMetrics, body[j]));
1602  dy += rowHeight; // body line height
1603  }
1604  dy += margin; // vertical margin below text
1605  }
1606 #endif // DRAW_CONTENT_AS_TAB
1607  }
1608 
1609  dx += margin * 2; // horizontal margins
1610  }
1611 
1612  // main rectangle
1613  m_labelROI = QRect(0, 0, dx, dy);
1614  }
1615 
1616  // draw label rectangle
1617  // CRITICAL FIX for macOS: m_screenPos is relative coordinates (0.0 to 1.0)
1618  // context.glW and context.glH are physical pixels (already scaled by
1619  // devicePixelRatio) m_screenPos is stored relative to LOGICAL pixels (as
1620  // seen in move2D implementation) We need to use logical pixels for position
1621  // calculation to match the coordinate system used by move2D and DrawWidgets
1622  // const int xStart = static_cast<int>(context.glW * m_screenPos[0]);
1623  // const int yStart = static_cast<int>(context.glH * (1.0f -
1624  // m_screenPos[1]));
1625  const float logicalW = context.glW / context.devicePixelRatio;
1626  const float logicalH = context.glH / context.devicePixelRatio;
1627  const int xStart = static_cast<int>(logicalW * m_screenPos[0]);
1628  const int yStart = static_cast<int>(logicalH * (1.0f - m_screenPos[1]));
1629 
1630  m_lastScreenPos[0] = xStart;
1631  m_lastScreenPos[1] = yStart - m_labelROI.height();
1632 
1633  // colors
1634  bool highlighted = (!entityPickingMode && isSelected());
1635  // default background color
1636  unsigned char alpha =
1637  static_cast<unsigned char>((context.labelOpacity / 100.0) * 255);
1638  ecvColor::Rgbaub defaultBkgColor(context.labelDefaultBkgCol, alpha);
1639  // default border color (mustn't be totally transparent!)
1640  ecvColor::Rgbaub defaultBorderColor(ecvColor::red, 255);
1641  if (!highlighted) {
1642  // apply only half of the transparency
1643  unsigned char halfAlpha = static_cast<unsigned char>(
1644  (50.0 + context.labelOpacity / 200.0) * 255);
1645  defaultBorderColor =
1646  ecvColor::Rgbaub(context.labelDefaultBkgCol, halfAlpha);
1647  }
1648 
1649  m_labelROI = QRect(xStart, yStart - m_labelROI.height(), m_labelROI.width(),
1650  m_labelROI.height());
1651 
1652  ecvColor::Rgbub defaultTextColor;
1653  if (context.labelOpacity < 40) {
1654  // under a given opacity level, we use the default text color instead!
1655  defaultTextColor = context.textDefaultCol;
1656  } else {
1657  defaultTextColor = ecvColor::Rgbub(255 - context.labelDefaultBkgCol.r,
1658  255 - context.labelDefaultBkgCol.g,
1659  255 - context.labelDefaultBkgCol.b);
1660  }
1661 
1662  // display text
1663  if (!entityPickingMode) {
1664  // label title
1665  m_historyMessage << title;
1666 
1667  if (m_showFullBody) {
1668  // Create QFontMetrics for text alignment calculations
1669  QFontMetrics bodyFontMetrics(bodyFont);
1670 
1671  for (int r = 0; r < tab.rowCount; ++r) {
1672  QString str;
1673  for (int c = 0; c < tab.colCount; ++c) {
1674  QString cellContent = tab.colContent[c][r];
1675  // Calculate actual text width
1676 #if (QT_VERSION <= QT_VERSION_CHECK(5, 0, 0))
1677  int textWidth = bodyFontMetrics.width(cellContent);
1678 #else
1679  int textWidth =
1680  bodyFontMetrics.horizontalAdvance(cellContent);
1681 #endif
1682  // Calculate target width (column width + margin for
1683  // spacing)
1684  int targetWidth = tab.colWidth[c];
1685  if (c < tab.colCount - 1) {
1686  // Add margin after each column except the last
1687  targetWidth += tabMarginX;
1688  }
1689  // Add spaces to align text
1690  int spaceWidth = textWidth < targetWidth
1691  ? targetWidth - textWidth
1692  : 0;
1693  if (spaceWidth > 0) {
1694  // Calculate number of spaces needed (approximate)
1695  // Use average character width for spacing
1696 #if (QT_VERSION <= QT_VERSION_CHECK(5, 0, 0))
1697  int spaceCharWidth = bodyFontMetrics.width(' ');
1698 #else
1699  int spaceCharWidth =
1700  bodyFontMetrics.horizontalAdvance(' ');
1701 #endif
1702  int numSpaces = spaceCharWidth > 0
1703  ? (spaceWidth + spaceCharWidth -
1704  1) / spaceCharWidth
1705  : 0;
1706  cellContent += QString(numSpaces, ' ');
1707  }
1708  str += cellContent;
1709  }
1710  m_historyMessage << str;
1711  }
1712  }
1713  }
1714 
1715  if (!entityPickingMode && count > 0 && !m_historyMessage.empty()) {
1716  // compute arrow head position
1717  CCVector3 position(0, 0, 0);
1718  for (size_t i = 0; i < count; ++i) {
1719  const CCVector3* p =
1720  m_pickedPoints[i].cloud->getPoint(m_pickedPoints[i].index);
1721  position += *p;
1722  }
1723  position /= static_cast<PointCoordinateType>(count);
1724 
1726  this->getViewId());
1727  param.center = position;
1728  // CRITICAL: m_labelROI uses logical pixels (calculated from
1729  // logicalW/logicalH) PCLVis::addCaption divides pos2D by
1730  // getRenderWindow()->GetSize() to get relative coordinates
1731  //
1732  // Coordinate system analysis:
1733  // - m_labelROI is in Qt coordinate system (top-left origin, Y increases
1734  // downward)
1735  // m_labelROI.y() is the top edge of the label rectangle
1736  // - VTK's SetPosition expects pixel coordinates that will be divided by
1737  // GetSize()
1738  // to get normalized coordinates (0.0 to 1.0) where Y=0 is at BOTTOM
1739  // - Since Qt Y increases downward and VTK Y=0 is at bottom, we need to
1740  // invert Y
1741  // VTK_Y_pixel = windowHeight - Qt_Y_pixel
1742  //
1743  // Get render window size for coordinate conversion and boundary
1744  // checking
1745  const float logicalW = context.glW / context.devicePixelRatio;
1746  const float logicalH = context.glH / context.devicePixelRatio;
1747 
1748  // m_labelROI coordinates are in Qt system (top-left origin)
1749  float posX = static_cast<float>(m_labelROI.x());
1750  float posY = static_cast<float>(m_labelROI.y());
1751 
1752  // Boundary check: ensure coordinates are within valid range
1753  // This is a safety measure to prevent caption from going off-screen
1754  posX = std::max(0.0f, std::min(posX, logicalW - 1.0f));
1755  posY = std::max(0.0f, std::min(posY, logicalH - 1.0f));
1756 
1757  // CRITICAL: macOS-specific fix for coordinate system conversion
1758  // On macOS, VTK's coordinate system may differ from Linux
1759  // Only apply Y-axis inversion on macOS where this issue occurs
1760 #ifdef Q_OS_MAC
1761  // Convert from Qt coordinate system (top-left) to VTK coordinate system
1762  // (bottom-left) VTK expects Y coordinate where 0 is at bottom, Qt has
1763  // Y=0 at top So we need to invert Y: VTK_Y = windowHeight - Qt_Y Since
1764  // PCLVis::addCaption divides by GetSize(), we pass pixel coordinates If
1765  // GetSize() returns logical pixels (Qt convention), this works
1766  // correctly
1767  float vtkY = logicalH - posY;
1768 
1769  // Final boundary check for VTK coordinates
1770  vtkY = std::max(0.0f, std::min(vtkY, logicalH - 1.0f));
1771 
1772  param.pos = CCVector2(posX, vtkY);
1773 #else
1774  // On Linux, use coordinates directly (no Y-axis inversion needed)
1775  param.pos = CCVector2(posX, posY);
1776 #endif
1777  param.color = ecvColor::FromRgbub(defaultTextColor);
1778  param.color.a = defaultBkgColor.a / 255.0f;
1779  param.text = m_historyMessage.join("\n");
1780  param.text = param.text.trimmed();
1781  param.fontSize = bodyFont.pointSize();
1782  ecvDisplayTools::DrawWidgets(param, false);
1783  }
1784 }
1785 
1787  const ccGLCameraParameters& camera,
1788  int& nearestPointIndex,
1789  double& nearestSquareDist) const {
1790  nearestPointIndex = -1;
1791  nearestSquareDist = -1.0;
1792  {
1793  // back project the clicked point in 3D
1794  CCVector3d clickPosd(clickPos.x, clickPos.y, 0.0);
1795  CCVector3d X(0, 0, 0);
1796  if (!camera.unproject(clickPosd, X)) {
1797  return false;
1798  }
1799 
1800  clickPosd.z = 1.0;
1801  CCVector3d Y(0, 0, 0);
1802  if (!camera.unproject(clickPosd, Y)) {
1803  return false;
1804  }
1805 
1806  CCVector3d xy = (Y - X);
1807  xy.normalize();
1808 
1809  for (unsigned i = 0; i < size(); ++i) {
1810  const PickedPoint& pp = getPickedPoint(i);
1811  if (pp.markerScale == 0) {
1812  // never displayed
1813  continue;
1814  }
1815 
1816  const CCVector3 P = pp.getPointPosition();
1817 
1818  // warning: we have to handle the relative GL transformation!
1819  ccGLMatrix trans;
1820  bool noGLTrans =
1821  pp.entity()
1822  ? !pp.entity()->getAbsoluteGLTransformation(trans)
1823  : true;
1824 
1825  CCVector3d Q2D;
1826  bool insideFrustum = false;
1827  if (noGLTrans) {
1828  camera.project(P, Q2D, &insideFrustum);
1829  } else {
1830  CCVector3 P3D = P;
1831  trans.apply(P3D);
1832  camera.project(P3D, Q2D, &insideFrustum);
1833  }
1834 
1835  if (!insideFrustum) {
1836  continue;
1837  }
1838 
1839  // closest distance to XY
1840  CCVector3d XP = (P.toDouble() - X);
1841  double squareDist = (XP - XP.dot(xy) * xy).norm2();
1842 
1843  if (squareDist <=
1844  static_cast<double>(pp.markerScale) * pp.markerScale) {
1845  if (nearestPointIndex < 0 || squareDist < nearestSquareDist) {
1846  nearestSquareDist = squareDist;
1847  nearestPointIndex = i;
1848  }
1849  }
1850  }
1851  }
1852 
1853  return (nearestPointIndex >= 0);
1854 }
Vector2Tpl< PointCoordinateType > CCVector2
Default 2D Vector.
Definition: CVGeom.h:780
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 name
int count
math::float3 position
math::float2 uv
#define QTCOMPAT_FONTMETRICS_WIDTH(fm, text)
Definition: QtCompat.h:339
void * X
Definition: SmallVector.cpp:45
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:36
Type y
Definition: CVGeom.h:36
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
double normd() const
Returns vector norm (forces double precision output)
Definition: CVGeom.h:426
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm() const
Returns vector norm.
Definition: CVGeom.h:424
Vector3Tpl cross(const Vector3Tpl &v) const
Cross product.
Definition: CVGeom.h:412
Vector3Tpl< double > toDouble() const
Cast operator to a double vector (explicit call version)
Definition: CVGeom.h:255
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
Type angle_rad(const Vector3Tpl &v) const
Returns the angle to another vector (in radians - in [0, pi].
Definition: CVGeom.h:497
bool addPickedPoint(ccGenericPointCloud *cloud, unsigned pointIndex, bool entityCenter=false)
Adds a point to this label.
Definition: ecv2DLabel.cpp:400
const PickedPoint & getPickedPoint(unsigned index) const
Returns a given point.
Definition: ecv2DLabel.h:194
QString m_sphereIdfix
Definition: ecv2DLabel.h:355
float m_screenPos[2]
close button ROI
Definition: ecv2DLabel.h:341
cc2DLabel(QString name=QString("label"))
Default constructor.
Definition: ecv2DLabel.cpp:120
QRect m_labelROI
label ROI
Definition: ecv2DLabel.h:333
bool m_dispIn2D
Whether to display the label in 2D.
Definition: ecv2DLabel.h:350
float m_relMarkerScale
Relative marker scale.
Definition: ecv2DLabel.h:353
virtual bool move2D(int x, int y, int dx, int dy, int screenWidth, int screenHeight) override
Called on mouse move (for 2D interactors)
Definition: ecv2DLabel.cpp:230
QString getTitle(int precision) const
Returns the (3D) label title.
Definition: ecv2DLabel.cpp:166
bool toFile_MeOnly(QFile &out, short dataVersion) const override
Save own object data.
Definition: ecv2DLabel.cpp:454
QString m_surfaceIdfix
Definition: ecv2DLabel.h:356
void clear3Dviews()
Definition: ecv2DLabel.cpp:262
QString m_lineID
Definition: ecv2DLabel.h:358
void getLabelInfo2(LabelInfo2 &info) const
Gets two-points label info.
Definition: ecv2DLabel.cpp:643
void clear(bool ignoreDependencies=false, bool ignoreCaption=true)
Clears label.
Definition: ecv2DLabel.cpp:240
short minimumFileVersion_MeOnly() const override
Definition: ecv2DLabel.cpp:504
virtual bool acceptClick(int x, int y, Qt::MouseButton button) override
Called on mouse click.
Definition: ecv2DLabel.cpp:832
void updateLabel()
Definition: ecv2DLabel.cpp:312
int m_lastScreenPos[2]
Label position at last display (absolute)
Definition: ecv2DLabel.h:344
bool fromFile_MeOnly(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap) override
Loads own object data.
Definition: ecv2DLabel.cpp:509
unsigned size() const
Returns current size.
Definition: ecv2DLabel.h:74
virtual void onDeletionOf(const ccHObject *obj) override
This method is called when another object is deleted.
Definition: ecv2DLabel.cpp:338
std::vector< PickedPoint > m_pickedPoints
Picked points.
Definition: ecv2DLabel.h:318
QString m_contourIdfix
Definition: ecv2DLabel.h:357
void drawMeOnly2D(CC_DRAW_CONTEXT &context)
Draws the entity only (not its children) - 2D version.
void updateName()
Updates the label 'name'.
Definition: ecv2DLabel.cpp:369
QStringList getLabelContent(int precision) const
Gets label content (as it will be displayed)
Definition: ecv2DLabel.cpp:700
void getLabelInfo3(LabelInfo3 &info) const
Gets three-points label info.
Definition: ecv2DLabel.cpp:659
void setPosition(float x, float y)
Sets relative position.
Definition: ecv2DLabel.cpp:225
virtual void drawMeOnly(CC_DRAW_CONTEXT &context) override
Draws the entity only (not its children)
Definition: ecv2DLabel.cpp:848
bool m_dispPointsLegend
Whether to display the point(s) legend.
Definition: ecv2DLabel.h:347
void clear2Dviews()
Definition: ecv2DLabel.cpp:285
bool pointPicking(const CCVector2d &clickPos, const ccGLCameraParameters &camera, int &nearestPointIndex, double &nearestSquareDist) const
Point (marker) picking.
static QString GetSFValueAsString(const LabelInfo1 &info, int precision)
Returns the SF value as a string.
Definition: ecv2DLabel.cpp:149
void update2DLabelView(CC_DRAW_CONTEXT &context, bool updateScreen=true)
Definition: ecv2DLabel.cpp:329
void clearLabel(bool ignoreCaption=true)
Definition: ecv2DLabel.cpp:303
void update3DLabelView(CC_DRAW_CONTEXT &context, bool updateScreen=true)
Definition: ecv2DLabel.cpp:320
void getLabelInfo1(LabelInfo1 &info) const
Returns one-point label info.
Definition: ecv2DLabel.cpp:600
void drawMeOnly3D(CC_DRAW_CONTEXT &context)
Draws the entity only (not its children) - 3D version.
Definition: ecv2DLabel.cpp:868
QStringList m_historyMessage
Definition: ecv2DLabel.h:335
bool m_showFullBody
Whether to show full label body or not.
Definition: ecv2DLabel.h:324
virtual QString getName() const override
Returns object name.
Definition: ecv2DLabel.cpp:196
virtual bool isVisible() const
Returns whether entity is visible or not.
virtual bool hasDisplayedScalarField() const
Returns whether an active scalar field is available or not.
virtual bool hasColors() const
Returns whether colors are enabled or not.
virtual void lockVisibility(bool state)
Locks/unlocks visibility.
virtual void setVisible(bool state)
Sets entity visibility.
virtual bool hasNormals() const
Returns whether normals are enabled or not.
virtual bool isSelected() const
Returns whether entity is selected or not.
virtual bool isRedraw() const
Returns whether entity is to be redraw.
static ccFacet * Create(cloudViewer::GenericIndexedCloudPersist *cloud, PointCoordinateType maxEdgeLength=0, bool transferOwnership=false, const PointCoordinateType *planeEquation=nullptr)
Creates a facet from a set of points.
Definition: ecvFacet.cpp:167
void apply(float vec[3]) const
Applies transformation to a 3D vector (in place) - float version.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
Generic mesh interface.
A 3D cloud interface with associated features (color, normals, octree, etc.)
virtual const CCVector3 & getPointNormal(unsigned pointIndex) const =0
Returns normal corresponding to a given point.
virtual const ecvColor::Rgb & getPointColor(unsigned pointIndex) const =0
Returns color corresponding to a given point.
static ccGenericPointCloud * ToGenericPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccGenericPointCloud.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
bool getAbsoluteGLTransformation(ccGLMatrix &trans) const
Definition: ecvHObject.cpp:740
virtual short minimumFileVersion_MeOnly() const
virtual bool fromFile_MeOnly(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap)
Loads own object data.
void addDependency(ccHObject *otherObject, int flags, bool additive=true)
Adds a new dependence (additive or not)
Definition: ecvHObject.cpp:455
virtual bool toFile_MeOnly(QFile &out, short dataVersion) const
Save own object data.
QString getViewId() const
Definition: ecvHObject.h:225
@ DP_NOTIFY_OTHER_ON_DELETE
Definition: ecvHObject.h:259
virtual void onDeletionOf(const ccHObject *obj)
This method is called when another object is deleted.
Definition: ecvHObject.cpp:519
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
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
QString m_name
Object name.
Definition: ecvObject.h:219
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
A scalar field associated to display-related parameters.
double getGlobalShift() const
Returns the global shift (if any)
QMultiMap< unsigned, unsigned > LoadedIDMap
Map of loaded unique IDs (old ID --> new ID)
static bool ReadError()
Sends a custom error message (read error) and returns 'false'.
static bool WriteError()
Sends a custom error message (write error) and returns 'false'.
static bool MemoryError()
Sends a custom error message (not enough memory) and returns 'false'.
CCVector3d toGlobal3d(const Vector3Tpl< T > &Plocal) const
Returns the point back-projected into the original coordinates system.
bool isShifted() const
Returns whether the cloud is shifted or not.
Sphere (primitive)
Definition: ecvSphere.h:16
virtual unsigned size() const =0
Returns the number of points.
virtual ScalarType getPointScalarValue(unsigned pointIndex) const =0
Returns the ith point associated scalar value.
A generic 3D point cloud with index-based and presistent access to points.
virtual const CCVector3 * getPointPersistentPtr(unsigned index)=0
Returns the ith point as a persistent pointer.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
virtual unsigned size() const =0
Returns the number of triangles.
const CCVector3 * getPointPersistentPtr(unsigned index) override
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
Definition: ScalarField.h:61
RGB color structure.
Definition: ecvColorTypes.h:49
RGBA color structure.
static void RemoveWidgets(const WIDGETS_PARAMETER &param, bool update=false)
static void GetGLCameraParameters(ccGLCameraParameters &params)
Returns the current OpenGL camera parameters.
static QMainWindow * GetMainWindow()
static QWidget * GetCurrentScreen()
static void DrawWidgets(const WIDGETS_PARAMETER &param, bool update=false)
static void DisplayText(const QString &text, int x, int y, unsigned char align=ALIGN_DEFAULT, float bkgAlpha=0.0f, const unsigned char *rgbColor=nullptr, const QFont *font=nullptr, const QString &id="")
Displays a string at a given 2D position.
static void UpdateScreen()
static QFont GetLabelDisplayFont()
static void GetContext(CC_DRAW_CONTEXT &CONTEXT)
Returns context information.
static const ecvViewportParameters & GetViewportParameters()
Standard parameters for GL displays/viewports.
bool perspectiveView
Perspective view state.
double zFar
Actual perspective 'zFar' value.
static const char POINT_INDEX_1[]
Definition: ecv2DLabel.cpp:45
static const char ENTITY_INDEX_1[]
Definition: ecv2DLabel.cpp:48
static const int c_arrowBaseSize
static const char ENTITY_INDEX_0[]
Definition: ecv2DLabel.cpp:47
static const QString CENTER_STRING
Definition: ecv2DLabel.cpp:43
static const int c_tabMarginY
static const QString MathSymbolDelta
Definition: ecv2DLabel.cpp:36
void AddPointCoordinates(QStringList &body, unsigned pointIndex, ccGenericPointCloud *cloud, int precision, QString pointName=QString())
Definition: ecv2DLabel.cpp:567
static const ecvColor::Rgb c_darkGreen(0, 200, 0)
static const int c_tabMarginX
static const char POINT_INDEX_0[]
Definition: ecv2DLabel.cpp:44
static QSharedPointer< ccFacet > c_unitTriMarker(nullptr)
static QSharedPointer< ccSphere > c_unitPointMarker(nullptr)
static const int c_margin
static const char ENTITY_INDEX_2[]
Definition: ecv2DLabel.cpp:49
static const QString SEPARATOR
Definition: ecv2DLabel.cpp:37
static const char POINT_INDEX_2[]
Definition: ecv2DLabel.cpp:46
#define MACRO_Draw2D(context)
#define MACRO_Draw3D(context)
#define MACRO_Foreground(context)
#define MACRO_VirtualTransEnabled(context)
#define MACRO_FastEntityPicking(context)
#define MACRO_EntityPicking(context)
@ CC_DRAW_2D
@ CC_DRAW_FOREGROUND
@ CC_DRAW_3D
@ CC_ENTITY_PICKING
@ WIDGET_RECTANGLE_2D
@ WIDGET_CAPTION
@ WIDGET_LINE_3D
@ WIDGET_SPHERE
@ WIDGET_T2D
@ WIDGET_POLYLINE
@ WIDGET_POLYGONMESH
ImGuiContext * context
Definition: Window.cpp:76
normal_z y
float
normal_z x
@ POINT_CLOUD
Definition: CVTypes.h:104
float RadiansToDegrees(int radians)
Convert radians to degrees.
Definition: CVMath.h:71
Rgbaf FromRgbub(const Rgbub &color)
RgbaTpl< unsigned char > Rgbaub
4 components, unsigned byte type
constexpr Rgb white(MAX, MAX, MAX)
constexpr Rgba ored(MAX, 0, 0, OPACITY)
constexpr Rgb red(MAX, 0, 0)
Rgbaf FromRgba(const Rgba &color)
constexpr Rgb darkGrey(MAX/2, MAX/2, MAX/2)
RgbTpl< unsigned char > Rgbub
3 components, unsigned byte type
constexpr Rgb yellow(MAX, MAX, 0)
constexpr Rgbub defaultBkgColor(135, 206, 235)
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370
Data table.
int maxBlockPerRow
Maximum number of blocks per row.
int add2x3Block()
Adds a 2x3 block (must be filled!)
int colCount
Number of columns.
int updateColumnsWidthTable(const QFontMetrics &fm)
Updates columns width table.
void setMaxBlockPerRow(int maxBlock)
Sets the maximum number of blocks per row.
Tab(int _maxBlockPerRow=2)
Default constructor.
int rowCount
Number of rows.
std::vector< QStringList > colContent
Columns content.
int blockCount
Number of 2x3 blocks.
std::vector< int > colWidth
Columns width.
ecvColor::Rgbaf color
void setLineWidget(const LineWidget &line)
One-point label info.
Definition: ecv2DLabel.h:217
ccGenericPointCloud * cloud
Definition: ecv2DLabel.h:219
ecvColor::Rgb rgb
Definition: ecv2DLabel.h:223
Two-points label info.
Definition: ecv2DLabel.h:254
ccGenericPointCloud * cloud1
Definition: ecv2DLabel.h:256
ccGenericPointCloud * cloud2
Definition: ecv2DLabel.h:258
Three-points label info.
Definition: ecv2DLabel.h:272
PointCoordinateType area
Definition: ecv2DLabel.h:280
ccGenericPointCloud * cloud1
Definition: ecv2DLabel.h:274
ccGenericPointCloud * cloud2
Definition: ecv2DLabel.h:276
ccGenericPointCloud * cloud3
Definition: ecv2DLabel.h:278
Picked point descriptor.
Definition: ecv2DLabel.h:122
CCVector2d uv
Barycentric coordinates (for triangles)
Definition: ecv2DLabel.h:136
CCVector3 getPointPosition() const
Returns the point position (3D)
Definition: ecv2DLabel.cpp:74
unsigned index
Point/triangle index.
Definition: ecv2DLabel.h:128
QString itemTitle() const
Definition: ecv2DLabel.cpp:51
ccGenericPointCloud * cloudOrVertices() const
Returns the cloud or the mesh vertices.
Definition: ecv2DLabel.cpp:104
ccGenericMesh * mesh
Mesh.
Definition: ecv2DLabel.h:126
bool entityCenterPoint
Entity center mode (index will be invalid)
Definition: ecv2DLabel.h:138
float markerScale
Last known marker scale.
Definition: ecv2DLabel.h:134
ccHObject * entity() const
Returns the associated entity (cloud or mesh)
Definition: ecv2DLabel.cpp:112
ccGenericPointCloud * cloud
Cloud.
Definition: ecv2DLabel.h:124
QString prefix(const char *pointTag) const
Returns the point prefix ('Point' or 'Point@Tri' or 'IDXX Center')
Definition: ecv2DLabel.cpp:61
unsigned getUniqueID() const
Returns the cloud or the mesh unique ID.
Definition: ecv2DLabel.cpp:96
OpenGL camera parameters.
ccGLMatrixd modelViewMat
Model view matrix (GL_MODELVIEW)
bool unproject(const CCVector3d &input2D, CCVector3d &output3D) const
Unprojects a 2D point (+ normalized 'z' coordinate) in 3D.
bool project(const CCVector3d &input3D, CCVector3d &output2D, bool *inFrustum=nullptr) const
Projects a 3D point in 2D (+ normalized 'z' coordinate)
Display context.
int drawingFlags
Drawing options (see below)