mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 10:47:11 +02:00
Merge pull request #8497 from hyperlogic/feature/improve-ik-quality
Raised max iterations for IK to 16 from 4
This commit is contained in:
commit
2c8ab9b2b8
2 changed files with 95 additions and 58 deletions
|
@ -1,3 +1,5 @@
|
||||||
set(TARGET_NAME animation)
|
set(TARGET_NAME animation)
|
||||||
setup_hifi_library(Network Script)
|
setup_hifi_library(Network Script)
|
||||||
link_hifi_libraries(shared model fbx)
|
link_hifi_libraries(shared model fbx)
|
||||||
|
|
||||||
|
target_nsight()
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <GLMHelpers.h>
|
#include <GLMHelpers.h>
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
#include <shared/NsightHelpers.h>
|
||||||
|
|
||||||
#include "ElbowConstraint.h"
|
#include "ElbowConstraint.h"
|
||||||
#include "SwingTwistConstraint.h"
|
#include "SwingTwistConstraint.h"
|
||||||
|
@ -144,9 +145,11 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
||||||
accumulator.clearAndClean();
|
accumulator.clearAndClean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float maxError = FLT_MAX;
|
||||||
int numLoops = 0;
|
int numLoops = 0;
|
||||||
const int MAX_IK_LOOPS = 4;
|
const int MAX_IK_LOOPS = 16;
|
||||||
while (numLoops < MAX_IK_LOOPS) {
|
const float MAX_ERROR_TOLERANCE = 0.1f; // cm
|
||||||
|
while (maxError > MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) {
|
||||||
++numLoops;
|
++numLoops;
|
||||||
|
|
||||||
// solve all targets
|
// solve all targets
|
||||||
|
@ -173,6 +176,18 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
||||||
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
|
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compute maxError
|
||||||
|
maxError = 0.0f;
|
||||||
|
for (size_t i = 0; i < targets.size(); i++) {
|
||||||
|
if (targets[i].getType() == IKTarget::Type::RotationAndPosition || targets[i].getType() == IKTarget::Type::HmdHead ||
|
||||||
|
targets[i].getType() == IKTarget::Type::HipsRelativeRotationAndPosition) {
|
||||||
|
float error = glm::length(absolutePoses[targets[i].getIndex()].trans - targets[i].getTranslation());
|
||||||
|
if (error > maxError) {
|
||||||
|
maxError = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally set the relative rotation of each tip to agree with absolute target rotation
|
// finally set the relative rotation of each tip to agree with absolute target rotation
|
||||||
|
@ -285,8 +300,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
||||||
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
|
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
|
||||||
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
||||||
// reduce angle by a fraction (for stability)
|
// reduce angle by a fraction (for stability)
|
||||||
const float fraction = 0.5f;
|
const float FRACTION = 0.5f;
|
||||||
angle *= fraction;
|
angle *= FRACTION;
|
||||||
deltaRotation = glm::angleAxis(angle, axis);
|
deltaRotation = glm::angleAxis(angle, axis);
|
||||||
|
|
||||||
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
|
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
|
||||||
|
@ -308,7 +323,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
||||||
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
|
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
|
||||||
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
|
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
|
||||||
float dotSign = copysignf(1.0f, twistPart.w);
|
float dotSign = copysignf(1.0f, twistPart.w);
|
||||||
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, fraction)) * deltaRotation;
|
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, FRACTION)) * deltaRotation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,6 +384,7 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
|
||||||
|
|
||||||
//virtual
|
//virtual
|
||||||
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||||
|
|
||||||
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;
|
||||||
|
@ -377,6 +393,9 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
||||||
if (_relativePoses.size() != underPoses.size()) {
|
if (_relativePoses.size() != underPoses.size()) {
|
||||||
loadPoses(underPoses);
|
loadPoses(underPoses);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
PROFILE_RANGE_EX("ik/relax", 0xffff00ff, 0);
|
||||||
|
|
||||||
// relax toward underPoses
|
// relax toward underPoses
|
||||||
// HACK: this relaxation needs to be constant per-frame rather than per-realtime
|
// HACK: this relaxation needs to be constant per-frame rather than per-realtime
|
||||||
// in order to prevent IK "flutter" for bad FPS. The bad news is that the good parts
|
// in order to prevent IK "flutter" for bad FPS. The bad news is that the good parts
|
||||||
|
@ -410,9 +429,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_relativePoses.empty()) {
|
if (!_relativePoses.empty()) {
|
||||||
|
|
||||||
// build a list of targets from _targetVarVec
|
// build a list of targets from _targetVarVec
|
||||||
std::vector<IKTarget> targets;
|
std::vector<IKTarget> targets;
|
||||||
computeTargets(animVars, targets, underPoses);
|
{
|
||||||
|
PROFILE_RANGE_EX("ik/computeTargets", 0xffff00ff, 0);
|
||||||
|
computeTargets(animVars, targets, underPoses);
|
||||||
|
}
|
||||||
|
|
||||||
if (targets.empty()) {
|
if (targets.empty()) {
|
||||||
// no IK targets but still need to enforce constraints
|
// no IK targets but still need to enforce constraints
|
||||||
|
@ -425,64 +448,76 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
||||||
++constraintItr;
|
++constraintItr;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// shift hips according to the _hipsOffset from the previous frame
|
|
||||||
float offsetLength = glm::length(_hipsOffset);
|
{
|
||||||
const float MIN_HIPS_OFFSET_LENGTH = 0.03f;
|
PROFILE_RANGE_EX("ik/shiftHips", 0xffff00ff, 0);
|
||||||
if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) {
|
|
||||||
// but only if offset is long enough
|
// shift hips according to the _hipsOffset from the previous frame
|
||||||
float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength);
|
float offsetLength = glm::length(_hipsOffset);
|
||||||
if (_hipsParentIndex == -1) {
|
const float MIN_HIPS_OFFSET_LENGTH = 0.03f;
|
||||||
// the hips are the root so _hipsOffset is in the correct frame
|
if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) {
|
||||||
_relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans + scaleFactor * _hipsOffset;
|
// but only if offset is long enough
|
||||||
} else {
|
float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength);
|
||||||
// the hips are NOT the root so we need to transform _hipsOffset into hips local-frame
|
if (_hipsParentIndex == -1) {
|
||||||
glm::quat hipsFrameRotation = _relativePoses[_hipsParentIndex].rot;
|
// the hips are the root so _hipsOffset is in the correct frame
|
||||||
int index = _skeleton->getParentIndex(_hipsParentIndex);
|
_relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans + scaleFactor * _hipsOffset;
|
||||||
while (index != -1) {
|
} else {
|
||||||
hipsFrameRotation *= _relativePoses[index].rot;
|
// the hips are NOT the root so we need to transform _hipsOffset into hips local-frame
|
||||||
index = _skeleton->getParentIndex(index);
|
glm::quat hipsFrameRotation = _relativePoses[_hipsParentIndex].rot;
|
||||||
|
int index = _skeleton->getParentIndex(_hipsParentIndex);
|
||||||
|
while (index != -1) {
|
||||||
|
hipsFrameRotation *= _relativePoses[index].rot;
|
||||||
|
index = _skeleton->getParentIndex(index);
|
||||||
|
}
|
||||||
|
_relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans
|
||||||
|
+ glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset);
|
||||||
}
|
}
|
||||||
_relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans
|
|
||||||
+ glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
solveWithCyclicCoordinateDescent(targets);
|
{
|
||||||
|
PROFILE_RANGE_EX("ik/ccd", 0xffff00ff, 0);
|
||||||
// measure new _hipsOffset for next frame
|
solveWithCyclicCoordinateDescent(targets);
|
||||||
// by looking for discrepancies between where a targeted endEffector is
|
|
||||||
// and where it wants to be (after IK solutions are done)
|
|
||||||
glm::vec3 newHipsOffset = Vectors::ZERO;
|
|
||||||
for (auto& target: targets) {
|
|
||||||
int targetIndex = target.getIndex();
|
|
||||||
if (targetIndex == _headIndex && _headIndex != -1) {
|
|
||||||
// special handling for headTarget
|
|
||||||
if (target.getType() == IKTarget::Type::RotationOnly) {
|
|
||||||
// we want to shift the hips to bring the underPose closer
|
|
||||||
// to where the head happens to be (overpose)
|
|
||||||
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans;
|
|
||||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
|
|
||||||
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
|
|
||||||
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
|
|
||||||
} else if (target.getType() == IKTarget::Type::HmdHead) {
|
|
||||||
// we want to shift the hips to bring the head to its designated position
|
|
||||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
|
|
||||||
_hipsOffset += target.getTranslation() - actual;
|
|
||||||
// and ignore all other targets
|
|
||||||
newHipsOffset = _hipsOffset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
|
||||||
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans;
|
|
||||||
glm::vec3 targetPosition = target.getTranslation();
|
|
||||||
newHipsOffset += targetPosition - actualPosition;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// smooth transitions by relaxing _hipsOffset toward the new value
|
{
|
||||||
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
|
PROFILE_RANGE_EX("ik/measureHipsOffset", 0xffff00ff, 0);
|
||||||
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
|
|
||||||
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
|
// measure new _hipsOffset for next frame
|
||||||
|
// by looking for discrepancies between where a targeted endEffector is
|
||||||
|
// and where it wants to be (after IK solutions are done)
|
||||||
|
glm::vec3 newHipsOffset = Vectors::ZERO;
|
||||||
|
for (auto& target: targets) {
|
||||||
|
int targetIndex = target.getIndex();
|
||||||
|
if (targetIndex == _headIndex && _headIndex != -1) {
|
||||||
|
// special handling for headTarget
|
||||||
|
if (target.getType() == IKTarget::Type::RotationOnly) {
|
||||||
|
// we want to shift the hips to bring the underPose closer
|
||||||
|
// to where the head happens to be (overpose)
|
||||||
|
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans;
|
||||||
|
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
|
||||||
|
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
|
||||||
|
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
|
||||||
|
} else if (target.getType() == IKTarget::Type::HmdHead) {
|
||||||
|
// we want to shift the hips to bring the head to its designated position
|
||||||
|
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
|
||||||
|
_hipsOffset += target.getTranslation() - actual;
|
||||||
|
// and ignore all other targets
|
||||||
|
newHipsOffset = _hipsOffset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
||||||
|
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans;
|
||||||
|
glm::vec3 targetPosition = target.getTranslation();
|
||||||
|
newHipsOffset += targetPosition - actualPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// smooth transitions by relaxing _hipsOffset toward the new value
|
||||||
|
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
|
||||||
|
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
|
||||||
|
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _relativePoses;
|
return _relativePoses;
|
||||||
|
|
Loading…
Reference in a new issue