ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvConstrainedPolyLineRepresentation.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <vtkActor2D.h>
11 #include <vtkCellArray.h>
12 #include <vtkMath.h>
13 #include <vtkObjectFactory.h>
14 #include <vtkPoints.h>
15 #include <vtkPolyData.h>
16 #include <vtkPolyDataMapper2D.h>
17 #include <vtkPropCollection.h>
18 #include <vtkProperty2D.h>
19 #include <vtkRenderer.h>
20 #include <vtkTextActor.h>
21 #include <vtkTextProperty.h>
22 #include <vtkWindow.h>
23 
24 #include <cmath>
25 #include <cstring>
26 #include <sstream>
27 #include <string>
28 
30 
32  : ShowAngleLabel(1),
33  ShowAngleArc(1),
34  ArcRadius(1.0),
35  Angle(0.0),
36  LabelSuffix(nullptr) {
37  // Initialize angle label actor with default properties
38  // These will be overridden by
39  // cvProtractorTool::applyTextPropertiesToLabel()
40  this->AngleLabelActor = vtkTextActor::New();
41  this->AngleLabelActor->SetTextScaleModeToNone();
42  this->AngleLabelActor->GetTextProperty()->SetColor(1.0, 1.0, 1.0); // White
43  this->AngleLabelActor->GetTextProperty()->SetFontSize(
44  20); // Default font size for angle display
45  this->AngleLabelActor->GetTextProperty()->SetBold(
46  0); // Not bold by default
47  this->AngleLabelActor->GetTextProperty()->SetItalic(0); // Not italic
48  this->AngleLabelActor->GetTextProperty()->SetShadow(
49  1); // Shadow for visibility
50  this->AngleLabelActor->GetTextProperty()->SetFontFamilyToArial();
51  this->AngleLabelActor->SetVisibility(1);
52 
53  // Initialize angle arc geometry
54  this->AngleArcPolyData = vtkPolyData::New();
55  this->AngleArcMapper = vtkPolyDataMapper2D::New();
56  this->AngleArcMapper->SetInputData(this->AngleArcPolyData);
57 
58  this->AngleArcActor = vtkActor2D::New();
59  this->AngleArcActor->SetMapper(this->AngleArcMapper);
60  this->AngleArcActor->GetProperty()->SetColor(1.0, 1.0, 0.0); // Yellow arc
61  this->AngleArcActor->GetProperty()->SetLineWidth(2.0);
62  this->AngleArcActor->SetVisibility(1);
63 }
64 
66  // CRITICAL: Remove actors from renderer before deleting them
67  if (this->Renderer) {
68  if (this->AngleLabelActor) {
69  this->Renderer->RemoveActor2D(this->AngleLabelActor);
70  }
71  if (this->AngleArcActor) {
72  this->Renderer->RemoveActor2D(this->AngleArcActor);
73  }
74  }
75 
76  if (this->LabelSuffix) {
77  delete[] this->LabelSuffix;
78  this->LabelSuffix = nullptr;
79  }
80  if (this->AngleLabelActor) {
81  this->AngleLabelActor->Delete();
82  this->AngleLabelActor = nullptr;
83  }
84  if (this->AngleArcActor) {
85  this->AngleArcActor->Delete();
86  this->AngleArcActor = nullptr;
87  }
88  if (this->AngleArcMapper) {
89  this->AngleArcMapper->Delete();
90  this->AngleArcMapper = nullptr;
91  }
92  if (this->AngleArcPolyData) {
93  this->AngleArcPolyData->Delete();
94  this->AngleArcPolyData = nullptr;
95  }
96 }
97 
99  // Remove actors from old renderer if present
100  if (this->Renderer && this->Renderer != ren) {
101  if (this->AngleLabelActor) {
102  this->Renderer->RemoveActor2D(this->AngleLabelActor);
103  }
104  if (this->AngleArcActor) {
105  this->Renderer->RemoveActor2D(this->AngleArcActor);
106  }
107  }
108 
109  // Call parent to set the renderer
110  this->Superclass::SetRenderer(ren);
111 
112  // Add our custom 2D actors to the new renderer
113  if (ren) {
114  if (this->AngleLabelActor) {
115  ren->AddActor2D(this->AngleLabelActor);
116  }
117  if (this->AngleArcActor) {
118  ren->AddActor2D(this->AngleArcActor);
119  }
120  }
121 }
122 
124  // Call parent class to build the poly line
125  this->Superclass::BuildRepresentation();
126 
127  // Only proceed if we have exactly 3 handles (angle measurement
128  // configuration)
129  if (this->GetNumberOfHandles() != 3) {
130  return;
131  }
132 
133  // Calculate and update angle
134  this->Angle = this->GetAngle();
135 
136  // Update angle label text (following ParaView format)
137  std::ostringstream labelStream;
138  labelStream.precision(2);
139  labelStream << std::fixed << this->Angle << "°";
140 
141  // Append instance label suffix if present (e.g., " #1", " #2")
142  if (this->LabelSuffix) {
143  labelStream << this->LabelSuffix;
144  }
145 
146  this->AngleLabelActor->SetInput(labelStream.str().c_str());
147 
148  // DO NOT set text properties here!
149  // Text properties should be set by
150  // cvProtractorTool::applyTextPropertiesToLabel() after
151  // BuildRepresentation() is called. Setting properties here would override
152  // user-configured settings.
153 
154  // Position label at the arc's center (bisector of the angle)
155  // ALWAYS place label on the acute angle side (< 180 degrees)
156  if (this->Renderer) {
157  double p1[3], center[3], p2[3];
158  this->GetHandlePosition(0, p1);
159  this->GetHandlePosition(1, center);
160  this->GetHandlePosition(2, p2);
161 
162  // Calculate vectors from center to points
163  double vec1[3], vec2[3];
164  vtkMath::Subtract(p1, center, vec1);
165  vtkMath::Subtract(p2, center, vec2);
166  double len1 = vtkMath::Norm(vec1);
167  double len2 = vtkMath::Norm(vec2);
168  vtkMath::Normalize(vec1);
169  vtkMath::Normalize(vec2);
170 
171  // Calculate the bisector direction (average of normalized vectors)
172  double bisector[3];
173  bisector[0] = vec1[0] + vec2[0];
174  bisector[1] = vec1[1] + vec2[1];
175  bisector[2] = vec1[2] + vec2[2];
176  vtkMath::Normalize(bisector);
177 
178  // Check if the angle is > 180 degrees
179  // If so, flip the bisector to point to the acute angle side
180  double dotProduct = vtkMath::Dot(vec1, vec2);
181  double angleRadians =
182  std::acos(std::max(-1.0, std::min(1.0, dotProduct)));
183  double angleDegrees = vtkMath::DegreesFromRadians(angleRadians);
184 
185  // If angle > 180 degrees, the bisector points to the obtuse side
186  // We need to flip it to point to the acute side
187  if (angleDegrees > 180.0) {
188  bisector[0] = -bisector[0];
189  bisector[1] = -bisector[1];
190  bisector[2] = -bisector[2];
191  }
192 
193  // Calculate adaptive arc radius (MUST match BuildAngleArc logic)
194  double minRayLength = std::min(len1, len2);
195  double radiusPercentage;
196 
197  if (angleDegrees < 30.0) {
198  // Small angle: larger radius for better visibility
199  radiusPercentage = 0.30 - (angleDegrees / 30.0) * 0.05;
200  } else if (angleDegrees < 90.0) {
201  // Medium angle
202  radiusPercentage = 0.25 - ((angleDegrees - 30.0) / 60.0) * 0.05;
203  } else {
204  // Large angle: smaller radius
205  radiusPercentage = 0.20 - ((angleDegrees - 90.0) / 90.0) * 0.05;
206  }
207 
208  double adaptiveRadius =
209  std::min(this->ArcRadius, minRayLength * radiusPercentage);
210  adaptiveRadius = std::max(adaptiveRadius, minRayLength * 0.01);
211  adaptiveRadius = std::min(adaptiveRadius, minRayLength * 0.40);
212 
213  // Adaptive label distance: further away for small angles to avoid ray
214  // occlusion For small angles (< 30°): use 2.5x arc radius to clear the
215  // tight rays For medium angles (30° - 90°): use 2.0x arc radius For
216  // large angles (> 90°): use 1.5x arc radius (rays are well separated)
217  double labelDistanceMultiplier;
218  if (angleDegrees < 30.0) {
219  // Small angle: need more distance to avoid ray occlusion
220  // Scale from 3.0x at 0° to 2.5x at 30°
221  labelDistanceMultiplier = 3.0 - (angleDegrees / 30.0) * 0.5;
222  } else if (angleDegrees < 90.0) {
223  // Medium angle: moderate distance
224  // Scale from 2.5x at 30° to 1.8x at 90°
225  labelDistanceMultiplier =
226  2.5 - ((angleDegrees - 30.0) / 60.0) * 0.7;
227  } else {
228  // Large angle: rays are well separated, can be closer
229  // Scale from 1.8x at 90° to 1.5x at 180°
230  labelDistanceMultiplier =
231  1.8 - ((angleDegrees - 90.0) / 90.0) * 0.3;
232  }
233 
234  // Position label along the bisector at adaptive distance
235  // This ensures label is always clearly visible and not occluded by rays
236  double labelDistance = adaptiveRadius * labelDistanceMultiplier;
237  double labelPos[3];
238  labelPos[0] = center[0] + bisector[0] * labelDistance;
239  labelPos[1] = center[1] + bisector[1] * labelDistance;
240  labelPos[2] = center[2] + bisector[2] * labelDistance;
241 
242  // Convert world coordinates to display coordinates
243  this->Renderer->SetWorldPoint(labelPos[0], labelPos[1], labelPos[2],
244  1.0);
245  this->Renderer->WorldToDisplay();
246  double* displayCoord = this->Renderer->GetDisplayPoint();
247 
248  // Calculate text width to center the label
249  // Get text bounding box in display coordinates
250  double bounds[4]; // [xmin, xmax, ymin, ymax]
251  this->AngleLabelActor->GetBoundingBox(this->Renderer, bounds);
252 
253  // Calculate text width
254  double textWidth = bounds[1] - bounds[0];
255 
256  // Center the text horizontally and add small vertical offset
257  // Note: vtkTextActor's position is at the bottom-left of the text
258  // Subtract half the text width to center it on the display coordinate
259  double centeredX = displayCoord[0] - textWidth / 2.0;
260  double centeredY =
261  displayCoord[1] + 5; // Small offset for visual separation
262 
263  this->AngleLabelActor->SetPosition(centeredX, centeredY);
264  }
265 
266  // Build angle arc
267  if (this->ShowAngleArc) {
268  this->BuildAngleArc();
269  }
270 
271  // Control visibility (respect both individual flags and representation
272  // visibility)
273  vtkTypeBool repVisible = this->GetVisibility();
274  this->AngleLabelActor->SetVisibility(repVisible && this->ShowAngleLabel);
275  this->AngleArcActor->SetVisibility(repVisible && this->ShowAngleArc);
276 }
277 
279  // Following ParaView pqAnglePropertyWidget::updateLabels() (lines 155-161)
280  if (this->GetNumberOfHandles() < 3) {
281  return 0.0;
282  }
283 
284  // Get the three points
285  double p1[3], center[3], p2[3];
286  this->GetHandlePosition(0, p1); // Point 1
287  this->GetHandlePosition(1, center); // Center (vertex)
288  this->GetHandlePosition(2, p2); // Point 2
289 
290  // Calculate vectors from center
291  double vec1[3], vec2[3];
292  vtkMath::Subtract(p1, center, vec1);
293  vtkMath::Subtract(p2, center, vec2);
294 
295  // Calculate angle using dot product
296  // angle = acos(vec1·vec2 / (|vec1| * |vec2|))
297  double norm1 = vtkMath::Norm(vec1);
298  double norm2 = vtkMath::Norm(vec2);
299 
300  if (norm1 < 1e-10 || norm2 < 1e-10) {
301  return 0.0; // Avoid division by zero
302  }
303 
304  double dotProduct = vtkMath::Dot(vec1, vec2);
305  double cosAngle = dotProduct / (norm1 * norm2);
306 
307  // Clamp to avoid numerical errors with acos
308  cosAngle = std::max(-1.0, std::min(1.0, cosAngle));
309 
310  // Convert from radians to degrees (ParaView uses
311  // vtkMath::DegreesFromRadians)
312  double angleRadians = std::acos(cosAngle);
313  return vtkMath::DegreesFromRadians(angleRadians);
314 }
315 
317  if (this->GetNumberOfHandles() < 3 || !this->Renderer) {
318  return;
319  }
320 
321  // Get the three points
322  double p1[3], center[3], p2[3];
323  this->GetHandlePosition(0, p1);
324  this->GetHandlePosition(1, center);
325  this->GetHandlePosition(2, p2);
326 
327  // Calculate vectors
328  double vec1[3], vec2[3];
329  vtkMath::Subtract(p1, center, vec1);
330  vtkMath::Subtract(p2, center, vec2);
331  double len1 = vtkMath::Norm(vec1);
332  double len2 = vtkMath::Norm(vec2);
333  vtkMath::Normalize(vec1);
334  vtkMath::Normalize(vec2);
335 
336  // Calculate the angle between rays
337  double dotProduct = vtkMath::Dot(vec1, vec2);
338  double angleRadians = std::acos(std::max(-1.0, std::min(1.0, dotProduct)));
339  double angleDegrees = vtkMath::DegreesFromRadians(angleRadians);
340 
341  // Adaptive arc radius based on angle size (VTK protractor style)
342  // Small angles: larger radius for better visibility
343  // Large angles: smaller radius to stay close to vertex
344  double minRayLength = std::min(len1, len2);
345  double radiusPercentage;
346 
347  if (angleDegrees < 30.0) {
348  // Small angle (0° - 30°): use 25-30% of ray length
349  // Larger radius makes small angles more visible
350  radiusPercentage = 0.30 - (angleDegrees / 30.0) * 0.05;
351  } else if (angleDegrees < 90.0) {
352  // Medium angle (30° - 90°): use 20-25% of ray length
353  radiusPercentage = 0.25 - ((angleDegrees - 30.0) / 60.0) * 0.05;
354  } else {
355  // Large angle (90° - 180°): use 15-20% of ray length
356  // Smaller radius keeps arc near vertex when rays are far apart
357  radiusPercentage = 0.20 - ((angleDegrees - 90.0) / 90.0) * 0.05;
358  }
359 
360  double adaptiveRadius =
361  std::min(this->ArcRadius, minRayLength * radiusPercentage);
362 
363  // Ensure radius is not too small (at least 1% of ray length)
364  // and not too large (at most 40% for very small angles)
365  adaptiveRadius = std::max(adaptiveRadius, minRayLength * 0.01);
366  adaptiveRadius = std::min(adaptiveRadius, minRayLength * 0.40);
367 
368  // Create arc geometry in display coordinates
369  vtkNew<vtkPoints> arcPoints;
370  vtkNew<vtkCellArray> arcLines;
371 
372  const int numArcPoints = 30; // More points for smoother arc
373 
374  for (int i = 0; i <= numArcPoints; ++i) {
375  double t = static_cast<double>(i) / numArcPoints;
376 
377  // Interpolate between vec1 and vec2 using spherical interpolation
378  double cosTheta = vtkMath::Dot(vec1, vec2);
379  double theta = std::acos(std::max(-1.0, std::min(1.0, cosTheta)));
380 
381  double sinTheta = std::sin(theta);
382  double a = (sinTheta > 1e-10) ? std::sin((1.0 - t) * theta) / sinTheta
383  : 1.0 - t;
384  double b = (sinTheta > 1e-10) ? std::sin(t * theta) / sinTheta : t;
385 
386  double interpVec[3];
387  for (int j = 0; j < 3; ++j) {
388  interpVec[j] = a * vec1[j] + b * vec2[j];
389  }
390  vtkMath::Normalize(interpVec);
391 
392  // Scale by adaptive arc radius and offset from center (world
393  // coordinates)
394  double arcPointWorld[3];
395  for (int j = 0; j < 3; ++j) {
396  arcPointWorld[j] = center[j] + adaptiveRadius * interpVec[j];
397  }
398 
399  // Convert world coordinates to display coordinates
400  this->Renderer->SetWorldPoint(arcPointWorld[0], arcPointWorld[1],
401  arcPointWorld[2], 1.0);
402  this->Renderer->WorldToDisplay();
403  double* displayCoord = this->Renderer->GetDisplayPoint();
404 
405  // Insert as 2D point (Z=0 for 2D actor)
406  arcPoints->InsertNextPoint(displayCoord[0], displayCoord[1], 0.0);
407  }
408 
409  // Create polyline
410  arcLines->InsertNextCell(numArcPoints + 1);
411  for (int i = 0; i <= numArcPoints; ++i) {
412  arcLines->InsertCellPoint(i);
413  }
414 
415  this->AngleArcPolyData->SetPoints(arcPoints);
416  this->AngleArcPolyData->SetLines(arcLines);
417  this->AngleArcPolyData->Modified();
418 }
419 
421  // Call parent to set visibility of line and handles
422  this->Superclass::SetVisibility(visible);
423 
424  // CRITICAL: Also control visibility of our custom 2D actors
425  // Force hide regardless of individual flags when representation is hidden
426  if (this->AngleLabelActor) {
427  if (visible) {
428  this->AngleLabelActor->SetVisibility(this->ShowAngleLabel);
429  } else {
430  this->AngleLabelActor->SetVisibility(0);
431  }
432  }
433  if (this->AngleArcActor) {
434  if (visible) {
435  this->AngleArcActor->SetVisibility(this->ShowAngleArc);
436  } else {
437  this->AngleArcActor->SetVisibility(0);
438  }
439  }
440 }
441 
443  // Call parent to get its 2D actors
444  this->Superclass::GetActors2D(pc);
445 
446  // Add our custom 2D actors so VTK knows about them
447  if (this->AngleLabelActor) {
448  pc->AddItem(this->AngleLabelActor);
449  }
450  if (this->AngleArcActor) {
451  pc->AddItem(this->AngleArcActor);
452  }
453 }
454 
456  vtkWindow* w) {
457  this->Superclass::ReleaseGraphicsResources(w);
458  if (this->AngleLabelActor) {
459  this->AngleLabelActor->ReleaseGraphicsResources(w);
460  }
461  if (this->AngleArcActor) {
462  this->AngleArcActor->ReleaseGraphicsResources(w);
463  }
464 }
465 
467  int count = this->Superclass::RenderOverlay(viewport);
468 
469  if (this->ShowAngleLabel && this->AngleLabelActor) {
470  count += this->AngleLabelActor->RenderOverlay(viewport);
471  }
472  if (this->ShowAngleArc && this->AngleArcActor) {
473  count += this->AngleArcActor->RenderOverlay(viewport);
474  }
475 
476  return count;
477 }
478 
480  vtkViewport* viewport) {
481  int count = this->Superclass::RenderOpaqueGeometry(viewport);
482 
483  if (this->ShowAngleArc && this->AngleArcActor) {
484  count += this->AngleArcActor->RenderOpaqueGeometry(viewport);
485  }
486 
487  return count;
488 }
489 
491  vtkViewport* viewport) {
492  int count = this->Superclass::RenderTranslucentPolygonalGeometry(viewport);
493  return count;
494 }
495 
496 vtkTypeBool
498  return this->Superclass::HasTranslucentPolygonalGeometry();
499 }
500 
501 // Compatibility API implementation for Display coordinates
503  double pos[3]) {
504  if (this->Renderer && this->GetNumberOfHandles() > 0) {
505  double worldPos[3];
506  this->Renderer->SetDisplayPoint(pos);
507  this->Renderer->DisplayToWorld();
508  this->Renderer->GetWorldPoint(worldPos);
509  // Normalize homogeneous coordinate
510  if (worldPos[3] != 0.0) {
511  worldPos[0] /= worldPos[3];
512  worldPos[1] /= worldPos[3];
513  worldPos[2] /= worldPos[3];
514  }
515  this->SetHandlePosition(0, worldPos);
516  }
517 }
518 
520  double pos[3]) {
521  if (this->Renderer && this->GetNumberOfHandles() > 0) {
522  double worldPos[3];
523  this->GetHandlePosition(0, worldPos);
524  this->Renderer->SetWorldPoint(worldPos[0], worldPos[1], worldPos[2],
525  1.0);
526  this->Renderer->WorldToDisplay();
527  this->Renderer->GetDisplayPoint(pos);
528  } else {
529  pos[0] = pos[1] = pos[2] = 0.0;
530  }
531 }
532 
534  double pos[3]) {
535  if (this->Renderer && this->GetNumberOfHandles() > 1) {
536  double worldPos[3];
537  this->Renderer->SetDisplayPoint(pos);
538  this->Renderer->DisplayToWorld();
539  this->Renderer->GetWorldPoint(worldPos);
540  if (worldPos[3] != 0.0) {
541  worldPos[0] /= worldPos[3];
542  worldPos[1] /= worldPos[3];
543  worldPos[2] /= worldPos[3];
544  }
545  this->SetHandlePosition(1, worldPos);
546  }
547 }
548 
550  double pos[3]) {
551  if (this->Renderer && this->GetNumberOfHandles() > 1) {
552  double worldPos[3];
553  this->GetHandlePosition(1, worldPos);
554  this->Renderer->SetWorldPoint(worldPos[0], worldPos[1], worldPos[2],
555  1.0);
556  this->Renderer->WorldToDisplay();
557  this->Renderer->GetDisplayPoint(pos);
558  } else {
559  pos[0] = pos[1] = pos[2] = 0.0;
560  }
561 }
562 
564  double pos[3]) {
565  if (this->Renderer && this->GetNumberOfHandles() > 2) {
566  double worldPos[3];
567  this->Renderer->SetDisplayPoint(pos);
568  this->Renderer->DisplayToWorld();
569  this->Renderer->GetWorldPoint(worldPos);
570  if (worldPos[3] != 0.0) {
571  worldPos[0] /= worldPos[3];
572  worldPos[1] /= worldPos[3];
573  worldPos[2] /= worldPos[3];
574  }
575  this->SetHandlePosition(2, worldPos);
576  }
577 }
578 
580  double pos[3]) {
581  if (this->Renderer && this->GetNumberOfHandles() > 2) {
582  double worldPos[3];
583  this->GetHandlePosition(2, worldPos);
584  this->Renderer->SetWorldPoint(worldPos[0], worldPos[1], worldPos[2],
585  1.0);
586  this->Renderer->WorldToDisplay();
587  this->Renderer->GetDisplayPoint(pos);
588  } else {
589  pos[0] = pos[1] = pos[2] = 0.0;
590  }
591 }
592 
593 // Compatibility: Get handle representations
594 vtkHandleRepresentation*
596  // vtkCurveRepresentation doesn't expose GetHandleRepresentation, so we
597  // return nullptr The handles are managed internally by
598  // vtkPolyLineRepresentation
599  return nullptr;
600 }
601 
602 vtkHandleRepresentation*
604  return nullptr;
605 }
606 
607 vtkHandleRepresentation*
609  return nullptr;
610 }
int count
Extended PolyLineRepresentation adding angle display functionality.
void SetVisibility(vtkTypeBool visible) override
Override SetVisibility to also control arc and label actors.
int RenderOpaqueGeometry(vtkViewport *viewport) override
vtkHandleRepresentation * GetPoint1Representation()
Compatibility: Get handle representations.
void SetRenderer(vtkRenderer *ren) override
Set the renderer for this representation.
double GetAngle()
Calculate the angle formed by the three handles.
int RenderTranslucentPolygonalGeometry(vtkViewport *viewport) override
void GetActors2D(vtkPropCollection *pc) override
Render the angle representation.
void BuildRepresentation() override
Build the representation for the angle measurement.
void ReleaseGraphicsResources(vtkWindow *w) override
Release graphics resources.
vtkStandardNewMacro(cvConstrainedPolyLineRepresentation)
a[190]
const double * e