#include "Ragdoll.h" #include #include #include #include "Animator.h" #include "Character.h" #include "CollisionMesh.h" #include "Physics.h" struct PartDefinition { const char* bone; const char* endBone; // set for hinges (elbows, knees): the far end of the part, used to find the bend float mass; float maxSwing; // degrees the part can swing around the joint, in any direction float coneTilt; // degrees the swing cone leans forward, so hips reach further forward than back float maxTwist; // degrees the part can twist along its own length, either way float minBend; // hinge range in degrees, 0 = straight float maxBend; float bendDirection; // 1 = folds forward, -1 = folds backward. Only used if the pose starts straight }; // The hand, foot, finger and neck bones ride along with the nearest part above them. // The root part has to come first, and parents have to come before their children. static const PartDefinition partDefinitions[] = { // bone endBone mass swing tilt twist bend range direction { "Hip", nullptr, 14.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, { "UpperTorso", nullptr, 22.0f, 30.0f, 0.0f, 25.0f, 0.0f, 0.0f, 0.0f }, { "Head", nullptr, 5.0f, 45.0f, 0.0f, 60.0f, 0.0f, 0.0f, 0.0f }, { "L_UpperArm", nullptr, 3.0f, 110.0f, 0.0f, 60.0f, 0.0f, 0.0f, 0.0f }, { "L_LowerArm", "L_Hand", 2.5f, 0.0f, 0.0f, 0.0f, 0.0f, 150.0f, 1.0f }, { "R_UpperArm", nullptr, 3.0f, 110.0f, 0.0f, 60.0f, 0.0f, 0.0f, 0.0f }, { "R_LowerArm", "R_Hand", 2.5f, 0.0f, 0.0f, 0.0f, 0.0f, 150.0f, 1.0f }, { "L_UpperLeg", nullptr, 9.0f, 65.0f, 35.0f, 30.0f, 0.0f, 0.0f, 0.0f }, { "L_LowerLeg", "L_Foot", 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 140.0f, -1.0f }, { "R_UpperLeg", nullptr, 9.0f, 65.0f, 35.0f, 30.0f, 0.0f, 0.0f, 0.0f }, { "R_LowerLeg", "R_Foot", 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 140.0f, -1.0f }, }; static constexpr int PART_COUNT = sizeof(partDefinitions) / sizeof(partDefinitions[0]); static constexpr float PART_MIN_SIZE = 0.05f; static constexpr float PART_MAX_SPEED = 30.0f; static constexpr float PART_MAX_ANGULAR_SPEED = 30.0f; static constexpr float SETTLE_SPEED = 0.2f; static constexpr float SETTLE_ANGULAR_SPEED = 0.4f; static constexpr float SETTLE_TIME = 0.6f; static constexpr float FALL_LIMIT = -20.0f; static constexpr float HINGE_MIN_POSE_BEND = 0.17f; // sin of 10 degrees, below this the pose counts as straight static const glm::quat IDENTITY_ROTATION(1.0f, 0.0f, 0.0f, 0.0f); // Walks the skeleton, handing every bone to the part it hangs from and recording each part's parent static void AssignBones(const AssimpNodeData& node, int currentPart, const Rig& rig, std::vector& boneParts, int (&partBones)[PART_COUNT], int (&partParents)[PART_COUNT]) { const auto info = rig.m_BoneInfoMap.find(node.name); for (int i = 0; i < PART_COUNT; i++) { if (node.name != partDefinitions[i].bone || info == rig.m_BoneInfoMap.end()) continue; partBones[i] = info->second.id; partParents[i] = i == 0 ? -1 : currentPart; currentPart = i; break; } if (info != rig.m_BoneInfoMap.end() && info->second.id >= 0 && info->second.id < (int)boneParts.size()) boneParts[info->second.id] = currentPart; for (const AssimpNodeData& child : node.children) AssignBones(child, currentPart, rig, boneParts, partBones, partParents); } static glm::vec3 SkinVertex(const RigVertex& vertex, const std::vector& bones) { glm::vec4 position(0.0f); float totalWeight = 0.0f; for (int i = 0; i < MAX_BONE_INFLUENCE; i++) { const int bone = vertex.m_BoneIDs[i]; if (bone < 0 || bone >= (int)bones.size()) continue; position += bones[bone] * glm::vec4(vertex.Position, 1.0f) * vertex.m_Weights[i]; totalWeight += vertex.m_Weights[i]; } return totalWeight > 0.0f ? glm::vec3(position) / totalWeight : vertex.Position; } Ragdoll::Ragdoll(CharacterBody& characterBody, const glm::mat4& rigMatrix, Physics& physics) { static int nextGroup = 1; group = nextGroup++; Rig* rig = characterBody.rig; Animator* animator = characterBody.animator; if (!rig || !animator || !animator->m_CurrentAnimation) { std::cout << "RAGDOLL NEEDS A RIG AND AN ANIMATION\n"; return; } // Bone matrices aren't filled in until the animator has stepped once characterBody.Step(0.001f); const std::vector poseBones = characterBody.GetBlendedBoneMatrices(); int partBones[PART_COUNT]; int partParents[PART_COUNT]; for (int i = 0; i < PART_COUNT; i++) { partBones[i] = -1; partParents[i] = -1; } boneParts.assign(poseBones.size(), 0); AssignBones(animator->m_CurrentAnimation->GetRootNode(), 0, *rig, boneParts, partBones, partParents); restBones.resize(poseBones.size()); for (int i = 0; i < (int)poseBones.size(); i++) restBones[i] = rigMatrix * poseBones[i]; // Each triangle goes to whichever part most of its skin weight belongs to std::vector partTriangles[PART_COUNT]; for (RigMesh* mesh : rig->meshes) { for (int i = 0; i + 2 < (int)mesh->indices.size(); i += 3) { const RigVertex* corners[3] = { &mesh->vertices[mesh->indices[i]], &mesh->vertices[mesh->indices[i + 1]], &mesh->vertices[mesh->indices[i + 2]] }; float partWeights[PART_COUNT] = {}; for (const RigVertex* corner : corners) { for (int j = 0; j < MAX_BONE_INFLUENCE; j++) { const int bone = corner->m_BoneIDs[j]; if (bone >= 0 && bone < (int)boneParts.size()) partWeights[boneParts[bone]] += corner->m_Weights[j]; } } int bestPart = 0; for (int j = 1; j < PART_COUNT; j++) { if (partWeights[j] > partWeights[bestPart]) bestPart = j; } partTriangles[bestPart].push_back({ glm::vec3(rigMatrix * glm::vec4(SkinVertex(*corners[0], poseBones), 1.0f)), glm::vec3(rigMatrix * glm::vec4(SkinVertex(*corners[1], poseBones), 1.0f)), glm::vec3(rigMatrix * glm::vec4(SkinVertex(*corners[2], poseBones), 1.0f)) }); } } // Parts start axis aligned around their triangles, in unit space like every other body for (int i = 0; i < PART_COUNT; i++) { glm::vec3 boundsMin(FLT_MAX); glm::vec3 boundsMax(-FLT_MAX); for (const Triangle& tri : partTriangles[i]) { boundsMin = glm::min(boundsMin, glm::min(tri.a, glm::min(tri.b, tri.c))); boundsMax = glm::max(boundsMax, glm::max(tri.a, glm::max(tri.b, tri.c))); } if (partTriangles[i].empty()) { const int bone = partBones[i] >= 0 ? partBones[i] : 0; boundsMin = boundsMax = glm::vec3(restBones[bone][3]); } const glm::vec3 center = (boundsMin + boundsMax) * 0.5f; const glm::vec3 size = glm::max(boundsMax - boundsMin, glm::vec3(PART_MIN_SIZE)); std::vector unitTriangles; unitTriangles.reserve(partTriangles[i].size()); for (const Triangle& tri : partTriangles[i]) unitTriangles.push_back({ (tri.a - center) / size, (tri.b - center) / size, (tri.c - center) / size }); Part* part = new Part(); part->restPosition = center; part->transform.t.TranslateTo(center); part->transform.t.ScaleTo(size); part->transform.t.RotateToQuaternion(IDENTITY_ROTATION); part->mesh = new CollisionMesh(unitTriangles); part->body = new p(&part->transform); part->body->mass = partDefinitions[i].mass; part->body->restitution = 0.0f; part->body->friction = 0.6f; part->body->collisionMesh = part->mesh; part->body->collisionGroup = group; part->body->maxSpeed = PART_MAX_SPEED; part->body->maxAngularSpeed = PART_MAX_ANGULAR_SPEED; physics.AddObject(part->body); parts.push_back(part); restInverses.push_back(glm::inverse(part->transform.t.GetTranslationMatrix())); } auto BonePosition = [&](const char* name, const glm::vec3& fallback) { const auto info = rig->m_BoneInfoMap.find(name); if (info == rig->m_BoneInfoMap.end()) return fallback; return glm::vec3(restBones[info->second.id] * glm::inverse(info->second.offset) * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); }; // The rig faces its local -Z, same as Character::GetForwardDirection const glm::vec3 forward = glm::normalize(glm::vec3(rigMatrix * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f))); // Joints sit on the child bone's origin. Everything starts unrotated, so local = world here. glm::vec3 jointAnchors[PART_COUNT]; jointAnchors[0] = parts[0]->restPosition; for (int i = 1; i < PART_COUNT; i++) { const PartDefinition& definition = partDefinitions[i]; if (partBones[i] < 0 || partParents[i] < 0) { std::cout << "RAGDOLL BONE NOT FOUND: " << definition.bone << "\n"; jointAnchors[i] = parts[i]->restPosition; continue; } const glm::vec3 anchor = BonePosition(definition.bone, parts[i]->restPosition); jointAnchors[i] = anchor; Part* parent = parts[partParents[i]]; Part* child = parts[i]; glm::vec3 axis = child->restPosition - anchor; axis = glm::length(axis) > 0.0001f ? glm::normalize(axis) : glm::vec3(0.0f, -1.0f, 0.0f); // Leaning only the parent's axis moves the cone without moving the part glm::vec3 coneAxis = axis; const glm::vec3 forwardOfAxis = forward - axis * glm::dot(forward, axis); if (glm::length(forwardOfAxis) > 0.001f) { const float tilt = glm::radians(definition.coneTilt); coneAxis = glm::normalize(axis * std::cos(tilt) + glm::normalize(forwardOfAxis) * std::sin(tilt)); } BallJoint* limits = new BallJoint(); limits->parent = parent->body; limits->child = child->body; limits->anchorParent = anchor - parent->restPosition; limits->anchorChild = anchor - child->restPosition; limits->axisParent = coneAxis; limits->axisChild = axis; limits->maxAngle = glm::radians(definition.maxSwing); limits->maxTwist = glm::radians(definition.maxTwist); if (definition.endBone) { const glm::vec3 upperDirection = glm::normalize(anchor - jointAnchors[partParents[i]]); const glm::vec3 lowerDirection = glm::normalize(BonePosition(definition.endBone, child->restPosition) - anchor); // A bent pose shows the real hinge. A straight one has to go by which way the limb should fold. glm::vec3 hinge = glm::cross(upperDirection, lowerDirection); if (glm::length(hinge) < HINGE_MIN_POSE_BEND) hinge = glm::cross(upperDirection, forward * definition.bendDirection); hinge = glm::length(hinge) > 0.001f ? glm::normalize(hinge) : glm::vec3(1.0f, 0.0f, 0.0f); limits->isHinge = true; limits->hingeAxisParent = hinge; limits->hingeAxisChild = hinge; limits->hingeRefParent = glm::normalize(upperDirection - hinge * glm::dot(upperDirection, hinge)); limits->hingeRefChild = glm::normalize(lowerDirection - hinge * glm::dot(lowerDirection, hinge)); limits->minBend = glm::radians(definition.minBend); limits->maxBend = glm::radians(definition.maxBend); } physics.AddJoint(limits); } // A nudge so it topples instead of balancing perfectly on its feet parts[1]->body->linearvelocity = glm::vec3(0.8f, 0.0f, 0.4f); } void Ragdoll::Update(float dt) { if (parts.empty()) return; if (parts[0]->transform.t.GetTranslationY() < FALL_LIMIT) { Reset(); return; } if (parts[0]->body->frozen) return; bool settled = true; for (Part* part : parts) { if (glm::length(part->body->linearvelocity) > SETTLE_SPEED || glm::length(part->body->angularvelocity) > SETTLE_ANGULAR_SPEED) { settled = false; break; } } settleTimer = settled ? settleTimer + dt : 0.0f; if (settleTimer < SETTLE_TIME) return; // Freezing saves the triangle tests and stops resting contacts jittering. Contact wakes it again. settleTimer = 0.0f; for (Part* part : parts) { part->body->frozen = true; part->body->linearvelocity = glm::vec3(0.0f); part->body->angularvelocity = glm::vec3(0.0f); } } void Ragdoll::Reset() { settleTimer = 0.0f; for (Part* part : parts) { part->transform.t.TranslateTo(part->restPosition); part->transform.t.RotateToQuaternion(IDENTITY_ROTATION); part->body->linearvelocity = glm::vec3(0.0f); part->body->angularvelocity = glm::vec3(0.0f); part->body->frozen = false; } } std::vector Ragdoll::GetBoneMatrices() const { std::vector bones = restBones; if (parts.empty()) return bones; std::vector partMoves; partMoves.reserve(parts.size()); for (int i = 0; i < (int)parts.size(); i++) { const t& transform = parts[i]->transform.t; partMoves.push_back(transform.GetTranslationMatrix() * transform.GetRotationMatrix() * restInverses[i]); } for (int i = 0; i < (int)bones.size(); i++) bones[i] = partMoves[boneParts[i]] * restBones[i]; return bones; }