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