7.2. Non-interactive precalculated animation

This approach means that at loading time we "fix" the whole the animation. Animation becomes something like a 3D movie.

A downside is that loading time (and other resources usage, like memory) is larger, especially for longer animations. That's because we store the whole animation in memory.

Huge advantage of this method: once loaded, animation can be played ultra-fast. Actually, it's as fast a displaying a still model (currently, this is exactly what is done under the hood: at each time, we simply display one chosen frame of animation; in the future this may change, but still will be lighting fast). That's the reward for long loading time and "fixing" the animation.

We generally do not recommend this method anymore.

7.2.1. 3D formats support

7.2.2. Structural equality

TNodeInterpolator class is used to build and render precalculated animations. For each provided frame we have an associated time. The resulting animation will change the first model to the last one, such that at any time point we will either use one of the predefined models (if point in time is close to the model's associated time) or a new model created by interpolating between two successive models in time.

Under the hood, we have quite intelligent algorithm that checks each pair of two successive models for structural equality. Structural equality simply means that the two models are equal, with the exception of various floating-point fields, on which they may differ. The idea is that we can define linear interpolation between two models that are structurally equal. So when you specify two structurally equal models for an animation, we can generate many intermediate scenes (this is the ScenesPerTime parameter to loading method) that smoothly show one model changing into the other. This can interpolate any floating-point field value, like SFColor, SFFloat, SFMatrix, SFRotation, SFVec2f and SFVec3f an all equivalent multi-valued fields (they can differ in values, but still must have the same number of items).

For example, the first model may be a small sphere with blue color, and the second model may be a larger sphere with white color. The resulting animation depicts a growing sphere with color fading from blue to white. More examples:

  • Moving, rotating, scaling objects may be expressed by changing transformation values.

  • Any kind of morphing (mesh deformation) may be expressed by changing values of IndexedFaceSet coordinates.

  • Materials, colors, lights may change. Even such properties like a material transparency, or a light position or direction.

  • Texture coordinates may change to achieve effects like a moving water surface.

Another advantage of structural equality is that we will perform aggressive merging of two structurally equal models. This means that when two nodes are detected to be exactly equal, one of them will be removed (and pointers rearranged to both point to the same node in memory). If the nodes are not exactly equal, we still check their children and possibly merge them. This is a huge saving in terms of memory, as practically all the non-animated parts of the model will only be kept once in memory. It's implemented quite intelligently, so it's actually a relatively fast process done during the model loading.

All the models of the animation do not actually have to be structurally equal. You can even change one model into something completely different. But in these cases we cannot generate smooth transition from one model to the other, and the animation will just show a sudden change into new version at it's time.

If you're concerned that possibly some parts of your animation are not structurally equal, you can always load them into view3dscene run with --debug-log command-line option. Then, at loading time, you will get messages on console if two successive models were not detected as structurally equal (and so a sharp change from one to the other will be shown in animation). The message will also describe exactly where the difference is found.

7.2.3. Generating intermediate scenes

First of all, the scenes are not interpolated when rendering. Instead, at loading time, we create a number of new interpolated models and save them (along with the models that were specified explicitly). The parameter ScenesPerTime says with what granularity the intermediate scenes are constructed for a time unit.

If you specify too large ScenesPerTime your animations will take a lot of time to prepare and will require a lot of memory. On the other hand too small ScenesPerTime value will result in an unpleasant jagged animation. Ideally, ScenesPerTime should be >= than the number of frames you will render in your time unit, but this is usually way too large value.

Special value of 0 for ScenesPerTime means that you want only the explicitly passed nodes in the scene, nothing more. No more intermediate scenes will ever be created. This creates a trivial animation that suddenly jumps from one still model to the next at specified times. It may be useful if you already have generated a lot of models, densely distributed over time, and you don't need TNodeInterpolator to insert any more scenes. Structural equality (or it's lack) doesn't change the look of such animation, as no additional interpolation is done when loading, but still structurally equal models may be merged to conserve memory use.

Internally, the TNodeInterpolator wraps each model (that was specified explicitly or created by interpolation) in a new node. This means that we have all the features of our static OpenGL rendering available when doing animations too.

7.2.4. Storing precalculated animations in castle-anim-frames files

We have a special file format to express precalculated animations: castle-anim-frames, Castle Game Engine animations. It references a number of static 3D model files and their corresponding times, describing the animation.

If you want to experiment with castle-anim-frames format, view3dscene can load and play animations in castle-anim-frames format. You can find example castle-anim-frames animations in VRML/X3D demo models (see directory castle-anim-frames/), the sources of our engine also contain simple examples in directory castle_game_engine/examples/ (like resource_animations that plays animations specified in resource.xml files for game creatures/items). Also The Castle uses such animations for all creatures and weapons.

In general, using a single glTF, X3D, VRML, Spine file is a much better approach than castle-anim-frames files.

Also, castle-anim-frames files may waste a lot of disk space if your animation tries to change two pieces of your model with drastically different speeds. Consider this:

  1. It's OK to create an animation with a box that blinks (changes color) 100 times per time unit. Just 2 model files are needed for this, with castle-anim-frames file specifying to loop them over a short period of time.

  2. It's also OK to create an animation with a sphere that blinks only once for a given time unit.

  3. But if you want to create an animation that contains both the box (blinking 100 times/time unit) and the sphere (blinking once for a time unit), you will have to prepare 100 still 3D files to express this!

VRML interpolators don't have this problem, since every interpolator has it's own set of keys. So both can be placed within the same file, without the need to explicitly write 100 values anywhere.

Despite this, there remains one practical advantage of using castle-anim-frames file format: you can design your animations using any authoring software that can export static VRML files. If your modeler can design animations, but doesn't save them to VRML interpolator nodes, all you have to do is to export your models a couple of times from a couple of different points in time.

In the old days, this allowed us to use Blender do design animations and export them. Nowadays, we export from Blender to glTF using standard Blender exporter, and there's no need for castle-anim-frames.