Animation
Trial includes support for describing animations – meaning how properties aught to evolve over time. These systems can be used to animate individual properties of objects or full skeleton rigs. The constructs on offer for this are fairly standard, allowing import from model files, including skinning and shape keys/morphs.
The relevant concepts for animation are as follows:
animation-track
An animation track is a sequence of keyframes and an interpolation method. Each keyframe is at a specific timecode, and has a value that the track should reach at that code. Timecodes between keyframes are interpolated between via theinterpolationmethod. A track's value can besampled for a desired time code.
Currently the values that a track can interpolate arereal,vec2,vec3,vec4, andquat. Other value types, especially composite types, need to be composed out of multiple tracks.clip
A clip is a collection of tracks. Clips retain whether they shouldloop-por not, and can benamed. When a clip issampled, the target object passed should be a sequence, table, or object that can be indexed into by thenameof thetracksin the clip.pose
A pose is a collection oftransforms for a skeleton. It represents a particular state for certain bones in the skeleton to be in. It also allows convenient access to theglobal-transformof a particular joint, and computing thematrix-paletteused to perform the skinning.skeleton
A skeleton contains all the information necessary for the skinning process, namely abind-poseandinv-bind-pose, arest-pose, and a table for thejoint-names.morph-group
Encapsulates a set of morph targets and their weights for a mesh. This is exposed as aweightsarray, a set oftexturesthat encode the vertex deformations for each mesh, and amorph-databuffer that holds the effective weights and indices of the morph targets to apply. When constructing amorph-group, you should pass the set of meshes that share the group as an initarg.animated-mesh
This is a helper class that contains the mesh data such as the extractedposition-normalsand packedvertex-data. It can be used to performcpu-skinning if needed over the standard gpu-driven skinning method. It also holds a set of meshes that encode the morph targets of the mesh inmorphs, along with themodel-namethat designates the larger morph group that the mesh is a part of. Finally it may hold aninitial-weightsarray.layer-controller
This mixin class implements theupdatemethod in such a way that extra animation poses can be layered onto the base pose. Layers can be added and removed viaadd-layerandremove-layer.fade-controller
This mixin class implements smooth playback of animation clips and the transitioning between them. As such implements a baseupdatemethod to animate thepose, and keeps track of possibly multiple fade targets, which can be added viafade-to. A base clip can also be immediately started viaplay.morph-group-controller
Handles the creation of the appropriatemorph-groupsfor all of the meshes in the model. Also provides theupdate-morph-datafunction to sync the data after animation updates the weights. To fetch the morph group for a mesh, usefind-morph.armature
A debug display to show poses and animation clips.animated-entity
A base class for animated 3d models. Requires ananimation-assetto be passed, but will take care of setting up everything else based on that asset. Is also afade-controller,layer-controller, andmorph-controllerso that animation clips can be easily used with the entity.
Note that it does not handle rendering of the mesh, only the vertex deformation needed to perform the animation.
When loading stuff from a model file you will not have to worry about any of the underlying stuff. All you need is an animated-entity and an asset to load the model with. From there you can just play, fade-to, and add-layer/remove-layer to manage your animations on the entities.
Animated-Assets
In order to use the animation pipeline from model files, the model format system needs to provide a subclass of the animation-asset. Currently the only format that supports this is the trial-gltf importer.
Using one should be as simple as this:
(define-asset (workbench model) model-file
#p"model.gltf")
(make-instance 'animated-entity :animation-asset (asset 'workbench 'model))Once loaded the asset must contain a skeleton, a hash table of clips, and a hash table of meshes. The animated-entity will automatically extract and reference the properties as needed when you play, fade-to, etc.
Trial will also parse out the following extra animation properties from the model, if supported:
next[string] The name of the clip to play after this.loop[boolean] Whether to loop this clip or not. Defaults to true if unset. Ifnextis set, this will be false.forward-kinematic[boolean] Whether this clip is forward-kinematic or not. If it is, then the root motion will be turned into physical movement instead.velocity-scale[float] The scale of the root motion when the clip is set to be forward-kinematic.blocking-p[boolean] Whether the animation should block from being switched away from.blend-duration[float] The default duration to use whenfade-toon this clip is called.
Defining Clips
Aside from the fully automated import of animations and skins from model files as used with the animated-entity, you can also programmatically define animation clips to animate other properties and features.
To do so, use define-clip which has the following general structure:
(define-clip sandstorm
(strength speed)
0.0 0.0 (vec 0.0 0.0)
1.0 0.8 (vec 0.2 0.0)
1.5 _ (vec 1.0 0.0)
2.0 1.0 _)Wherein _ is used to omit a track from a keyframe. By default track interpolations are set to :linear. If you specify :hermite or :cubic for a track, you must wrap each keyframe value in a list to pass the extra values needed for the interpolation handles.
Once a clip is defined, you can retrieve it by its name via clip and use sample to apply the track's effects to an object that contains the properties to be animated.
Note that in general sample is modifying, meaning it updates the values in place to avoid producing garbage.
Animation clips are only meant for tweening properties. There is no support for evaluating functions at certain times or running other more complex code. If you need a system to put together sequences of actions and other such changes, please have a look at action-lists. They are trivial to integrate with Trial and are geared for that use.
Morphs / Shape Keys
In addition to rigged animation, Trial also allows animation or deformation based on shape keys. In order to make use of morphs, you should use the morphed-entity. This entity holds a vector that adds the respective morph-group and data texture to each of the vertex-arrays of the entity.
When rendering a vertex-array, you must use the same index of the vertex-array to retrieve the respective morph data cell in the morphs vector. The car holds the morph-group of which you should bind the morph-data buffer to the shader-program, and the cdr holds the texture that you should bind to the morph_targets uniform. The basic-animated-entity takes care of all of this logic for you.
In order to control the weights, you can setf the respective entry in the morph-groups weights array. All weights should be single-floats in the range [0,1]. After setting them, you should update-morph-data to refresh the buffer.
Weights can also be animated via a weights-track. When setting its frames, it expects a values array that is longer than the times array. Frame values for the different morph targets are stored interleaved, meaning for instance for a linear interpolation track with two morph targets, the values should be
frame0_morph0 frame0_morph1 frame1_morph0 frame1_morph1 ...
When sampling from the track you should either pass an array of weights to animate, or a pose in which case the weights array corresponding to the track's weights in the pose is updated. In either case the array must be present and must match in length to the number of morph targets that the track expects. Usually you'll want to use the weights array of the corresponding morph-group instance.