Files
WickedEngine/Editor/HumanoidWindow.cpp
T
Dennis Brakhane 282b81c3d9 replace default reference captures, fix missing calls (#1153)
* forAll -> forEach

* replace default reference captures with more specific ones

* fix missing calls in fixXYZ/forEachSelectedWithRefresh
2025-07-08 17:08:59 +02:00

523 lines
18 KiB
C++

#include "stdafx.h"
#include "HumanoidWindow.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 | wi::gui::Window::WindowControls::FIT_ALL_WIDGETS_VERTICAL);
SetSize(XMFLOAT2(670, 620));
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->componentsWnd.RefreshEntityTree();
});
infoLabel.Create("");
infoLabel.SetText("This window will stay open even if you select other entities until it is collapsed, so you can select other bone entities.");
infoLabel.SetFitTextEnabled(true);
AddWidget(&infoLabel);
auto forEachSelected = [this] (auto func) {
return [this, func] (auto args) {
wi::scene::Scene& scene = editor->GetCurrentScene();
HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity);
if (humanoid != nullptr) {
func(humanoid, args);
}
};
};
lookatCheckBox.Create("Look At: ");
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.OnClick(forEachSelected([] (auto humanoid, auto args) {
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");
AddWidget(&lookatMouseCheckBox);
lookatMouseCheckBox.SetCheck(true);
lookatEntityCombo.Create("Look At Entity: ");
lookatEntityCombo.SetTooltip("If this is set to an entity with TransformComponent, it will override the lookAt position that was set directly and it will also be saved with the scene.");
lookatEntityCombo.OnSelect(forEachSelected([] (auto humanoid, auto args) {
humanoid->lookAtEntity = (Entity)args.userdata;
}));
AddWidget(&lookatEntityCombo);
ragdollCheckBox.Create("Ragdoll: ");
ragdollCheckBox.SetTooltip("Activate dynamic ragdoll physics.\nNote that kinematic ragdoll physics is always active (ragdoll is animation-driven/kinematic by default).\nNote that scaling humanoid will disable ragdoll physics and you need to re-enable if you want to.");
ragdollCheckBox.OnClick(forEachSelected([] (auto humanoid, auto args) {
humanoid->SetRagdollPhysicsEnabled(args.bValue);
}));
AddWidget(&ragdollCheckBox);
capsuleShadowCheckBox.Create("Capsule Shadow Disabled: ");
capsuleShadowCheckBox.SetTooltip("Disable capsule shadow for this specific humanoid.");
capsuleShadowCheckBox.OnClick(forEachSelected([] (auto humanoid, auto args) {
humanoid->SetCapsuleShadowDisabled(args.bValue);
}));
AddWidget(&capsuleShadowCheckBox);
headRotMaxXSlider.Create(0, 90, 60, 180, "Head horizontal: ");
headRotMaxXSlider.SetTooltip("Limit horizontal head movement (input in degrees)");
headRotMaxXSlider.OnSlide(forEachSelected([] (auto humanoid, auto args) {
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.OnSlide(forEachSelected([] (auto humanoid, auto args) {
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.OnSlide(forEachSelected([] (auto humanoid, auto args) {
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.OnSlide(forEachSelected([] (auto humanoid, auto args) {
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.OnSlide(forEachSelected([] (auto humanoid, auto args) {
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.OnSlide(forEachSelected([] (auto humanoid, auto args) {
humanoid->eye_rotation_speed = args.fValue;
}));
AddWidget(&eyeRotSpeedSlider);
headSizeSlider.Create(0.5f, 2, 1, 1000, "Head size: ");
headSizeSlider.SetTooltip("Adjust head size.");
headSizeSlider.OnSlide(forEachSelected([this] (auto humanoid, auto args) {
Entity bone = humanoid->bones[size_t(HumanoidComponent::HumanoidBone::Head)];
wi::scene::Scene& scene = editor->GetCurrentScene();
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);
ragdollFatnessSlider.Create(0.5f, 2, 1, 1000, "Ragdoll fatness: ");
ragdollFatnessSlider.SetTooltip("Adjust overall fatness of ragdoll physics skeleton.");
ragdollFatnessSlider.OnSlide(forEachSelected([] (auto humanoid, auto args) {
humanoid->ragdoll_fatness = args.fValue;
humanoid->ragdoll = {}; // request recreate
}));
AddWidget(&ragdollFatnessSlider);
ragdollHeadSizeSlider.Create(0.5f, 2, 1, 1000, "Ragdoll head: ");
ragdollHeadSizeSlider.SetTooltip("Adjust overall size of ragdoll physics head.");
ragdollHeadSizeSlider.OnSlide(forEachSelected([] (auto humanoid, auto args) {
humanoid->ragdoll_headsize = args.fValue;
humanoid->ragdoll = {}; // request recreate
}));
AddWidget(&ragdollHeadSizeSlider);
boneList.Create("Bones: ");
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->componentsWnd.RefreshEntityTree();
});
AddWidget(&boneList);
SetMinimized(true);
SetVisible(false);
SetEntity(INVALID_ENTITY);
}
void HumanoidWindow::SetEntity(Entity entity)
{
Scene& scene = editor->GetCurrentScene();
const HumanoidComponent* humanoid = scene.humanoids.GetComponent(entity);
if (humanoid != nullptr)
{
ragdollCheckBox.SetCheck(humanoid->IsRagdollPhysicsEnabled()); // this is always force updated
}
if (humanoid != nullptr || IsCollapsed())
{
if (this->entity != entity)
{
this->entity = entity;
RefreshBoneList();
}
if (humanoid != nullptr)
{
lookatCheckBox.SetCheck(humanoid->IsLookAtEnabled());
ragdollCheckBox.SetCheck(humanoid->IsRagdollPhysicsEnabled());
capsuleShadowCheckBox.SetCheck(humanoid->IsCapsuleShadowDisabled());
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);
ragdollFatnessSlider.SetValue(humanoid->ragdoll_fatness);
ragdollHeadSizeSlider.SetValue(humanoid->ragdoll_headsize);
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);
}
lookatEntityCombo.ClearItems();
lookatEntityCombo.AddItem("NONE " ICON_DISABLED, INVALID_ENTITY);
for (size_t i = 0; i < scene.transforms.GetCount(); ++i)
{
Entity transformEntity = scene.transforms.GetEntity(i);
const NameComponent* name = scene.names.GetComponent(transformEntity);
lookatEntityCombo.AddItem(name == nullptr ? std::to_string(transformEntity) : name->name, transformEntity);
if (humanoid->lookAtEntity == transformEntity)
{
lookatEntityCombo.SetSelectedWithoutCallback((int)lookatEntityCombo.GetItemCount() - 1);
}
}
}
}
}
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::ResizeLayout()
{
wi::gui::Window::ResizeLayout();
layout.margin_left = 110;
layout.add_fullwidth(infoLabel);
layout.add_right(lookatCheckBox);
lookatMouseCheckBox.SetPos(XMFLOAT2(lookatCheckBox.GetPos().x - 120, lookatCheckBox.GetPos().y));
layout.add(lookatEntityCombo);
layout.add_right(ragdollCheckBox);
layout.add_right(capsuleShadowCheckBox);
layout.add(headRotMaxXSlider);
layout.add(headRotMaxYSlider);
layout.add(headRotSpeedSlider);
layout.add(eyeRotMaxXSlider);
layout.add(eyeRotMaxYSlider);
layout.add(eyeRotSpeedSlider);
layout.add(headSizeSlider);
layout.add(ragdollFatnessSlider);
layout.add(ragdollHeadSizeSlider);
layout.jump();
layout.add_fullwidth(boneList);
}
void HumanoidWindow::UpdateHumanoids()
{
// Update humanoids to look at mouse:
if (lookatMouseCheckBox.GetCheck())
{
Scene& scene = editor->GetCurrentScene();
const CameraComponent& camera = editor->GetCurrentEditorScene().camera;
wi::primitive::Ray ray = editor->pickRay;
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
}
}
}
}