Beginning with version 2021 SP1, HOOPS Visualize has the ability to perform simple animations. These animations can be used to demonstrate how assemblies fit together, better show complex parts within a single view, demonstrate how an object changes over time, and more.
As the developer, you have a certain amount of control over the complexity and structure of your own scenes, and HOOPS Visualize will render frames as fast as the hardware is able. However, please note that while Visualize can perform simple animations reasonably well, it is first and foremost an engineering visualization engine. High performance, computationally intensive animation involving a large amount of geometry and post-processing is theoretically possible, but is not the goal of the animation API. It follows that such performance should not necessarily be expected. If you require this capability, please contact support to determine the best options for your project.
HOOPS Visualize has several different properties that can be animated, including scale, rotation, translation, camera, color, and opacity.
Programmatically speaking, animations are composed of a number of objects which describe how the animation changes over time. From lowest level to highest, these objects are arranged as follows:
Keyframes > KeyframeArray > Sampler > Animation > AnimationControl
- Keyframes. Like in traditional animation, a keyframe defines start and end points of an animation, or portion of an animation. In HOOPS Visualize, there are a number of different keyframe types, depending on what kind of animation you're creating. These are further explained below.
- KeyframeArray. A collection of keyframes the sampler will use to create the animation.
- Sampler. Samples an animation based on keyframes in order to interpolate between the scene's start and end state.
- Animation. Uses a sampler to create one or more animation channels, which describe what kind of animation will be occurring in the scene. Scale operations will use a scale channel, rotation animations will use a rotation channel, etc. Created from a HPS::Model object.
- AnimationControl. Used to conduct playback of animations. Created by and associated with the HPS::View.
In the next section, we'll look at how these parts fit together.
Linear translation example
Let's take a look at a short code example. This snippet creates a translation animation which moves the model along the X axis.
Looking over this code, you may infer by the class names that we are preparing a linear animation using two vector objects. This means the keyframes will be timed in a linear fashion along a straight line. Let's break this code down to explain more about what's happening.
View hierarchy references. The first part of the code is simply getting references to the various view hierarchy objects we'll need for the subsequent sections - HPS::Canvas, HPS::View, HPS::Model. Depending on your window framework, this might be slightly different from what is shown, but the concept is the same.
Building keyframes. The first piece of the animation, creating the keyframes, is simple. We have two keyframes - a start and an end - and we want to translate uniformly between them. The first parameter indicates the "tick" at which that keyframe will be reached (more on ticks later). The Vector parameters are describing the translation amount. So in this code, we are starting at a position of [0, 0, 0] and ending at a position of [-5, 0, 0]. If, for example, you wanted to translate in a third direction, simply add another keyframe with a new position.
Collecting keyframes. The next code block is simply adding all keyframes of our animation into a collection which will be used in the next step.
Sampler. The next task is to run the keyframe array through the sampler. Notice that we are using VectorKeyframe, so we must use HPS::VectorSampler (other animation types will use different keyframe and sampler objects). Here is where we set the animation to Linear (other interpolation options include Constant, SphericalLinear, and Spline).
Animation. The HPS::Animation object is created using the HPS::Model that you want to animate (for more on Models and Views, see this page). In this example, we are only animating the model's translation, so we add a single translation channel using AddTranslationChannel. The name "translationChannel" is only an identifier - you can use any name you find convenient.
The second parameter is a HPS::KeyPath, which points to the segment that will be affected by this animation. In this code snippet, we only have a single unambiguous hierarchy: Canvas > Layout > View > Model, so we can simply animate the entire canvas as a HPS::SprocketPath. However, if you have multiple views, models, or parts of models that you want to animate independently, you will need to be more specific with your HPS::KeyPath, specifying your keys down to the individual segment (see the API docs for SprocketPath). Lastly, we add the sampler from the previous step.
Additional channels can be created to animate different aspects of the model, or to combine multiple animations in a single model. See this section for an example of combining two animations.
Before playback is conducted you must first supply the animation(s) to the AnimationControl. In our example, we are running the animation over 4000 milliseconds. We are also setting the milliseconds-per-tick value to 1000. This means that each "tick" of the animation will take one second, although this can be adjusted to your requirements. The HPS::AnimationControl object also allows you to start, stop, resume, and wait on the animation.
The result of the animation is as follows:
Notice the code sample above does not load a model. If you intend to run the code in this section, be sure to load a model before you execute.
Animated rotations rely on quaternions to describe the rotation. As such, we'll need to use animation objects that can handle quaternion rotation. Although the objects are built in slightly different ways, the fundamental structure of the animation remains the same. We'll get a reference to the view hierarchy, specify keyframes, specify a rotation, and add this data to an HPS::Animation object.
Again, you'll have to load the model in a separate step. The result of this animation is a 360 degree rotation:
You may have noticed that the model isn't quite spinning around the center of the model. By default, rotations will rotate around the target segment's origin point, which might not be inside the model itself. If you want to rotate about something other than the segment center point, you must make use of both a translation and rotation. There are many ways to create that transformation, but let's imagine you have a matrix that represents the full transformation you want to occur for some target at tick X. You could use HPS::MatrixKit::Decompose to get a translation, rotation, and scale from that matrix, then make a keyframe for each of those values that occur at tick X and append them to their respective channel's keyframe arrays. The transformation variables like scale, translation, and rotation are decomposed in the animation channels so that they can be directly interpolated using the preferences of the sampler.
We've looked at how animations work in general, and we've discussed how simple translation and rotation animations work. Since we already have the code for both animations, combining the two can be easily done. All that is needed is to add a rotation channel with the new rotation sampler to the original HPS::Animation object. The complete code sample is as follows:
The combined animation:
Other animation types
Other animations types are created in a similar way - you would just need to use different objects to build the animation. For example, to animate color, you would use a set of HPS::RGBColorKeyframe, HPS::RGBColorKeyframeArray, and HPS::RGBColorSampler. The same HPS::Animation class accepts the color keyframes and will work based on the sampler you provide.
To animate scale, you would build the animation in the same way as the translation example (see above), using HPS::VectorKeyframe and associated classes. In this case, the keyframe values represent scale rather than position. And instead of adding a translation channel, you would add a scale channel using HPS::Animation::AddScaleChannel. Here's an example of code that scales a model by 300%:
Supported animation types, keyframes, and the samplers they work with are listed in the following table:
|Animation type||Associated keyframe||Associated sampler|
|Camera up vector||HPS::VectorKeyframe||HPS::VectorSampler|
So far, all of our examples have used linear interpolation, however, there are a few other options available if you require them:
|HPS::Sampler::InterpolationType::Constant||Will not interpolate your model between keyframes. Instead, the model abruptly changes state to the next keyframe.|
|HPS::Sampler::InterpolationType::Linear||Interpolates between keyframes in a steady manner.|
|HPS::Sampler::InterpolationType::SphericalLinear||Currently used just for rotation animations. Can be used as a smoother way to interpolate quaternions - commonly referred to as "slerp".|
|HPS::Sampler::InterpolationType::Spline||HOOPS Visualize will compute a spline between keyframes that will be used to affect the animation.|
The next section illustrates how the interpolation types change the result. It will also demonstrate how to animate specific segments rather than the entire scene.
Animating specific segments
Below is an example showing the difference between constant and linear interpolation of a color animation. It's also an example of how to animate different segments in the same scene. We've created some sample geometry - two cylinders which each exist in separate segments. These cylinders are using the same keyframes and colors, but use different interpolation types. Also note how the HPS::KeyPath objects are built from leaf to root, specifying two individual segments that we want to animate independent of the rest of the scene. This is different from previous examples, where we used the HPS::KeyPath parameter to simply animate all geometry in the canvas as a whole:
Note there is nothing to load in a separate step - you can run this example as-is in one of the sandboxes included with HOOPS Visualize. The result of this code is shown below. The linearly interpolated cylinder is at the top, the constant interpolation is below:
Rendering speed. HOOPS Visualize will do its best to keep your animation running according to your specification. However, updates scheduled by the animation API are not interrupted if they cannot be completed before the next frame is scheduled. Therefore, if the hardware is unable to keep up with the rendering demands of the scene, frames will be skipped. For example, let's say you set a "milliseconds-per-tick" value of 1000 with ten keyframes which occur at ticks 1-10 (keyframe value changes occur every second), but the hardware needs two seconds to complete each update. In this case, you would see the animation render every other tick.