Transitions Tutorial for KiwiViewer and VES
The VES/Kiwi development team has added a new feature to the kiwi library for use in iOS and Android applications: animated transitions.
Transitions are animations of a change in the scene graph over time. They are useful because they provide users with information about how items in the two scene graphs (the initial and final graphs) are related to each other. Heer and Robertson [1], in “Animated Transitions in Statistical Data Graphics,” offer a thorough discussion of transitions and present a taxonomy of transition types in scientific visualization.
In the kiwi library, transitions are transient in nature; once you construct a transition and queue it for the application to process, you need never inspect it again, as the object representing the transition is reference-counted and disowned by the application once the animation is complete.
Transition objects hold the initial and final state of one or more scene graph node properties (e.g., a color) and compute intermediate values based on the amount of time elapsed since the transition was queued. Any transition can be run in series or in parallel with other transitions. When run in parallel – as long as they do not set the same attribute on the same scene-graph node – the changes will appear simultaneously. When run in series, one transition starts as its predecessor completes, making complex sequences of actions possible.
The new transitions also provide easings. Easings are ways to alter the rate at which a transition occurs relative to the actual elapsed time. They are frequently used in traditional (hand) animation to achieve realistic motion and/or add “character” to motion. Instead of linearly interpolating a property between its initial and final values according to Δt (the time since the transition began), the time is composed with a nonlinear easing function. Many easing functions are illustrated on easings.net and most of them are available in the kiwi library.
What follows is a tutorial on the specific way transitions are implemented in kiwi.
Animated Transitions by Example: Basics
The kiwi library provides animated transitions for you to use. They are similar to D3’s transitions. The one you are most probably interested in is the one that moves the camera: vesKiwiCameraTransition. A transition should be created using its static create method, which returns a shared pointer to a new transition instance.
vesKiwiCameraTransition::Ptr transition =
vesKiwiCameraTransition::create(camera);
The reason you should prefer this method is because the “set” methods on transitions can be chained together starting with a pointer to the transition:
vesKiwiCameraTransition::Ptr transition =
vesKiwiCameraTransition::create(camera)
// Camera transition properties
->setInitialFocus(vesVector3f(0., 0., 0.))
->setFinalFocus(vesVector3f(0., 0., 10.))
// Base transition properties
->setDuration(2.0);
Note that you must always call subclass-specific set methods before calling a parent class’ set methods. This is due to the notion that the parent class set methods return a shared pointer to the parent class on which you cannot call subclass methods. For example the setDuration() call above returns a vesKiwiTransition::Ptr, which does not know about setInitialFocus() or setFinalFocus(). Therefore, always set the duration last.
Once you have all of the properties set on a transition, you add the transition to an application by calling the addTransition method. Once a transition completes, it removes itself from the application. This means that you do not need to create a temporary variable to hold the transition:
kiwiApp->addTransition(
vesKiwiCameraTransition::create(camera)
// Camera transition properties
->setInitialFocus(vesVector3f(0., 0., 0.))
->setFinalFocus(vesVector3f(0., 0., 10.))
// Base transition properties
->setDuration(2.0));
The transition will be deleted automatically when the application no longer holds a reference to it. Note that transitions generally own references to the objects they will modify; removing an object from the scene graph may keep it from being rendered, but it will still exist and be modified by the transition until completion.
Transitions can be told to queue other transitions as they become active or when they complete using one transition’s alsoStart or followedBy, respectively:
kiwiApp->addTransition(
vesKiwiCameraTransition::create(camera)
->setInitialFocus(vesVector3f(0., 0., 0.))
->setFinalFocus(vesVector3f(0., 0., 10.))
->setDuration(2.0)
->alsoStart(
vesKiwiTransformTransition::create(transform)
->setTranslation(vesVector3f(10., 0., 0.))
->setDuration(2.0))
->followedBy(
vesKiwiTransformTransition::create(transform)
->setRotation(vesVector3f(1., 0., 0.), 90.0)
->setDuration(2.0)));
You can see how indentation can be used to show the transitions that are being referenced, but you must be careful to maintain the code properly because it can also be misleading in a way that makes debugging difficult.
In certain cases, you might want to reference one transition’s properties while setting up another transition. However, creating variables to hold the transition references so that you can refer to property values can be awkward. Thus, the create methods usually take an optional final argument that is a shared pointer reference to the transition. The pointer is set as the instance is created. This example sets the duration of one transition to match another one:
// Ensure transitions started at the same time
// complete at the same time: vesKiwiCameraTransition::Ptr tx1;
kiwiApp->addTransition(
vesKiwiCameraTransition::create(camera, tx1)
->setInitialFocus(vesVector3f(0., 0., 0.))
->setFinalFocus(vesVector3f(0., 0., 10.))
->setDuration(2.0)
->alsoStart(
vesKiwiTransformTransition::create(transform)
->setTranslation(vesVector3f(10., 0., 0.))
->setDuration(tx1->duration())));
Initial Conditions
Transitions do their best to obtain initial conditions from the current state of the objects they will be animating at the time they are queued by the application, not at their construction time. The queue time and construction time can be different, specifically when transitions are run in series using the followedBy() method. Most transitions also provide a means for you to manually set the initial state. However, this can lead to discontinuities at the beginning of a transition. Therefore, it is important to be careful.
Final Conditions
In some instances, you may not want to spend the time precomputing a transform matrix by hand in order to get a good final state for a transition. In these situations, you can force initial/final state settings during construction:
vesKiwiCameraTransition::Ptr tx1;
kiwiApp->addTransition(
vesKiwiCameraTransition::create(camera, tx1)
->setInitialFocus(vesVector3f(0., 0., 0.))
->setFinalFocus(vesVector3f(0., 0., 10.))
->setDuration(2.0));
// Force the transition to grab initial values now:
tx1->takeInitialOrientation();
// It is safe to modify the camera here because the transition will reset the state at the
// beginning of the next render.
camera->setViewPlaneNormal(vesVector3f(1., 0., 0.)); camera->reset();
// Tell the transition to use the camera's current state as the final endpoint
:tx1->takeFinalOrientation();
Methods for forcing a transition to sample the current state of an object to obtain an initial or final value generally have names beginning with take and do not accept arguments.
Transition Time Versus Progress
Most transitions have a clear beginning and ending state and a fixed duration. If this is the case, you can change the rate at which the transition occurs to make it visually smoother (or more overt). The way to do this is with easing functions á la Penner [2] or Sitnik [3]. Kiwi provides several functions, all of which are derived from vesKiwiEasing. Each one takes a progress indicator number between 0 and 1 and returns a modified number between 0 and 1. The modified number is passed to the transition, which uses it as a “fraction completed.”
Non-transitions
Sometimes you need a transition for which no completion time is known a priori. One example would be showing FTP download progress when the file size is unavailable; there is no way to know how much of the download is completed, so you cannot estimate where to position an absolute-valued progress indicator. This is not something vesKiwiTransition supports directly yet, but you can write subclasses that provide this kind of behavior. In such cases, your transition should ignore the “fraction completed” passed through the easing and to the transition’s prepareState() method. Instead, prepareState() can query the transition for the current time and its start time, using the difference to define some state. In the future, a method may be available to obtain the time since the last rendering.
Specific Transitions
Camera Transition
Camera transitions allow you to independently change the following:
• The initial and final focal points (where the camera is aimed)
• The initial and final orientations of the camera (the direction of the “lens” from the focal point) as a quaternion
• The focal distance of the camera lens from the focal point
• The parallel scaling factor used when parallel projection is enabled
Camera transitions also allow you to set the entire initial or final “frame” at once by specifying an eye, aim, and
up vector.
See src/kiwi/Testing/TestAnimation.cpp for an example of how to use this class.
Actor Transitions
There are three classes you can use to transform actors:
• vesActorTranslationTransition provides a way to transition the translation property of a vesActor instance
• vesActorCenterTransition provides a way to transition the center property of a vesActor instance controlling
its center of rotation
• vesActorRotationTransition provides a way to transition the rotation property of a vesActor instance
These are all simply template-specializations of the vesKiwiIVarTransition class, which uses get and set methods to obtain initial values and prepare intermediate states.
See src/kiwi/Testing/TestGradientBackground.cpp for an example.
Representation Color Transitions
The vesKiwiPolyDataRepresentation class provides methods to set the color and opacity of VTK polydata. As with actor transitions, we provide typedefs to template specializations of the vesKiwiIVarTransition class to control color and opacity:
• vesKiwiPolyDataColorTransition
• vesKiwiPolyDataOpacityTransition
Note that currently, the kiwi library ignores opacity. Therefore, transitioning it is not likely to be useful.
Scalar value
Finally, you can transition any variable that uses a primitive storage type given just its address. The vesKiwiScalarTransition is a template class that takes a pointer to a primitive storage type (e.g., double or int) and transitions the value stored. Note that you must guarantee that the address is valid for the duration of the transition.
See src/kiwi/Testing/TestTransitions.cpp for an example of how to use this class.
An Example
Finally, although it is difficult to capture the action of a transition, Figure 1 shows several frames of an animation generated by translating two actors (using a “bounce” easing) as the camera is rotated. The actors are polydata created using ParaView’s “3D Text” source and were placed in the same directory as the other Kiwi sample data.
Figure 1. Several frames of an animation
DemoApp::Ptr app; // A subclass of vesKiwiBaseApp
std::string filename1 = this->sourceDirectory() +
std::string("/Apps/iOS/Kiwi/Kiwi/Data/txt-ves.vtp");
std::string filename2 = this->sourceDirectory() +
std::string("/Apps/iOS/Kiwi/Kiwi/Data/txt-kiwi.vtp");
vesKiwiPolyDataRepresentation::Ptr rep1 = app->
loadData(filename1);
vesKiwiPolyDataRepresentation::Ptr rep2 = app->
loadData(filename2);
app->resetView(false);
app->addTransition(
vesActorTranslationTransition::create(rep1->actor())
->setInitialValue(vesVector3f(-4., 3.0, 0.))
->setFinalValue(vesVector3f(0., 0.0, 0.))
->setEasing(vesKiwiBounceOutEasing::create())
->setDuration(3.00)
->alsoStart( vesActorTranslationTransition::create
(rep2->actor())
->setInitialValue(vesVector3f(4., -3.0, 0.))
->setFinalValue(vesVector3f(0., 0.0, 0.))
->setEasing(vesKiwiBounceOutEasing::create())
->setDuration(3.00)
->alsoStart( vesKiwiCameraTransition::create(app
->camera())
->setInitialFrame(
vesVector3f(3.548, 0.5, 20.), // eye pt
vesVector3f(3.548, 0.5, 0.), // aim pt
vesVector3f(1., -1., 0.) // up
)
->setFinalFrame(
vesVector3f(0, 0., 10.), // eye pt
vesVector3f(3.548, 0.5, 0.), // aim pt
vesVector3f(0., +1., 0.) // up
)
->setDuration(3.0)
)
)
);
Usually, the call to addTransition() would be placed in a subroutine that is called whenever the representations should be animated. The representations are then either member variables (when they are permanent fixtures in the scene) or arguments to this subroutine (when they are being added to or removed from the scene).
References
[1] J. Heer, G. Robertson, “Animated Transitions in Statistical Data Graphics,”
http://vis.berkeley.edu/papers/animated_transitions/, 2007.
[2] R. Penner, "Easing Functions," http://www.robertpenner.com/easing/, 2001.
[3] A. Sitnik, "Easing Functions Cheat Sheet," http://easings.net/, 2013.
David Thompson is an R&D Engineer in the scientific computing group at Kitware, Inc., focused on interactive, visual tools for modeling, simulation, and analysis. He was a mechanical engineer once.
Aashish Chaudhary is an R&D Engineer on the Scientific Computing team at Kitware. Prior to joining Kitware, he developed a graphics engine and open-source tools for information and geo-visualization. Some of his interests are software engineering, rendering, and visualization
Casey Goodlett focuses on developing innovative software solutions to research problems in the fields of medical image analysis and computer vision. In particular, Casey has experience in registration of data (images, point clouds, surfaces), optimization methods, software application development, and software project management.