diff --git a/Editor/ArmatureWindow.cpp b/Editor/ArmatureWindow.cpp index 7e71b884b..c3acc87b1 100644 --- a/Editor/ArmatureWindow.cpp +++ b/Editor/ArmatureWindow.cpp @@ -145,11 +145,11 @@ void ArmatureWindow::RefreshBoneList() const NameComponent* name = scene.names.GetComponent(bone); if (name == nullptr) { - item.name += "[no_name] " + std::to_string(entity); + item.name += "[no_name] " + std::to_string(bone); } else if (name->name.empty()) { - item.name += "[name_empty] " + std::to_string(entity); + item.name += "[name_empty] " + std::to_string(bone); } else { diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index 7f2533df8..5c99f8b27 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -39,6 +39,7 @@ set (SOURCE_FILES OptionsWindow.cpp ComponentsWindow.cpp TerrainWindow.cpp + HumanoidWindow.cpp xatlas.cpp ) diff --git a/Editor/ComponentsWindow.cpp b/Editor/ComponentsWindow.cpp index 6730c8322..3a75a2f19 100644 --- a/Editor/ComponentsWindow.cpp +++ b/Editor/ComponentsWindow.cpp @@ -40,6 +40,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) cameraComponentWnd.Create(editor); expressionWnd.Create(editor); armatureWnd.Create(editor); + humanoidWnd.Create(editor); terrainWnd.Create(editor); @@ -316,6 +317,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) AddWidget(&cameraComponentWnd); AddWidget(&expressionWnd); AddWidget(&armatureWnd); + AddWidget(&humanoidWnd); AddWidget(&terrainWnd); materialWnd.SetVisible(false); @@ -343,6 +345,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) cameraComponentWnd.SetVisible(false); expressionWnd.SetVisible(false); armatureWnd.SetVisible(false); + humanoidWnd.SetVisible(false); terrainWnd.SetVisible(false); SetSize(editor->optionsWnd.GetSize()); @@ -686,19 +689,6 @@ void ComponentsWindow::ResizeLayout() scriptWnd.SetVisible(false); } - if (scene.expressions.Contains(expressionWnd.entity)) - { - expressionWnd.SetVisible(true); - expressionWnd.SetPos(pos); - expressionWnd.SetSize(XMFLOAT2(width, expressionWnd.GetScale().y)); - pos.y += expressionWnd.GetSize().y; - pos.y += padding; - } - else - { - expressionWnd.SetVisible(false); - } - if (scene.armatures.Contains(armatureWnd.entity)) { armatureWnd.SetVisible(true); @@ -712,6 +702,32 @@ void ComponentsWindow::ResizeLayout() armatureWnd.SetVisible(false); } + if (scene.humanoids.Contains(humanoidWnd.entity)) + { + humanoidWnd.SetVisible(true); + humanoidWnd.SetPos(pos); + humanoidWnd.SetSize(XMFLOAT2(width, humanoidWnd.GetScale().y)); + pos.y += humanoidWnd.GetSize().y; + pos.y += padding; + } + else + { + humanoidWnd.SetVisible(false); + } + + if (scene.expressions.Contains(expressionWnd.entity)) + { + expressionWnd.SetVisible(true); + expressionWnd.SetPos(pos); + expressionWnd.SetSize(XMFLOAT2(width, expressionWnd.GetScale().y)); + pos.y += expressionWnd.GetSize().y; + pos.y += padding; + } + else + { + expressionWnd.SetVisible(false); + } + if (scene.terrains.Contains(terrainWnd.entity)) { terrainWnd.SetVisible(true); diff --git a/Editor/ComponentsWindow.h b/Editor/ComponentsWindow.h index edc0ab487..13a25c145 100644 --- a/Editor/ComponentsWindow.h +++ b/Editor/ComponentsWindow.h @@ -25,6 +25,7 @@ #include "CameraComponentWindow.h" #include "ExpressionWindow.h" #include "ArmatureWindow.h" +#include "HumanoidWindow.h" #include "TerrainWindow.h" class EditorComponent; @@ -64,5 +65,6 @@ public: CameraComponentWindow cameraComponentWnd; ExpressionWindow expressionWnd; ArmatureWindow armatureWnd; + HumanoidWindow humanoidWnd; TerrainWindow terrainWnd; }; diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 02134796b..5fab9897d 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -358,6 +358,7 @@ void EditorComponent::Load() componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY); componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY); componentsWnd.armatureWnd.SetEntity(INVALID_ENTITY); + componentsWnd.humanoidWnd.SetEntity(INVALID_ENTITY); componentsWnd.terrainWnd.SetEntity(INVALID_ENTITY); optionsWnd.RefreshEntityTree(); @@ -428,6 +429,18 @@ void EditorComponent::Load() GetGUI().AddWidget(&fullscreenButton); + bugButton.Create(""); + bugButton.SetShadowRadius(2); + bugButton.font.params.shadowColor = wi::Color::Transparent(); + bugButton.SetTooltip("Opens a browser window where you can report a bug or an issue.\nURL: https://github.com/turanszkij/WickedEngine/issues/new"); + bugButton.SetColor(wi::Color(50, 160, 200, 180), wi::gui::WIDGETSTATE::IDLE); + bugButton.SetColor(wi::Color(120, 200, 200, 255), wi::gui::WIDGETSTATE::FOCUS); + bugButton.OnClick([](wi::gui::EventArgs args) { + wi::helper::OpenUrl("https://github.com/turanszkij/WickedEngine/issues/new"); + }); + GetGUI().AddWidget(&bugButton); + + aboutButton.Create(""); aboutButton.SetShadowRadius(2); aboutButton.font.params.shadowColor = wi::Color::Transparent(); @@ -937,18 +950,44 @@ void EditorComponent::Update(float dt) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR a = transform.GetPositionV(); - XMVECTOR b = a + XMVectorSet(0, 1, 0, 0); + XMVECTOR b = a + XMVectorSet(0, 0.1f, 0, 0); // Search for child to connect bone tip: bool child_found = false; - for (Entity child : armature.boneCollection) + for (size_t h = 0; (h < scene.humanoids.GetCount()) && !child_found; ++h) { - const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(child); - if (hierarchy != nullptr && hierarchy->parentID == entity && scene.transforms.Contains(child)) + const HumanoidComponent& humanoid = scene.humanoids[h]; + int bodypart = 0; + for (Entity child : humanoid.bones) { - const TransformComponent& child_transform = *scene.transforms.GetComponent(child); - b = child_transform.GetPositionV(); - child_found = true; - break; + const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(child); + if (hierarchy != nullptr && hierarchy->parentID == entity && scene.transforms.Contains(child)) + { + if (bodypart == int(HumanoidComponent::HumanoidBone::Hips)) + { + // skip root-hip connection + child_found = true; + break; + } + const TransformComponent& child_transform = *scene.transforms.GetComponent(child); + b = child_transform.GetPositionV(); + child_found = true; + break; + } + bodypart++; + } + } + if (!child_found) + { + for (Entity child : armature.boneCollection) + { + const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(child); + if (hierarchy != nullptr && hierarchy->parentID == entity && scene.transforms.Contains(child)) + { + const TransformComponent& child_transform = *scene.transforms.GetComponent(child); + b = child_transform.GetPositionV(); + child_found = true; + break; + } } } if (!child_found) @@ -1393,6 +1432,7 @@ void EditorComponent::Update(float dt) componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY); componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY); componentsWnd.armatureWnd.SetEntity(INVALID_ENTITY); + componentsWnd.humanoidWnd.SetEntity(INVALID_ENTITY); componentsWnd.terrainWnd.SetEntity(INVALID_ENTITY); } else @@ -1433,6 +1473,7 @@ void EditorComponent::Update(float dt) componentsWnd.cameraComponentWnd.SetEntity(picked.entity); componentsWnd.expressionWnd.SetEntity(picked.entity); componentsWnd.armatureWnd.SetEntity(picked.entity); + componentsWnd.humanoidWnd.SetEntity(picked.entity); componentsWnd.terrainWnd.SetEntity(picked.entity); if (picked.subsetIndex >= 0) @@ -2128,18 +2169,44 @@ void EditorComponent::Render() const continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR a = transform.GetPositionV(); - XMVECTOR b = a + XMVectorSet(0, 1, 0, 0); + XMVECTOR b = a + XMVectorSet(0, 0.1f, 0, 0); // Search for child to connect bone tip: bool child_found = false; - for (Entity child : armature.boneCollection) + for (size_t h = 0; (h < scene.humanoids.GetCount()) && !child_found; ++h) { - const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(child); - if (hierarchy != nullptr && hierarchy->parentID == entity && scene.transforms.Contains(child)) + const HumanoidComponent& humanoid = scene.humanoids[h]; + int bodypart = 0; + for (Entity child : humanoid.bones) { - const TransformComponent& child_transform = *scene.transforms.GetComponent(child); - b = child_transform.GetPositionV(); - child_found = true; - break; + const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(child); + if (hierarchy != nullptr && hierarchy->parentID == entity && scene.transforms.Contains(child)) + { + if (bodypart == int(HumanoidComponent::HumanoidBone::Hips)) + { + // skip root-hip connection + child_found = true; + break; + } + const TransformComponent& child_transform = *scene.transforms.GetComponent(child); + b = child_transform.GetPositionV(); + child_found = true; + break; + } + bodypart++; + } + } + if (!child_found) + { + for (Entity child : armature.boneCollection) + { + const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(child); + if (hierarchy != nullptr && hierarchy->parentID == entity && scene.transforms.Contains(child)) + { + const TransformComponent& child_transform = *scene.transforms.GetComponent(child); + b = child_transform.GetPositionV(); + child_found = true; + break; + } } } if (!child_found) @@ -2846,6 +2913,7 @@ void EditorComponent::UpdateTopMenuAnimation() exitButton.SetText(exitButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_EXIT " Exit" : ICON_EXIT); aboutButton.SetText(aboutButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_HELP " About" : ICON_HELP); fullscreenButton.SetText(fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? fullscreen_text : fullscreen ? ICON_FA_COMPRESS : ICON_FULLSCREEN); + bugButton.SetText(bugButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_BUG " Bug report" : ICON_BUG); logButton.SetText(logButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_BACKLOG " Backlog" : ICON_BACKLOG); closeButton.SetText(closeButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_CLOSE " Close" : ICON_CLOSE); openButton.SetText(openButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_OPEN " Open" : ICON_OPEN); @@ -2854,6 +2922,7 @@ void EditorComponent::UpdateTopMenuAnimation() exitButton.SetSize(XMFLOAT2(wi::math::Lerp(exitButton.GetSize().x, exitButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); aboutButton.SetSize(XMFLOAT2(wi::math::Lerp(aboutButton.GetSize().x, aboutButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); fullscreenButton.SetSize(XMFLOAT2(wi::math::Lerp(fullscreenButton.GetSize().x, fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); + bugButton.SetSize(XMFLOAT2(wi::math::Lerp(bugButton.GetSize().x, bugButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); logButton.SetSize(XMFLOAT2(wi::math::Lerp(logButton.GetSize().x, logButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); closeButton.SetSize(XMFLOAT2(wi::math::Lerp(closeButton.GetSize().x, closeButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); openButton.SetSize(XMFLOAT2(wi::math::Lerp(openButton.GetSize().x, openButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); @@ -2861,7 +2930,8 @@ void EditorComponent::UpdateTopMenuAnimation() exitButton.SetPos(XMFLOAT2(screenW - exitButton.GetSize().x, 0)); aboutButton.SetPos(XMFLOAT2(exitButton.GetPos().x - aboutButton.GetSize().x - padding, 0)); - fullscreenButton.SetPos(XMFLOAT2(aboutButton.GetPos().x - fullscreenButton.GetSize().x - padding, 0)); + bugButton.SetPos(XMFLOAT2(exitButton.GetPos().x - bugButton.GetSize().x - padding, 0)); + fullscreenButton.SetPos(XMFLOAT2(bugButton.GetPos().x - fullscreenButton.GetSize().x - padding, 0)); logButton.SetPos(XMFLOAT2(fullscreenButton.GetPos().x - logButton.GetSize().x - padding, 0)); closeButton.SetPos(XMFLOAT2(logButton.GetPos().x - closeButton.GetSize().x - padding, 0)); openButton.SetPos(XMFLOAT2(closeButton.GetPos().x - openButton.GetSize().x - padding, 0)); diff --git a/Editor/Editor.h b/Editor/Editor.h index f5d8ca6a7..7516b3ca7 100644 --- a/Editor/Editor.h +++ b/Editor/Editor.h @@ -27,8 +27,9 @@ public: wi::gui::Button openButton; wi::gui::Button closeButton; wi::gui::Button logButton; - wi::gui::Button aboutButton; wi::gui::Button fullscreenButton; + wi::gui::Button bugButton; + wi::gui::Button aboutButton; wi::gui::Button exitButton; wi::gui::Label aboutLabel; @@ -63,8 +64,8 @@ public: float selectionOutlineTimer = 0; const XMFLOAT4 selectionColor = XMFLOAT4(1, 0.6f, 0, 1); const XMFLOAT4 selectionColor2 = XMFLOAT4(0, 1, 0.6f, 0.35f); - const wi::Color inactiveEntityColor = wi::Color::fromFloat4(XMFLOAT4(1, 1, 1, 0.5f)); - const wi::Color hoveredEntityColor = wi::Color::fromFloat4(XMFLOAT4(1, 1, 1, 1)); + wi::Color inactiveEntityColor = wi::Color::fromFloat4(XMFLOAT4(1, 1, 1, 0.5f)); + wi::Color hoveredEntityColor = wi::Color::fromFloat4(XMFLOAT4(1, 1, 1, 1)); wi::graphics::RenderPass renderpass_editor; wi::graphics::Texture editor_depthbuffer; diff --git a/Editor/Editor_SOURCE.vcxitems b/Editor/Editor_SOURCE.vcxitems index 8c12175ac..0449c24d1 100644 --- a/Editor/Editor_SOURCE.vcxitems +++ b/Editor/Editor_SOURCE.vcxitems @@ -28,6 +28,7 @@ + @@ -147,6 +148,7 @@ + diff --git a/Editor/Editor_SOURCE.vcxitems.filters b/Editor/Editor_SOURCE.vcxitems.filters index 1784e052e..d15c727f4 100644 --- a/Editor/Editor_SOURCE.vcxitems.filters +++ b/Editor/Editor_SOURCE.vcxitems.filters @@ -84,6 +84,7 @@ + @@ -132,6 +133,7 @@ + diff --git a/Editor/HumanoidWindow.cpp b/Editor/HumanoidWindow.cpp new file mode 100644 index 000000000..5d1da2165 --- /dev/null +++ b/Editor/HumanoidWindow.cpp @@ -0,0 +1,538 @@ +#include "stdafx.h" +#include "HumanoidWindow.h" +#include "Editor.h" + +using namespace wi::ecs; +using namespace wi::scene; + +void HumanoidWindow::Create(EditorComponent* _editor) +{ + editor = _editor; + + wi::gui::Window::Create(ICON_HUMANOID " Humanoid", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); + SetSize(XMFLOAT2(670, 500)); + + closeButton.SetTooltip("Delete HumanoidComponent"); + OnClose([=](wi::gui::EventArgs args) { + + wi::Archive& archive = editor->AdvanceHistory(); + archive << EditorComponent::HISTORYOP_COMPONENT_DATA; + editor->RecordEntity(archive, entity); + + editor->GetCurrentScene().humanoids.Remove(entity); + + editor->RecordEntity(archive, entity); + + editor->optionsWnd.RefreshEntityTree(); + }); + + float x = 60; + float y = 4; + float hei = 20; + float step = hei + 2; + float wid = 220; + + infoLabel.Create(""); + infoLabel.SetSize(XMFLOAT2(100, 50)); + infoLabel.SetText("This window will stay open even if you select other entities until it is collapsed, so you can select other bone entities."); + AddWidget(&infoLabel); + + lookatCheckBox.Create("LookAt: "); + lookatCheckBox.SetTooltip("Enable updating the lookAt direction. If enabled, head will turn to face the lookAt point.\nA sample lookAt point can be generated by the editor if you enable the Follow mouse option."); + lookatCheckBox.SetSize(XMFLOAT2(hei, hei)); + lookatCheckBox.OnClick([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + if (humanoid != nullptr) + { + humanoid->SetLookAtEnabled(args.bValue); + } + }); + AddWidget(&lookatCheckBox); + + lookatMouseCheckBox.Create("Follow mouse: "); + lookatMouseCheckBox.SetTooltip("Generates a sample lookAt point at the mouse position. If LookAt is enabled, the character's head will try to face in the direction of the mouse"); + lookatMouseCheckBox.SetSize(XMFLOAT2(hei, hei)); + AddWidget(&lookatMouseCheckBox); + lookatMouseCheckBox.SetCheck(true); + + headRotMaxXSlider.Create(0, 90, 60, 180, "Head horizontal: "); + headRotMaxXSlider.SetTooltip("Limit horizontal head movement (input in degrees)"); + headRotMaxXSlider.SetSize(XMFLOAT2(wid, hei)); + headRotMaxXSlider.OnSlide([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + if (humanoid != nullptr) + { + humanoid->head_rotation_max.x = wi::math::DegreesToRadians(args.fValue); + } + }); + AddWidget(&headRotMaxXSlider); + + headRotMaxYSlider.Create(0, 60, 30, 60, "Head vertical: "); + headRotMaxYSlider.SetTooltip("Limit vertical head movement (input in degrees)"); + headRotMaxYSlider.SetSize(XMFLOAT2(wid, hei)); + headRotMaxYSlider.OnSlide([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + if (humanoid != nullptr) + { + humanoid->head_rotation_max.y = wi::math::DegreesToRadians(args.fValue); + } + }); + AddWidget(&headRotMaxYSlider); + + headRotSpeedSlider.Create(0.05f, 1, 0.1f, 1000, "Head speed: "); + headRotSpeedSlider.SetTooltip("Adjust head turning speed."); + headRotSpeedSlider.SetSize(XMFLOAT2(wid, hei)); + headRotSpeedSlider.OnSlide([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + if (humanoid != nullptr) + { + humanoid->head_rotation_speed = args.fValue; + } + }); + AddWidget(&headRotSpeedSlider); + + + eyeRotMaxXSlider.Create(0, 40, 20, 40, "Eye horizontal: "); + eyeRotMaxXSlider.SetTooltip("Limit horizontal eye movement (input in degrees)"); + eyeRotMaxXSlider.SetSize(XMFLOAT2(wid, hei)); + eyeRotMaxXSlider.OnSlide([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + if (humanoid != nullptr) + { + humanoid->eye_rotation_max.x = wi::math::DegreesToRadians(args.fValue); + } + }); + AddWidget(&eyeRotMaxXSlider); + + eyeRotMaxYSlider.Create(0, 30, 15, 30, "Eye vertical: "); + eyeRotMaxYSlider.SetTooltip("Limit vertical eye movement (input in degrees)"); + eyeRotMaxYSlider.SetSize(XMFLOAT2(wid, hei)); + eyeRotMaxYSlider.OnSlide([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + if (humanoid != nullptr) + { + humanoid->eye_rotation_max.y = wi::math::DegreesToRadians(args.fValue); + } + }); + AddWidget(&eyeRotMaxYSlider); + + eyeRotSpeedSlider.Create(0.05f, 1, 0.2f, 1000, "Eye speed: "); + eyeRotSpeedSlider.SetTooltip("Adjust eye turning speed."); + eyeRotSpeedSlider.SetSize(XMFLOAT2(wid, hei)); + eyeRotSpeedSlider.OnSlide([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + if (humanoid != nullptr) + { + humanoid->eye_rotation_speed = args.fValue; + } + }); + AddWidget(&eyeRotSpeedSlider); + + headSizeSlider.Create(0.5f, 2, 1, 1000, "Head size: "); + headSizeSlider.SetTooltip("Adjust head size."); + headSizeSlider.SetSize(XMFLOAT2(wid, hei)); + headSizeSlider.OnSlide([=](wi::gui::EventArgs args) { + wi::scene::Scene& scene = editor->GetCurrentScene(); + HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + if (humanoid != nullptr) + { + Entity bone = humanoid->bones[size_t(HumanoidComponent::HumanoidBone::Head)]; + TransformComponent* transform = scene.transforms.GetComponent(bone); + if (transform != nullptr) + { + transform->SetDirty(); + transform->scale_local.x = args.fValue; + transform->scale_local.y = args.fValue; + transform->scale_local.z = args.fValue; + } + } + }); + AddWidget(&headSizeSlider); + + boneList.Create("Bones: "); + boneList.SetSize(XMFLOAT2(wid, 200)); + boneList.SetPos(XMFLOAT2(4, y += step)); + boneList.OnSelect([=](wi::gui::EventArgs args) { + + if (args.iValue < 0) + return; + + wi::Archive& archive = editor->AdvanceHistory(); + archive << EditorComponent::HISTORYOP_SELECTION; + // record PREVIOUS selection state... + editor->RecordSelection(archive); + + editor->translator.selected.clear(); + + for (int i = 0; i < boneList.GetItemCount(); ++i) + { + const wi::gui::TreeList::Item& item = boneList.GetItem(i); + if (item.selected) + { + wi::scene::PickResult pick; + pick.entity = (Entity)item.userdata; + if (pick.entity != INVALID_ENTITY) + { + editor->AddSelected(pick); + } + } + } + + // record NEW selection state... + editor->RecordSelection(archive); + + editor->optionsWnd.RefreshEntityTree(); + + }); + AddWidget(&boneList); + + + SetMinimized(true); + SetVisible(false); + + SetEntity(INVALID_ENTITY); +} + +void HumanoidWindow::SetEntity(Entity entity) +{ + if (this->entity == entity) + return; + + Scene& scene = editor->GetCurrentScene(); + + const HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + + if (humanoid != nullptr || IsCollapsed()) + { + this->entity = entity; + RefreshBoneList(); + + if (humanoid != nullptr) + { + lookatCheckBox.SetCheck(humanoid->IsLookAtEnabled()); + headRotMaxXSlider.SetValue(wi::math::RadiansToDegrees(humanoid->head_rotation_max.x)); + headRotMaxYSlider.SetValue(wi::math::RadiansToDegrees(humanoid->head_rotation_max.y)); + headRotSpeedSlider.SetValue(humanoid->head_rotation_speed); + eyeRotMaxXSlider.SetValue(wi::math::RadiansToDegrees(humanoid->eye_rotation_max.x)); + eyeRotMaxYSlider.SetValue(wi::math::RadiansToDegrees(humanoid->eye_rotation_max.y)); + eyeRotSpeedSlider.SetValue(humanoid->eye_rotation_speed); + + Entity bone = humanoid->bones[size_t(HumanoidComponent::HumanoidBone::Head)]; + const TransformComponent* transform = scene.transforms.GetComponent(bone); + if (transform != nullptr) + { + headSizeSlider.SetValue(transform->scale_local.x); + } + } + } +} +void HumanoidWindow::RefreshBoneList() +{ + Scene& scene = editor->GetCurrentScene(); + + const HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity); + + if (humanoid != nullptr) + { + boneList.ClearItems(); + for (int i = 0; i < arraysize(humanoid->bones); ++i) + { + HumanoidComponent::HumanoidBone type = (HumanoidComponent::HumanoidBone)i; + Entity bone = humanoid->bones[i]; + + wi::gui::TreeList::Item item; + item.userdata = bone; + item.level = 1; + + item.name += ICON_BONE " ["; + + switch (type) + { + case wi::scene::HumanoidComponent::HumanoidBone::Hips: + boneList.AddItem("Torso"); // grouping item + item.name += "Hips"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::Spine: + item.name += "Spine"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::Chest: + item.name += "Chest"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::UpperChest: + item.name += "UpperChest"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::Neck: + item.name += "Neck"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::Head: + boneList.AddItem("Head"); // grouping item + item.name += "Head"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftEye: + item.name += "LeftEye"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightEye: + item.name += "RightEye"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::Jaw: + item.name += "Jaw"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftUpperLeg: + boneList.AddItem("Legs"); // grouping item + item.name += "LeftUpperLeg"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftLowerLeg: + item.name += "LeftLowerLeg"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftFoot: + item.name += "LeftFoot"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftToes: + item.name += "LeftToes"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightUpperLeg: + item.name += "RightUpperLeg"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightLowerLeg: + item.name += "RightLowerLeg"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightFoot: + item.name += "RightFoot"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightToes: + item.name += "RightToes"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftShoulder: + boneList.AddItem("Arms"); // grouping item + item.name += "LeftShoulder"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftUpperArm: + item.name += "LeftUpperArm"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftLowerArm: + item.name += "LeftLowerArm"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftHand: + item.name += "LeftHand"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightShoulder: + item.name += "RightShoulder"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightUpperArm: + item.name += "RightUpperArm"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightLowerArm: + item.name += "RightLowerArm"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightHand: + item.name += "RightHand"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftThumbMetacarpal: + boneList.AddItem("Fingers"); // grouping item + item.name += "LeftThumbMetacarpal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftThumbProximal: + item.name += "LeftThumbProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftThumbDistal: + item.name += "LeftThumbDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftIndexProximal: + item.name += "LeftIndexProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftIndexIntermediate: + item.name += "LeftIndexIntermediate"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftIndexDistal: + item.name += "LeftIndexDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftMiddleProximal: + item.name += "LeftMiddleProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftMiddleIntermediate: + item.name += "LeftMiddleIntermediate"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftMiddleDistal: + item.name += "LeftMiddleDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftRingProximal: + item.name += "LeftRingProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftRingIntermediate: + item.name += "LeftRingIntermediate"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftRingDistal: + item.name += "LeftRingDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftLittleProximal: + item.name += "LeftLittleProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftLittleIntermediate: + item.name += "LeftLittleIntermediate"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::LeftLittleDistal: + item.name += "LeftLittleDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightThumbMetacarpal: + item.name += "RightThumbMetacarpal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightThumbProximal: + item.name += "RightThumbProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightThumbDistal: + item.name += "RightThumbDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightIndexIntermediate: + item.name += "RightIndexIntermediate"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightIndexDistal: + item.name += "RightIndexDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightIndexProximal: + item.name += "RightIndexProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightMiddleProximal: + item.name += "RightMiddleProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightMiddleIntermediate: + item.name += "RightMiddleIntermediate"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightMiddleDistal: + item.name += "RightMiddleDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightRingProximal: + item.name += "RightRingProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightRingIntermediate: + item.name += "RightRingIntermediate"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightRingDistal: + item.name += "RightRingDistal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightLittleProximal: + item.name += "RightLittleProximal"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightLittleIntermediate: + item.name += "RightLittleIntermediate"; + break; + case wi::scene::HumanoidComponent::HumanoidBone::RightLittleDistal: + item.name += "RightLittleDistal"; + break; + default: + assert(0); // unhandled type + break; + } + item.name += "] "; + + if (bone == INVALID_ENTITY) + { + item.name += ICON_DISABLED; + } + else + { + const NameComponent* name = scene.names.GetComponent(bone); + if (name == nullptr) + { + item.name += "[no_name] " + std::to_string(bone); + } + else if (name->name.empty()) + { + item.name += "[name_empty] " + std::to_string(bone); + } + else + { + item.name += name->name; + } + } + + boneList.AddItem(item); + } + } +} + +void HumanoidWindow::Update(const wi::Canvas& canvas, float dt) +{ + wi::gui::Window::Update(canvas, dt); + + if (lookatMouseCheckBox.GetCheck()) + { + Scene& scene = editor->GetCurrentScene(); + const CameraComponent& camera = editor->GetCurrentEditorScene().camera; + const wi::input::MouseState& mouse = wi::input::GetMouseState(); + wi::primitive::Ray ray = wi::renderer::GetPickRay((long)mouse.position.x, (long)mouse.position.y, canvas, camera); + + for (size_t i = 0; i < scene.humanoids.GetCount(); ++i) + { + HumanoidComponent& humanoid = scene.humanoids[i]; + + Entity bone = humanoid.bones[size_t(HumanoidComponent::HumanoidBone::Head)]; + const TransformComponent* transform = scene.transforms.GetComponent(bone); + if (transform != nullptr) + { + float dist = wi::math::Distance(transform->GetPosition(), ray.origin); + dist = std::min(1.0f, dist); + XMStoreFloat3(&humanoid.lookAt, camera.GetEye() + XMLoadFloat3(&ray.direction) * dist); // look at near plane position + } + } + } +} +void HumanoidWindow::ResizeLayout() +{ + wi::gui::Window::ResizeLayout(); + const float padding = 4; + const float width = GetWidgetAreaSize().x; + float y = padding; + float jump = 20; + + const float margin_left = 110; + const float margin_right = 45; + + auto add = [&](wi::gui::Widget& widget) { + if (!widget.IsVisible()) + return; + widget.SetPos(XMFLOAT2(margin_left, y)); + widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y)); + y += widget.GetSize().y; + y += padding; + }; + auto add_right = [&](wi::gui::Widget& widget) { + if (!widget.IsVisible()) + return; + widget.SetPos(XMFLOAT2(width - margin_right - widget.GetSize().x, y)); + y += widget.GetSize().y; + y += padding; + }; + auto add_fullwidth = [&](wi::gui::Widget& widget) { + if (!widget.IsVisible()) + return; + const float margin_left = padding; + const float margin_right = padding; + widget.SetPos(XMFLOAT2(margin_left, y)); + widget.SetSize(XMFLOAT2(width - margin_left - margin_right, widget.GetScale().y)); + y += widget.GetSize().y; + y += padding; + }; + + add_fullwidth(infoLabel); + add_right(lookatCheckBox); + lookatMouseCheckBox.SetPos(XMFLOAT2(lookatCheckBox.GetPos().x - 120, lookatCheckBox.GetPos().y)); + add(headRotMaxXSlider); + add(headRotMaxYSlider); + add(headRotSpeedSlider); + add(eyeRotMaxXSlider); + add(eyeRotMaxYSlider); + add(eyeRotSpeedSlider); + add(headSizeSlider); + + y += jump; + + add_fullwidth(boneList); + +} diff --git a/Editor/HumanoidWindow.h b/Editor/HumanoidWindow.h new file mode 100644 index 000000000..57402a5bd --- /dev/null +++ b/Editor/HumanoidWindow.h @@ -0,0 +1,31 @@ +#pragma once +#include "WickedEngine.h" + +class EditorComponent; + +class HumanoidWindow : public wi::gui::Window +{ +public: + void Create(EditorComponent* editor); + + EditorComponent* editor = nullptr; + wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY; + void SetEntity(wi::ecs::Entity entity); + void RefreshBoneList(); + + wi::gui::Label infoLabel; + wi::gui::CheckBox lookatMouseCheckBox; + wi::gui::CheckBox lookatCheckBox; + wi::gui::Slider headRotMaxXSlider; + wi::gui::Slider headRotMaxYSlider; + wi::gui::Slider headRotSpeedSlider; + wi::gui::Slider eyeRotMaxXSlider; + wi::gui::Slider eyeRotMaxYSlider; + wi::gui::Slider eyeRotSpeedSlider; + wi::gui::Slider headSizeSlider; + wi::gui::TreeList boneList; + + void Update(const wi::Canvas& canvas, float dt) override; + void ResizeLayout() override; +}; + diff --git a/Editor/IconDefinitions.h b/Editor/IconDefinitions.h index ae108ef27..2e9498330 100644 --- a/Editor/IconDefinitions.h +++ b/Editor/IconDefinitions.h @@ -31,7 +31,7 @@ #define ICON_SCRIPT ICON_FA_SCROLL #define ICON_HIERARCHY ICON_FA_ARROWS_DOWN_TO_PEOPLE #define ICON_EXPRESSION ICON_FA_MASKS_THEATER - +#define ICON_HUMANOID ICON_FA_PERSON_RAYS #define ICON_TERRAIN ICON_FA_MOUNTAIN_SUN #define ICON_SAVE ICON_FA_FLOPPY_DISK @@ -47,6 +47,7 @@ #define ICON_TRANSLATE ICON_FA_UP_DOWN_LEFT_RIGHT #define ICON_CHECK ICON_FA_CHECK #define ICON_DISABLED ICON_FA_BAN +#define ICON_BUG ICON_FA_BUG #define ICON_LEFT_RIGHT ICON_FA_LEFT_RIGHT #define ICON_UP_DOWN ICON_FA_UP_DOWN diff --git a/Editor/ModelImporter_GLTF.cpp b/Editor/ModelImporter_GLTF.cpp index c9e6779b8..ec400dcc8 100644 --- a/Editor/ModelImporter_GLTF.cpp +++ b/Editor/ModelImporter_GLTF.cpp @@ -1620,10 +1620,14 @@ void Import_Extension_VRM(LoaderState& state) if (ext_vrm->second.Has("blendShapeMaster")) { // https://github.com/vrm-c/vrm-specification/tree/master/specification/0.0#vrm-extension-morph-setting-jsonextensionsvrmblendshapemaster - Entity entity = CreateEntity(); + Entity entity = state.rootEntity; + if (state.scene->expressions.Contains(entity)) + { + entity = CreateEntity(); + state.scene->Component_Attach(entity, state.rootEntity); + state.scene->names.Create(entity) = "blendShapeMaster"; + } ExpressionComponent& component = state.scene->expressions.Create(entity); - state.scene->Component_Attach(entity, state.rootEntity); - state.scene->names.Create(entity) = state.name + "_blendShapeMaster"; const auto& blendShapeMaster = ext_vrm->second.Get("blendShapeMaster"); if (blendShapeMaster.Has("blendShapeGroups")) @@ -1883,6 +1887,305 @@ void Import_Extension_VRM(LoaderState& state) } } } + + if (ext_vrm->second.Has("humanoid")) + { + // https://github.com/vrm-c/vrm-specification/tree/master/specification/0.0#vrm-extension-models-bone-mapping-jsonextensionsvrmhumanoid + Entity entity = state.rootEntity; + if (state.scene->humanoids.Contains(entity)) + { + entity = CreateEntity(); + state.scene->Component_Attach(entity, state.rootEntity); + state.scene->names.Create(entity) = "humanoid"; + } + HumanoidComponent& component = state.scene->humanoids.Create(entity); + component.default_look_direction = XMFLOAT3(0, 0, -1); + + const auto& humanoid = ext_vrm->second.Get("humanoid"); + if (humanoid.Has("humanBones")) + { + const auto& humanBones = humanoid.Get("humanBones"); + + for (size_t bone_index = 0; bone_index < humanBones.ArrayLen(); ++bone_index) + { + const auto& humanBone = humanBones.Get(int(bone_index)); + Entity boneID = INVALID_ENTITY; + + // https://github.com/vrm-c/vrm-specification/tree/master/specification/0.0#defined-bones + if (humanBone.Has("node")) + { + const auto& value = humanBone.Get("node"); + int node = value.GetNumberAsInt(); + boneID = state.entityMap[node]; + } + if (humanBone.Has("bone")) + { + const auto& value = humanBone.Get("bone"); + const std::string& type = wi::helper::toUpper(value.Get()); + + if (!type.compare("NECK")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Neck)] = boneID; + } + else if (!type.compare("HEAD")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Head)] = boneID; + } + else if (!type.compare("LEFTEYE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftEye)] = boneID; + } + else if (!type.compare("RIGHTEYE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightEye)] = boneID; + } + else if (!type.compare("JAW")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Jaw)] = boneID; + } + else if (!type.compare("HIPS")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Hips)] = boneID; + } + else if (!type.compare("SPINE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Spine)] = boneID; + } + else if (!type.compare("CHEST")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Chest)] = boneID; + } + else if (!type.compare("UPPERCHEST")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::UpperChest)] = boneID; + } + else if (!type.compare("LEFTSHOULDER")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftShoulder)] = boneID; + } + else if (!type.compare("RIGHTSHOULDER")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightShoulder)] = boneID; + } + else if (!type.compare("LEFTUPPERARM")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftUpperArm)] = boneID; + } + else if (!type.compare("RIGHTUPPERARM")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightUpperArm)] = boneID; + } + else if (!type.compare("LEFTLOWERARM")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLowerArm)] = boneID; + } + else if (!type.compare("RIGHTLOWERARM")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLowerArm)] = boneID; + } + else if (!type.compare("LEFTHAND")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftHand)] = boneID; + } + else if (!type.compare("RIGHTHAND")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightHand)] = boneID; + } + else if (!type.compare("LEFTUPPERLEG")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftUpperLeg)] = boneID; + } + else if (!type.compare("RIGHTUPPERLEG")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightUpperLeg)] = boneID; + } + else if (!type.compare("LEFTLOWERLEG")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLowerLeg)] = boneID; + } + else if (!type.compare("RIGHTLOWERLEG")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLowerLeg)] = boneID; + } + else if (!type.compare("LEFTFOOT")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftFoot)] = boneID; + } + else if (!type.compare("RIGHTFOOT")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightFoot)] = boneID; + } + else if (!type.compare("LEFTTOE")) // here it is Toe, not Toes + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftToes)] = boneID; + } + else if (!type.compare("RIGHTTOE")) // here it is Toe, not Toes + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightToes)] = boneID; + } + else if (!type.compare("LEFTTHUMBPROXIMAL")) + { + // VRM 0.0 thumb proximal = VRM 1.0 thumb metacarpal + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbMetacarpal)] = boneID; + } + else if (!type.compare("RIGHTTHUMBPROXIMAL")) + { + // VRM 0.0 thumb proximal = VRM 1.0 thumb metacarpal + component.bones[size_t(HumanoidComponent::HumanoidBone::RightThumbMetacarpal)] = boneID; + } + else if (!type.compare("LEFTTHUMBINTERMEDIATE")) + { + // VRM 0.0 thumb intermediate = VRM 1.0 thumb proximal + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbProximal)] = boneID; + } + else if (!type.compare("RIGHTTHUMBINTERMEDIATE")) + { + // VRM 0.0 thumb intermediate = VRM 1.0 thumb proximal + component.bones[size_t(HumanoidComponent::HumanoidBone::RightThumbProximal)] = boneID; + } + else if (!type.compare("LEFTTHUMBDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbDistal)] = boneID; + } + else if (!type.compare("RIGHTTHUMBDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightThumbDistal)] = boneID; + } + else if (!type.compare("LEFTINDEXPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexProximal)] = boneID; + } + else if (!type.compare("RIGHTINDEXPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightIndexProximal)] = boneID; + } + else if (!type.compare("LEFTINDEXINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexIntermediate)] = boneID; + } + else if (!type.compare("RIGHTINDEXINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightIndexIntermediate)] = boneID; + } + else if (!type.compare("LEFTINDEXDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexDistal)] = boneID; + } + else if (!type.compare("RIGHTINDEXDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightIndexDistal)] = boneID; + } + else if (!type.compare("LEFTMIDDLEPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleProximal)] = boneID; + } + else if (!type.compare("RIGHTMIDDLEPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleProximal)] = boneID; + } + else if (!type.compare("LEFTMIDDLEINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleIntermediate)] = boneID; + } + else if (!type.compare("RIGHTMIDDLEINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleIntermediate)] = boneID; + } + else if (!type.compare("LEFTMIDDLEDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleDistal)] = boneID; + } + else if (!type.compare("RIGHTMIDDLEDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleDistal)] = boneID; + } + else if (!type.compare("LEFTRINGPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftRingProximal)] = boneID; + } + else if (!type.compare("RIGHTRINGPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightRingProximal)] = boneID; + } + else if (!type.compare("LEFTRINGINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftRingIntermediate)] = boneID; + } + else if (!type.compare("RIGHTRINGINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightRingIntermediate)] = boneID; + } + else if (!type.compare("LEFTRINGDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftRingDistal)] = boneID; + } + else if (!type.compare("RIGHTRINGDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightRingDistal)] = boneID; + } + else if (!type.compare("LEFTLITTLEPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleProximal)] = boneID; + } + else if (!type.compare("RIGHTLITTLEPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLittleProximal)] = boneID; + } + else if (!type.compare("LEFTLITTLEINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleIntermediate)] = boneID; + } + else if (!type.compare("RIGHTLITTLEINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLittleIntermediate)] = boneID; + } + else if (!type.compare("LEFTLITTLEDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleDistal)] = boneID; + } + else if (!type.compare("RIGHTLITTLEDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLittleDistal)] = boneID; + } + } + } + } + } + +#if 0 // todo toon shading parameters + if (ext_vrm->second.Has("materialProperties")) + { + const auto& materialProperties = ext_vrm->second.Get("materialProperties"); + + for (size_t material_index = 0; material_index < materialProperties.ArrayLen(); ++material_index) + { + const auto& material = materialProperties.Get(int(material_index)); + MaterialComponent* component = nullptr; + + if (material.Has("name")) + { + const auto& value = material.Get("name"); + const std::string& name = value.Get(); + Entity entity = state.scene->Entity_FindByName(name); + component = state.scene->materials.GetComponent(entity); + } + + if (component != nullptr) + { + if (material.Has("shader")) + { + const auto& value = material.Get("shader"); + const std::string& name = wi::helper::toUpper(value.Get()); + if (!name.compare("VRM/MTOON")) + { + component->shaderType = MaterialComponent::SHADERTYPE_CARTOON; + } + } + } + } + } +#endif + } } @@ -1895,10 +2198,14 @@ void Import_Extension_VRMC(LoaderState& state) if (ext_vrm->second.Has("expressions")) { // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/expressions.md#vrmc_vrmexpressions - Entity entity = CreateEntity(); + Entity entity = state.rootEntity; + if (state.scene->expressions.Contains(entity)) + { + entity = CreateEntity(); + state.scene->Component_Attach(entity, state.rootEntity); + state.scene->names.Create(entity) = "expressions"; + } ExpressionComponent& component = state.scene->expressions.Create(entity); - state.scene->Component_Attach(entity, state.rootEntity); - state.scene->names.Create(entity) = state.name + "_expressions"; const auto& expressions = ext_vrm->second.Get("expressions"); static const char* expression_types[] = { @@ -2058,6 +2365,263 @@ void Import_Extension_VRMC(LoaderState& state) } } } + + if (ext_vrm->second.Has("humanoid")) + { + // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/humanoid.md + Entity entity = state.rootEntity; + if (state.scene->humanoids.Contains(entity)) + { + entity = CreateEntity(); + state.scene->Component_Attach(entity, state.rootEntity); + state.scene->names.Create(entity) = "humanoid"; + } + HumanoidComponent& component = state.scene->humanoids.Create(entity); + + const auto& humanoid = ext_vrm->second.Get("humanoid"); + if (humanoid.Has("humanBones")) + { + const auto& humanBones = humanoid.Get("humanBones"); + + const auto& keys = humanBones.Keys(); + for (const auto& key : keys) + { + Entity boneID = INVALID_ENTITY; + + const auto& value = humanBones.Get(key); + if (value.Has("node")) + { + const auto& node_value = value.Get("node"); + int node = node_value.GetNumberAsInt(); + boneID = state.entityMap[node]; + } + + const std::string& type = wi::helper::toUpper(key); + if (!type.compare("NECK")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Neck)] = boneID; + } + else if (!type.compare("HEAD")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Head)] = boneID; + } + else if (!type.compare("LEFTEYE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftEye)] = boneID; + } + else if (!type.compare("RIGHTEYE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightEye)] = boneID; + } + else if (!type.compare("JAW")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Jaw)] = boneID; + } + else if (!type.compare("HIPS")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Hips)] = boneID; + } + else if (!type.compare("SPINE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Spine)] = boneID; + } + else if (!type.compare("CHEST")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::Chest)] = boneID; + } + else if (!type.compare("UPPERCHEST")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::UpperChest)] = boneID; + } + else if (!type.compare("LEFTSHOULDER")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftShoulder)] = boneID; + } + else if (!type.compare("RIGHTSHOULDER")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightShoulder)] = boneID; + } + else if (!type.compare("LEFTUPPERARM")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftUpperArm)] = boneID; + } + else if (!type.compare("RIGHTUPPERARM")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightUpperArm)] = boneID; + } + else if (!type.compare("LEFTLOWERARM")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLowerArm)] = boneID; + } + else if (!type.compare("RIGHTLOWERARM")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLowerArm)] = boneID; + } + else if (!type.compare("LEFTHAND")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftHand)] = boneID; + } + else if (!type.compare("RIGHTHAND")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightHand)] = boneID; + } + else if (!type.compare("LEFTUPPERLEG")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftUpperLeg)] = boneID; + } + else if (!type.compare("RIGHTUPPERLEG")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightUpperLeg)] = boneID; + } + else if (!type.compare("LEFTLOWERLEG")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLowerLeg)] = boneID; + } + else if (!type.compare("RIGHTLOWERLEG")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLowerLeg)] = boneID; + } + else if (!type.compare("LEFTFOOT")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftFoot)] = boneID; + } + else if (!type.compare("RIGHTFOOT")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightFoot)] = boneID; + } + else if (!type.compare("LEFTTOES")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftToes)] = boneID; + } + else if (!type.compare("RIGHTTOES")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightToes)] = boneID; + } + else if (!type.compare("LEFTTHUMBPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbProximal)] = boneID; + } + else if (!type.compare("RIGHTTHUMBPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightThumbProximal)] = boneID; + } + else if (!type.compare("LEFTTHUMBINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbMetacarpal)] = boneID; + } + else if (!type.compare("RIGHTTHUMBINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightThumbMetacarpal)] = boneID; + } + else if (!type.compare("LEFTTHUMBDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbDistal)] = boneID; + } + else if (!type.compare("RIGHTTHUMBDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightThumbDistal)] = boneID; + } + else if (!type.compare("LEFTINDEXPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexProximal)] = boneID; + } + else if (!type.compare("RIGHTINDEXPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightIndexProximal)] = boneID; + } + else if (!type.compare("LEFTINDEXINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexIntermediate)] = boneID; + } + else if (!type.compare("RIGHTINDEXINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightIndexIntermediate)] = boneID; + } + else if (!type.compare("LEFTINDEXDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexDistal)] = boneID; + } + else if (!type.compare("RIGHTINDEXDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightIndexDistal)] = boneID; + } + else if (!type.compare("LEFTMIDDLEPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleProximal)] = boneID; + } + else if (!type.compare("RIGHTMIDDLEPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleProximal)] = boneID; + } + else if (!type.compare("LEFTMIDDLEINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleIntermediate)] = boneID; + } + else if (!type.compare("RIGHTMIDDLEINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleIntermediate)] = boneID; + } + else if (!type.compare("LEFTMIDDLEDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleDistal)] = boneID; + } + else if (!type.compare("RIGHTMIDDLEDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleDistal)] = boneID; + } + else if (!type.compare("LEFTRINGPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftRingProximal)] = boneID; + } + else if (!type.compare("RIGHTRINGPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightRingProximal)] = boneID; + } + else if (!type.compare("LEFTRINGINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftRingIntermediate)] = boneID; + } + else if (!type.compare("RIGHTRINGINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightRingIntermediate)] = boneID; + } + else if (!type.compare("LEFTRINGDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftRingDistal)] = boneID; + } + else if (!type.compare("RIGHTRINGDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightRingDistal)] = boneID; + } + else if (!type.compare("LEFTLITTLEPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleProximal)] = boneID; + } + else if (!type.compare("RIGHTLITTLEPROXIMAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLittleProximal)] = boneID; + } + else if (!type.compare("LEFTLITTLEINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleIntermediate)] = boneID; + } + else if (!type.compare("RIGHTLITTLEINTERMEDIATE")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLittleIntermediate)] = boneID; + } + else if (!type.compare("LEFTLITTLEDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleDistal)] = boneID; + } + else if (!type.compare("RIGHTLITTLEDISTAL")) + { + component.bones[size_t(HumanoidComponent::HumanoidBone::RightLittleDistal)] = boneID; + } + + } + } + + } } auto ext_vrmc_springbone = state.gltfModel.extensions.find("VRMC_springBone"); diff --git a/Editor/OptionsWindow.cpp b/Editor/OptionsWindow.cpp index c98b898e3..38aab7462 100644 --- a/Editor/OptionsWindow.cpp +++ b/Editor/OptionsWindow.cpp @@ -325,6 +325,7 @@ void OptionsWindow::Create(EditorComponent* _editor) filterCombo.AddItem("Collider " ICON_COLLIDER, (uint64_t)Filter::Collider); filterCombo.AddItem("Script " ICON_SCRIPT, (uint64_t)Filter::Script); filterCombo.AddItem("Expression " ICON_EXPRESSION, (uint64_t)Filter::Expression); + filterCombo.AddItem("Humanoid " ICON_HUMANOID, (uint64_t)Filter::Humanoid); filterCombo.AddItem("Terrain " ICON_TERRAIN, (uint64_t)Filter::Terrain); filterCombo.SetTooltip("Apply filtering to the Entities"); filterCombo.OnSelect([&](wi::gui::EventArgs args) { @@ -555,6 +556,18 @@ void OptionsWindow::Create(EditorComponent* _editor) editor->componentsWnd.weatherWnd.default_sky_horizon = dark_point; editor->componentsWnd.weatherWnd.default_sky_zenith = theme_color_idle; + if ((Theme)args.userdata == Theme::Bright) + { + editor->inactiveEntityColor = theme_color_focus; + editor->hoveredEntityColor = theme_color_focus; + } + else + { + editor->inactiveEntityColor = theme.font.color; + editor->hoveredEntityColor = theme.font.color; + } + editor->inactiveEntityColor.setA(150); + }); AddWidget(&themeCombo); @@ -727,6 +740,10 @@ void OptionsWindow::PushToEntityTree(wi::ecs::Entity entity, int level) { item.name += ICON_ARMATURE " "; } + if (scene.humanoids.Contains(entity)) + { + item.name += ICON_HUMANOID " "; + } if (scene.lights.Contains(entity)) { const LightComponent* light = scene.lights.GetComponent(entity); @@ -1027,6 +1044,14 @@ void OptionsWindow::RefreshEntityTree() } } + if (has_flag(filter, Filter::Humanoid)) + { + for (size_t i = 0; i < scene.humanoids.GetCount(); ++i) + { + PushToEntityTree(scene.humanoids.GetEntity(i), 0); + } + } + if (has_flag(filter, Filter::Expression)) { for (size_t i = 0; i < scene.expressions.GetCount(); ++i) @@ -1035,6 +1060,14 @@ void OptionsWindow::RefreshEntityTree() } } + if (has_flag(filter, Filter::Terrain)) + { + for (size_t i = 0; i < scene.terrains.GetCount(); ++i) + { + PushToEntityTree(scene.terrains.GetEntity(i), 0); + } + } + entitytree_added_items.clear(); entitytree_opened_items.clear(); } diff --git a/Editor/OptionsWindow.h b/Editor/OptionsWindow.h index c38ee6b41..858f4c0ce 100644 --- a/Editor/OptionsWindow.h +++ b/Editor/OptionsWindow.h @@ -56,6 +56,7 @@ public: Expression = 1 << 18, Terrain = 1 << 19, Spring = 1 << 20, + Humanoid = 1 << 21, All = ~0ull, } filter = Filter::All; diff --git a/WickedEngine/wiHelper.cpp b/WickedEngine/wiHelper.cpp index a19bd7531..6545cc0b2 100644 --- a/WickedEngine/wiHelper.cpp +++ b/WickedEngine/wiHelper.cpp @@ -1196,4 +1196,23 @@ namespace wi::helper ms = time_span.count(); } } + + void OpenUrl(const std::string& url) + { +#ifdef PLATFORM_WINDOWS_DESKTOP + std::string op = "start " + url; + int status = system(op.c_str()); + wi::backlog::post("wi::helper::OpenUrl(" + url + ") returned status: " + std::to_string(status)); + return; +#endif // PLATFORM_WINDOWS_DESKTOP + +#ifdef PLATFORM_LINUX + std::string op = "xdg-open " + url; + int status = system(op.c_str()); + wi::backlog::post("wi::helper::OpenUrl(" + url + ") returned status: " + std::to_string(status)); + return; +#endif // PLATFORM_WINDOWS_DESKTOP + + wi::backlog::post("wi::helper::OpenUrl(" + url + "): not implemented for this operating system!", wi::backlog::LogLevel::Warning); + } } diff --git a/WickedEngine/wiHelper.h b/WickedEngine/wiHelper.h index b865aae56..813502339 100644 --- a/WickedEngine/wiHelper.h +++ b/WickedEngine/wiHelper.h @@ -131,4 +131,7 @@ namespace wi::helper // Spins for the given time and does nothing (OS can not overtake) void Spin(float milliseconds); + + // Opens URL in the default browser + void OpenUrl(const std::string& url); }; diff --git a/WickedEngine/wiMath.cpp b/WickedEngine/wiMath.cpp index f3b1cae1c..8f50f2623 100644 --- a/WickedEngine/wiMath.cpp +++ b/WickedEngine/wiMath.cpp @@ -104,15 +104,18 @@ namespace wi::math } return angle; } - float GetAngle(const XMFLOAT3& a, const XMFLOAT3& b, const XMFLOAT3& axis) + float GetAngle(const XMFLOAT3& a, const XMFLOAT3& b, const XMFLOAT3& axis, float max) { XMVECTOR A = XMLoadFloat3(&a); XMVECTOR B = XMLoadFloat3(&b); - float dp = XMVectorGetX(XMVector3Dot(A, B)); - dp = wi::math::Clamp(dp, -1, 1); - float angle = std::acos(dp); - XMVECTOR CROSS = XMVector3Cross(A, B); - if (XMVectorGetX(XMVector3Dot(CROSS, XMLoadFloat3(&axis))) < 0) + XMVECTOR AXIS = XMLoadFloat3(&axis); + return GetAngle(A, B, AXIS, max); + } + float GetAngle(XMVECTOR A, XMVECTOR B, XMVECTOR AXIS, float max) + { + float angle = XMVectorGetX(XMVector3AngleBetweenVectors(A, B)); + angle = std::min(angle, max); + if (XMVectorGetX(XMVector3Dot(XMVector3Cross(A, B), AXIS)) < 0) { angle = XM_2PI - angle; } diff --git a/WickedEngine/wiMath.h b/WickedEngine/wiMath.h index 2ac6577fe..f1e6c4249 100644 --- a/WickedEngine/wiMath.h +++ b/WickedEngine/wiMath.h @@ -261,8 +261,12 @@ namespace wi::math return XMVectorGetX(XMVector3Dot(planeNormal, point - planeOrigin)); } + constexpr float RadiansToDegrees(float radians) { return radians / XM_PI * 180.0f; } + constexpr float DegreesToRadians(float degrees) { return degrees / 180.0f * XM_PI; } + float GetAngle(const XMFLOAT2& a, const XMFLOAT2& b); - float GetAngle(const XMFLOAT3& a, const XMFLOAT3& b, const XMFLOAT3& axis); + float GetAngle(const XMFLOAT3& a, const XMFLOAT3& b, const XMFLOAT3& axis, float max = XM_2PI); + float GetAngle(XMVECTOR A, XMVECTOR B, XMVECTOR axis, float max = XM_2PI); void ConstructTriangleEquilateral(float radius, XMFLOAT4& A, XMFLOAT4& B, XMFLOAT4& C); void GetBarycentric(const XMVECTOR& p, const XMVECTOR& a, const XMVECTOR& b, const XMVECTOR& c, float &u, float &v, float &w, bool clamp = false); diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index af1b5d533..b3450d207 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -232,11 +232,7 @@ namespace wi::scene wi::jobsystem::Wait(ctx); // dependencies - RunInverseKinematicsUpdateSystem(ctx); - - RunColliderUpdateSystem(ctx); - - RunSpringUpdateSystem(ctx); + RunProceduralAnimationUpdateSystem(ctx); RunArmatureUpdateSystem(ctx); @@ -1970,7 +1966,7 @@ namespace wi::scene if (expression_mastering.blink_timer >= 1 + all_blink_length) { expression.weight = 0; - expression_mastering.blink_timer = 0; + expression_mastering.blink_timer = -wi::random::GetRandom(0.0f, 1.0f); } expression.SetDirty(); } @@ -2017,7 +2013,7 @@ namespace wi::scene } if (expression_mastering.look_timer >= 1 + expression_mastering.look_length * expression_mastering.look_frequency) { - expression_mastering.look_timer = 0; + expression_mastering.look_timer = -wi::random::GetRandom(0.0f, 1.0f); } } @@ -2138,8 +2134,246 @@ namespace wi::scene } } } - void Scene::RunColliderUpdateSystem(wi::jobsystem::context& ctx) + void Scene::RunProceduralAnimationUpdateSystem(wi::jobsystem::context& ctx) { + if (dt <= 0) + return; + + if (inverse_kinematics.GetCount() > 0 || humanoids.GetCount() > 0) + { + transforms_temp.resize(transforms.GetCount()); + transforms_temp = transforms.GetComponentArray(); // make copy + } + + bool recompute_hierarchy = false; + for (size_t i = 0; i < inverse_kinematics.GetCount(); ++i) + { + const InverseKinematicsComponent& ik = inverse_kinematics[i]; + if (ik.IsDisabled()) + { + continue; + } + Entity entity = inverse_kinematics.GetEntity(i); + size_t transform_index = transforms.GetIndex(entity); + size_t target_index = transforms.GetIndex(ik.target); + const HierarchyComponent* hier = hierarchy.GetComponent(entity); + if (transform_index == ~0ull || target_index == ~0ull || hier == nullptr) + { + continue; + } + TransformComponent& transform = transforms_temp[transform_index]; + TransformComponent& target = transforms_temp[target_index]; + + 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; + 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(**) + + // stack stores all traversed chain links so far: + stack[chain] = child_transform; + + // Compute required parent rotation that moves ik transform closer to target transform: + size_t parent_index = transforms.GetIndex(parent_entity); + if (parent_index == ~0ull) + continue; + 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(); + // rotate parent: + 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) + { + Entity parent_of_parent_entity = hier_parent->parentID; + size_t parent_of_parent_index = transforms.GetIndex(parent_of_parent_entity); + if (parent_of_parent_index != ~0ull) + { + 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); + // 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; + for (int recurse_chain = (int)chain; recurse_chain >= 0; --recurse_chain) + { + stack[recurse_chain]->UpdateTransform_Parented(*recurse_parent); + recurse_parent = stack[recurse_chain]; + } + + if (hier_parent == nullptr) + { + // chain root reached, exit + break; + } + + // move up in the chain by one: + 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 + + } + } + } + + for (size_t i = 0; i < humanoids.GetCount(); ++i) + { + HumanoidComponent& humanoid = humanoids[i]; + + struct LookAtSource + { + HumanoidComponent::HumanoidBone type; + XMFLOAT2* rotation_max; + float* rotation_speed; + XMFLOAT4* lookAtDeltaRotationState; + }; + LookAtSource sources[] = { + { HumanoidComponent::HumanoidBone::Head, &humanoid.head_rotation_max, &humanoid.head_rotation_speed, &humanoid.lookAtDeltaRotationState_Head }, + { HumanoidComponent::HumanoidBone::LeftEye, &humanoid.eye_rotation_max, &humanoid.eye_rotation_speed, &humanoid.lookAtDeltaRotationState_LeftEye }, + { HumanoidComponent::HumanoidBone::RightEye, &humanoid.eye_rotation_max, &humanoid.eye_rotation_speed, &humanoid.lookAtDeltaRotationState_RightEye }, + }; + for (auto& source : sources) + { + Entity bone = humanoid.bones[size_t(source.type)]; + size_t boneIndex = transforms.GetIndex(bone); + + if (boneIndex < transforms_temp.size()) + { + recompute_hierarchy = true; + TransformComponent& transform = transforms_temp[boneIndex]; + XMVECTOR Q = XMQuaternionIdentity(); + + if (humanoid.IsLookAtEnabled()) + { + const HierarchyComponent* hier = hierarchy.GetComponent(bone); + size_t parent_index = hier == nullptr ? ~0ull : transforms.GetIndex(hier->parentID); + if (parent_index != ~0ull) + { + const TransformComponent& parent_transform = transforms_temp[parent_index]; + transform.UpdateTransform_Parented(parent_transform); + } + + XMVECTOR P = transform.GetPositionV(); + XMMATRIX W = XMLoadFloat4x4(&transform.world); + XMMATRIX InverseW = XMMatrixInverse(nullptr, W); + XMVECTOR FORWARD = XMLoadFloat3(&humanoid.default_look_direction); + XMVECTOR UP = XMVectorSet(0, 1, 0, 0); + XMVECTOR SIDE = XMVectorSet(1, 0, 0, 0); + XMVECTOR TARGET = XMVector3TransformNormal(XMVector3Normalize(XMLoadFloat3(&humanoid.lookAt) - P), InverseW); + XMVECTOR TARGET_HORIZONTAL = XMVector3Normalize(XMVectorSetY(TARGET, 0)); + XMVECTOR TARGET_VERTICAL = XMVector3Normalize(XMVectorSetX(TARGET, 0) + FORWARD); + + const float angle_horizontal = wi::math::GetAngle(FORWARD, TARGET_HORIZONTAL, UP, source.rotation_max->x); + const float angle_vertical = wi::math::GetAngle(FORWARD, TARGET_VERTICAL, SIDE, source.rotation_max->y); + + Q = XMQuaternionNormalize(XMQuaternionRotationRollPitchYaw(angle_vertical, angle_horizontal, 0)); + } + + Q = XMQuaternionSlerp(XMLoadFloat4(source.lookAtDeltaRotationState), Q, *source.rotation_speed); + Q = XMQuaternionNormalize(Q); + XMStoreFloat4(source.lookAtDeltaRotationState, Q); + + // Local space and world space updated separately: + transform.Rotate(Q); // local space for having hierarchy recompute at the end + XMMATRIX W = XMLoadFloat4x4(&transform.world); + W = XMMatrixRotationQuaternion(Q) * W; + XMStoreFloat4x4(&transform.world, W); // world space to have immediate feedback from parent to child (head -> eyes) + +#if 0 + wi::renderer::RenderableLine line; + line.color_start = XMFLOAT4(0, 0, 1, 1); + line.color_end = XMFLOAT4(0, 1, 0, 1); + XMVECTOR E = P + FORWARD; + XMStoreFloat3(&line.start, P); + XMStoreFloat3(&line.end, E); + wi::renderer::DrawLine(line); + + line.color_end = XMFLOAT4(1, 0, 0, 1); + E = P + TARGET; + XMStoreFloat3(&line.end, E); + wi::renderer::DrawLine(line); + + line.color_start = line.color_end = XMFLOAT4(1, 0, 1, 1); + E = P + UP; + XMStoreFloat3(&line.end, E); + wi::renderer::DrawLine(line); + + line.color_start = line.color_end = XMFLOAT4(1, 1, 0, 1); + E = P + SIDE; + XMStoreFloat3(&line.end, E); + wi::renderer::DrawLine(line); + + std::string text = "angle_horizontal = " + std::to_string(angle_horizontal); + text += "\nangle_vertical = " + std::to_string(angle_vertical); + wi::renderer::DebugTextParams textparams; + textparams.flags |= wi::renderer::DebugTextParams::CAMERA_FACING; + textparams.flags |= wi::renderer::DebugTextParams::CAMERA_SCALING; + textparams.position = humanoid.lookAt; + textparams.scaling = 0.8f; + wi::renderer::DrawDebugText(text.c_str(), textparams); +#endif + } + } + } + + if (recompute_hierarchy) + { + wi::jobsystem::Dispatch(ctx, (uint32_t)hierarchy.GetCount(), small_subtask_groupsize, [&](wi::jobsystem::JobArgs args) { + + HierarchyComponent& hier = hierarchy[args.jobIndex]; + Entity entity = hierarchy.GetEntity(args.jobIndex); + size_t child_index = transforms.GetIndex(entity); + if (child_index == ~0ull) + return; + + TransformComponent& transform_child = transforms_temp[child_index]; + XMMATRIX worldmatrix = transform_child.GetLocalMatrix(); + + Entity parentID = hier.parentID; + while (parentID != INVALID_ENTITY) + { + size_t parent_index = transforms.GetIndex(parentID); + if (parent_index == ~0ull) + break; + TransformComponent& transform_parent = transforms_temp[parent_index]; + worldmatrix *= transform_parent.GetLocalMatrix(); + + const HierarchyComponent* hier_recursive = hierarchy.GetComponent(parentID); + if (hier_recursive != nullptr) + { + parentID = hier_recursive->parentID; + } + else + { + parentID = INVALID_ENTITY; + } + } + + // Now the real (not temp) transform world matrix is updated: + XMStoreFloat4x4(&transforms[child_index].world, worldmatrix); + + }); + + wi::jobsystem::Wait(ctx); + } + + // Colliders: aabb_colliders_cpu.clear(); colliders_cpu.clear(); colliders_gpu.clear(); @@ -2181,23 +2415,23 @@ namespace wi::scene aabb = collider.capsule.getAABB(); break; case ColliderComponent::Shape::Plane: - { - collider.plane.origin = collider.sphere.center; - XMVECTOR N = XMVectorSet(0, 1, 0, 0); - N = XMVector3Normalize(XMVector3TransformNormal(N, W)); - XMStoreFloat3(&collider.plane.normal, N); + { + collider.plane.origin = collider.sphere.center; + XMVECTOR N = XMVectorSet(0, 1, 0, 0); + N = XMVector3Normalize(XMVector3TransformNormal(N, W)); + XMStoreFloat3(&collider.plane.normal, N); - aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(1, 1, 1)); + aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(1, 1, 1)); - XMMATRIX PLANE = XMMatrixScaling(collider.radius, 1, collider.radius); - PLANE = PLANE * XMMatrixTranslationFromVector(XMLoadFloat3(&collider.offset)); - PLANE = PLANE * W; - aabb = aabb.transform(PLANE); + XMMATRIX PLANE = XMMatrixScaling(collider.radius, 1, collider.radius); + PLANE = PLANE * XMMatrixTranslationFromVector(XMLoadFloat3(&collider.offset)); + PLANE = PLANE * W; + aabb = aabb.transform(PLANE); - PLANE = XMMatrixInverse(nullptr, PLANE); - XMStoreFloat4x4(&collider.plane.projection, PLANE); - } - break; + PLANE = XMMatrixInverse(nullptr, PLANE); + XMStoreFloat4x4(&collider.plane.projection, PLANE); + } + break; } const LayerComponent* layer = layers.GetComponent(entity); @@ -2216,11 +2450,8 @@ namespace wi::scene colliders_gpu.push_back(collider); } } - } - void Scene::RunSpringUpdateSystem(wi::jobsystem::context& ctx) - { - if (dt <= 0) - return; + + // Springs: static float time = 0; time += dt; const XMVECTOR windDir = XMLoadFloat3(&weather.windDirection); @@ -2344,14 +2575,17 @@ namespace wi::scene // apply scaling to radius: XMFLOAT3 scale = transform.GetScale(); const float hitRadius = spring.hitRadius * std::max(scale.x, std::max(scale.y, scale.z)); + wi::primitive::Sphere tail_sphere; + XMStoreFloat3(&tail_sphere.center, tail_next); + tail_sphere.radius = hitRadius; for (size_t collider_index = 0; collider_index < colliders_cpu.size(); ++collider_index) { + if (!aabb_colliders_cpu[collider_index].intersects(tail_sphere)) + continue; + const ColliderComponent& collider = colliders_cpu[collider_index]; - wi::primitive::Sphere tail_sphere; - XMStoreFloat3(&tail_sphere.center, tail_next); // tail_sphere center can change within loop! - tail_sphere.radius = hitRadius; float dist = 0; XMFLOAT3 direction = {}; switch (collider.shape) @@ -2378,6 +2612,9 @@ namespace wi::scene // Limit offset to keep distance from parent: tail_next = position_root + to_tail * boneLength; } + + XMStoreFloat3(&tail_sphere.center, tail_next); + tail_sphere.radius = hitRadius; } } #endif @@ -2397,131 +2634,6 @@ namespace wi::scene } } - void Scene::RunInverseKinematicsUpdateSystem(wi::jobsystem::context& ctx) - { - if (inverse_kinematics.GetCount() > 0) - { - transforms_temp.resize(transforms.GetCount()); - transforms_temp = transforms.GetComponentArray(); // make copy - } - - bool recompute_hierarchy = false; - for (size_t i = 0; i < inverse_kinematics.GetCount(); ++i) - { - const InverseKinematicsComponent& ik = inverse_kinematics[i]; - if (ik.IsDisabled()) - { - continue; - } - Entity entity = inverse_kinematics.GetEntity(i); - size_t transform_index = transforms.GetIndex(entity); - size_t target_index = transforms.GetIndex(ik.target); - const HierarchyComponent* hier = hierarchy.GetComponent(entity); - if (transform_index == ~0ull || target_index == ~0ull || hier == nullptr) - { - continue; - } - TransformComponent& transform = transforms_temp[transform_index]; - TransformComponent& target = transforms_temp[target_index]; - - 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; - 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(**) - - // stack stores all traversed chain links so far: - stack[chain] = child_transform; - - // Compute required parent rotation that moves ik transform closer to target transform: - size_t parent_index = transforms.GetIndex(parent_entity); - if (parent_index == ~0ull) - continue; - 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(); - // rotate parent: - 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) - { - Entity parent_of_parent_entity = hier_parent->parentID; - size_t parent_of_parent_index = transforms.GetIndex(parent_of_parent_entity); - if (parent_of_parent_index != ~0ull) - { - 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); - // 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; - for (int recurse_chain = (int)chain; recurse_chain >= 0; --recurse_chain) - { - stack[recurse_chain]->UpdateTransform_Parented(*recurse_parent); - recurse_parent = stack[recurse_chain]; - } - - if (hier_parent == nullptr) - { - // chain root reached, exit - break; - } - - // move up in the chain by one: - 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 - - } - } - } - - if (recompute_hierarchy) - { - // (**)If there was IK, we need to recompute transform hierarchy. This is only necessary for transforms that have parent - // transforms that are IK. Because the IK chain is computed from child to parent upwards, IK that have child would not update - // its transform properly in some cases (such as if animation writes to that child) - for (size_t i = 0; i < hierarchy.GetCount(); ++i) - { - const HierarchyComponent& parentcomponent = hierarchy[i]; - Entity entity = hierarchy.GetEntity(i); - - size_t transform_index = transforms.GetIndex(entity); - size_t parent_index = transforms.GetIndex(parentcomponent.parentID); - if (transform_index != ~0ull && parent_index != ~0ull) - { - TransformComponent* transform_child = &transforms_temp[transform_index]; - TransformComponent* transform_parent = &transforms_temp[parent_index]; - transform_child->UpdateTransform_Parented(*transform_parent); - } - } - } - - if (inverse_kinematics.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::RunArmatureUpdateSystem(wi::jobsystem::context& ctx) { wi::jobsystem::Dispatch(ctx, (uint32_t)armatures.GetCount(), 1, [&](wi::jobsystem::JobArgs args) { diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index a5a940cf9..1e7d15eec 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -48,6 +48,7 @@ namespace wi::scene wi::ecs::ComponentManager& colliders = componentLibrary.Register("wi::scene::Scene::colliders", 2); // version = 2 wi::ecs::ComponentManager& scripts = componentLibrary.Register("wi::scene::Scene::scripts"); wi::ecs::ComponentManager& expressions = componentLibrary.Register("wi::scene::Scene::expressions"); + wi::ecs::ComponentManager& humanoids = componentLibrary.Register("wi::scene::Scene::humanoids"); wi::ecs::ComponentManager& terrains = componentLibrary.Register("wi::scene::Scene::terrains", 1); // version = 1 // Non-serialized attributes: @@ -308,9 +309,7 @@ namespace wi::scene void RunTransformUpdateSystem(wi::jobsystem::context& ctx); void RunHierarchyUpdateSystem(wi::jobsystem::context& ctx); void RunExpressionUpdateSystem(wi::jobsystem::context& ctx); - void RunColliderUpdateSystem(wi::jobsystem::context& ctx); - void RunSpringUpdateSystem(wi::jobsystem::context& ctx); - void RunInverseKinematicsUpdateSystem(wi::jobsystem::context& ctx); + void RunProceduralAnimationUpdateSystem(wi::jobsystem::context& ctx); void RunArmatureUpdateSystem(wi::jobsystem::context& ctx); void RunMeshUpdateSystem(wi::jobsystem::context& ctx); void RunMaterialUpdateSystem(wi::jobsystem::context& ctx); diff --git a/WickedEngine/wiScene_Components.cpp b/WickedEngine/wiScene_Components.cpp index 35de89d1a..c1c089e13 100644 --- a/WickedEngine/wiScene_Components.cpp +++ b/WickedEngine/wiScene_Components.cpp @@ -1473,7 +1473,9 @@ namespace wi::scene XMMATRIX _VP = XMMatrixMultiply(_V, _P); XMStoreFloat4x4(&View, _V); XMStoreFloat4x4(&VP, _VP); - XMStoreFloat4x4(&InvView, XMMatrixInverse(nullptr, _V)); + XMMATRIX _InvV = XMMatrixInverse(nullptr, _V); + XMStoreFloat4x4(&InvView, _InvV); + XMStoreFloat3x3(&rotationMatrix, _InvV); XMStoreFloat4x4(&InvVP, XMMatrixInverse(nullptr, _VP)); XMStoreFloat4x4(&Projection, _P); XMStoreFloat4x4(&InvProjection, XMMatrixInverse(nullptr, _P)); diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index b654786b6..d0bb506c4 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -1531,4 +1531,104 @@ namespace wi::scene void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); }; + + struct HumanoidComponent + { + enum FLAGS + { + NONE = 0, + LOOKAT = 1 << 0, + }; + uint32_t _flags = LOOKAT; + + // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/humanoid.md#list-of-humanoid-bones + enum class HumanoidBone + { + // Torso: + Hips, // Required + Spine, // Required + Chest, + UpperChest, + Neck, + + // Head: + Head, // Required + LeftEye, + RightEye, + Jaw, + + // Leg: + LeftUpperLeg, // Required + LeftLowerLeg, // Required + LeftFoot, // Required + LeftToes, + RightUpperLeg, // Required + RightLowerLeg, // Required + RightFoot, // Required + RightToes, + + // Arm: + LeftShoulder, + LeftUpperArm, // Required + LeftLowerArm, // Required + LeftHand, // Required + RightShoulder, + RightUpperArm, // Required + RightLowerArm, // Required + RightHand, // Required + + // Finger: + LeftThumbMetacarpal, + LeftThumbProximal, + LeftThumbDistal, + LeftIndexProximal, + LeftIndexIntermediate, + LeftIndexDistal, + LeftMiddleProximal, + LeftMiddleIntermediate, + LeftMiddleDistal, + LeftRingProximal, + LeftRingIntermediate, + LeftRingDistal, + LeftLittleProximal, + LeftLittleIntermediate, + LeftLittleDistal, + RightThumbMetacarpal, + RightThumbProximal, + RightThumbDistal, + RightIndexIntermediate, + RightIndexDistal, + RightIndexProximal, + RightMiddleProximal, + RightMiddleIntermediate, + RightMiddleDistal, + RightRingProximal, + RightRingIntermediate, + RightRingDistal, + RightLittleProximal, + RightLittleIntermediate, + RightLittleDistal, + + Count + }; + wi::ecs::Entity bones[size_t(HumanoidBone::Count)] = {}; + + constexpr bool IsLookAtEnabled() const { return _flags & LOOKAT; } + + constexpr void SetLookAtEnabled(bool value = true) { if (value) { _flags |= LOOKAT; } else { _flags &= ~LOOKAT; } } + + XMFLOAT3 default_look_direction = XMFLOAT3(0, 0, 1); + XMFLOAT2 head_rotation_max = XMFLOAT2(XM_PI / 3.0f, XM_PI / 6.0f); + float head_rotation_speed = 0.1f; + XMFLOAT2 eye_rotation_max = XMFLOAT2(XM_PI / 20.0f, XM_PI / 20.0f); + float eye_rotation_speed = 0.1f; + + // Non-serialized attributes: + XMFLOAT3 lookAt = {}; // lookAt target pos, can be set by user + XMFLOAT4 lookAtDeltaRotationState_Head = XMFLOAT4(0, 0, 0, 1); + XMFLOAT4 lookAtDeltaRotationState_LeftEye = XMFLOAT4(0, 0, 0, 1); + XMFLOAT4 lookAtDeltaRotationState_RightEye = XMFLOAT4(0, 0, 0, 1); + + void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); + }; } diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index ba1934bc1..3ce41d595 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -1636,6 +1636,33 @@ namespace wi::scene } } } + void HumanoidComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) + { + if (archive.IsReadMode()) + { + archive >> _flags; + archive >> default_look_direction; + archive >> head_rotation_max; + archive >> head_rotation_speed; + + for (auto& entity : bones) + { + SerializeEntity(archive, entity, seri); + } + } + else + { + archive << _flags; + archive << default_look_direction; + archive << head_rotation_max; + archive << head_rotation_speed; + + for (auto& entity : bones) + { + SerializeEntity(archive, entity, seri); + } + } + } void Scene::Serialize(wi::Archive& archive) { diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 93b235ace..9280461b7 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 = 45; + const int revision = 46; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision); diff --git a/features.txt b/features.txt index 10af7e152..51b9eb027 100644 --- a/features.txt +++ b/features.txt @@ -99,7 +99,9 @@ KHR_lights_punctual GLTF 2.0 - VRM 0.0 extensions supported: VRM_secondaryAnimation VRM_blendShapeMaster +VRM_humanoid GLTF 2.0 - VRM 1.0 extensions supported: VRMC_springBone VRMC_vrm_expressions +VRMC_vrm_humanoid