From fa221fa7e391ae664c77d3ccb1fa181a531442b3 Mon Sep 17 00:00:00 2001 From: Christoph Haag Date: Tue, 8 Sep 2015 09:44:20 +0200 Subject: [PATCH 01/23] In gcc5 nullptr can't be directly used as bool See https://gcc.gnu.org/gcc-5/porting_to.html "Converting std::nullptr_t to bool" --- libraries/animation/src/AnimNodeLoader.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 5f0260db6d..27ff845557 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -75,7 +75,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { qCCritical(animation) << "AnimNodeLoader, error reading string" \ << #NAME << ", id =" << ID \ << ", url =" << URL.toDisplayString(); \ - return nullptr; \ + return (bool) nullptr; \ } \ QString NAME = NAME##_VAL.toString() @@ -102,7 +102,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { qCCritical(animation) << "AnimNodeLoader, error reading double" \ << #NAME << "id =" << ID \ << ", url =" << URL.toDisplayString(); \ - return nullptr; \ + return (bool) nullptr; \ } \ float NAME = (float)NAME##_VAL.toDouble() @@ -283,7 +283,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, auto statesValue = jsonObj.value("states"); if (!statesValue.isArray()) { qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in stateMachine node, id =" << nodeId << ", url =" << jsonUrl.toDisplayString(); - return nullptr; + return (bool) nullptr; } // build a map for all children by name. @@ -302,7 +302,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, for (const auto& stateValue : statesArray) { if (!stateValue.isObject()) { qCCritical(animation) << "AnimNodeLoader, bad state object in \"states\", id =" << nodeId << ", url =" << jsonUrl.toDisplayString(); - return nullptr; + return (bool) nullptr; } auto stateObj = stateValue.toObject(); @@ -318,7 +318,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, auto iter = childMap.find(stdId); if (iter == childMap.end()) { qCCritical(animation) << "AnimNodeLoader, could not find stateMachine child (state) with nodeId =" << nodeId << "stateId =" << id << "url =" << jsonUrl.toDisplayString(); - return nullptr; + return (bool) nullptr; } auto statePtr = std::make_shared(stdId, iter->second, interpTarget, interpDuration); @@ -337,14 +337,14 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, auto transitionsValue = stateObj.value("transitions"); if (!transitionsValue.isArray()) { qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in stateMachine node, stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString(); - return nullptr; + return (bool) nullptr; } auto transitionsArray = transitionsValue.toArray(); for (const auto& transitionValue : transitionsArray) { if (!transitionValue.isObject()) { qCritical(animation) << "AnimNodeLoader, bad transition object in \"transtions\", stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString(); - return nullptr; + return (bool) nullptr; } auto transitionObj = transitionValue.toObject(); @@ -363,7 +363,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, srcState->addTransition(AnimStateMachine::State::Transition(transition.second.first, iter->second)); } else { qCCritical(animation) << "AnimNodeLoader, bad state machine transtion from srcState =" << srcState->_id.c_str() << "dstState =" << transition.second.second.c_str() << "nodeId =" << nodeId << "url = " << jsonUrl.toDisplayString(); - return nullptr; + return (bool) nullptr; } } From 6a8f131a8351b99bea89143876ea862d086af253 Mon Sep 17 00:00:00 2001 From: Christoph Haag Date: Tue, 8 Sep 2015 09:47:57 +0200 Subject: [PATCH 02/23] move assert into ifndef QT_NO_DEBUG it belongs to --- tests/entities/src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp index a94bda9a86..807a57cdb8 100644 --- a/tests/entities/src/main.cpp +++ b/tests/entities/src/main.cpp @@ -84,8 +84,8 @@ void testByteCountCodedStable(const T& value) { Q_ASSERT(decoder.data == coder.data); #ifndef QT_NO_DEBUG auto consumed = decoder.decode(encoded.data(), encoded.size()); - #endif Q_ASSERT(consumed == (unsigned int)originalEncodedSize); + #endif } @@ -124,9 +124,9 @@ void testPropertyFlags(uint32_t value) { { #ifndef QT_NO_DEBUG int decodeSize = decodeNew.decode((const uint8_t*)encoded.data(), encoded.size()); - #endif Q_ASSERT(originalSize == decodeSize); Q_ASSERT(decodeNew == original); + #endif } } From ddcee763ca68db0c96c5721c42c85df881d22374 Mon Sep 17 00:00:00 2001 From: Christoph Haag Date: Tue, 8 Sep 2015 19:10:15 +0200 Subject: [PATCH 03/23] extend macros to take return value if necessary --- libraries/animation/src/AnimNodeLoader.cpp | 52 +++++++++++----------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 27ff845557..60037b146e 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -69,13 +69,13 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { return nullptr; } -#define READ_STRING(NAME, JSON_OBJ, ID, URL) \ +#define READ_STRING(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isString()) { \ qCCritical(animation) << "AnimNodeLoader, error reading string" \ << #NAME << ", id =" << ID \ << ", url =" << URL.toDisplayString(); \ - return (bool) nullptr; \ + return ERROR_RETURN; \ } \ QString NAME = NAME##_VAL.toString() @@ -86,23 +86,23 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { NAME = NAME##_VAL.toString(); \ } -#define READ_BOOL(NAME, JSON_OBJ, ID, URL) \ +#define READ_BOOL(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isBool()) { \ qCCritical(animation) << "AnimNodeLoader, error reading bool" \ << #NAME << ", id =" << ID \ << ", url =" << URL.toDisplayString(); \ - return nullptr; \ + return ERROR_RETURN; \ } \ bool NAME = NAME##_VAL.toBool() -#define READ_FLOAT(NAME, JSON_OBJ, ID, URL) \ +#define READ_FLOAT(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isDouble()) { \ qCCritical(animation) << "AnimNodeLoader, error reading double" \ << #NAME << "id =" << ID \ << ", url =" << URL.toDisplayString(); \ - return (bool) nullptr; \ + return ERROR_RETURN; \ } \ float NAME = (float)NAME##_VAL.toDouble() @@ -171,11 +171,11 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { - READ_STRING(url, jsonObj, id, jsonUrl); - READ_FLOAT(startFrame, jsonObj, id, jsonUrl); - READ_FLOAT(endFrame, jsonObj, id, jsonUrl); - READ_FLOAT(timeScale, jsonObj, id, jsonUrl); - READ_BOOL(loopFlag, jsonObj, id, jsonUrl); + READ_STRING(url, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(startFrame, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(endFrame, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr); + READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr); READ_OPTIONAL_STRING(startFrameVar, jsonObj); READ_OPTIONAL_STRING(endFrameVar, jsonObj); @@ -202,7 +202,7 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { - READ_FLOAT(alpha, jsonObj, id, jsonUrl); + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_OPTIONAL_STRING(alphaVar, jsonObj); @@ -239,8 +239,8 @@ static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { - READ_STRING(boneSet, jsonObj, id, jsonUrl); - READ_FLOAT(alpha, jsonObj, id, jsonUrl); + READ_STRING(boneSet, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); auto boneSetEnum = stringToBoneSetEnum(boneSet); if (boneSetEnum == AnimOverlay::NumBoneSets) { @@ -278,12 +278,12 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, auto smNode = std::static_pointer_cast(node); assert(smNode); - READ_STRING(currentState, jsonObj, nodeId, jsonUrl); + READ_STRING(currentState, jsonObj, nodeId, jsonUrl, false); auto statesValue = jsonObj.value("states"); if (!statesValue.isArray()) { qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in stateMachine node, id =" << nodeId << ", url =" << jsonUrl.toDisplayString(); - return (bool) nullptr; + return false; } // build a map for all children by name. @@ -302,13 +302,13 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, for (const auto& stateValue : statesArray) { if (!stateValue.isObject()) { qCCritical(animation) << "AnimNodeLoader, bad state object in \"states\", id =" << nodeId << ", url =" << jsonUrl.toDisplayString(); - return (bool) nullptr; + return false; } auto stateObj = stateValue.toObject(); - READ_STRING(id, stateObj, nodeId, jsonUrl); - READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl); - READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl); + READ_STRING(id, stateObj, nodeId, jsonUrl, false); + READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false); + READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false); READ_OPTIONAL_STRING(interpTargetVar, stateObj); READ_OPTIONAL_STRING(interpDurationVar, stateObj); @@ -318,7 +318,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, auto iter = childMap.find(stdId); if (iter == childMap.end()) { qCCritical(animation) << "AnimNodeLoader, could not find stateMachine child (state) with nodeId =" << nodeId << "stateId =" << id << "url =" << jsonUrl.toDisplayString(); - return (bool) nullptr; + return false; } auto statePtr = std::make_shared(stdId, iter->second, interpTarget, interpDuration); @@ -337,19 +337,19 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, auto transitionsValue = stateObj.value("transitions"); if (!transitionsValue.isArray()) { qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in stateMachine node, stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString(); - return (bool) nullptr; + return false; } auto transitionsArray = transitionsValue.toArray(); for (const auto& transitionValue : transitionsArray) { if (!transitionValue.isObject()) { qCritical(animation) << "AnimNodeLoader, bad transition object in \"transtions\", stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString(); - return (bool) nullptr; + return false; } auto transitionObj = transitionValue.toObject(); - READ_STRING(var, transitionObj, nodeId, jsonUrl); - READ_STRING(state, transitionObj, nodeId, jsonUrl); + READ_STRING(var, transitionObj, nodeId, jsonUrl, false); + READ_STRING(state, transitionObj, nodeId, jsonUrl, false); transitionMap.insert(TransitionMap::value_type(statePtr, StringPair(var.toStdString(), state.toStdString()))); } @@ -363,7 +363,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, srcState->addTransition(AnimStateMachine::State::Transition(transition.second.first, iter->second)); } else { qCCritical(animation) << "AnimNodeLoader, bad state machine transtion from srcState =" << srcState->_id.c_str() << "dstState =" << transition.second.second.c_str() << "nodeId =" << nodeId << "url = " << jsonUrl.toDisplayString(); - return (bool) nullptr; + return false; } } From c3e16d33c1aed162218e1db370167bfd19c26df2 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 18:18:50 -0700 Subject: [PATCH 04/23] add InverseKinematics type --- libraries/animation/src/AnimNode.h | 1 + libraries/animation/src/AnimNodeLoader.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 4675ae358f..5c92dd0ecc 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -40,6 +40,7 @@ public: BlendLinear, Overlay, StateMachine, + InverseKinematics, NumTypes }; using Pointer = std::shared_ptr; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 866f443005..59d06442ab 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -42,6 +42,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::BlendLinear: return "blendLinear"; case AnimNode::Type::Overlay: return "overlay"; case AnimNode::Type::StateMachine: return "stateMachine"; + case AnimNode::Type::InverseKinematics: return nullptr; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -53,6 +54,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::BlendLinear: return loadBlendLinearNode; case AnimNode::Type::Overlay: return loadOverlayNode; case AnimNode::Type::StateMachine: return loadStateMachineNode; + case AnimNode::Type::InverseKinematics: return nullptr; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -64,6 +66,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::BlendLinear: return processBlendLinearNode; case AnimNode::Type::Overlay: return processOverlayNode; case AnimNode::Type::StateMachine: return processStateMachineNode; + case AnimNode::Type::InverseKinematics: return nullptr; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; From 00c446d1b01320b39cb5caca0e0d56a0e5e87d5c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 18:19:18 -0700 Subject: [PATCH 05/23] sort includes --- libraries/animation/src/AnimSkeleton.cpp | 8 +++++--- libraries/animation/src/AnimSkeleton.h | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 5dfed265a2..a112eb648d 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -9,10 +9,12 @@ // #include "AnimSkeleton.h" -#include "AnimationLogging.h" -#include "GLMHelpers.h" + #include -#include + +#include + +#include "AnimationLogging.h" const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), glm::quat(), diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 4a6c3c0087..e1976d929b 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -12,8 +12,10 @@ #define hifi_AnimSkeleton #include +#include +#include -#include "FBXReader.h" +#include struct AnimPose { AnimPose() {} @@ -58,6 +60,7 @@ public: // relative to parent pose const AnimPose& getRelativeBindPose(int jointIndex) const; + const AnimPoseVec& getRelativeBindPoses() const { return _relativeBindPoses; } int getParentIndex(int jointIndex) const; From b6cef3d1a91b9413af0728208f7658cb32a47bf2 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 18:19:33 -0700 Subject: [PATCH 06/23] fix Qt includes --- libraries/animation/src/AnimNodeLoader.h | 2 +- libraries/animation/src/AnimationCache.h | 6 +++--- libraries/networking/src/ResourceCache.h | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index 713d980f06..bc7d574c39 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -15,7 +15,7 @@ #include #include -#include +#include #include "AnimNode.h" diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 50184862eb..3a8fbf3a61 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -12,9 +12,9 @@ #ifndef hifi_AnimationCache_h #define hifi_AnimationCache_h -#include -#include -#include +#include +#include +#include #include #include diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index a11ee30174..3bae7f5b9d 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -12,17 +12,17 @@ #ifndef hifi_ResourceCache_h #define hifi_ResourceCache_h -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include From 3d661095dcf82f47750db895569190bca043e25b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 18:20:03 -0700 Subject: [PATCH 07/23] fix some bugs in RotationConstraints --- libraries/animation/src/ElbowConstraint.cpp | 1 - libraries/animation/src/ElbowConstraint.h | 2 - libraries/animation/src/RotationConstraint.h | 12 +- .../animation/src/SwingTwistConstraint.cpp | 108 +++++++++++++++--- .../animation/src/SwingTwistConstraint.h | 15 ++- 5 files changed, 113 insertions(+), 25 deletions(-) diff --git a/libraries/animation/src/ElbowConstraint.cpp b/libraries/animation/src/ElbowConstraint.cpp index a6b2210644..6833c1762e 100644 --- a/libraries/animation/src/ElbowConstraint.cpp +++ b/libraries/animation/src/ElbowConstraint.cpp @@ -15,7 +15,6 @@ #include ElbowConstraint::ElbowConstraint() : - _referenceRotation(), _minAngle(-PI), _maxAngle(PI) { diff --git a/libraries/animation/src/ElbowConstraint.h b/libraries/animation/src/ElbowConstraint.h index 3c4481cfa8..21288715b5 100644 --- a/libraries/animation/src/ElbowConstraint.h +++ b/libraries/animation/src/ElbowConstraint.h @@ -15,12 +15,10 @@ class ElbowConstraint : public RotationConstraint { public: ElbowConstraint(); - virtual void setReferenceRotation(const glm::quat& rotation) override { _referenceRotation = rotation; } void setHingeAxis(const glm::vec3& axis); void setAngleLimits(float minAngle, float maxAngle); virtual bool apply(glm::quat& rotation) const override; protected: - glm::quat _referenceRotation; glm::vec3 _axis; glm::vec3 _perpAxis; float _minAngle; diff --git a/libraries/animation/src/RotationConstraint.h b/libraries/animation/src/RotationConstraint.h index a05c0d0526..6926302ec8 100644 --- a/libraries/animation/src/RotationConstraint.h +++ b/libraries/animation/src/RotationConstraint.h @@ -15,15 +15,21 @@ class RotationConstraint { public: - RotationConstraint() {} + RotationConstraint() : _referenceRotation() {} virtual ~RotationConstraint() {} - /// \param rotation the default rotation that represents - virtual void setReferenceRotation(const glm::quat& rotation) = 0; + /// \param referenceRotation the rotation from which rotation changes are measured. + virtual void setReferenceRotation(const glm::quat& rotation) { _referenceRotation = rotation; } + + /// \return the rotation from which rotation changes are measured. + const glm::quat& getReferenceRotation() const { return _referenceRotation; } /// \param rotation rotation to clamp /// \return true if rotation is clamped virtual bool apply(glm::quat& rotation) const = 0; + +protected: + glm::quat _referenceRotation = glm::quat(); }; #endif // hifi_RotationConstraint_h diff --git a/libraries/animation/src/SwingTwistConstraint.cpp b/libraries/animation/src/SwingTwistConstraint.cpp index 96140f4deb..cd50588f02 100644 --- a/libraries/animation/src/SwingTwistConstraint.cpp +++ b/libraries/animation/src/SwingTwistConstraint.cpp @@ -32,14 +32,20 @@ void SwingTwistConstraint::SwingLimitFunction::setCone(float maxAngle) { } void SwingTwistConstraint::SwingLimitFunction::setMinDots(const std::vector& minDots) { - int numDots = minDots.size(); + uint32_t numDots = minDots.size(); _minDots.clear(); - _minDots.reserve(numDots); - for (int i = 0; i < numDots; ++i) { - _minDots.push_back(glm::clamp(minDots[i], MIN_MINDOT, MAX_MINDOT)); + if (numDots == 0) { + // push two copies of MIN_MINDOT + _minDots.push_back(MIN_MINDOT); + _minDots.push_back(MIN_MINDOT); + } else { + _minDots.reserve(numDots); + for (uint32_t i = 0; i < numDots; ++i) { + _minDots.push_back(glm::clamp(minDots[i], MIN_MINDOT, MAX_MINDOT)); + } + // push the first value to the back to establish cyclic boundary conditions + _minDots.push_back(_minDots[0]); } - // push the first value to the back to establish cyclic boundary conditions - _minDots.push_back(_minDots[0]); } float SwingTwistConstraint::SwingLimitFunction::getMinDot(float theta) const { @@ -58,8 +64,8 @@ float SwingTwistConstraint::SwingLimitFunction::getMinDot(float theta) const { } SwingTwistConstraint::SwingTwistConstraint() : + RotationConstraint(), _swingLimitFunction(), - _referenceRotation(), _minTwist(-PI), _maxTwist(PI) { @@ -69,6 +75,85 @@ void SwingTwistConstraint::setSwingLimits(std::vector minDots) { _swingLimitFunction.setMinDots(minDots); } +void SwingTwistConstraint::setSwingLimits(const std::vector& swungDirections) { + struct SwingLimitData { + SwingLimitData() : _theta(0.0f), _minDot(1.0f) {} + SwingLimitData(float theta, float minDot) : _theta(theta), _minDot(minDot) {} + float _theta; + float _minDot; + bool operator<(const SwingLimitData& other) const { return _theta < other._theta; } + }; + std::vector limits; + + uint32_t numLimits = swungDirections.size(); + limits.reserve(numLimits); + + // compute the limit pairs: + const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f); + for (uint32_t i = 0; i < numLimits; ++i) { + float directionLength = glm::length(swungDirections[i]); + if (directionLength > EPSILON) { + glm::vec3 swingAxis = glm::cross(yAxis, swungDirections[i]); + float theta = atan2f(-swingAxis.z, swingAxis.x); + if (theta < 0.0f) { + theta += TWO_PI; + } + limits.push_back(SwingLimitData(theta, swungDirections[i].y / directionLength)); + } + } + + std::vector minDots; + numLimits = limits.size(); + if (numLimits == 0) { + // trivial case: nearly free constraint + std::vector minDots; + _swingLimitFunction.setMinDots(minDots); + } else if (numLimits == 1) { + // trivial case: uniform conical constraint + std::vector minDots; + minDots.push_back(limits[0]._minDot); + _swingLimitFunction.setMinDots(minDots); + } else { + // interesting case: potentially non-uniform constraints + + // sort limits by theta + std::sort(limits.begin(), limits.end()); + + // extrapolate evenly distributed limits for fast lookup table + float deltaTheta = TWO_PI / (float)(numLimits); + uint32_t rightIndex = 0; + for (uint32_t i = 0; i < numLimits; ++i) { + float theta = (float)i * deltaTheta; + uint32_t leftIndex = (rightIndex - 1) % numLimits; + while (rightIndex < numLimits && theta > limits[rightIndex]._theta) { + leftIndex = rightIndex++; + } + + if (leftIndex == numLimits - 1) { + // we straddle the boundary + rightIndex = 0; + } + + float rightTheta = limits[rightIndex]._theta; + float leftTheta = limits[leftIndex]._theta; + if (leftTheta > rightTheta) { + // we straddle the boundary, but we need to figure out which way to stride + // in order to keep theta between left and right + if (leftTheta > theta) { + leftTheta -= TWO_PI; + } else { + rightTheta += TWO_PI; + } + } + + // blend between the left and right minDots to get the value that corresponds to this theta + float rightWeight = (theta - leftTheta) / (rightTheta - leftTheta); + minDots.push_back((1.0f - rightWeight) * limits[leftIndex]._minDot + rightWeight * limits[rightIndex]._minDot); + } + } + _swingLimitFunction.setMinDots(minDots); +} + void SwingTwistConstraint::setTwistLimits(float minTwist, float maxTwist) { // NOTE: min/maxTwist angles should be in the range [-PI, PI] _minTwist = glm::min(minTwist, maxTwist); @@ -107,13 +192,10 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const { float theta = atan2f(-swingAxis.z, swingAxis.x); float minDot = _swingLimitFunction.getMinDot(theta); if (glm::dot(swungY, yAxis) < minDot) { - // The swing limits are violated so we use the maxAngle to supply a new rotation. - float maxAngle = acosf(minDot); - if (minDot < 0.0f) { - maxAngle = PI - maxAngle; - } + // The swing limits are violated so we extract the angle from midDot and + // use it to supply a new rotation. swingAxis /= axisLength; - swingRotation = glm::angleAxis(maxAngle, swingAxis); + swingRotation = glm::angleAxis(acosf(minDot), swingAxis); swingWasClamped = true; } } diff --git a/libraries/animation/src/SwingTwistConstraint.h b/libraries/animation/src/SwingTwistConstraint.h index c5e6d9e569..b516e984b4 100644 --- a/libraries/animation/src/SwingTwistConstraint.h +++ b/libraries/animation/src/SwingTwistConstraint.h @@ -16,7 +16,7 @@ #include -class SwingTwistConstraint : RotationConstraint { +class SwingTwistConstraint : public RotationConstraint { public: // The SwingTwistConstraint starts in the "referenceRotation" and then measures an initial twist // about the yAxis followed by a swing about some axis that lies in the XZ plane, such that the twist @@ -25,10 +25,7 @@ public: SwingTwistConstraint(); - /// \param referenceRotation the rotation from which rotation changes are measured. - virtual void setReferenceRotation(const glm::quat& referenceRotation) override { _referenceRotation = referenceRotation; } - - /// \param minDots vector of minimum dot products between the twist and swung axes. + /// \param minDots vector of minimum dot products between the twist and swung axes /// \brief The values are minimum dot-products between the twist axis and the swung axes /// that correspond to swing axes equally spaced around the XZ plane. Another way to /// think about it is that the dot-products correspond to correspond to angles (theta) @@ -38,6 +35,13 @@ public: /// description of how this works. void setSwingLimits(std::vector minDots); + /// \param swungDirections vector of directions that lie on the swing limit boundary + /// \brief For each swungDirection we compute the corresponding [theta, minDot] pair. + /// We expect the values of theta to NOT be uniformly spaced around the range [0, TWO_PI] + /// so we'll use the input set to extrapolate a lookup function of evenly spaced values. + void setSwingLimits(const std::vector& swungDirections); + + /// \param minTwist the minimum angle of rotation about the twist axis /// \param maxTwist the maximum angle of rotation about the twist axis void setTwistLimits(float minTwist, float maxTwist); @@ -71,7 +75,6 @@ public: protected: SwingLimitFunction _swingLimitFunction; - glm::quat _referenceRotation; float _minTwist; float _maxTwist; }; From ee265aba4a82bc82670ec2e4a98c03fceb4d2484 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 18:20:22 -0700 Subject: [PATCH 08/23] add AnimInverseKinematics class --- .../animation/src/AnimInverseKinematics.cpp | 639 ++++++++++++++++++ .../animation/src/AnimInverseKinematics.h | 76 +++ .../src/AnimInverseKinematicsTests.cpp | 269 ++++++++ .../src/AnimInverseKinematicsTests.h | 27 + 4 files changed, 1011 insertions(+) create mode 100644 libraries/animation/src/AnimInverseKinematics.cpp create mode 100644 libraries/animation/src/AnimInverseKinematics.h create mode 100644 tests/animation/src/AnimInverseKinematicsTests.cpp create mode 100644 tests/animation/src/AnimInverseKinematicsTests.h diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp new file mode 100644 index 0000000000..0f21c6282e --- /dev/null +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -0,0 +1,639 @@ +// +// AnimInverseKinematics.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 "AnimInverseKinematics.h" + +#include +#include +#include // adebug + +#include "ElbowConstraint.h" +#include "SwingTwistConstraint.h" + +AnimInverseKinematics::AnimInverseKinematics(const std::string& id) : AnimNode(AnimNode::Type::InverseKinematics, id) { +} + +AnimInverseKinematics::~AnimInverseKinematics() { + clearConstraints(); +} + +void AnimInverseKinematics::loadDefaultPoses(const AnimPoseVec& poses) { + _defaultRelativePoses = poses; + assert(_skeleton && _skeleton->getNumJoints() == (int)poses.size()); + /* + // BUG: sometimes poses are in centimeters but we expect meters. + // HACK WORKAROUND: check for very large translation of hips and scale down as necessary + int hipsIndex = _skeleton->nameToJointIndex("Hips"); + if (hipsIndex > -1 && + glm::length(_defaultRelativePoses[hipsIndex].trans) > 10.0f && + glm::length(_defaultRelativePoses[hipsIndex].scale) > 0.1f) { + _defaultRelativePoses[hipsIndex].scale = glm::vec3(0.01f); + _defaultRelativePoses[hipsIndex].trans *= 0.01f; + } + */ +} + +void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) { + assert(_skeleton && _skeleton->getNumJoints() == (int)poses.size()); + if (_skeleton->getNumJoints() == (int)poses.size()) { + _relativePoses = poses; + } else { + _relativePoses.clear(); + } + /* + // BUG: sometimes poses are in centimeters but we expect meters. + // HACK WORKAROUND: check for very large translation of hips and scale down as necessary + int hipsIndex = _skeleton->nameToJointIndex("Hips"); + if (hipsIndex > -1 && + glm::length(_relativePoses[hipsIndex].trans) > 10.0f && + glm::length(_relativePoses[hipsIndex].scale) > 0.1f) { + _relativePoses[hipsIndex].scale = glm::vec3(0.01f); + _relativePoses[hipsIndex].trans *= 0.01f; + } + */ +} + +void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) const { + int numJoints = (int)_relativePoses.size(); + absolutePoses.clear(); + absolutePoses.reserve(numJoints); + assert(numJoints <= _skeleton->getNumJoints()); + for (int i = 0; i < numJoints; ++i) { + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex < 0) { + absolutePoses[i] = _relativePoses[i]; + } else { + absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; + } + } +} + +void AnimInverseKinematics::updateTarget(int index, const glm::vec3& position, const glm::quat& rotation) { + std::map::iterator targetItr = _absoluteTargets.find(index); + if (targetItr != _absoluteTargets.end()) { + // update existing target + targetItr->second.pose.rot = rotation; + targetItr->second.pose.trans = position; + } else { + // add new target + assert(index >= 0 && index < _skeleton->getNumJoints()); + + IKTarget target; + target.pose = AnimPose(glm::vec3(1.0f), rotation, position); + + // walk down the skeleton hierarchy to find the joint's root + int rootIndex = -1; + int parentIndex = _skeleton->getParentIndex(index); + while (parentIndex != -1) { + rootIndex = parentIndex; + parentIndex = _skeleton->getParentIndex(parentIndex); + } + target.rootIndex = rootIndex; + std::cout << "adebug adding target for end-effector " << index << " with rootIndex " << rootIndex << std::endl; // adebug + + _absoluteTargets[index] = target; + if (index > _maxTargetIndex) { + _maxTargetIndex = index; + } + } +} + +void AnimInverseKinematics::updateTarget(const QString& name, const glm::vec3& position, const glm::quat& rotation) { + int index = _skeleton->nameToJointIndex(name); + if (index != -1) { + updateTarget(index, position, rotation); + } +} + +void AnimInverseKinematics::clearTarget(int index) { + _absoluteTargets.erase(index); + + // recompute _maxTargetIndex + _maxTargetIndex = 0; + for (auto& targetPair: _absoluteTargets) { + int targetIndex = targetPair.first; + if (targetIndex < _maxTargetIndex) { + _maxTargetIndex = targetIndex; + } + } +} + +void AnimInverseKinematics::clearAllTargets() { + _absoluteTargets.clear(); + _maxTargetIndex = 0; +} + +//virtual +const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) { + static int adebug = 0; ++adebug; + // NOTE: we assume that _relativePoses are up to date (e.g. loadPoses() was just called) + if (_relativePoses.empty()) { + return _relativePoses; + } + + relaxTowardDefaults(dt); + + if (_absoluteTargets.empty()) { + // no IK targets but still need to enforce constraints + std::map::iterator constraintItr = _constraints.begin(); + while (constraintItr != _constraints.end()) { + int index = constraintItr->first; + glm::quat rotation = _relativePoses[index].rot; +// glm::quat oldRotation = rotation; // adebug +// bool appliedConstraint = constraintItr->second->apply(rotation); +// if (0 == (adebug % 100) && appliedConstraint) { +// float angle = glm::angle(rotation * glm::inverse(oldRotation)); // adebug +// std::cout << "adebug 001 applied constraint to index " << index << std::endl; // adebug +// } + _relativePoses[index].rot = rotation; + ++constraintItr; + } + } else { + // compute absolute poses that correspond to relative target poses + AnimPoseVec absolutePoses; + computeAbsolutePoses(absolutePoses); + + float largestError = 0.0f; + const float ACCEPTABLE_RELATIVE_ERROR = 1.0e-3f; + int numLoops = 0; + const int MAX_IK_LOOPS = 16; + const quint64 MAX_IK_TIME = 10 * USECS_PER_MSEC; +// quint64 loopStart = usecTimestampNow(); + quint64 expiry = usecTimestampNow() + MAX_IK_TIME; + do { + largestError = 0.0f; + for (auto& targetPair: _absoluteTargets) { + int lowestMovedIndex = _relativePoses.size() - 1; + int tipIndex = targetPair.first; + AnimPose targetPose = targetPair.second.pose; + int rootIndex = targetPair.second.rootIndex; + if (rootIndex != -1) { + // transform targetPose into skeleton's absolute frame + AnimPose& rootPose = _relativePoses[rootIndex]; + targetPose.trans = rootPose.trans + rootPose.rot * targetPose.trans; + targetPose.rot = rootPose.rot * targetPose.rot; + } + + glm::vec3 tip = absolutePoses[tipIndex].trans; + float error = glm::length(targetPose.trans - tip); + if (error < ACCEPTABLE_RELATIVE_ERROR) { + if (largestError < error) { + largestError = error; + } + // this targetPose has been met + // finally set the relative rotation of the tip to agree with absolute target rotation + int parentIndex = _skeleton->getParentIndex(tipIndex); + if (parentIndex != -1) { + // compute tip's new parent-relative rotation + // Q = Qp * q --> q' = Qp^ * Q + glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot; + /* + RotationConstraint* constraint = getConstraint(tipIndex); + if (constraint) { +// bool appliedConstraint = false; + if (0 == (adebug % 5)) { // && appliedConstraint) { + //std::cout << "adebug 001 applied constraint to index " << tipIndex << std::endl; // adebug + constraint->applyVerbose(newRelativeRotation); + } else { + constraint->apply(newRelativeRotation); + } + // TODO: ATM the final rotation target just fails but we need to provide + // feedback to the IK system so that it can adjust the bones up the skeleton + // to help this rotation target get met. + + // TODO: setting the absolutePose.rot is not so simple if the relative rotation had to be constrained + //absolutePoses[tipIndex].rot = targetPose.rot; + } + */ + _relativePoses[tipIndex].rot = newRelativeRotation; + } + + break; + } + + // descend toward root, rotating each joint to get tip closer to target + int index = _skeleton->getParentIndex(tipIndex); + while (index != -1 && error > ACCEPTABLE_RELATIVE_ERROR) { + // compute the two lines that should be aligned + glm::vec3 jointPosition = absolutePoses[index].trans; + glm::vec3 leverArm = tip - jointPosition; + glm::vec3 pivotLine = targetPose.trans - jointPosition; + + // compute the axis of the rotation that would align them + glm::vec3 axis = glm::cross(leverArm, pivotLine); + float axisLength = glm::length(axis); + if (axisLength > EPSILON) { + // compute deltaRotation for alignment (brings tip closer to target) + axis /= axisLength; + float angle = acosf(glm::dot(leverArm, pivotLine) / (glm::length(leverArm) * glm::length(pivotLine))); + + // NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is + // still possible for the angle to be zero so we also check that to avoid unnecessary calculations. + if (angle > EPSILON) { + glm::quat deltaRotation = glm::angleAxis(angle, axis); + + int parentIndex = _skeleton->getParentIndex(index); + if (parentIndex == -1) { + // accumulate any delta at the root's relative +// glm::quat newRot = glm::normalize(deltaRotation * _relativePoses[index].rot); +// _relativePoses[index].rot = newRot; +// absolutePoses[index].rot = newRot; + // TODO? apply constraints to root? + // TODO: harvest the root's transform as movement of entire skeleton + } else { + // compute joint's new parent-relative rotation + // Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q + glm::quat newRot = glm::normalize(glm::inverse(absolutePoses[parentIndex].rot) * deltaRotation * absolutePoses[index].rot); + RotationConstraint* constraint = getConstraint(index); + if (constraint) { +// glm::quat oldRot = newRot; // adebug +// bool appliedConstraint = // adebug + constraint->apply(newRot); +// if (0 == (adebug % 100) && appliedConstraint) { +// float angle = glm::angle(newRot * glm::inverse(oldRot)); // adebug +// std::cout << "adebug 001 applied constraint to index " << index << " angle = " << angle << std::endl; // adebug +// } + } + _relativePoses[index].rot = newRot; + } + // this joint has been changed so we check to see if it has the lowest index + if (index < lowestMovedIndex) { + lowestMovedIndex = index; + } + + // keep track of tip's new position as we descend towards root + tip = jointPosition + deltaRotation * leverArm; + error = glm::length(targetPose.trans - tip); + } + } + index = _skeleton->getParentIndex(index); + } + if (largestError < error) { + largestError = error; + } + + if (lowestMovedIndex <= _maxTargetIndex && lowestMovedIndex < tipIndex) { + // only update the absolutePoses that matter: those between lowestMovedIndex and _maxTargetIndex + for (int i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex != -1) { + absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; + } + } + } + + // finally set the relative rotation of the tip to agree with absolute target rotation + int parentIndex = _skeleton->getParentIndex(tipIndex); + if (parentIndex != -1) { + // compute tip's new parent-relative rotation + // Q = Qp * q --> q' = Qp^ * Q + glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot; + /* + RotationConstraint* constraint = getConstraint(tipIndex); + if (constraint) { +// bool appliedConstraint = false; + if (0 == (adebug % 5)) { // && appliedConstraint) { + //std::cout << "adebug 001 applied constraint to index " << tipIndex << std::endl; // adebug + constraint->applyVerbose(newRelativeRotation); + } else { + constraint->apply(newRelativeRotation); + } + // TODO: ATM the final rotation target just fails but we need to provide + // feedback to the IK system so that it can adjust the bones up the skeleton + // to help this rotation target get met. + } + */ + _relativePoses[tipIndex].rot = newRelativeRotation; + // TODO: setting the absolutePose.rot is not so simple if the relative rotation had to be constrained + absolutePoses[tipIndex].rot = targetPose.rot; + } + } + ++numLoops; + } while (largestError > ACCEPTABLE_RELATIVE_ERROR && numLoops < MAX_IK_LOOPS && usecTimestampNow() < expiry); + /* + if (0 == (adebug % 20)) { + std::cout << "adebug" + << " l = " << numLoops + << " t = " << float(usecTimestampNow() - loopStart) + << " e = " << largestError + << std::endl; // adebug + } + */ + } + return _relativePoses; +} + +RotationConstraint* AnimInverseKinematics::getConstraint(int index) { + RotationConstraint* constraint = nullptr; + std::map::iterator constraintItr = _constraints.find(index); + if (constraintItr != _constraints.end()) { + constraint = constraintItr->second; + } + return constraint; +} + +void AnimInverseKinematics::clearConstraints() { + std::map::iterator constraintItr = _constraints.begin(); + while (constraintItr != _constraints.end()) { + delete constraintItr->second; + ++constraintItr; + } + _constraints.clear(); +} + +const glm::vec3 xAxis(1.0f, 0.0f, 0.0f); +const glm::vec3 yAxis(0.0f, 1.0f, 0.0f); +const glm::vec3 zAxis(0.0f, 0.0f, 1.0f); + +void AnimInverseKinematics::initConstraints() { + if (!_skeleton) { + return; + } + // We create constraints for the joints shown here + // (and their Left counterparts if applicable). + // + // O RightHand + // / + // O / + // | O RightForeArm + // Neck O / + // | / + // O-------O-------O----O----O RightArm + // Spine2| + // | + // O Spine1 + // | + // | + // O Spine + // | + // | + // O---O---O RightUpLeg + // | | + // | | + // | | + // O O RightLeg + // | | + // y | | + // | | | + // | O O RightFoot + // z | / / + // \ | O--O O--O + // \| + // x -----+ + + loadDefaultPoses(_skeleton->getRelativeBindPoses()); + + // compute corresponding absolute poses + int numJoints = (int)_defaultRelativePoses.size(); + AnimPoseVec absolutePoses; + absolutePoses.reserve(numJoints); + for (int i = 0; i < numJoints; ++i) { + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex < 0) { + absolutePoses[i] = _defaultRelativePoses[i]; + } else { + absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i]; + } + } + + _constraints.clear(); + for (int i = 0; i < numJoints; ++i) { + QString name = _skeleton->getJointName(i); + bool isLeft = name.startsWith("Left", Qt::CaseInsensitive); + float mirror = isLeft ? -1.0f : 1.0f; + if (isLeft) { + //name.remove(0, 4); + } else if (name.startsWith("Right", Qt::CaseInsensitive)) { + //name.remove(0, 5); + } + + RotationConstraint* constraint = nullptr; + if (0 == name.compare("Arm", Qt::CaseInsensitive)) { + SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); + stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); + stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f); + + // these directions are approximate swing limits in root-frame + // NOTE: they don't need to be normalized + std::vector swungDirections; + swungDirections.push_back(glm::vec3(mirror * 1.0f, 1.0f, 1.0f)); + swungDirections.push_back(glm::vec3(mirror * 1.0f, 0.0f, 1.0f)); + swungDirections.push_back(glm::vec3(mirror * 1.0f, -1.0f, 0.5f)); + swungDirections.push_back(glm::vec3(mirror * 0.0f, -1.0f, 0.0f)); + swungDirections.push_back(glm::vec3(mirror * 0.0f, -1.0f, -1.0f)); + swungDirections.push_back(glm::vec3(mirror * -0.5f, 0.0f, -1.0f)); + swungDirections.push_back(glm::vec3(mirror * 0.0f, 1.0f, -1.0f)); + swungDirections.push_back(glm::vec3(mirror * 0.0f, 1.0f, 0.0f)); + + // rotate directions into joint-frame + glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot); + int numDirections = (int)swungDirections.size(); + for (int j = 0; j < numDirections; ++j) { + swungDirections[j] = invAbsoluteRotation * swungDirections[j]; + } + stConstraint->setSwingLimits(swungDirections); + + constraint = static_cast(stConstraint); + } else if (0 == name.compare("UpLeg", Qt::CaseInsensitive)) { + SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); + stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); + stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f); + + // these directions are approximate swing limits in root-frame + // NOTE: they don't need to be normalized + std::vector swungDirections; + swungDirections.push_back(glm::vec3(mirror * 0.25f, 0.0f, 1.0f)); + swungDirections.push_back(glm::vec3(mirror * -0.5f, 0.0f, 1.0f)); + swungDirections.push_back(glm::vec3(mirror * -1.0f, 0.0f, 1.0f)); + swungDirections.push_back(glm::vec3(mirror * -1.0f, 0.0f, 0.0f)); + swungDirections.push_back(glm::vec3(mirror * -0.5f, -0.5f, -1.0f)); + swungDirections.push_back(glm::vec3(mirror * 0.0f, -0.75f, -1.0f)); + swungDirections.push_back(glm::vec3(mirror * 0.25f, -1.0f, 0.0f)); + swungDirections.push_back(glm::vec3(mirror * 0.25f, -1.0f, 1.0f)); + + // rotate directions into joint-frame + glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot); + int numDirections = (int)swungDirections.size(); + for (int j = 0; j < numDirections; ++j) { + swungDirections[j] = invAbsoluteRotation * swungDirections[j]; + } + stConstraint->setSwingLimits(swungDirections); + + constraint = static_cast(stConstraint); + } else if (0 == name.compare("RightHand", Qt::CaseInsensitive)) { + std::cout << "adebug creating constraint for RightHand" << std::endl; // adebug + SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); + stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); + const float MAX_HAND_TWIST = PI / 2.0f; + stConstraint->setTwistLimits(-MAX_HAND_TWIST, MAX_HAND_TWIST); + + // these directions are approximate swing limits in parent-frame + // NOTE: they don't need to be normalized + std::vector swungDirections; + swungDirections.push_back(glm::vec3(1.0f, 1.0f, 0.0f)); + swungDirections.push_back(glm::vec3(0.75f, 1.0f, -1.0f)); + swungDirections.push_back(glm::vec3(-0.75f, 1.0f, -1.0f)); + swungDirections.push_back(glm::vec3(-1.0f, 1.0f, 0.0f)); + swungDirections.push_back(glm::vec3(-0.75f, 1.0f, 1.0f)); + swungDirections.push_back(glm::vec3(0.75f, 1.0f, 1.0f)); + + // rotate directions into joint-frame + glm::quat invRelativeRotation = glm::inverse(_defaultRelativePoses[i].rot); + int numDirections = (int)swungDirections.size(); + for (int j = 0; j < numDirections; ++j) { + swungDirections[j] = invRelativeRotation * swungDirections[j]; + } + stConstraint->setSwingLimits(swungDirections); + + /* + std::vector minDots; + const float MAX_HAND_SWING = PI / 3.0f; + minDots.push_back(cosf(MAX_HAND_SWING)); + stConstraint->setSwingLimits(minDots); + */ + constraint = static_cast(stConstraint); + } else if (name.startsWith("SpineXXX", Qt::CaseInsensitive)) { + SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); + stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); + const float MAX_SPINE_TWIST = PI / 8.0f; + stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); + + std::vector minDots; + const float MAX_SPINE_SWING = PI / 14.0f; + minDots.push_back(cosf(MAX_SPINE_SWING)); + stConstraint->setSwingLimits(minDots); + + constraint = static_cast(stConstraint); + } else if (0 == name.compare("NeckXXX", Qt::CaseInsensitive)) { + SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); + stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); + const float MAX_NECK_TWIST = PI / 2.0f; + stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST); + + std::vector minDots; + const float MAX_NECK_SWING = PI / 3.0f; + minDots.push_back(cosf(MAX_NECK_SWING)); + stConstraint->setSwingLimits(minDots); + + constraint = static_cast(stConstraint); + } else if (0 == name.compare("ForeArm", Qt::CaseInsensitive)) { + // The elbow joint rotates about the parent-frame's zAxis (-zAxis) for the Right (Left) arm. + ElbowConstraint* eConstraint = new ElbowConstraint(); + glm::quat referenceRotation = _defaultRelativePoses[i].rot; + eConstraint->setReferenceRotation(referenceRotation); + + // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame + // then measure the angles to swing the yAxis into alignment + glm::vec3 hingeAxis = - mirror * zAxis; + const float MIN_ELBOW_ANGLE = 0.0f; + const float MAX_ELBOW_ANGLE = 7.0f * PI / 8.0f; + glm::quat invReferenceRotation = glm::inverse(referenceRotation); + glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_ELBOW_ANGLE, hingeAxis) * yAxis; + glm::vec3 maxSwingAxis = invReferenceRotation * glm::angleAxis(MAX_ELBOW_ANGLE, hingeAxis) * yAxis; + + // for the rest of the math we rotate hingeAxis into the child frame + hingeAxis = referenceRotation * hingeAxis; + eConstraint->setHingeAxis(hingeAxis); + + glm::vec3 projectedYAxis = glm::normalize(yAxis - glm::dot(yAxis, hingeAxis) * hingeAxis); + float minAngle = acosf(glm::dot(projectedYAxis, minSwingAxis)); + if (glm::dot(hingeAxis, glm::cross(projectedYAxis, minSwingAxis)) < 0.0f) { + minAngle = - minAngle; + } + float maxAngle = acosf(glm::dot(projectedYAxis, maxSwingAxis)); + if (glm::dot(hingeAxis, glm::cross(projectedYAxis, maxSwingAxis)) < 0.0f) { + maxAngle = - maxAngle; + } + eConstraint->setAngleLimits(minAngle, maxAngle); + + constraint = static_cast(eConstraint); + } else if (0 == name.compare("Leg", Qt::CaseInsensitive)) { + // The knee joint rotates about the parent-frame's -xAxis. + ElbowConstraint* eConstraint = new ElbowConstraint(); + glm::quat referenceRotation = _defaultRelativePoses[i].rot; + eConstraint->setReferenceRotation(referenceRotation); + glm::vec3 hingeAxis = -1.0f * xAxis; + + // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame + // then measure the angles to swing the yAxis into alignment + const float MIN_KNEE_ANGLE = 0.0f; + const float MAX_KNEE_ANGLE = 3.0f * PI / 4.0f; + glm::quat invReferenceRotation = glm::inverse(referenceRotation); + glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * yAxis; + glm::vec3 maxSwingAxis = invReferenceRotation * glm::angleAxis(MAX_KNEE_ANGLE, hingeAxis) * yAxis; + + // for the rest of the math we rotate hingeAxis into the child frame + hingeAxis = referenceRotation * hingeAxis; + eConstraint->setHingeAxis(hingeAxis); + + glm::vec3 projectedYAxis = glm::normalize(yAxis - glm::dot(yAxis, hingeAxis) * hingeAxis); + float minAngle = acosf(glm::dot(projectedYAxis, minSwingAxis)); + if (glm::dot(hingeAxis, glm::cross(projectedYAxis, minSwingAxis)) < 0.0f) { + minAngle = - minAngle; + } + float maxAngle = acosf(glm::dot(projectedYAxis, maxSwingAxis)); + if (glm::dot(hingeAxis, glm::cross(projectedYAxis, maxSwingAxis)) < 0.0f) { + maxAngle = - maxAngle; + } + eConstraint->setAngleLimits(minAngle, maxAngle); + + constraint = static_cast(eConstraint); + } else if (0 == name.compare("Foot", Qt::CaseInsensitive)) { + SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); + stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); + stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f); + + // these directions are approximate swing limits in parent-frame + // NOTE: they don't need to be normalized + std::vector swungDirections; + swungDirections.push_back(yAxis); + swungDirections.push_back(xAxis); + swungDirections.push_back(glm::vec3(1.0f, 1.0f, 1.0f)); + swungDirections.push_back(glm::vec3(1.0f, 1.0f, -1.0f)); + + // rotate directions into joint-frame + glm::quat invRelativeRotation = glm::inverse(_defaultRelativePoses[i].rot); + int numDirections = (int)swungDirections.size(); + for (int j = 0; j < numDirections; ++j) { + swungDirections[j] = invRelativeRotation * swungDirections[j]; + } + stConstraint->setSwingLimits(swungDirections); + + constraint = static_cast(stConstraint); + } + if (constraint) { + _constraints[i] = constraint; + std::cout << "adebug " << i << " '" << _skeleton->getJointName(i).toStdString() << "' constraint = " << (void*)(constraint) << std::endl; // adebug + } + } +} + +void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { + AnimNode::setSkeletonInternal(skeleton); + if (skeleton) { + initConstraints(); + } else { + clearConstraints(); + } +} + +void AnimInverseKinematics::relaxTowardDefaults(float dt) { + // NOTE: for now we just use a single relaxation timescale for all joints, but in the future + // we could vary the timescale on a per-joint basis or do other fancy things. + + // for each joint: lerp towards the default pose + const float RELAXATION_TIMESCALE = 0.25f; + const float alpha = glm::clamp(dt / RELAXATION_TIMESCALE, 0.0f, 1.0f); + int numJoints = (int)_relativePoses.size(); + for (int i = 0; i < numJoints; ++i) { + float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot, _defaultRelativePoses[i].rot)); + _relativePoses[i].rot = glm::normalize(glm::lerp(_relativePoses[i].rot, dotSign * _defaultRelativePoses[i].rot, alpha)); + } +} + diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h new file mode 100644 index 0000000000..03ca5640eb --- /dev/null +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -0,0 +1,76 @@ +// +// AnimInverseKinematics.h +// +// 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 +// + +#ifndef hifi_AnimInverseKinematics_h +#define hifi_AnimInverseKinematics_h + +#include + +#include "AnimNode.h" + +class RotationConstraint; + +class AnimInverseKinematics : public AnimNode { +public: + + AnimInverseKinematics(const std::string& id); + virtual ~AnimInverseKinematics() override; + + void loadDefaultPoses(const AnimPoseVec& poses); + void loadPoses(const AnimPoseVec& poses); + const AnimPoseVec& getRelativePoses() const { return _relativePoses; } + void computeAbsolutePoses(AnimPoseVec& absolutePoses) const; + + /// \param index index of end effector + /// \param position target position in the frame of the end-effector's root (not the parent!) + /// \param rotation target rotation in the frame of the end-effector's root (not the parent!) + void updateTarget(int index, const glm::vec3& position, const glm::quat& rotation); + + /// \param name name of end effector + /// \param position target position in the frame of the end-effector's root (not the parent!) + /// \param rotation target rotation in the frame of the end-effector's root (not the parent!) + void updateTarget(const QString& name, const glm::vec3& position, const glm::quat& rotation); + + void clearTarget(int index); + void clearAllTargets(); + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) override; + +protected: + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton); + + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } + + void relaxTowardDefaults(float dt); + + RotationConstraint* getConstraint(int index); + void clearConstraints(); + void initConstraints(); + + struct IKTarget { + AnimPose pose; // in root-frame + int rootIndex; // cached root index + }; + + std::map _constraints; + std::map _absoluteTargets; // IK targets of end-points + AnimPoseVec _defaultRelativePoses; // poses of the relaxed state + AnimPoseVec _relativePoses; // current relative poses + + // no copies + AnimInverseKinematics(const AnimInverseKinematics&) = delete; + AnimInverseKinematics& operator=(const AnimInverseKinematics&) = delete; + + // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses + // during the the cyclic coordinate descent algorithm + int _maxTargetIndex = 0; +}; + +#endif // hifi_AnimInverseKinematics_h diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp new file mode 100644 index 0000000000..3958db1242 --- /dev/null +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -0,0 +1,269 @@ +// +// AnimInverseKinematicsTests.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 "AnimInverseKinematicsTests.h" + +#include + +#include +#include +#include +#include +#include + +#include "../QTestExtensions.h" + +QTEST_MAIN(AnimInverseKinematicsTests) + +const glm::vec3 origin(0.0f); +const glm::vec3 xAxis(1.0f, 0.0f, 0.0f); +const glm::vec3 yAxis(0.0f, 1.0f, 0.0f); +const glm::vec3 zAxis(0.0f, 0.0f, 1.0f); +const glm::quat identity = glm::quat(); +const glm::quat quaterTurnAroundZ = glm::angleAxis(0.5f * PI, zAxis); + + +void makeTestFBXJoints(std::vector& fbxJoints) { + FBXJoint joint; + joint.isFree = false; + joint.freeLineage.clear(); + joint.parentIndex = -1; + joint.distanceToParent = 1.0f; + joint.boneRadius = 1.0f; + + joint.translation = origin; // T + joint.preTransform = glm::mat4(); // Roff * Rp + joint.preRotation = identity; // Rpre + joint.rotation = identity; // R + joint.postRotation = identity; // Rpost + joint.postTransform = glm::mat4(); // Rp-1 * Soff * Sp * S * Sp-1 + + // World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1) + joint.transform = glm::mat4(); + joint.rotationMin = glm::vec3(-PI); + joint.rotationMax = glm::vec3(PI); + joint.inverseDefaultRotation = identity; + joint.inverseBindRotation = identity; + joint.bindTransform = glm::mat4(); + + joint.name = ""; + joint.isSkeletonJoint = false; + + // we make a list of joints that look like this: + // + // A------>B------>C------>D + + joint.name = "A"; + joint.parentIndex = -1; + joint.translation = origin; + fbxJoints.push_back(joint); + + joint.name = "B"; + joint.parentIndex = 0; + joint.translation = xAxis; + fbxJoints.push_back(joint); + + joint.name = "C"; + joint.parentIndex = 1; + joint.translation = xAxis; + fbxJoints.push_back(joint); + + joint.name = "D"; + joint.parentIndex = 2; + joint.translation = xAxis; + fbxJoints.push_back(joint); + + // compute each joint's transform + for (int i = 1; i < (int)fbxJoints.size(); ++i) { + FBXJoint& j = fbxJoints[i]; + int parentIndex = j.parentIndex; + // World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1) + j.transform = fbxJoints[parentIndex].transform * + glm::translate(j.translation) * + j.preTransform * + glm::mat4_cast(j.preRotation * j.rotation * j.postRotation) * + j.postTransform; + j.inverseBindRotation = identity; + j.bindTransform = j.transform; + } +} + +void AnimInverseKinematicsTests::testSingleChain() { + std::vector fbxJoints; + AnimPose offset; + makeTestFBXJoints(fbxJoints, offset); + + // create a skeleton and doll + AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints); + AnimSkeleton::Pointer skeletonPtr(skeleton); + AnimInverseKinematics ikDoll("doll"); + ikDoll.setSkeleton(skeletonPtr); + + { // easy test IK of joint C + // load intial poses that look like this: + // + // A------>B------>C------>D + AnimPose pose; + pose.scale = glm::vec3(1.0f); + pose.rot = identity; + pose.trans = origin; + + std::vector poses; + poses.push_back(pose); + + pose.trans = xAxis; + for (int i = 1; i < (int)fbxJoints.size(); ++i) { + poses.push_back(pose); + } + ikDoll.loadPoses(poses); + + // we'll put a target t on D for <2, 1, 0> like this: + // + // t + // + // + // A------>B------>C------>D + // + int indexD = 3; + glm::vec3 targetPosition(2.0f, 1.0f, 0.0f); + glm::quat targetRotation = glm::angleAxis(PI / 2.0f, zAxis); + ikDoll.updateTarget(indexD, targetPosition, targetRotation); + + // the IK solution should be: + // + // D + // | + // | + // A------>B------>C + // + float dt = 1.0f; + ikDoll.evaluate(dt); + + // verify absolute results + // NOTE: since we expect this solution to converge very quickly (one loop) + // we can impose very tight error thresholds. + std::vector absolutePoses; + ikDoll.computeAbsolutePoses(absolutePoses); + float acceptableAngle = 0.0001f; + QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngle); + QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngle); + QCOMPARE_QUATS(absolutePoses[2].rot, quaterTurnAroundZ, acceptableAngle); + QCOMPARE_QUATS(absolutePoses[3].rot, quaterTurnAroundZ, acceptableAngle); + + QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, EPSILON); + QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, EPSILON); + QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, EPSILON); + QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, targetPosition, EPSILON); + + // verify relative results + const std::vector& relativePoses = ikDoll.getRelativePoses(); + QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngle); + QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngle); + QCOMPARE_QUATS(relativePoses[2].rot, quaterTurnAroundZ, acceptableAngle); + QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngle); + + QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, EPSILON); + QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, EPSILON); + QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, EPSILON); + QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, EPSILON); + } + + { // hard test IK of joint C + // load intial poses that look like this: + // + // D<------C + // | + // | + // A------>B + // + AnimPose pose; + pose.scale = glm::vec3(1.0f); + pose.rot = identity; + pose.trans = origin; + + std::vector poses; + poses.push_back(pose); + pose.trans = xAxis; + + pose.rot = quaterTurnAroundZ; + poses.push_back(pose); + poses.push_back(pose); + poses.push_back(pose); + ikDoll.loadPoses(poses); + + // we'll put a target t on D for <3, 0, 0> like this: + // + // D<------C + // | + // | + // A------>B t + // + int indexD = 3; + glm::vec3 targetPosition(3.0f, 0.0f, 0.0f); + glm::quat targetRotation = identity; + ikDoll.updateTarget(indexD, targetPosition, targetRotation); + + // the IK solution should be: + // + // A------>B------>C------>D + // + float dt = 1.0f; + ikDoll.evaluate(dt); + + // verify absolute results + // NOTE: the IK algorithm doesn't converge very fast for full-reach targets, + // so we'll consider the poses as good if they are closer than they started. + // + // NOTE: constraints may help speed up convergence since some joints may get clamped + // to maximum extension. TODO: experiment with tightening the error thresholds when + // constraints are working. + std::vector absolutePoses; + ikDoll.computeAbsolutePoses(absolutePoses); + float acceptableAngle = 0.1f; // radians + QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngle); + QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngle); + QCOMPARE_QUATS(absolutePoses[2].rot, identity, acceptableAngle); + QCOMPARE_QUATS(absolutePoses[3].rot, identity, acceptableAngle); + + float acceptableDistance = 0.4f; + QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, EPSILON); + QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, acceptableDistance); + QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, acceptableDistance); + QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, 3.0f * xAxis, acceptableDistance); + + // verify relative results + const std::vector& relativePoses = ikDoll.getRelativePoses(); + QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngle); + QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngle); + QCOMPARE_QUATS(relativePoses[2].rot, identity, acceptableAngle); + QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngle); + + QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, EPSILON); + QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, EPSILON); + QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, EPSILON); + QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, EPSILON); + } +} + +void AnimInverseKinematicsTests::testBar() { + // test AnimPose math + // TODO: move this to other test file + glm::vec3 transA = glm::vec3(1.0f, 2.0f, 3.0f); + glm::vec3 transB = glm::vec3(5.0f, 7.0f, 9.0f); + glm::quat rot = identity; + glm::vec3 scale = glm::vec3(1.0f); + + AnimPose poseA(scale, rot, transA); + AnimPose poseB(scale, rot, transB); + AnimPose poseC = poseA * poseB; + + glm::vec3 expectedTransC = transA + transB; + QCOMPARE_WITH_ABS_ERROR(expectedTransC, poseC.trans, EPSILON); +} + diff --git a/tests/animation/src/AnimInverseKinematicsTests.h b/tests/animation/src/AnimInverseKinematicsTests.h new file mode 100644 index 0000000000..dd1b18e9ae --- /dev/null +++ b/tests/animation/src/AnimInverseKinematicsTests.h @@ -0,0 +1,27 @@ +// +// AnimInverseKinematicsTests.h +// +// 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 +// + +#ifndef hifi_AnimInverseKinematicsTests_h +#define hifi_AnimInverseKinematicsTests_h + +#include +#include + +inline float getErrorDifference(float a, float b) { + return fabs(a - b); +} + +class AnimInverseKinematicsTests : public QObject { + Q_OBJECT +private slots: + void testSingleChain(); + void testBar(); +}; + +#endif // hifi_AnimInverseKinematicsTests_h From 381828dac39b5683c532fb92635c575d215bee19 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 19:02:16 -0700 Subject: [PATCH 09/23] remove hackery for bad length units in AnimSkeleton --- .../animation/src/AnimInverseKinematics.cpp | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 0f21c6282e..4a50fc61a5 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -26,17 +26,6 @@ AnimInverseKinematics::~AnimInverseKinematics() { void AnimInverseKinematics::loadDefaultPoses(const AnimPoseVec& poses) { _defaultRelativePoses = poses; assert(_skeleton && _skeleton->getNumJoints() == (int)poses.size()); - /* - // BUG: sometimes poses are in centimeters but we expect meters. - // HACK WORKAROUND: check for very large translation of hips and scale down as necessary - int hipsIndex = _skeleton->nameToJointIndex("Hips"); - if (hipsIndex > -1 && - glm::length(_defaultRelativePoses[hipsIndex].trans) > 10.0f && - glm::length(_defaultRelativePoses[hipsIndex].scale) > 0.1f) { - _defaultRelativePoses[hipsIndex].scale = glm::vec3(0.01f); - _defaultRelativePoses[hipsIndex].trans *= 0.01f; - } - */ } void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) { @@ -46,17 +35,6 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) { } else { _relativePoses.clear(); } - /* - // BUG: sometimes poses are in centimeters but we expect meters. - // HACK WORKAROUND: check for very large translation of hips and scale down as necessary - int hipsIndex = _skeleton->nameToJointIndex("Hips"); - if (hipsIndex > -1 && - glm::length(_relativePoses[hipsIndex].trans) > 10.0f && - glm::length(_relativePoses[hipsIndex].scale) > 0.1f) { - _relativePoses[hipsIndex].scale = glm::vec3(0.01f); - _relativePoses[hipsIndex].trans *= 0.01f; - } - */ } void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) const { From e86e7601188b89403a7ad5d0562144753d7c80a0 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 19:14:31 -0700 Subject: [PATCH 10/23] remove debug stuff and nerfed constriants --- .../animation/src/AnimInverseKinematics.cpp | 61 ++----------------- 1 file changed, 5 insertions(+), 56 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 4a50fc61a5..ce6499557e 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -11,7 +11,6 @@ #include #include -#include // adebug #include "ElbowConstraint.h" #include "SwingTwistConstraint.h" @@ -73,7 +72,6 @@ void AnimInverseKinematics::updateTarget(int index, const glm::vec3& position, c parentIndex = _skeleton->getParentIndex(parentIndex); } target.rootIndex = rootIndex; - std::cout << "adebug adding target for end-effector " << index << " with rootIndex " << rootIndex << std::endl; // adebug _absoluteTargets[index] = target; if (index > _maxTargetIndex) { @@ -109,7 +107,6 @@ void AnimInverseKinematics::clearAllTargets() { //virtual const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) { - static int adebug = 0; ++adebug; // NOTE: we assume that _relativePoses are up to date (e.g. loadPoses() was just called) if (_relativePoses.empty()) { return _relativePoses; @@ -123,12 +120,7 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar while (constraintItr != _constraints.end()) { int index = constraintItr->first; glm::quat rotation = _relativePoses[index].rot; -// glm::quat oldRotation = rotation; // adebug -// bool appliedConstraint = constraintItr->second->apply(rotation); -// if (0 == (adebug % 100) && appliedConstraint) { -// float angle = glm::angle(rotation * glm::inverse(oldRotation)); // adebug -// std::cout << "adebug 001 applied constraint to index " << index << std::endl; // adebug -// } + constraintItr->second->apply(rotation); _relativePoses[index].rot = rotation; ++constraintItr; } @@ -142,7 +134,6 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar int numLoops = 0; const int MAX_IK_LOOPS = 16; const quint64 MAX_IK_TIME = 10 * USECS_PER_MSEC; -// quint64 loopStart = usecTimestampNow(); quint64 expiry = usecTimestampNow() + MAX_IK_TIME; do { largestError = 0.0f; @@ -171,27 +162,15 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar // compute tip's new parent-relative rotation // Q = Qp * q --> q' = Qp^ * Q glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot; - /* RotationConstraint* constraint = getConstraint(tipIndex); if (constraint) { -// bool appliedConstraint = false; - if (0 == (adebug % 5)) { // && appliedConstraint) { - //std::cout << "adebug 001 applied constraint to index " << tipIndex << std::endl; // adebug - constraint->applyVerbose(newRelativeRotation); - } else { - constraint->apply(newRelativeRotation); - } - // TODO: ATM the final rotation target just fails but we need to provide + constraint->apply(newRelativeRotation); + // TODO: ATM the final rotation target may fails but we need to provide // feedback to the IK system so that it can adjust the bones up the skeleton // to help this rotation target get met. - - // TODO: setting the absolutePose.rot is not so simple if the relative rotation had to be constrained - //absolutePoses[tipIndex].rot = targetPose.rot; } - */ _relativePoses[tipIndex].rot = newRelativeRotation; } - break; } @@ -218,25 +197,15 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar int parentIndex = _skeleton->getParentIndex(index); if (parentIndex == -1) { - // accumulate any delta at the root's relative -// glm::quat newRot = glm::normalize(deltaRotation * _relativePoses[index].rot); -// _relativePoses[index].rot = newRot; -// absolutePoses[index].rot = newRot; // TODO? apply constraints to root? - // TODO: harvest the root's transform as movement of entire skeleton + // TODO? harvest the root's transform as movement of entire skeleton? } else { // compute joint's new parent-relative rotation // Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q glm::quat newRot = glm::normalize(glm::inverse(absolutePoses[parentIndex].rot) * deltaRotation * absolutePoses[index].rot); RotationConstraint* constraint = getConstraint(index); if (constraint) { -// glm::quat oldRot = newRot; // adebug -// bool appliedConstraint = // adebug constraint->apply(newRot); -// if (0 == (adebug % 100) && appliedConstraint) { -// float angle = glm::angle(newRot * glm::inverse(oldRot)); // adebug -// std::cout << "adebug 001 applied constraint to index " << index << " angle = " << angle << std::endl; // adebug -// } } _relativePoses[index].rot = newRot; } @@ -272,37 +241,19 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar // compute tip's new parent-relative rotation // Q = Qp * q --> q' = Qp^ * Q glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot; - /* RotationConstraint* constraint = getConstraint(tipIndex); if (constraint) { -// bool appliedConstraint = false; - if (0 == (adebug % 5)) { // && appliedConstraint) { - //std::cout << "adebug 001 applied constraint to index " << tipIndex << std::endl; // adebug - constraint->applyVerbose(newRelativeRotation); - } else { - constraint->apply(newRelativeRotation); - } + constraint->apply(newRelativeRotation); // TODO: ATM the final rotation target just fails but we need to provide // feedback to the IK system so that it can adjust the bones up the skeleton // to help this rotation target get met. } - */ _relativePoses[tipIndex].rot = newRelativeRotation; - // TODO: setting the absolutePose.rot is not so simple if the relative rotation had to be constrained absolutePoses[tipIndex].rot = targetPose.rot; } } ++numLoops; } while (largestError > ACCEPTABLE_RELATIVE_ERROR && numLoops < MAX_IK_LOOPS && usecTimestampNow() < expiry); - /* - if (0 == (adebug % 20)) { - std::cout << "adebug" - << " l = " << numLoops - << " t = " << float(usecTimestampNow() - loopStart) - << " e = " << largestError - << std::endl; // adebug - } - */ } return _relativePoses; } @@ -445,7 +396,6 @@ void AnimInverseKinematics::initConstraints() { constraint = static_cast(stConstraint); } else if (0 == name.compare("RightHand", Qt::CaseInsensitive)) { - std::cout << "adebug creating constraint for RightHand" << std::endl; // adebug SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); const float MAX_HAND_TWIST = PI / 2.0f; @@ -587,7 +537,6 @@ void AnimInverseKinematics::initConstraints() { } if (constraint) { _constraints[i] = constraint; - std::cout << "adebug " << i << " '" << _skeleton->getJointName(i).toStdString() << "' constraint = " << (void*)(constraint) << std::endl; // adebug } } } From 75ecf0020dc6523ceec0d7be1af40e135d5571cb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 8 Sep 2015 19:21:56 -0700 Subject: [PATCH 11/23] WIP commit, added AnimController node. --- interface/src/avatar/MyAvatar.cpp | 4 +- libraries/animation/src/AnimController.cpp | 92 ++++++ libraries/animation/src/AnimController.h | 58 ++++ libraries/animation/src/AnimNode.h | 1 + libraries/animation/src/AnimNodeLoader.cpp | 55 +++- libraries/animation/src/AnimStateMachine.h | 4 +- libraries/animation/src/Rig.cpp | 36 +- tests/animation/src/data/avatar.json | 365 +++++++++++---------- 8 files changed, 420 insertions(+), 195 deletions(-) create mode 100644 libraries/animation/src/AnimController.cpp create mode 100644 libraries/animation/src/AnimController.h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 89ebe650d0..33c677878a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1241,8 +1241,8 @@ void MyAvatar::initHeadBones() { void MyAvatar::initAnimGraph() { // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 // python2 -m SimpleHTTPServer& - //auto graphUrl = QUrl("http://localhost:8000/avatar.json"); - auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/e2cb37aee601b6fba31d60eac3f6ae3ef72d4a66/avatar.json"); + auto graphUrl = QUrl("http://localhost:8000/avatar.json"); + //auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/e2cb37aee601b6fba31d60eac3f6ae3ef72d4a66/avatar.json"); _skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } diff --git a/libraries/animation/src/AnimController.cpp b/libraries/animation/src/AnimController.cpp new file mode 100644 index 0000000000..a6e1f15a14 --- /dev/null +++ b/libraries/animation/src/AnimController.cpp @@ -0,0 +1,92 @@ +// +// AnimController.cpp +// +// Created by Anthony J. Thibault on 9/8/15. +// Copyright (c) 2015 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 "AnimController.h" +#include "AnimationLogging.h" + +AnimController::AnimController(const std::string& id, float alpha) : + AnimNode(AnimNode::Type::Controller, id), + _alpha(alpha) { + +} + +AnimController::~AnimController() { + +} + +const AnimPoseVec& AnimController::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) { + _alpha = animVars.lookup(_alphaVar, _alpha); + + for (auto& jointVar : _jointVars) { + if (!jointVar.hasPerformedJointLookup) { + QString qJointName = QString::fromStdString(jointVar.jointName); + jointVar.jointIndex = _skeleton->nameToJointIndex(qJointName); + if (jointVar.jointIndex < 0) { + qCWarning(animation) << "AnimController could not find jointName" << jointVar.jointIndex << "in skeleton"; + } + jointVar.hasPerformedJointLookup = true; + } + if (jointVar.jointIndex >= 0) { + AnimPose pose(animVars.lookup(jointVar.var, glm::mat4())); + _poses[jointVar.jointIndex] = pose; + } + } + + return _poses; +} + +const AnimPoseVec& AnimController::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { + _alpha = animVars.lookup(_alphaVar, _alpha); + + for (auto& jointVar : _jointVars) { + if (!jointVar.hasPerformedJointLookup) { + QString qJointName = QString::fromStdString(jointVar.jointName); + jointVar.jointIndex = _skeleton->nameToJointIndex(qJointName); + if (jointVar.jointIndex < 0) { + qCWarning(animation) << "AnimController could not find jointName" << jointVar.jointIndex << "in skeleton"; + } + jointVar.hasPerformedJointLookup = true; + } + + if (jointVar.jointIndex >= 0) { + AnimPose pose(animVars.lookup(jointVar.var, underPoses[jointVar.jointIndex])); + _poses[jointVar.jointIndex] = pose; + } + } + + return _poses; +} + +void AnimController::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { + AnimNode::setSkeletonInternal(skeleton); + + // invalidate all jointVar indices + for (auto& jointVar : _jointVars) { + jointVar.jointIndex = -1; + jointVar.hasPerformedJointLookup = false; + } + + // potentially allocate new joints. + _poses.resize(skeleton->getNumJoints()); + + // set all joints to identity + for (auto& pose : _poses) { + pose = AnimPose::identity; + } +} + +// for AnimDebugDraw rendering +const AnimPoseVec& AnimController::getPosesInternal() const { + return _poses; +} + +void AnimController::addJointVar(const JointVar& jointVar) { + _jointVars.push_back(jointVar); +} diff --git a/libraries/animation/src/AnimController.h b/libraries/animation/src/AnimController.h new file mode 100644 index 0000000000..15805d72fd --- /dev/null +++ b/libraries/animation/src/AnimController.h @@ -0,0 +1,58 @@ +// +// AnimController.h +// +// Created by Anthony J. Thibault on 9/8/15. +// Copyright (c) 2015 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_AnimController_h +#define hifi_AnimController_h + +#include "AnimNode.h" + +// Allows procedural control over a set of joints. + +class AnimController : public AnimNode { +public: + friend class AnimTests; + + AnimController(const std::string& id, float alpha); + virtual ~AnimController() override; + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; + virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; + + void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; } + + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; + + struct JointVar { + JointVar(const std::string& varIn, const std::string& jointNameIn) : var(varIn), jointName(jointNameIn), jointIndex(-1), hasPerformedJointLookup(false) {} + std::string var = ""; + std::string jointName = ""; + int jointIndex = -1; + bool hasPerformedJointLookup = false; + }; + + void addJointVar(const JointVar& jointVar); + +protected: + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + + AnimPoseVec _poses; + float _alpha; + std::string _alphaVar; + + std::vector _jointVars; + + // no copies + AnimController(const AnimController&) = delete; + AnimController& operator=(const AnimController&) = delete; + +}; + +#endif // hifi_AnimController_h diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 4675ae358f..1762fb7d2f 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -40,6 +40,7 @@ public: BlendLinear, Overlay, StateMachine, + Controller, NumTypes }; using Pointer = std::shared_ptr; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 866f443005..bf253bb247 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -20,21 +20,25 @@ #include "AnimOverlay.h" #include "AnimNodeLoader.h" #include "AnimStateMachine.h" +#include "AnimController.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); +using NodeProcessFunc = AnimNode::Pointer (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); // factory functions static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadOverlayNode(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 loadControllerNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); // called after children have been loaded -static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -static bool processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +// returns node on success, nullptr on failure. +static AnimNode::Pointer processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } +static AnimNode::Pointer processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } +static AnimNode::Pointer processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } +AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer processControllerNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { @@ -42,6 +46,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::BlendLinear: return "blendLinear"; case AnimNode::Type::Overlay: return "overlay"; case AnimNode::Type::StateMachine: return "stateMachine"; + case AnimNode::Type::Controller: return "controller"; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -53,6 +58,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::BlendLinear: return loadBlendLinearNode; case AnimNode::Type::Overlay: return loadOverlayNode; case AnimNode::Type::StateMachine: return loadStateMachineNode; + case AnimNode::Type::Controller: return loadControllerNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -64,6 +70,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::BlendLinear: return processBlendLinearNode; case AnimNode::Type::Overlay: return processOverlayNode; case AnimNode::Type::StateMachine: return processStateMachineNode; + case AnimNode::Type::Controller: return processControllerNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -268,13 +275,47 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const return node; } +static AnimNode::Pointer loadControllerNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + + READ_FLOAT(alpha, jsonObj, id, jsonUrl); + auto node = std::make_shared(id.toStdString(), alpha); + + READ_OPTIONAL_STRING(alphaVar, jsonObj); + if (!alphaVar.isEmpty()) { + node->setAlphaVar(alphaVar.toStdString()); + } + + auto jointsValue = jsonObj.value("joints"); + if (!jointsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad array \"joints\" in controller node, id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + + auto jointsArray = jointsValue.toArray(); + for (const auto& jointValue : jointsArray) { + if (!jointValue.isObject()) { + qCCritical(animation) << "AnimNodeLoader, bad state object in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + auto jointObj = jointValue.toObject(); + + READ_STRING(var, jointObj, id, jsonUrl); + READ_STRING(jointName, jointObj, id, jsonUrl); + + AnimController::JointVar jointVar(var.toStdString(), jointName.toStdString()); + node->addJointVar(jointVar); + }; + + return node; +} + void buildChildMap(std::map& map, AnimNode::Pointer node) { for ( auto child : node->_children ) { map.insert(std::pair(child->_id, child)); } } -bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) { +AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) { auto smNode = std::static_pointer_cast(node); assert(smNode); @@ -373,7 +414,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, } smNode->setCurrentState(iter->second); - return true; + return node; } AnimNodeLoader::AnimNodeLoader(const QUrl& url) : diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index f2d941a568..d6ea237d60 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -35,13 +35,13 @@ class AnimStateMachine : public AnimNode { public: friend class AnimNodeLoader; - friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); + friend AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); protected: class State { public: friend AnimStateMachine; - friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); + friend AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); using Pointer = std::shared_ptr; using ConstPointer = std::shared_ptr; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 18e6eff095..7e33ac51a4 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -952,18 +952,30 @@ void Rig::updateFromHeadParameters(const HeadParameters& params) { void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) { if (index >= 0 && _jointStates[index].getParentIndex() >= 0) { - auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; + if (_enableAnimGraph && _animSkeleton) { + glm::vec3 xAxis(1.0f, 0.0f, 0.0f); + glm::vec3 yAxis(0.0f, 1.0f, 0.0f); + glm::vec3 zAxis(0.0f, 0.0f, 1.0f); + glm::quat rot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, zAxis) * + glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, xAxis) * + glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, yAxis)); + AnimPose pose = _animSkeleton->getRelativeBindPose(index); + pose.rot = rot; + _animVars.set("lean", static_cast(pose)); + } else { + auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; - // get the rotation axes in joint space and use them to adjust the rotation - glm::vec3 xAxis(1.0f, 0.0f, 0.0f); - glm::vec3 yAxis(0.0f, 1.0f, 0.0f); - glm::vec3 zAxis(0.0f, 0.0f, 1.0f); - glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index)); - setJointRotationInConstrainedFrame(index, - glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) * - glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) * - glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) * - getJointState(index).getDefaultRotation(), DEFAULT_PRIORITY); + // get the rotation axes in joint space and use them to adjust the rotation + glm::vec3 xAxis(1.0f, 0.0f, 0.0f); + glm::vec3 yAxis(0.0f, 1.0f, 0.0f); + glm::vec3 zAxis(0.0f, 0.0f, 1.0f); + glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index)); + setJointRotationInConstrainedFrame(index, + glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) * + glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) * + glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) * + getJointState(index).getDefaultRotation(), DEFAULT_PRIORITY); + } } } @@ -1026,7 +1038,7 @@ void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) { _animNode = nodeIn; _animNode->setSkeleton(_animSkeleton); }); - connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) { + connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; }); } diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 24967979ea..9ca887c4db 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -2,189 +2,210 @@ "version": "1.0", "root": { "id": "root", - "type": "stateMachine", + "type": "overlay", "data": { - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "walkFwd", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "walkBwd", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "strafeRight", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "strafeLeft", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" } - ] - } - ] + "alpha": 1.0, + "boneSet": "spineOnly" }, "children": [ { - "id": "idle", - "type": "clip", + "id": "spineLean", + "type": "controller", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 1.0, + "joints": [ + { "var": "lean", "jointName": "Spine" } + ] }, "children": [] }, { - "id": "walkFwd", - "type": "clip", + "id": "mainStateMachine", + "type": "stateMachine", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "timeScaleVar": "walkTimeScale" + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "walkFwd", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "walkBwd", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "strafeRight", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "strafeLeft", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" } + ] + } + ] }, - "children": [] - }, - { - "id": "walkBwd", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", - "startFrame": 0.0, - "endFrame": 37.0, - "timeScale": 1.0, - "loopFlag": true, - "timeScaleVar": "walkTimeScale" - }, - "children": [] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 28.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "turnRight", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeft", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", - "startFrame": 0.0, - "endFrame": 31.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeRight", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", - "startFrame": 0.0, - "endFrame": 31.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] + "children": [ + { + "id": "idle", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwd", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "timeScaleVar": "walkTimeScale" + }, + "children": [] + }, + { + "id": "walkBwd", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", + "startFrame": 0.0, + "endFrame": 37.0, + "timeScale": 1.0, + "loopFlag": true, + "timeScaleVar": "walkTimeScale" + }, + "children": [] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeft", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", + "startFrame": 0.0, + "endFrame": 31.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeRight", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", + "startFrame": 0.0, + "endFrame": 31.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] } ] } From 39aef6edc97d6894365c95e9e972535affc3efbf Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 8 Sep 2015 21:42:42 -0700 Subject: [PATCH 12/23] AnimController fix for crash if underPoses vector was empty. --- libraries/animation/src/AnimController.cpp | 7 ++++++- libraries/animation/src/Rig.cpp | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimController.cpp b/libraries/animation/src/AnimController.cpp index a6e1f15a14..d6edef57b5 100644 --- a/libraries/animation/src/AnimController.cpp +++ b/libraries/animation/src/AnimController.cpp @@ -56,7 +56,12 @@ const AnimPoseVec& AnimController::overlay(const AnimVariantMap& animVars, float } if (jointVar.jointIndex >= 0) { - AnimPose pose(animVars.lookup(jointVar.var, underPoses[jointVar.jointIndex])); + AnimPose pose; + if (jointVar.jointIndex <= (int)underPoses.size()) { + pose = AnimPose(animVars.lookup(jointVar.var, underPoses[jointVar.jointIndex])); + } else { + pose = AnimPose(animVars.lookup(jointVar.var, AnimPose::identity)); + } _poses[jointVar.jointIndex] = pose; } } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 7e33ac51a4..2dae075454 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -962,7 +962,7 @@ void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, floa AnimPose pose = _animSkeleton->getRelativeBindPose(index); pose.rot = rot; _animVars.set("lean", static_cast(pose)); - } else { + } else if (!_enableAnimGraph) { auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; // get the rotation axes in joint space and use them to adjust the rotation From c1d72876527b69ed822a51f820aa5c8e4509cd6b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 9 Sep 2015 12:24:32 -0700 Subject: [PATCH 13/23] AnimController node now takes absolute rotation vars, instead of relative. --- libraries/animation/src/AnimController.cpp | 52 +++++++++++++++++++--- libraries/animation/src/AnimSkeleton.cpp | 8 ++++ libraries/animation/src/AnimSkeleton.h | 2 + libraries/animation/src/Rig.cpp | 6 +-- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/libraries/animation/src/AnimController.cpp b/libraries/animation/src/AnimController.cpp index d6edef57b5..6cf95da26b 100644 --- a/libraries/animation/src/AnimController.cpp +++ b/libraries/animation/src/AnimController.cpp @@ -34,8 +34,22 @@ const AnimPoseVec& AnimController::evaluate(const AnimVariantMap& animVars, floa jointVar.hasPerformedJointLookup = true; } if (jointVar.jointIndex >= 0) { - AnimPose pose(animVars.lookup(jointVar.var, glm::mat4())); - _poses[jointVar.jointIndex] = pose; + + // jointVar is an absolute rotation, if it is not set we will use the bindPose as our default value + AnimPose defaultPose = _skeleton->getAbsoluteBindPose(jointVar.jointIndex); + glm::quat absRot = animVars.lookup(jointVar.var, defaultPose.rot); + + // because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose + // here we use the bind pose + int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); + glm::quat parentAbsRot; + if (parentIndex >= 0) { + parentAbsRot = _skeleton->getAbsoluteBindPose(parentIndex).rot; + } + + // convert from absolute to relative + glm::quat relRot = glm::inverse(parentAbsRot) * absRot; + _poses[jointVar.jointIndex] = AnimPose(defaultPose.scale, relRot, defaultPose.trans); } } @@ -56,13 +70,39 @@ const AnimPoseVec& AnimController::overlay(const AnimVariantMap& animVars, float } if (jointVar.jointIndex >= 0) { - AnimPose pose; + + AnimPose defaultPose; + glm::quat absRot; + glm::quat parentAbsRot; if (jointVar.jointIndex <= (int)underPoses.size()) { - pose = AnimPose(animVars.lookup(jointVar.var, underPoses[jointVar.jointIndex])); + + // jointVar is an absolute rotation, if it is not set we will use the underPose as our default value + defaultPose = _skeleton->getAbsolutePose(jointVar.jointIndex, underPoses); + absRot = animVars.lookup(jointVar.var, defaultPose.rot); + + // because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose. + int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); + if (parentIndex >= 0) { + parentAbsRot = _skeleton->getAbsolutePose(parentIndex, underPoses).rot; + } + } else { - pose = AnimPose(animVars.lookup(jointVar.var, AnimPose::identity)); + + // jointVar is an absolute rotation, if it is not set we will use the bindPose as our default value + defaultPose = _skeleton->getAbsoluteBindPose(jointVar.jointIndex); + absRot = animVars.lookup(jointVar.var, defaultPose.rot); + + // because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose + // here we use the bind pose + int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); + if (parentIndex >= 0) { + parentAbsRot = _skeleton->getAbsoluteBindPose(parentIndex).rot; + } } - _poses[jointVar.jointIndex] = pose; + + // convert from absolute to relative + glm::quat relRot = glm::inverse(parentAbsRot) * absRot; + _poses[jointVar.jointIndex] = AnimPose(defaultPose.scale, relRot, defaultPose.trans); } } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index a112eb648d..c6bae37edd 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -105,6 +105,14 @@ const QString& AnimSkeleton::getJointName(int jointIndex) const { return _joints[jointIndex].name; } +AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const { + if (jointIndex < 0) { + return AnimPose::identity; + } else { + return getAbsolutePose(_joints[jointIndex].parentIndex, poses) * poses[jointIndex]; + } +} + void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints, const AnimPose& geometryOffset) { _joints = joints; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index e1976d929b..056d8fbd9b 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -64,6 +64,8 @@ public: int getParentIndex(int jointIndex) const; + AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const; + #ifndef NDEBUG void dump() const; void dump(const AnimPoseVec& poses) const; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 2dae075454..5e39f334c5 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -956,12 +956,10 @@ void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, floa glm::vec3 xAxis(1.0f, 0.0f, 0.0f); glm::vec3 yAxis(0.0f, 1.0f, 0.0f); glm::vec3 zAxis(0.0f, 0.0f, 1.0f); - glm::quat rot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, zAxis) * + glm::quat absRot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, zAxis) * glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, xAxis) * glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, yAxis)); - AnimPose pose = _animSkeleton->getRelativeBindPose(index); - pose.rot = rot; - _animVars.set("lean", static_cast(pose)); + _animVars.set("lean", absRot); } else if (!_enableAnimGraph) { auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; From 756eb54a0a840eed2178ef165d1eed09ebbb98ec Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 9 Sep 2015 15:28:21 -0700 Subject: [PATCH 14/23] AnimNodeLoader support for InverseKinematics node. --- libraries/animation/src/AnimController.cpp | 35 +- .../animation/src/AnimInverseKinematics.cpp | 72 +++- .../animation/src/AnimInverseKinematics.h | 25 +- libraries/animation/src/AnimNodeLoader.cpp | 36 +- libraries/animation/src/Rig.cpp | 3 + tests/animation/src/data/avatar.json | 389 ++++++++++-------- 6 files changed, 340 insertions(+), 220 deletions(-) diff --git a/libraries/animation/src/AnimController.cpp b/libraries/animation/src/AnimController.cpp index 6cf95da26b..c021124732 100644 --- a/libraries/animation/src/AnimController.cpp +++ b/libraries/animation/src/AnimController.cpp @@ -22,38 +22,7 @@ AnimController::~AnimController() { } const AnimPoseVec& AnimController::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) { - _alpha = animVars.lookup(_alphaVar, _alpha); - - for (auto& jointVar : _jointVars) { - if (!jointVar.hasPerformedJointLookup) { - QString qJointName = QString::fromStdString(jointVar.jointName); - jointVar.jointIndex = _skeleton->nameToJointIndex(qJointName); - if (jointVar.jointIndex < 0) { - qCWarning(animation) << "AnimController could not find jointName" << jointVar.jointIndex << "in skeleton"; - } - jointVar.hasPerformedJointLookup = true; - } - if (jointVar.jointIndex >= 0) { - - // jointVar is an absolute rotation, if it is not set we will use the bindPose as our default value - AnimPose defaultPose = _skeleton->getAbsoluteBindPose(jointVar.jointIndex); - glm::quat absRot = animVars.lookup(jointVar.var, defaultPose.rot); - - // because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose - // here we use the bind pose - int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); - glm::quat parentAbsRot; - if (parentIndex >= 0) { - parentAbsRot = _skeleton->getAbsoluteBindPose(parentIndex).rot; - } - - // convert from absolute to relative - glm::quat relRot = glm::inverse(parentAbsRot) * absRot; - _poses[jointVar.jointIndex] = AnimPose(defaultPose.scale, relRot, defaultPose.trans); - } - } - - return _poses; + return overlay(animVars, dt, triggersOut, _skeleton->getRelativeBindPoses()); } const AnimPoseVec& AnimController::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { @@ -64,7 +33,7 @@ const AnimPoseVec& AnimController::overlay(const AnimVariantMap& animVars, float QString qJointName = QString::fromStdString(jointVar.jointName); jointVar.jointIndex = _skeleton->nameToJointIndex(qJointName); if (jointVar.jointIndex < 0) { - qCWarning(animation) << "AnimController could not find jointName" << jointVar.jointIndex << "in skeleton"; + qCWarning(animation) << "AnimController could not find jointName" << qJointName << "in skeleton"; } jointVar.hasPerformedJointLookup = true; } diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index ce6499557e..cd38d23f68 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -14,6 +14,7 @@ #include "ElbowConstraint.h" #include "SwingTwistConstraint.h" +#include "AnimationLogging.h" AnimInverseKinematics::AnimInverseKinematics(const std::string& id) : AnimNode(AnimNode::Type::InverseKinematics, id) { } @@ -51,6 +52,23 @@ void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) con } } +void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar) { + // if there are dups, last one wins. + _targetVarVec.push_back(IKTargetVar(jointName, positionVar.toStdString(), rotationVar.toStdString())); +} + +static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int index) { + // walk down the skeleton hierarchy to find the joint's root + int rootIndex = -1; + int parentIndex = skeleton->getParentIndex(index); + while (parentIndex != -1) { + rootIndex = parentIndex; + parentIndex = skeleton->getParentIndex(parentIndex); + } + return rootIndex; +} + +/* void AnimInverseKinematics::updateTarget(int index, const glm::vec3& position, const glm::quat& rotation) { std::map::iterator targetItr = _absoluteTargets.find(index); if (targetItr != _absoluteTargets.end()) { @@ -104,15 +122,47 @@ void AnimInverseKinematics::clearAllTargets() { _absoluteTargets.clear(); _maxTargetIndex = 0; } +*/ //virtual const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) { + // NOTE: we assume that _relativePoses are up to date (e.g. loadPoses() was just called) if (_relativePoses.empty()) { return _relativePoses; } - relaxTowardDefaults(dt); + // evaluate target vars + for (auto& targetVar : _targetVarVec) { + + // lazy look up of jointIndices and insertion into _absoluteTargets map + if (!targetVar.hasPerformedJointLookup) { + targetVar.jointIndex = _skeleton->nameToJointIndex(targetVar.jointName); + if (targetVar.jointIndex >= 0) { + // insert into _absoluteTargets map + IKTarget target; + target.pose = AnimPose::identity; + target.rootIndex = findRootJointInSkeleton(_skeleton, targetVar.jointIndex); + _absoluteTargets[targetVar.jointIndex] = target; + } else { + qCWarning(animation) << "AnimInverseKinematics could not find jointName" << targetVar.jointName << "in skeleton"; + } + targetVar.hasPerformedJointLookup = true; + } + + if (targetVar.jointIndex >= 0) { + // update pose in _absoluteTargets map + auto iter = _absoluteTargets.find(targetVar.jointIndex); + if (iter != _absoluteTargets.end()) { + AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses); + iter->second.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans); + iter->second.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot); + } + } + } + + // RELAX! Don't do it. + // relaxTowardDefaults(dt); if (_absoluteTargets.empty()) { // no IK targets but still need to enforce constraints @@ -258,6 +308,12 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar return _relativePoses; } +//virtual +const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { + loadPoses(underPoses); + return evaluate(animVars, dt, triggersOut); +} + RotationConstraint* AnimInverseKinematics::getConstraint(int index) { RotationConstraint* constraint = nullptr; std::map::iterator constraintItr = _constraints.find(index); @@ -284,7 +340,7 @@ void AnimInverseKinematics::initConstraints() { if (!_skeleton) { return; } - // We create constraints for the joints shown here + // We create constraints for the joints shown here // (and their Left counterparts if applicable). // // O RightHand @@ -543,11 +599,23 @@ void AnimInverseKinematics::initConstraints() { void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { AnimNode::setSkeletonInternal(skeleton); + + // invalidate all targetVars + for (auto& targetVar : _targetVarVec) { + targetVar.hasPerformedJointLookup = false; + } + + // invalidate all targets + _absoluteTargets.clear(); + + // No constraints! + /* if (skeleton) { initConstraints(); } else { clearConstraints(); } + */ } void AnimInverseKinematics::relaxTowardDefaults(float dt) { diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 03ca5640eb..7fb1a615be 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -27,6 +27,9 @@ public: const AnimPoseVec& getRelativePoses() const { return _relativePoses; } void computeAbsolutePoses(AnimPoseVec& absolutePoses) const; + void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar); + + /* /// \param index index of end effector /// \param position target position in the frame of the end-effector's root (not the parent!) /// \param rotation target rotation in the frame of the end-effector's root (not the parent!) @@ -39,8 +42,10 @@ public: void clearTarget(int index); void clearAllTargets(); + */ virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) override; + virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; protected: virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton); @@ -54,12 +59,28 @@ protected: void clearConstraints(); void initConstraints(); + struct IKTargetVar { + IKTargetVar(const QString& jointNameIn, const std::string& positionVarIn, const std::string& rotationVarIn) : + jointName(jointNameIn), + positionVar(positionVarIn), + rotationVar(rotationVarIn), + jointIndex(-1), + hasPerformedJointLookup(false) {} + + std::string positionVar; + std::string rotationVar; + QString jointName; + int jointIndex; // cached joint index + bool hasPerformedJointLookup = false; + }; + struct IKTarget { - AnimPose pose; // in root-frame - int rootIndex; // cached root index + AnimPose pose; + int rootIndex; }; std::map _constraints; + std::vector _targetVarVec; std::map _absoluteTargets; // IK targets of end-points AnimPoseVec _defaultRelativePoses; // poses of the relaxed state AnimPoseVec _relativePoses; // current relative poses diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 984f0dbe0a..a210327f66 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -21,6 +21,7 @@ #include "AnimNodeLoader.h" #include "AnimStateMachine.h" #include "AnimController.h" +#include "AnimInverseKinematics.h" using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); using NodeProcessFunc = AnimNode::Pointer (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -31,6 +32,7 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q static AnimNode::Pointer loadOverlayNode(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 loadControllerNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); // called after children have been loaded // returns node on success, nullptr on failure. @@ -39,6 +41,7 @@ static AnimNode::Pointer processBlendLinearNode(AnimNode::Pointer node, const QJ static AnimNode::Pointer processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer processControllerNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } +static AnimNode::Pointer processInverseKinematicsNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { @@ -47,7 +50,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::Overlay: return "overlay"; case AnimNode::Type::StateMachine: return "stateMachine"; case AnimNode::Type::Controller: return "controller"; - case AnimNode::Type::InverseKinematics: return nullptr; + case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -60,7 +63,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::Overlay: return loadOverlayNode; case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::Controller: return loadControllerNode; - case AnimNode::Type::InverseKinematics: return nullptr; + case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -73,7 +76,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::Overlay: return processOverlayNode; case AnimNode::Type::StateMachine: return processStateMachineNode; case AnimNode::Type::Controller: return processControllerNode; - case AnimNode::Type::InverseKinematics: return nullptr; + case AnimNode::Type::InverseKinematics: return processInverseKinematicsNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -312,6 +315,33 @@ static AnimNode::Pointer loadControllerNode(const QJsonObject& jsonObj, const QS return node; } +AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + auto node = std::make_shared(id.toStdString()); + + auto targetsValue = jsonObj.value("targets"); + if (!targetsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad array \"targets\" in inverseKinematics node, id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + + auto targetsArray = targetsValue.toArray(); + for (const auto& targetValue : targetsArray) { + if (!targetValue.isObject()) { + qCCritical(animation) << "AnimNodeLoader, bad state object in \"targets\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + auto targetObj = targetValue.toObject(); + + READ_STRING(jointName, targetObj, id, jsonUrl); + READ_STRING(positionVar, targetObj, id, jsonUrl); + READ_STRING(rotationVar, targetObj, id, jsonUrl); + + node->setTargetVars(jointName, positionVar, rotationVar); + }; + + return node; +} + void buildChildMap(std::map& map, AnimNode::Pointer node) { for ( auto child : node->_children ) { map.insert(std::pair(child->_id, child)); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 5e39f334c5..660e2eaaab 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -444,6 +444,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos static float t = 0.0f; _animVars.set("sine", static_cast(0.5 * sin(t) + 0.5)); + _animVars.set("rightHandPosition", glm::vec3(cos(t), sin(t) + 1.0f, 1.0f)); + _animVars.set("leftHandPosition", glm::vec3(cos(-t + 3.1415f), sin(-t + 3.1415f) + 1.0f, 1.0f)); + // default anim vars to notMoving and notTurning _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 9ca887c4db..3122fd433d 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -1,209 +1,238 @@ { "version": "1.0", "root": { - "id": "root", + "id": "ikOverlay", "type": "overlay", "data": { "alpha": 1.0, - "boneSet": "spineOnly" + "boneSet": "fullBody" }, "children": [ { - "id": "spineLean", - "type": "controller", + "id": "ik", + "type": "inverseKinematics", "data": { - "alpha": 1.0, - "joints": [ - { "var": "lean", "jointName": "Spine" } + "targets": [ + { + "jointName": "RightHand", + "positionVar": "rightHandPosition", + "rotationVar": "rightHandRotation" + }, + { + "jointName": "LeftHand", + "positionVar": "leftHandPosition", + "rotationVar": "leftHandRotation" + } ] }, "children": [] }, { - "id": "mainStateMachine", - "type": "stateMachine", + "id": "controllerOverlay", + "type": "overlay", "data": { - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "walkFwd", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "walkBwd", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "strafeRight", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "strafeLeft", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" } - ] - } - ] + "alpha": 1.0, + "boneSet": "spineOnly" }, "children": [ { - "id": "idle", - "type": "clip", + "id": "spineLean", + "type": "controller", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 1.0, + "joints": [ + { "var": "lean", "jointName": "Spine" } + ] }, "children": [] }, { - "id": "walkFwd", - "type": "clip", + "id": "mainStateMachine", + "type": "stateMachine", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "timeScaleVar": "walkTimeScale" + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "walkFwd", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "walkBwd", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "strafeRight", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "strafeLeft", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" } + ] + } + ] }, - "children": [] - }, - { - "id": "walkBwd", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", - "startFrame": 0.0, - "endFrame": 37.0, - "timeScale": 1.0, - "loopFlag": true, - "timeScaleVar": "walkTimeScale" - }, - "children": [] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 28.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "turnRight", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeft", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", - "startFrame": 0.0, - "endFrame": 31.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeRight", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", - "startFrame": 0.0, - "endFrame": 31.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] + "children": [ + { + "id": "idle", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwd", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "timeScaleVar": "walkTimeScale" + }, + "children": [] + }, + { + "id": "walkBwd", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", + "startFrame": 0.0, + "endFrame": 37.0, + "timeScale": 1.0, + "loopFlag": true, + "timeScaleVar": "walkTimeScale" + }, + "children": [] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeft", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", + "startFrame": 0.0, + "endFrame": 31.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeRight", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", + "startFrame": 0.0, + "endFrame": 31.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] } ] } From ad49d0dd5906cecc34cfd3fedb252b97b2330150 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 9 Sep 2015 16:09:38 -0700 Subject: [PATCH 15/23] AnimInverseKinematics: update _maxTargetIndex correctly. --- libraries/animation/src/AnimInverseKinematics.cpp | 6 ++++++ libraries/animation/src/Rig.cpp | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index cd38d23f68..f1b21918c7 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -144,6 +144,10 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar target.pose = AnimPose::identity; target.rootIndex = findRootJointInSkeleton(_skeleton, targetVar.jointIndex); _absoluteTargets[targetVar.jointIndex] = target; + + if (targetVar.jointIndex > _maxTargetIndex) { + _maxTargetIndex = targetVar.jointIndex; + } } else { qCWarning(animation) << "AnimInverseKinematics could not find jointName" << targetVar.jointName << "in skeleton"; } @@ -608,6 +612,8 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele // invalidate all targets _absoluteTargets.clear(); + _maxTargetIndex = 0; + // No constraints! /* if (skeleton) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 660e2eaaab..82be56d724 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -444,8 +444,8 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos static float t = 0.0f; _animVars.set("sine", static_cast(0.5 * sin(t) + 0.5)); - _animVars.set("rightHandPosition", glm::vec3(cos(t), sin(t) + 1.0f, 1.0f)); - _animVars.set("leftHandPosition", glm::vec3(cos(-t + 3.1415f), sin(-t + 3.1415f) + 1.0f, 1.0f)); + _animVars.set("rightHandPosition", glm::vec3(0.5f * cos(t), 0.5f * sin(t) + 1.5f, 1.0f)); + _animVars.set("leftHandPosition", glm::vec3(0.5f * cos(-t + 3.1415f), 0.5f * sin(-t + 3.1415f) + 1.5f, 1.0f)); // default anim vars to notMoving and notTurning _animVars.set("isMovingForward", false); From 09b2d8e4a45d50e660489820167ecd493a686087 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 9 Sep 2015 18:16:57 -0700 Subject: [PATCH 16/23] Seed AnimGraph with hand controller position and orientation. --- .../animation/src/AnimInverseKinematics.cpp | 56 ------------------- .../animation/src/AnimInverseKinematics.h | 15 ----- libraries/animation/src/Rig.cpp | 16 +++++- 3 files changed, 13 insertions(+), 74 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index f1b21918c7..71401b6c60 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -68,62 +68,6 @@ static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int inde return rootIndex; } -/* -void AnimInverseKinematics::updateTarget(int index, const glm::vec3& position, const glm::quat& rotation) { - std::map::iterator targetItr = _absoluteTargets.find(index); - if (targetItr != _absoluteTargets.end()) { - // update existing target - targetItr->second.pose.rot = rotation; - targetItr->second.pose.trans = position; - } else { - // add new target - assert(index >= 0 && index < _skeleton->getNumJoints()); - - IKTarget target; - target.pose = AnimPose(glm::vec3(1.0f), rotation, position); - - // walk down the skeleton hierarchy to find the joint's root - int rootIndex = -1; - int parentIndex = _skeleton->getParentIndex(index); - while (parentIndex != -1) { - rootIndex = parentIndex; - parentIndex = _skeleton->getParentIndex(parentIndex); - } - target.rootIndex = rootIndex; - - _absoluteTargets[index] = target; - if (index > _maxTargetIndex) { - _maxTargetIndex = index; - } - } -} - -void AnimInverseKinematics::updateTarget(const QString& name, const glm::vec3& position, const glm::quat& rotation) { - int index = _skeleton->nameToJointIndex(name); - if (index != -1) { - updateTarget(index, position, rotation); - } -} - -void AnimInverseKinematics::clearTarget(int index) { - _absoluteTargets.erase(index); - - // recompute _maxTargetIndex - _maxTargetIndex = 0; - for (auto& targetPair: _absoluteTargets) { - int targetIndex = targetPair.first; - if (targetIndex < _maxTargetIndex) { - _maxTargetIndex = targetIndex; - } - } -} - -void AnimInverseKinematics::clearAllTargets() { - _absoluteTargets.clear(); - _maxTargetIndex = 0; -} -*/ - //virtual const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) { diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 7fb1a615be..8452f654a2 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -29,21 +29,6 @@ public: void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar); - /* - /// \param index index of end effector - /// \param position target position in the frame of the end-effector's root (not the parent!) - /// \param rotation target rotation in the frame of the end-effector's root (not the parent!) - void updateTarget(int index, const glm::vec3& position, const glm::quat& rotation); - - /// \param name name of end effector - /// \param position target position in the frame of the end-effector's root (not the parent!) - /// \param rotation target rotation in the frame of the end-effector's root (not the parent!) - void updateTarget(const QString& name, const glm::vec3& position, const glm::quat& rotation); - - void clearTarget(int index); - void clearAllTargets(); - */ - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) override; virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 82be56d724..cc5ceac31f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -444,9 +444,6 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos static float t = 0.0f; _animVars.set("sine", static_cast(0.5 * sin(t) + 0.5)); - _animVars.set("rightHandPosition", glm::vec3(0.5f * cos(t), 0.5f * sin(t) + 1.5f, 1.0f)); - _animVars.set("leftHandPosition", glm::vec3(0.5f * cos(-t + 3.1415f), 0.5f * sin(-t + 3.1415f) + 1.5f, 1.0f)); - // default anim vars to notMoving and notTurning _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); @@ -740,6 +737,19 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q } int numFree = freeLineage.size(); + if (_enableAnimGraph && _animSkeleton) { + if (endIndex == _leftHandJointIndex) { + auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans; + _animVars.set("leftHandPosition", targetPosition + rootTrans); + _animVars.set("leftHandRotation", targetRotation); + } else if (endIndex == _rightHandJointIndex) { + auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans; + _animVars.set("rightHandPosition", targetPosition + rootTrans); + _animVars.set("rightHandRotation", targetRotation); + } + return; + } + // store and remember topmost parent transform glm::mat4 topParentTransform; { From 839047e583956403b1077a66ea9ed50a310d233a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 9 Sep 2015 21:41:56 -0700 Subject: [PATCH 17/23] Improvements to squeezeHands.js * Account for controller dead spot. This helps us to see first few frames of the opening animation and the last few frames of the closing animation * Added a small amount of smoothing on the triggers * Fixed bug when controllers were closed quickly and the animation frame was never getting set, because frame and lastFrame were equal. * Changed animation urls to Ozan's latest. --- examples/controllers/squeezeHands.js | 97 +++++++++++++++------------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/examples/controllers/squeezeHands.js b/examples/controllers/squeezeHands.js index 9c42b35d43..29965f77eb 100644 --- a/examples/controllers/squeezeHands.js +++ b/examples/controllers/squeezeHands.js @@ -9,71 +9,80 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var HIFI_OZAN_BUCKET = "http://hifi-public.s3.amazonaws.com/ozan/"; -var rightHandAnimation = HIFI_PUBLIC_BUCKET + "animations/RightHandAnimPhilip.fbx"; -var leftHandAnimation = HIFI_PUBLIC_BUCKET + "animations/LeftHandAnimPhilip.fbx"; +var rightHandAnimation = HIFI_OZAN_BUCKET + "anim/squeeze_hands/right_hand_anim.fbx"; +var leftHandAnimation = HIFI_OZAN_BUCKET + "anim/squeeze_hands/left_hand_anim.fbx"; -var LEFT = 0; -var RIGHT = 1; +var lastLeftTrigger = 0; +var lastRightTrigger = 0; -var lastLeftFrame = 0; -var lastRightFrame = 0; - -var leftDirection = true; -var rightDirection = true; +var leftIsClosing = true; +var rightIsClosing = true; var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation? -var SMOOTH_FACTOR = 0.0; +var SMOOTH_FACTOR = 0.75; var MAX_FRAMES = 30.0; var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); +var CONTROLLER_DEAD_SPOT = 0.25; + +function clamp(val, min, max) { + if (val < min) { + return min; + } else if (val > max) { + return max; + } else { + return val; + } +} + +function normalizeControllerValue(val) { + return clamp((val - CONTROLLER_DEAD_SPOT) / (1.0 - CONTROLLER_DEAD_SPOT), 0.0, 1.0); +} + Script.update.connect(function(deltaTime) { - var leftTriggerValue = Controller.getActionValue(LEFT_HAND_CLICK); - var rightTriggerValue = Controller.getActionValue(RIGHT_HAND_CLICK); + var leftTrigger = normalizeControllerValue(Controller.getActionValue(LEFT_HAND_CLICK)); + var rightTrigger = normalizeControllerValue(Controller.getActionValue(RIGHT_HAND_CLICK)); - var leftFrame, rightFrame; + // Average last few trigger values together for a bit of smoothing + var smoothLeftTrigger = leftTrigger * (1.0 - SMOOTH_FACTOR) + lastLeftTrigger * SMOOTH_FACTOR; + var smoothRightTrigger = rightTrigger * (1.0 - SMOOTH_FACTOR) + lastRightTrigger * SMOOTH_FACTOR; - // Average last few trigger frames together for a bit of smoothing - leftFrame = (leftTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastLeftFrame * SMOOTH_FACTOR; - rightFrame = (rightTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastRightFrame * SMOOTH_FACTOR; - - if (!leftDirection) { - leftFrame = MAX_FRAMES - leftFrame; - } - if (!rightDirection) { - rightFrame = MAX_FRAMES - rightFrame; + if (leftTrigger == 0.0) { + leftIsClosing = true; + } else if (leftTrigger == 1.0) { + leftIsClosing = false; } - if ((leftTriggerValue == 1.0) && (leftDirection == true)) { - leftDirection = false; - lastLeftFrame = MAX_FRAMES - leftFrame; - } else if ((leftTriggerValue == 0.0) && (leftDirection == false)) { - leftDirection = true; - lastLeftFrame = leftFrame; - } - if ((rightTriggerValue == 1.0) && (rightDirection == true)) { - rightDirection = false; - lastRightFrame = MAX_FRAMES - rightFrame; - } else if ((rightTriggerValue == 0.0) && (rightDirection == false)) { - rightDirection = true; - lastRightFrame = rightFrame; + if (rightTrigger == 0.0) { + rightIsClosing = true; + } else if (rightTrigger == 1.0) { + rightIsClosing = false; } - if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){ - MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); - } - if ((rightFrame != lastRightFrame) && rightHandAnimation.length) { - MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); + lastLeftTrigger = smoothLeftTrigger; + lastRightTrigger = smoothRightTrigger; + + // 0..15 + var leftFrame = smoothLeftTrigger * LAST_FRAME; + var rightFrame = smoothRightTrigger * LAST_FRAME; + + var adjustedLeftFrame = (leftIsClosing) ? leftFrame : (MAX_FRAMES - leftFrame); + if (leftHandAnimation.length) { + MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, adjustedLeftFrame, adjustedLeftFrame); } - lastLeftFrame = leftFrame; - lastRightFrame = rightFrame; + var adjustedRightFrame = (rightIsClosing) ? rightFrame : (MAX_FRAMES - rightFrame); + if (rightHandAnimation.length) { + MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, adjustedRightFrame, adjustedRightFrame); + } }); Script.scriptEnding.connect(function() { MyAvatar.stopAnimation(leftHandAnimation); MyAvatar.stopAnimation(rightHandAnimation); -}); \ No newline at end of file +}); From 68076ccf452f59dc667c2bbd684587ac34283512 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 10 Sep 2015 10:57:30 -0700 Subject: [PATCH 18/23] updated anim graph url to use ik-avatar.json --- interface/src/avatar/MyAvatar.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 33c677878a..c792aec0c8 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1239,10 +1239,15 @@ void MyAvatar::initHeadBones() { } void MyAvatar::initAnimGraph() { + // avatar.json // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 - // python2 -m SimpleHTTPServer& - auto graphUrl = QUrl("http://localhost:8000/avatar.json"); - //auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/e2cb37aee601b6fba31d60eac3f6ae3ef72d4a66/avatar.json"); + // + // ik-avatar.json + // https://gist.github.com/hyperlogic/e58e0a24cc341ad5d060 + // + // or run a local web-server + // python -m SimpleHTTPServer& + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/e58e0a24cc341ad5d060/raw/2a994bef7726ce8e9efcee7622b8b1a1b6b67490/ik-avatar.json"); _skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } From 48bd54f81bc9b823422ac09a61d01bd2b391b0fe Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 10 Sep 2015 10:57:37 -0700 Subject: [PATCH 19/23] Eliminate excessive debug logging from procedural setup --- libraries/procedural/src/procedural/Procedural.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 7224704e7d..e282dbb8c3 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -162,7 +162,7 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& size) { if (replaceIndex != std::string::npos) { fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); } - qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str(); + //qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str(); _fragmentShader = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSource)); _shader = gpu::ShaderPointer(gpu::Shader::createProgram(_vertexShader, _fragmentShader)); gpu::Shader::makeProgram(*_shader); From 9a9838fd0d97cdf349399f9c7d9849aa595d5729 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 10 Sep 2015 11:47:41 -0700 Subject: [PATCH 20/23] Clean up of AnimNodeLoader after merge from master. --- libraries/animation/src/AnimNodeLoader.cpp | 30 +++++++++++----------- libraries/animation/src/AnimStateMachine.h | 4 +-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 5ec03f539d..aa58845f01 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -24,7 +24,7 @@ #include "AnimInverseKinematics.h" using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); -using NodeProcessFunc = AnimNode::Pointer (*)(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); // factory functions static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -36,12 +36,12 @@ static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, c // called after children have been loaded // returns node on success, nullptr on failure. -static AnimNode::Pointer processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } -static AnimNode::Pointer processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } -static AnimNode::Pointer processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } -AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); -static AnimNode::Pointer processControllerNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } -static AnimNode::Pointer processInverseKinematicsNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return node; } +static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } +static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } +static bool processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } +bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static bool processControllerNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } +static bool processInverseKinematicsNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { @@ -283,7 +283,7 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const static AnimNode::Pointer loadControllerNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { - READ_FLOAT(alpha, jsonObj, id, jsonUrl); + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); auto node = std::make_shared(id.toStdString(), alpha); READ_OPTIONAL_STRING(alphaVar, jsonObj); @@ -305,8 +305,8 @@ static AnimNode::Pointer loadControllerNode(const QJsonObject& jsonObj, const QS } auto jointObj = jointValue.toObject(); - READ_STRING(var, jointObj, id, jsonUrl); - READ_STRING(jointName, jointObj, id, jsonUrl); + READ_STRING(var, jointObj, id, jsonUrl, nullptr); + READ_STRING(jointName, jointObj, id, jsonUrl, nullptr); AnimController::JointVar jointVar(var.toStdString(), jointName.toStdString()); node->addJointVar(jointVar); @@ -332,9 +332,9 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS } auto targetObj = targetValue.toObject(); - READ_STRING(jointName, targetObj, id, jsonUrl); - READ_STRING(positionVar, targetObj, id, jsonUrl); - READ_STRING(rotationVar, targetObj, id, jsonUrl); + READ_STRING(jointName, targetObj, id, jsonUrl, nullptr); + READ_STRING(positionVar, targetObj, id, jsonUrl, nullptr); + READ_STRING(rotationVar, targetObj, id, jsonUrl, nullptr); node->setTargetVars(jointName, positionVar, rotationVar); }; @@ -348,7 +348,7 @@ void buildChildMap(std::map& map, AnimNode::Poin } } -AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) { +bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) { auto smNode = std::static_pointer_cast(node); assert(smNode); @@ -447,7 +447,7 @@ AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObj } smNode->setCurrentState(iter->second); - return node; + return true; } AnimNodeLoader::AnimNodeLoader(const QUrl& url) : diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index d6ea237d60..f2d941a568 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -35,13 +35,13 @@ class AnimStateMachine : public AnimNode { public: friend class AnimNodeLoader; - friend AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); + friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); protected: class State { public: friend AnimStateMachine; - friend AnimNode::Pointer processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); + friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); using Pointer = std::shared_ptr; using ConstPointer = std::shared_ptr; From 698ccc6fb05a4b87b40da38699633f7838feccde Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 10 Sep 2015 13:48:36 -0700 Subject: [PATCH 21/23] don't change camera when edit.js is activated. don't highlight entites I mouse-hover over. --- examples/edit.js | 7 ++----- examples/libraries/entityList.js | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index d778ff324d..55b745a4e1 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -260,7 +260,6 @@ var toolBar = (function () { cameraManager.disable(); } else { hasShownPropertiesTool = false; - cameraManager.enable(); entityListTool.setVisible(true); gridTool.setVisible(true); grid.setEnabled(true); @@ -670,15 +669,11 @@ function mouseMove(event) { lastMousePosition = { x: event.x, y: event.y }; - highlightEntityUnderCursor(lastMousePosition, false); idleMouseTimerId = Script.setTimeout(handleIdleMouse, IDLE_MOUSE_TIMEOUT); } function handleIdleMouse() { idleMouseTimerId = null; - if (isActive) { - highlightEntityUnderCursor(lastMousePosition, true); - } } function highlightEntityUnderCursor(position, accurateRay) { @@ -802,6 +797,7 @@ function mouseClickEvent(event) { selectionDisplay.select(selectedEntityID, event); if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); @@ -1142,6 +1138,7 @@ Controller.keyReleaseEvent.connect(function (event) { } else if (event.text == "f") { if (isActive) { if (selectionManager.hasSelection()) { + cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); diff --git a/examples/libraries/entityList.js b/examples/libraries/entityList.js index 66dc9f336f..3d6bf4d14f 100644 --- a/examples/libraries/entityList.js +++ b/examples/libraries/entityList.js @@ -49,7 +49,7 @@ EntityListTool = function(opts) { var selectedIDs = []; for (var i = 0; i < selectionManager.selections.length; i++) { - selectedIDs.push(selectionManager.selections[i].id); // ? + selectedIDs.push(selectionManager.selections[i].id); } var data = { @@ -70,6 +70,7 @@ EntityListTool = function(opts) { } selectionManager.setSelections(entityIDs); if (data.focus) { + cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); From f58804a30e582b22e83062d46ef8f1ed5114b774 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 10 Sep 2015 14:35:44 -0700 Subject: [PATCH 22/23] repair to packing of query data from headless viewer --- libraries/octree/src/OctreeHeadlessViewer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index ca50fc001e..529328a8c0 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -216,7 +216,10 @@ void OctreeHeadlessViewer::queryOctree() { // setup the query packet auto queryPacket = NLPacket::create(packetType); - _octreeQuery.getBroadcastData(reinterpret_cast(queryPacket->getPayload())); + + // read the data to our packet and set the payload size to fit the query + int querySize = _octreeQuery.getBroadcastData(reinterpret_cast(queryPacket->getPayload())); + queryPacket->setPayloadSize(querySize); // ask the NodeList to send it nodeList->sendPacket(std::move(queryPacket), *node); From 4aa553c183c500e67f1342166160829a5a6c2e83 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 10 Sep 2015 14:53:56 -0700 Subject: [PATCH 23/23] Stop DDE and EyeTracker early but don't destroy until later in shutdown --- interface/src/Application.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 793a1034f4..07c1b4e38d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -786,12 +786,12 @@ void Application::aboutToQuit() { } void Application::cleanupBeforeQuit() { - // Terminate third party processes so that they're not left running in the event of a subsequent shutdown crash + // Stop third party processes so that they're not left running in the event of a subsequent shutdown crash. #ifdef HAVE_DDE - DependencyManager::destroy(); + DependencyManager::get()->setEnabled(false); #endif #ifdef HAVE_IVIEWHMD - DependencyManager::destroy(); + DependencyManager::get()->setEnabled(false, true); #endif if (_keyboardFocusHighlightID > 0) { @@ -842,6 +842,14 @@ void Application::cleanupBeforeQuit() { // destroy the AudioClient so it and its thread have a chance to go down safely DependencyManager::destroy(); + + // Destroy third party processes after scripts have finished using them. +#ifdef HAVE_DDE + DependencyManager::destroy(); +#endif +#ifdef HAVE_IVIEWHMD + DependencyManager::destroy(); +#endif } void Application::emptyLocalCache() {