Animation: add function to blend Action into pose

Add function `BKE_pose_apply_action_blend()`, which blends a given
Action into current pose. The Action is evaluated at a specified frame,
and the result is applied to the armature's pose.

A blend factor can be given to blend between the current pose and the
one in the Action. Quaternions are interpolated with SLERP; it is
assumed that their FCurves are consecutively stored in the Action.

This function will be used in the upcoming new Pose Library.
This commit is contained in:
Sybren A. Stüvel 2021-07-02 13:47:53 +02:00
parent 9473c61b36
commit 28dc07a153
4 changed files with 143 additions and 0 deletions

View File

@ -268,6 +268,12 @@ void animsys_evaluate_action(struct PointerRNA *ptr,
const struct AnimationEvalContext *anim_eval_context,
bool flush_to_original);
/* Evaluate action, and blend the result into the current values (instead of overwriting fully). */
void animsys_blend_in_action(struct PointerRNA *ptr,
struct bAction *act,
const AnimationEvalContext *anim_eval_context,
float blend_factor);
/* Evaluate Action Group */
void animsys_evaluate_action_group(struct PointerRNA *ptr,
struct bAction *act,

View File

@ -215,6 +215,10 @@ void BKE_pose_apply_action_all_bones(struct Object *ob,
struct bAction *action,
struct AnimationEvalContext *anim_eval_context);
void BKE_pose_apply_action_blend(struct Object *ob,
struct bAction *action,
struct AnimationEvalContext *anim_eval_context,
float blend_factor);
void vec_roll_to_mat3(const float vec[3], const float roll, float r_mat[3][3]);
void vec_roll_to_mat3_normalized(const float nor[3], const float roll, float r_mat[3][3]);

View File

@ -621,6 +621,115 @@ static void animsys_evaluate_fcurves(PointerRNA *ptr,
}
}
/* This function assumes that the quaternion is fully keyed, and is stored in array index order. */
static void animsys_quaternion_evaluate_fcurves(PathResolvedRNA quat_rna,
FCurve *first_fcurve,
const AnimationEvalContext *anim_eval_context,
float r_quaternion[4])
{
FCurve *quat_curve_fcu = first_fcurve;
for (int prop_index = 0; prop_index < 4; ++prop_index, quat_curve_fcu = quat_curve_fcu->next) {
/* Big fat assumption that the quaternion is fully keyed, and stored in order. */
BLI_assert(STREQ(quat_curve_fcu->rna_path, first_fcurve->rna_path) &&
quat_curve_fcu->array_index == prop_index);
quat_rna.prop_index = prop_index;
r_quaternion[prop_index] = calculate_fcurve(&quat_rna, quat_curve_fcu, anim_eval_context);
}
}
/* This function assumes that the quaternion is fully keyed, and is stored in array index order. */
static void animsys_blend_fcurves_quaternion(PathResolvedRNA *anim_rna,
FCurve *first_fcurve,
const AnimationEvalContext *anim_eval_context,
const float blend_factor)
{
float current_quat[4];
RNA_property_float_get_array(&anim_rna->ptr, anim_rna->prop, current_quat);
float target_quat[4];
animsys_quaternion_evaluate_fcurves(*anim_rna, first_fcurve, anim_eval_context, target_quat);
float blended_quat[4];
interp_qt_qtqt(blended_quat, current_quat, target_quat, blend_factor);
RNA_property_float_set_array(&anim_rna->ptr, anim_rna->prop, blended_quat);
}
/* LERP between current value (blend_factor=0.0) and the value from the FCurve (blend_factor=1.0)
*/
static void animsys_blend_in_fcurves(PointerRNA *ptr,
ListBase *fcurves,
const AnimationEvalContext *anim_eval_context,
const float blend_factor)
{
char *channel_to_skip = NULL;
int num_channels_to_skip = 0;
LISTBASE_FOREACH (FCurve *, fcu, fcurves) {
if (num_channels_to_skip) {
/* For skipping already-handled rotation channels. Rotation channels are handled per group,
* and not per individual channel. */
BLI_assert(channel_to_skip != NULL);
if (STREQ(channel_to_skip, fcu->rna_path)) {
/* This is indeed the channel we want to skip. */
num_channels_to_skip--;
continue;
}
}
if (!is_fcurve_evaluatable(fcu)) {
continue;
}
PathResolvedRNA anim_rna;
if (!BKE_animsys_rna_path_resolve(ptr, fcu->rna_path, fcu->array_index, &anim_rna)) {
continue;
}
if (STREQ(RNA_property_identifier(anim_rna.prop), "rotation_quaternion")) {
animsys_blend_fcurves_quaternion(&anim_rna, fcu, anim_eval_context, blend_factor);
/* Skip the next three channels, because those have already been handled here. */
MEM_SAFE_FREE(channel_to_skip);
channel_to_skip = BLI_strdup(fcu->rna_path);
num_channels_to_skip = 3;
continue;
}
/* TODO(Sybren): do something similar as above for Euler and Axis/Angle representations. */
const float fcurve_value = calculate_fcurve(&anim_rna, fcu, anim_eval_context);
float current_value;
float value_to_write;
if (BKE_animsys_read_from_rna_path(&anim_rna, &current_value)) {
value_to_write = (1 - blend_factor) * current_value + blend_factor * fcurve_value;
switch (RNA_property_type(anim_rna.prop)) {
case PROP_BOOLEAN:
/* Without this, anything less than 1.0 is converted to 'False' by
* ANIMSYS_FLOAT_AS_BOOL(). This is probably not desirable for blends, where anything
* above a 50% blend should act more like the FCurve than like the current value. */
case PROP_INT:
case PROP_ENUM:
value_to_write = roundf(value_to_write);
break;
default:
/* All other types are just handled as float, and value_to_write is already correct. */
break;
}
}
else {
/* Unable to read the current value for blending, so just apply the FCurve value instead. */
value_to_write = fcurve_value;
}
BKE_animsys_write_to_rna_path(&anim_rna, value_to_write);
}
MEM_SAFE_FREE(channel_to_skip);
}
/* ***************************************** */
/* Driver Evaluation */
@ -769,6 +878,16 @@ void animsys_evaluate_action(PointerRNA *ptr,
animsys_evaluate_fcurves(ptr, &act->curves, anim_eval_context, flush_to_original);
}
/* Evaluate Action and blend it into the current values of the animated properties. */
void animsys_blend_in_action(PointerRNA *ptr,
bAction *act,
const AnimationEvalContext *anim_eval_context,
const float blend_factor)
{
action_idcode_patch_check(ptr->owner_id, act);
animsys_blend_in_fcurves(ptr, &act->curves, anim_eval_context, blend_factor);
}
/* ***************************************** */
/* NLA System - Evaluation */

View File

@ -76,6 +76,20 @@ void BKE_pose_apply_action_all_bones(struct Object *ob,
animsys_evaluate_action(&pose_owner_ptr, action, anim_eval_context, false);
}
void BKE_pose_apply_action_blend(struct Object *ob,
struct bAction *action,
struct AnimationEvalContext *anim_eval_context,
const float blend_factor)
{
auto evaluate_and_blend = [blend_factor](PointerRNA *ptr,
bAction *act,
const AnimationEvalContext *anim_eval_context) {
animsys_blend_in_action(ptr, act, anim_eval_context, blend_factor);
};
pose_apply(ob, action, anim_eval_context, evaluate_and_blend);
}
namespace {
void pose_apply(struct Object *ob,
struct bAction *action,