I am trying to do some simple animation blending and I am quite confused on how to handle pushing the new animations to the skeleton and then blending it and then stopping the blending so that the original animation continues playing.
This is how I have my set up at the moment:
I have an animation state and I am switching the state using WSAD keys and pushing the relevant animation clip to blend to.
The playerController is of type AnimationData which stores per frame rotations. The AnimationData class stores a std::vector<AnimationData> transitionTo; which I am pushing animations to in order to transition. Quite new to this and would appreciate any insight on how to make this work correctly.
// player skeleton
playerController.Render(frameNumber, time);
// Switch the animation being rendered based on the current state of the player
switch (m_AnimState)
{
// If the player is running, render the running animation
case Running:
if(playerController.transitionTo.size() < 1)
{
playerController.timeStart = std::chrono::high_resolution_clock::now();
playerController.transitionTo.push_back(runCycle);
}
break;
// If the player is turning left, render the turning left animation
case TurnLeft:
if(playerController.transitionTo.size() < 1)
{
playerController.timeStart = std::chrono::high_resolution_clock::now();
playerController.transitionTo.push_back(veerLeftCycle);
}
m_AnimState = Running;
break;
// // If the player is turning right, render the turning right animation
// The default state of the player is in rest pose, (Idle)
default:
break;
}
This is how I am rendering the joints and blending the two animations currently
void AnimationData::Render(Mat4& viewMatrix, float scale, int frame, double time)
{
RenderJoint(viewMatrix, Mat4::Identity(), &this->root, scale, frame);
}
// This will render a joint for a single animation frame
void AnimationData::render_joint(Mat4& viewMatrix, Mat4 parentMatrix, Joint* joint, float scale, int frame)
{
// Time since animation started
auto currentTime = std::chrono::high_resolution_clock::now();
double nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(currentTime - timeStart).count();
double time_in_seconds = nanoseconds / 1e+9;
// Determine updated pose for current joint
auto f = (frame + 1) % frame_count;
Quaternion updatedPose = CalculateNewPose(f, time_in_seconds, scale, joint->id);
Mat4 finalRotationMatrix = updatedPose.ToRotationMatrix();
vec4 offset_from_parent = ...
auto Offset = Mat4::Translate({offset.x, offset.y, offset.z});
auto global = Mat4::Identity();
global = parentMatrix * Offset * finalRotationMatrix;
// multiply offset by parent matrix to get it into the correct space to render
offset_from_parent = parentMatrix * offset_from_parent;
// for each child of the current joint, recursively render the joint
// using start and end position
for(auto& child : joint->Children)
{
auto end = global * vec3(child.joint_offset[0], child.joint_offset[1], child.joint_offset[2], 1.0f);
auto endpoint = end.toVec3();
auto start = offset_from_parent.toVec3();
RenderCylinder(viewMatrix, start, endpoint, finalRotationMatrix);
// Recursively render the child joint
RenderJoint(viewMatrix, global, &child, scale, frame);
}
}
// Animation blending
Quaternion AnimationData::BlendPose(vec3& a, vec3& b, float time, float slerpAmount)
{
// Animation A rotation
Quaternion a_rotX = Quaternion(a.x, vec3(1.0f, 0.0f, 0.0f).unit()); a_rotX.Normalize();
Quaternion a_rotY = Quaternion(a.y, vec3(0.0f, 1.0f, 0.0f).unit()); a_rotY.Normalize();
Quaternion a_rotZ = Quaternion(a.z, vec3(0.0f, 0.0f, 1.0f).unit()); a_rotZ.Normalize();
Quaternion a_animARotQuat = (a_rotZ * a_rotY) * a_rotX;
// Animation B rotation
Quaternion b_rotX = Quaternion(b.x, vec3(1.0f, 0.0f, 0.0f).unit()); b_rotX.Normalize();
Quaternion b_rotY = Quaternion(b.y, vec3(0.0f, 1.0f, 0.0f).unit()); b_rotY.Normalize();
Quaternion b_rotZ = Quaternion(b.z, vec3(0.0f, 0.0f, 1.0f).unit()); b_rotZ.Normalize();
Quaternion b_animARotQuat = (b_rotZ * b_rotY) * b_rotX;
// Determine the slerp amount using time, ensure its 0-1 range.
double amount_to_lerp = std::fmod(time, slerpAmount) / slerpAmount;
Quaternion blendedRot = Slerp(a_animARotQuat, b_animARotQuat, amount_to_lerp);
return blendedRot;
}
Quaternion AnimationData::CalculateNewPose(int frame, float time, float slerpAmount, int jointID)
{
// Set up pose for animation A and B
vec4 translation = vec4(0.0f, 0.0f, 0.0f, 1.0f);
vec3 anim_pose_A = SampleAnimation(frame, jointID);
vec3 anim_pose_B = SampleAnimation(frame, jointID);
// if there is a transition animation clip to transition to, set rotation to the animation clips rotation
// at this current frame
if(!transitionTo.empty())
{
// Get the first animation clip and sample animation to get current joint pose transforms
anim_pose_B = transitionTo[transitionTo.size() - 1].SampleAnimation(frame, jointID);
}
// Blend the poses
Quaternion blendedPose = BlendPose(anim_pose_A, anim_pose_B, time, slerpAmount);
return blendedPose;
}