vk-book/shared/UtilsAnim.cpp
2025-05-23 21:13:53 -04:00

319 lines
12 KiB
C++

#include "UtilsAnim.h"
#include "UtilsGLTF.h"
#include "VulkanApp.h"
#include <assimp/GltfMaterial.h>
#include <assimp/cimport.h>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <assimp/types.h>
#include <cmath>
using glm::quat;
AnimationChannel initChannel(const aiNodeAnim* anim)
{
AnimationChannel channel;
channel.pos.resize(anim->mNumPositionKeys);
for (uint32_t i = 0; i < anim->mNumPositionKeys; ++i) {
channel.pos[i] = { .pos = aiVector3DToVec3(anim->mPositionKeys[i].mValue), .time = (float)anim->mPositionKeys[i].mTime };
}
channel.rot.resize(anim->mNumRotationKeys);
for (uint32_t i = 0; i < anim->mNumRotationKeys; ++i) {
channel.rot[i] = { .rot = aiQuaternionToQuat(anim->mRotationKeys[i].mValue), .time = (float)anim->mRotationKeys[i].mTime };
}
channel.scale.resize(anim->mNumScalingKeys);
for (uint32_t i = 0; i < anim->mNumScalingKeys; ++i) {
channel.scale[i] = { .scale = aiVector3DToVec3(anim->mScalingKeys[i].mValue), .time = (float)anim->mScalingKeys[i].mTime };
}
return channel;
}
template <typename T> uint32_t getTimeIndex(const std::vector<T>& t, float time)
{
return std::max(
0,
(int)std::distance(t.begin(), std::lower_bound(t.begin(), t.end(), time, [&](const T& lhs, float rhs) { return lhs.time < rhs; })) -
1);
}
float interpolationVal(float lastTimeStamp, float nextTimeStamp, float animationTime)
{
return (animationTime - lastTimeStamp) / (nextTimeStamp - lastTimeStamp);
}
vec3 interpolatePosition(const AnimationChannel& channel, float time)
{
if (channel.pos.size() == 1)
return channel.pos[0].pos;
uint32_t start = getTimeIndex<>(channel.pos, time);
uint32_t end = start + 1;
float mix = interpolationVal(channel.pos[start].time, channel.pos[end].time, time);
return glm::mix(channel.pos[start].pos, channel.pos[end].pos, mix);
}
glm::quat interpolateRotation(const AnimationChannel& channel, float time)
{
if (channel.rot.size() == 1)
return channel.rot[0].rot;
uint32_t start = getTimeIndex<>(channel.rot, time);
uint32_t end = start + 1;
float mix = interpolationVal(channel.rot[start].time, channel.rot[end].time, time);
return glm::slerp(channel.rot[start].rot, channel.rot[end].rot, mix);
}
vec3 interpolateScaling(const AnimationChannel& channel, float time)
{
if (channel.scale.size() == 1)
return channel.scale[0].scale;
uint32_t start = getTimeIndex<>(channel.scale, time);
uint32_t end = start + 1;
float coef = interpolationVal(channel.scale[start].time, channel.scale[end].time, time);
return glm::mix(channel.scale[start].scale, channel.scale[end].scale, coef);
}
mat4 animationTransform(const AnimationChannel& channel, float time)
{
mat4 translation = glm::translate(mat4(1.0f), interpolatePosition(channel, time));
mat4 rotation = glm::toMat4(glm::normalize(interpolateRotation(channel, time)));
mat4 scale = glm::scale(mat4(1.0f), interpolateScaling(channel, time));
return translation * rotation * scale;
}
mat4 animationTransformBlending(const AnimationChannel& channel1, float time1, const AnimationChannel& channel2, float time2, float weight)
{
mat4 trans1 = glm::translate(mat4(1.0f), interpolatePosition(channel1, time1));
mat4 trans2 = glm::translate(mat4(1.0f), interpolatePosition(channel2, time2));
mat4 translation = glm::mix(trans1, trans2, weight);
quat rot1 = interpolateRotation(channel1, time1);
quat rot2 = interpolateRotation(channel2, time2);
mat4 rotation = glm::toMat4(glm::normalize(glm::slerp(rot1, rot2, weight)));
vec3 scl1 = interpolateScaling(channel1, time1);
vec3 scl2 = interpolateScaling(channel2, time2);
mat4 scale = glm::scale(mat4(1.0f), glm::mix(scl1, scl2, weight));
return translation * rotation * scale;
}
MorphState morphTransform(const MorphTarget& target, const MorphingChannel& channel, float time)
{
MorphState ms;
ms.meshId = target.meshId;
float mix = 0.0f;
int start = 0;
int end = 0;
if (channel.key.size() > 0) {
start = getTimeIndex(channel.key, time);
end = start + 1;
mix = interpolationVal(channel.key[start].time, channel.key[end].time, time);
}
for (uint32_t i = 0; i < std::min((uint32_t)target.offset.size(), (uint32_t)MAX_MORPH_WEIGHTS); ++i) {
ms.morphTarget[i] = target.offset[channel.key[start].mesh[i]];
ms.weights[i] = glm::mix(channel.key[start].weight[i], channel.key[end].weight[i], mix);
}
return ms;
}
void initAnimations(GLTFContext& glTF, const aiScene* scene)
{
glTF.animations.resize(scene->mNumAnimations);
for (uint32_t i = 0; i < scene->mNumAnimations; ++i) {
Animation& anim = glTF.animations[i];
anim.name = scene->mAnimations[i]->mName.C_Str();
anim.duration = scene->mAnimations[i]->mDuration;
anim.ticksPerSecond = scene->mAnimations[i]->mTicksPerSecond;
for (uint32_t c = 0; c < scene->mAnimations[i]->mNumChannels; c++) {
const aiNodeAnim* channel = scene->mAnimations[i]->mChannels[c];
const char* boneName = channel->mNodeName.data;
uint32_t boneId = glTF.bonesByName[boneName].boneId;
if (boneId == ~0u) {
for (const GLTFNode& node : glTF.nodesStorage) {
if (node.name != boneName)
continue;
boneId = node.modelMtxId;
glTF.bonesByName[boneName] = {
.boneId = boneId,
.transform = glTF.hasBones ? glm::inverse(node.transform) : mat4(1),
};
break;
}
}
assert(boneId != ~0u);
anim.channels[boneId] = initChannel(channel);
}
const uint32_t numMorphTargetChannels = scene->mAnimations[i]->mNumMorphMeshChannels;
anim.morphChannels.resize(numMorphTargetChannels);
for (uint32_t c = 0; c < numMorphTargetChannels; c++) {
const aiMeshMorphAnim* channel = scene->mAnimations[i]->mMorphMeshChannels[c];
MorphingChannel& morphChannel = anim.morphChannels[c];
morphChannel.name = channel->mName.C_Str();
morphChannel.key.resize(channel->mNumKeys);
for (uint32_t k = 0; k < channel->mNumKeys; ++k) {
MorphingChannelKey& key = morphChannel.key[k];
key.time = channel->mKeys[k].mTime;
for (uint32_t v = 0; v < std::min((uint32_t)MAX_MORPH_WEIGHTS, channel->mKeys[k].mNumValuesAndWeights); ++v) {
key.mesh[v] = channel->mKeys[k].mValues[v];
key.weight[v] = channel->mKeys[k].mWeights[v];
}
}
}
}
}
void updateAnimation(GLTFContext& glTF, AnimationState& anim, float dt)
{
if (!anim.active || (anim.animId == ~0u)) {
glTF.morphing = false;
glTF.skinning = false;
return;
}
const Animation& activeAnim = glTF.animations[anim.animId];
anim.currentTime += activeAnim.ticksPerSecond * dt;
if (anim.playOnce && anim.currentTime > activeAnim.duration) {
anim.currentTime = activeAnim.duration;
anim.active = false;
} else
anim.currentTime = fmodf(anim.currentTime, activeAnim.duration);
// Apply animations
std::function<void(GLTFNodeRef gltfNode, const mat4& parentTransform)> traverseTree = [&](GLTFNodeRef gltfNode,
const mat4& parentTransform) {
const GLTFBone& bone = glTF.bonesByName[glTF.nodesStorage[gltfNode].name];
const uint32_t boneId = bone.boneId;
if (boneId != ~0u) {
assert(boneId == glTF.nodesStorage[gltfNode].modelMtxId);
auto channel = activeAnim.channels.find(boneId);
const bool hasActiveChannel = channel != activeAnim.channels.end();
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] =
parentTransform *
(hasActiveChannel ? animationTransform(channel->second, anim.currentTime) : glTF.nodesStorage[gltfNode].transform);
glTF.skinning = true;
} else {
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] = parentTransform * glTF.nodesStorage[gltfNode].transform;
}
for (uint32_t i = 0; i < glTF.nodesStorage[gltfNode].children.size(); i++) {
const GLTFNodeRef child = glTF.nodesStorage[gltfNode].children[i];
traverseTree(child, glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId]);
}
};
traverseTree(glTF.root, mat4(1.0f));
for (const std::pair<std::string, GLTFBone>& b : glTF.bonesByName) {
if (b.second.boneId != ~0u) {
glTF.matrices[b.second.boneId] = glTF.matrices[b.second.boneId] * b.second.transform;
}
}
glTF.morphStates.resize(glTF.meshesStorage.size());
// update morphing
if (glTF.enableMorphing) {
if (!activeAnim.morphChannels.empty()) {
for (size_t i = 0; i < activeAnim.morphChannels.size(); ++i) {
const MorphingChannel& channel = activeAnim.morphChannels[i];
const uint32_t meshId = glTF.meshesRemap[channel.name];
const MorphTarget& morphTarget = glTF.morphTargets[meshId];
if (morphTarget.meshId != ~0u) {
glTF.morphStates[morphTarget.meshId] = morphTransform(morphTarget, channel, anim.currentTime);
}
}
glTF.morphing = true;
}
}
}
void updateAnimationBlending(GLTFContext& glTF, AnimationState& anim1, AnimationState& anim2, float weight, float dt)
{
if (anim1.active && anim2.active) {
const Animation& activeAnim1 = glTF.animations[anim1.animId];
anim1.currentTime += activeAnim1.ticksPerSecond * dt;
if (anim1.playOnce && anim1.currentTime > activeAnim1.duration) {
anim1.currentTime = activeAnim1.duration;
anim1.active = false;
} else {
anim1.currentTime = fmodf(anim1.currentTime, activeAnim1.duration);
}
const Animation& activeAnim2 = glTF.animations[anim2.animId];
anim2.currentTime += activeAnim2.ticksPerSecond * dt;
if (anim2.playOnce && anim2.currentTime > activeAnim2.duration) {
anim2.currentTime = activeAnim2.duration;
anim2.active = false;
} else {
anim2.currentTime = fmodf(anim2.currentTime, activeAnim2.duration);
}
// Update skinning
std::function<void(GLTFNodeRef gltfNode, const mat4& parentTransform)> traverseTree = [&](GLTFNodeRef gltfNode,
const mat4& parentTransform) {
const GLTFBone& bone = glTF.bonesByName[glTF.nodesStorage[gltfNode].name];
const uint32_t boneId = bone.boneId;
if (boneId != ~0u) {
auto channel1 = activeAnim1.channels.find(boneId);
auto channel2 = activeAnim2.channels.find(boneId);
if (channel1 != activeAnim1.channels.end() && channel2 != activeAnim2.channels.end()) {
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] =
parentTransform *
animationTransformBlending(channel1->second, anim1.currentTime, channel2->second, anim2.currentTime, weight);
} else if (channel1 != activeAnim1.channels.end()) {
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] = parentTransform * animationTransform(channel1->second, anim1.currentTime);
} else if (channel2 != activeAnim2.channels.end()) {
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] = parentTransform * animationTransform(channel2->second, anim2.currentTime);
} else {
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] = parentTransform * glTF.nodesStorage[gltfNode].transform;
}
glTF.skinning = true;
}
for (uint32_t i = 0; i < glTF.nodesStorage[gltfNode].children.size(); i++) {
const uint32_t child = glTF.nodesStorage[gltfNode].children[i];
traverseTree(child, glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId]);
}
};
traverseTree(glTF.root, mat4(1.0f));
for (const std::pair<std::string, GLTFBone>& b : glTF.bonesByName) {
if (b.second.boneId != ~0u) {
glTF.matrices[b.second.boneId] = glTF.matrices[b.second.boneId] * b.second.transform;
}
}
} else {
glTF.morphing = false;
glTF.skinning = false;
}
}