Runtime animation retargeting (#627)

* runtime animation retargeting

* improvements, fixes

* character controller script: separate animation file from character

* editor fix

* script improvements

* foot positioning fixes
This commit is contained in:
Turánszki János
2023-01-22 12:29:13 -05:00
committed by GitHub
parent ab53daad94
commit 30c47c83cc
15 changed files with 509 additions and 315 deletions
+8 -181
View File
@@ -5,65 +5,6 @@
using namespace wi::ecs;
using namespace wi::scene;
XMMATRIX ComputeWorldMatrixRecursive(Scene& scene, Entity entity, XMMATRIX localMatrix)
{
HierarchyComponent* hier = scene.hierarchy.GetComponent(entity);
if (hier != nullptr)
{
Entity parentID = hier->parentID;
while (parentID != INVALID_ENTITY)
{
TransformComponent* transform_parent = scene.transforms.GetComponent(parentID);
if (transform_parent == nullptr)
break;
localMatrix *= transform_parent->GetLocalMatrix();
const HierarchyComponent* hier_recursive = scene.hierarchy.GetComponent(parentID);
if (hier_recursive != nullptr)
{
parentID = hier_recursive->parentID;
}
else
{
parentID = INVALID_ENTITY;
}
}
}
return localMatrix;
}
XMMATRIX ComputeInverseParentMatrixRecursive(Scene& scene, Entity entity)
{
XMMATRIX inverseParentMatrix = XMMatrixIdentity();
HierarchyComponent* hier = scene.hierarchy.GetComponent(entity);
if (hier != nullptr)
{
Entity parentID = hier->parentID;
while (parentID != INVALID_ENTITY)
{
TransformComponent* transform_parent = scene.transforms.GetComponent(parentID);
if (transform_parent == nullptr)
break;
inverseParentMatrix *= transform_parent->GetLocalMatrix();
const HierarchyComponent* hier_recursive = scene.hierarchy.GetComponent(parentID);
if (hier_recursive != nullptr)
{
parentID = hier_recursive->parentID;
}
else
{
parentID = INVALID_ENTITY;
}
}
inverseParentMatrix = XMMatrixInverse(nullptr, inverseParentMatrix);
}
return inverseParentMatrix;
}
void AnimationWindow::Create(EditorComponent* _editor)
{
editor = _editor;
@@ -970,132 +911,18 @@ void AnimationWindow::Create(EditorComponent* _editor)
retargetCombo.OnSelect([=](wi::gui::EventArgs args) {
retargetCombo.SetSelectedWithoutCallback(-1);
wi::scene::Scene& scene = editor->GetCurrentScene();
const AnimationComponent* animation_source = scene.animations.GetComponent(entity);
if (animation_source == nullptr)
return;
const HumanoidComponent* humanoid_dest = scene.humanoids.GetComponent((Entity)args.userdata);
if (humanoid_dest == nullptr)
return;
bool retarget_valid = false;
Scene retarget_scene;
Entity retarget_entity = CreateEntity();
AnimationComponent& animation = retarget_scene.animations.Create(retarget_entity);
animation = *animation_source;
animation.channels.clear();
animation.samplers.clear();
NameComponent name;
const NameComponent* name_source = scene.names.GetComponent(entity);
if (name_source != nullptr)
Entity retarget_entity = scene.RetargetAnimation((Entity)args.userdata, entity, true);
if (retarget_entity != INVALID_ENTITY)
{
name.name += name_source->name;
}
scene.names.Create(retarget_entity) = name;
TransformComponent transform;
const TransformComponent* transform_source = scene.transforms.GetComponent(entity);
if (transform_source != nullptr)
{
transform = *transform_source;
}
scene.transforms.Create(retarget_entity) = transform;
scene.Component_Attach(retarget_entity, (Entity)args.userdata);
for (auto& channel : animation_source->channels)
{
bool found = false;
for (size_t i = 0; (i < scene.humanoids.GetCount()) && !found; ++i)
NameComponent name;
const NameComponent* name_source = scene.names.GetComponent(entity);
if (name_source != nullptr)
{
const HumanoidComponent& humanoid_source = scene.humanoids[i];
for (size_t humanoidBoneIndex = 0; humanoidBoneIndex < arraysize(humanoid_source.bones); ++humanoidBoneIndex)
{
Entity bone_source = humanoid_source.bones[humanoidBoneIndex];
if (bone_source == channel.target)
{
retarget_valid = true;
found = true;
Entity bone_dest = humanoid_dest->bones[humanoidBoneIndex];
auto& retarget_channel = animation.channels.emplace_back();
retarget_channel = channel;
retarget_channel.target = bone_dest;
retarget_channel.samplerIndex = (int)animation.samplers.size();
auto& sampler = animation_source->samplers[channel.samplerIndex];
auto& retarget_sampler = animation.samplers.emplace_back();
retarget_sampler = sampler;
retarget_sampler.backwards_compatibility_data = {};
Entity retarget_animation_data_entity = CreateEntity();
auto& retarget_animation_data = retarget_scene.animation_datas.Create(retarget_animation_data_entity);
retarget_sampler.data = retarget_animation_data_entity;
auto& animation_data = scene.animation_datas.Contains(sampler.data) ? *scene.animation_datas.GetComponent(sampler.data) : sampler.backwards_compatibility_data;
retarget_animation_data = animation_data;
TransformComponent* transform_source = scene.transforms.GetComponent(bone_source);
TransformComponent* transform_dest = scene.transforms.GetComponent(bone_dest);
if (transform_source != nullptr && transform_dest != nullptr)
{
XMMATRIX bindMatrix = ComputeWorldMatrixRecursive(scene, bone_source, transform_source->GetLocalMatrix());
XMMATRIX inverseBindMatrix = XMMatrixInverse(nullptr, bindMatrix);
XMMATRIX targetMatrix = ComputeWorldMatrixRecursive(scene, bone_dest, transform_dest->GetLocalMatrix());
XMMATRIX inverseParentMatrix = ComputeInverseParentMatrixRecursive(scene, bone_dest);
XMVECTOR S, R, T; // matrix decompose destinations
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::SCALE:
for (size_t offset = 0; offset < retarget_animation_data.keyframe_data.size(); offset += 3)
{
XMFLOAT3* data = (XMFLOAT3*)&retarget_animation_data.keyframe_data[offset];
TransformComponent transform = *transform_source;
transform.scale_local = *data;
XMMATRIX localMatrix = inverseBindMatrix * ComputeWorldMatrixRecursive(scene, bone_source, transform.GetLocalMatrix());
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
XMMatrixDecompose(&S, &R, &T, localMatrix);
XMStoreFloat3(data, S);
}
break;
case AnimationComponent::AnimationChannel::Path::ROTATION:
for (size_t offset = 0; offset < retarget_animation_data.keyframe_data.size(); offset += 4)
{
XMFLOAT4* data = (XMFLOAT4*)&retarget_animation_data.keyframe_data[offset];
TransformComponent transform = *transform_source;
transform.rotation_local = *data;
XMMATRIX localMatrix = inverseBindMatrix * ComputeWorldMatrixRecursive(scene, bone_source, transform.GetLocalMatrix());
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
XMMatrixDecompose(&S, &R, &T, localMatrix);
XMStoreFloat4(data, R);
}
break;
case AnimationComponent::AnimationChannel::Path::TRANSLATION:
for (size_t offset = 0; offset < retarget_animation_data.keyframe_data.size(); offset += 3)
{
XMFLOAT3* data = (XMFLOAT3*)&retarget_animation_data.keyframe_data[offset];
TransformComponent transform = *transform_source;
transform.translation_local = *data;
XMMATRIX localMatrix = inverseBindMatrix * ComputeWorldMatrixRecursive(scene, bone_source, transform.GetLocalMatrix());
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
XMMatrixDecompose(&S, &R, &T, localMatrix);
XMStoreFloat3(data, T);
}
break;
default:
break;
}
}
break;
}
}
name.name += name_source->name;
}
}
if (retarget_valid)
{
scene.Merge(retarget_scene);
scene.names.Create(retarget_entity) = name;
editor->optionsWnd.RefreshEntityTree();
}
});