498 lines
20 KiB
C++
498 lines
20 KiB
C++
#include <memory>
|
|
#include <stack>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include <filament/Engine.h>
|
|
#include <filament/TransformManager.h>
|
|
#include <filament/RenderableManager.h>
|
|
|
|
#include <gltfio/Animator.h>
|
|
|
|
#include "Log.hpp"
|
|
|
|
#include "scene/AnimationManager.hpp"
|
|
#include "scene/SceneAsset.hpp"
|
|
#include "scene/GltfSceneAssetInstance.hpp"
|
|
|
|
namespace thermion
|
|
{
|
|
|
|
using namespace filament;
|
|
using namespace utils;
|
|
|
|
AnimationManager::AnimationManager(Engine *engine, Scene *scene) : _engine(engine), _scene(scene)
|
|
{
|
|
auto &transformManager = _engine->getTransformManager();
|
|
auto &renderableManager = _engine->getRenderableManager();
|
|
_gltfAnimationComponentManager = std::make_unique<GltfAnimationComponentManager>(transformManager, renderableManager);
|
|
_morphAnimationComponentManager = std::make_unique<MorphAnimationComponentManager>(transformManager, renderableManager);
|
|
_boneAnimationComponentManager = std::make_unique<BoneAnimationComponentManager>(transformManager, renderableManager);
|
|
}
|
|
|
|
bool AnimationManager::setMorphAnimationBuffer(
|
|
utils::Entity entity,
|
|
const float *const morphData,
|
|
const uint32_t *const morphIndices,
|
|
int numMorphTargets,
|
|
int numFrames,
|
|
float frameLengthInMs)
|
|
{
|
|
|
|
std::lock_guard lock(_mutex);
|
|
|
|
if (!_morphAnimationComponentManager->hasComponent(entity))
|
|
{
|
|
_morphAnimationComponentManager->addAnimationComponent(entity);
|
|
}
|
|
|
|
auto animationComponentInstance = _morphAnimationComponentManager->getInstance(entity);
|
|
auto &animationComponent = _morphAnimationComponentManager->elementAt<0>(animationComponentInstance);
|
|
auto &morphAnimations = animationComponent.animations;
|
|
|
|
MorphAnimation morphAnimation;
|
|
|
|
morphAnimation.frameData.clear();
|
|
morphAnimation.frameData.insert(
|
|
morphAnimation.frameData.begin(),
|
|
morphData,
|
|
morphData + (numFrames * numMorphTargets));
|
|
morphAnimation.frameLengthInMs = frameLengthInMs;
|
|
morphAnimation.morphIndices.resize(numMorphTargets);
|
|
for (int i = 0; i < numMorphTargets; i++)
|
|
{
|
|
morphAnimation.morphIndices[i] = morphIndices[i];
|
|
}
|
|
morphAnimation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
|
|
|
|
morphAnimation.start = high_resolution_clock::now();
|
|
morphAnimation.lengthInFrames = numFrames;
|
|
|
|
morphAnimations.emplace_back(morphAnimation);
|
|
|
|
auto& foo = morphAnimations[morphAnimations.size() - 1];
|
|
|
|
return true;
|
|
}
|
|
|
|
void AnimationManager::clearMorphAnimationBuffer(
|
|
utils::Entity entity)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
|
|
auto animationComponentInstance = _morphAnimationComponentManager->getInstance(entity);
|
|
auto &animationComponent = _morphAnimationComponentManager->elementAt<0>(animationComponentInstance);
|
|
auto &morphAnimations = animationComponent.animations;
|
|
morphAnimations.clear();
|
|
}
|
|
|
|
void AnimationManager::resetToRestPose(GltfSceneAssetInstance *instance)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
|
|
auto filamentInstance = instance->getInstance();
|
|
auto skinCount = filamentInstance->getSkinCount();
|
|
|
|
TransformManager &transformManager = _engine->getTransformManager();
|
|
|
|
//
|
|
// To reset the skeleton to its rest pose, we could just call animator->resetBoneMatrices(),
|
|
// which sets all bone matrices to the identity matrix. However, any subsequent calls to animator->updateBoneMatrices()
|
|
// may result in unexpected poses, because that method uses each bone's transform to calculate
|
|
// the bone matrices (and resetBoneMatrices does not affect this transform).
|
|
// To "fully" reset the bone, we need to set its local transform (i.e. relative to its parent)
|
|
// to its original orientation in rest pose.
|
|
//
|
|
// This can be calculated as:
|
|
//
|
|
// auto rest = inverse(parentTransformInModelSpace) * bindMatrix
|
|
//
|
|
// (where bindMatrix is the inverse of the inverseBindMatrix).
|
|
//
|
|
// The only requirement is that parent bone transforms are reset before child bone transforms.
|
|
// glTF/Filament does not guarantee that parent bones are listed before child bones under a FilamentInstance.
|
|
// We ensure that parents are reset before children by:
|
|
// - pushing all bones onto a stack
|
|
// - iterate over the stack
|
|
// - look at the bone at the top of the stack
|
|
// - if the bone already been reset, pop and continue iterating over the stack
|
|
// - otherwise
|
|
// - if the bone has a parent that has not been reset, push the parent to the top of the stack and continue iterating
|
|
// - otherwise
|
|
// - pop the bone, reset its transform and mark it as completed
|
|
for (int skinIndex = 0; skinIndex < skinCount; skinIndex++)
|
|
{
|
|
std::unordered_set<Entity, Entity::Hasher> joints;
|
|
std::unordered_set<Entity, Entity::Hasher> completed;
|
|
std::stack<Entity> stack;
|
|
|
|
auto transforms = getBoneRestTranforms(instance, skinIndex);
|
|
|
|
for (int i = 0; i < filamentInstance->getJointCountAt(skinIndex); i++)
|
|
{
|
|
auto restTransform = transforms[i];
|
|
const auto &joint = filamentInstance->getJointsAt(skinIndex)[i];
|
|
auto transformInstance = transformManager.getInstance(joint);
|
|
transformManager.setTransform(transformInstance, restTransform);
|
|
}
|
|
}
|
|
filamentInstance->getAnimator()->updateBoneMatrices();
|
|
return;
|
|
}
|
|
|
|
std::vector<math::mat4f> AnimationManager::getBoneRestTranforms(GltfSceneAssetInstance *instance, int skinIndex)
|
|
{
|
|
|
|
std::vector<math::mat4f> transforms;
|
|
|
|
auto filamentInstance = instance->getInstance();
|
|
auto skinCount = filamentInstance->getSkinCount();
|
|
|
|
TransformManager &transformManager = _engine->getTransformManager();
|
|
|
|
transforms.resize(filamentInstance->getJointCountAt(skinIndex));
|
|
|
|
//
|
|
// To reset the skeleton to its rest pose, we could just call animator->resetBoneMatrices(),
|
|
// which sets all bone matrices to the identity matrix. However, any subsequent calls to animator->updateBoneMatrices()
|
|
// may result in unexpected poses, because that method uses each bone's transform to calculate
|
|
// the bone matrices (and resetBoneMatrices does not affect this transform).
|
|
// To "fully" reset the bone, we need to set its local transform (i.e. relative to its parent)
|
|
// to its original orientation in rest pose.
|
|
//
|
|
// This can be calculated as:
|
|
//
|
|
// auto rest = inverse(parentTransformInModelSpace) * bindMatrix
|
|
//
|
|
// (where bindMatrix is the inverse of the inverseBindMatrix).
|
|
//
|
|
// The only requirement is that parent bone transforms are reset before child bone transforms.
|
|
// glTF/Filament does not guarantee that parent bones are listed before child bones under a FilamentInstance.
|
|
// We ensure that parents are reset before children by:
|
|
// - pushing all bones onto a stack
|
|
// - iterate over the stack
|
|
// - look at the bone at the top of the stack
|
|
// - if the bone already been reset, pop and continue iterating over the stack
|
|
// - otherwise
|
|
// - if the bone has a parent that has not been reset, push the parent to the top of the stack and continue iterating
|
|
// - otherwise
|
|
// - pop the bone, reset its transform and mark it as completed
|
|
std::vector<Entity> joints;
|
|
std::unordered_set<Entity, Entity::Hasher> completed;
|
|
std::stack<Entity> stack;
|
|
|
|
for (int i = 0; i < filamentInstance->getJointCountAt(skinIndex); i++)
|
|
{
|
|
const auto &joint = filamentInstance->getJointsAt(skinIndex)[i];
|
|
joints.push_back(joint);
|
|
stack.push(joint);
|
|
}
|
|
|
|
while (!stack.empty())
|
|
{
|
|
const auto &joint = stack.top();
|
|
|
|
// if we've already handled this node previously (e.g. when we encountered it as a parent), then skip
|
|
if (completed.find(joint) != completed.end())
|
|
{
|
|
stack.pop();
|
|
continue;
|
|
}
|
|
|
|
const auto transformInstance = transformManager.getInstance(joint);
|
|
auto parent = transformManager.getParent(transformInstance);
|
|
|
|
// we need to handle parent joints before handling their children
|
|
// therefore, if this joint has a parent that hasn't been handled yet,
|
|
// push the parent to the top of the stack and start the loop again
|
|
const auto &jointIter = std::find(joints.begin(), joints.end(), joint);
|
|
auto parentIter = std::find(joints.begin(), joints.end(), parent);
|
|
|
|
if (parentIter != joints.end() && completed.find(parent) == completed.end())
|
|
{
|
|
stack.push(parent);
|
|
continue;
|
|
}
|
|
|
|
// otherwise let's get the inverse bind matrix for the joint
|
|
math::mat4f inverseBindMatrix;
|
|
bool found = false;
|
|
for (int i = 0; i < filamentInstance->getJointCountAt(skinIndex); i++)
|
|
{
|
|
if (filamentInstance->getJointsAt(skinIndex)[i] == joint)
|
|
{
|
|
inverseBindMatrix = filamentInstance->getInverseBindMatricesAt(skinIndex)[i];
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
ASSERT_PRECONDITION(found, "Failed to find inverse bind matrix for joint %d", joint);
|
|
|
|
// now we need to ascend back up the hierarchy to calculate the modelSpaceTransform
|
|
math::mat4f modelSpaceTransform;
|
|
while (parentIter != joints.end())
|
|
{
|
|
const auto transformInstance = transformManager.getInstance(parent);
|
|
const auto parentIndex = distance(joints.begin(), parentIter);
|
|
const auto transform = transforms[parentIndex];
|
|
modelSpaceTransform = transform * modelSpaceTransform;
|
|
parent = transformManager.getParent(transformInstance);
|
|
parentIter = std::find(joints.begin(), joints.end(), parent);
|
|
}
|
|
|
|
const auto bindMatrix = inverse(inverseBindMatrix);
|
|
|
|
const auto inverseModelSpaceTransform = inverse(modelSpaceTransform);
|
|
|
|
const auto jointIndex = distance(joints.begin(), jointIter);
|
|
transforms[jointIndex] = inverseModelSpaceTransform * bindMatrix;
|
|
completed.insert(joint);
|
|
stack.pop();
|
|
}
|
|
return transforms;
|
|
}
|
|
|
|
void AnimationManager::updateBoneMatrices(GltfSceneAssetInstance *instance)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
instance->getInstance()->getAnimator()->updateBoneMatrices();
|
|
}
|
|
|
|
bool AnimationManager::addBoneAnimation(GltfSceneAssetInstance *instance,
|
|
int skinIndex,
|
|
int boneIndex,
|
|
const float *const frameData,
|
|
int numFrames,
|
|
float frameLengthInMs,
|
|
float fadeOutInSecs,
|
|
float fadeInInSecs,
|
|
float maxDelta)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
|
|
BoneAnimation animation;
|
|
animation.boneIndex = boneIndex;
|
|
animation.frameData.clear();
|
|
|
|
const auto &inverseBindMatrix = instance->getInstance()->getInverseBindMatricesAt(skinIndex)[boneIndex];
|
|
for (int i = 0; i < numFrames; i++)
|
|
{
|
|
math::mat4f frame(
|
|
frameData[i * 16],
|
|
frameData[(i * 16) + 1],
|
|
frameData[(i * 16) + 2],
|
|
frameData[(i * 16) + 3],
|
|
frameData[(i * 16) + 4],
|
|
frameData[(i * 16) + 5],
|
|
frameData[(i * 16) + 6],
|
|
frameData[(i * 16) + 7],
|
|
frameData[(i * 16) + 8],
|
|
frameData[(i * 16) + 9],
|
|
frameData[(i * 16) + 10],
|
|
frameData[(i * 16) + 11],
|
|
frameData[(i * 16) + 12],
|
|
frameData[(i * 16) + 13],
|
|
frameData[(i * 16) + 14],
|
|
frameData[(i * 16) + 15]);
|
|
|
|
animation.frameData.push_back(frame);
|
|
}
|
|
|
|
animation.frameLengthInMs = frameLengthInMs;
|
|
animation.start = std::chrono::high_resolution_clock::now();
|
|
animation.reverse = false;
|
|
animation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
|
|
animation.lengthInFrames = numFrames;
|
|
animation.frameLengthInMs = frameLengthInMs;
|
|
animation.fadeOutInSecs = fadeOutInSecs;
|
|
animation.fadeInInSecs = fadeInInSecs;
|
|
animation.maxDelta = maxDelta;
|
|
animation.skinIndex = skinIndex;
|
|
if (!_boneAnimationComponentManager->hasComponent(instance->getInstance()->getRoot()))
|
|
{
|
|
Log("ERROR: specified entity is not animatable (has no animation component attached).");
|
|
return false;
|
|
}
|
|
auto animationComponentInstance = _boneAnimationComponentManager->getInstance(instance->getInstance()->getRoot());
|
|
|
|
auto &animationComponent = _boneAnimationComponentManager->elementAt<0>(animationComponentInstance);
|
|
// auto &boneAnimations = animationComponent.boneAnimations;
|
|
|
|
// boneAnimations.emplace_back(animation);
|
|
|
|
return true;
|
|
}
|
|
|
|
void AnimationManager::playGltfAnimation(GltfSceneAssetInstance *instance, int index, bool loop, bool reverse, bool replaceActive, float crossfade, float startOffset)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
|
|
if (index < 0)
|
|
{
|
|
Log("ERROR: glTF animation index must be greater than zero.");
|
|
return;
|
|
}
|
|
|
|
_gltfAnimationComponentManager->addGltfAnimation(instance->getInstance(), index, loop, reverse, replaceActive, crossfade, startOffset);
|
|
}
|
|
|
|
void AnimationManager::stopGltfAnimation(GltfSceneAssetInstance *instance, int index)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
auto animationComponentInstance = _gltfAnimationComponentManager->getInstance(instance->getEntity());
|
|
auto &animationComponent = _gltfAnimationComponentManager->elementAt<0>(animationComponentInstance);
|
|
|
|
auto erased = std::remove_if(animationComponent.animations.begin(),
|
|
animationComponent.animations.end(),
|
|
[=](GltfAnimation &anim)
|
|
{ return anim.index == index; });
|
|
animationComponent.animations.erase(erased,
|
|
animationComponent.animations.end());
|
|
return;
|
|
}
|
|
|
|
void AnimationManager::setMorphTargetWeights(utils::Entity entity, const float *const weights, const int count)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
RenderableManager &rm = _engine->getRenderableManager();
|
|
auto renderableInstance = rm.getInstance(entity);
|
|
|
|
rm.setMorphWeights(
|
|
renderableInstance,
|
|
weights,
|
|
count);
|
|
}
|
|
|
|
void AnimationManager::setGltfAnimationFrame(GltfSceneAssetInstance *instance, int animationIndex, int animationFrame)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
auto offset = 60 * animationFrame * 1000; // TODO - don't hardcore 60fps framerate
|
|
instance->getInstance()->getAnimator()->applyAnimation(animationIndex, offset);
|
|
instance->getInstance()->getAnimator()->updateBoneMatrices();
|
|
return;
|
|
}
|
|
|
|
float AnimationManager::getGltfAnimationDuration(GltfSceneAssetInstance *instance, int animationIndex)
|
|
{
|
|
return instance->getInstance()->getAnimator()->getAnimationDuration(animationIndex);
|
|
}
|
|
|
|
std::vector<std::string> AnimationManager::getGltfAnimationNames(GltfSceneAssetInstance *instance)
|
|
{
|
|
std::vector<std::string> names;
|
|
|
|
size_t count = instance->getInstance()->getAnimator()->getAnimationCount();
|
|
|
|
for (size_t i = 0; i < count; i++)
|
|
{
|
|
names.push_back(instance->getInstance()->getAnimator()->getAnimationName(i));
|
|
}
|
|
return names;
|
|
}
|
|
|
|
std::vector<std::string> AnimationManager::getMorphTargetNames(GltfSceneAsset *asset, EntityId childEntity)
|
|
{
|
|
std::vector<std::string> names;
|
|
|
|
auto filamentAsset = asset->getAsset();
|
|
|
|
const utils::Entity targetEntity = utils::Entity::import(childEntity);
|
|
|
|
size_t count = filamentAsset->getMorphTargetCountAt(targetEntity);
|
|
for (int j = 0; j < count; j++)
|
|
{
|
|
const char *morphName = filamentAsset->getMorphTargetNameAt(targetEntity, j);
|
|
names.push_back(morphName);
|
|
}
|
|
return names;
|
|
}
|
|
|
|
vector<Entity> AnimationManager::getBoneEntities(GltfSceneAssetInstance *instance, int skinIndex)
|
|
{
|
|
auto *joints = instance->getInstance()->getJointsAt(skinIndex);
|
|
auto jointCount = instance->getInstance()->getJointCountAt(skinIndex);
|
|
std::vector<Entity> boneEntities(joints, joints + jointCount);
|
|
return boneEntities;
|
|
}
|
|
|
|
void AnimationManager::update(uint64_t frameTimeInNanos)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
_gltfAnimationComponentManager->update();
|
|
_morphAnimationComponentManager->update();
|
|
_boneAnimationComponentManager->update();
|
|
}
|
|
|
|
math::mat4f AnimationManager::getInverseBindMatrix(GltfSceneAssetInstance *instance, int skinIndex, int boneIndex)
|
|
{
|
|
return instance->getInstance()->getInverseBindMatricesAt(skinIndex)[boneIndex];
|
|
}
|
|
|
|
bool AnimationManager::setBoneTransform(GltfSceneAssetInstance *instance, int32_t skinIndex, int boneIndex, math::mat4f transform)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
|
|
RenderableManager &rm = _engine->getRenderableManager();
|
|
|
|
const auto &renderableInstance = rm.getInstance(instance->getEntity());
|
|
|
|
if (!renderableInstance.isValid())
|
|
{
|
|
Log("Specified entity is not a renderable. You probably provided the ultimate parent entity of a glTF asset, which is non-renderable. ");
|
|
return false;
|
|
}
|
|
|
|
rm.setBones(
|
|
renderableInstance,
|
|
&transform,
|
|
1,
|
|
boneIndex);
|
|
return true;
|
|
}
|
|
|
|
bool AnimationManager::addGltfAnimationComponent(GltfSceneAssetInstance *instance)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
_gltfAnimationComponentManager->addAnimationComponent(instance->getInstance());
|
|
TRACE("Added glTF animation component");
|
|
return true;
|
|
}
|
|
|
|
void AnimationManager::removeGltfAnimationComponent(GltfSceneAssetInstance *instance)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
_gltfAnimationComponentManager->removeAnimationComponent(instance->getInstance());
|
|
TRACE("Removed glTF animation component");
|
|
}
|
|
|
|
bool AnimationManager::addBoneAnimationComponent(GltfSceneAssetInstance *instance)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
_boneAnimationComponentManager->addAnimationComponent(instance->getInstance());
|
|
TRACE("Added bone animation component");
|
|
return true;
|
|
}
|
|
|
|
void AnimationManager::removeBoneAnimationComponent(GltfSceneAssetInstance *instance)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
_boneAnimationComponentManager->removeAnimationComponent(instance->getInstance());
|
|
TRACE("Removed bone animation component");
|
|
}
|
|
|
|
bool AnimationManager::addMorphAnimationComponent(utils::Entity entity)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
_morphAnimationComponentManager->addAnimationComponent(entity);
|
|
TRACE("Added morph animation component");
|
|
return true;
|
|
}
|
|
|
|
void AnimationManager::removeMorphAnimationComponent(utils::Entity entity)
|
|
{
|
|
std::lock_guard lock(_mutex);
|
|
_morphAnimationComponentManager->removeAnimationComponent(entity);
|
|
TRACE("Removed morph animation component");
|
|
}
|
|
|
|
} |