We know that there are three ways to adjust 3D model, rotate, translate and scale. If these transforms act on camera, we will see the model stand still. The status of camera in vtk can be defined by focal point, position and viewUp, so we can change these parameters to achive our goal.

using namespace std;

#define vtkSPtr vtkSmartPointer
#define vtkSPtrNew(Var, Type) vtkSPtr<Type> Var = vtkSPtr<Type>::New();

vtkSPtr<vtkTransform> m_UserTransform;
vtkSPtr<vtkImageActor> m_ImageActor;
vtkSPtr<vtkRenderer> m_Renderer;
vtkSPtr<vtkRenderWindow> m_RenderWindow;

class CustomStyle : public vtkInteractorStyleTrackballCamera
{
public:
    static CustomStyle *New(){ return new CustomStyle(); }

    void Rotate() override
    {
        vtkInteractorStyleTrackballCamera::Rotate();
    }
    void Pan() override
    {
        vtkInteractorStyleTrackballCamera::Pan();
    }
    void OnMouseWheelForward() override
    {
        vtkInteractorStyleTrackballCamera::OnMouseWheelForward();
    }
    void OnMouseWheelBackward() override
    {
        vtkInteractorStyleTrackballCamera::OnMouseWheelBackward();
    }

    void InitCamera()
    {
        double *bounds = m_ImageActor->GetDisplayBounds();
        vtkCamera *camera = m_Renderer->GetActiveCamera();
        PointStruct focalPt( (bounds[0] + bounds[1])/2.0, (bounds[2] + bounds[3])/2.0, bounds[5] );
        PointStruct pos( (bounds[0] + bounds[1])/2.0, (bounds[2] + bounds[3])/2.0, bounds[5] + 0.1 );
        camera->SetViewUp( 0, 1, 0 );
        m_UserTransform->TransformPoint( focalPt.point, focalPt.point );
        camera->SetFocalPoint( focalPt.point );
        pos = focalPt;
        pos[2] += 10;

        camera->SetParallelProjection( true );
        camera->SetPosition( pos.point );
        camera->Modified();
        m_RenderWindow->Render();
    }
};

int main()
{
    setbuf( stdout, nullptr );

    vtkSPtrNew( reader, vtkPNGReader );
    reader->SetFileName( "/Users/weiyang/Desktop/person.png" );
    reader->Update();

    m_ImageActor = vtkSmartPointer<vtkImageActor>::New();
    m_ImageActor->SetInputData( reader->GetOutput() );

    m_UserTransform = vtkSmartPointer<vtkTransform>::New();
    m_UserTransform->PreMultiply();
    m_UserTransform->Identity();
    m_UserTransform->Scale( 0.005, 0.005, 0.005 );
    m_ImageActor->SetUserTransform( m_UserTransform );

    vtkSPtrNew( axesActor, vtkAxesActor );

    m_Renderer = vtkSPtr<vtkRenderer>::New();
    m_Renderer->AddActor( m_ImageActor );
    m_Renderer->AddActor( axesActor );
    m_Renderer->SetBackground(.4, .5, .6);
    m_Renderer->ResetCamera();

    // Setup render window
    m_RenderWindow = vtkSPtr<vtkRenderWindow>::New();
    m_RenderWindow->AddRenderer( m_Renderer );
    m_RenderWindow->SetSize( 500, 500 );

    // Setup render window interactor
    vtkSPtrNew( renderWindowInteractor, vtkRenderWindowInteractor );

    vtkSPtrNew( CStyle, CustomStyle );
    renderWindowInteractor->SetInteractorStyle( CStyle );
    CStyle->InitCamera();

    // Render and start interaction
    renderWindowInteractor->SetRenderWindow( m_RenderWindow );
    renderWindowInteractor->Start();
    return 0;
}

Translate

Move the image and the camera at the same time, it seems that the image didn’t change position but the axes actor moved. We need to create a moving transform to replace camera.

    void Pan() override
    {
        vtkSPtrNew( offTrans, vtkTransform );
        vtkCamera *camera = m_Renderer->GetActiveCamera();
        PointStruct lastEventPos( Interactor->GetLastEventPosition()[0], Interactor->GetLastEventPosition()[1], 0 );
        PointStruct eventPos( Interactor->GetEventPosition()[0], Interactor->GetEventPosition()[1], 0 );
        m_Renderer->SetDisplayPoint( lastEventPos.point );
        m_Renderer->DisplayToWorld();
        m_Renderer->GetWorldPoint( lastEventPos.point );
        m_Renderer->SetDisplayPoint( eventPos.point );
        m_Renderer->DisplayToWorld();
        m_Renderer->GetWorldPoint( eventPos.point );
        PointStruct vec = eventPos - lastEventPos;
        offTrans->Translate( (-vec).point );
        offTrans->Update();

        vtkSPtrNew( newTrans, vtkTransform );
        newTrans->Concatenate( offTrans );
        newTrans->Concatenate( m_UserTransform );
        m_ImageActor->SetUserTransform( newTrans );
        m_UserTransform = newTrans;

        PointStruct focalPt( camera->GetFocalPoint() );
        PointStruct pos( camera->GetPosition() );
        PointStruct projectDir = pos - focalPt;
        projectDir.Unit();
        double dis = camera->GetDistance();
        offTrans->TransformPoint( focalPt.point, focalPt.point );
        pos = focalPt + projectDir * dis;
        camera->SetFocalPoint( focalPt.point );
        camera->SetPosition( pos.point );
        camera->Modified();
        m_Renderer->ResetCameraClippingRange();
    }

