Building VTK User Interfaces: Part 3c – VTK Interaction

The final VTK part of the project – interaction! A big requirement for this project was to allow a user to pilot a bot in the 3D environment. The first step in adding this is to build custom camera types, so that we can look at the environment through the correct perspective. This post looks at adding a rendering loop, handling keyboard and mouse input, and setting up a few template cameras.

So how is this done? To be honest, this code gave me a bit of a headache – the VTK mail group didn’t really respond to my ask (i.e. no cheat codes) and there are quite a few ways to do it – so working out the a suitable method took a bit of time. In the end I had to try a few things (cue the 100 monkeys and typewriters) while traveling (sitting in South Station in Boston at the moment).

[From http://pamperandcurves.blogspot.com/2014/07/6-things-you-will-experience-as-blogger.html%5D

But, I finally broke it down into the following steps:

  1. Add a rendering loop to the main program – VTK only renders when things change, so for safety’s sake it’s best to have a main loop that just renders the scene at a constant 30fps
  2. Build a camera/interactor superclass! Cameras share a lot of code, so encapsulate the common code in a parent class, in the same way as with the SceneObject superclass
  3. Construct a few template cameras that address generic functionality we might have with the bot:
    1. A top-down map view camera for a commander role
    2. A first-person camera for an observer/planner role
    3. A 3rd-person camera that binds to a bot for a pilot-type role

Code for the updated project can be found here!

Adding a Render Timer

A render timer effectively triggers every 30ms and requests that VTK re-render the scene. This is needed because VTK doesn’t redraw the scene unless it notices updates, which, at least with the camera code I am working with, doesn’t seem to happen all the time.

This simple modification is done inside the main loop, where:

  • A callback is specified (in this case ‘__3DRenderingLoop()’) that asks VTK to redraw the scene
  • A timer is created in the main program, the callback is attached, and the timer is started

The implementation is based on the command/observer model, which is a general paradigm for adding callbacks (event handlers) that is ubiquitous in VTK. The command/observer model is discussed in more detail when working through the keyboard and mouse code for the cameras, this is just a first introduction to the model.

The updates that add the timer can all be found in ‘bot_vis_main.py’. Here is a quick overview of the additions:

  • Create a callback function that will be invoked with every timer event and ask the renderer – which is the source ‘obj’ of the event – to redraw the scene:
def __3DRenderingLoop(obj, event):
    '''
    Main loop for rendering at a constant ~30Hz
    '''
    iren = obj
    iren.GetRenderWindow().Render()
  • Create a repeating timer in the main loop:
    # Sign up to receive TimerEvent for the timed rendering loop
    renderObserverId = renderWindowInteractor.AddObserver('TimerEvent', __3DRenderingLoop)
  • Attach the callback to the timer:
    renderTimerId = __3DViewLoopTimerId = renderWindowInteractor.CreateRepeatingTimer(30);
  • Delete the timer when the program exits (strictly optional here, but a good practice either way):
    # Once done, remove the timer to clean up just to be neat
    renderWindowInteractor.DestroyTimer(__3DViewLoopTimerId)
    renderWindowInteractor.RemoveObserver(renderObserverId)

Handling User Interaction

Now that we are rendering at a constant rate, the next step is to handle user interaction. The user interaction will allow you to move cameras around in a specific way. In this post, three examples are provided – a top-down, map-viewing camera, a 1st-person camera, and a 3rd-person camera that tracks a bot.

Before diving into the code, the controller/observer model is worth a quick discussion because it is a generic way to bind to any event in VTK. This post will focus specifically on camera interactors, but it’s worth knowing how to attach to generic events. For example, you can detect when an object is ‘picked’ in 3D (press ‘p’ while holding your mouse cursor over a 3D mesh) and then you can allow the user to move the object. This is notoriously painful in XNA or DirectX (at least when I tried it), so I was quite blown away when it came stock standard in VTK, not to mention with a nicely polished event in the interactor.

docPicking

 

The Controller/Observer Model

In general, there are two ways of monitoring properties in an environment in code:

  • Polling – Polling is a non-invasive way of checking something, for example: During the main rendering loop, we can check the keyboard or mouse and ask if something is pressed
  • Events – Events provide an update when the underlying framework notices that something has changed, for example: If a model is moved, an event will alert all event handlers that this has happened

Both methods have their advantages and disadvantages. Polling, in general, is simpler but quickly turns into a lot more code. Event handling is far more concise and elegant, but you will need to be careful that you attach and detach from the events, otherwise you will get strange errors and tracing them can be a total pain.

VTK works according to the controller/observer model, which is effectively events and event handling. This means that you:

  • Should attach to the provided events with callbacks and should disconnect your attached event handlers when the owner class is being disposed
  • Should not write timer loops in your main program to monitor properties (= polling)

So how do you find what events you can attach to? All of the available events are described in the VTK documentation under the ‘Events’ heading of the respective class. An example is shown below.

docEvents

One last thing to discuss is how to bind an observer to an event. This was actually shown when adding a rendering loop timer, but wasn’t discussed in much detail. To add and remove observers in code:

  • Create a callback function that the event will call when it is triggered. The callback will be passed two parameters – the source object that triggered the event, and the type of event:
def __3DRenderingLoop(obj, event):
  • Add an observer to the event on the source object – in the previous case we were adding a timer observer with a callback to the ‘__3DRenderingLoop()’ function:
    renderWindowInteractor.AddObserver('TimerEvent', __3DRenderingLoop)
  • Remove the observer from the source object when it is not needed anymore:
    renderWindowInteractor.RemoveObserver(renderObserverId)

That’s pretty much the extent of it! These functions will be used extensively to handle events for building the camera interactor styles. Before jumping into the camera design though, we need to quickly discuss the ‘vtkInteractorStyle’ class…

The VTKInteractorStyle Class

In VTK, the ‘RenderWindowInteractor’ class (which we created in the main loop) uses a ‘vtkInteractorStyle’ to control user input. Out of the box, VTK has some great standard functionality for neat things like picking, using anaglyph views, etc.

Some of the standard functionality in a normal VTK interactor style:

  • ‘R’ lets you reset the camera (very useful in the next few sections)
  • ‘P’ lets you pick the object that is under the mouse cursor (a red box will be shown around it)
  • ‘F’ lets you focus on a model that is under the mouse cursor
  • ‘W’ sets the scene to a full wireframe
  • ‘S’ sets it back to filled triangles
  • ‘3’ sets the camera to Anaglyph mode, if you have the red/green 3D glasses you can see a 3D view of the scene (otherwise it just hurts your eyes)

We want to retain this when building cameras, so instead of building a brand-new interaction framework, cameras in this project will extend the ‘vtkInteractorStyle’ to keep all of it and just add on the specific viewing style we would like.

When we have inherited the ‘vtkInteractorStyle’ class for the new camera and added in the functionality that we would like, we just need to call ‘RenderWindowInteractor.SetStyle()’ to assign the new style to the normal window interactor:

    # Now set it as the interactor style for the interactor
    renderWindowInteractor.SetInteractorStyle(interactorStyle)

You can then still press ‘F’ to focus, ‘P’ to pick, ‘W’ for wireframe, or ‘3’ for anaglyph viewing.

So with all the descriptions, disclaimers, etc. covered, let’s jump into the code.

Cameras

As discussed in the introduction, the cameras share a common set of functions (strictly speaking they are interactor styles and I’ll use the terms interchangeably in this post). Rather than copying and pasting multiple times, this project uses a superclass that inherits from ‘vtkInteractorStyle’. Once this is built, we will work through a few camera types that are likely to pop up in this project. With the observer structure already discussed, the remainder just comes down to a pile of vector math to move the widget in the same direction as the thingamabob, which is the heart of camera motion (please excuse my technical lingo).

An InteractorStyle Superclass

The camera/interactor style superclass contains all the members and methods that are shared amongst the different cameras. These are:

  • The keyboard and mouse observers are added to the render window interactor (which is passed in as a constructor parameter), a child camera class just has to override these methods to get keyboard and mouse information (‘MouseMoveCallback()’, etc.)
  • A set method that puts the camera in a specific location – the VTK camera contains both a target focal point and a camera position, and some of camera types override this for a specific behaviour, so it’s best to force a child class to implement the ‘SetCameraPosition’ method to deal with these scenarios
  • A disconnect method ‘Disconnect()’ that removes the observers from the render window interactor

Speaking of forcing a child camera class to implement a method, it seems that the Python language doesn’t have a keyword for abstract methods. To manage this, all the abstract methods throw an exception if they are called and end up in the ‘InteractorSuperclass’ (note the exceptions being thrown in all the methods below). If the method is not overridden in the child class it will end up in the parent and throw the exception. This seems to be the best way I can find to manage class abstraction in Python.

The complete code is shown below for reference and can be found in ‘InteractorSuperclass.py’ under the ‘scene’ folder in the project (the project can be downloaded at the top of the page):

class InteractorSuperclass(vtk.vtkInteractorStyle):
    '''
    Inherit the VTK class vtkInteractorStyleUser and build a superclass for all cameras.
    Ref: http://www.vtk.org/doc/nightly/html/classvtkInteractorStyleUser.html#details
    '''

    def __init__(self, renderer, iren):
        '''
        Initialize the common observers
        '''
        # Add the mouse observer to handle the movement events.
        self.SetCurrentRenderer(renderer)
        #Ref: http://vtk.org/gitweb?p=VTK.git;a=blob;f=Examples/Modelling/Python/SpherePuzzle.py
        self.__obsIDMouseMoveTag = iren.AddObserver("MouseMoveEvent", self.MouseMoveCallback)
        self.__obsIDKeydownTag = self.AddObserver("KeyPressEvent", self.KeydownCallback)
        self.__obsIDKeyupTag = self.AddObserver("KeyReleaseEvent", self.KeyupCallback)

    def SetCameraPosition(self, posVec3):
        raise NotImplementedError("SetCameraPosition hasn't been overwritten in " + self.__name__ + ". This is an abstract method, don't call the base.")

    def Disconnect(self):
        '''
        Clear out all the added observers
        '''
        iren = self.GetInteractor()
        iren.RemoveObserver(self.__obsIDMouseMoveTag)
        self.RemoveObserver(self.__obsIDKeydownTag)
        self.RemoveObserver(self.__obsIDKeyupTag)

    def MouseMoveCallback(self, obj, event):
        raise NotImplementedError("MouseMoveCallback hasn't been overwritten in " + self.__name__ + ". This is an abstract method, don't call the base.")

    def KeydownCallback(self, obj, event):
        raise NotImplementedError("KeydownCallback hasn't been overwritten in " + self.__name__ + ". This is an abstract method, don't call the base.")

    def KeyupCallback(self, obj, event):
        raise NotImplementedError("KeyupCallback hasn't been overwritten in " + self.__name__ + ". This is an abstract method, don't call the base.")

That’s it! Next step: Creating some cameras!

A Top-Down Map View Camera

As a first example, a top-down camera is a simple case that demonstrates how to apply the ‘InteractorSuperclass’. A top-down camera is useful for a overview-type role, i.e. for specifying waypoints in a path-planning scenario, or looking at a a strategic view of the units like Command and Conquer (…and my parents told me playing computer games is a waste of time!). The movement for a top-down camera is easy to implement – it just involves changing the position of the camera without any rotational effects – so it serves as a suitable introduction to the interactor structure.

camMapView

The code for this camera can be found in ‘InteractorMapUser.py’. The overridden callback functions allow us to move the camera around freely, and demonstrates the principles of a custom camera implementation.

A quick discussion on the code:

  • When the camera is initialized, the focal point of the camera is set to be below the camera. This is so the camera is looking down (it’s also a little forward, but negligibly so) – these two points will be moved around when keys are pressed
    def __init__(self, renderer, iren):
        # Call the parent constructor
        InteractorSuperclass.__init__(self, renderer, iren)

        # Set the camera to look along Z when it is initialized
        camera = self.GetCurrentRenderer().GetActiveCamera()
        camera.SetFocalPoint((0, 0, 0))
        camera.SetPosition((0, 30, -0.01))
        camera.SetViewAngle(60) #Make it a little wider.
  • No mouse movement is used with this camera, so ‘MouseMoveCallback’ is just overridden to skip past the exception in the ‘InteractorSuperclass’ base class:
    def MouseMoveCallback(self, obj, event):
        # Leave the mouse for picking
        return
  • ‘KeydownCallback’ does most of the work – a user works with the numeric keypad to move the camera around – so the keycode is retrieved from the parent interactor
  • If a numpad key is pressed (8, 5, 4, and 6 to move around; 7 and 9 to zoom in and out), call the specific movement method:
    def KeydownCallback(self, obj, event):
        '''
        Responding to keyboard events for now, want something more interactive later.
        Ref: http://portal.nersc.gov/svn/visit/tags/2.6.0/vendor_branches/vtk/src/Examples/GUI/Python/CustomInteraction.py
        '''
        # Get the interactor
        iren = self.GetInteractor()
        if iren is None: return

        key = iren.GetKeyCode()
        if key == "8": # Move forward
            self.__MoveForward()
        if key == "5": # Move backward
            self.__MoveBackward()
        if key == "6": # Move right
            self.__MoveRight()
        if key == "4":
            self.__MoveLeft()
        if key == "7":
            self.__MoveIn()
        if key == "9":
            self.__MoveOut()
  • Each of the movement functions operate similarly (the code is just below the description):
    • Get the current camera position ‘cam’ and the current focal point ‘focal’
    • Create a ‘vec’ vector that will be the direction of movement (numpad 8 and 5 generate movement along the z-axis; numpad 4 and 6 along the x-axis; numpad 7 and 9 along the y-axis)
    • Add the movement to the current camera position and focal point, and save these in ‘newCam’ and ‘newFocal’ respectively
    • Set the camera position and focal point to the new vectors
    • Ask the renderer to update the clipping planes for the camera
    def __MoveForward(self):
        camera = self.GetCurrentRenderer().GetActiveCamera()
        cam = camera.GetPosition()
        focal = camera.GetFocalPoint()
        vec = [0, 0, 1]
        newCam = [0, 0, 0]
        newFocal = [0, 0, 0]
        vtk.vtkMath.Add(cam, vec, newCam)
        vtk.vtkMath.Add(focal, vec, newFocal)
        camera.SetPosition(newCam)
        camera.SetFocalPoint(newFocal)
        # Update the clipping range of the camera
        self.GetCurrentRenderer().ResetCameraClippingRange()

 

Note: Updating the clipping planes is essential. If this step is skipped, strange things will happen when you move around, which can be frustum-strating.

…Okay, that was a terrible joke, I let off a nerdy guffaw so loud I scared the people sitting near me in South Station terminal (where I’m writing this)…

…Moving swiftly along…

To add this to the main program, just swap out the normal interactor style for the render interactor:

    # Set up the custom style for your camera interactor
    interactorStyle = InteractorMapUser.InteractorMapUser(renderer, renderWindowInteractor)
    # Now set it as the interactor style for the interactor
    renderWindowInteractor.SetInteractorStyle(interactorStyle)
    interactorStyle.EnabledOn()

When you run this, you should now be able to move the camera around with the keypad. If you don’t see anything, just hit ‘R’ to reset the camera (which is standard functionality of the VTK interactor style). Note that you can still pick objects with ‘P’ and reset the camera, so we are extending the interactor style, not replacing it.

An Unbound 1st-Person Camera

Now that we have the interactor styles working and a simple map camera as an example, let’s build something a little more interesting – a first-person camera that is controlled by the mouse and keyboard.

camFirstPerson

To do this, we have to rotate the camera as the mouse moves (only two axes for now – pitch and yaw) and move the camera along the camera axes as keys are pressed. You can then ‘look’ around and move in the direction that you are facing.

This isn’t a great implementation, more a proof of concept, however this code is used later to bind the camera to a Vuzix Wrap 920 VR headset. That reminds me – if you know anyone at Oculus VR, please ask them to send me my Rift glasses already, I’m going blind working with this older headset.

The complete code for the 1st person camera can be found in ‘Interactor1stPersonUser.py’ in the project. Here is a quick breakdown of the components:

  • ‘MouseMoveCallback’ is now used because it sets the rotation of the camera, a snippet of it is provided below
  • When the mouse is moved:
    • The change in mouse position, ‘dx’ and ‘dy’, is calculated from difference between the current and the last mouse position
    • The screen size is retrieved from the render window
    • The camera is yawed (about the y-axis) by the ‘dx’ change, which is scaled to fit the screen size
    • The camera is pitched (about the y-axis) by the ‘dy’ change, which is also scaled to fit the screen size
    • Lastly the clipping planes are updated so the camera frustums are correct
    def MouseMoveCallback(self, obj, event):
        # Get the interactor
        iren = self.GetInteractor()
        if iren is None: return

        # Ref: http://portal.nersc.gov/svn/visit/trunk/vendor_branches/vtk/src/Rendering/vtkInteractorStyleTrackballCamera.cxx
        dx = iren.GetEventPosition()[0] - iren.GetLastEventPosition()[0];
        dy = iren.GetEventPosition()[1] - iren.GetLastEventPosition()[1];

        # Rotate the focal point around (yaw = x) and (pitch =y) by a factor of the mouse differentials dx and dy
        camera = self.GetCurrentRenderer().GetActiveCamera()
        camera.SetRoll(0)

        screenSize = iren.GetRenderWindow().GetSize()

        # Yaw changes in a negative direction but the pitch is correct
        camera.Yaw(-float(dx) / float(screenSize[0]) * 360 * 2.0);
        camera.Pitch(float(dy) / float(screenSize[1]) * 180 / 2.0);

        # Update the clipping range of the camera
        self.GetCurrentRenderer().ResetCameraClippingRange()

With that it would be possible to look around. You might also want to let your user move around along the camera axes, and this is handled by the keyboard callback (which is provided as a snippet below):

  • Get the parent interactor ‘iren’
  • As with the map camera, if any keys are pressed, call the respective movement method
    def KeydownCallback(self, obj, event):
        '''
        Responding to keyboard events for now, want something more interactive later.
        Ref: http://portal.nersc.gov/svn/visit/tags/2.6.0/vendor_branches/vtk/src/Examples/GUI/Python/CustomInteraction.py
        '''
        # Get the interactor
        iren = self.GetInteractor()
        if iren is None: return

        key = iren.GetKeyCode()
        if key == "8": # Move forward
            self.__MoveForward()
        if key == "5": # Move backward
            self.__MoveBackward()
        if key == "6": # Move right
            self.__MoveRight()
        if key == "4":
            self.__MoveLeft()

The movements are a little more complex because we have to move in the direction that the camera is looking. This is a bit of vector math:

  • To move forward, the vector from the focal point to the camera is calculated as ‘vec’, i.e. ‘vec’ = ‘focal’ – ‘cam’
  • This points us in the direction of forward motion, however it is of an arbitrary magnitude so it is normalized to give a unit vector
  • For forward motion, the ‘vec’ unit vector is added to both the camera position as well as the focal position to move the camera in that direction
    def __MoveForward(self):
        camera = self.GetCurrentRenderer().GetActiveCamera()
        cam = camera.GetPosition()
        focal = camera.GetFocalPoint()
        vec = [0, 0, 0]
        newCam = [0, 0, 0]
        newFocal = [0, 0, 0]
        vtk.vtkMath.Subtract(focal, cam, vec)
        vtk.vtkMath.Normalize(vec)
        vtk.vtkMath.Add(cam, vec, newCam)
        vtk.vtkMath.Add(focal, vec, newFocal)
        camera.SetPosition(newCam)
        camera.SetFocalPoint(newFocal)
        # Update the clipping range of the camera
        self.GetCurrentRenderer().ResetCameraClippingRange()

Backward motion is calculated in the same way, except that we subtract the ‘vec’ pointing vector from the camera position and the focal position.

Moving right and left (strafing) is a little more complex. To do this, we need to have a pointing vector that sticks out of the right side of the camera. This is calculated in the following way (code snippet below):

  • Calculate the forward pointing unit-vector ‘vec’ again in the same way, i.e. the normalized vector of focal point – camera position
  • Create a vector that points upward, i.e. (0, 1, 0)
  • Calculate the cross product of the forward vector by the up vector, which will give us an orthogonal vector pointing right relative to the camera
  • Add this to the camera position and focal point to move it right
    def __MoveRight(self):
        camera = self.GetCurrentRenderer().GetActiveCamera()
        cam = camera.GetPosition()
        focal = camera.GetFocalPoint()
        up = [0, 1, 0] #We don't want roll
        vec = [0, 0, 0]
        newCam = [0, 0, 0]
        newFocal = [0, 0, 0]
        vtk.vtkMath.Subtract(focal, cam, vec)
        vec[1] = 0 #We don't want roll
        vtk.vtkMath.Normalize(vec)
        vtk.vtkMath.Cross(vec, up, vec)
        vtk.vtkMath.Add(cam, vec, newCam)
        vtk.vtkMath.Add(focal, vec, newFocal)
        camera.SetPosition(newCam)
        camera.SetFocalPoint(newFocal)
        # Update the clipping range of the camera
        self.GetCurrentRenderer().ResetCameraClippingRange()

As above, moving left is calculated in the same way, except we subtract ‘vec’ from the camera position and the focal point.

That sums up a template for a 1st-person camera in VTK. However, it may need a bit of TLC (saving it for a later post) and you may need to press ‘R’ to reset the camera when first using it. If you want to set it as the interactor style for the environment, just create one and set the render interactor’s style to it in the main program ‘bot_vis_main.py’:

    # Set up the custom style for your camera interactor
    interactorStyle = Interactor1stPersonUser.Interactor1stPersonUser(renderer, renderWindowInteractor)
    # Now set it as the interactor style for the interactor
    renderWindowInteractor.SetInteractorStyle(interactorStyle)
    interactorStyle.EnabledOn()

A Bound 3rd-Person Camera for the Bot

The final camera that is introduced is by far the most useful in this project – a camera that is bound to a ‘SceneObject’ to get a 3rd-person view of the bot as it operates in the environment. With a few small modifications, it can also be adapted into a bound 1st-person camera for piloting. As the bot moves around, the camera follows it from a fixed distance.

This type of camera is ubiquitous in Zita Asteria and ZAsteroids. It’s the same type of code, with the exception that the camera position and focal point are fed through a first-order filter to smooth its motion. This post has become a little long so this modification is omitted, but please comment if you are also interested in a code sample of a ‘smooth’, bound camera in VTK.

cam3rdPerson

The code for this interactor style can be found in ‘Interactor3rdPerson.py’. Essentially the only fundamental change is that we add an observer to track changes to the target ‘SceneObject’. However, it’s worth doing a quick fly-over of the code:

  • In the constructor of ‘Interactor3rdPerson’, we pass in a ‘trackedSceneObject’ and ‘cameraOffsetVec3’:
    • ‘trackedSceneObject’ is the bot we wish to bind to
    • ‘cameraOffsetVec3’ is the offset from the center of the bot where the camera will be positioned
    • These two variables are stored in local object members so that we can use them when the bot moves
    def __init__(self, renderer, iren, trackedSceneObject, cameraOffsetVec3):
        # Call the parent constructor
        InteractorSuperclass.__init__(self, renderer, iren)

        # Set the tracked object and the offset
        self.__trackedObject = trackedSceneObject
        self.__camOffsetVec3 = cameraOffsetVec3
  • A few camera-specific parameters are set (the viewing angle is made slightly wider), and the mouse movement callback is called to set the camera up (it might not move later and the camera may start somewhere arbitrary):
        camera = self.GetCurrentRenderer().GetActiveCamera()
        camera.SetRoll(0)
        camera.SetViewAngle(60) #Make it a little wider.

        # Do a first pass call in case the object doesn't move
        self.MouseMoveCallback(None, None)
  • In the last part of the constructor, an observer is added to the tracked object – when it moves, ‘ModifiedEvent()’ will be called in the interactor style:
        #Ref: http://www.vtk.org/doc/nightly/html/classvtkObject.html
        self.__trackingId = self.__trackedObject.vtkActor.AddObserver("ModifiedEvent", self.TrackedObjectMovedCallback)

        # In the event that the object doesn't move, update the frame now
        self.TrackedObjectMovedCallback(self.__trackedObject, "ModifiedEvent")

In this case, we have now added an observer to the target bot. It is necessary to remove this when the camera is removed:

  • The ‘Disconnect()’ method of the camera class is useful here – when an interactor style is removed, you should call this method to clear the observer in the bound bot
  • To do this, the source actor is found from the ‘trackedObject’, and the observer is removed:
    def Disconnect(self):
        # Call the parent Disconnect() method
        super(Interactor3rdPerson,self).Disconnect()
        # Remove the tracking
        self.__trackedObject.vtkActor.RemoveObserver(self.__trackingId)

We add the method that is called when the tracked bot is moved (‘TrackedObjectMovedCallback’):

  • After a bit of initialization, the tracked object’s rotation and position are pulled and stored in ‘objRot’ and ‘objPos’ respectively:
    def TrackedObjectMovedCallback(self, obj, event):
        # Get the interactor
        iren = self.GetInteractor()

        # Get the active camera
        camera = self.GetCurrentRenderer().GetActiveCamera()

        # Get the tracked object's orientation and position
        objRot = self.__trackedObject.vtkActor.GetOrientation()
        objPos = self.__trackedObject.vtkActor.GetPosition()
  • We can assume that if the object is modified, the position and rotation have changed, so update these:
    • Set the focal point of the camera to be the center of the bot
    • Set the camera position to the center of the bot plus the camera offset
        # Reset the focal point to the center of the object and the camera position to the absolute offset
        camera.SetFocalPoint(objPos)
        camera.SetPosition(objPos[0] + self.__camOffsetVec3[0], objPos[1] + self.__camOffsetVec3[1], objPos[2] + self.__camOffsetVec3[2])
  • In this interactor style, we only care that the camera faces in the same heading as the object, so:
    • Set the camera’s up vector to be unit-Y
    • Rotate the camera around Y so that it faces in the same direction as the bot
        # Final azimuthal (heading) rotation
        # Set the camera's up to unit-y so that we rotate around that only,
        # otherwise it will rotate around the orthogonal axes of the focal point->camera vector
        camera.SetViewUp([0, 1, 0])
        # Rotate the camera heading
        camera.Azimuth(objRot[1])
  • Finally, we have almost definitely moved the clipping planes, so update them again:
        # Update the clipping range of the camera
        self.GetCurrentRenderer().ResetCameraClippingRange()

And that’s that! If you want to use this camera in the main code, just create the style interactor with a target ‘SceneObject’ (a bot in most cases) and in the same way as the other cameras. Here I’ve added in the three different cameras, uncomment the interactor style that you would like to use (for the moment, the ‘Interactor3rdPerson’):

    # Set up the custom style for your camera interactor
    # Uncomment one of the following below to select it
    #interactorStyle = InteractorMapUser.InteractorMapUser(renderer, renderWindowInteractor)
    #interactorStyle = Interactor1stPersonUser.Interactor1stPersonUser(renderer, renderWindowInteractor)
    interactorStyle = Interactor3rdPerson.Interactor3rdPerson(renderer, renderWindowInteractor, bots[1], [0, 7, -10])
    # Now set it as the interactor style for the interactor
    renderWindowInteractor.SetInteractorStyle(interactorStyle)
    interactorStyle.EnabledOn()

Summary

That sums up the last of the VTK components for this project. If you have downloaded the code you should be able to just run it with Python (run ‘python bot_vis_main.py’ in the ‘bot_vis_platform’ folder).

The next two tutorials will add a 2D PyQt GUI and a full LCM communication channel to finish off the project. These two have mostly been written and are a lot easier than this section, so I should be able to upload them in the next week or two!

– Sam

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s