From b8b1df5799cfd2a2ca5fe443729b55662e174cd6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 25 Jul 2014 15:24:44 -0700 Subject: [PATCH] MuscleConstraints for ragdoll; velocity for verlet points --- interface/src/avatar/SkeletonModel.cpp | 113 +++++++++++++++---------- interface/src/avatar/SkeletonModel.h | 3 + 2 files changed, 73 insertions(+), 43 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 4956a2b389..3caaad1391 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -21,6 +21,7 @@ #include "Avatar.h" #include "Hand.h" #include "Menu.h" +#include "MuscleConstraint.h" #include "SkeletonModel.h" SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : @@ -507,6 +508,7 @@ void SkeletonModel::renderRagdoll() { // virtual void SkeletonModel::initRagdollPoints() { clearRagdollConstraintsAndPoints(); + _muscleConstraints.clear(); // one point for each joint int numJoints = _jointStates.size(); @@ -514,8 +516,7 @@ void SkeletonModel::initRagdollPoints() { for (int i = 0; i < numJoints; ++i) { const JointState& state = _jointStates.at(i); glm::vec3 position = state.getPosition(); - _ragdollPoints[i]._position = position; - _ragdollPoints[i]._lastPosition = position; + _ragdollPoints[i].initPosition(position); } } @@ -525,11 +526,12 @@ void SkeletonModel::buildRagdollConstraints() { const int numPoints = _ragdollPoints.size(); assert(numPoints == _jointStates.size()); + float minBone = FLT_MAX; + float maxBone = -FLT_MAX; QMultiMap families; for (int i = 0; i < numPoints; ++i) { const JointState& state = _jointStates.at(i); - const FBXJoint& joint = state.getFBXJoint(); - int parentIndex = joint.parentIndex; + int parentIndex = state.getParentIndex(); if (parentIndex == -1) { FixedConstraint* anchor = new FixedConstraint(&(_ragdollPoints[i]), glm::vec3(0.0f)); _ragdollConstraints.push_back(anchor); @@ -539,20 +541,59 @@ void SkeletonModel::buildRagdollConstraints() { _ragdollConstraints.push_back(bone); families.insert(parentIndex, i); } + float boneLength = glm::length(state.getPositionInParentFrame()); + if (boneLength > maxBone) { + maxBone = boneLength; + } else if (boneLength < minBone) { + minBone = boneLength; + } } // Joints that have multiple children effectively have rigid constraints between the children - // in the parent frame, so we add constraints between children in the same family. + // in the parent frame, so we add DistanceConstraints between children in the same family. QMultiMap::iterator itr = families.begin(); while (itr != families.end()) { QList children = families.values(itr.key()); - if (children.size() > 1) { - for (int i = 1; i < children.size(); ++i) { + int numChildren = children.size(); + if (numChildren > 1) { + for (int i = 1; i < numChildren; ++i) { DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[i-1]]), &(_ragdollPoints[children[i]])); _ragdollConstraints.push_back(bone); } + if (numChildren > 2) { + DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[numChildren-1]]), &(_ragdollPoints[children[0]])); + _ragdollConstraints.push_back(bone); + } } ++itr; } + + float MAX_STRENGTH = 0.3f; + float MIN_STRENGTH = 0.005f; + // each joint gets a MuscleConstraint to its parent + for (int i = 1; i < numPoints; ++i) { + const JointState& state = _jointStates.at(i); + int p = state.getParentIndex(); + if (p == -1) { + continue; + } + MuscleConstraint* constraint = new MuscleConstraint(&(_ragdollPoints[p]), &(_ragdollPoints[i])); + _ragdollConstraints.push_back(constraint); + _muscleConstraints.push_back(constraint); + + // Short joints are more susceptible to wiggle so we modulate the strength based on the joint's length: + // long = weak and short = strong. + constraint->setIndices(p, i); + float boneLength = glm::length(state.getPositionInParentFrame()); + + float strength = MIN_STRENGTH + (MAX_STRENGTH - MIN_STRENGTH) * (maxBone - boneLength) / (maxBone - minBone); + if (!families.contains(i)) { + // Although muscles only pull on the children not parents, nevertheless those joints that have + // parents AND children are more stable than joints at the end such as fingers. For such joints we + // bestow maximum strength which helps reduce wiggle. + strength = MAX_MUSCLE_STRENGTH; + } + constraint->setStrength(strength); + } } @@ -600,7 +641,8 @@ void SkeletonModel::updateVisibleJointStates() { // virtual void SkeletonModel::stepRagdollForward(float deltaTime) { - moveShapesTowardJoints(deltaTime); + Ragdoll::stepRagdollForward(deltaTime); + updateMuscles(); } float DENSITY_OF_WATER = 1000.0f; // kg/m^3 @@ -668,14 +710,16 @@ void SkeletonModel::buildShapes() { if (_ragdollPoints.size() == numStates) { int numJoints = _jointStates.size(); for (int i = 0; i < numJoints; ++i) { - _ragdollPoints[i]._lastPosition = _ragdollPoints.at(i)._position; - _ragdollPoints[i]._position = _jointStates.at(i).getPosition(); + _ragdollPoints[i].initPosition(_jointStates.at(i).getPosition()); } } enforceRagdollConstraints(); } void SkeletonModel::moveShapesTowardJoints(float deltaTime) { + // KEEP: although we don't currently use this method we may eventually need it to help + // unravel a skelton that has become tangled in its constraints. So let's keep this + // around for a while just in case. const int numStates = _jointStates.size(); assert(_jointStates.size() == _ragdollPoints.size()); if (_ragdollPoints.size() != numStates) { @@ -686,41 +730,26 @@ void SkeletonModel::moveShapesTowardJoints(float deltaTime) { const float RAGDOLL_FOLLOWS_JOINTS_TIMESCALE = 0.05f; float fraction = glm::clamp(deltaTime / RAGDOLL_FOLLOWS_JOINTS_TIMESCALE, 0.0f, 1.0f); - // SIMPLE LINEAR SLAVING -- KEEP this implementation for reference - //float oneMinusFraction = 1.0f - fraction; - //for (int i = 0; i < numStates; ++i) { - // _ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position; - // _ragdollPoints[i]._position = oneMinusFraction * _ragdollPoints[i]._position + fraction * _jointStates.at(i).getPosition(); - //} - // SIMPLE LINEAR SLAVING -- KEEP - - // parent-relative linear slaving + float oneMinusFraction = 1.0f - fraction; for (int i = 0; i < numStates; ++i) { - JointState& state = _jointStates[i]; - _ragdollPoints[i]._lastPosition = _ragdollPoints.at(i)._position; + _ragdollPoints[i].initPosition(oneMinusFraction * _ragdollPoints[i]._position + + fraction * _jointStates.at(i).getPosition()); + } +} - int p = state.getParentIndex(); - if (p == -1) { - _ragdollPoints[i]._position = glm::vec3(0.0f); +void SkeletonModel::updateMuscles() { + int numConstraints = _muscleConstraints.size(); + for (int i = 0; i < numConstraints; ++i) { + MuscleConstraint* constraint = _muscleConstraints[i]; + int j = constraint->getParentIndex(); + if (j == -1) { continue; } - if (state.getDistanceToParent() < EPSILON) { - _ragdollPoints[i]._position = _ragdollPoints.at(p)._position; + int k = constraint->getChildIndex(); + if (k == -1) { continue; } - - glm::vec3 bone = _ragdollPoints.at(i)._lastPosition - _ragdollPoints.at(p)._lastPosition; - const JointState& parentState = _jointStates.at(p); - glm::vec3 targetBone = state.getPosition() - parentState.getPosition(); - - glm::vec3 newBone = (1.0f - fraction) * bone + fraction * targetBone; - float boneLength = glm::length(newBone); - if (boneLength > EPSILON) { - // slam newBone's length to that of the joint helps maintain distance constraints - newBone *= state.getDistanceToParent() / boneLength; - } - // set the new position relative to parent's new position - _ragdollPoints[i]._position = _ragdollPoints.at(p)._position + newBone; + constraint->setChildOffset(_jointStates.at(k).getPosition() - _jointStates.at(j).getPosition()); } } @@ -740,8 +769,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { int parentIndex = joint.parentIndex; if (parentIndex == -1) { transforms[i] = _jointStates[i].getTransform(); - _ragdollPoints[i]._position = extractTranslation(transforms[i]); - _ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position; + _ragdollPoints[i].initPosition(extractTranslation(transforms[i])); continue; } @@ -749,8 +777,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform; // setting the ragdollPoints here slams the VerletShapes into their default positions - _ragdollPoints[i]._position = extractTranslation(transforms[i]); - _ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position; + _ragdollPoints[i].initPosition(extractTranslation(transforms[i])); } // compute bounding box that encloses all shapes diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 7a88d890ac..5cb89f5e44 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -18,6 +18,7 @@ #include class Avatar; +class MuscleConstraint; /// A skeleton loaded from a model. class SkeletonModel : public Model, public Ragdoll { @@ -100,6 +101,7 @@ public: virtual void stepRagdollForward(float deltaTime); void moveShapesTowardJoints(float fraction); + void updateMuscles(); void computeBoundingShape(const FBXGeometry& geometry); void renderBoundingCollisionShapes(float alpha); @@ -144,6 +146,7 @@ private: CapsuleShape _boundingShape; glm::vec3 _boundingShapeLocalOffset; + QVector _muscleConstraints; }; #endif // hifi_SkeletonModel_h