From bebe54efc659bac1f109a7d46081598e7ea35e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Sun, 14 Aug 2022 22:46:54 +0200 Subject: [PATCH] Light animation support (#509) --- Content/models/animation_test.wiscene | Bin 0 -> 4995 bytes .../morph_target_animation_test.wiscene | Bin 0 -> 54423 bytes Editor/AnimationWindow.cpp | 175 +++++++++++-- Editor/AnimationWindow.h | 2 + Editor/IKWindow.cpp | 2 +- Editor/IconDefinitions.h | 5 + Editor/NameWindow.cpp | 2 +- Editor/OptionsWindow.cpp | 13 +- Editor/OptionsWindow.h | 1 + Editor/WeatherWindow.cpp | 4 +- Editor/WeatherWindow.h | 3 + WickedEngine/wiScene.cpp | 243 +++++++++++++++--- WickedEngine/wiScene.h | 10 +- WickedEngine/wiVersion.cpp | 2 +- 14 files changed, 392 insertions(+), 70 deletions(-) create mode 100644 Content/models/animation_test.wiscene create mode 100644 Content/models/morph_target_animation_test.wiscene diff --git a/Content/models/animation_test.wiscene b/Content/models/animation_test.wiscene new file mode 100644 index 0000000000000000000000000000000000000000..05c033b35b71e3ae26609b242b04016e3aff2056 GIT binary patch literal 4995 zcmeH~J!~9B6vrnfU@({jJA4%=LQtfjfzSZ)?ds4qC=nlSBUu;gh%M~oNREIIR!%}h zKmj5I8oP)fP(_px5aK(zKnOVnxUeGn6p2CL1W1!O^Jd>~XKsA<9fc7EBkjKT{_ktv z+nL$jVbgxxrtqyws}tn|mBCOLnXDWh8<{NEDu>3)6L+ZUaP?qqVtoHtZL^SVZM~y|wrV$0`u|zP-Q^iTWBta-JPXbnWcC;5kzG@| zr`;UkEfraY=yN{teom)0o%vQjv6n$#pc%T}ozoT@>Y+YxWX{XA?BaO%hcuXb>hZ8I zg1SC-(tCHyF{cM|lo&3y?YoRHPtK=`i;eL1IXGXj7v5enaz0gF?Cof=x0v%yGn(`5 z;)t)I`Ri2in! z`9SPb865de#qUzew^@ILZ;A99+koQW<|eE_4g{hPboO^{fg5U>Qvao{R0na zu$EGf%i0ash+E75L?5j|7c2B{rOgF%DI0#ed=7?P5kf`!11Y5 zxm!nlBl5R0xg|i`{<_HAdX$NuDL88#89&y-mF4BapI2+ijq6iUEm-E1Vm!4U^G8V# zPk;njf!-$%x0(r!GF}o8h+Rn~_OE76{$7O2ACB_H?2Fk)pW3`vx@xXnm}z|TZr%Q^ z{Faz~k0h~OoX1CK<*@Sw*ORla?{^n-Gv|MC^a{C(nGdMPVxxjVtjiuyGE6eqx@@32W=UGo!6YMAU4{MY) H$r`)~%Zcze literal 0 HcmV?d00001 diff --git a/Content/models/morph_target_animation_test.wiscene b/Content/models/morph_target_animation_test.wiscene new file mode 100644 index 0000000000000000000000000000000000000000..b2f542d391ad6242f12a328197a91ec747866d5b GIT binary patch literal 54423 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 z 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 literal 0 HcmV?d00001 diff --git a/Editor/AnimationWindow.cpp b/Editor/AnimationWindow.cpp index 9c8245d66..294043e6c 100644 --- a/Editor/AnimationWindow.cpp +++ b/Editor/AnimationWindow.cpp @@ -81,8 +81,7 @@ void AnimationWindow::Create(EditorComponent* _editor) loopedCheckBox.SetCheckText(ICON_LOOP); AddWidget(&loopedCheckBox); - playButton.Create("Play"); - playButton.SetTooltip("Play/Pause animation."); + playButton.Create(ICON_PLAY); playButton.SetSize(XMFLOAT2(100, hei)); playButton.SetPos(XMFLOAT2(loopedCheckBox.GetPos().x + loopedCheckBox.GetSize().x + 5, y)); playButton.OnClick([&](wi::gui::EventArgs args) { @@ -101,9 +100,9 @@ void AnimationWindow::Create(EditorComponent* _editor) }); AddWidget(&playButton); - stopButton.Create("Stop"); - stopButton.SetTooltip("Stop animation."); - stopButton.SetSize(XMFLOAT2(100, hei)); + stopButton.Create(ICON_STOP); + stopButton.SetTooltip("Stop"); + stopButton.SetSize(XMFLOAT2(70, 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); @@ -197,7 +196,12 @@ void AnimationWindow::Create(EditorComponent* _editor) recordCombo.AddItem("Rotation " ICON_ROTATE); recordCombo.AddItem("Scale " ICON_SCALE); recordCombo.AddItem("Morph weights " ICON_MESH); - recordCombo.AddItem("Close loop " ICON_LOOP); + recordCombo.AddItem("Light [color] " ICON_POINTLIGHT); + recordCombo.AddItem("Light [intensity] " ICON_POINTLIGHT); + recordCombo.AddItem("Light [range] " ICON_POINTLIGHT); + recordCombo.AddItem("Light [inner cone] " ICON_POINTLIGHT); + recordCombo.AddItem("Light [outer cone] " ICON_POINTLIGHT); + recordCombo.AddItem("Close loop " ICON_LOOP, ~0ull); recordCombo.OnSelect([&](wi::gui::EventArgs args) { if (args.iValue == 0) return; @@ -209,7 +213,7 @@ void AnimationWindow::Create(EditorComponent* _editor) { const float current_time = animation->timer; - if (args.iValue == 6) + if (args.userdata == ~0ull) { // Close loop: for (auto& channel : animation->channels) @@ -237,6 +241,8 @@ void AnimationWindow::Create(EditorComponent* _editor) switch (channel.path) { case wi::scene::AnimationComponent::AnimationChannel::TRANSLATION: + case wi::scene::AnimationComponent::AnimationChannel::SCALE: + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_COLOR: { 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]); @@ -251,13 +257,6 @@ void AnimationWindow::Create(EditorComponent* _editor) 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); @@ -278,6 +277,14 @@ void AnimationWindow::Create(EditorComponent* _editor) } } break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_INTENSITY: + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_RANGE: + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_INNERCONE: + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_OUTERCONE: + { + animation_data->keyframe_data.push_back(animation_data->keyframe_data[keyFirst]); + } + break; default: break; } @@ -287,7 +294,7 @@ void AnimationWindow::Create(EditorComponent* _editor) else { // Add keyframe type: - wi::vector paths; + wi::vector paths; // stack allocation would be better here.. switch (args.iValue) { @@ -309,6 +316,21 @@ void AnimationWindow::Create(EditorComponent* _editor) case 5: paths.push_back(AnimationComponent::AnimationChannel::Path::WEIGHTS); break; + case 6: + paths.push_back(AnimationComponent::AnimationChannel::Path::LIGHT_COLOR); + break; + case 7: + paths.push_back(AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY); + break; + case 8: + paths.push_back(AnimationComponent::AnimationChannel::Path::LIGHT_RANGE); + break; + case 9: + paths.push_back(AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE); + break; + case 10: + paths.push_back(AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE); + break; } for (auto path : paths) @@ -421,6 +443,78 @@ void AnimationWindow::Create(EditorComponent* _editor) } } break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_COLOR: + { + const LightComponent* light = scene.lights.GetComponent(channel.target); + if (light != nullptr) + { + animation_data->keyframe_data.push_back(light->color.x); + animation_data->keyframe_data.push_back(light->color.y); + animation_data->keyframe_data.push_back(light->color.z); + } + else + { + animation_data->keyframe_times.pop_back(); + animation->channels.pop_back(); + } + } + break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_INTENSITY: + { + const LightComponent* light = scene.lights.GetComponent(channel.target); + if (light != nullptr) + { + animation_data->keyframe_data.push_back(light->intensity); + } + else + { + animation_data->keyframe_times.pop_back(); + animation->channels.pop_back(); + } + } + break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_RANGE: + { + const LightComponent* light = scene.lights.GetComponent(channel.target); + if (light != nullptr) + { + animation_data->keyframe_data.push_back(light->range); + } + else + { + animation_data->keyframe_times.pop_back(); + animation->channels.pop_back(); + } + } + break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_INNERCONE: + { + const LightComponent* light = scene.lights.GetComponent(channel.target); + if (light != nullptr) + { + animation_data->keyframe_data.push_back(light->innerConeAngle); + } + else + { + animation_data->keyframe_times.pop_back(); + animation->channels.pop_back(); + } + } + break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_OUTERCONE: + { + const LightComponent* light = scene.lights.GetComponent(channel.target); + if (light != nullptr) + { + animation_data->keyframe_data.push_back(light->outerConeAngle); + } + else + { + animation_data->keyframe_times.pop_back(); + animation->channels.pop_back(); + } + } + break; default: break; } @@ -438,7 +532,7 @@ void AnimationWindow::Create(EditorComponent* _editor) keyframesList.Create("Keyframes"); keyframesList.SetSize(XMFLOAT2(wid, 200)); - keyframesList.SetPos(XMFLOAT2(x, y += step)); + keyframesList.SetPos(XMFLOAT2(4, y += step)); keyframesList.OnSelect([=](wi::gui::EventArgs args) { wi::scene::Scene& scene = editor->GetCurrentScene(); AnimationComponent* animation = scene.animations.GetComponent(entity); @@ -453,9 +547,7 @@ void AnimationWindow::Create(EditorComponent* _editor) const AnimationDataComponent* animation_data = scene.animation_datas.GetComponent(sam.data); if (animation_data != nullptr && animation_data->keyframe_times.size() > timeIndex) { - wi::vector tmp = animation_data->keyframe_times; - std::sort(tmp.begin(), tmp.end()); - float time = tmp[timeIndex]; + float time = animation_data->keyframe_times[timeIndex]; animation->timer = time; } } @@ -479,6 +571,8 @@ void AnimationWindow::Create(EditorComponent* _editor) switch (channel.path) { case AnimationComponent::AnimationChannel::Path::TRANSLATION: + case AnimationComponent::AnimationChannel::Path::SCALE: + case AnimationComponent::AnimationChannel::Path::LIGHT_COLOR: 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; @@ -486,10 +580,6 @@ void AnimationWindow::Create(EditorComponent* _editor) 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); @@ -506,6 +596,12 @@ void AnimationWindow::Create(EditorComponent* _editor) } } break; + case AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY: + case AnimationComponent::AnimationChannel::Path::LIGHT_RANGE: + case AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE: + case AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE: + animation_data->keyframe_times.erase(animation_data->keyframe_times.begin() + timeIndex); + break; default: break; } @@ -561,11 +657,13 @@ void AnimationWindow::Update() if (animation.IsPlaying()) { - playButton.SetText("Pause"); + playButton.SetText(ICON_PAUSE); + playButton.SetTooltip("Pause"); } else { - playButton.SetText("Play"); + playButton.SetText(ICON_PLAY); + playButton.SetTooltip("Play"); } if(!animation.samplers.empty()) @@ -607,7 +705,6 @@ void AnimationWindow::RefreshKeyframesList() wi::gui::TreeList::Item item; switch (channel.path) { - default: case wi::scene::AnimationComponent::AnimationChannel::TRANSLATION: item.name += ICON_TRANSLATE " "; break; @@ -620,6 +717,23 @@ void AnimationWindow::RefreshKeyframesList() case wi::scene::AnimationComponent::AnimationChannel::WEIGHTS: item.name += ICON_MESH " "; break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_COLOR: + item.name += ICON_POINTLIGHT " [color] "; + break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_INTENSITY: + item.name += ICON_POINTLIGHT " [intensity] "; + break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_RANGE: + item.name += ICON_POINTLIGHT " [range] "; + break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_INNERCONE: + item.name += ICON_POINTLIGHT " [inner cone] "; + break; + case wi::scene::AnimationComponent::AnimationChannel::LIGHT_OUTERCONE: + item.name += ICON_POINTLIGHT " [outer cone] "; + break; + default: + break; } const NameComponent* name = scene.names.GetComponent(channel.target); if (name == nullptr) @@ -660,3 +774,12 @@ void AnimationWindow::RefreshKeyframesList() channelIndex++; } } + + +void AnimationWindow::ResizeLayout() +{ + wi::gui::Window::ResizeLayout(); + const float padding = 4; + const float width = GetWidgetAreaSize().x - padding * 2; + keyframesList.SetSize(XMFLOAT2(width, keyframesList.GetSize().y)); +} diff --git a/Editor/AnimationWindow.h b/Editor/AnimationWindow.h index 8c13052e8..d7a49c004 100644 --- a/Editor/AnimationWindow.h +++ b/Editor/AnimationWindow.h @@ -28,5 +28,7 @@ public: void Update(); void RefreshKeyframesList(); + + void ResizeLayout() override; }; diff --git a/Editor/IKWindow.cpp b/Editor/IKWindow.cpp index c30fe77bc..23c10c531 100644 --- a/Editor/IKWindow.cpp +++ b/Editor/IKWindow.cpp @@ -9,7 +9,7 @@ using namespace wi::scene; void IKWindow::Create(EditorComponent* _editor) { editor = _editor; - wi::gui::Window::Create("Inverse Kinematics", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); + wi::gui::Window::Create(ICON_IK " Inverse Kinematics", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); SetSize(XMFLOAT2(400, 110)); closeButton.SetTooltip("Delete InverseKinematicsComponent"); diff --git a/Editor/IconDefinitions.h b/Editor/IconDefinitions.h index 71c8bb0be..6287cc14a 100644 --- a/Editor/IconDefinitions.h +++ b/Editor/IconDefinitions.h @@ -24,6 +24,8 @@ #define ICON_MATERIAL ICON_FA_FILL_DRIP #define ICON_WEATHER ICON_FA_CLOUD #define ICON_BONE ICON_FA_BONE +#define ICON_IK ICON_FA_HAND_FIST +#define ICON_NAME ICON_FA_COMMENT_DOTS #define ICON_TERRAIN ICON_FA_MOUNTAIN_SUN @@ -46,3 +48,6 @@ #define ICON_SQUARE ICON_FA_SQUARE_FULL #define ICON_CUBE ICON_FA_CUBE #define ICON_LOOP ICON_FA_REPEAT +#define ICON_PLAY ICON_FA_PLAY +#define ICON_PAUSE ICON_FA_PAUSE +#define ICON_STOP ICON_FA_STOP diff --git a/Editor/NameWindow.cpp b/Editor/NameWindow.cpp index a1e4f136e..51c6991f5 100644 --- a/Editor/NameWindow.cpp +++ b/Editor/NameWindow.cpp @@ -9,7 +9,7 @@ using namespace wi::scene; void NameWindow::Create(EditorComponent* _editor) { editor = _editor; - wi::gui::Window::Create("Name", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); + wi::gui::Window::Create(ICON_NAME " Name", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); SetSize(XMFLOAT2(360, 60)); closeButton.SetTooltip("Delete NameComponent"); diff --git a/Editor/OptionsWindow.cpp b/Editor/OptionsWindow.cpp index a96e77a41..4bfc8b77c 100644 --- a/Editor/OptionsWindow.cpp +++ b/Editor/OptionsWindow.cpp @@ -272,6 +272,7 @@ void OptionsWindow::Create(EditorComponent* _editor) filterCombo.AddItem("Force " ICON_FORCE, (uint64_t)Filter::Force); filterCombo.AddItem("Emitter " ICON_EMITTER, (uint64_t)Filter::Emitter); filterCombo.AddItem("Hairparticle " ICON_HAIR, (uint64_t)Filter::Hairparticle); + filterCombo.AddItem("Inverse Kinematics " ICON_IK, (uint64_t)Filter::IK); filterCombo.SetTooltip("Apply filtering to the Entities"); filterCombo.OnSelect([&](wi::gui::EventArgs args) { filter = (Filter)args.userdata; @@ -642,6 +643,8 @@ void OptionsWindow::Create(EditorComponent* _editor) editor->saveButton.sprites[i].params.enableCornerRounding(); editor->saveButton.sprites[i].params.corners_rounding[2].radius = 10; } + editor->componentsWnd.weatherWnd.default_sky_horizon = dark_point; + editor->componentsWnd.weatherWnd.default_sky_zenith = theme_color_idle; }); AddWidget(&themeCombo); @@ -866,11 +869,16 @@ void OptionsWindow::PushToEntityTree(wi::ecs::Entity entity, int level) { item.name += ICON_WEATHER " "; } + if (scene.inverse_kinematics.Contains(entity)) + { + item.name += ICON_IK " "; + } if (entity == terragen.terrainEntity) { item.name += ICON_TERRAIN " "; } - for (size_t i = 0; i < scene.armatures.GetCount(); ++i) + bool bone_found = false; + for (size_t i = 0; i < scene.armatures.GetCount() && !bone_found; ++i) { const ArmatureComponent& armature = scene.armatures[i]; for (Entity bone : armature.boneCollection) @@ -878,6 +886,7 @@ void OptionsWindow::PushToEntityTree(wi::ecs::Entity entity, int level) if (entity == bone) { item.name += ICON_BONE " "; + bone_found = true; break; } } @@ -1080,7 +1089,7 @@ void OptionsWindow::RefreshEntityTree() } } - if (has_flag(filter, Filter::All)) + if (has_flag(filter, Filter::IK)) { for (size_t i = 0; i < scene.inverse_kinematics.GetCount(); ++i) { diff --git a/Editor/OptionsWindow.h b/Editor/OptionsWindow.h index 1f7aab669..71ea19ec8 100644 --- a/Editor/OptionsWindow.h +++ b/Editor/OptionsWindow.h @@ -55,6 +55,7 @@ public: Force = 1 << 10, Emitter = 1 << 11, Hairparticle = 1 << 12, + IK = 1 << 13, All = ~0ull, } filter = Filter::All; diff --git a/Editor/WeatherWindow.cpp b/Editor/WeatherWindow.cpp index aef7300a8..d004a7318 100644 --- a/Editor/WeatherWindow.cpp +++ b/Editor/WeatherWindow.cpp @@ -772,8 +772,8 @@ void WeatherWindow::Update() { scene.weather = {}; scene.weather.SetSimpleSky(true); - scene.weather.zenith = XMFLOAT3(1, 1, 1); - scene.weather.horizon = wi::Color(20, 20, 20); + scene.weather.zenith = default_sky_zenith; + scene.weather.horizon = default_sky_horizon; } } diff --git a/Editor/WeatherWindow.h b/Editor/WeatherWindow.h index 5db68977a..b5e631169 100644 --- a/Editor/WeatherWindow.h +++ b/Editor/WeatherWindow.h @@ -18,6 +18,9 @@ public: wi::scene::WeatherComponent& GetWeather() const; void InvalidateProbes() const; + XMFLOAT3 default_sky_horizon = XMFLOAT3(0, 0, 0); + XMFLOAT3 default_sky_zenith = XMFLOAT3(0, 0, 0); + wi::gui::Button primaryButton; wi::gui::CheckBox heightFogCheckBox; wi::gui::Slider fogStartSlider; diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index c5b3782fe..3cbe89448 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -2791,9 +2791,11 @@ namespace wi::scene const float right = animationdata->keyframe_times[keyRight]; TransformComponent transform; + LightComponent light; TransformComponent* target_transform = nullptr; MeshComponent* target_mesh = nullptr; + LightComponent* target_light = nullptr; if (channel.path == AnimationComponent::AnimationChannel::Path::WEIGHTS) { @@ -2805,6 +2807,19 @@ namespace wi::scene continue; animation.morph_weights_temp.resize(target_mesh->targets.size()); } + else if ( + channel.path == AnimationComponent::AnimationChannel::Path::LIGHT_COLOR || + channel.path == AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY || + channel.path == AnimationComponent::AnimationChannel::Path::LIGHT_RANGE || + channel.path == AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE || + channel.path == AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE + ) + { + target_light = lights.GetComponent(channel.target); + if (target_light == nullptr) + continue; + light = *target_light; + } else { target_transform = transforms.GetComponent(channel.target); @@ -2850,6 +2865,36 @@ namespace wi::scene } } break; + case AnimationComponent::AnimationChannel::Path::LIGHT_COLOR: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 3); + light.color = ((const XMFLOAT3*)animationdata->keyframe_data.data())[key]; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + light.intensity = animationdata->keyframe_data[key]; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_RANGE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + light.range = animationdata->keyframe_data[key]; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + light.innerConeAngle = animationdata->keyframe_data[key]; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + light.outerConeAngle = animationdata->keyframe_data[key]; + } + break; } } break; @@ -2912,6 +2957,52 @@ namespace wi::scene } } break; + case AnimationComponent::AnimationChannel::Path::LIGHT_COLOR: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 3); + const XMFLOAT3* data = (const XMFLOAT3*)animationdata->keyframe_data.data(); + XMVECTOR vLeft = XMLoadFloat3(&data[keyLeft]); + XMVECTOR vRight = XMLoadFloat3(&data[keyRight]); + XMVECTOR vAnim = XMVectorLerp(vLeft, vRight, t); + XMStoreFloat3(&light.color, vAnim); + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + float vLeft = animationdata->keyframe_data[keyLeft]; + float vRight = animationdata->keyframe_data[keyRight]; + float vAnim = wi::math::Lerp(vLeft, vRight, t); + light.intensity = vAnim; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_RANGE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + float vLeft = animationdata->keyframe_data[keyLeft]; + float vRight = animationdata->keyframe_data[keyRight]; + float vAnim = wi::math::Lerp(vLeft, vRight, t); + light.range = vAnim; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + float vLeft = animationdata->keyframe_data[keyLeft]; + float vRight = animationdata->keyframe_data[keyRight]; + float vAnim = wi::math::Lerp(vLeft, vRight, t); + light.innerConeAngle = vAnim; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + float vLeft = animationdata->keyframe_data[keyLeft]; + float vRight = animationdata->keyframe_data[keyRight]; + float vAnim = wi::math::Lerp(vLeft, vRight, t); + light.outerConeAngle = vAnim; + } + break; } } break; @@ -2985,17 +3076,73 @@ namespace wi::scene } } break; + case AnimationComponent::AnimationChannel::Path::LIGHT_COLOR: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size() * 3 * 3); + const XMFLOAT3* data = (const XMFLOAT3*)animationdata->keyframe_data.data(); + XMVECTOR vLeft = XMLoadFloat3(&data[keyLeft * 3 + 1]); + XMVECTOR vLeftTanOut = dt * XMLoadFloat3(&data[keyLeft * 3 + 2]); + XMVECTOR vRightTanIn = dt * XMLoadFloat3(&data[keyRight * 3 + 0]); + XMVECTOR vRight = XMLoadFloat3(&data[keyRight * 3 + 1]); + XMVECTOR vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn; + XMStoreFloat3(&light.color, vAnim); + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_INTENSITY: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + float vLeft = animationdata->keyframe_data[keyLeft * 3 + 1]; + float vLeftTanOut = animationdata->keyframe_data[keyLeft * 3 + 2]; + float vRightTanIn = animationdata->keyframe_data[keyRight * 3 + 0]; + float vRight = animationdata->keyframe_data[keyRight * 3 + 1]; + float vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn; + light.intensity = vAnim; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_RANGE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + float vLeft = animationdata->keyframe_data[keyLeft * 3 + 1]; + float vLeftTanOut = animationdata->keyframe_data[keyLeft * 3 + 2]; + float vRightTanIn = animationdata->keyframe_data[keyRight * 3 + 0]; + float vRight = animationdata->keyframe_data[keyRight * 3 + 1]; + float vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn; + light.range = vAnim; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_INNERCONE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + float vLeft = animationdata->keyframe_data[keyLeft * 3 + 1]; + float vLeftTanOut = animationdata->keyframe_data[keyLeft * 3 + 2]; + float vRightTanIn = animationdata->keyframe_data[keyRight * 3 + 0]; + float vRight = animationdata->keyframe_data[keyRight * 3 + 1]; + float vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn; + light.innerConeAngle = vAnim; + } + break; + case AnimationComponent::AnimationChannel::Path::LIGHT_OUTERCONE: + { + assert(animationdata->keyframe_data.size() == animationdata->keyframe_times.size()); + float vLeft = animationdata->keyframe_data[keyLeft * 3 + 1]; + float vLeftTanOut = animationdata->keyframe_data[keyLeft * 3 + 2]; + float vRightTanIn = animationdata->keyframe_data[keyRight * 3 + 0]; + float vRight = animationdata->keyframe_data[keyRight * 3 + 1]; + float vAnim = (2 * t3 - 3 * t2 + 1) * vLeft + (t3 - 2 * t2 + t) * vLeftTanOut + (-2 * t3 + 3 * t2) * vRight + (t3 - t2) * vRightTanIn; + light.outerConeAngle = vAnim; + } + break; } } break; } + const float t = animation.amount; + if (target_transform != nullptr) { target_transform->SetDirty(); - const float t = animation.amount; - const XMVECTOR aS = XMLoadFloat3(&target_transform->scale_local); const XMVECTOR aR = XMLoadFloat4(&target_transform->rotation_local); const XMVECTOR aT = XMLoadFloat3(&target_transform->translation_local); @@ -3015,8 +3162,6 @@ namespace wi::scene if (target_mesh != nullptr) { - const float t = animation.amount; - for (size_t j = 0; j < target_mesh->targets.size(); ++j) { target_mesh->targets[j].weight = wi::math::Lerp(target_mesh->targets[j].weight, animation.morph_weights_temp[j], t); @@ -3025,6 +3170,15 @@ namespace wi::scene target_mesh->dirty_morph = true; } + if (target_light != nullptr) + { + target_light->color = wi::math::Lerp(target_light->color, light.color, t); + target_light->intensity = wi::math::Lerp(target_light->intensity, light.intensity, t); + target_light->range = wi::math::Lerp(target_light->range, light.range, t); + target_light->innerConeAngle = wi::math::Lerp(target_light->innerConeAngle, light.innerConeAngle, t); + target_light->outerConeAngle = wi::math::Lerp(target_light->outerConeAngle, light.outerConeAngle, t); + } + } if (animation.IsPlaying()) @@ -3109,6 +3263,12 @@ namespace wi::scene const XMVECTOR windDir = XMLoadFloat3(&weather.windDirection); const XMVECTOR gravity = XMVectorSet(0, -9.8f, 0, 0); + if (springs.GetCount() > 0) + { + transforms_temp.resize(transforms.GetCount()); + transforms_temp = transforms.GetComponentArray(); // make copy + } + for (size_t i = 0; i < springs.GetCount(); ++i) { SpringComponent& spring = springs[i]; @@ -3117,31 +3277,34 @@ namespace wi::scene continue; } Entity entity = springs.GetEntity(i); - TransformComponent* transform = transforms.GetComponent(entity); - if (transform == nullptr) + size_t transform_index = transforms.GetIndex(entity); + if (transform_index == ~0ull) { assert(0); continue; } + TransformComponent& transform = transforms_temp[transform_index]; if (spring.IsResetting()) { spring.Reset(false); - spring.center_of_mass = transform->GetPosition(); + spring.center_of_mass = transform.GetPosition(); spring.velocity = XMFLOAT3(0, 0, 0); } const HierarchyComponent* hier = hierarchy.GetComponent(entity); - TransformComponent* parent_transform = hier == nullptr ? nullptr : transforms.GetComponent(hier->parentID); - if (parent_transform != nullptr) + size_t parent_index = hier == nullptr ? ~0ull : transforms.GetIndex(hier->parentID); + //TransformComponent* parent_transform = hier == nullptr ? nullptr : transforms.GetComponent(hier->parentID); + if (parent_index != ~0ull) { // Spring hierarchy resolve depends on spring component order! // It works best when parent spring is located before child spring! // It will work the other way, but results will be less convincing - transform->UpdateTransform_Parented(*parent_transform); + const TransformComponent& parent_transform = transforms_temp[parent_index]; + transform.UpdateTransform_Parented(parent_transform); } - const XMVECTOR position_current = transform->GetPositionV(); + const XMVECTOR position_current = transform.GetPositionV(); XMVECTOR position_prev = XMLoadFloat3(&spring.center_of_mass); XMVECTOR force = (position_current - position_prev) * spring.stiffness; @@ -3158,9 +3321,10 @@ namespace wi::scene velocity += force * dt; XMVECTOR position_target = position_prev + velocity * dt; - if (parent_transform != nullptr) + if (parent_index != ~0ull) { - const XMVECTOR position_parent = parent_transform->GetPositionV(); + TransformComponent& parent_transform = transforms_temp[parent_index]; + const XMVECTOR position_parent = parent_transform.GetPositionV(); const XMVECTOR parent_to_child = position_current - position_parent; const XMVECTOR parent_to_target = position_target - position_parent; @@ -3177,25 +3341,34 @@ namespace wi::scene const XMVECTOR axis = XMVector3Normalize(XMVector3Cross(dir_parent_to_child, dir_parent_to_target)); const float angle = XMScalarACos(XMVectorGetX(XMVector3Dot(dir_parent_to_child, dir_parent_to_target))); // don't use std::acos! const XMVECTOR Q = XMQuaternionNormalize(XMQuaternionRotationNormal(axis, angle)); - TransformComponent saved_parent = *parent_transform; + TransformComponent saved_parent = parent_transform; saved_parent.ApplyTransform(); saved_parent.Rotate(Q); saved_parent.UpdateTransform(); - std::swap(saved_parent.world, parent_transform->world); // only store temporary result, not modifying actual local space! + std::swap(saved_parent.world, parent_transform.world); // only store temporary result, not modifying actual local space! } XMStoreFloat3(&spring.center_of_mass, position_target); velocity *= spring.damping; XMStoreFloat3(&spring.velocity, velocity); - *((XMFLOAT3*)&transform->world._41) = spring.center_of_mass; + *((XMFLOAT3*)&transform.world._41) = spring.center_of_mass; + } + + if (springs.GetCount() > 0) + { + for (size_t i = 0; i < transforms.GetCount(); ++i) + { + // IK shouldn't modify local space, so only update the world matrices! + transforms[i].world = transforms_temp[i].world; + } } } void Scene::RunInverseKinematicsUpdateSystem(wi::jobsystem::context& ctx) { if (inverse_kinematics.GetCount() > 0) { - ik_temp.resize(transforms.GetCount()); - ik_temp = transforms.GetComponentArray(); // make copy + transforms_temp.resize(transforms.GetCount()); + transforms_temp = transforms.GetComponentArray(); // make copy } bool recompute_hierarchy = false; @@ -3214,15 +3387,15 @@ namespace wi::scene { continue; } - TransformComponent* transform = &ik_temp[transform_index]; - TransformComponent* target = &ik_temp[target_index]; + TransformComponent& transform = transforms_temp[transform_index]; + TransformComponent& target = transforms_temp[target_index]; - const XMVECTOR target_pos = target->GetPositionV(); + const XMVECTOR target_pos = target.GetPositionV(); for (uint32_t iteration = 0; iteration < ik.iteration_count; ++iteration) { TransformComponent* stack[32] = {}; Entity parent_entity = hier->parentID; - TransformComponent* child_transform = transform; + TransformComponent* child_transform = &transform; for (uint32_t chain = 0; chain < std::min(ik.chain_length, (uint32_t)arraysize(stack)); ++chain) { recompute_hierarchy = true; // any IK will trigger a full transform hierarchy recompute step at the end(**) @@ -3234,19 +3407,19 @@ namespace wi::scene size_t parent_index = transforms.GetIndex(parent_entity); if (parent_index == ~0ull) continue; - TransformComponent* parent_transform = &ik_temp[parent_index]; - const XMVECTOR parent_pos = parent_transform->GetPositionV(); - const XMVECTOR dir_parent_to_ik = XMVector3Normalize(transform->GetPositionV() - parent_pos); + TransformComponent& parent_transform = transforms_temp[parent_index]; + const XMVECTOR parent_pos = parent_transform.GetPositionV(); + const XMVECTOR dir_parent_to_ik = XMVector3Normalize(transform.GetPositionV() - parent_pos); const XMVECTOR dir_parent_to_target = XMVector3Normalize(target_pos - parent_pos); const XMVECTOR axis = XMVector3Normalize(XMVector3Cross(dir_parent_to_ik, dir_parent_to_target)); const float angle = XMScalarACos(XMVectorGetX(XMVector3Dot(dir_parent_to_ik, dir_parent_to_target))); const XMVECTOR Q = XMQuaternionNormalize(XMQuaternionRotationNormal(axis, angle)); // parent to world space: - parent_transform->ApplyTransform(); + parent_transform.ApplyTransform(); // rotate parent: - parent_transform->Rotate(Q); - parent_transform->UpdateTransform(); + parent_transform.Rotate(Q); + parent_transform.UpdateTransform(); // parent back to local space (if parent has parent): const HierarchyComponent* hier_parent = hierarchy.GetComponent(parent_entity); if (hier_parent != nullptr) @@ -3255,15 +3428,15 @@ namespace wi::scene size_t parent_of_parent_index = transforms.GetIndex(parent_of_parent_entity); if (parent_of_parent_index != ~0ull) { - const TransformComponent* transform_parent_of_parent = &ik_temp[parent_of_parent_index]; + const TransformComponent* transform_parent_of_parent = &transforms_temp[parent_of_parent_index]; XMMATRIX parent_of_parent_inverse = XMMatrixInverse(nullptr, XMLoadFloat4x4(&transform_parent_of_parent->world)); - parent_transform->MatrixTransform(parent_of_parent_inverse); + parent_transform.MatrixTransform(parent_of_parent_inverse); // Do not call UpdateTransform() here, to keep parent world matrix in world space! } } // update chain from parent to children: - const TransformComponent* recurse_parent = parent_transform; + const TransformComponent* recurse_parent = &parent_transform; for (int recurse_chain = (int)chain; recurse_chain >= 0; --recurse_chain) { stack[recurse_chain]->UpdateTransform_Parented(*recurse_parent); @@ -3277,7 +3450,7 @@ namespace wi::scene } // move up in the chain by one: - child_transform = parent_transform; + child_transform = &parent_transform; parent_entity = hier_parent->parentID; assert(chain < (uint32_t)arraysize(stack) - 1); // if this is encountered, just extend stack array size @@ -3299,8 +3472,8 @@ namespace wi::scene size_t parent_index = transforms.GetIndex(parentcomponent.parentID); if (transform_index != ~0ull && parent_index != ~0ull) { - TransformComponent* transform_child = &ik_temp[transform_index]; - TransformComponent* transform_parent = &ik_temp[parent_index]; + TransformComponent* transform_child = &transforms_temp[transform_index]; + TransformComponent* transform_parent = &transforms_temp[parent_index]; transform_child->UpdateTransform_Parented(*transform_parent); } } @@ -3311,7 +3484,7 @@ namespace wi::scene for (size_t i = 0; i < transforms.GetCount(); ++i) { // IK shouldn't modify local space, so only update the world matrices! - transforms[i].world = ik_temp[i].world; + transforms[i].world = transforms_temp[i].world; } } } diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index eb7634165..5146a5d49 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -845,7 +845,6 @@ namespace wi::scene LIGHTMAPONLY_STATIC = 1 << 3, }; uint32_t _flags = EMPTY; - XMFLOAT3 color = XMFLOAT3(1, 1, 1); enum LightType { @@ -860,6 +859,8 @@ namespace wi::scene ENUM_FORCE_UINT32 = 0xFFFFFFFF, }; LightType type = POINT; + + XMFLOAT3 color = XMFLOAT3(1, 1, 1); float intensity = 1.0f; // Brightness of light in. The units that this is defined in depend on the type of light. Point and spot lights use luminous intensity in candela (lm/sr) while directional lights use illuminance in lux (lm/m2). https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_lights_punctual float range = 10.0f; float outerConeAngle = XM_PIDIV4; @@ -1112,6 +1113,11 @@ namespace wi::scene ROTATION, SCALE, WEIGHTS, + LIGHT_COLOR, + LIGHT_INTENSITY, + LIGHT_RANGE, + LIGHT_INNERCONE, + LIGHT_OUTERCONE, UNKNOWN, TYPE_FORCE_UINT32 = 0xFFFFFFFF } path = TRANSLATION; @@ -1445,7 +1451,7 @@ namespace wi::scene uint32_t impostorMaterialOffset = ~0u; mutable std::atomic_bool lightmap_refresh_needed{ false }; - wi::vector ik_temp; + wi::vector transforms_temp; // Ocean GPU state: wi::Ocean ocean; diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index e5b38b164..a5b1d5d71 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 9; + const int revision = 10; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);