add method to retrieve rest local transforms
This commit is contained in:
@@ -117,6 +117,8 @@ namespace flutter_filament
|
|||||||
const float *const frameData,
|
const float *const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs);
|
float frameLengthInMs);
|
||||||
|
|
||||||
|
std::unique_ptr<std::vector<math::mat4f>> getBoneRestTranforms(EntityId entityId, int skinIndex);
|
||||||
void resetBones(EntityId entityId);
|
void resetBones(EntityId entityId);
|
||||||
bool setTransform(EntityId entityId, math::mat4f transform);
|
bool setTransform(EntityId entityId, math::mat4f transform);
|
||||||
bool updateBoneMatrices(EntityId entityId);
|
bool updateBoneMatrices(EntityId entityId);
|
||||||
|
|||||||
@@ -823,19 +823,24 @@ namespace flutter_filament
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto skinCount = instance->getSkinCount();
|
auto skinCount = instance->getSkinCount();
|
||||||
|
|
||||||
TransformManager &transformManager = _engine->getTransformManager();
|
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.
|
// To reset the skeleton to its rest pose, we could just call animator->resetBoneMatrices(),
|
||||||
// However, any subsequent calls to animator->updateBoneMatrices() may result in
|
// which sets all bone matrices to the identity matrix. However, any subsequent calls to animator->updateBoneMatrices()
|
||||||
// unexpected poses, because updateBoneMatrices uses each bone's transform to calculate
|
// 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).
|
// the bone matrices (and resetBoneMatrices does not affect this transform).
|
||||||
// To "fully" reset the bone, we need to reset the transform node to its original orientation.
|
// To "fully" reset the bone, we need to set its local transform (i.e. relative to its parent)
|
||||||
// This transform is relative to its parent, so can be calculated as:
|
// to its original orientation in rest pose.
|
||||||
// auto rest = inverse(parentTransformInModelSpace) * inverse(inverseBindMatrix).
|
//
|
||||||
|
// 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.
|
// 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.
|
// 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:
|
// We ensure that parents are reset before children by:
|
||||||
@@ -852,70 +857,140 @@ namespace flutter_filament
|
|||||||
std::unordered_set<Entity,Entity::Hasher> joints;
|
std::unordered_set<Entity,Entity::Hasher> joints;
|
||||||
std::unordered_set<Entity,Entity::Hasher> completed;
|
std::unordered_set<Entity,Entity::Hasher> completed;
|
||||||
std::stack<Entity> stack;
|
std::stack<Entity> stack;
|
||||||
|
|
||||||
|
auto transforms = getBoneRestTranforms(entityId, skinIndex);
|
||||||
|
|
||||||
for (int i = 0; i < instance->getJointCountAt(skinIndex); i++)
|
for (int i = 0; i < instance->getJointCountAt(skinIndex); i++)
|
||||||
{
|
{
|
||||||
|
auto restTransform = transforms->at(i);
|
||||||
const auto& joint = instance->getJointsAt(skinIndex)[i];
|
const auto& joint = instance->getJointsAt(skinIndex)[i];
|
||||||
joints.insert(joint);
|
auto transformInstance = transformManager.getInstance(joint);
|
||||||
stack.push(joint);
|
transformManager.setTransform(transformInstance, restTransform);
|
||||||
}
|
|
||||||
|
|
||||||
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 it onto the top of the stack and start the loop again
|
|
||||||
if(joints.find(parent) != joints.end() && completed.find(parent) == completed.end()) {
|
|
||||||
stack.push(parent);
|
|
||||||
Log("Pushing parent %d", parent);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise let's get the inverse bind matrix for the joint
|
|
||||||
math::mat4f inverseBindMatrix;
|
|
||||||
bool found = false;
|
|
||||||
for (int i = 0; i < instance->getJointCountAt(skinIndex); i++)
|
|
||||||
{
|
|
||||||
if(instance->getJointsAt(skinIndex)[i] == joint) {
|
|
||||||
Log("Resetting bone %d", i);
|
|
||||||
inverseBindMatrix = instance->getInverseBindMatricesAt(skinIndex)[i];
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ASSERT_PRECONDITION(found, "Failed to find joint");
|
|
||||||
|
|
||||||
// now we need to ascend back up the hierarchy to calculate the modelSpaceTransform
|
|
||||||
math::mat4f modelSpaceTransform;
|
|
||||||
while(joints.find(parent) != joints.end()) {
|
|
||||||
Log("Accumulating model space transform for parent %d", parent);
|
|
||||||
const auto transformInstance = transformManager.getInstance(parent);
|
|
||||||
const auto transform = transformManager.getTransform(transformInstance);
|
|
||||||
modelSpaceTransform = transform * modelSpaceTransform;
|
|
||||||
parent = transformManager.getParent(transformInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto bindMatrix = inverse(inverseBindMatrix);
|
|
||||||
|
|
||||||
const auto inverseModelSpaceTransform = inverse(modelSpaceTransform);
|
|
||||||
transformManager.setTransform(transformInstance, inverseModelSpaceTransform * bindMatrix);
|
|
||||||
completed.insert(joint);
|
|
||||||
stack.pop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
instance->getAnimator()->updateBoneMatrices();
|
instance->getAnimator()->updateBoneMatrices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<std::vector<math::mat4f>> SceneManager::getBoneRestTranforms(EntityId entityId, int skinIndex) {
|
||||||
|
|
||||||
|
auto transforms = std::make_unique<std::vector<math::mat4f>>();
|
||||||
|
|
||||||
|
auto *instance = getInstanceByEntityId(entityId);
|
||||||
|
if (!instance)
|
||||||
|
{
|
||||||
|
auto *asset = getAssetByEntityId(entityId);
|
||||||
|
if (asset)
|
||||||
|
{
|
||||||
|
instance = asset->getInstance();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return transforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto skinCount = instance->getSkinCount();
|
||||||
|
|
||||||
|
TransformManager &transformManager = _engine->getTransformManager();
|
||||||
|
|
||||||
|
transforms->resize(instance->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 < instance->getJointCountAt(skinIndex); i++)
|
||||||
|
{
|
||||||
|
const auto& joint = instance->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 < instance->getJointCountAt(skinIndex); i++)
|
||||||
|
{
|
||||||
|
if(instance->getJointsAt(skinIndex)[i] == joint) {
|
||||||
|
inverseBindMatrix = instance->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->at(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->at(jointIndex) = inverseModelSpaceTransform * bindMatrix;
|
||||||
|
completed.insert(joint);
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
return transforms;
|
||||||
|
}
|
||||||
|
|
||||||
bool SceneManager::updateBoneMatrices(EntityId entityId) {
|
bool SceneManager::updateBoneMatrices(EntityId entityId) {
|
||||||
auto *instance = getInstanceByEntityId(entityId);
|
auto *instance = getInstanceByEntityId(entityId);
|
||||||
if (!instance)
|
if (!instance)
|
||||||
@@ -1096,19 +1171,29 @@ namespace flutter_filament
|
|||||||
{
|
{
|
||||||
std::lock_guard lock(_mutex);
|
std::lock_guard lock(_mutex);
|
||||||
|
|
||||||
const auto *instance = getInstanceByEntityId(entityId);
|
auto *instance = getInstanceByEntityId(entityId);
|
||||||
if (!instance)
|
if (!instance)
|
||||||
{
|
{
|
||||||
return;
|
auto *asset = getAssetByEntityId(entityId);
|
||||||
|
if (asset)
|
||||||
|
{
|
||||||
|
instance = asset->getInstance();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log("Failed to find instance for entity");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto animationComponentInstance = _animationComponentManager->getInstance(instance->getRoot());
|
auto animationComponentInstance = _animationComponentManager->getInstance(instance->getRoot());
|
||||||
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance);
|
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance);
|
||||||
|
|
||||||
animationComponent.gltfAnimations.erase(std::remove_if(animationComponent.gltfAnimations.begin(),
|
auto erased = std::remove_if(animationComponent.gltfAnimations.begin(),
|
||||||
animationComponent.gltfAnimations.end(),
|
animationComponent.gltfAnimations.end(),
|
||||||
[=](GltfAnimation &anim)
|
[=](GltfAnimation &anim)
|
||||||
{ return anim.index == index; }),
|
{ return anim.index == index; });
|
||||||
|
animationComponent.gltfAnimations.erase(erased,
|
||||||
animationComponent.gltfAnimations.end());
|
animationComponent.gltfAnimations.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user