Here is a simple example about make vtkImageActor always face current active camera and keep it under a vtkFollower object.
It’s impossible to send image data to vtkFollower directly, so we have to add the feature that adjust orientation of image actor automatically when rendering. To keep the lower status, we need to move it further from camera.
#include <iostream>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkActor.h>
#include <vtkConeSource.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkPolyDataMapper.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTextSource.h>
#include <vtkAxesActor.h>
#include <vtkImageActor.h>
#include <vtkPNGReader.h>
#include <vtkTransform.h>
#include <vtkFollower.h>
#include <vtkImageMapper.h>
#include <vtkBillboardTextActor3D.h>
#include "point.hpp"
#include <vtkJPEGReader.h>
#include <vtkImageSlice.h>
#include <vtkImageMapper3D.h>
#include <vtkCamera.h>
#define vtkSPtr vtkSmartPointer
#define vtkSPtrNew(Var, Type) vtkSPtr<Type> Var = vtkSPtr<Type>::New();
using namespace std;
class ImgFollower: public vtkImageActor
{
public:
vtkTypeMacro(ImgFollower,vtkImageActor);
static ImgFollower *New();
vtkSetMacro( Camera, vtkSmartPointer<vtkCamera> );
virtual void Render(vtkRenderer *ren)
{
vtkImageActor::Render( ren );
this->ComputeMatrix();
}
virtual void ComputeMatrix()
{
// check whether or not need to rebuild the matrix
if ( this->GetMTime() > this->MatrixMTime ||
(this->Camera && this->Camera->GetMTime() > this->MatrixMTime) )
{
this->GetOrientation();
this->Transform->Push();
this->Transform->Identity();
this->Transform->PostMultiply();
this->Transform->Translate(-this->Origin[0],
-this->Origin[1],
-this->Origin[2]);
// scale
this->Transform->Scale(this->Scale[0],
this->Scale[1],
this->Scale[2]);
// rotate
this->Transform->RotateY(this->Orientation[1]);
this->Transform->RotateX(this->Orientation[0]);
this->Transform->RotateZ(this->Orientation[2]);
if (this->Camera)
{
double *pos, *vup, distance;
double Rx[3], Ry[3], Rz[3];
vtkSmartPointer<vtkMatrix4x4> matrix = vtkSmartPointer<vtkMatrix4x4>::New();
// do the rotation
// first rotate y
pos = this->Camera->GetPosition();
vup = this->Camera->GetViewUp();
Point focalPt( this->Camera->GetFocalPoint() );
Point viewDir;
vtkMath::Subtract( focalPt.point, pos, viewDir.point );
viewDir.Unit();
if (this->Camera->GetParallelProjection())
{
this->Camera->GetDirectionOfProjection(Rz);
Rz[0] = -Rz[0];
Rz[1] = -Rz[1];
Rz[2] = -Rz[2];
}
else
{
distance = sqrt(
(pos[0] - this->Position[0])*(pos[0] - this->Position[0]) +
(pos[1] - this->Position[1])*(pos[1] - this->Position[1]) +
(pos[2] - this->Position[2])*(pos[2] - this->Position[2]));
for (int i = 0; i < 3; i++)
{
Rz[i] = (pos[i] - this->Position[i])/distance;
}
}
// We cannot directly use the vup angle since it can be aligned with Rz:
//vtkMath::Cross(vup,Rz,Rx);
//vtkMath::Normalize(Rx);
//vtkMath::Cross(Rz,Rx,Ry);
//instead use the view right angle:
double dop[3], vur[3];
this->Camera->GetDirectionOfProjection(dop);
vtkMath::Cross(dop,vup,vur);
vtkMath::Normalize(vur);
vtkMath::Cross(Rz, vur, Ry);
vtkMath::Normalize(Ry);
vtkMath::Cross(Ry,Rz,Rx);
matrix->Element[0][0] = Rx[0];
matrix->Element[1][0] = Rx[1];
matrix->Element[2][0] = Rx[2];
matrix->Element[0][1] = Ry[0];
matrix->Element[1][1] = Ry[1];
matrix->Element[2][1] = Ry[2];
matrix->Element[0][2] = Rz[0];
matrix->Element[1][2] = Rz[1];
matrix->Element[2][2] = Rz[2];
this->Transform->Concatenate(matrix);
// move the image follower to lower postion for other actor can be above it.
this->Transform->Translate( viewDir.point );
}
// translate to projection reference point PRP
// this is the camera's position blasted through
// the current matrix
this->Transform->Translate(this->Origin[0] + this->Position[0],
this->Origin[1] + this->Position[1],
this->Origin[2] + this->Position[2]);
// apply user defined transform last if there is one
if (this->UserTransform)
{
this->Transform->Concatenate(this->UserTransform->GetMatrix());
}
this->Transform->PreMultiply();
this->Transform->GetMatrix(this->Matrix);
this->MatrixMTime.Modified();
this->Transform->Pop();
}
}
protected:
vtkSmartPointer<vtkCamera> Camera;
};
vtkStandardNewMacro(ImgFollower);
int main()
{
vtkSPtrNew( reader, vtkPNGReader );
reader->SetFileName( "screenshot.png" );
reader->Update();
vtkSPtrNew( imgActor, ImgFollower );
imgActor->SetInputData( reader->GetOutput() );
//imgActor->SetPosition( 0, 0, 0 ); // if we want to keep their relative status unchanged, position need to be same
vtkSmartPointer<vtkAxesActor> axes = vtkSmartPointer<vtkAxesActor>::New();
axes->AxisLabelsOff();
axes->SetTotalLength( 3, 3, 3 );
vtkSmartPointer<vtkTextSource> text = vtkSmartPointer<vtkTextSource>::New();
text->SetText( "12.4" );
text->SetBacking(false);
text->Update();
vtkSPtrNew( mapper, vtkPolyDataMapper );
mapper->SetInputConnection( text->GetOutputPort() );
vtkSPtrNew( follower, vtkFollower );
follower->SetMapper( mapper );
vtkSPtrNew( renderer, vtkRenderer );
renderer->AddActor( axes );
imgActor->SetCamera( renderer->GetActiveCamera() );
follower->SetCamera( renderer->GetActiveCamera() );
renderer->AddActor( imgActor );
renderer->AddActor( follower );
renderer->SetBackground( 0, 0, 0 );
vtkSPtrNew( renderWindow, vtkRenderWindow );
renderWindow->AddRenderer( renderer );
vtkSPtrNew( renderWindowInteractor, vtkRenderWindowInteractor );
renderWindowInteractor->SetRenderWindow( renderWindow );
renderer->ResetCamera();
renderer->ResetCamera();
renderWindow->Render();
renderWindowInteractor->Start();
return 0;
}
Result:
The file point.hpp can be found at https://www.weiy.city/2020/09/project-point-on-line-and-plane-by-special-direction/.