From d7416434bf9c869e26a96c5503252665ca98bc84 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 26 Jun 2017 11:38:24 -0700 Subject: [PATCH 1/4] added tool to visualize avatar-animation.json using graphviz. --- tools/avatar-json-to-dot.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tools/avatar-json-to-dot.js diff --git a/tools/avatar-json-to-dot.js b/tools/avatar-json-to-dot.js new file mode 100644 index 0000000000..fcd75a99c1 --- /dev/null +++ b/tools/avatar-json-to-dot.js @@ -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('}'); + } +}); From 8602d57a577c8344962bffd4d2a11a272ac17561 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 26 Jun 2017 14:09:10 -0700 Subject: [PATCH 2/4] Eliminated to wiggle while pucks are enabled. * Added new anim node AnimDefaultPose * AnimNodeLoader was changed to support the addition of the AnimDefaultPose node * Edited default avatar-animation.json to insert an AnimOverlay and AnimDefaultPose between the IK node and the rest of the "underPose". * Rig uses this to fade in default pose for the toes when the hip/feet puck are active. This effectively deadens the toe animations, without effecting the hand animations. * Also, the rig was changed to use the LimitCenter solution when the feet are enabled but the hips are not. --- .../resources/avatar/avatar-animation.json | 21 ++++------- libraries/animation/src/AnimDefaultPose.cpp | 34 ++++++++++++++++++ libraries/animation/src/AnimDefaultPose.h | 36 +++++++++++++++++++ libraries/animation/src/AnimNode.h | 1 + libraries/animation/src/AnimNodeLoader.cpp | 13 ++++++- libraries/animation/src/AnimOverlay.cpp | 15 ++++++++ libraries/animation/src/AnimOverlay.h | 2 ++ libraries/animation/src/Rig.cpp | 20 +++++++++-- 8 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 libraries/animation/src/AnimDefaultPose.cpp create mode 100644 libraries/animation/src/AnimDefaultPose.h diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 018987b58b..a493d8e9af 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -150,28 +150,19 @@ "children": [] }, { - "id": "hipsManipulatorOverlay", + "id": "defaultPoseOverlay", "type": "overlay", "data": { "alpha": 0.0, - "boneSet": "hipsOnly" + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" }, "children": [ { - "id": "hipsManipulator", - "type": "manipulator", + "id": "defaultPose", + "type": "defaultPose", "data": { - "alpha": 0.0, - "alphaVar": "hipsManipulatorAlpha", - "joints": [ - { - "jointName": "Hips", - "rotationType": "absolute", - "translationType": "absolute", - "rotationVar": "hipsManipulatorRotation", - "translationVar": "hipsManipulatorPosition" - } - ] }, "children": [] }, diff --git a/libraries/animation/src/AnimDefaultPose.cpp b/libraries/animation/src/AnimDefaultPose.cpp new file mode 100644 index 0000000000..70bcbe7c21 --- /dev/null +++ b/libraries/animation/src/AnimDefaultPose.cpp @@ -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; +} diff --git a/libraries/animation/src/AnimDefaultPose.h b/libraries/animation/src/AnimDefaultPose.h new file mode 100644 index 0000000000..eefefac7af --- /dev/null +++ b/libraries/animation/src/AnimDefaultPose.h @@ -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 +#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 diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 10db38f42e..6d9d35b19b 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -44,6 +44,7 @@ public: StateMachine, Manipulator, InverseKinematics, + DefaultPose, NumTypes }; using Pointer = std::shared_ptr; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 2a1978127d..33f3d72756 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -23,6 +23,7 @@ #include "AnimStateMachine.h" #include "AnimManipulator.h" #include "AnimInverseKinematics.h" +#include "AnimDefaultPose.h" 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); @@ -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 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 loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); // called after children have been loaded // 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::Manipulator: return "manipulator"; case AnimNode::Type::InverseKinematics: return "inverseKinematics"; + case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -109,6 +112,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::Manipulator: return loadManipulatorNode; case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; + case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -123,6 +127,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::StateMachine: return processStateMachineNode; case AnimNode::Type::Manipulator: return processDoNothing; case AnimNode::Type::InverseKinematics: return processDoNothing; + case AnimNode::Type::DefaultPose: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -347,7 +352,8 @@ static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { "empty", "leftHand", "rightHand", - "hipsOnly" + "hipsOnly", + "bothFeet" }; static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { @@ -517,6 +523,11 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS return node; } +static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + auto node = std::make_shared(id); + return node; +} + void buildChildMap(std::map& map, AnimNode::Pointer node) { for (int i = 0; i < (int)node->getChildCount(); ++i) { map.insert(std::pair(node->getChild(i)->getID(), i)); diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index e086413dde..10594af20a 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -35,6 +35,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) { case LeftHandBoneSet: buildLeftHandBoneSet(); break; case RightHandBoneSet: buildRightHandBoneSet(); break; case HipsOnlyBoneSet: buildHipsOnlyBoneSet(); break; + case BothFeetBoneSet: buildBothFeetBoneSet(); break; default: case EmptyBoneSet: buildEmptyBoneSet(); break; } @@ -196,6 +197,20 @@ void AnimOverlay::buildHipsOnlyBoneSet() { _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 const AnimPoseVec& AnimOverlay::getPosesInternal() const { return _poses; diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index ed9439feb7..8b6e1529fc 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -38,6 +38,7 @@ public: LeftHandBoneSet, RightHandBoneSet, HipsOnlyBoneSet, + BothFeetBoneSet, NumBoneSets }; @@ -77,6 +78,7 @@ public: void buildLeftHandBoneSet(); void buildRightHandBoneSet(); void buildHipsOnlyBoneSet(); + void buildBothFeetBoneSet(); // no copies AnimOverlay(const AnimOverlay&) = delete; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3f63f226e7..3d04b0b26f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -27,6 +27,7 @@ #include "AnimationLogging.h" #include "AnimClip.h" #include "AnimInverseKinematics.h" +#include "AnimOverlay.h" #include "AnimSkeleton.h" #include "AnimUtil.h" #include "IKTarget.h" @@ -1459,13 +1460,28 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateFeet(leftFootEnabled, rightFootEnabled, 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); + + // 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("hipsPosition", params.controllerPoses[ControllerType_Hips].trans()); _animVars.set("hipsRotation", params.controllerPoses[ControllerType_Hips].rot()); } else { - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } From 2814c945821edc60f14d692df69812896407e9c2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 26 Jun 2017 17:43:51 -0700 Subject: [PATCH 3/4] Hips should no longer waggle when feet are active --- libraries/animation/src/AnimInverseKinematics.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 3e948a4f5b..5d98357ea3 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -870,9 +870,9 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); glm::vec3 hipsOffset = scaleFactor * _hipsOffset; if (_hipsParentIndex == -1) { - _relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() + hipsOffset; + _relativePoses[_hipsIndex].trans() = _relativePoses[_hipsIndex].trans() + hipsOffset; } else { - auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); + auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); absHipsPose.trans() += hipsOffset; _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose; } @@ -1732,6 +1732,10 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s break; case SolutionSource::RelaxToLimitCenterPoses: blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR); + // special case for hips: copy over hips pose whether or not IK is enabled. + if (_hipsIndex >= 0 && _hipsIndex < _relativePoses.size()) { + _relativePoses[_hipsIndex] = _limitCenterPoses[_hipsIndex]; + } break; case SolutionSource::PreviousSolution: // do nothing... _relativePoses is already the previous solution From d4122cff5ad20cf5b9db00d62f2e72c3cc9b4a61 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 27 Jun 2017 11:29:08 -0700 Subject: [PATCH 4/4] warning fix --- libraries/animation/src/AnimInverseKinematics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 5d98357ea3..d7076a443e 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1733,7 +1733,7 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s case SolutionSource::RelaxToLimitCenterPoses: blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR); // special case for hips: copy over hips pose whether or not IK is enabled. - if (_hipsIndex >= 0 && _hipsIndex < _relativePoses.size()) { + if (_hipsIndex >= 0 && _hipsIndex < (int)_relativePoses.size()) { _relativePoses[_hipsIndex] = _limitCenterPoses[_hipsIndex]; } break;