Rotate

We have to learn some basic knowledge about camera for rotating operation. It moved camera around the object which is watched when user rotates camera, so we need to create our elevation and azimuth functions.

    void Rotate() override
    {
        int dx = Interactor->GetEventPosition()[0] - Interactor->GetLastEventPosition()[0];
        int dy = Interactor->GetEventPosition()[1] - Interactor->GetLastEventPosition()[1];
        int *size = this->CurrentRenderer->GetRenderWindow()->GetSize();
        double delta_evelation = 20.0 / size[1];
        double delta_azimuth = 20.0 / size[0];
        double angleAzimuth = dx * delta_azimuth * MotionFactor;
        double angleElevation = dy * delta_evelation * MotionFactor;
        AzimuthActor( angleAzimuth );
        ElevationActor( angleElevation );
    }

    void ElevationActor( const double &angle )
    {
        PointStruct xAxes(1, 0, 0), yAxes(0, 1, 0), zAxes(0, 0, 1);
        m_UserTransform->TransformVector( xAxes.point, xAxes.point );
        m_UserTransform->TransformVector( yAxes.point, yAxes.point );
        m_UserTransform->TransformVector( zAxes.point, zAxes.point );

        vtkSPtrNew( rotateTrans, vtkTransform );
        PointStruct rotateCenter( 0, 0, 0 );
        rotateTrans->Translate( -rotateCenter[0], -rotateCenter[1], -rotateCenter[2] );
        rotateTrans->RotateWXYZ( angle, xAxes.point );
        rotateTrans->Translate( rotateCenter[0], rotateCenter[1], rotateCenter[2] );
        rotateTrans->Update();

        vtkSPtrNew( transform, vtkTransform );
        transform->Concatenate( rotateTrans );
        transform->Concatenate( m_UserTransform );
        transform->Update();
        m_ImageActor->SetUserTransform( transform );
        m_UserTransform = transform;

        AdjustCameraAfterRotateImage( rotateTrans );
    }

    void AzimuthActor( const double &angle)
    {
        PointStruct xAxes(1, 0, 0), yAxes(0, 1, 0), zAxes(0, 0, 1);

        m_UserTransform->TransformVector( xAxes.point, xAxes.point );
        m_UserTransform->TransformVector( yAxes.point, yAxes.point );
        m_UserTransform->TransformVector( zAxes.point, zAxes.point );

        vtkSPtrNew( rotateTrans, vtkTransform );
        rotateTrans->RotateWXYZ( angle, yAxes.point );
        rotateTrans->Update();
        vtkSPtrNew( transform, vtkTransform );
        transform->Concatenate( rotateTrans );
        transform->Concatenate( m_UserTransform );
        transform->Update();
        m_ImageActor->SetUserTransform( transform );
        m_UserTransform = transform;

        AdjustCameraAfterRotateImage( rotateTrans );
    }
    void AdjustCameraAfterRotateImage( const vtkSPtr<vtkTransform> &rotateTrans )
    {
        vtkCamera *camera = m_Renderer->GetActiveCamera();
        PointStruct focalPt;
        camera->GetFocalPoint( focalPt.point );
        PointStruct pos;
        camera->GetPosition( pos.point );
        PointStruct viewUp;
        camera->GetViewUp( viewUp.point );
        PointStruct projectDir = pos - focalPt;
        projectDir.Unit();
        //to transform the normal, multiply by the transposed inverse matrix
        rotateTrans->TransformNormal( projectDir.point, projectDir.point );
        rotateTrans->TransformPoint( focalPt.point, focalPt.point );
        double dis = m_Renderer->GetActiveCamera()->GetDistance();
        pos = focalPt + dis * projectDir;
        rotateTrans->TransformVector( viewUp.point, viewUp.point );
        camera->SetFocalPoint( focalPt.point );
        camera->SetPosition( pos.point );
        camera->SetViewUp( viewUp.point );
        camera->Modified();
        m_Renderer->ResetCameraClippingRange();
    }

Scale

In this part, we should scale the image, move camera’s focal position and change the parallel scale parameter if the camera has Parallel Projection.

