Merge pull request #10822 from hyperlogic/feature/no-toe-wiggle

Eliminated toe wiggle while pucks are enabled.
This commit is contained in:
Andrew Meadows 2017-06-28 08:30:27 -07:00 committed by GitHub
commit 1e23fa7baa
10 changed files with 157 additions and 20 deletions

View file

@ -150,28 +150,19 @@
"children": [] "children": []
}, },
{ {
"id": "hipsManipulatorOverlay", "id": "defaultPoseOverlay",
"type": "overlay", "type": "overlay",
"data": { "data": {
"alpha": 0.0, "alpha": 0.0,
"boneSet": "hipsOnly" "alphaVar": "defaultPoseOverlayAlpha",
"boneSet": "fullBody",
"boneSetVar": "defaultPoseOverlayBoneSet"
}, },
"children": [ "children": [
{ {
"id": "hipsManipulator", "id": "defaultPose",
"type": "manipulator", "type": "defaultPose",
"data": { "data": {
"alpha": 0.0,
"alphaVar": "hipsManipulatorAlpha",
"joints": [
{
"jointName": "Hips",
"rotationType": "absolute",
"translationType": "absolute",
"rotationVar": "hipsManipulatorRotation",
"translationVar": "hipsManipulatorPosition"
}
]
}, },
"children": [] "children": []
}, },

View file

@ -0,0 +1,34 @@
//
// AnimDefaultPose.cpp
//
// Created by Anthony J. Thibault on 6/26/17.
// Copyright (c) 2017 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AnimDefaultPose.h"
AnimDefaultPose::AnimDefaultPose(const QString& id) :
AnimNode(AnimNode::Type::DefaultPose, id)
{
}
AnimDefaultPose::~AnimDefaultPose() {
}
const AnimPoseVec& AnimDefaultPose::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
if (_skeleton) {
_poses = _skeleton->getRelativeDefaultPoses();
} else {
_poses.clear();
}
return _poses;
}
const AnimPoseVec& AnimDefaultPose::getPosesInternal() const {
return _poses;
}

View file

@ -0,0 +1,36 @@
//
// AnimDefaultPose.h
//
// Created by Anthony J. Thibault on 6/26/17.
// Copyright (c) 2017 High Fidelity, Inc. All rights reserved.
//
// 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_AnimDefaultPose_h
#define hifi_AnimDefaultPose_h
#include <string>
#include "AnimNode.h"
// Always returns the default pose of the current skeleton.
class AnimDefaultPose : public AnimNode {
public:
AnimDefaultPose(const QString& id);
virtual ~AnimDefaultPose() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
protected:
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
AnimPoseVec _poses;
// no copies
AnimDefaultPose(const AnimDefaultPose&) = delete;
AnimDefaultPose& operator=(const AnimDefaultPose&) = delete;
};
#endif // hifi_AnimDefaultPose_h

View file

@ -870,9 +870,9 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength);
glm::vec3 hipsOffset = scaleFactor * _hipsOffset; glm::vec3 hipsOffset = scaleFactor * _hipsOffset;
if (_hipsParentIndex == -1) { if (_hipsParentIndex == -1) {
_relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() + hipsOffset; _relativePoses[_hipsIndex].trans() = _relativePoses[_hipsIndex].trans() + hipsOffset;
} else { } else {
auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses);
absHipsPose.trans() += hipsOffset; absHipsPose.trans() += hipsOffset;
_relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose; _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose;
} }
@ -1732,6 +1732,10 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s
break; break;
case SolutionSource::RelaxToLimitCenterPoses: case SolutionSource::RelaxToLimitCenterPoses:
blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR); blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR);
// special case for hips: copy over hips pose whether or not IK is enabled.
if (_hipsIndex >= 0 && _hipsIndex < (int)_relativePoses.size()) {
_relativePoses[_hipsIndex] = _limitCenterPoses[_hipsIndex];
}
break; break;
case SolutionSource::PreviousSolution: case SolutionSource::PreviousSolution:
// do nothing... _relativePoses is already the previous solution // do nothing... _relativePoses is already the previous solution

View file

