From 99c1d782115bea4ab1b87d7ea044bbbf4224e1f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Sun, 11 Aug 2024 15:21:41 +0200 Subject: [PATCH] character foot placement also works when moving; leaning control; improvements; --- .../ScriptingAPI-Documentation.md | 4 +- WickedEngine/wiScene.cpp | 215 ++++++++++-------- WickedEngine/wiScene_BindLua.cpp | 18 ++ WickedEngine/wiScene_BindLua.h | 2 + WickedEngine/wiScene_Components.cpp | 12 +- WickedEngine/wiScene_Components.h | 7 + WickedEngine/wiVersion.cpp | 2 +- 7 files changed, 155 insertions(+), 105 deletions(-) diff --git a/Content/Documentation/ScriptingAPI-Documentation.md b/Content/Documentation/ScriptingAPI-Documentation.md index 10623e9e4..1f10bf44e 100644 --- a/Content/Documentation/ScriptingAPI-Documentation.md +++ b/Content/Documentation/ScriptingAPI-Documentation.md @@ -1512,9 +1512,11 @@ Implementation of basic character controller features such as movement in the sc - Strafe(Vector value) -- Similar to Move, but relative to the facing direction. - Jump(float amount) -- Jump upwards by an amount. The jump will be executed in the next scene update, with collisions. - Turn(Vector value) -- Turn towards a direction continuously. +- Lean(float value) -- Lean sideways, negative values mean left, positive values mean right -- AddAnimation(Entity entity) -- Adds animation for tracking blending state +- AddAnimation(Entity entity) -- Adds animation for tracking blending state. The simple animation blending will perform blend-out for each animation except the currenttly active one - PlayAnimation(Entity entity) -- Play the animation. This will be blended in as primary animation, others will be belnded out. +- StopAnimation() -- stops current animation - SetAnimationAmount(float value) -- Set target blend amount of current animation - GetAnimatioNAmount() : float -- returns target blend amount of current animation - IsAnimationEnded() : bool --returns true if the current animation is ended, false otherwise diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 5ff790221..258106d30 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -1946,6 +1946,8 @@ namespace wi::scene for (size_t animation_index = 0; animation_index < animation_queue.animations.size(); ++animation_index) { AnimationComponent& animation = *animation_queue.animations[animation_index]; + if (!animation.IsPlaying()) + continue; animation.last_update_time = animation.timer; for (const AnimationComponent::AnimationChannel& channel : animation.channels) @@ -3175,8 +3177,33 @@ namespace wi::scene auto range = wi::profiler::BeginRangeCPU("Procedural Animations"); // Character IK foot placement, should be after animations and hierarchy update: + for (size_t i = 0; i < characters.GetCount(); ++i) + { + CharacterComponent& character = characters[i]; + if (character.left_foot != INVALID_ENTITY && character.right_foot != INVALID_ENTITY) + continue; + HumanoidComponent* humanoid = humanoids.GetComponent(character.humanoidEntity); + if (humanoid == nullptr) + continue; + character.left_foot = humanoid->bones[size_t(HumanoidComponent::HumanoidBone::LeftFoot)]; + { + InverseKinematicsComponent& ik = inverse_kinematics.Create(character.left_foot); + ik.use_target_position = true; + ik.chain_length = 2; + ik.iteration_count = 10; + } + character.right_foot = humanoid->bones[size_t(HumanoidComponent::HumanoidBone::RightFoot)]; + { + InverseKinematicsComponent& ik = inverse_kinematics.Create(character.right_foot); + ik.use_target_position = true; + ik.chain_length = 2; + ik.iteration_count = 10; + } + } wi::jobsystem::Dispatch(ctx, (uint32_t)characters.GetCount(), 1, [&](wi::jobsystem::JobArgs args) { CharacterComponent& character = characters[args.jobIndex]; + if (character.left_foot == INVALID_ENTITY || character.right_foot == INVALID_ENTITY) + return; if (character.humanoidEntity == INVALID_ENTITY) return; HumanoidComponent* humanoid = humanoids.GetComponent(character.humanoidEntity); @@ -3184,105 +3211,106 @@ namespace wi::scene return; Entity entity = characters.GetEntity(args.jobIndex); - uint32_t layer = ~0u; + uint32_t layer = 0; LayerComponent* layercomponent = layers.GetComponent(entity); if (layercomponent != nullptr) { layer = layercomponent->GetLayerMask(); } - Entity left_foot = humanoid->bones[size_t(HumanoidComponent::HumanoidBone::LeftFoot)]; - Entity right_foot = humanoid->bones[size_t(HumanoidComponent::HumanoidBone::RightFoot)]; - if (left_foot != INVALID_ENTITY && right_foot != INVALID_ENTITY) + float base_y = character.position.y; + Entity ik_foot = INVALID_ENTITY; + XMFLOAT3 ik_pos = XMFLOAT3(0, 0, 0); + XMFLOAT3 left_pos = XMFLOAT3(0, 0, 0); + XMFLOAT3 right_pos = XMFLOAT3(0, 0, 0); + TransformComponent* left_transform = transforms.GetComponent(character.left_foot); + TransformComponent* right_transform = transforms.GetComponent(character.right_foot); + if (left_transform != nullptr && right_transform != nullptr) { - float base_y = character.position.y; - Entity ik_foot = INVALID_ENTITY; - XMFLOAT3 ik_pos = XMFLOAT3(0, 0, 0); + left_pos = left_transform->GetPosition(); + right_pos = right_transform->GetPosition(); + } - if (character.IsFootPlacementEnabled() && character.ground_intersect && XMVectorGetX(XMVector3Length(XMVectorSetY(XMLoadFloat3(&character.velocity), 0))) < 0.1f) + if (character.IsFootPlacementEnabled() && character.ground_intersect) + { + // Compute root offset : + // I determine which foot wants to step on lower ground, that will offset whole root downwards + // The other foot will be the upper foot which will be later attached an Inverse Kinematics(IK) effector + Ray left_ray(XMFLOAT3(left_pos.x, left_pos.y + 0.5f, left_pos.z), XMFLOAT3(0, -1, 0), 0, 1); + Ray right_ray(XMFLOAT3(right_pos.x, right_pos.y + 0.5f, right_pos.z), XMFLOAT3(0, -1, 0), 0, 1); + RayIntersectionResult left_result = Intersects(left_ray, FILTER_NAVIGATION_MESH | FILTER_COLLIDER, ~layer); + RayIntersectionResult right_result = Intersects(right_ray, FILTER_NAVIGATION_MESH | FILTER_COLLIDER, ~layer); + float left_diff = 0; + float right_diff = 0; + if (left_result.entity != INVALID_ENTITY) { - TransformComponent* left_transform = transforms.GetComponent(left_foot); - TransformComponent* right_transform = transforms.GetComponent(right_foot); - if (left_transform != nullptr && right_transform != nullptr) + left_diff = left_result.position.y - base_y; + } + if (right_result.entity != INVALID_ENTITY) + { + right_diff = right_result.position.y - base_y; + } + float diff = left_diff; + if (left_result.position.y > right_result.position.y + 0.01f) + { + diff = right_diff; + if (left_result.entity != INVALID_ENTITY) { - // Compute root offset : - // I determine which foot wants to step on lower ground, that will offset whole root downwards - // The other foot will be the upper foot which will be later attached an Inverse Kinematics(IK) effector - XMFLOAT3 left_pos = left_transform->GetPosition(); - XMFLOAT3 right_pos = right_transform->GetPosition(); - Ray left_ray(XMFLOAT3(left_pos.x, left_pos.y + 1, left_pos.z), XMFLOAT3(0, -1, 0), 0, 1.8f); - Ray right_ray(XMFLOAT3(right_pos.x, right_pos.y + 1, right_pos.z), XMFLOAT3(0, -1, 0), 0, 1.8f); - RayIntersectionResult left_result = Intersects(left_ray, FILTER_NAVIGATION_MESH, FILTER_COLLIDER, layer); - RayIntersectionResult right_result = Intersects(right_ray, FILTER_NAVIGATION_MESH, FILTER_COLLIDER, layer); - float left_diff = 0; - float right_diff = 0; - if (left_result.entity != INVALID_ENTITY) - { - left_diff = left_result.position.y - base_y; - } - if (right_result.entity != INVALID_ENTITY) - { - right_diff = right_result.position.y - base_y; - } - float diff = left_diff; - if (left_result.position.y > right_result.position.y) - { - diff = right_diff; - if (left_result.entity != INVALID_ENTITY) - { - ik_foot = left_foot; - ik_pos = left_result.position; - } - } - else - { - if (right_result.entity != INVALID_ENTITY) - { - ik_foot = right_foot; - ik_pos = right_result.position; - } - } - character.root_offset = wi::math::Lerp(character.root_offset, diff, 0.1f); + ik_foot = character.left_foot; + ik_pos = left_result.position; } } else { - character.root_offset = wi::math::Lerp(character.root_offset, 0.0f, 0.1f); + if (right_result.entity != INVALID_ENTITY) + { + ik_foot = character.right_foot; + ik_pos = right_result.position; + } } + character.root_offset = wi::math::Lerp(character.root_offset, diff, 0.1f); + } + else + { + character.root_offset = wi::math::Lerp(character.root_offset, 0.0f, 0.1f); + } - TransformComponent* humanoid_transform = transforms.GetComponent(character.humanoidEntity); - if (humanoid_transform != nullptr) - { - // Offset root transform to lower foot pos: - humanoid_transform->translation_local.y = character.root_offset; - humanoid_transform->SetDirty(); - } + TransformComponent* humanoid_transform = transforms.GetComponent(character.humanoidEntity); + if (humanoid_transform != nullptr) + { + // Offset root transform to lower foot pos: + humanoid_transform->translation_local.y = character.root_offset; + humanoid_transform->SetDirty(); + } - // Because IK component removals and creates can be performed below, we must lock: + // Ease out inverse kinematics by default: + if (inverse_kinematics.Contains(character.left_foot)) + { + InverseKinematicsComponent& ik = *inverse_kinematics.GetComponent(character.left_foot); + ik.target_position = wi::math::Lerp(ik.target_position, left_pos, 0.6f); + } + if (inverse_kinematics.Contains(character.right_foot)) + { + InverseKinematicsComponent& ik = *inverse_kinematics.GetComponent(character.right_foot); + ik.target_position = wi::math::Lerp(ik.target_position, right_pos, 0.6f); + } + + // The upper foot will use IK: + if (ik_foot != INVALID_ENTITY && inverse_kinematics.Contains(ik_foot)) + { + InverseKinematicsComponent& ik = *inverse_kinematics.GetComponent(ik_foot); + ik_pos.y += 0.16f; + ik.target_position = wi::math::Lerp(ik.target_position, ik_pos, 0.6f); +#if 0 + // Debug draw foot target: locker.lock(); - - // Remove IK effectors by default: - if (inverse_kinematics.Contains(left_foot)) - { - inverse_kinematics.Remove(left_foot); - } - if (inverse_kinematics.Contains(right_foot)) - { - inverse_kinematics.Remove(right_foot); - } - - // The upper foot will use IK: - if (ik_foot != INVALID_ENTITY) - { - InverseKinematicsComponent& ik = inverse_kinematics.Create(ik_foot); - ik.use_target_position = true; - ik.target_position = ik_pos; - ik.target_position.y += 0.15f; - ik.chain_length = 2; - ik.iteration_count = 10; - } - + wi::renderer::RenderablePoint point; + point.position = ik.target_position; + point.color = XMFLOAT4(1, 1, 0, 1); + point.size = 0.1f; + wi::renderer::DrawPoint(point); locker.unlock(); +#endif } }); wi::jobsystem::Wait(ctx); @@ -3378,25 +3406,6 @@ namespace wi::scene break; } } - if (constrain) - { - // Constraint swapping fixes for flipped model orientations: - if (facing < 0) - { - // Note: this is a fix for VRM 1.0 and Mixamo model - std::swap(constraint_min, constraint_max); - } - const TransformComponent* bone_transform = transforms.GetComponent(bone); - if (bone_transform != nullptr) - { - if (bone_transform->GetForward().z < 0) - { - // Note: this is a fix for FBX Mixamo models - std::swap(constraint_min, constraint_max); - } - } - break; - } bone_type_idx++; } } @@ -5302,13 +5311,14 @@ namespace wi::scene { static const XMVECTOR up = XMVectorSet(0, 1, 0, 0); static const XMMATRIX rotY = XMMatrixRotationY(XM_PI); + static const int max_substeps = 4; wi::jobsystem::Dispatch(ctx, (uint32_t)characters.GetCount(), 1, [&](wi::jobsystem::JobArgs args) { CharacterComponent& character = characters[args.jobIndex]; if (!character.IsActive()) return; Entity entity = characters.GetEntity(args.jobIndex); - uint32_t layer = ~0u; + uint32_t layer = 0; LayerComponent* layercomponent = layers.GetComponent(entity); if (layercomponent != nullptr) { @@ -5396,8 +5406,10 @@ namespace wi::scene } // Fixed timestep logic: - while (character.accumulator >= timestep) + int steps = 0; + while (character.accumulator >= timestep && steps <= max_substeps) { + steps++; XMStoreFloat3(&character.position_prev, position); character.accumulator -= timestep; if (character.swimming) @@ -5458,6 +5470,7 @@ namespace wi::scene character.leaning_next = lerp(character.leaning_next, velocity_leaning, 0.05f); character.leaning = lerp(character.leaning, character.leaning_next, 0.05f); } + character.accumulator = clamp(character.accumulator, 0.0f, timestep); character.alpha = character.accumulator / timestep; if (platform_velocity_count > 0) diff --git a/WickedEngine/wiScene_BindLua.cpp b/WickedEngine/wiScene_BindLua.cpp index 3959b172c..806bbba97 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -7493,9 +7493,11 @@ Luna::FunctionType CharacterComponent_BindLua::metho lunamethod(CharacterComponent_BindLua, Strafe), lunamethod(CharacterComponent_BindLua, Jump), lunamethod(CharacterComponent_BindLua, Turn), + lunamethod(CharacterComponent_BindLua, Lean), lunamethod(CharacterComponent_BindLua, AddAnimation), lunamethod(CharacterComponent_BindLua, PlayAnimation), + lunamethod(CharacterComponent_BindLua, StopAnimation), lunamethod(CharacterComponent_BindLua, SetAnimationAmount), lunamethod(CharacterComponent_BindLua, GetAnimationAmount), lunamethod(CharacterComponent_BindLua, IsAnimationEnded), @@ -7606,6 +7608,17 @@ int CharacterComponent_BindLua::Turn(lua_State* L) component->Turn(v->GetFloat3()); return 0; } +int CharacterComponent_BindLua::Lean(lua_State* L) +{ + int argc = wi::lua::SGetArgCount(L); + if (argc < 1) + { + wi::lua::SError(L, "Lean(float value) not enough arguments!"); + return 0; + } + component->Lean(wi::lua::SGetFloat(L, 1)); + return 0; +} int CharacterComponent_BindLua::AddAnimation(lua_State* L) { @@ -7630,6 +7643,11 @@ int CharacterComponent_BindLua::PlayAnimation(lua_State* L) component->PlayAnimation(entity); return 0; } +int CharacterComponent_BindLua::StopAnimation(lua_State* L) +{ + component->StopAnimation(); + return 0; +} int CharacterComponent_BindLua::SetAnimationAmount(lua_State* L) { int argc = wi::lua::SGetArgCount(L); diff --git a/WickedEngine/wiScene_BindLua.h b/WickedEngine/wiScene_BindLua.h index 5b477281b..fdf57740e 100644 --- a/WickedEngine/wiScene_BindLua.h +++ b/WickedEngine/wiScene_BindLua.h @@ -1857,9 +1857,11 @@ namespace wi::lua::scene int Strafe(lua_State* L); int Jump(lua_State* L); int Turn(lua_State* L); + int Lean(lua_State* L); int AddAnimation(lua_State* L); int PlayAnimation(lua_State* L); + int StopAnimation(lua_State* L); int SetAnimationAmount(lua_State* L); int GetAnimationAmount(lua_State* L); int IsAnimationEnded(lua_State* L); diff --git a/WickedEngine/wiScene_Components.cpp b/WickedEngine/wiScene_Components.cpp index 238e7391f..098a19c67 100644 --- a/WickedEngine/wiScene_Components.cpp +++ b/WickedEngine/wiScene_Components.cpp @@ -2374,11 +2374,15 @@ namespace wi::scene } facing_next = direction; } - void CharacterComponent::AddAnimation(wi::ecs::Entity entity) + void CharacterComponent::Lean(float amount) + { + leaning_next = amount; + } + void CharacterComponent::AddAnimation(Entity entity) { animations.push_back(entity); } - void CharacterComponent::PlayAnimation(wi::ecs::Entity entity) + void CharacterComponent::PlayAnimation(Entity entity) { if (currentAnimation != entity) { @@ -2386,6 +2390,10 @@ namespace wi::scene currentAnimation = entity; } } + void CharacterComponent::StopAnimation() + { + currentAnimation = INVALID_ENTITY; + } void CharacterComponent::SetAnimationAmount(float amount) { anim_amount = amount; diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index f6b8bdda8..b54a74de6 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -2104,6 +2104,8 @@ namespace wi::scene bool ground_intersect = false; bool swimming = false; wi::ecs::Entity humanoidEntity = wi::ecs::INVALID_ENTITY; + wi::ecs::Entity left_foot = wi::ecs::INVALID_ENTITY; + wi::ecs::Entity right_foot = wi::ecs::INVALID_ENTITY; float root_offset = 0; bool foot_placement_enabled = true; wi::PathQuery pathquery; @@ -2120,11 +2122,16 @@ namespace wi::scene void Move(const XMFLOAT3& direction); // Apply movement relative to the character facing in the next update void Strafe(const XMFLOAT3& direction); + // Apply upwards movement immediately void Jump(float amount); + // Turn towards the direction smoothly void Turn(const XMFLOAT3& direction); + // Lean sideways, negative values mean left, positive values mean right + void Lean(float amount); void AddAnimation(wi::ecs::Entity entity); void PlayAnimation(wi::ecs::Entity entity); + void StopAnimation(); void SetAnimationAmount(float amount); float GetAnimationAmount() const; bool IsAnimationEnded() const; diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index cc3a8f3e5..86dd4556c 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 = 537; + const int revision = 538; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);