1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkProp3DAxisFollower.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 #include "vtkProp3DAxisFollower.h"
16 
17 #include "vtkAxisActor.h"
18 #include "vtkBoundingBox.h"
19 #include "vtkCamera.h"
20 #include "vtkCoordinate.h"
21 #include "vtkMath.h"
22 #include "vtkMatrix4x4.h"
23 #include "vtkObjectFactory.h"
24 #include "vtkProperty.h"
25 #include "vtkTexture.h"
26 #include "vtkTransform.h"
27 #include "vtkViewport.h"
28 
29 #include <cmath>
30 
31 vtkStandardNewMacro(vtkProp3DAxisFollower);
32 
33 // List of vectors per axis (depending on which one needs to be
34 // followed.
35 // Order here is X, Y, and Z.
36 // Set of two axis aligned vectors that would define the Y vector.
37 // Order is MINMIN, MINMAX, MAXMAX, MAXMIN
38 namespace
39 {
40 const double AxisAlignedY[3][4][2][3] = {
41   { { { 0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0 } }, { { 0.0, 1.0, 0.0 }, { 0.0, 0.0, -1.0 } },
42     { { 0.0, -1.0, 0.0 }, { 0.0, 0.0, -1.0 } }, { { 0.0, -1.0, 0.0 }, { 0.0, 0.0, 1.0 } } },
43   { { { 1.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0 } }, { { 1.0, 0.0, 0.0 }, { 0.0, 0.0, -1.0 } },
44     { { -1.0, 0.0, 0.0 }, { 0.0, 0.0, -1.0 } }, { { -1.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0 } } },
45   { { { 1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 } }, { { 1.0, 0.0, 0.0 }, { 0.0, -1.0, 0.0 } },
46     { { -1.0, 0.0, 0.0 }, { 0.0, -1.0, 0.0 } }, { { -1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 } } }
47 };
48 }
49 //------------------------------------------------------------------------------
50 // Creates a follower with no camera set
vtkProp3DAxisFollower()51 vtkProp3DAxisFollower::vtkProp3DAxisFollower()
52 {
53   this->AutoCenter = 1;
54 
55   this->EnableDistanceLOD = 0;
56   this->DistanceLODThreshold = 0.80;
57 
58   this->EnableViewAngleLOD = 1;
59   this->ViewAngleLODThreshold = 0.34;
60 
61   this->ScreenOffsetVector[0] = 0.0;
62   this->ScreenOffsetVector[1] = 10.0;
63 
64   this->Axis = nullptr;
65   this->Viewport = nullptr;
66 
67   this->TextUpsideDown = -1;
68   this->VisibleAtCurrentViewAngle = -1;
69 }
70 
71 //------------------------------------------------------------------------------
72 vtkProp3DAxisFollower::~vtkProp3DAxisFollower() = default;
73 
74 //------------------------------------------------------------------------------
SetAxis(vtkAxisActor * axis)75 void vtkProp3DAxisFollower::SetAxis(vtkAxisActor* axis)
76 {
77   if (!axis)
78   {
79     vtkErrorMacro("Invalid or nullptr axis\n");
80     return;
81   }
82 
83   if (this->Axis != axis)
84   {
85     // \NOTE: Don't increment the ref count of axis as it could lead to
86     // circular references.
87     this->Axis = axis;
88     this->Modified();
89   }
90 }
91 
92 //------------------------------------------------------------------------------
GetAxis()93 vtkAxisActor* vtkProp3DAxisFollower::GetAxis()
94 {
95   return this->Axis;
96 }
97 
98 //------------------------------------------------------------------------------
SetViewport(vtkViewport * vp)99 void vtkProp3DAxisFollower::SetViewport(vtkViewport* vp)
100 {
101   if (this->Viewport != vp)
102   {
103     // \NOTE: Don't increment the ref count of vtkViewport as it could lead to
104     // circular references.
105     this->Viewport = vp;
106     this->Modified();
107   }
108 }
109 
110 //------------------------------------------------------------------------------
GetViewport()111 vtkViewport* vtkProp3DAxisFollower::GetViewport()
112 {
113   return this->Viewport;
114 }
115 
116 //------------------------------------------------------------------------------
CalculateOrthogonalVectors(double rX[3],double rY[3],double rZ[3],vtkAxisActor * axis,double * dop,vtkViewport * viewport)117 void vtkProp3DAxisFollower::CalculateOrthogonalVectors(
118   double rX[3], double rY[3], double rZ[3], vtkAxisActor* axis, double* dop, vtkViewport* viewport)
119 {
120   if (!rX || !rY || !rZ)
121   {
122     vtkErrorMacro("Invalid or nullptr direction vectors\n");
123     return;
124   }
125 
126   if (!axis)
127   {
128     vtkErrorMacro("Invalid or nullptr axis\n");
129     return;
130   }
131 
132   if (!dop)
133   {
134     vtkErrorMacro("Invalid or nullptr direction of projection vector\n");
135     return;
136   }
137 
138   if (!viewport)
139   {
140     vtkErrorMacro("Invalid or nullptr renderer\n");
141     return;
142   }
143 
144   vtkMatrix4x4* cameraMatrix = this->Camera->GetViewTransformMatrix();
145 
146   vtkCoordinate* c1Axis = axis->GetPoint1Coordinate();
147   vtkCoordinate* c2Axis = axis->GetPoint2Coordinate();
148   double* axisPt1 = c1Axis->GetComputedWorldValue(viewport);
149   double* axisPt2 = c2Axis->GetComputedWorldValue(viewport);
150 
151   rX[0] = axisPt2[0] - axisPt1[0];
152   rX[1] = axisPt2[1] - axisPt1[1];
153   rX[2] = axisPt2[2] - axisPt1[2];
154   vtkMath::Normalize(rX);
155 
156   if (rX[0] != dop[0] || rX[1] != dop[1] || rX[2] != dop[2])
157   {
158     // Get Y
159     vtkMath::Cross(rX, dop, rY);
160     vtkMath::Normalize(rY);
161 
162     // Get Z
163     vtkMath::Cross(rX, rY, rZ);
164     vtkMath::Normalize(rZ);
165   }
166   else
167   {
168     vtkMath::Perpendiculars(rX, rY, rZ, 0.);
169   }
170   double a[3], b[3];
171 
172   // Need homogeneous points.
173   double homoPt1[4] = { axisPt1[0], axisPt1[1], axisPt1[2], 1.0 };
174   double homoPt2[4] = { axisPt2[0], axisPt2[1], axisPt2[2], 1.0 };
175 
176   double* viewCoordinatePt1 = cameraMatrix->MultiplyDoublePoint(homoPt1);
177   a[0] = viewCoordinatePt1[0];
178   a[1] = viewCoordinatePt1[1];
179   a[2] = viewCoordinatePt1[2];
180 
181   double* viewCoordinatePt2 = cameraMatrix->MultiplyDoublePoint(homoPt2);
182   b[0] = viewCoordinatePt2[0];
183   b[1] = viewCoordinatePt2[1];
184   b[2] = viewCoordinatePt2[2];
185 
186   // If the text is upside down, we make a 180 rotation to keep it readable.
187   if (this->IsTextUpsideDown(a, b))
188   {
189     this->TextUpsideDown = 1;
190     rX[0] = -rX[0];
191     rX[1] = -rX[1];
192     rX[2] = -rX[2];
193     rZ[0] = -rZ[0];
194     rZ[1] = -rZ[1];
195     rZ[2] = -rZ[2];
196   }
197   else
198   {
199     this->TextUpsideDown = 0;
200   }
201 }
202 
203 //------------------------------------------------------------------------------
AutoScale(vtkViewport * viewport,vtkCamera * camera,double screenSize,double position[3])204 double vtkProp3DAxisFollower::AutoScale(
205   vtkViewport* viewport, vtkCamera* camera, double screenSize, double position[3])
206 {
207   double newScale = 0.0;
208 
209   if (!viewport)
210   {
211     std::cerr << "Invalid or nullptr viewport \n";
212     return newScale;
213   }
214 
215   if (!camera)
216   {
217     std::cerr << "Invalid or nullptr camera \n";
218     return newScale;
219   }
220 
221   if (!position)
222   {
223     std::cerr << "Invalid or nullptr position \n";
224     return newScale;
225   }
226 
227   double factor = 1;
228   if (viewport->GetSize()[1] > 0)
229   {
230     factor = 2.0 * screenSize * tan(vtkMath::RadiansFromDegrees(camera->GetViewAngle() / 2.0)) /
231       viewport->GetSize()[1];
232   }
233 
234   double dist = sqrt(vtkMath::Distance2BetweenPoints(position, camera->GetPosition()));
235   newScale = factor * dist;
236 
237   return newScale;
238 }
239 
240 //------------------------------------------------------------------------------
ComputeMatrix()241 void vtkProp3DAxisFollower::ComputeMatrix()
242 {
243   if (!this->Axis)
244   {
245     vtkErrorMacro("ERROR: Invalid axis\n");
246     return;
247   }
248 
249   if (this->EnableDistanceLOD && !this->TestDistanceVisibility())
250   {
251     this->SetVisibility(0);
252     return;
253   }
254 
255   // check whether or not need to rebuild the matrix
256   if (this->GetMTime() > this->MatrixMTime ||
257     (this->Camera && this->Camera->GetMTime() > this->MatrixMTime))
258   {
259     this->GetOrientation();
260     this->Transform->Push();
261     this->Transform->Identity();
262     this->Transform->PostMultiply();
263     this->Transform->GetMatrix(this->Matrix);
264 
265     double pivotPoint[3] = { this->Origin[0], this->Origin[1], this->Origin[2] };
266 
267     if (this->AutoCenter)
268     {
269       // Don't apply the user matrix when retrieving the center.
270       this->Device->SetUserMatrix(nullptr);
271 
272       double* center = this->Device->GetCenter();
273       pivotPoint[0] = center[0];
274       pivotPoint[1] = center[1];
275       pivotPoint[2] = center[2];
276     }
277 
278     // Move pivot point to origin
279     this->Transform->Translate(-pivotPoint[0], -pivotPoint[1], -pivotPoint[2]);
280     // Scale
281     this->Transform->Scale(this->Scale[0], this->Scale[1], this->Scale[2]);
282 
283     // Rotate
284     this->Transform->RotateY(this->Orientation[1]);
285     this->Transform->RotateX(this->Orientation[0]);
286     this->Transform->RotateZ(this->Orientation[2]);
287 
288     double translation[3] = { 0.0, 0.0, 0.0 };
289     if (this->Axis)
290     {
291       vtkMatrix4x4* matrix = this->InternalMatrix;
292       matrix->Identity();
293       double rX[3], rY[3], rZ[3];
294 
295       this->ComputeRotationAndTranlation(this->Viewport, translation, rX, rY, rZ, this->Axis);
296 
297       vtkMath::Normalize(rX);
298       vtkMath::Normalize(rY);
299       vtkMath::Normalize(rZ);
300 
301       matrix->Element[0][0] = rX[0];
302       matrix->Element[1][0] = rX[1];
303       matrix->Element[2][0] = rX[2];
304       matrix->Element[0][1] = rY[0];
305       matrix->Element[1][1] = rY[1];
306       matrix->Element[2][1] = rY[2];
307       matrix->Element[0][2] = rZ[0];
308       matrix->Element[1][2] = rZ[1];
309       matrix->Element[2][2] = rZ[2];
310 
311       this->Transform->Concatenate(matrix);
312     }
313 
314     this->Transform->Translate(this->Origin[0] + this->Position[0] + translation[0],
315       this->Origin[1] + this->Position[1] + translation[1],
316       this->Origin[2] + this->Position[2] + translation[2]);
317 
318     // Apply user defined matrix last if there is one
319     if (this->UserMatrix)
320     {
321       this->Transform->Concatenate(this->UserMatrix);
322     }
323 
324     this->Transform->PreMultiply();
325     this->Transform->GetMatrix(this->Matrix);
326     this->MatrixMTime.Modified();
327     this->Transform->Pop();
328   }
329 
330   this->SetVisibility(this->VisibleAtCurrentViewAngle);
331 }
332 
333 //------------------------------------------------------------------------------
ComputeRotationAndTranlation(vtkViewport * viewport,double translation[3],double rX[3],double rY[3],double rZ[3],vtkAxisActor * axis)334 void vtkProp3DAxisFollower ::ComputeRotationAndTranlation(vtkViewport* viewport,
335   double translation[3], double rX[3], double rY[3], double rZ[3], vtkAxisActor* axis)
336 {
337   double autoScaleHoriz =
338     this->AutoScale(viewport, this->Camera, this->ScreenOffsetVector[0], this->Position);
339   double autoScaleVert =
340     this->AutoScale(viewport, this->Camera, this->ScreenOffsetVector[1], this->Position);
341 
342   double dop[3];
343   this->Camera->GetDirectionOfProjection(dop);
344   vtkMath::Normalize(dop);
345 
346   this->CalculateOrthogonalVectors(rX, rY, rZ, axis, dop, viewport);
347 
348   double dotVal = vtkMath::Dot(rZ, dop);
349 
350   double origRx[3] = { rX[0], rX[1], rX[2] };
351   double origRy[3] = { rY[0], rY[1], rY[2] };
352 
353   // NOTE: Basically the idea here is that dotVal will be positive
354   // only when we have projection direction aligned with our z directon
355   // and when that happens it means that our Y is inverted.
356   if (dotVal > 0)
357   {
358     rY[0] = -rY[0];
359     rY[1] = -rY[1];
360     rY[2] = -rY[2];
361   }
362 
363   // Check visibility at current view angle.
364   if (this->EnableViewAngleLOD)
365   {
366     this->ExecuteViewAngleVisibility(rZ);
367   }
368 
369   // Since we already stored all the possible Y axes that are geometry aligned,
370   // we compare our vertical vector with these vectors and if it aligns then we
371   // translate in opposite direction.
372   int axisPosition = this->Axis->GetAxisPosition();
373   int vertSign;
374   double vertDotVal1 =
375     vtkMath::Dot(AxisAlignedY[this->Axis->GetAxisType()][axisPosition][0], origRy);
376   double vertDotVal2 =
377     vtkMath::Dot(AxisAlignedY[this->Axis->GetAxisType()][axisPosition][1], origRy);
378 
379   if (fabs(vertDotVal1) > fabs(vertDotVal2))
380   {
381     vertSign = (vertDotVal1 > 0 ? -1 : 1);
382   }
383   else
384   {
385     vertSign = (vertDotVal2 > 0 ? -1 : 1);
386   }
387 
388   int horizSign = this->TextUpsideDown ? -1 : 1;
389   translation[0] = origRy[0] * autoScaleVert * vertSign + origRx[0] * autoScaleHoriz * horizSign;
390   translation[1] = origRy[1] * autoScaleVert * vertSign + origRx[1] * autoScaleHoriz * horizSign;
391   translation[2] = origRy[2] * autoScaleVert * vertSign + origRx[2] * autoScaleHoriz * horizSign;
392 }
393 
394 //------------------------------------------------------------------------------
ComputerAutoCenterTranslation(const double & vtkNotUsed (autoScaleFactor),double translation[3])395 void vtkProp3DAxisFollower::ComputerAutoCenterTranslation(
396   const double& vtkNotUsed(autoScaleFactor), double translation[3])
397 {
398   if (!translation)
399   {
400     vtkErrorMacro("ERROR: Invalid or nullptr translation\n");
401     return;
402   }
403 
404   const double* bounds = this->GetProp3D()->GetBounds();
405 
406   // Offset by half of width.
407   double halfWidth = (bounds[1] - bounds[0]) * 0.5 * this->Scale[0];
408 
409   if (this->TextUpsideDown == 1)
410   {
411     halfWidth = -halfWidth;
412   }
413 
414   if (this->Axis->GetAxisType() == vtkAxisActor::VTK_AXIS_TYPE_X)
415   {
416     translation[0] = translation[0] - halfWidth;
417   }
418   else if (this->Axis->GetAxisType() == vtkAxisActor::VTK_AXIS_TYPE_Y)
419   {
420     translation[1] = translation[1] - halfWidth;
421   }
422   else if (this->Axis->GetAxisType() == vtkAxisActor::VTK_AXIS_TYPE_Z)
423   {
424     translation[2] = translation[2] - halfWidth;
425   }
426   else
427   {
428     // Do nothing.
429   }
430 }
431 
432 //------------------------------------------------------------------------------
TestDistanceVisibility()433 int vtkProp3DAxisFollower::TestDistanceVisibility()
434 {
435   if (!this->Camera->GetParallelProjection())
436   {
437     double cameraClippingRange[2];
438 
439     this->Camera->GetClippingRange(cameraClippingRange);
440 
441     // We are considering the far clip plane for evaluation. In certain
442     // odd conditions it might not work.
443     const double maxVisibleDistanceFromCamera =
444       this->DistanceLODThreshold * (cameraClippingRange[1]);
445 
446     double dist =
447       sqrt(vtkMath::Distance2BetweenPoints(this->Camera->GetPosition(), this->Position));
448 
449     if (dist > maxVisibleDistanceFromCamera)
450     {
451       // Need to make sure we are not looking at a flat axis and therefore should enable it anyway
452       if (this->Axis)
453       {
454         vtkBoundingBox bbox(this->Axis->GetBounds());
455         return (bbox.GetDiagonalLength() > (cameraClippingRange[1] - cameraClippingRange[0])) ? 1
456                                                                                               : 0;
457       }
458       return 0;
459     }
460     else
461     {
462       return 1;
463     }
464   }
465   else
466   {
467     return 1;
468   }
469 }
470 
471 //------------------------------------------------------------------------------
ExecuteViewAngleVisibility(double normal[3])472 void vtkProp3DAxisFollower::ExecuteViewAngleVisibility(double normal[3])
473 {
474   if (!normal)
475   {
476     vtkErrorMacro("ERROR: Invalid or nullptr normal\n");
477     return;
478   }
479 
480   double* cameraPos = this->Camera->GetPosition();
481   double dir[3] = { this->Position[0] - cameraPos[0], this->Position[1] - cameraPos[1],
482     this->Position[2] - cameraPos[2] };
483   vtkMath::Normalize(dir);
484   double dotDir = vtkMath::Dot(dir, normal);
485   if (fabs(dotDir) < this->ViewAngleLODThreshold)
486   {
487     this->VisibleAtCurrentViewAngle = 0;
488   }
489   else
490   {
491     this->VisibleAtCurrentViewAngle = 1;
492   }
493 }
494 
495 //------------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)496 void vtkProp3DAxisFollower::PrintSelf(ostream& os, vtkIndent indent)
497 {
498   this->Superclass::PrintSelf(os, indent);
499 
500   os << indent << "AutoCenter: (" << this->AutoCenter << ")\n";
501   os << indent << "EnableDistanceLOD: (" << this->EnableDistanceLOD << ")\n";
502   os << indent << "DistanceLODThreshold: (" << this->DistanceLODThreshold << ")\n";
503   os << indent << "EnableViewAngleLOD: (" << this->EnableViewAngleLOD << ")\n";
504   os << indent << "ViewAngleLODThreshold: (" << this->ViewAngleLODThreshold << ")\n";
505   os << indent << "ScreenOffsetVector: (" << this->ScreenOffsetVector[0] << " "
506      << this->ScreenOffsetVector[1] << ")\n";
507 
508   if (this->Axis)
509   {
510     os << indent << "Axis: (" << this->Axis << ")\n";
511   }
512   else
513   {
514     os << indent << "Axis: (none)\n";
515   }
516 }
517 
518 //------------------------------------------------------------------------------
ShallowCopy(vtkProp * prop)519 void vtkProp3DAxisFollower::ShallowCopy(vtkProp* prop)
520 {
521   vtkProp3DAxisFollower* f = vtkProp3DAxisFollower::SafeDownCast(prop);
522   if (f != nullptr)
523   {
524     this->SetAutoCenter(f->GetAutoCenter());
525     this->SetEnableDistanceLOD(f->GetEnableDistanceLOD());
526     this->SetDistanceLODThreshold(f->GetDistanceLODThreshold());
527     this->SetEnableViewAngleLOD(f->GetEnableViewAngleLOD());
528     this->SetViewAngleLODThreshold(f->GetViewAngleLODThreshold());
529     this->SetScreenOffsetVector(f->GetScreenOffsetVector());
530     this->SetAxis(f->GetAxis());
531   }
532 
533   // Now do superclass
534   this->Superclass::ShallowCopy(prop);
535 }
536 
537 //------------------------------------------------------------------------------
IsTextUpsideDown(double * a,double * b)538 bool vtkProp3DAxisFollower::IsTextUpsideDown(double* a, double* b)
539 {
540   double angle = vtkMath::RadiansFromDegrees(this->Orientation[2]);
541   return (b[0] - a[0]) * cos(angle) - (b[1] - a[1]) * sin(angle) < 0;
542 }
543 
544 //------------------------------------------------------------------------------
SetScreenOffset(double offset)545 void vtkProp3DAxisFollower::SetScreenOffset(double offset)
546 {
547   this->SetScreenOffsetVector(1, offset);
548 }
549 
550 //------------------------------------------------------------------------------
GetScreenOffset()551 double vtkProp3DAxisFollower::GetScreenOffset()
552 {
553   return this->GetScreenOffsetVector()[1];
554 }
555 
556 //------------------------------------------------------------------------------
RenderOpaqueGeometry(vtkViewport * viewport)557 int vtkProp3DAxisFollower::RenderOpaqueGeometry(vtkViewport* viewport)
558 {
559   this->SetViewport(viewport);
560   return this->Superclass::RenderOpaqueGeometry(viewport);
561 }
562 
563 //------------------------------------------------------------------------------
RenderTranslucentPolygonalGeometry(vtkViewport * viewport)564 int vtkProp3DAxisFollower::RenderTranslucentPolygonalGeometry(vtkViewport* viewport)
565 {
566   this->SetViewport(viewport);
567   return this->Superclass::RenderTranslucentPolygonalGeometry(viewport);
568 }
569 
570 //------------------------------------------------------------------------------
RenderVolumetricGeometry(vtkViewport * viewport)571 int vtkProp3DAxisFollower::RenderVolumetricGeometry(vtkViewport* viewport)
572 {
573   this->SetViewport(viewport);
574   return this->Superclass::RenderVolumetricGeometry(viewport);
575 }
576