mirror of
https://github.com/overte-org/overte.git
synced 2025-04-14 07:47:30 +02:00
Merge pull request #10595 from hyperlogic/feature/spine-spline
Spline IK target support
This commit is contained in:
commit
e3c342aa77
13 changed files with 594 additions and 49 deletions
|
@ -103,8 +103,8 @@
|
||||||
"rotationVar": "spine2Rotation",
|
"rotationVar": "spine2Rotation",
|
||||||
"typeVar": "spine2Type",
|
"typeVar": "spine2Type",
|
||||||
"weightVar": "spine2Weight",
|
"weightVar": "spine2Weight",
|
||||||
"weight": 1.0,
|
"weight": 2.0,
|
||||||
"flexCoefficients": [1.0, 0.5, 0.5]
|
"flexCoefficients": [1.0, 0.5, 0.25]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"jointName": "Head",
|
"jointName": "Head",
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
"typeVar": "headType",
|
"typeVar": "headType",
|
||||||
"weightVar": "headWeight",
|
"weightVar": "headWeight",
|
||||||
"weight": 4.0,
|
"weight": 4.0,
|
||||||
"flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25]
|
"flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"jointName": "LeftArm",
|
"jointName": "LeftArm",
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
#include "ElbowConstraint.h"
|
#include "ElbowConstraint.h"
|
||||||
#include "SwingTwistConstraint.h"
|
#include "SwingTwistConstraint.h"
|
||||||
#include "AnimationLogging.h"
|
#include "AnimationLogging.h"
|
||||||
|
#include "CubicHermiteSpline.h"
|
||||||
|
#include "AnimUtil.h"
|
||||||
|
|
||||||
AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
|
AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
|
||||||
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) :
|
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) :
|
||||||
|
@ -59,7 +61,8 @@ AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimN
|
||||||
|
|
||||||
AnimInverseKinematics::~AnimInverseKinematics() {
|
AnimInverseKinematics::~AnimInverseKinematics() {
|
||||||
clearConstraints();
|
clearConstraints();
|
||||||
_accumulators.clear();
|
_rotationAccumulators.clear();
|
||||||
|
_translationAccumulators.clear();
|
||||||
_targetVarVec.clear();
|
_targetVarVec.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,10 +75,12 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) {
|
||||||
assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size())));
|
assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size())));
|
||||||
if (_skeleton->getNumJoints() == (int)poses.size()) {
|
if (_skeleton->getNumJoints() == (int)poses.size()) {
|
||||||
_relativePoses = poses;
|
_relativePoses = poses;
|
||||||
_accumulators.resize(_relativePoses.size());
|
_rotationAccumulators.resize(_relativePoses.size());
|
||||||
|
_translationAccumulators.resize(_relativePoses.size());
|
||||||
} else {
|
} else {
|
||||||
_relativePoses.clear();
|
_relativePoses.clear();
|
||||||
_accumulators.clear();
|
_rotationAccumulators.clear();
|
||||||
|
_translationAccumulators.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,14 +180,17 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets) {
|
void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<IKTarget>& targets) {
|
||||||
// compute absolute poses that correspond to relative target poses
|
// compute absolute poses that correspond to relative target poses
|
||||||
AnimPoseVec absolutePoses;
|
AnimPoseVec absolutePoses;
|
||||||
absolutePoses.resize(_relativePoses.size());
|
absolutePoses.resize(_relativePoses.size());
|
||||||
computeAbsolutePoses(absolutePoses);
|
computeAbsolutePoses(absolutePoses);
|
||||||
|
|
||||||
// clear the accumulators before we start the IK solver
|
// clear the accumulators before we start the IK solver
|
||||||
for (auto& accumulator: _accumulators) {
|
for (auto& accumulator : _rotationAccumulators) {
|
||||||
|
accumulator.clearAndClean();
|
||||||
|
}
|
||||||
|
for (auto& accumulator : _translationAccumulators) {
|
||||||
accumulator.clearAndClean();
|
accumulator.clearAndClean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,14 +205,22 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext&
|
||||||
|
|
||||||
// solve all targets
|
// solve all targets
|
||||||
for (auto& target: targets) {
|
for (auto& target: targets) {
|
||||||
solveTargetWithCCD(context, target, absolutePoses, debug);
|
if (target.getType() == IKTarget::Type::Spline) {
|
||||||
|
solveTargetWithSpline(context, target, absolutePoses, debug);
|
||||||
|
} else {
|
||||||
|
solveTargetWithCCD(context, target, absolutePoses, debug);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// harvest accumulated rotations and apply the average
|
// harvest accumulated rotations and apply the average
|
||||||
for (int i = 0; i < (int)_relativePoses.size(); ++i) {
|
for (int i = 0; i < (int)_relativePoses.size(); ++i) {
|
||||||
if (_accumulators[i].size() > 0) {
|
if (_rotationAccumulators[i].size() > 0) {
|
||||||
_relativePoses[i].rot() = _accumulators[i].getAverage();
|
_relativePoses[i].rot() = _rotationAccumulators[i].getAverage();
|
||||||
_accumulators[i].clear();
|
_rotationAccumulators[i].clear();
|
||||||
|
}
|
||||||
|
if (_translationAccumulators[i].size() > 0) {
|
||||||
|
_relativePoses[i].trans() = _translationAccumulators[i].getAverage();
|
||||||
|
_translationAccumulators[i].clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +252,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext&
|
||||||
int parentIndex = _skeleton->getParentIndex(tipIndex);
|
int parentIndex = _skeleton->getParentIndex(tipIndex);
|
||||||
|
|
||||||
// update rotationOnly targets that don't lie on the ik chain of other ik targets.
|
// update rotationOnly targets that don't lie on the ik chain of other ik targets.
|
||||||
if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
|
if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
|
||||||
const glm::quat& targetRotation = target.getRotation();
|
const glm::quat& targetRotation = target.getRotation();
|
||||||
// compute tip's new parent-relative rotation
|
// compute tip's new parent-relative rotation
|
||||||
// Q = Qp * q --> q' = Qp^ * Q
|
// Q = Qp * q --> q' = Qp^ * Q
|
||||||
|
@ -311,10 +327,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
||||||
}
|
}
|
||||||
|
|
||||||
// store the relative rotation change in the accumulator
|
// store the relative rotation change in the accumulator
|
||||||
_accumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
|
_rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
|
||||||
|
|
||||||
|
glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans();
|
||||||
|
_translationAccumulators[tipIndex].add(tipRelativeTranslation);
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained);
|
debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,10 +441,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
||||||
}
|
}
|
||||||
|
|
||||||
// store the relative rotation change in the accumulator
|
// store the relative rotation change in the accumulator
|
||||||
_accumulators[pivotIndex].add(newRot, target.getWeight());
|
_rotationAccumulators[pivotIndex].add(newRot, target.getWeight());
|
||||||
|
|
||||||
|
glm::vec3 newTrans = _relativePoses[pivotIndex].trans();
|
||||||
|
_translationAccumulators[pivotIndex].add(newTrans);
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
debugJointMap[pivotIndex] = DebugJoint(newRot, constrained);
|
debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained);
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep track of tip's new transform as we descend towards root
|
// keep track of tip's new transform as we descend towards root
|
||||||
|
@ -444,6 +466,187 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) {
|
||||||
|
float linearDistance = glm::length(basePose.trans() - tipPose.trans());
|
||||||
|
glm::vec3 p0 = basePose.trans();
|
||||||
|
glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y);
|
||||||
|
glm::vec3 p1 = tipPose.trans();
|
||||||
|
glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y);
|
||||||
|
|
||||||
|
return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre-compute information about each joint influeced by this spline IK target.
|
||||||
|
void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) {
|
||||||
|
std::vector<SplineJointInfo> splineJointInfoVec;
|
||||||
|
|
||||||
|
// build spline between the default poses.
|
||||||
|
AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex());
|
||||||
|
AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex);
|
||||||
|
|
||||||
|
CubicHermiteSplineFunctorWithArcLength spline;
|
||||||
|
if (target.getIndex() == _headIndex) {
|
||||||
|
// set gain factors so that more curvature occurs near the tip of the spline.
|
||||||
|
const float HIPS_GAIN = 0.5f;
|
||||||
|
const float HEAD_GAIN = 1.0f;
|
||||||
|
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
|
||||||
|
} else {
|
||||||
|
spline = computeSplineFromTipAndBase(tipPose, basePose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// measure the total arc length along the spline
|
||||||
|
float totalArcLength = spline.arcLength(1.0f);
|
||||||
|
|
||||||
|
glm::vec3 baseToTip = tipPose.trans() - basePose.trans();
|
||||||
|
float baseToTipLength = glm::length(baseToTip);
|
||||||
|
glm::vec3 baseToTipNormal = baseToTip / baseToTipLength;
|
||||||
|
|
||||||
|
int index = target.getIndex();
|
||||||
|
int endIndex = _skeleton->getParentIndex(_hipsIndex);
|
||||||
|
while (index != endIndex) {
|
||||||
|
AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index);
|
||||||
|
|
||||||
|
float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength;
|
||||||
|
|
||||||
|
// compute offset from spline to the default pose.
|
||||||
|
float t = spline.arcLengthInverse(ratio * totalArcLength);
|
||||||
|
|
||||||
|
// compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis
|
||||||
|
glm::vec3 y = glm::normalize(spline.d(t));
|
||||||
|
glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X;
|
||||||
|
glm::vec3 u, v, w;
|
||||||
|
generateBasisVectors(y, x, v, u, w);
|
||||||
|
glm::mat3 m(u, v, glm::cross(u, v));
|
||||||
|
glm::quat rot = glm::normalize(glm::quat_cast(m));
|
||||||
|
|
||||||
|
AnimPose pose(glm::vec3(1.0f), rot, spline(t));
|
||||||
|
AnimPose offsetPose = pose.inverse() * defaultPose;
|
||||||
|
|
||||||
|
SplineJointInfo splineJointInfo = { index, ratio, offsetPose };
|
||||||
|
splineJointInfoVec.push_back(splineJointInfo);
|
||||||
|
index = _skeleton->getParentIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
_splineJointInfoMap[target.getIndex()] = splineJointInfoVec;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<AnimInverseKinematics::SplineJointInfo>* AnimInverseKinematics::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) {
|
||||||
|
// find or create splineJointInfo for this target
|
||||||
|
auto iter = _splineJointInfoMap.find(target.getIndex());
|
||||||
|
if (iter != _splineJointInfoMap.end()) {
|
||||||
|
return &(iter->second);
|
||||||
|
} else {
|
||||||
|
computeSplineJointInfosForIKTarget(context, target);
|
||||||
|
auto iter = _splineJointInfoMap.find(target.getIndex());
|
||||||
|
if (iter != _splineJointInfoMap.end()) {
|
||||||
|
return &(iter->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) {
|
||||||
|
|
||||||
|
std::map<int, DebugJoint> debugJointMap;
|
||||||
|
|
||||||
|
const int baseIndex = _hipsIndex;
|
||||||
|
|
||||||
|
// build spline from tip to base
|
||||||
|
AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
|
||||||
|
AnimPose basePose = absolutePoses[baseIndex];
|
||||||
|
CubicHermiteSplineFunctorWithArcLength spline;
|
||||||
|
if (target.getIndex() == _headIndex) {
|
||||||
|
// set gain factors so that more curvature occurs near the tip of the spline.
|
||||||
|
const float HIPS_GAIN = 0.5f;
|
||||||
|
const float HEAD_GAIN = 1.0f;
|
||||||
|
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
|
||||||
|
} else {
|
||||||
|
spline = computeSplineFromTipAndBase(tipPose, basePose);
|
||||||
|
}
|
||||||
|
float totalArcLength = spline.arcLength(1.0f);
|
||||||
|
|
||||||
|
// This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way)
|
||||||
|
// when the head is arched backwards very far.
|
||||||
|
glm::quat halfRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f));
|
||||||
|
if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) {
|
||||||
|
tipPose.rot() = -tipPose.rot();
|
||||||
|
}
|
||||||
|
|
||||||
|
// find or create splineJointInfo for this target
|
||||||
|
const std::vector<SplineJointInfo>* splineJointInfoVec = findOrCreateSplineJointInfo(context, target);
|
||||||
|
|
||||||
|
if (splineJointInfoVec && splineJointInfoVec->size() > 0) {
|
||||||
|
const int baseParentIndex = _skeleton->getParentIndex(baseIndex);
|
||||||
|
AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose();
|
||||||
|
|
||||||
|
// go thru splineJointInfoVec backwards (base to tip)
|
||||||
|
for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) {
|
||||||
|
const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i];
|
||||||
|
float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength);
|
||||||
|
glm::vec3 trans = spline(t);
|
||||||
|
|
||||||
|
// for head splines, preform most twist toward the tip by using ease in function. t^2
|
||||||
|
float rotT = t;
|
||||||
|
if (target.getIndex() == _headIndex) {
|
||||||
|
rotT = t * t;
|
||||||
|
}
|
||||||
|
glm::quat twistRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT));
|
||||||
|
|
||||||
|
// compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis
|
||||||
|
glm::vec3 y = glm::normalize(spline.d(t));
|
||||||
|
glm::vec3 x = twistRot * Vectors::UNIT_X;
|
||||||
|
glm::vec3 u, v, w;
|
||||||
|
generateBasisVectors(y, x, v, u, w);
|
||||||
|
glm::mat3 m(u, v, glm::cross(u, v));
|
||||||
|
glm::quat rot = glm::normalize(glm::quat_cast(m));
|
||||||
|
|
||||||
|
AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose;
|
||||||
|
|
||||||
|
// apply flex coefficent
|
||||||
|
AnimPose flexedAbsPose;
|
||||||
|
::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose);
|
||||||
|
|
||||||
|
AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose;
|
||||||
|
_rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight());
|
||||||
|
|
||||||
|
bool constrained = false;
|
||||||
|
if (splineJointInfo.jointIndex != _hipsIndex) {
|
||||||
|
// constrain the amount the spine can stretch or compress
|
||||||
|
float length = glm::length(relPose.trans());
|
||||||
|
const float EPSILON = 0.0001f;
|
||||||
|
if (length > EPSILON) {
|
||||||
|
float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans());
|
||||||
|
const float STRETCH_COMPRESS_PERCENTAGE = 0.15f;
|
||||||
|
const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE);
|
||||||
|
const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE);
|
||||||
|
if (length > MAX_LENGTH) {
|
||||||
|
relPose.trans() = (relPose.trans() / length) * MAX_LENGTH;
|
||||||
|
constrained = true;
|
||||||
|
} else if (length < MIN_LENGTH) {
|
||||||
|
relPose.trans() = (relPose.trans() / length) * MIN_LENGTH;
|
||||||
|
constrained = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
relPose.trans() = glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight());
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentAbsPose = flexedAbsPose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
debugDrawIKChain(debugJointMap, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//virtual
|
//virtual
|
||||||
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) {
|
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) {
|
||||||
// don't call this function, call overlay() instead
|
// don't call this function, call overlay() instead
|
||||||
|
@ -456,10 +659,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
||||||
// allows solutionSource to be overridden by an animVar
|
// allows solutionSource to be overridden by an animVar
|
||||||
auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource);
|
auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource);
|
||||||
|
|
||||||
if (context.getEnableDebugDrawIKConstraints()) {
|
|
||||||
debugDrawConstraints(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
|
const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
|
||||||
if (dt > MAX_OVERLAY_DT) {
|
if (dt > MAX_OVERLAY_DT) {
|
||||||
dt = MAX_OVERLAY_DT;
|
dt = MAX_OVERLAY_DT;
|
||||||
|
@ -568,7 +767,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
||||||
|
|
||||||
{
|
{
|
||||||
PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
|
PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
|
||||||
solveWithCyclicCoordinateDescent(context, targets);
|
solve(context, targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_hipsTargetIndex < 0) {
|
if (_hipsTargetIndex < 0) {
|
||||||
|
@ -578,6 +777,10 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
||||||
_hipsOffset = Vectors::ZERO;
|
_hipsOffset = Vectors::ZERO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.getEnableDebugDrawIKConstraints()) {
|
||||||
|
debugDrawConstraints(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_leftHandIndex > -1) {
|
if (_leftHandIndex > -1) {
|
||||||
|
@ -1057,7 +1260,10 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
|
||||||
|
|
||||||
_maxTargetIndex = -1;
|
_maxTargetIndex = -1;
|
||||||
|
|
||||||
for (auto& accumulator: _accumulators) {
|
for (auto& accumulator: _rotationAccumulators) {
|
||||||
|
accumulator.clearAndClean();
|
||||||
|
}
|
||||||
|
for (auto& accumulator: _translationAccumulators) {
|
||||||
accumulator.clearAndClean();
|
accumulator.clearAndClean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1138,6 +1344,7 @@ void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJoi
|
||||||
// copy debug joint rotations into the relative poses
|
// copy debug joint rotations into the relative poses
|
||||||
for (auto& debugJoint : debugJointMap) {
|
for (auto& debugJoint : debugJointMap) {
|
||||||
poses[debugJoint.first].rot() = debugJoint.second.relRot;
|
poses[debugJoint.first].rot() = debugJoint.second.relRot;
|
||||||
|
poses[debugJoint.first].trans() = debugJoint.second.relTrans;
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert relative poses to absolute
|
// convert relative poses to absolute
|
||||||
|
@ -1303,7 +1510,7 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A
|
||||||
int numJoints = (int)_relativePoses.size();
|
int numJoints = (int)_relativePoses.size();
|
||||||
for (int i = 0; i < numJoints; ++i) {
|
for (int i = 0; i < numJoints; ++i) {
|
||||||
float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot()));
|
float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot()));
|
||||||
if (_accumulators[i].isDirty()) {
|
if (_rotationAccumulators[i].isDirty()) {
|
||||||
// this joint is affected by IK --> blend toward the targetPoses rotation
|
// this joint is affected by IK --> blend toward the targetPoses rotation
|
||||||
_relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor));
|
_relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor));
|
||||||
} else {
|
} else {
|
||||||
|
@ -1337,3 +1544,46 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const {
|
||||||
|
|
||||||
|
for (auto& target : targets) {
|
||||||
|
|
||||||
|
if (target.getType() != IKTarget::Type::Spline) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int baseIndex = _hipsIndex;
|
||||||
|
|
||||||
|
// build spline
|
||||||
|
AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
|
||||||
|
AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses);
|
||||||
|
|
||||||
|
CubicHermiteSplineFunctorWithArcLength spline;
|
||||||
|
if (target.getIndex() == _headIndex) {
|
||||||
|
// set gain factors so that more curvature occurs near the tip of the spline.
|
||||||
|
const float HIPS_GAIN = 0.5f;
|
||||||
|
const float HEAD_GAIN = 1.0f;
|
||||||
|
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
|
||||||
|
} else {
|
||||||
|
spline = computeSplineFromTipAndBase(tipPose, basePose);
|
||||||
|
}
|
||||||
|
float totalArcLength = spline.arcLength(1.0f);
|
||||||
|
|
||||||
|
const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
|
// draw red and white stripped spline, parameterized by arc length.
|
||||||
|
// i.e. each stripe should be the same length.
|
||||||
|
AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix());
|
||||||
|
const int NUM_SEGMENTS = 20;
|
||||||
|
const float dArcLength = totalArcLength / NUM_SEGMENTS;
|
||||||
|
float arcLength = 0.0f;
|
||||||
|
for (int i = 0; i < NUM_SEGMENTS; i++) {
|
||||||
|
float prevT = spline.arcLengthInverse(arcLength);
|
||||||
|
float nextT = spline.arcLengthInverse(arcLength + dArcLength);
|
||||||
|
DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(spline(prevT)), geomToWorldPose.xformPoint(spline(nextT)), (i % 2) == 0 ? RED : WHITE);
|
||||||
|
arcLength += dArcLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "IKTarget.h"
|
#include "IKTarget.h"
|
||||||
|
|
||||||
#include "RotationAccumulator.h"
|
#include "RotationAccumulator.h"
|
||||||
|
#include "TranslationAccumulator.h"
|
||||||
|
|
||||||
class RotationConstraint;
|
class RotationConstraint;
|
||||||
|
|
||||||
|
@ -62,21 +63,33 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
||||||
void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets);
|
void solve(const AnimContext& context, const std::vector<IKTarget>& targets);
|
||||||
void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
|
void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
|
||||||
|
void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
|
||||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
|
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
|
||||||
struct DebugJoint {
|
struct DebugJoint {
|
||||||
DebugJoint() : relRot(), constrained(false) {}
|
DebugJoint() : relRot(), constrained(false) {}
|
||||||
DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {}
|
DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {}
|
||||||
glm::quat relRot;
|
glm::quat relRot;
|
||||||
|
glm::vec3 relTrans;
|
||||||
bool constrained;
|
bool constrained;
|
||||||
};
|
};
|
||||||
void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const;
|
void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const;
|
||||||
void debugDrawRelativePoses(const AnimContext& context) const;
|
void debugDrawRelativePoses(const AnimContext& context) const;
|
||||||
void debugDrawConstraints(const AnimContext& context) const;
|
void debugDrawConstraints(const AnimContext& context) const;
|
||||||
|
void debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const;
|
||||||
void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
|
void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
|
||||||
void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
|
void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
|
||||||
|
|
||||||
|
// used to pre-compute information about each joint influeced by a spline IK target.
|
||||||
|
struct SplineJointInfo {
|
||||||
|
int jointIndex; // joint in the skeleton that this information pertains to.
|
||||||
|
float ratio; // percentage (0..1) along the spline for this joint.
|
||||||
|
AnimPose offsetPose; // local offset from the spline to the joint.
|
||||||
|
};
|
||||||
|
|
||||||
|
void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target);
|
||||||
|
const std::vector<SplineJointInfo>* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target);
|
||||||
|
|
||||||
// for AnimDebugDraw rendering
|
// for AnimDebugDraw rendering
|
||||||
virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
|
virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
|
||||||
|
@ -109,12 +122,15 @@ protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<int, RotationConstraint*> _constraints;
|
std::map<int, RotationConstraint*> _constraints;
|
||||||
std::vector<RotationAccumulator> _accumulators;
|
std::vector<RotationAccumulator> _rotationAccumulators;
|
||||||
|
std::vector<TranslationAccumulator> _translationAccumulators;
|
||||||
std::vector<IKTargetVar> _targetVarVec;
|
std::vector<IKTargetVar> _targetVarVec;
|
||||||
AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
|
AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
|
||||||
AnimPoseVec _relativePoses; // current relative poses
|
AnimPoseVec _relativePoses; // current relative poses
|
||||||
AnimPoseVec _limitCenterPoses; // relative
|
AnimPoseVec _limitCenterPoses; // relative
|
||||||
|
|
||||||
|
std::map<int, std::vector<SplineJointInfo>> _splineJointInfoMap;
|
||||||
|
|
||||||
// experimental data for moving hips during IK
|
// experimental data for moving hips during IK
|
||||||
glm::vec3 _hipsOffset { Vectors::ZERO };
|
glm::vec3 _hipsOffset { Vectors::ZERO };
|
||||||
float _maxHipsOffsetLength{ FLT_MAX };
|
float _maxHipsOffsetLength{ FLT_MAX };
|
||||||
|
|
|
@ -44,6 +44,9 @@ void IKTarget::setType(int type) {
|
||||||
case (int)Type::HipsRelativeRotationAndPosition:
|
case (int)Type::HipsRelativeRotationAndPosition:
|
||||||
_type = Type::HipsRelativeRotationAndPosition;
|
_type = Type::HipsRelativeRotationAndPosition;
|
||||||
break;
|
break;
|
||||||
|
case (int)Type::Spline:
|
||||||
|
_type = Type::Spline;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
_type = Type::Unknown;
|
_type = Type::Unknown;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ public:
|
||||||
RotationOnly,
|
RotationOnly,
|
||||||
HmdHead,
|
HmdHead,
|
||||||
HipsRelativeRotationAndPosition,
|
HipsRelativeRotationAndPosition,
|
||||||
|
Spline,
|
||||||
Unknown
|
Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -402,16 +402,6 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::restoreJointRotation(int index, float fraction, float priority) {
|
|
||||||
// AJT: DEAD CODE?
|
|
||||||
ASSERT(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Rig::restoreJointTranslation(int index, float fraction, float priority) {
|
|
||||||
// AJT: DEAD CODE?
|
|
||||||
ASSERT(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
|
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
|
||||||
if (isIndexValid(jointIndex)) {
|
if (isIndexValid(jointIndex)) {
|
||||||
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
|
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
|
||||||
|
@ -1041,8 +1031,8 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
||||||
_animVars.set("hipsType", (int)IKTarget::Type::Unknown);
|
_animVars.set("hipsType", (int)IKTarget::Type::Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.spine2Enabled) {
|
if (params.hipsEnabled && params.spine2Enabled) {
|
||||||
_animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition);
|
_animVars.set("spine2Type", (int)IKTarget::Type::Spline);
|
||||||
_animVars.set("spine2Position", extractTranslation(params.spine2Matrix));
|
_animVars.set("spine2Position", extractTranslation(params.spine2Matrix));
|
||||||
_animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix));
|
_animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix));
|
||||||
} else {
|
} else {
|
||||||
|
@ -1052,7 +1042,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
||||||
if (params.leftArmEnabled) {
|
if (params.leftArmEnabled) {
|
||||||
_animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition);
|
_animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition);
|
||||||
_animVars.set("leftArmPosition", params.leftArmPosition);
|
_animVars.set("leftArmPosition", params.leftArmPosition);
|
||||||
_animVars.set("leftArmRotation", params.leftArmRotation);
|
_animVars.set("leftArmRotation", params.leftArmRotation);
|
||||||
} else {
|
} else {
|
||||||
_animVars.set("leftArmType", (int)IKTarget::Type::Unknown);
|
_animVars.set("leftArmType", (int)IKTarget::Type::Unknown);
|
||||||
}
|
}
|
||||||
|
@ -1102,9 +1092,9 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) {
|
||||||
_animVars.set("headPosition", params.rigHeadPosition);
|
_animVars.set("headPosition", params.rigHeadPosition);
|
||||||
_animVars.set("headRotation", params.rigHeadOrientation);
|
_animVars.set("headRotation", params.rigHeadOrientation);
|
||||||
if (params.hipsEnabled) {
|
if (params.hipsEnabled) {
|
||||||
// Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type.
|
// Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type.
|
||||||
// this will allow the spine to bend more, ensuring that it can reach the head target position.
|
// this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position.
|
||||||
_animVars.set("headType", (int)IKTarget::Type::RotationAndPosition);
|
_animVars.set("headType", (int)IKTarget::Type::Spline);
|
||||||
_animVars.unset("headWeight"); // use the default weight for this target.
|
_animVars.unset("headWeight"); // use the default weight for this target.
|
||||||
} else {
|
} else {
|
||||||
// When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff,
|
// When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff,
|
||||||
|
|
|
@ -134,10 +134,6 @@ public:
|
||||||
void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
|
void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
|
||||||
void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
|
void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
|
||||||
|
|
||||||
// legacy
|
|
||||||
void restoreJointRotation(int index, float fraction, float priority);
|
|
||||||
void restoreJointTranslation(int index, float fraction, float priority);
|
|
||||||
|
|
||||||
// if translation and rotation is identity, position will be in rig space
|
// if translation and rotation is identity, position will be in rig space
|
||||||
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
|
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
|
||||||
glm::vec3 translation, glm::quat rotation) const;
|
glm::vec3 translation, glm::quat rotation) const;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// RotationAccumulator.h
|
// RotationAccumulator.cpp
|
||||||
//
|
//
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
|
@ -27,7 +27,7 @@ void RotationAccumulator::clear() {
|
||||||
_numRotations = 0;
|
_numRotations = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RotationAccumulator::clearAndClean() {
|
void RotationAccumulator::clearAndClean() {
|
||||||
clear();
|
clear();
|
||||||
_isDirty = false;
|
_isDirty = false;
|
||||||
}
|
}
|
||||||
|
|
34
libraries/animation/src/TranslationAccumulator.cpp
Normal file
34
libraries/animation/src/TranslationAccumulator.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// TranslationAccumulator.cpp
|
||||||
|
//
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "TranslationAccumulator.h"
|
||||||
|
|
||||||
|
void TranslationAccumulator::add(const glm::vec3& translation, float weight) {
|
||||||
|
_accum += weight * translation;
|
||||||
|
_totalWeight += weight;
|
||||||
|
_isDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 TranslationAccumulator::getAverage() {
|
||||||
|
if (_totalWeight > 0.0f) {
|
||||||
|
return _accum / _totalWeight;
|
||||||
|
} else {
|
||||||
|
return glm::vec3();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslationAccumulator::clear() {
|
||||||
|
_accum *= 0.0f;
|
||||||
|
_totalWeight = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslationAccumulator::clearAndClean() {
|
||||||
|
clear();
|
||||||
|
_isDirty = false;
|
||||||
|
}
|
42
libraries/animation/src/TranslationAccumulator.h
Normal file
42
libraries/animation/src/TranslationAccumulator.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// TranslationAccumulator.h
|
||||||
|
//
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_TranslationAccumulator_h
|
||||||
|
#define hifi_TranslationAccumulator_h
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
class TranslationAccumulator {
|
||||||
|
public:
|
||||||
|
TranslationAccumulator() : _accum(0.0f, 0.0f, 0.0f), _totalWeight(0), _isDirty(false) { }
|
||||||
|
|
||||||
|
int size() const { return _totalWeight > 0.0f; }
|
||||||
|
|
||||||
|
/// \param translation translation to add
|
||||||
|
/// \param weight contribution factor of this translation to total accumulation
|
||||||
|
void add(const glm::vec3& translation, float weight = 1.0f);
|
||||||
|
|
||||||
|
glm::vec3 getAverage();
|
||||||
|
|
||||||
|
/// \return true if any translation were accumulated
|
||||||
|
bool isDirty() const { return _isDirty; }
|
||||||
|
|
||||||
|
/// \brief clear accumulated translation but don't change _isDirty
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/// \brief clear accumulated translation and set _isDirty to false
|
||||||
|
void clearAndClean();
|
||||||
|
|
||||||
|
private:
|
||||||
|
glm::vec3 _accum;
|
||||||
|
float _totalWeight;
|
||||||
|
bool _isDirty;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_TranslationAccumulator_h
|
113
libraries/shared/src/CubicHermiteSpline.h
Normal file
113
libraries/shared/src/CubicHermiteSpline.h
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
//
|
||||||
|
// CubicHermiteSpline.h
|
||||||
|
//
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_CubicHermiteSpline_h
|
||||||
|
#define hifi_CubicHermiteSpline_h
|
||||||
|
|
||||||
|
#include "GLMHelpers.h"
|
||||||
|
|
||||||
|
class CubicHermiteSplineFunctor {
|
||||||
|
public:
|
||||||
|
CubicHermiteSplineFunctor() : _p0(), _m0(), _p1(), _m1() {}
|
||||||
|
CubicHermiteSplineFunctor(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : _p0(p0), _m0(m0), _p1(p1), _m1(m1) {}
|
||||||
|
|
||||||
|
CubicHermiteSplineFunctor(const CubicHermiteSplineFunctor& orig) : _p0(orig._p0), _m0(orig._m0), _p1(orig._p1), _m1(orig._m1) {}
|
||||||
|
|
||||||
|
virtual ~CubicHermiteSplineFunctor() {}
|
||||||
|
|
||||||
|
// evalute the hermite curve at parameter t (0..1)
|
||||||
|
glm::vec3 operator()(float t) const {
|
||||||
|
float t2 = t * t;
|
||||||
|
float t3 = t2 * t;
|
||||||
|
float w0 = 2.0f * t3 - 3.0f * t2 + 1.0f;
|
||||||
|
float w1 = t3 - 2.0f * t2 + t;
|
||||||
|
float w2 = -2.0f * t3 + 3.0f * t2;
|
||||||
|
float w3 = t3 - t2;
|
||||||
|
return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaulate the first derivative of the hermite curve at parameter t (0..1)
|
||||||
|
glm::vec3 d(float t) const {
|
||||||
|
float t2 = t * t;
|
||||||
|
float w0 = -6.0f * t + 6.0f * t2;
|
||||||
|
float w1 = 1.0f - 4.0f * t + 3.0f * t2;
|
||||||
|
float w2 = 6.0f * t - 6.0f * t2;
|
||||||
|
float w3 = -2.0f * t + 3.0f * t2;
|
||||||
|
return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaulate the second derivative of the hermite curve at paramter t (0..1)
|
||||||
|
glm::vec3 d2(float t) const {
|
||||||
|
float w0 = -6.0f + 12.0f * t;
|
||||||
|
float w1 = -4.0f + 6.0f * t;
|
||||||
|
float w2 = 6.0f - 12.0f * t;
|
||||||
|
float w3 = -2.0f + 6.0f * t;
|
||||||
|
return w0 + _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
glm::vec3 _p0;
|
||||||
|
glm::vec3 _m0;
|
||||||
|
glm::vec3 _p1;
|
||||||
|
glm::vec3 _m1;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor {
|
||||||
|
public:
|
||||||
|
enum Constants { NUM_SUBDIVISIONS = 30 };
|
||||||
|
|
||||||
|
CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() {
|
||||||
|
memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1));
|
||||||
|
}
|
||||||
|
CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) {
|
||||||
|
// initialize _values with the accumulated arcLength along the spline.
|
||||||
|
const float DELTA = 1.0f / NUM_SUBDIVISIONS;
|
||||||
|
float alpha = 0.0f;
|
||||||
|
float accum = 0.0f;
|
||||||
|
_values[0] = 0.0f;
|
||||||
|
for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) {
|
||||||
|
accum += glm::distance(this->operator()(alpha),
|
||||||
|
this->operator()(alpha + DELTA));
|
||||||
|
alpha += DELTA;
|
||||||
|
_values[i] = accum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) {
|
||||||
|
memcpy(_values, orig._values, sizeof(float) * (NUM_SUBDIVISIONS + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// given the spline parameter (0..1) output the arcLength of the spline up to that point.
|
||||||
|
float arcLength(float t) const {
|
||||||
|
float index = t * NUM_SUBDIVISIONS;
|
||||||
|
int prevIndex = std::min(std::max(0, (int)glm::floor(index)), (int)NUM_SUBDIVISIONS);
|
||||||
|
int nextIndex = std::min(std::max(0, (int)glm::ceil(index)), (int)NUM_SUBDIVISIONS);
|
||||||
|
float alpha = glm::fract(index);
|
||||||
|
return lerp(_values[prevIndex], _values[nextIndex], alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// given an arcLength compute the spline parameter (0..1) that cooresponds to that arcLength.
|
||||||
|
float arcLengthInverse(float s) const {
|
||||||
|
// find first item in _values that is > s.
|
||||||
|
int nextIndex;
|
||||||
|
for (nextIndex = 0; nextIndex < NUM_SUBDIVISIONS; nextIndex++) {
|
||||||
|
if (_values[nextIndex] > s) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int prevIndex = std::min(std::max(0, nextIndex - 1), (int)NUM_SUBDIVISIONS);
|
||||||
|
float alpha = glm::clamp((s - _values[prevIndex]) / (_values[nextIndex] - _values[prevIndex]), 0.0f, 1.0f);
|
||||||
|
const float DELTA = 1.0f / NUM_SUBDIVISIONS;
|
||||||
|
return lerp(prevIndex * DELTA, nextIndex * DELTA, alpha);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
float _values[NUM_SUBDIVISIONS + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_CubicHermiteSpline_h
|
77
tests/shared/src/CubicHermiteSplineTests.cpp
Normal file
77
tests/shared/src/CubicHermiteSplineTests.cpp
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
//
|
||||||
|
// CubicHermiteSplineTests.cpp
|
||||||
|
// tests/shared/src
|
||||||
|
//
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "CubicHermiteSplineTests.h"
|
||||||
|
#include "../QTestExtensions.h"
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include "CubicHermiteSpline.h"
|
||||||
|
|
||||||
|
QTEST_MAIN(CubicHermiteSplineTests)
|
||||||
|
|
||||||
|
void CubicHermiteSplineTests::testCubicHermiteSplineFunctor() {
|
||||||
|
glm::vec3 p0(0.0f, 0.0f, 0.0f);
|
||||||
|
glm::vec3 m0(1.0f, 0.0f, 0.0f);
|
||||||
|
glm::vec3 p1(1.0f, 1.0f, 0.0f);
|
||||||
|
glm::vec3 m1(2.0f, 0.0f, 0.0f);
|
||||||
|
CubicHermiteSplineFunctor hermiteSpline(p0, m0, p1, m1);
|
||||||
|
|
||||||
|
const float EPSILON = 0.0001f;
|
||||||
|
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(p0, hermiteSpline(0.0f), EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(p1, hermiteSpline(1.0f), EPSILON);
|
||||||
|
|
||||||
|
// these values were computed offline.
|
||||||
|
const glm::vec3 oneFourth(0.203125f, 0.15625f, 0.0f);
|
||||||
|
const glm::vec3 oneHalf(0.375f, 0.5f, 0.0f);
|
||||||
|
const glm::vec3 threeFourths(0.609375f, 0.84375f, 0.0f);
|
||||||
|
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(oneFourth, hermiteSpline(0.25f), EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(oneHalf, hermiteSpline(0.5f), EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(threeFourths, hermiteSpline(0.75f), EPSILON);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CubicHermiteSplineTests::testCubicHermiteSplineFunctorWithArcLength() {
|
||||||
|
glm::vec3 p0(0.0f, 0.0f, 0.0f);
|
||||||
|
glm::vec3 m0(1.0f, 0.0f, 0.0f);
|
||||||
|
glm::vec3 p1(1.0f, 1.0f, 0.0f);
|
||||||
|
glm::vec3 m1(2.0f, 0.0f, 0.0f);
|
||||||
|
CubicHermiteSplineFunctorWithArcLength hermiteSpline(p0, m0, p1, m1);
|
||||||
|
|
||||||
|
const float EPSILON = 0.001f;
|
||||||
|
|
||||||
|
float arcLengths[5] = {
|
||||||
|
hermiteSpline.arcLength(0.0f),
|
||||||
|
hermiteSpline.arcLength(0.25f),
|
||||||
|
hermiteSpline.arcLength(0.5f),
|
||||||
|
hermiteSpline.arcLength(0.75f),
|
||||||
|
hermiteSpline.arcLength(1.0f)
|
||||||
|
};
|
||||||
|
|
||||||
|
// these values were computed offline
|
||||||
|
float referenceArcLengths[5] = {
|
||||||
|
0.0f,
|
||||||
|
0.268317f,
|
||||||
|
0.652788f,
|
||||||
|
1.07096f,
|
||||||
|
1.50267f
|
||||||
|
};
|
||||||
|
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(arcLengths[0], referenceArcLengths[0], EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(arcLengths[1], referenceArcLengths[1], EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(arcLengths[2], referenceArcLengths[2], EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(arcLengths[3], referenceArcLengths[3], EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(arcLengths[4], referenceArcLengths[4], EPSILON);
|
||||||
|
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(0.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[0]), EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(0.25f, hermiteSpline.arcLengthInverse(referenceArcLengths[1]), EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(0.5f, hermiteSpline.arcLengthInverse(referenceArcLengths[2]), EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(0.75f, hermiteSpline.arcLengthInverse(referenceArcLengths[3]), EPSILON);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(1.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[4]), EPSILON);
|
||||||
|
}
|
23
tests/shared/src/CubicHermiteSplineTests.h
Normal file
23
tests/shared/src/CubicHermiteSplineTests.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// CubicHermiteSplineTests.h
|
||||||
|
// tests/shared/src
|
||||||
|
//
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_CubicHermiteSplineTests_h
|
||||||
|
#define hifi_CubicHermiteSplineTests_h
|
||||||
|
|
||||||
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
|
class CubicHermiteSplineTests : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
private slots:
|
||||||
|
void testCubicHermiteSplineFunctor();
|
||||||
|
void testCubicHermiteSplineFunctorWithArcLength();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_TransformTests_h
|
Loading…
Reference in a new issue