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 theinterpolation
method. A track's value can besample
d 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-p
or not, and can bename
d. When a clip issample
d, the target object passed should be a sequence, table, or object that can be indexed into by thename
of thetracks
in the clip.pose
A pose is a collection oftransform
s for a skeleton. It represents a particular state for certain bones in the skeleton to be in. It also allows convenient access to theglobal-transform
of a particular joint, and computing thematrix-palette
used to perform the skinning.skeleton
A skeleton contains all the information necessary for the skinning process, namely abind-pose
andinv-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 aweights
array, a set oftextures
that encode the vertex deformations for each mesh, and amorph-data
buffer 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-normals
and packedvertex-data
. It can be used to performcpu-skin
ning 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-name
that designates the larger morph group that the mesh is a part of. Finally it may hold aninitial-weights
array.layer-controller
This mixin class implements theupdate
method in such a way that extra animation poses can be layered onto the base pose. Layers can be added and removed viaadd-layer
andremove-layer
.fade-controller
This mixin class implements smooth playback of animation clips and the transitioning between them. As such implements a baseupdate
method 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-groups
for all of the meshes in the model. Also provides theupdate-morph-data
function 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-asset
to be passed, but will take care of setting up everything else based on that asset. Is also afade-controller
,layer-controller
, andmorph-controller
so 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. Ifnext
is 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-to
on 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-group
s 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.