From 10f42c4bb637625d5f70ff990ebf900d0ca928d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Mon, 6 May 2024 07:23:38 +0200 Subject: [PATCH] fixes: springbone rest pose; reverse facing character ik; character controller script foot placement root offset to work without root bone (mixamo); --- .../character_controller.lua | 54 ++++----- Editor/ArmatureWindow.cpp | 1 - Editor/ComponentsWindow.cpp | 2 +- Editor/ModelImporter_FBX.cpp | 2 +- Editor/ModelImporter_GLTF.cpp | 2 - WickedEngine/wiPhysics_Bullet.cpp | 14 +-- WickedEngine/wiScene.cpp | 104 +++++++++++++----- WickedEngine/wiScene.h | 9 +- WickedEngine/wiScene_Components.h | 1 - WickedEngine/wiScene_Serializers.cpp | 1 + WickedEngine/wiVersion.cpp | 2 +- 11 files changed, 116 insertions(+), 76 deletions(-) diff --git a/Content/scripts/character_controller/character_controller.lua b/Content/scripts/character_controller/character_controller.lua index e4a2f71a1..e2ff0abd9 100644 --- a/Content/scripts/character_controller/character_controller.lua +++ b/Content/scripts/character_controller/character_controller.lua @@ -331,7 +331,7 @@ local function Character(model_entity, start_position, face, controllable, anim_ controllable = true, fixed_update_remain = 0, timestep_occured = false, - root_bone_offset = 0, + root_offset = 0, foot_placed_left = false, foot_placed_right = false, mood = Mood.Neutral, @@ -392,26 +392,21 @@ local function Character(model_entity, start_position, face, controllable, anim_ end end - -- Create a base capsule collider if it's not yet configured for character: - -- It will be used for movement logic and GPU collision effects - if scene.Component_GetCollider(self.humanoid) == nil then - local collider = scene.Component_CreateCollider(self.model) - self.collider = self.model - collider.SetCPUEnabled(false) - collider.SetGPUEnabled(true) - collider.Shape = ColliderShape.Capsule - collider.Radius = 0.3 - collider.Offset = Vector(0, collider.Radius, 0) - collider.Tail = Vector(0, 1.4, 0) - local head_transform = scene.Component_GetTransform(self.head) - if head_transform ~= nil then - collider.Tail = head_transform.GetPosition() - end - else - self.collider = self.humanoid + -- Create a base capsule collider for character: + local collider = scene.Component_CreateCollider(self.model) + self.collider = self.model + collider.SetCPUEnabled(false) + collider.SetGPUEnabled(true) + collider.Shape = ColliderShape.Capsule + collider.Radius = 0.3 + collider.Offset = Vector(0, collider.Radius, 0) + collider.Tail = Vector(0, 1.4, 0) + local head_transform = scene.Component_GetTransform(self.head) + if head_transform ~= nil then + collider.Tail = head_transform.GetPosition() end - self.root = scene.Entity_FindByName("Root", self.model) + self.root = self.humanoid self.anims[States.IDLE] = scene.RetargetAnimation(self.humanoid, animations.IDLE, false, anim_scene) self.anims[States.WALK] = scene.RetargetAnimation(self.humanoid, animations.WALK, false, anim_scene) @@ -847,8 +842,8 @@ local function Character(model_entity, start_position, face, controllable, anim_ local base_y = self.position.GetY() 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 + -- 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 if (self.state == States.IDLE or self.state == States.DANCE) and self.velocity.GetY() == 0 then local pos_left = scene.Component_GetTransform(self.left_foot).GetPosition() @@ -882,19 +877,16 @@ local function Character(model_entity, start_position, face, controllable, anim_ ik_pos = collPos_right end end - self.root_bone_offset = math.lerp(self.root_bone_offset, diff, 0.1) + self.root_offset = math.lerp(self.root_offset, diff, 0.1) else - self.root_bone_offset = math.lerp(self.root_bone_offset, 0, 0.1) + self.root_offset = math.lerp(self.root_offset, 0, 0.1) end -- Offset root transform to lower foot pos: - local root_bone_transform = scene.Component_GetTransform(self.root) - if root_bone_transform ~= nil then - root_bone_transform.ClearTransform() - local root_pos = root_bone_transform.GetPosition() - root_bone_transform.Translate(Vector(0, self.root_bone_offset)) - --DrawDebugText(self.root_bone_offset, self.position, Vector(1,1,1,1), 0.1, DEBUG_TEXT_CAMERA_FACING) - --DrawPoint(vector.Add(root_pos, Vector(0, self.root_bone_offset)), 0.1, Vector(1,0,0,1)) + local root_transform = scene.Component_GetTransform(self.root) + if root_transform ~= nil then + root_transform.Translation_local = Vector(0, self.root_offset) + root_transform.SetDirty(true) end -- Remove IK effectors by default: @@ -1080,7 +1072,7 @@ local function ThirdPersonCamera(character) local character_position = character_transform.GetPosition() self.target_rot_horizontal = math.lerp(self.target_rot_horizontal, self.character.target_rot_horizontal, 0.1) self.target_rot_vertical = math.lerp(self.target_rot_vertical, self.character.target_rot_vertical, 0.1) - self.target_height = math.lerp(self.target_height, character_position.GetY() + self.character.target_height, 0.1) + self.target_height = math.lerp(self.target_height, character_position.GetY() + self.character.target_height + self.character.root_offset, 0.1) local camera_transform = scene.Component_GetTransform(self.camera) local target_transform = TransformComponent() diff --git a/Editor/ArmatureWindow.cpp b/Editor/ArmatureWindow.cpp index 9982a2300..6a3c3d798 100644 --- a/Editor/ArmatureWindow.cpp +++ b/Editor/ArmatureWindow.cpp @@ -143,7 +143,6 @@ void ArmatureWindow::Create(EditorComponent* _editor) HumanoidComponent humanoid; bool found_anything = false; - humanoid.default_look_direction = XMFLOAT3(0, 0, -1); for (size_t i = 0; i < armature->boneCollection.size(); ++i) { diff --git a/Editor/ComponentsWindow.cpp b/Editor/ComponentsWindow.cpp index 6035aa875..a8c3af4d1 100644 --- a/Editor/ComponentsWindow.cpp +++ b/Editor/ComponentsWindow.cpp @@ -88,7 +88,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) newComponentCombo.AddItem("Transform " ICON_TRANSFORM, ADD_TRANSFORM); newComponentCombo.AddItem("Light " ICON_POINTLIGHT, ADD_LIGHT); newComponentCombo.AddItem("Material " ICON_MATERIAL, ADD_MATERIAL); - newComponentCombo.AddItem("Spring", ADD_SPRING); + newComponentCombo.AddItem("Spring " ICON_SPRING, ADD_SPRING); newComponentCombo.AddItem("Inverse Kinematics " ICON_IK, ADD_IK); newComponentCombo.AddItem("Sound " ICON_SOUND, ADD_SOUND); newComponentCombo.AddItem("Environment Probe " ICON_ENVIRONMENTPROBE, ADD_ENVPROBE); diff --git a/Editor/ModelImporter_FBX.cpp b/Editor/ModelImporter_FBX.cpp index 72748a35e..ca1d40d90 100644 --- a/Editor/ModelImporter_FBX.cpp +++ b/Editor/ModelImporter_FBX.cpp @@ -804,7 +804,7 @@ void Import_Mixamo_Bone(Scene& scene, Entity rootEntity, Entity boneEntity) auto get_humanoid = [&]() -> HumanoidComponent& { if (scene.humanoids.GetCount() == 0) { - scene.humanoids.Create(rootEntity).default_look_direction = XMFLOAT3(0, 0, -1); + scene.humanoids.Create(rootEntity); } // Note: it seems there are multiple armatures for multiple body parts in some FBX from Mixamo, // but there should be only one humanoid, so we don't create humanoids for each armature diff --git a/Editor/ModelImporter_GLTF.cpp b/Editor/ModelImporter_GLTF.cpp index c0f51d73c..dc75b75c8 100644 --- a/Editor/ModelImporter_GLTF.cpp +++ b/Editor/ModelImporter_GLTF.cpp @@ -2791,7 +2791,6 @@ void Import_Extension_VRMC(LoaderState& state) 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")) @@ -3233,7 +3232,6 @@ void Import_Mixamo_Bone(LoaderState& state, Entity boneEntity, const tinygltf::N if (component == nullptr) { component = &state.scene->humanoids.Create(state.rootEntity); - component->default_look_direction = XMFLOAT3(0, 0, -1); } return *component; }; diff --git a/WickedEngine/wiPhysics_Bullet.cpp b/WickedEngine/wiPhysics_Bullet.cpp index 0b690fbe1..9520994e8 100644 --- a/WickedEngine/wiPhysics_Bullet.cpp +++ b/WickedEngine/wiPhysics_Bullet.cpp @@ -577,15 +577,7 @@ namespace wi::physics #endif //Detect which way humanoid is facing in rest pose: - float facing = 1; - Entity left_shoulder = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftUpperArm]; - Entity right_shoulder = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightUpperArm]; - const XMVECTOR& left_shoulder_pos = scene.FindBoneRestPose(left_shoulder).r[3]; - const XMVECTOR& right_shoulder_pos = scene.FindBoneRestPose(right_shoulder).r[3]; - if (XMVectorGetX(right_shoulder_pos) < XMVectorGetX(left_shoulder_pos)) - { - facing = -1; - } + const float facing = scene.GetHumanoidDefaultFacing(humanoid, humanoidEntity); // Whole ragdoll will take a uniform scaling: const XMMATRIX scaleMatrix = XMMatrixScaling(scale, scale, scale); @@ -668,8 +660,8 @@ namespace wi::physics // Calculations here will be done in armature local space. // Unfortunately since humanoid can be separate from armature, we use a "find" utility to find bone rest matrix in armature // Note that current scaling of character is applied here separately from rest pose - XMMATRIX restA = scene.FindBoneRestPose(entityA) * scaleMatrix; - XMMATRIX restB = scene.FindBoneRestPose(entityB) * scaleMatrix; + XMMATRIX restA = scene.GetRestPose(entityA) * scaleMatrix; + XMMATRIX restB = scene.GetRestPose(entityB) * scaleMatrix; XMVECTOR rootA = restA.r[3]; XMVECTOR rootB = restB.r[3]; diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 036a78c48..406c95bc7 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -3057,6 +3057,8 @@ namespace wi::scene for (size_t humanoid_idx = 0; (humanoid_idx < humanoids.GetCount()) && !constrain; ++humanoid_idx) { const HumanoidComponent& humanoid = humanoids[humanoid_idx]; + Entity humanoidEntity = humanoids.GetEntity(humanoid_idx); + const float facing = GetHumanoidDefaultFacing(humanoid, humanoidEntity); int bone_type_idx = 0; for (auto& bone : humanoid.bones) { @@ -3081,7 +3083,24 @@ namespace wi::scene } } 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++; } } @@ -3092,13 +3111,14 @@ namespace wi::scene // Apply constrained rotation: Q = XMQuaternionIdentity(); XMMATRIX W = XMLoadFloat4x4(&parent_transform.world); + const float iteration_count_rcp = 1.0f / (float)ik.iteration_count; 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; + const float axis_min = ((float*)&constraint_min)[axis_idx] * iteration_count_rcp; + const float axis_max = ((float*)&constraint_max)[axis_idx] * iteration_count_rcp; 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)); @@ -3170,6 +3190,7 @@ namespace wi::scene for (size_t i = 0; i < humanoids.GetCount(); ++i) { + Entity humanoidEntity = humanoids.GetEntity(i); HumanoidComponent& humanoid = humanoids[i]; // The head is always taken as reference frame transform even for the eyes: @@ -3184,7 +3205,7 @@ namespace wi::scene const XMVECTOR UP = XMVectorSet(0, 1, 0, 0); const XMVECTOR SIDE = XMVectorSet(1, 0, 0, 0); - const XMVECTOR FORWARD = XMLoadFloat3(&humanoid.default_look_direction); + const XMVECTOR FORWARD = XMVectorSet(0, 0, GetHumanoidDefaultFacing(humanoid, humanoidEntity), 0); struct LookAtSource { @@ -6502,9 +6523,9 @@ namespace wi::scene return INVALID_ENTITY; } - XMMATRIX Scene::FindBoneRestPose(wi::ecs::Entity bone) const + XMMATRIX Scene::GetRestPose(wi::ecs::Entity entity) const { - if (bone != INVALID_ENTITY) + if (entity != INVALID_ENTITY) { for (size_t i = 0; i < armatures.GetCount(); ++i) { @@ -6513,7 +6534,7 @@ namespace wi::scene for (auto& x : armature.boneCollection) { boneIndex++; - if (x == bone) + if (x == entity) { XMMATRIX inverseBindMatrix = XMLoadFloat4x4(armature.inverseBindMatrices.data() + boneIndex); XMMATRIX bindMatrix = XMMatrixInverse(nullptr, inverseBindMatrix); @@ -6521,10 +6542,36 @@ namespace wi::scene } } } + + const TransformComponent* transform = transforms.GetComponent(entity); + if (transform != nullptr) + { + return XMLoadFloat4x4(&transform->world); + } } return XMMatrixIdentity(); } + float Scene::GetHumanoidDefaultFacing(const HumanoidComponent& humanoid, Entity humanoidEntity) const + { + Entity left_shoulder = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftUpperArm]; + Entity right_shoulder = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightUpperArm]; + XMVECTOR left_shoulder_pos = GetRestPose(left_shoulder).r[3]; + XMVECTOR right_shoulder_pos = GetRestPose(right_shoulder).r[3]; + const TransformComponent* transform = transforms.GetComponent(humanoidEntity); + if (transform != nullptr) + { + XMVECTOR S = transform->GetScaleV(); + left_shoulder_pos *= S; + right_shoulder_pos *= S; + } + if (XMVectorGetX(right_shoulder_pos) < XMVectorGetX(left_shoulder_pos)) + { + return -1; + } + return 1; + } + void Scene::ScanAnimationDependencies() { if (animations.GetCount() == 0) @@ -6647,19 +6694,23 @@ namespace wi::scene Entity entity = spring.entity; TransformComponent& transform = *spring.transform; - XMMATRIX parentWorldMatrix = XMMatrixIdentity(); - if (spring.parent_transform != nullptr) - { - transform.UpdateTransform_Parented(*spring.parent_transform); - parentWorldMatrix = XMLoadFloat4x4(&spring.parent_transform->world); - } - - XMVECTOR position_root = transform.GetPositionV(); - if (spring.IsResetting()) { spring.Reset(false); + // Note: the spring resetting works on the rest pose, not the current pose! + + XMMATRIX parentWorldMatrix = XMMatrixIdentity(); + { + const HierarchyComponent* hier = hierarchy.GetComponent(entity); + if (hier != nullptr) + { + parentWorldMatrix = GetRestPose(hier->parentID); + } + } + XMMATRIX parentWorldMatrixInverse = XMMatrixInverse(nullptr, parentWorldMatrix); + + XMVECTOR position_root = GetRestPose(entity).r[3]; XMVECTOR tail = position_root + XMVectorSet(0, 1, 0, 0); // Search for child to find the rest pose tail position: bool child_found = false; @@ -6669,8 +6720,7 @@ namespace wi::scene Entity child = hierarchy.GetEntity(j); if (hier.parentID == entity && transforms.Contains(child)) { - const TransformComponent& child_transform = *transforms.GetComponent(child); - tail = child_transform.GetPositionV(); + tail = GetRestPose(child).r[3]; child_found = true; break; } @@ -6678,23 +6728,27 @@ namespace wi::scene if (!child_found) { // No child, try to guess tail position compared to parent (if it has parent): - const HierarchyComponent* hier = hierarchy.GetComponent(entity); - if (hier != nullptr && transforms.Contains(hier->parentID)) - { - const TransformComponent& parent_transform = *transforms.GetComponent(hier->parentID); - XMVECTOR ab = position_root - parent_transform.GetPositionV(); - tail = position_root + ab; - } + const XMVECTOR parent_pos = parentWorldMatrix.r[3]; + const XMVECTOR ab = position_root - parent_pos; + tail = position_root + ab; } XMVECTOR axis = tail - position_root; - XMMATRIX parentWorldMatrixInverse = XMMatrixInverse(nullptr, parentWorldMatrix); axis = XMVector3TransformNormal(axis, parentWorldMatrixInverse); XMStoreFloat3(&spring.boneAxis, axis); XMStoreFloat3(&spring.currentTail, tail); spring.prevTail = spring.currentTail; } + XMMATRIX parentWorldMatrix = XMMatrixIdentity(); + if (spring.parent_transform != nullptr) + { + transform.UpdateTransform_Parented(*spring.parent_transform); + parentWorldMatrix = XMLoadFloat4x4(&spring.parent_transform->world); + } + + XMVECTOR position_root = transform.GetPositionV(); + // fixup spring locations by snapping position to parent's tail: // (This is done after resetting code intentionally) if (parent_spring != nullptr) diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 9462cd76d..9b6de43af 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -513,8 +513,13 @@ namespace wi::scene wi::ecs::Entity RetargetAnimation(wi::ecs::Entity dst, wi::ecs::Entity src, bool bake_data, const Scene* src_scene = nullptr); // If you don't know which armature the bone is contained int, this function can be used to find the first such armature and return the bone's rest matrix - // If not found, return identity matrix - XMMATRIX FindBoneRestPose(wi::ecs::Entity bone) const; + // If not found, and entity has a transform, it returns transform matrix + // Otherwise, returns identity matrix + XMMATRIX GetRestPose(wi::ecs::Entity entity) const; + XMMATRIX FindBoneRestPose(wi::ecs::Entity bone) { return GetRestPose(bone); }; // back-compat of GetRestPose + + // Returns 1 if humanoid's default facing direction is forward, -1 if it's backward + float GetHumanoidDefaultFacing(const HumanoidComponent& humanoid, wi::ecs::Entity humanoidEntity) const; // All triangles of the object will be injected into the voxel grid // subtract: if false (default), voxels will be added, if true then voxels will be removed diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index d56138a1c..2d371c320 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -1935,7 +1935,6 @@ namespace wi::scene constexpr void SetLookAtEnabled(bool value = true) { if (value) { _flags |= LOOKAT; } else { _flags &= ~LOOKAT; } } constexpr void SetRagdollPhysicsEnabled(bool value = true) { if (value) { _flags |= RAGDOLL_PHYSICS; } else { _flags &= ~RAGDOLL_PHYSICS; } } - 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); diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index de63baba8..b7225ad80 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -1986,6 +1986,7 @@ namespace wi::scene } void HumanoidComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) { + XMFLOAT3 default_look_direction = XMFLOAT3(0, 0, 1); if (archive.IsReadMode()) { archive >> _flags; diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index dcff5f5ed..e2b4fa2e7 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 = 449; + const int revision = 450; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);