Render 3D objects to constant size in 2D.

We are creating an application using VTK to visualize a network of nodes and connecting edges in 3D. In a previous version of this application, visualization was solely done in 2D.
Since these networks may have large variations in scale, number of nodes, and number of edges, the old application used constant-size graphical elements to visualize both the edges and the nodes: the edges using fixed width lines, and the nodes using squares with fixed height and width. This has the advantage that points which initially seem to be very close or overlapping, when zoomed in, appear to lie farther apart. In this way, you gain more insight into a given part of the network.
We would like to create similar behavior in 3D. For the lines, we have an acceptable solution: we use simple lines given with a fixed width, but make OpenGL render them as tubes. However, for the squares (cubes in 3D) this is not possible. We are really looking for a way to render these objects at the 3D positions they have in the world, but with a fixed size when rendered to the 2D render window. Even when zooming into the object. This is comparable to what is called “paper space units” in a package like ArcGis.

How could we achieve this? All hints and tips (or questions) would be greatly appreciated.

This is perfectly feasible and you can even do better.

What works for us (at least up to a few ten thousand points) is to use vtkGlyph3D to display the point markers and adjust ScaleFactor when the camera is modified to make displayed glyphs on screen remain approximately the same size regardless of how much you zoom in/out. The advantage of this approach (compared to forcing all the glyphs to have the exact same size in screen coordinates) is that we can preserve effect of perspective projection: points that are closer/farther will be still represented by somewhat larger/smaller glyphs.

1 Like

Thank you very much for the useful reply and nice video! I totally agree that the preserving perspective projection is an advantage.

Just wondering: how do you determine the initial glyph size? And how do you prevent them from being rendered too large from all angles? E.g. in a relatively “thin” data set, glyph sizes in the top view are very different than in a side view.

The scaling is determined based on size at the camera’s focal point. By adjusting the camera’s view angle and distance from focal point, you can control how much glyph sizes vary depending on distance from camera.

Hello, Ari,

I do this to keep a probe marker at constant screen size:

I use this code to rescale a given actor to achieve the sought effect:

void v3dMouseInteractor::rescalePickMarkerActor()
{
    double cameraFocalPoint[3], cameraPosition[3];
    double objectPosition[3];
    double newScale = 0;
    double totalSquared = 0;

    if( ! m_vtkMathObj )
        m_vtkMathObj = vtkSmartPointer<vtkMath>::New();

    // Keeping the pick marker actor
    // the same size relative to the viewport. This is done by
    // changing the scale of the object to be proportional to
    // the distance between the current camera and the object.
    if( m_pickMarkerActor ){
        // Get a pointer to the current camera according to the main renderer.
        vtkCamera *currentCamera = static_cast<vtkCamera *>( this->GetDefaultRenderer()->GetActiveCamera() );
        currentCamera->GetPosition( cameraPosition );
        currentCamera->GetFocalPoint( cameraFocalPoint );
        m_pickMarkerActor->GetPosition( objectPosition );

        totalSquared = std::sqrt(m_vtkMathObj->Distance2BetweenPoints( objectPosition, cameraPosition ));
        newScale = totalSquared / 20000;    // The denominator value is an abitary number that gives the desired size of the objects on screen.
        Application::instance()->logInfo( "Distance to picked location: " + QString::number(totalSquared) );
        m_pickMarkerActor->SetScale( newScale );

        this->GetDefaultRenderer()->Render();
    }
}

You can attach it to any event (e.g. mouse wheel in a mouse interactor):

void v3dMouseInteractor::OnMouseWheelForward()
{
    // Forward the event to the superclass.
    vtkInteractorStyleTrackballCamera::OnMouseWheelForward();

    rescalePickMarkerActor();
}

void v3dMouseInteractor::OnMouseWheelBackward()
{
    // Forward the event to the superclass.
    vtkInteractorStyleTrackballCamera::OnMouseWheelBackward();

    rescalePickMarkerActor();
}

Complete code here: https://github.com/PauloCarvalhoRJ/gammaray/blob/master/viewer3d/v3dmouseinteractor.cpp

all the best,

Paulo

2 Likes