@ -44,6 +44,7 @@ public:
StateMachine, StateMachine,
Manipulator, Manipulator,
InverseKinematics, InverseKinematics,
DefaultPose,
NumTypes NumTypes
}; };
using Pointer = std::shared_ptr<AnimNode>; using Pointer = std::shared_ptr<AnimNode>;

View file

@ -23,6 +23,7 @@
#include "AnimStateMachine.h" #include "AnimStateMachine.h"
#include "AnimManipulator.h" #include "AnimManipulator.h"
#include "AnimInverseKinematics.h" #include "AnimInverseKinematics.h"
#include "AnimDefaultPose.h"
using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
@ -35,6 +36,7 @@ static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QStri
static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
// called after children have been loaded // called after children have been loaded
// returns node on success, nullptr on failure. // returns node on success, nullptr on failure.
@ -50,6 +52,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) {
case AnimNode::Type::StateMachine: return "stateMachine"; case AnimNode::Type::StateMachine: return "stateMachine";
case AnimNode::Type::Manipulator: return "manipulator"; case AnimNode::Type::Manipulator: return "manipulator";
case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::InverseKinematics: return "inverseKinematics";
case AnimNode::Type::DefaultPose: return "defaultPose";
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
@ -109,6 +112,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::StateMachine: return loadStateMachineNode;
case AnimNode::Type::Manipulator: return loadManipulatorNode; case AnimNode::Type::Manipulator: return loadManipulatorNode;
case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode;
case AnimNode::Type::DefaultPose: return loadDefaultPoseNode;
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
@ -123,6 +127,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
case AnimNode::Type::StateMachine: return processStateMachineNode; case AnimNode::Type::StateMachine: return processStateMachineNode;
case AnimNode::Type::Manipulator: return processDoNothing; case AnimNode::Type::Manipulator: return processDoNothing;
case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::InverseKinematics: return processDoNothing;
case AnimNode::Type::DefaultPose: return processDoNothing;
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
@ -347,7 +352,8 @@ static const char* boneSetStrings[AnimOverlay::NumBoneSets] = {
"empty", "empty",
"leftHand", "leftHand",
"rightHand", "rightHand",
"hipsOnly" "hipsOnly",
"bothFeet"
}; };
static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) {
@ -517,6 +523,11 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS
return node; return node;
} }
static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
auto node = std::make_shared<AnimDefaultPose>(id);
return node;
}
void buildChildMap(std::map<QString, int>& map, AnimNode::Pointer node) { void buildChildMap(std::map<QString, int>& map, AnimNode::Pointer node) {
for (int i = 0; i < (int)node->getChildCount(); ++i) { for (int i = 0; i < (int)node->getChildCount(); ++i) {
map.insert(std::pair<QString, int>(node->getChild(i)->getID(), i)); map.insert(std::pair<QString, int>(node->getChild(i)->getID(), i));

View file

@ -35,6 +35,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) {
case LeftHandBoneSet: buildLeftHandBoneSet(); break; case LeftHandBoneSet: buildLeftHandBoneSet(); break;
case RightHandBoneSet: buildRightHandBoneSet(); break; case RightHandBoneSet: buildRightHandBoneSet(); break;
case HipsOnlyBoneSet: buildHipsOnlyBoneSet(); break; case HipsOnlyBoneSet: buildHipsOnlyBoneSet(); break;
case BothFeetBoneSet: buildBothFeetBoneSet(); break;
default: default:
case EmptyBoneSet: buildEmptyBoneSet(); break; case EmptyBoneSet: buildEmptyBoneSet(); break;
} }
@ -196,6 +197,20 @@ void AnimOverlay::buildHipsOnlyBoneSet() {
_boneSetVec[hipsJoint] = 1.0f; _boneSetVec[hipsJoint] = 1.0f;
} }
void AnimOverlay::buildBothFeetBoneSet() {
assert(_skeleton);
buildEmptyBoneSet();
int rightFoot = _skeleton->nameToJointIndex("RightFoot");
for_each_child_joint(_skeleton, rightFoot, [&](int i) {
_boneSetVec[i] = 1.0f;
});
int leftFoot = _skeleton->nameToJointIndex("LeftFoot");
for_each_child_joint(_skeleton, leftFoot, [&](int i) {
_boneSetVec[i] = 1.0f;
});
}
// for AnimDebugDraw rendering // for AnimDebugDraw rendering
const AnimPoseVec& AnimOverlay::getPosesInternal() const { const AnimPoseVec& AnimOverlay::getPosesInternal() const {
return _poses; return _poses;

View file

@ -38,6 +38,7 @@ public:
LeftHandBoneSet, LeftHandBoneSet,
RightHandBoneSet, RightHandBoneSet,
HipsOnlyBoneSet, HipsOnlyBoneSet,
BothFeetBoneSet,
NumBoneSets NumBoneSets
}; };
@ -77,6 +78,7 @@ public:
void buildLeftHandBoneSet(); void buildLeftHandBoneSet();
void buildRightHandBoneSet(); void buildRightHandBoneSet();
void buildHipsOnlyBoneSet(); void buildHipsOnlyBoneSet();
void buildBothFeetBoneSet();
// no copies // no copies
AnimOverlay(const AnimOverlay&) = delete; AnimOverlay(const AnimOverlay&) = delete;

View file

@ -27,6 +27,7 @@
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "AnimClip.h" #include "AnimClip.h"
#include "AnimInverseKinematics.h" #include "AnimInverseKinematics.h"
#include "AnimOverlay.h"
#include "AnimSkeleton.h" #include "AnimSkeleton.h"
#include "AnimUtil.h" #include "AnimUtil.h"
#include "IKTarget.h" #include "IKTarget.h"
@ -1459,13 +1460,28 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
updateFeet(leftFootEnabled, rightFootEnabled, updateFeet(leftFootEnabled, rightFootEnabled,
params.controllerPoses[ControllerType_LeftFoot], params.controllerPoses[ControllerType_RightFoot]); params.controllerPoses[ControllerType_LeftFoot], params.controllerPoses[ControllerType_RightFoot]);
if (hipsEnabled) { // if the hips or the feet are being controlled.
if (hipsEnabled || rightFootEnabled || leftFootEnabled) {
// for more predictable IK solve from the center of the joint limits, not from the underpose
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses);
// replace the feet animation with the default pose, this is to prevent unexpected toe wiggling.
_animVars.set("defaultPoseOverlayAlpha", 1.0f);
_animVars.set("defaultPoseOverlayBoneSet", (int)AnimOverlay::BothFeetBoneSet);
} else {
// augment the IK with the underPose.
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses);
// feet should follow source animation
_animVars.unset("defaultPoseOverlayAlpha");
_animVars.unset("defaultPoseOverlayBoneSet");
}
if (hipsEnabled) {
_animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition);
_animVars.set("hipsPosition", params.controllerPoses[ControllerType_Hips].trans()); _animVars.set("hipsPosition", params.controllerPoses[ControllerType_Hips].trans());
_animVars.set("hipsRotation", params.controllerPoses[ControllerType_Hips].rot()); _animVars.set("hipsRotation", params.controllerPoses[ControllerType_Hips].rot());
} else { } else {
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses);
_animVars.set("hipsType", (int)IKTarget::Type::Unknown); _animVars.set("hipsType", (int)IKTarget::Type::Unknown);
} }

View file

@ -0,0 +1,27 @@
// usage:
// node avatar-json-to-dot.js /path/to/avatar-animaton.json > out.dot
//
// Then if you have graphviz installed you can run the following command to generate a png.
// dot -Tpng out.dot > out.png
var fs = require('fs');
var filename = process.argv[2];
function dumpNodes(node) {
node.children.forEach(function (child) {
console.log(' ' + node.id + ' -> ' + child.id + ';');
dumpNodes(child);
});
}
fs.readFile(filename, 'utf8', function (err, data) {
if (err) {
console.log('error opening ' + filename + ', err = ' + err);
} else {
var graph = JSON.parse(data);
console.log('digraph graphname {');
console.log(' rankdir = "LR";');
dumpNodes(graph.root);
console.log('}');
}
});