Files
WickedEngine/Editor/AnimationWindow.cpp
T

663 lines
22 KiB
C++

#include "stdafx.h"
#include "AnimationWindow.h"
#include "Editor.h"
using namespace wi::ecs;
using namespace wi::scene;
void AnimationWindow::Create(EditorComponent* _editor)
{
editor = _editor;
wi::gui::Window::Create(ICON_ANIMATION " Animation", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE);
SetSize(XMFLOAT2(520, 400));
closeButton.SetTooltip("Delete Animation");
OnClose([=](wi::gui::EventArgs args) {
wi::Archive& archive = editor->AdvanceHistory();
archive << EditorComponent::HISTORYOP_COMPONENT_DATA;
editor->RecordEntity(archive, entity);
editor->GetCurrentScene().animations.Remove(entity);
editor->RecordEntity(archive, entity);
editor->optionsWnd.RefreshEntityTree();
});
float x = 80;
float y = 0;
float hei = 18;
float wid = 200;
float step = hei + 4;
modeComboBox.Create("Sampling: ");
modeComboBox.SetSize(XMFLOAT2(wid, hei));
modeComboBox.SetPos(XMFLOAT2(x, y));
modeComboBox.SetEnabled(false);
modeComboBox.AddItem("Step", AnimationComponent::AnimationSampler::Mode::STEP);
modeComboBox.AddItem("Linear", AnimationComponent::AnimationSampler::Mode::LINEAR);
modeComboBox.AddItem("Cubic spline", AnimationComponent::AnimationSampler::Mode::CUBICSPLINE);
modeComboBox.OnSelect([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
for (const AnimationComponent::AnimationChannel& channel : animation->channels)
{
assert(channel.samplerIndex < (int)animation->samplers.size());
AnimationComponent::AnimationSampler& sampler = animation->samplers[channel.samplerIndex];
sampler.mode = (AnimationComponent::AnimationSampler::Mode)args.userdata;
if (sampler.mode == AnimationComponent::AnimationSampler::Mode::CUBICSPLINE)
{
const AnimationDataComponent* animationdata = editor->GetCurrentScene().animation_datas.GetComponent(sampler.data);
if (animationdata == nullptr)
{
sampler.mode = AnimationComponent::AnimationSampler::Mode::LINEAR;
}
else if (animationdata->keyframe_data.size() != animationdata->keyframe_times.size() * 3 * 3)
{
sampler.mode = AnimationComponent::AnimationSampler::Mode::LINEAR;
}
}
}
}
});
modeComboBox.SetTooltip("Choose how animation data is interpreted between keyframes.\nNote that Cubic spline sampling requires spline animation data, otherwise, it will fall back to Linear!");
AddWidget(&modeComboBox);
loopedCheckBox.Create("Looped: ");
loopedCheckBox.SetTooltip("Toggle animation looping behaviour.");
loopedCheckBox.SetSize(XMFLOAT2(hei, hei));
loopedCheckBox.SetPos(XMFLOAT2(x, y += step));
loopedCheckBox.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->SetLooped(args.bValue);
}
});
loopedCheckBox.SetCheckText(ICON_LOOP);
AddWidget(&loopedCheckBox);
playButton.Create("Play");
playButton.SetTooltip("Play/Pause animation.");
playButton.SetSize(XMFLOAT2(100, hei));
playButton.SetPos(XMFLOAT2(loopedCheckBox.GetPos().x + loopedCheckBox.GetSize().x + 5, y));
playButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
if (animation->IsPlaying())
{
animation->Pause();
}
else
{
animation->Play();
}
}
});
AddWidget(&playButton);
stopButton.Create("Stop");
stopButton.SetTooltip("Stop animation.");
stopButton.SetSize(XMFLOAT2(100, hei));
stopButton.SetPos(XMFLOAT2(playButton.GetPos().x + playButton.GetSize().x + 5, y));
stopButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->Stop();
}
});
AddWidget(&stopButton);
timerSlider.Create(0, 1, 0, 100000, "Timer: ");
timerSlider.SetSize(XMFLOAT2(wid, hei));
timerSlider.SetPos(XMFLOAT2(x, y += step));
timerSlider.OnSlide([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->timer = args.fValue;
}
});
timerSlider.SetEnabled(false);
timerSlider.SetTooltip("Set the animation timer by hand.");
AddWidget(&timerSlider);
amountSlider.Create(0, 1, 1, 100000, "Amount: ");
amountSlider.SetSize(XMFLOAT2(wid, hei));
amountSlider.SetPos(XMFLOAT2(x, y += step));
amountSlider.OnSlide([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->amount = args.fValue;
}
});
amountSlider.SetEnabled(false);
amountSlider.SetTooltip("Set the animation blending amount by hand.");
AddWidget(&amountSlider);
speedSlider.Create(0, 4, 1, 100000, "Speed: ");
speedSlider.SetSize(XMFLOAT2(wid, hei));
speedSlider.SetPos(XMFLOAT2(x, y += step));
speedSlider.OnSlide([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->speed = args.fValue;
}
});
speedSlider.SetEnabled(false);
speedSlider.SetTooltip("Set the animation speed.");
AddWidget(&speedSlider);
startInput.Create("Start");
startInput.SetDescription("Start time: ");
startInput.SetSize(XMFLOAT2(wid, hei));
startInput.SetPos(XMFLOAT2(x, y += step));
startInput.OnInputAccepted([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->start = args.fValue;
}
});
startInput.SetTooltip("Set the animation start in seconds. This will be the loop's starting point.");
AddWidget(&startInput);
endInput.Create("End");
endInput.SetDescription("End time: ");
endInput.SetSize(XMFLOAT2(wid, hei));
endInput.SetPos(XMFLOAT2(x, y += step));
endInput.OnInputAccepted([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->end = args.fValue;
}
});
endInput.SetTooltip("Set the animation end in seconds. This is relative to 0, not the animation start.");
AddWidget(&endInput);
recordCombo.Create("Record: ");
recordCombo.selected_font.anim.typewriter.looped = true;
recordCombo.selected_font.anim.typewriter.time = 2;
recordCombo.selected_font.anim.typewriter.character_start = 1;
recordCombo.SetSize(XMFLOAT2(wid, hei));
recordCombo.SetPos(XMFLOAT2(x, y += step));
recordCombo.AddItem("...");
recordCombo.AddItem("Transform " ICON_TRANSLATE " " ICON_ROTATE " " ICON_SCALE);
recordCombo.AddItem("Position " ICON_TRANSLATE);
recordCombo.AddItem("Rotation " ICON_ROTATE);
recordCombo.AddItem("Scale " ICON_SCALE);
recordCombo.AddItem("Morph weights " ICON_MESH);
recordCombo.AddItem("Close loop " ICON_LOOP);
recordCombo.OnSelect([&](wi::gui::EventArgs args) {
if (args.iValue == 0)
return;
wi::scene::Scene& scene = editor->GetCurrentScene();
AnimationComponent* animation = scene.animations.GetComponent(entity);
if (animation != nullptr)
{
const float current_time = animation->timer;
if (args.iValue == 6)
{
// Close loop:
for (auto& channel : animation->channels)
{
auto& sam = animation->samplers[channel.samplerIndex];
AnimationDataComponent* animation_data = scene.animation_datas.GetComponent(sam.data);
if (animation_data != nullptr)
{
// Search for leftmost keyframe:
int keyFirst = 0;
float timeFirst = std::numeric_limits<float>::max();
for (int k = 0; k < (int)animation_data->keyframe_times.size(); ++k)
{
const float time = animation_data->keyframe_times[k];
if (time < timeFirst)
{
timeFirst = time;
keyFirst = k;
}
}
// Duplicate first frame to current position:
animation_data->keyframe_times.push_back(current_time);
switch (channel.path)
{
case wi::scene::AnimationComponent::AnimationChannel::TRANSLATION:
{
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 3 + 0]);
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 3 + 1]);
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 3 + 2]);
}
break;
case wi::scene::AnimationComponent::AnimationChannel::ROTATION:
{
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 4 + 0]);
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 4 + 1]);
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 4 + 2]);
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 4 + 3]);
}
break;
case wi::scene::AnimationComponent::AnimationChannel::SCALE:
{
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 3 + 0]);
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 3 + 1]);
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * 3 + 2]);
}
break;
case wi::scene::AnimationComponent::AnimationChannel::WEIGHTS:
{
const MeshComponent* mesh = scene.meshes.GetComponent(channel.target);
if (mesh == nullptr && scene.objects.Contains(channel.target))
{
// Also try query mesh of selected object:
ObjectComponent* object = scene.objects.GetComponent(channel.target);
mesh = scene.meshes.GetComponent(object->meshID);
}
if (mesh != nullptr && !mesh->targets.empty())
{
int idx = 0;
for (const MeshComponent::MeshMorphTarget& morph : mesh->targets)
{
animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst * mesh->targets.size() + idx]);
idx++;
}
}
}
break;
default:
break;
}
}
}
}
else
{
// Add keyframe type:
wi::vector<AnimationComponent::AnimationChannel::Path> paths;
switch (args.iValue)
{
default:
case 1:
paths.push_back(AnimationComponent::AnimationChannel::Path::TRANSLATION);
paths.push_back(AnimationComponent::AnimationChannel::Path::ROTATION);
paths.push_back(AnimationComponent::AnimationChannel::Path::SCALE);
break;
case 2:
paths.push_back(AnimationComponent::AnimationChannel::Path::TRANSLATION);
break;
case 3:
paths.push_back(AnimationComponent::AnimationChannel::Path::ROTATION);
break;
case 4:
paths.push_back(AnimationComponent::AnimationChannel::Path::SCALE);
break;
case 5:
paths.push_back(AnimationComponent::AnimationChannel::Path::WEIGHTS);
break;
}
for (auto path : paths)
{
for (auto& selected : editor->translator.selected)
{
int channelIndex = -1;
for (int i = 0; i < (int)animation->channels.size(); ++i)
{
// Search for channel for this path and target:
auto& channel = animation->channels[i];
if (channel.path == path && channel.target == selected.entity)
{
channelIndex = i;
break;
}
}
if (channelIndex < 0)
{
// No channel found for this path and target, create it:
channelIndex = (int)animation->channels.size();
auto& channel = animation->channels.emplace_back();
channel.samplerIndex = (int)animation->samplers.size();
channel.target = selected.entity;
channel.path = path;
auto& sam = animation->samplers.emplace_back();
Entity animation_data_entity = CreateEntity();
scene.animation_datas.Create(animation_data_entity);
sam.data = animation_data_entity;
}
auto& channel = animation->channels[channelIndex];
AnimationDataComponent* animation_data = scene.animation_datas.GetComponent(animation->samplers[channel.samplerIndex].data);
if (animation_data != nullptr)
{
animation_data->keyframe_times.push_back(current_time);
switch (channel.path)
{
case wi::scene::AnimationComponent::AnimationChannel::TRANSLATION:
{
const TransformComponent* transform = scene.transforms.GetComponent(channel.target);
if (transform != nullptr)
{
animation_data->keyframe_data.push_back(transform->translation_local.x);
animation_data->keyframe_data.push_back(transform->translation_local.y);
animation_data->keyframe_data.push_back(transform->translation_local.z);
}
else
{
animation_data->keyframe_times.pop_back();
animation->channels.pop_back();
}
}
break;
case wi::scene::AnimationComponent::AnimationChannel::ROTATION:
{
const TransformComponent* transform = scene.transforms.GetComponent(channel.target);
if (transform != nullptr)
{
animation_data->keyframe_data.push_back(transform->rotation_local.x);
animation_data->keyframe_data.push_back(transform->rotation_local.y);
animation_data->keyframe_data.push_back(transform->rotation_local.z);
animation_data->keyframe_data.push_back(transform->rotation_local.w);
}
else
{
animation_data->keyframe_times.pop_back();
animation->channels.pop_back();
}
}
break;
case wi::scene::AnimationComponent::AnimationChannel::SCALE:
{
const TransformComponent* transform = scene.transforms.GetComponent(channel.target);
if (transform != nullptr)
{
animation_data->keyframe_data.push_back(transform->scale_local.x);
animation_data->keyframe_data.push_back(transform->scale_local.y);
animation_data->keyframe_data.push_back(transform->scale_local.z);
}
else
{
animation_data->keyframe_times.pop_back();
animation->channels.pop_back();
}
}
break;
case wi::scene::AnimationComponent::AnimationChannel::WEIGHTS:
{
const MeshComponent* mesh = scene.meshes.GetComponent(channel.target);
if (mesh == nullptr && scene.objects.Contains(selected.entity))
{
// Also try query mesh of selected object:
ObjectComponent* object = scene.objects.GetComponent(selected.entity);
mesh = scene.meshes.GetComponent(object->meshID);
channel.target = selected.entity;
}
if (mesh != nullptr && !mesh->targets.empty())
{
for (const MeshComponent::MeshMorphTarget& morph : mesh->targets)
{
animation_data->keyframe_data.push_back(morph.weight);
}
}
else
{
animation_data->keyframe_times.pop_back();
animation->channels.pop_back();
}
}
break;
default:
break;
}
}
}
}
}
}
recordCombo.SetSelectedWithoutCallback(0);
RefreshKeyframesList();
});
recordCombo.SetTooltip("Record selected entities' specified channels into the animation at the current time.");
AddWidget(&recordCombo);
keyframesList.Create("Keyframes");
keyframesList.SetSize(XMFLOAT2(wid, 200));
keyframesList.SetPos(XMFLOAT2(x, y += step));
keyframesList.OnSelect([=](wi::gui::EventArgs args) {
wi::scene::Scene& scene = editor->GetCurrentScene();
AnimationComponent* animation = scene.animations.GetComponent(entity);
if (animation != nullptr)
{
uint32_t channelIndex = args.userdata & 0xFFFFFFFF;
uint32_t timeIndex = uint32_t(args.userdata >> 32ull) & 0xFFFFFFFF;
if (animation->channels.size() > channelIndex)
{
const AnimationComponent::AnimationChannel& channel = animation->channels[channelIndex];
const AnimationComponent::AnimationSampler& sam = animation->samplers[channel.samplerIndex];
const AnimationDataComponent* animation_data = scene.animation_datas.GetComponent(sam.data);
if (animation_data != nullptr && animation_data->keyframe_times.size() > timeIndex)
{
wi::vector<float> tmp = animation_data->keyframe_times;
std::sort(tmp.begin(), tmp.end());
float time = tmp[timeIndex];
animation->timer = time;
}
}
}
});
keyframesList.OnDelete([=](wi::gui::EventArgs args) {
wi::scene::Scene& scene = editor->GetCurrentScene();
AnimationComponent* animation = scene.animations.GetComponent(entity);
if (animation != nullptr)
{
uint32_t channelIndex = args.userdata & 0xFFFFFFFF;
uint32_t timeIndex = uint32_t(args.userdata >> 32ull) & 0xFFFFFFFF;
if (animation->channels.size() > channelIndex)
{
const AnimationComponent::AnimationChannel& channel = animation->channels[channelIndex];
const AnimationComponent::AnimationSampler& sam = animation->samplers[channel.samplerIndex];
AnimationDataComponent* animation_data = scene.animation_datas.GetComponent(sam.data);
if (animation_data != nullptr && animation_data->keyframe_times.size() > timeIndex)
{
// specific keyframe deletion:
switch (channel.path)
{
case AnimationComponent::AnimationChannel::Path::TRANSLATION:
animation_data->keyframe_times.erase(animation_data->keyframe_times.begin() + timeIndex);
animation_data->keyframe_data.erase(animation_data->keyframe_data.begin() + timeIndex * 3, animation_data->keyframe_data.begin() + timeIndex * 3 + 3);
break;
case AnimationComponent::AnimationChannel::Path::ROTATION:
animation_data->keyframe_times.erase(animation_data->keyframe_times.begin() + timeIndex);
animation_data->keyframe_data.erase(animation_data->keyframe_data.begin() + timeIndex * 4, animation_data->keyframe_data.begin() + timeIndex * 4 + 4);
break;
case AnimationComponent::AnimationChannel::Path::SCALE:
animation_data->keyframe_times.erase(animation_data->keyframe_times.begin() + timeIndex);
animation_data->keyframe_data.erase(animation_data->keyframe_data.begin() + timeIndex * 3, animation_data->keyframe_data.begin() + timeIndex * 3 + 3);
break;
case AnimationComponent::AnimationChannel::Path::WEIGHTS:
{
MeshComponent* mesh = scene.meshes.GetComponent(channel.target);
if (mesh == nullptr && scene.objects.Contains(channel.target))
{
// Also try query mesh of selected object:
ObjectComponent* object = scene.objects.GetComponent(channel.target);
mesh = scene.meshes.GetComponent(object->meshID);
}
if (mesh != nullptr)
{
animation_data->keyframe_times.erase(animation_data->keyframe_times.begin() + timeIndex);
animation_data->keyframe_data.erase(animation_data->keyframe_data.begin() + timeIndex * mesh->targets.size(), animation_data->keyframe_data.begin() + timeIndex * mesh->targets.size() + mesh->targets.size());
}
}
break;
default:
break;
}
}
else
{
// entire channel deletion:
animation->channels.erase(animation->channels.begin() + channelIndex);
}
}
}
RefreshKeyframesList();
});
AddWidget(&keyframesList);
SetMinimized(true);
SetVisible(false);
}
void AnimationWindow::SetEntity(Entity entity)
{
if (this->entity != entity)
{
wi::scene::Scene& scene = editor->GetCurrentScene();
AnimationComponent* animation = scene.animations.GetComponent(entity);
if (animation != nullptr || IsCollapsed())
{
this->entity = entity;
RefreshKeyframesList();
}
}
}
void AnimationWindow::Update()
{
Scene& scene = editor->GetCurrentScene();
if (!scene.animations.Contains(entity))
{
SetEnabled(false);
return;
}
else
{
SetEnabled(true);
}
AnimationComponent& animation = *scene.animations.GetComponent(entity);
if (animation.IsPlaying())
{
playButton.SetText("Pause");
}
else
{
playButton.SetText("Play");
}
if(!animation.samplers.empty())
{
modeComboBox.SetSelectedByUserdataWithoutCallback(animation.samplers[0].mode);
}
else
{
modeComboBox.SetSelectedByUserdataWithoutCallback(AnimationComponent::AnimationSampler().mode);
}
loopedCheckBox.SetCheck(animation.IsLooped());
timerSlider.SetRange(animation.start, animation.end);
timerSlider.SetValue(animation.timer);
amountSlider.SetValue(animation.amount);
speedSlider.SetValue(animation.speed);
startInput.SetValue(animation.start);
endInput.SetValue(animation.end);
}
void AnimationWindow::RefreshKeyframesList()
{
Scene& scene = editor->GetCurrentScene();
if (!scene.animations.Contains(entity))
{
SetEntity(INVALID_ENTITY);
return;
}
AnimationComponent& animation = *scene.animations.GetComponent(entity);
keyframesList.ClearItems();
uint32_t channelIndex = 0;
for (const AnimationComponent::AnimationChannel& channel : animation.channels)
{
wi::gui::TreeList::Item item;
switch (channel.path)
{
default:
case wi::scene::AnimationComponent::AnimationChannel::TRANSLATION:
item.name += ICON_TRANSLATE " ";
break;
case wi::scene::AnimationComponent::AnimationChannel::ROTATION:
item.name += ICON_ROTATE " ";
break;
case wi::scene::AnimationComponent::AnimationChannel::SCALE:
item.name += ICON_SCALE " ";
break;
case wi::scene::AnimationComponent::AnimationChannel::WEIGHTS:
item.name += ICON_MESH " ";
break;
}
const NameComponent* name = scene.names.GetComponent(channel.target);
if (name == nullptr)
{
item.name += "[no_name] " + std::to_string(channel.target);
}
else if (name->name.empty())
{
item.name += "[name_empty] " + std::to_string(channel.target);
}
else
{
item.name += name->name;
}
item.userdata = 0ull;
item.userdata |= channelIndex & 0xFFFFFFFF;
item.userdata |= uint64_t(0xFFFFFFFF) << 32ull; // invalid time index, means entire channel
keyframesList.AddItem(item);
auto& sam = animation.samplers[channel.samplerIndex];
AnimationDataComponent* animation_data = scene.animation_datas.GetComponent(sam.data);
if (animation_data != nullptr)
{
uint32_t timeIndex = 0;
for (float time : animation_data->keyframe_times)
{
wi::gui::TreeList::Item item2;
item2.name = std::to_string(time) + " sec";
item2.level = 1;
item2.userdata = 0ull;
item2.userdata |= channelIndex & 0xFFFFFFFF;
item2.userdata |= uint64_t(timeIndex & 0xFFFFFFFF) << 32ull;
keyframesList.AddItem(item2);
timeIndex++;
}
}
channelIndex++;
}
}