320 lines
12 KiB
C++
320 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;
|
||
|
|
}
|
||
|
|
}
|