vtkSPtr<vtkTransform> CreateLocalTrans(PointStruct origin, PointStruct xDir, PointStruct yDir)
{
    vtkSPtrNew( resultTrans, vtkTransform );
    PointStruct zDir = xDir ^ yDir;
    zDir.Unit();

    double elements1[16] = { xDir[0], xDir[1], xDir[2], 0,
                            yDir[0], yDir[1], yDir[2], 0,
                            zDir[0], zDir[1], zDir[2], 0,
                            0, 0, 0, 1 };

    resultTrans->Concatenate(elements1);     //rotation

    double elements2[16] = { 1, 0, 0, -origin[0],
                             0, 1, 0, -origin[1],
                             0, 0, 1, -origin[2],
                             0, 0, 0, 1 };

    resultTrans->Concatenate(elements2);    //translation
    resultTrans->Update();

    return resultTrans;
}

void OnMouseWheelChanged( const double &factor )
{
    PointStruct origin(0, 0, 0), xAxes(1, 0, 0), yAxes(0, 1, 0);
    m_UserTransform->TransformPoint( origin.point, origin.point );
    m_UserTransform->TransformVector( xAxes.point, xAxes.point );
    m_UserTransform->TransformVector( yAxes.point, yAxes.point );

    vtkSPtr<vtkTransform> worldInversedTrans = CreateLocalTrans( PointStruct(0, 0, 0), xAxes, yAxes );
    vtkSPtrNew( worldTrans, vtkTransform );
    worldTrans->DeepCopy( worldInversedTrans );
    worldTrans->Inverse();
    worldTrans->Update();

    vtkSPtrNew( scaleTrans, vtkTransform );
    scaleTrans->Scale( factor, factor, 1 );
    scaleTrans->Update();

    vtkSPtrNew( offTrans, vtkTransform );
    offTrans->Concatenate( worldTrans );
    offTrans->Concatenate( scaleTrans );
    offTrans->Concatenate( worldInversedTrans );
    offTrans->Update();

    vtkSPtrNew( finalTrans, vtkTransform );
    finalTrans->Concatenate( offTrans );
    finalTrans->Concatenate( m_UserTransform );
    finalTrans->Update();

    m_ImageActor->SetUserTransform( finalTrans );
    m_UserTransform = finalTrans;

    vtkCamera *camera = m_Renderer->GetActiveCamera();
    PointStruct focalPt;
    camera->GetFocalPoint( focalPt.point );
    PointStruct pos;
    camera->GetPosition( pos.point );
    PointStruct projectDir = pos - focalPt;
    projectDir.Unit();
    double dis = camera->GetDistance();
    offTrans->TransformPoint( focalPt.point, focalPt.point );
    cout << "focalPt: " << focalPt;
    PointStruct newPos = focalPt + projectDir * dis;
    camera->SetFocalPoint( focalPt.point );
    camera->SetPosition( newPos.point );
    double newParallelScale = camera->GetParallelScale() * factor;
    camera->SetParallelScale( newParallelScale );
    camera->Modified();
    m_Renderer->ResetCameraClippingRange();
    m_RenderWindow->Render();
}

Use the function in our vtkInteractorStyle class.

    void OnMouseWheelForward() override
    {
        double wheelFactor = MotionFactor * 0.2 * MouseWheelMotionFactor;
        double factor = 1.0 / pow( 1.1, wheelFactor );
        cout << "factor: " << factor << endl;
        OnMouseWheelChanged( factor );
    }
    void OnMouseWheelBackward() override
    {
        double wheelFactor = MotionFactor * -0.2 * MouseWheelMotionFactor;
        double factor = 1.0 / pow( 1.1, wheelFactor );
        cout << "factor: " << factor << endl;
        OnMouseWheelChanged( factor );
    }

We define three unit vectors: (x_a, y_a, z_a), (x_b, y_b, z_b), (x_c, y_c, z_c). Then create two matrices:

    \[A = \begin{pmatrix} x_a & y_a & z_a & 0 \\ x_b & y_b & z_b & 0 \\ x_c & y_c & z_c & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}\]

    \[B = \begin{pmatrix} x_a & x_b & x_c & 0 \\ y_a & y_b & y_c & 0 \\ z_a & z_b & z_c & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}\]

we have: A \times B = E

The header files we used in our project:

#include <iostream>

#include <vtkSmartPointer.h>
#include <vtkProperty.h>
#include <vtkPolyData.h>
#include <vtkTriangleFilter.h>
#include <vtkRegularPolygonSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkCamera.h>
#include <vtkNamedColors.h>
#include <vtkImageActor.h>
#include <vtkImageViewer2.h>
#include <vtkPNGReader.h>
#include <vtkPlaneSource.h>
#include <vtkImageActor.h>
#include <vtkFollower.h>
#include <vtkTransform.h>
#include <vtkAxesActor.h>
#include <vtkImageActor.h>

#include "../share/tool.h"

Categories: VTK

0 0 vote
Article Rating
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
trackback

[…] I write the reason why concatenate transform and its inversed transform for offTrans in scale operation based on the article Rotate, Translate And Scale For Camera […]

A prohibited operation
1
0
Would love your thoughts, please comment.x
()
x