diff --git a/Content/scripts/character_controller/character_controller.lua b/Content/scripts/character_controller/character_controller.lua index bcbac92a0..401748a4c 100644 --- a/Content/scripts/character_controller/character_controller.lua +++ b/Content/scripts/character_controller/character_controller.lua @@ -105,11 +105,14 @@ Character = { self.target = CreateEntity() local target_transform = character_scene.Component_CreateTransform(self.target) - target_transform.Translate(character_scene.Component_GetTransform(self.head).GetPosition()) + target_transform.Translate(character_scene.Component_GetTransform(self.neck).GetPosition()) target_transform.Translate(self.start_position) character_scene.Component_Attach(self.target, self.model) + self.root = character_scene.Entity_FindByName("Root") + self.root_bone_offset = 0 + scene.Merge(character_scene) end, @@ -364,6 +367,72 @@ Character = { PutWaterRipple(script_dir() .. "assets/ripple.png", wp) end end + + -- IK foot placement: + local root_bone_transform = scene.Component_GetTransform(self.root) + root_bone_transform.ClearTransform() + self.root_bone_offset = math.lerp(self.root_bone_offset, 0, 0.1) + scene.UpdateHierarchy() -- Note: if I don't do this, you get foot positions after IK, but we want foot positions after animation, to start from "fresh" + local ik_foot = INVALID_ENTITY + local ik_pos = Vector() + -- Compute root bone offset: + -- I determine which foot wants to step on lower ground, that will offset root bone of skeleton downwards + -- The other foot will be the upper foot which will be later attached an Inverse Kinematics (IK) effector + if self.state == self.states.IDLE and self.velocity.GetY() == 0 then + local pos_left = scene.Component_GetTransform(self.left_foot).GetPosition() + local pos_right = scene.Component_GetTransform(self.right_foot).GetPosition() + local ray_left = Ray(vector.Add(pos_left, Vector(0, 1)), Vector(0, -1), 0, 1.8) + local ray_right = Ray(vector.Add(pos_right, Vector(0, 1)), Vector(0, -1), 0, 1.8) + -- Ray trace for both feet: + local collEntity_left,collPos_left = scene.Intersects(ray_left, FILTER_NAVIGATION_MESH | FILTER_COLLIDER, ~self.layerMask) + local collEntity_right,collPos_right = scene.Intersects(ray_right, FILTER_NAVIGATION_MESH | FILTER_COLLIDER, ~self.layerMask) + local diff_left = 0 + local diff_right = 0 + if collEntity_left ~= INVALID_ENTITY then + diff_left = vector.Subtract(collPos_left, pos_left).GetY() + end + if collEntity_right ~= INVALID_ENTITY then + diff_right = vector.Subtract(collPos_right, pos_right).GetY() + end + local diff = diff_left + if collPos_left.GetY() > collPos_right.GetY() then + diff = diff_right + if collEntity_left ~= INVALID_ENTITY then + ik_foot = self.left_foot + ik_pos = collPos_left + end + else + if collEntity_right ~= INVALID_ENTITY then + ik_foot = self.right_foot + ik_pos = collPos_right + end + end + self.root_bone_offset = math.lerp(self.root_bone_offset, diff + 0.1, 0.2) + end + root_bone_transform.Translate(Vector(0, self.root_bone_offset)) + + -- Remove IK effectors by default: + if scene.Component_GetInverseKinematics(self.left_foot) ~= nil then + scene.Component_RemoveInverseKinematics(self.left_foot) + end + if scene.Component_GetInverseKinematics(self.right_foot) ~= nil then + scene.Component_RemoveInverseKinematics(self.right_foot) + end + -- The upper foot will use IK effector in IDLE state: + if ik_foot ~= INVALID_ENTITY then + if self.foot_placement == nil then + self.foot_placement = CreateEntity() + scene.Component_CreateTransform(self.foot_placement) + end + --DrawAxis(ik_pos, 0.2) + local transform = scene.Component_GetTransform(self.foot_placement) + transform.ClearTransform() + transform.Translate(vector.Add(ik_pos, Vector(0, 0.15))) + local ik = scene.Component_CreateInverseKinematics(ik_foot) + ik.SetTarget(self.foot_placement) + ik.SetChainLength(2) + ik.SetIterationCount(10) + end end, @@ -480,6 +549,7 @@ ThirdPersonCamera = { ClearWorld() LoadModel(script_dir() .. "assets/level.wiscene") --LoadModel(script_dir() .. "assets/terrain.wiscene") +--LoadModel(script_dir() .. "assets/ballpark.wiscene") --dofile(script_dir() .. "../dungeon_generator/dungeon_generator.lua") local player = Character @@ -508,6 +578,8 @@ runProcess(function() --application.SetInfoDisplay(false) application.SetFPSDisplay(true) --path.SetResolutionScale(0.5) + --path.SetFSR2Enabled(true) + --path.SetFSR2Preset(FSR2_Preset.Performance) while true do diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 92f1a4720..bc4a9b3c7 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -2219,7 +2219,6 @@ namespace wi::scene if (inverse_kinematics.GetCount() > 0 || humanoids.GetCount() > 0) { - transforms_temp.resize(transforms.GetCount()); transforms_temp = transforms.GetComponentArray(); // make copy } @@ -2263,9 +2262,81 @@ namespace wi::scene 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)); + + // Check if this transform is part of a humanoid and need some constraining: + bool constrain = false; + XMFLOAT3 constraint_min = XMFLOAT3(0, 0, 0); + XMFLOAT3 constraint_max = XMFLOAT3(0, 0, 0); + for (size_t humanoid_idx = 0; (humanoid_idx < humanoids.GetCount()) && !constrain; ++humanoid_idx) + { + const HumanoidComponent& humanoid = humanoids[humanoid_idx]; + int bone_type_idx = 0; + for (auto& bone : humanoid.bones) + { + if (bone == parent_entity) + { + switch ((HumanoidComponent::HumanoidBone)bone_type_idx) + { + default: + break; + case HumanoidComponent::HumanoidBone::LeftUpperLeg: + case HumanoidComponent::HumanoidBone::RightUpperLeg: + constrain = true; + constraint_min = XMFLOAT3(XM_PI * 0.6f, XM_PI * 0.1f, XM_PI * 0.1f); + constraint_max = XMFLOAT3(XM_PI * 0.1f, XM_PI * 0.1f, XM_PI * 0.1f); + break; + case HumanoidComponent::HumanoidBone::LeftLowerLeg: + case HumanoidComponent::HumanoidBone::RightLowerLeg: + constrain = true; + constraint_min = XMFLOAT3(0, 0, 0); + constraint_max = XMFLOAT3(XM_PI * 0.8f, 0, 0); + break; + } + } + if (constrain) + break; + bone_type_idx++; + } + } + + XMVECTOR Q; + if (constrain) + { + // Apply constrained rotation: + Q = XMQuaternionIdentity(); + XMMATRIX W = XMLoadFloat4x4(&parent_transform.world); + for (int axis_idx = 0; axis_idx < 3; ++axis_idx) + { + XMFLOAT3 axis_floats = XMFLOAT3(0, 0, 0); + ((float*)&axis_floats)[axis_idx] = 1; + XMVECTOR axis = XMLoadFloat3(&axis_floats); + const float axis_min = ((float*)&constraint_min)[axis_idx] / (float)ik.iteration_count; + const float axis_max = ((float*)&constraint_max)[axis_idx] / (float)ik.iteration_count; + axis = XMVector3Normalize(XMVector3TransformNormal(axis, W)); + const XMVECTOR projA = XMVector3Normalize(dir_parent_to_ik - axis * XMVector3Dot(axis, dir_parent_to_ik)); + const XMVECTOR projB = XMVector3Normalize(dir_parent_to_target - axis * XMVector3Dot(axis, dir_parent_to_target)); + float angle = XMVectorGetX(XMVector3AngleBetweenNormals(projA, projB)); + if (XMVectorGetX(XMVector3Dot(XMVector3Cross(projA, projB), axis)) < 0) + { + angle = XM_2PI - std::min(angle, axis_min); + } + else + { + angle = std::min(angle, axis_max); + } + const XMVECTOR Q1 = XMQuaternionNormalize(XMQuaternionRotationNormal(axis, angle)); + W = XMMatrixRotationQuaternion(Q1) * W; + Q = XMQuaternionMultiply(Q1, Q); + } + Q = XMQuaternionNormalize(Q); + } + else + { + // Simple shortest rotation without constraint: + 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))); + Q = XMQuaternionNormalize(XMQuaternionRotationNormal(axis, angle)); + } // parent to world space: parent_transform.ApplyTransform(); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 4155e4efa..6a10ee1bd 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 = 106; + const int revision = 107; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);