I create two renderers that show the same 3D model. Make the camera in the right renderer linkage with the left one’s, that means we can get the same view in the right renderer when we modify the status of the camera of the left renderer.
Here is all information of implementations.
main.cpp
#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 <vtkOBJReader.h>
#include <vtkCamera.h>
#include "CustomIteractorStyle.h"
using namespace std;
int main()
{
const string filePath = "/Users/weiyang/Desktop/8.obj";
vtkSPtrNew( reader, vtkOBJReader );
reader->SetFileName( filePath.c_str() );
reader->Update();
vtkSPtrNew( mapper1, vtkPolyDataMapper );
mapper1->SetInputConnection( reader->GetOutputPort() );
vtkSPtrNew( actor1, vtkActor );
actor1->SetMapper( mapper1 );
vtkSPtrNew( mapper2, vtkPolyDataMapper );
mapper2->SetInputConnection( reader->GetOutputPort() );
vtkSPtrNew( actor2, vtkActor );
actor2->SetMapper( mapper2 );
// Define viewport ranges
// (xmin, ymin, xmax, ymax) left-top and right-bottom point
double leftViewport[4] = {0.0, 0.0, 0.5, 1.0};
double rightViewport[4] = {0.5, 0.0, 1.0, 1.0};
// Setup renderers
vtkSmartPointer<vtkRenderer> leftRenderer =
vtkSmartPointer<vtkRenderer>::New();
leftRenderer->SetViewport( leftViewport );
leftRenderer->SetBackground( .6, .5, .4 );
leftRenderer->AddActor( actor1 );
leftRenderer->ResetCamera();
leftRenderer->GetActiveCamera()->ParallelProjectionOn();
vtkSmartPointer<vtkRenderer> rightRenderer =
vtkSmartPointer<vtkRenderer>::New();
rightRenderer->SetViewport( rightViewport );
rightRenderer->SetBackground( .4, .5, .6);
rightRenderer->AddActor( actor2 );
rightRenderer->ResetCamera();
rightRenderer->GetActiveCamera()->ParallelProjectionOn();
vtkSPtrNew( renderWindow, vtkRenderWindow );
renderWindow->AddRenderer( leftRenderer );
renderWindow->AddRenderer( rightRenderer );
vtkSPtrNew( renderWindowInteractor, vtkRenderWindowInteractor );
renderWindowInteractor->SetRenderWindow( renderWindow );
vtkSPtrNew( iStyle, CustomIteractorStyle );
iStyle->Setm_LeftRender( leftRenderer );
iStyle->Setm_RightRender( rightRenderer );
iStyle->Init();
renderWindowInteractor->SetInteractorStyle( iStyle );
renderWindow->Render();
renderWindowInteractor->Start();
return 0;
}
CustomIteractorStyle.h:
#pragma once
#include <iostream>
#include <vtkSmartPointer.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkActor.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTransform.h>
#include "point.hpp"
#define CPP_SET_MACRO(name,type) \
void Set##name(type _arg) \
{ \
if (this->name != _arg) \
{ \
this->name = _arg; \
} \
}
#define vtkSPtr vtkSmartPointer
#define vtkSPtrNew(Var, Type) vtkSPtr<Type> Var = vtkSPtr<Type>::New();
class CustomIteractorStyle: public vtkInteractorStyleTrackballCamera
{
public:
static CustomIteractorStyle *New(){ return new CustomIteractorStyle(); }
void OnMouseWheelForward() override;
void OnMouseWheelBackward() override;
void Rotate() override;
void Pan() override;
void Init();
CPP_SET_MACRO( m_LeftRender, vtkSmartPointer<vtkRenderer> );
CPP_SET_MACRO( m_RightRender, vtkSmartPointer<vtkRenderer> );
protected:
CustomIteractorStyle();
~CustomIteractorStyle() override;
void KeepSameCameraStatus();
vtkSmartPointer<vtkRenderer> m_LeftRender;
vtkSmartPointer<vtkRenderer> m_RightRender;
Point m_LastVTKCameraFocal;
};
CustomIteractorStyle.cpp:
#include "CustomIteractorStyle.h"
#include "point.hpp"
#include <vtkCamera.h>
void CustomIteractorStyle::OnMouseWheelForward()
{
vtkInteractorStyleTrackballCamera::OnMouseWheelForward();
KeepSameCameraStatus();
}
void CustomIteractorStyle::OnMouseWheelBackward()
{
vtkInteractorStyleTrackballCamera::OnMouseWheelBackward();
KeepSameCameraStatus();
}
void CustomIteractorStyle::Rotate()
{
vtkInteractorStyleTrackballCamera::Rotate();
KeepSameCameraStatus();
}
void CustomIteractorStyle::Pan()
{
vtkInteractorStyleTrackballCamera::Pan();
KeepSameCameraStatus();
}
void CustomIteractorStyle::Init()
{
m_LastVTKCameraFocal = Point( m_LeftRender->GetActiveCamera()->GetFocalPoint() );
}
CustomIteractorStyle::CustomIteractorStyle()
{
}
CustomIteractorStyle::~CustomIteractorStyle()
{
}
void CustomIteractorStyle::KeepSameCameraStatus()
{
vtkCamera *targetCamera = m_RightRender->GetActiveCamera();
vtkCamera *sampleCamera = m_LeftRender->GetActiveCamera();
Point sampleFocal, sampleCameraPos, sampleViewUp;
sampleCamera->GetFocalPoint( sampleFocal.point );
sampleCamera->GetPosition( sampleCameraPos.point );
sampleCamera->GetViewUp( sampleViewUp.point );
double parallelValue = sampleCamera->GetParallelScale();
Point focalToPos = sampleCameraPos - sampleFocal;
focalToPos.Unit();
Point moveVec = sampleFocal - m_LastVTKCameraFocal;
Point targetFocal, targetCameraPos;
targetCamera->GetFocalPoint( targetFocal.point );
targetCamera->GetPosition( targetCameraPos.point );
double dis = (targetFocal - targetCameraPos).Length();
Point newFocal = moveVec + targetFocal;
targetCamera->SetFocalPoint( newFocal.point );
Point newPos = newFocal + focalToPos * dis;
targetCamera->SetPosition( newPos.point );
targetCamera->SetViewUp( sampleViewUp.point );
targetCamera->SetParallelScale( parallelValue );
m_LastVTKCameraFocal = sampleFocal;
);
}
But I can’t see the whole body of the right one after rotating the left 3D model.
Let’s set the same clipping range with the left camera.
void CustomIteractorStyle::KeepSameCameraStatus()
{
//...
double *sClippingRange = sampleCamera->GetClippingRange();
targetCamera->SetClippingRange( sClippingRange[0], sClippingRange[1]
It seems correct after configure the clipping range.
The OBJ file I used in my project can be created on the page: create 3d text
The relevant interfaces in VTK:
// Automatically set the clipping range of the camera based on the
// visible actors
void vtkRenderer::ResetCameraClippingRange()
{
double allBounds[6];
this->ComputeVisiblePropBounds(allBounds);
if (!vtkMath::AreBoundsInitialized(allBounds))
{
vtkDebugMacro(<< "Cannot reset camera clipping range!");
}
else
{
this->ResetCameraClippingRange(allBounds);
}
// Here to let parallel/distributed compositing intercept
// and do the right thing.
this->InvokeEvent(vtkCommand::ResetCameraClippingRangeEvent, this);
}
// Reset the camera clipping range to include this entire bounding box
void vtkRenderer::ResetCameraClippingRange(double bounds[6])
{
double vn[3], position[3], a, b, c, d;
double range[2], dist;
int i, j, k;
// Don't reset the clipping range when we don't have any 3D visible props
if (!vtkMath::AreBoundsInitialized(bounds))
{
return;
}
this->GetActiveCameraAndResetIfCreated();
if (this->ActiveCamera == nullptr)
{
vtkErrorMacro(<< "Trying to reset clipping range of non-existent camera");
return;
}
if (!this->ActiveCamera->GetUseOffAxisProjection())
{
this->ActiveCamera->GetViewPlaneNormal(vn);
this->ActiveCamera->GetPosition(position);
this->ExpandBounds(bounds, this->ActiveCamera->GetModelTransformMatrix());
}
else
{
this->ActiveCamera->GetEyePosition(position);
this->ActiveCamera->GetEyePlaneNormal(vn);
this->ExpandBounds(bounds, this->ActiveCamera->GetModelViewTransformMatrix());
}
a = -vn[0];
b = -vn[1];
c = -vn[2];
d = -(a * position[0] + b * position[1] + c * position[2]);
// Set the max near clipping plane and the min far clipping plane
range[0] = a * bounds[0] + b * bounds[2] + c * bounds[4] + d;
range[1] = 1e-18;
// Find the closest / farthest bounding box vertex
for (k = 0; k < 2; k++)
{
for (j = 0; j < 2; j++)
{
for (i = 0; i < 2; i++)
{
dist = a * bounds[i] + b * bounds[2 + j] + c * bounds[4 + k] + d;
range[0] = (dist < range[0]) ? (dist) : (range[0]);
range[1] = (dist > range[1]) ? (dist) : (range[1]);
}
}
}
// do not let far - near be less than 0.1 of the window height
// this is for cases such as 2D images which may have zero range
double minGap = 0.0;
if (this->ActiveCamera->GetParallelProjection())
{
minGap = 0.1 * this->ActiveCamera->GetParallelScale();
}
else
{
double angle = vtkMath::RadiansFromDegrees(this->ActiveCamera->GetViewAngle());
minGap = 0.2 * tan(angle / 2.0) * range[1];
}
if (range[1] - range[0] < minGap)
{
minGap = minGap - range[1] + range[0];
range[1] += minGap / 2.0;
range[0] -= minGap / 2.0;
}
// Do not let the range behind the camera throw off the calculation.
if (range[0] < 0.0)
{
range[0] = 0.0;
}
// Give ourselves a little breathing room
range[0] = 0.99 * range[0] - (range[1] - range[0]) * this->ClippingRangeExpansion;
range[1] = 1.01 * range[1] + (range[1] - range[0]) * this->ClippingRangeExpansion;
// Make sure near is not bigger than far
range[0] = (range[0] >= range[1]) ? (0.01 * range[1]) : (range[0]);
// Make sure near is at least some fraction of far - this prevents near
// from being behind the camera or too close in front. How close is too
// close depends on the resolution of the depth buffer
if (!this->NearClippingPlaneTolerance)
{
this->NearClippingPlaneTolerance = 0.01;
if (this->RenderWindow)
{
int ZBufferDepth = this->RenderWindow->GetDepthBufferSize();
if (ZBufferDepth > 16)
{
this->NearClippingPlaneTolerance = 0.001;
}
}
}
// make sure the front clipping range is not too far from the far clippnig
// range, this is to make sure that the zbuffer resolution is effectively
// used
if (range[0] < this->NearClippingPlaneTolerance * range[1])
{
range[0] = this->NearClippingPlaneTolerance * range[1];
}
this->ActiveCamera->SetClippingRange(range);
}