Fluid camera movement in a scene

A bit of context: The GUI element for this question are made with QML.

I’m currently building a scene – i.e. several actor in the same space related to one another through a matrix transform hierarchy – and am adding a GUI element displaying a listing of all the actor residing in the scene. The purpose of this list is to offer a prospective user the ability to quickly focus on an actor of interest by clicking on the actor’s name in the list so that the camera then focuses on this actor through a fluid movement.

There are two requirements for this particular use case to produce a satisfactory effect.

  1. Once the user clicks on the actor’s name in the list, the camera should be properly focused on the actor. That is, it is properly positioned and oriented.
  2. Ideally, the camera should move from its pre-click position to its post-click position in a fluid way. That is, the camera should properly interpolate its movement from its pre-click to post-click location.

Now, I’ve been having some problem achieving both effects.

With respect to (1), one way of achieving something close to what I want is to use the ResetCamera method of the vtkRenderer in the following way: vtkRenderer->ResetCamera(vtkProp->GetBounds()). However, while this indeed does 80% of what’s desired, it does not properly orient the camera with respect to the Prop3D. That is, the roll, pitch and yaw isn’t adjusted. I’m still struggling to find a way to do this.

With respect to (2), I’ve been trying to achieve the effect with a vtkCameraInterpolator but I’m struggling to succeed for the two following reasons. First, there does not seem to have a way to have vtkRenderer::ResetCamera and vtkCameraInterpolator to interact with one another. That is, use of vtkCameraInterpolator seem to preclude the use of the ResetCamera function. Second, I don’t fully understand how to setup vtkCameraInterpolator in order to make the movement happen. Do I need to define the movement myself and then add as many camera to the interpolator as there are interpolation points in the camera path movement? This seems to be what the InterpolateCamera example does but when looking at the CameraOrientation example and CameraOrientationWidget, this appears less clear to me.

So what I’m basically asking is how to achieve (1) and (2). A user click on an actor’s name in a QML gui element and the camera moves in a fluid motion to focus on the actor with a pre-determined orientation. Any help or pointers appreciated.

Just wanted to give an update on my own question as I’ve made quite a bit of headway.

First, one of my main problem was placing the update camera logic in the improper thread. Fellas and gals, if you have a QML button or element calling a Q_OBJECT slot in order to trigger a camera movement, you can’t have your camera update loop operate within the slot itself. More precisely, in order for the movement to happen at all, you’ll have to have the camera update logic take place in the QtQuick render thread before the scene graph state is synchronized. As the QQuickVTKRenderItem doc states with respect to the synch() method:

This is the function called on the QtQuick render thread before the scenegraph state
is synchronized. This is where most of the pipeline updates, camera manipulations, etc. and
other pre-render steps can be performed.
                                                                                            
\note At the time of this method execution, the GUI thread is blocked. Hence, it is safe to
perform state synchronization between the GUI elements and the VTK classes here.

And that’s basically where a call to vtkCameraInterpolator::interpolateCamera needs to take place.

So how do we do that? Suppose I’m in my QObject class handling my QML triggered event. I call this class my FocusList as its a list of scene vtkProp3D on which my camera needs to focus in the event of a click. When the user clicks on the prop’s name, it triggers the FocusList::focusOn slot.

void FocusList::focusOn(vtkProp3D *vtkProp) {
    if (prop == nullptr || this->m_renderer == nullptr)
        return;

     // this->m_renderer is of type QQuickVTKRenderItem
    auto *cameraControl = new CameraControl(vtkProp, this->m_renderer->renderer());
    this->m_renderer->addWidget(cameraControl);
    connect(cameraControl, &CameraControl::interpolationTick, this, &FocusList::interpolationTick);
    connect(cameraControl, &CameraControl::finished, this->m_renderer, &QQuickVTKRenderItem::removeWidget);
    cameraControl->start();
}

CameraControl is a derived QQuickVTKInteractiveWidget that overrides its synch method. The call to start triggers a timer which will then emit interpolationTick event. These events will trigger an update of the QML tree, thereby properly triggering the CameraControl::sync method in the scene graph thread. Each call to the CameraControl::synch interpolates the camera by one step like so:

void CameraControl::sync(vtkRenderer *renderer) {
    auto *activeCamera = renderer->GetActiveCamera();

    if (this->m_step < this->m_numSteps) {
        const auto t = static_cast<double>(this->m_step) * this->m_rangeT / static_cast<double>(this->m_numSteps - 1);
        this->m_interpolator->InterpolateCamera(t, activeCamera);
        renderer->ResetCameraClippingRange();
        this->m_step++;
    } else {
        emit finished(this);
        this->deleteLater();
    }
}

The camera interpolation path is setup in the CameraControl ctor.

This produces satisfactory results so far with respect to the camera placement and camera movement.

I am stuck in the exact same problem with u. I am currently working on VTK+PyQt.

Your can simply set up a QTimer and update the camera position in the callback function.

Hi Lasso,
Is there a sample code that you can share for using QTimer?

Here’s some example code from Slicer. PyQt would be basically the same but with some different syntax.

1 Like