From 836cdeb1038d65dddd0da7c06bd2c21db66aa155 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 30 Jul 2015 18:40:48 -0700 Subject: [PATCH 01/52] Baby steps toward new animation system * AnimNode pure virtual base class for all animation nodes. * AnimClip playback of a single FBX animation. --- libraries/animation/src/AnimClip.h | 26 ++++++++++++++++++++++++++ libraries/animation/src/AnimNode.h | 19 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 libraries/animation/src/AnimClip.h create mode 100644 libraries/animation/src/AnimNode.h diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h new file mode 100644 index 0000000000..15c6dffae9 --- /dev/null +++ b/libraries/animation/src/AnimClip.h @@ -0,0 +1,26 @@ +// +// AnimClip.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_AnimClip_h +#define hifi_AnimClip_h + +class AnimClip : public AnimNode { + + void setURL(const std::string& url); + void setStartFrame(AnimFrame startFrame); + void setEndFrame(AnimFrame startFrame); + void setLoopFlag(bool loopFlag); + void setTimeScale(float timeScale); + +public: + virtual const float getEnd() const; + virtual const AnimPose& evaluate(float t); +}; + +#endif // hifi_AnimClip_h diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h new file mode 100644 index 0000000000..648138d06b --- /dev/null +++ b/libraries/animation/src/AnimNode.h @@ -0,0 +1,19 @@ +// +// AnimInterface.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_AnimNode_h +#define hifi_AnimNode_h + +class AnimNode { +public: + virtual float getEnd() const = 0; + virtual const AnimPose& evaluate(float t) = 0; +}; + +#endif // hifi_AnimNode_h From 35196a00593db15b03f13b3c507ee93d250f0ce1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 30 Jul 2015 22:07:05 -0700 Subject: [PATCH 02/52] bare-bones AnimClip implementation with tests! It accumulates time and handles looping, and should handle onDone and onLoop events in the future. --- libraries/animation/src/AnimClip.cpp | 77 ++++++++++++++++++++++++ libraries/animation/src/AnimClip.h | 38 +++++++++--- libraries/animation/src/AnimNode.h | 7 ++- tests/animation/CMakeLists.txt | 2 +- tests/animation/src/AnimClipTests.cpp | 85 +++++++++++++++++++++++++++ tests/animation/src/AnimClipTests.h | 27 +++++++++ 6 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 libraries/animation/src/AnimClip.cpp create mode 100644 tests/animation/src/AnimClipTests.cpp create mode 100644 tests/animation/src/AnimClipTests.h diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp new file mode 100644 index 0000000000..5b0284c08b --- /dev/null +++ b/libraries/animation/src/AnimClip.cpp @@ -0,0 +1,77 @@ +// +// AnimClip.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 "AnimClip.h" +#include "AnimationLogging.h" + +AnimClip::AnimClip(const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : + _url(url), + _startFrame(startFrame), + _endFrame(endFrame), + _timeScale(timeScale), + _frame(startFrame), + _loopFlag(loopFlag) +{ + +} + +AnimClip::~AnimClip() { + +} + +void AnimClip::setURL(const std::string& url) { + // TODO: + _url = url; +} + +void AnimClip::setStartFrame(float startFrame) { + _startFrame = startFrame; +} + +void AnimClip::setEndFrame(float endFrame) { + _endFrame = endFrame; +} + +void AnimClip::setLoopFlag(bool loopFlag) { + _loopFlag = loopFlag; +} + +const AnimPose& AnimClip::evaluate(float dt) { + const float startFrame = std::min(_startFrame, _endFrame); + if (startFrame == _endFrame) { + // when startFrame >= endFrame + _frame = _endFrame; + } else if (_timeScale > 0.0f) { + // accumulate time, keeping track of loops and end of animation events. + const float FRAMES_PER_SECOND = 30.0f; + float framesRemaining = (dt * _timeScale) * FRAMES_PER_SECOND; + while (framesRemaining > 0.0f) { + float framesTillEnd = _endFrame - _frame; + if (framesRemaining >= framesTillEnd) { + if (_loopFlag) { + // anim loop + // TODO: trigger onLoop event + framesRemaining -= framesTillEnd; + _frame = startFrame; + } else { + // anim end + // TODO: trigger onDone event + _frame = _endFrame; + framesRemaining = 0.0f; + } + } else { + _frame += framesRemaining; + framesRemaining = 0.0f; + } + } + } + // TODO: eval animation + + return _frame; +} diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 15c6dffae9..60ef8e74f2 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -10,17 +10,41 @@ #ifndef hifi_AnimClip_h #define hifi_AnimClip_h +#include +#include "AnimationCache.h" +#include "AnimNode.h" + class AnimClip : public AnimNode { +public: + friend class AnimClipTests; + + AnimClip(const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag); + virtual ~AnimClip(); void setURL(const std::string& url); - void setStartFrame(AnimFrame startFrame); - void setEndFrame(AnimFrame startFrame); - void setLoopFlag(bool loopFlag); - void setTimeScale(float timeScale); + const std::string& getURL() const { return _url; } -public: - virtual const float getEnd() const; - virtual const AnimPose& evaluate(float t); + void setStartFrame(float startFrame); + float getStartFrame() const { return _startFrame; } + + void setEndFrame(float endFrame); + float getEndFrame() const { return _endFrame; } + + void setTimeScale(float timeScale) { _timeScale = timeScale; } + float getTimeScale() const { return _timeScale; } + + void setLoopFlag(bool loopFlag); + bool getLoopFlag() const { return _loopFlag; } + + virtual const AnimPose& evaluate(float dt); +protected: + AnimationPointer _anim; + std::string _url; + float _startFrame; + float _endFrame; + float _timeScale; + float _frame; + bool _loopFlag; }; #endif // hifi_AnimClip_h diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 648138d06b..8e78012865 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -10,10 +10,13 @@ #ifndef hifi_AnimNode_h #define hifi_AnimNode_h +typedef float AnimPose; + class AnimNode { public: - virtual float getEnd() const = 0; - virtual const AnimPose& evaluate(float t) = 0; + virtual ~AnimNode() {} + + virtual const AnimPose& evaluate(float dt) = 0; }; #endif // hifi_AnimNode_h diff --git a/tests/animation/CMakeLists.txt b/tests/animation/CMakeLists.txt index 2e9dbc9424..a66e391f69 100644 --- a/tests/animation/CMakeLists.txt +++ b/tests/animation/CMakeLists.txt @@ -1,7 +1,7 @@ # Declare dependencies macro (setup_testcase_dependencies) # link in the shared libraries - link_hifi_libraries(shared animation gpu fbx model) + link_hifi_libraries(shared animation gpu fbx model networking) copy_dlls_beside_windows_executable() endmacro () diff --git a/tests/animation/src/AnimClipTests.cpp b/tests/animation/src/AnimClipTests.cpp new file mode 100644 index 0000000000..68f92afb2f --- /dev/null +++ b/tests/animation/src/AnimClipTests.cpp @@ -0,0 +1,85 @@ +// +// AnimClipTests.cpp +// tests/rig/src +// +// 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 "AnimClipTests.h" +#include "AnimClip.h" +#include "AnimationLogging.h" + +#include <../QTestExtensions.h> + +QTEST_MAIN(AnimClipTests) + +const float EPSILON = 0.001f; + +void AnimClipTests::testAccessors() { + std::string url = "foo"; + float startFrame = 2.0f; + float endFrame = 20.0f; + float timeScale = 1.1f; + float loopFlag = true; + + AnimClip clip(url, startFrame, endFrame, timeScale, loopFlag); + QVERIFY(clip.getURL() == url); + QVERIFY(clip.getStartFrame() == startFrame); + QVERIFY(clip.getEndFrame() == endFrame); + QVERIFY(clip.getTimeScale() == timeScale); + QVERIFY(clip.getLoopFlag() == loopFlag); + + std::string url2 = "bar"; + float startFrame2 = 22.0f; + float endFrame2 = 100.0f; + float timeScale2 = 1.2f; + float loopFlag2 = false; + + clip.setURL(url2); + clip.setStartFrame(startFrame2); + clip.setEndFrame(endFrame2); + clip.setTimeScale(timeScale2); + clip.setLoopFlag(loopFlag2); + + QVERIFY(clip.getURL() == url2); + QVERIFY(clip.getStartFrame() == startFrame2); + QVERIFY(clip.getEndFrame() == endFrame2); + QVERIFY(clip.getTimeScale() == timeScale2); + QVERIFY(clip.getLoopFlag() == loopFlag2); +} + +static float secsToFrames(float secs) { + const float FRAMES_PER_SECOND = 30.0f; + return secs * FRAMES_PER_SECOND; +} + +static float framesToSec(float secs) { + const float FRAMES_PER_SECOND = 30.0f; + return secs / FRAMES_PER_SECOND; +} + +void AnimClipTests::testEvaulate() { + std::string url = "foo"; + float startFrame = 2.0f; + float endFrame = 22.0f; + float timeScale = 1.0f; + float loopFlag = true; + + AnimClip clip(url, startFrame, endFrame, timeScale, loopFlag); + + clip.evaluate(framesToSec(10.0f)); + QCOMPARE_WITH_ABS_ERROR(clip._frame, 12.0f, EPSILON); + + // does it loop? + clip.evaluate(framesToSec(11.0f)); + QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); + + // does it pause at end? + clip.setLoopFlag(false); + clip.evaluate(framesToSec(20.0f)); + QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, EPSILON); +} + diff --git a/tests/animation/src/AnimClipTests.h b/tests/animation/src/AnimClipTests.h new file mode 100644 index 0000000000..f49690d400 --- /dev/null +++ b/tests/animation/src/AnimClipTests.h @@ -0,0 +1,27 @@ +// +// AnimClipTests.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_AnimClipTests_h +#define hifi_AnimClipTests_h + +#include +#include + +inline float getErrorDifference(float a, float b) { + return fabs(a - b); +} + +class AnimClipTests : public QObject { + Q_OBJECT +private slots: + void testAccessors(); + void testEvaulate(); +}; + +#endif // hifi_TransformTests_h From 343b2ccf9d491a7d94fd50c9d6ddd656c6f3f95f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 31 Jul 2015 15:17:47 -0700 Subject: [PATCH 03/52] Added AnimNodeLoader which loads AnimClip nodes from json * added tests and sample json file --- libraries/animation/src/AnimClip.cpp | 3 +- libraries/animation/src/AnimClip.h | 11 +- libraries/animation/src/AnimNode.h | 38 +++++ libraries/animation/src/AnimNodeLoader.cpp | 173 +++++++++++++++++++++ libraries/animation/src/AnimNodeLoader.h | 23 +++ tests/animation/src/AnimClipTests.cpp | 40 ++++- tests/animation/src/AnimClipTests.h | 1 + tests/animation/src/test.json | 52 +++++++ 8 files changed, 334 insertions(+), 7 deletions(-) create mode 100644 libraries/animation/src/AnimNodeLoader.cpp create mode 100644 libraries/animation/src/AnimNodeLoader.h create mode 100644 tests/animation/src/test.json diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 5b0284c08b..a819304d18 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -10,7 +10,8 @@ #include "AnimClip.h" #include "AnimationLogging.h" -AnimClip::AnimClip(const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : +AnimClip::AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : + AnimNode(AnimNode::ClipType, id), _url(url), _startFrame(startFrame), _endFrame(endFrame), diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 60ef8e74f2..db3419332b 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -18,7 +18,7 @@ class AnimClip : public AnimNode { public: friend class AnimClipTests; - AnimClip(const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag); + AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag); virtual ~AnimClip(); void setURL(const std::string& url); @@ -37,14 +37,21 @@ public: bool getLoopFlag() const { return _loopFlag; } virtual const AnimPose& evaluate(float dt); + protected: AnimationPointer _anim; + std::string _url; float _startFrame; float _endFrame; float _timeScale; - float _frame; bool _loopFlag; + + float _frame; + + // no copies + AnimClip(const AnimClip&) = delete; + AnimClip& operator=(const AnimClip&) = delete; }; #endif // hifi_AnimClip_h diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 8e78012865..af1fd8d5d5 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -10,13 +10,51 @@ #ifndef hifi_AnimNode_h #define hifi_AnimNode_h +#include +#include +#include +#include + typedef float AnimPose; +class QJsonObject; class AnimNode { public: + enum Type { + ClipType = 0, + NumTypes + }; + + AnimNode(Type type, const std::string& id) : _type(type), _id(id) {} + + const std::string& getID() const { return _id; } + Type getType() const { return _type; } + + void addChild(std::shared_ptr child) { _children.push_back(child); } + void removeChild(std::shared_ptr child) { + auto iter = std::find(_children.begin(), _children.end(), child); + if (iter != _children.end()) { + _children.erase(iter); + } + } + const std::shared_ptr& getChild(int i) const { + assert(i >= 0 && i < (int)_children.size()); + return _children[i]; + } + int getChildCount() const { return (int)_children.size(); } + virtual ~AnimNode() {} virtual const AnimPose& evaluate(float dt) = 0; + +protected: + std::string _id; + Type _type; + std::vector> _children; + + // no copies + AnimNode(const AnimNode&) = delete; + AnimNode& operator=(const AnimNode&) = delete; }; #endif // hifi_AnimNode_h diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp new file mode 100644 index 0000000000..c2cb75364d --- /dev/null +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -0,0 +1,173 @@ +// +// AnimNodeLoader.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 +// + +#include +#include +#include +#include + +#include "AnimNode.h" +#include "AnimClip.h" +#include "AnimationLogging.h" +#include "AnimNodeLoader.h" + +struct TypeInfo { + AnimNode::Type type; + const char* str; +}; + +// This will result in a compile error if someone adds a new +// item to the AnimNode::Type enum. This is by design. +static TypeInfo typeInfoArray[AnimNode::NumTypes] = { + { AnimNode::ClipType, "clip" } +}; + +typedef std::shared_ptr (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); + +static std::shared_ptr loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); + +static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { + loadClipNode +}; + +#define READ_STRING(NAME, JSON_OBJ, ID, URL) \ + auto NAME##_VAL = JSON_OBJ.value(#NAME); \ + if (!NAME##_VAL.isString()) { \ + qCCritical(animation) << "AnimNodeLoader, error reading string" \ + << #NAME << ", id =" << ID \ + << ", url =" << URL; \ + return nullptr; \ + } \ + QString NAME = NAME##_VAL.toString() + +#define READ_BOOL(NAME, JSON_OBJ, ID, URL) \ + auto NAME##_VAL = JSON_OBJ.value(#NAME); \ + if (!NAME##_VAL.isBool()) { \ + qCCritical(animation) << "AnimNodeLoader, error reading bool" \ + << #NAME << ", id =" << ID \ + << ", url =" << URL; \ + return nullptr; \ + } \ + bool NAME = NAME##_VAL.toBool() + +#define READ_FLOAT(NAME, JSON_OBJ, ID, URL) \ + auto NAME##_VAL = JSON_OBJ.value(#NAME); \ + if (!NAME##_VAL.isDouble()) { \ + qCCritical(animation) << "AnimNodeLoader, error reading double" \ + << #NAME << "id =" << ID \ + << ", url =" << URL; \ + return nullptr; \ + } \ + float NAME = (float)NAME##_VAL.toDouble() + +static AnimNode::Type stringToEnum(const QString& str) { + for (int i = 0; i < AnimNode::NumTypes; i++ ) { + if (str == typeInfoArray[i].str) { + return typeInfoArray[i].type; + } + } + return AnimNode::NumTypes; +} + +static std::shared_ptr loadNode(const QJsonObject& jsonObj, const QString& jsonUrl) { + auto idVal = jsonObj.value("id"); + if (!idVal.isString()) { + qCCritical(animation) << "AnimNodeLoader, bad string \"id\", url =" << jsonUrl; + return nullptr; + } + QString id = idVal.toString(); + + auto typeVal = jsonObj.value("type"); + if (!typeVal.isString()) { + qCCritical(animation) << "AnimNodeLoader, bad object \"type\", id =" << id << ", url =" << jsonUrl; + return nullptr; + } + QString typeStr = typeVal.toString(); + AnimNode::Type type = stringToEnum(typeStr); + if (type == AnimNode::NumTypes) { + qCCritical(animation) << "AnimNodeLoader, unknown node type" << typeStr << ", id =" << id << ", url =" << jsonUrl; + return nullptr; + } + + auto dataValue = jsonObj.value("data"); + if (!dataValue.isObject()) { + qCCritical(animation) << "AnimNodeLoader, bad string \"data\", id =" << id << ", url =" << jsonUrl; + return nullptr; + } + auto dataObj = dataValue.toObject(); + + assert((int)type >= 0 && type < AnimNode::NumTypes); + auto node = nodeLoaderFuncs[type](dataObj, id, jsonUrl); + + auto childrenValue = jsonObj.value("children"); + if (!childrenValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad array \"children\", id =" << id << ", url =" << jsonUrl; + return nullptr; + } + auto childrenAry = childrenValue.toArray(); + for (auto& childValue : childrenAry) { + if (!childValue.isObject()) { + qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl; + return nullptr; + } + node->addChild(loadNode(childValue.toObject(), jsonUrl)); + } + return node; +} + +static std::shared_ptr loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& 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); + + return std::make_shared(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag); +} + +std::shared_ptr AnimNodeLoader::load(const std::string& filename) const { + // load entire file into a string. + QString jsonUrl = QString::fromStdString(filename); + QFile file; + file.setFileName(jsonUrl); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCCritical(animation) << "AnimNodeLoader, could not open url =" << jsonUrl; + return nullptr; + } + QString contents = file.readAll(); + file.close(); + + // convert string into a json doc + auto doc = QJsonDocument::fromJson(contents.toUtf8()); + QJsonObject obj = doc.object(); + + // version + QJsonValue versionVal = obj.value("version"); + if (!versionVal.isString()) { + qCCritical(animation) << "AnimNodeLoader, bad string \"version\", url =" << jsonUrl; + return nullptr; + } + QString version = versionVal.toString(); + + // check version + if (version != "1.0") { + qCCritical(animation) << "AnimNodeLoader, bad version number" << version << "expected \"1.0\", url =" << jsonUrl; + return nullptr; + } + + // root + QJsonValue rootVal = obj.value("root"); + if (!rootVal.isObject()) { + qCCritical(animation) << "AnimNodeLoader, bad object \"root\", url =" << jsonUrl; + return nullptr; + } + + return loadNode(rootVal.toObject(), jsonUrl); +} diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h new file mode 100644 index 0000000000..941ea32f02 --- /dev/null +++ b/libraries/animation/src/AnimNodeLoader.h @@ -0,0 +1,23 @@ +// +// AnimNodeLoader.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_AnimNodeLoader_h +#define hifi_AnimNodeLoader_h + +#include + +class AnimNode; + +class AnimNodeLoader { +public: + // TODO: load from url + std::shared_ptr load(const std::string& filename) const; +}; + +#endif // hifi_AnimNodeLoader diff --git a/tests/animation/src/AnimClipTests.cpp b/tests/animation/src/AnimClipTests.cpp index 68f92afb2f..bab52f17a3 100644 --- a/tests/animation/src/AnimClipTests.cpp +++ b/tests/animation/src/AnimClipTests.cpp @@ -9,6 +9,7 @@ // #include "AnimClipTests.h" +#include "AnimNodeLoader.h" #include "AnimClip.h" #include "AnimationLogging.h" @@ -19,13 +20,18 @@ QTEST_MAIN(AnimClipTests) const float EPSILON = 0.001f; void AnimClipTests::testAccessors() { + std::string id = "my anim clip"; std::string url = "foo"; float startFrame = 2.0f; float endFrame = 20.0f; float timeScale = 1.1f; - float loopFlag = true; + bool loopFlag = true; + + AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag); + + QVERIFY(clip.getID() == id); + QVERIFY(clip.getType() == AnimNode::ClipType); - AnimClip clip(url, startFrame, endFrame, timeScale, loopFlag); QVERIFY(clip.getURL() == url); QVERIFY(clip.getStartFrame() == startFrame); QVERIFY(clip.getEndFrame() == endFrame); @@ -36,7 +42,7 @@ void AnimClipTests::testAccessors() { float startFrame2 = 22.0f; float endFrame2 = 100.0f; float timeScale2 = 1.2f; - float loopFlag2 = false; + bool loopFlag2 = false; clip.setURL(url2); clip.setStartFrame(startFrame2); @@ -62,13 +68,14 @@ static float framesToSec(float secs) { } void AnimClipTests::testEvaulate() { + std::string id = "my clip node"; std::string url = "foo"; float startFrame = 2.0f; float endFrame = 22.0f; float timeScale = 1.0f; float loopFlag = true; - AnimClip clip(url, startFrame, endFrame, timeScale, loopFlag); + AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag); clip.evaluate(framesToSec(10.0f)); QCOMPARE_WITH_ABS_ERROR(clip._frame, 12.0f, EPSILON); @@ -83,3 +90,28 @@ void AnimClipTests::testEvaulate() { QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, EPSILON); } +void AnimClipTests::testLoader() { + AnimNodeLoader loader; + auto node = loader.load("../../../tests/animation/src/test.json"); + QVERIFY((bool)node); + QVERIFY(node->getID() == "idle"); + QVERIFY(node->getType() == AnimNode::ClipType); + + auto clip = std::static_pointer_cast(node); + + QVERIFY(clip->getURL() == "idle.fbx"); + QVERIFY(clip->getStartFrame() == 0.0f); + QVERIFY(clip->getEndFrame() == 30.0f); + QVERIFY(clip->getTimeScale() == 1.0f); + QVERIFY(clip->getLoopFlag() == true); + + QVERIFY(clip->getChildCount() == 3); + + std::shared_ptr nodes[3] = { clip->getChild(0), clip->getChild(1), clip->getChild(2) }; + QVERIFY(nodes[0]->getID() == "test01"); + QVERIFY(nodes[0]->getChildCount() == 0); + QVERIFY(nodes[1]->getID() == "test02"); + QVERIFY(nodes[1]->getChildCount() == 0); + QVERIFY(nodes[2]->getID() == "test03"); + QVERIFY(nodes[2]->getChildCount() == 0); +} diff --git a/tests/animation/src/AnimClipTests.h b/tests/animation/src/AnimClipTests.h index f49690d400..08f25c324f 100644 --- a/tests/animation/src/AnimClipTests.h +++ b/tests/animation/src/AnimClipTests.h @@ -22,6 +22,7 @@ class AnimClipTests : public QObject { private slots: void testAccessors(); void testEvaulate(); + void testLoader(); }; #endif // hifi_TransformTests_h diff --git a/tests/animation/src/test.json b/tests/animation/src/test.json new file mode 100644 index 0000000000..0bcf91201a --- /dev/null +++ b/tests/animation/src/test.json @@ -0,0 +1,52 @@ +{ + "version": "1.0", + "root": { + "id": "idle", + "type": "clip", + "data": { + "url": "idle.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [ + { + "id": "test01", + "type": "clip", + "data": { + "url": "test01.fbx", + "startFrame": 1.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "test02", + "type": "clip", + "data": { + "url": "test02.fbx", + "startFrame": 2.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "test03", + "type": "clip", + "data": { + "url": "test03.fbx", + "startFrame": 3.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + } +} From da809efcd6d60f9531d392b23232397ad6a2bc4f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 31 Jul 2015 19:06:50 -0700 Subject: [PATCH 04/52] WIP commit, DOES NOT BUILD. * Added AnimSkeleton class * Attempt to copy animation frames when _networkAnimation has finished loading. Fill in the holes with bind pose. --- libraries/animation/src/AnimClip.cpp | 92 +++++++++++++++++++--- libraries/animation/src/AnimClip.h | 11 ++- libraries/animation/src/AnimNode.h | 29 +++++-- libraries/animation/src/AnimNodeLoader.cpp | 10 +-- libraries/animation/src/AnimSkeleton.cpp | 36 +++++++++ libraries/animation/src/AnimSkeleton.h | 30 +++++++ libraries/shared/src/GLMHelpers.cpp | 1 - 7 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 libraries/animation/src/AnimSkeleton.cpp create mode 100644 libraries/animation/src/AnimSkeleton.h diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index a819304d18..51b01560fb 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -27,7 +27,7 @@ AnimClip::~AnimClip() { } void AnimClip::setURL(const std::string& url) { - // TODO: + _networkAnim = DependencyManager::get()->getAnimation(QString::fromStdString(url)); _url = url; } @@ -43,11 +43,11 @@ void AnimClip::setLoopFlag(bool loopFlag) { _loopFlag = loopFlag; } -const AnimPose& AnimClip::evaluate(float dt) { +float AnimClip::accumulateTime(float frame, float dt) const { const float startFrame = std::min(_startFrame, _endFrame); if (startFrame == _endFrame) { // when startFrame >= endFrame - _frame = _endFrame; + frame = _endFrame; } else if (_timeScale > 0.0f) { // accumulate time, keeping track of loops and end of animation events. const float FRAMES_PER_SECOND = 30.0f; @@ -59,20 +59,94 @@ const AnimPose& AnimClip::evaluate(float dt) { // anim loop // TODO: trigger onLoop event framesRemaining -= framesTillEnd; - _frame = startFrame; + frame = startFrame; } else { // anim end // TODO: trigger onDone event - _frame = _endFrame; + frame = _endFrame; framesRemaining = 0.0f; } } else { - _frame += framesRemaining; + frame += framesRemaining; framesRemaining = 0.0f; } } } - // TODO: eval animation - - return _frame; + return frame; +} + +const std::vector& AnimClip::evaluate(float dt) { + _frame = accumulateTime(_frame, dt); + + if (!_anim && _networkAnim && _networkAnim->isLoaded() && _skeleton) { + copyFramesFromNetworkAnim(); + _networkAnim = nullptr; + } + + if (_anim) { + int frameCount = _anim.size(); + + int prevIndex = (int)glm::floor(_frame); + int nextIndex = (int)glm::ceil(_frame); + if (_loopFlag && nextIndex >= frameCount) { + nextIndex = 0; + } + + // It can be quite possible for the user to set _startFrame and _endFrame to + // values before or past valid ranges. We clamp the frames here. + prevIndex = std::min(std::max(0, prevIndex), frameCount - 1); + nextIndex = std::min(std::max(0, nextIndex), frameCount - 1); + + const std::vector& prevFrame = _anim[prevIndex]; + const std::vector& nextFrame = _anim[nextIndex]; + float alpha = glm::fract(_frame); + + for (size_t i = 0; i < _bones.size(); i++) { + const AnimBone& prevBone = prevFrame[i]; + const AnimBone& nextBone = nextFrame[i]; + _bones[i].scale = glm::lerp(prevBone.scale, nextBone.scale, alpha); + _bones[i].rot = glm::normalize(glm::lerp(prevBone.rot, nextBone.rot, alpha)); + _bones[i].trans = glm::lerp(prevBone.trans, nextBone.trans, alpha); + } + } + + return _bones; +} + +void AnimClip::copyFromNetworkAnim() { + assert(_networkAnim && _networkAnim->isLoaded() && _skeleton); + _anim.clear(); + + // build a mapping from animation joint indices to skeleton joint indices. + // by matching joints with the same name. + const FBXGeometry& geom = _networkAnim->getGeometry(); + const QVector& joints = geom.joints; + std::vector jointMap; + const int animJointCount = joints.count(); + jointMap.reserve(animJointCount); + for (int i = 0; i < animJointCount; i++) { + int skeletonJoint _skeleton.nameToJointIndex(joints.at(i).name); + jointMap.push_back(skeletonJoint); + } + + const int frameCount = geom.animationFrames.size(); + const int skeletonJointCount = _skeleton.jointCount(); + _anim.resize(frameCount); + for (int i = 0; i < frameCount; i++) { + + // init all joints in animation to bind pose + _anim[i].reserve(skeletonJointCount); + for (int j = 0; j < skeletonJointCount; j++) { + _anim[i].push_back(_skeleton.getBindPose(j)); + } + + // init over all joint animations + for (int j = 0; j < animJointCount; j++) { + int k = jointMap[j]; + if (k >= 0 && k < skeletonJointCount) { + // currently animations only have rotation. + _anim[i][k].rot = geom.animationFrames[i].rotations[j]; + } + } + } } diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index db3419332b..a43ee98194 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -36,10 +36,17 @@ public: void setLoopFlag(bool loopFlag); bool getLoopFlag() const { return _loopFlag; } - virtual const AnimPose& evaluate(float dt); + virtual const std::vector& evaluate(float dt); protected: - AnimationPointer _anim; + float accumulateTime(float frame, float dt) const; + void copyFromNetworkAnim(); + + AnimationPointer _networkAnim; + std::vector _bones; + + // _anim[frame][joint] + std::vector> _anim; std::string _url; float _startFrame; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index af1fd8d5d5..36f5c29ef3 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -1,5 +1,5 @@ // -// AnimInterface.h +// AnimNode.h // // Copyright 2015 High Fidelity, Inc. // @@ -14,8 +14,18 @@ #include #include #include +#include +#include + +#include "AnimSkeleton.h" + +struct AnimBone { + AnimBone() {} + glm::vec3 scale; + glm::quat rot; + glm::vec3 trans; +}; -typedef float AnimPose; class QJsonObject; class AnimNode { @@ -24,33 +34,38 @@ public: ClipType = 0, NumTypes }; + typedef std::shared_ptr Pointer; AnimNode(Type type, const std::string& id) : _type(type), _id(id) {} const std::string& getID() const { return _id; } Type getType() const { return _type; } - void addChild(std::shared_ptr child) { _children.push_back(child); } - void removeChild(std::shared_ptr child) { + void addChild(Pointer child) { _children.push_back(child); } + void removeChild(Pointer child) { auto iter = std::find(_children.begin(), _children.end(), child); if (iter != _children.end()) { _children.erase(iter); } } - const std::shared_ptr& getChild(int i) const { + Pointer getChild(int i) const { assert(i >= 0 && i < (int)_children.size()); return _children[i]; } int getChildCount() const { return (int)_children.size(); } + void setSkeleton(AnimSkeleton::Pointer skeleton) { _skeleton = skeleton; } + AnimSkeleton::Pointer getSkeleton() const { return _skeleton; } + virtual ~AnimNode() {} - virtual const AnimPose& evaluate(float dt) = 0; + virtual const std::vector& evaluate(float dt) = 0; protected: std::string _id; Type _type; - std::vector> _children; + std::vector _children; + AnimSkeleton::Pointer _skeleton; // no copies AnimNode(const AnimNode&) = delete; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index c2cb75364d..d9b34e3c86 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -28,9 +28,9 @@ static TypeInfo typeInfoArray[AnimNode::NumTypes] = { { AnimNode::ClipType, "clip" } }; -typedef std::shared_ptr (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); +typedef AnimNode::Pointer (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); -static std::shared_ptr loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); +static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { loadClipNode @@ -75,7 +75,7 @@ static AnimNode::Type stringToEnum(const QString& str) { return AnimNode::NumTypes; } -static std::shared_ptr loadNode(const QJsonObject& jsonObj, const QString& jsonUrl) { +static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QString& jsonUrl) { auto idVal = jsonObj.value("id"); if (!idVal.isString()) { qCCritical(animation) << "AnimNodeLoader, bad string \"id\", url =" << jsonUrl; @@ -121,7 +121,7 @@ static std::shared_ptr loadNode(const QJsonObject& jsonObj, const QStr return node; } -static std::shared_ptr loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl) { +static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl) { READ_STRING(url, jsonObj, id, jsonUrl); READ_FLOAT(startFrame, jsonObj, id, jsonUrl); @@ -132,7 +132,7 @@ static std::shared_ptr loadClipNode(const QJsonObject& jsonObj, const return std::make_shared(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag); } -std::shared_ptr AnimNodeLoader::load(const std::string& filename) const { +AnimNode::Pointer AnimNodeLoader::load(const std::string& filename) const { // load entire file into a string. QString jsonUrl = QString::fromStdString(filename); QFile file; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp new file mode 100644 index 0000000000..fbd31d55b9 --- /dev/null +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -0,0 +1,36 @@ +// +// AnimSkeleton.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 "AnimSkeleton.h" + +AnimSkeleton::AnimSkeleton(const std::vector& joints) { + _joints = joints; +} + +int AnimSkeltion::nameToJointIndex(const QString& jointName) const { + for (int i = 0; i < _joints.size(); i++) { + if (_joints.name == jointName) { + return i; + } + } + return -1; +} + +int AnimSkeleton::getNumJoints() const { + return _joints.size(); +} + +AnimBone getBindPose(int jointIndex) const { + // TODO: what coordinate frame is the bindTransform in? local to the bones parent frame? or model? + return AnimBone bone(glm::vec3(1.0f, 1.0f, 1.0f), + glm::quat_cast(_joints[jointIndex].bindTransform), + glm::vec3(0.0f, 0.0f, 0.0f)); +} + + diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h new file mode 100644 index 0000000000..a48d7dfa50 --- /dev/null +++ b/libraries/animation/src/AnimSkeleton.h @@ -0,0 +1,30 @@ +// +// AnimSkeleton.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_AnimSkeleton +#define hifi_AnimSkeleton + +#include + +#include "FBXReader.h" + +class AnimSkeleton { +public: + typedef std::shared_ptr Pointer; + AnimSkeleton(const std::vector& joints); + int nameToJointIndex(const QString& jointName) const; + int getNumJoints() const; + AnimBone getBindPose(int jointIndex) const; +} + +protected: + std::vector _joints; +}; + +#endif diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index d5b2917369..6084bf9354 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -347,4 +347,3 @@ QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size) { return result; } - From f5dee717a1d64a677fe71b7e9a4596dfd0667b52 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 31 Jul 2015 22:08:39 -0700 Subject: [PATCH 05/52] Added fbx loading via animation cache. * added AnimPose::copyFromNetworkAnim() which should, re-map bone ids to match the current skeleton, and fill in missing bones with bind pose frames. * added ability to set a skeleton on a node. I might need to add a recursive version of this. * it compiles! * tests run! --- libraries/animation/src/AnimClip.cpp | 42 +++++++++++----------- libraries/animation/src/AnimClip.h | 6 ++-- libraries/animation/src/AnimNode.h | 11 ++---- libraries/animation/src/AnimNodeLoader.cpp | 2 +- libraries/animation/src/AnimSkeleton.cpp | 14 ++++---- libraries/animation/src/AnimSkeleton.h | 13 +++++-- libraries/shared/src/GLMHelpers.h | 15 ++++++++ tests/animation/src/AnimClipTests.cpp | 22 ++++++++---- tests/animation/src/AnimClipTests.h | 2 ++ tests/animation/src/{ => data}/test.json | 0 10 files changed, 79 insertions(+), 48 deletions(-) rename tests/animation/src/{ => data}/test.json (100%) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 51b01560fb..d10430dec3 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -7,6 +7,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GLMHelpers.h" #include "AnimClip.h" #include "AnimationLogging.h" @@ -16,8 +17,8 @@ AnimClip::AnimClip(const std::string& id, const std::string& url, float startFra _startFrame(startFrame), _endFrame(endFrame), _timeScale(timeScale), - _frame(startFrame), - _loopFlag(loopFlag) + _loopFlag(loopFlag), + _frame(startFrame) { } @@ -27,7 +28,8 @@ AnimClip::~AnimClip() { } void AnimClip::setURL(const std::string& url) { - _networkAnim = DependencyManager::get()->getAnimation(QString::fromStdString(url)); + auto animCache = DependencyManager::get(); + _networkAnim = animCache->getAnimation(QString::fromStdString(url)); _url = url; } @@ -75,15 +77,15 @@ float AnimClip::accumulateTime(float frame, float dt) const { return frame; } -const std::vector& AnimClip::evaluate(float dt) { +const std::vector& AnimClip::evaluate(float dt) { _frame = accumulateTime(_frame, dt); - if (!_anim && _networkAnim && _networkAnim->isLoaded() && _skeleton) { - copyFramesFromNetworkAnim(); - _networkAnim = nullptr; + if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { + copyFromNetworkAnim(); + _networkAnim.reset(); } - if (_anim) { + if (_anim.size()) { int frameCount = _anim.size(); int prevIndex = (int)glm::floor(_frame); @@ -97,20 +99,20 @@ const std::vector& AnimClip::evaluate(float dt) { prevIndex = std::min(std::max(0, prevIndex), frameCount - 1); nextIndex = std::min(std::max(0, nextIndex), frameCount - 1); - const std::vector& prevFrame = _anim[prevIndex]; - const std::vector& nextFrame = _anim[nextIndex]; + const std::vector& prevFrame = _anim[prevIndex]; + const std::vector& nextFrame = _anim[nextIndex]; float alpha = glm::fract(_frame); - for (size_t i = 0; i < _bones.size(); i++) { - const AnimBone& prevBone = prevFrame[i]; - const AnimBone& nextBone = nextFrame[i]; - _bones[i].scale = glm::lerp(prevBone.scale, nextBone.scale, alpha); - _bones[i].rot = glm::normalize(glm::lerp(prevBone.rot, nextBone.rot, alpha)); - _bones[i].trans = glm::lerp(prevBone.trans, nextBone.trans, alpha); + for (size_t i = 0; i < _poses.size(); i++) { + const AnimPose& prevBone = prevFrame[i]; + const AnimPose& nextBone = nextFrame[i]; + _poses[i].scale = lerp(prevBone.scale, nextBone.scale, alpha); + _poses[i].rot = glm::normalize(glm::lerp(prevBone.rot, nextBone.rot, alpha)); + _poses[i].trans = lerp(prevBone.trans, nextBone.trans, alpha); } } - return _bones; + return _poses; } void AnimClip::copyFromNetworkAnim() { @@ -125,19 +127,19 @@ void AnimClip::copyFromNetworkAnim() { const int animJointCount = joints.count(); jointMap.reserve(animJointCount); for (int i = 0; i < animJointCount; i++) { - int skeletonJoint _skeleton.nameToJointIndex(joints.at(i).name); + int skeletonJoint = _skeleton->nameToJointIndex(joints.at(i).name); jointMap.push_back(skeletonJoint); } const int frameCount = geom.animationFrames.size(); - const int skeletonJointCount = _skeleton.jointCount(); + const int skeletonJointCount = _skeleton->getNumJoints(); _anim.resize(frameCount); for (int i = 0; i < frameCount; i++) { // init all joints in animation to bind pose _anim[i].reserve(skeletonJointCount); for (int j = 0; j < skeletonJointCount; j++) { - _anim[i].push_back(_skeleton.getBindPose(j)); + _anim[i].push_back(_skeleton->getBindPose(j)); } // init over all joint animations diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index a43ee98194..9858e5b524 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -36,17 +36,17 @@ public: void setLoopFlag(bool loopFlag); bool getLoopFlag() const { return _loopFlag; } - virtual const std::vector& evaluate(float dt); + virtual const std::vector& evaluate(float dt); protected: float accumulateTime(float frame, float dt) const; void copyFromNetworkAnim(); AnimationPointer _networkAnim; - std::vector _bones; + std::vector _poses; // _anim[frame][joint] - std::vector> _anim; + std::vector> _anim; std::string _url; float _startFrame; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 36f5c29ef3..a801b8b2bc 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -19,13 +19,6 @@ #include "AnimSkeleton.h" -struct AnimBone { - AnimBone() {} - glm::vec3 scale; - glm::quat rot; - glm::vec3 trans; -}; - class QJsonObject; class AnimNode { @@ -59,11 +52,11 @@ public: virtual ~AnimNode() {} - virtual const std::vector& evaluate(float dt) = 0; + virtual const std::vector& evaluate(float dt) = 0; protected: - std::string _id; Type _type; + std::string _id; std::vector _children; AnimSkeleton::Pointer _skeleton; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index d9b34e3c86..2d3a5a010c 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -111,7 +111,7 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QString& jso return nullptr; } auto childrenAry = childrenValue.toArray(); - for (auto& childValue : childrenAry) { + for (const auto& childValue : childrenAry) { if (!childValue.isObject()) { qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl; return nullptr; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index fbd31d55b9..695dea0752 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -13,9 +13,9 @@ AnimSkeleton::AnimSkeleton(const std::vector& joints) { _joints = joints; } -int AnimSkeltion::nameToJointIndex(const QString& jointName) const { - for (int i = 0; i < _joints.size(); i++) { - if (_joints.name == jointName) { +int AnimSkeleton::nameToJointIndex(const QString& jointName) const { + for (size_t i = 0; i < _joints.size(); i++) { + if (_joints[i].name == jointName) { return i; } } @@ -26,11 +26,11 @@ int AnimSkeleton::getNumJoints() const { return _joints.size(); } -AnimBone getBindPose(int jointIndex) const { +AnimPose AnimSkeleton::getBindPose(int jointIndex) const { // TODO: what coordinate frame is the bindTransform in? local to the bones parent frame? or model? - return AnimBone bone(glm::vec3(1.0f, 1.0f, 1.0f), - glm::quat_cast(_joints[jointIndex].bindTransform), - glm::vec3(0.0f, 0.0f, 0.0f)); + return AnimPose(glm::vec3(1.0f, 1.0f, 1.0f), + glm::quat_cast(_joints[jointIndex].bindTransform), + glm::vec3(0.0f, 0.0f, 0.0f)); } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index a48d7dfa50..0966c2b5f7 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -14,14 +14,23 @@ #include "FBXReader.h" +struct AnimPose { + AnimPose() {} + AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {} + + glm::vec3 scale; + glm::quat rot; + glm::vec3 trans; +}; + class AnimSkeleton { public: typedef std::shared_ptr Pointer; + AnimSkeleton(const std::vector& joints); int nameToJointIndex(const QString& jointName) const; int getNumJoints() const; - AnimBone getBindPose(int jointIndex) const; -} + AnimPose getBindPose(int jointIndex) const; protected: std::vector _joints; diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 6874f3b391..1a61c426e5 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -150,4 +150,19 @@ T toNormalizedDeviceScale(const T& value, const T& size) { #define PITCH(euler) euler.x #define ROLL(euler) euler.z +template +glm::detail::tvec2 lerp(const glm::detail::tvec2& x, const glm::detail::tvec2& y, T a) { + return x * (T(1) - a) + (y * a); +} + +template +glm::detail::tvec3 lerp(const glm::detail::tvec3& x, const glm::detail::tvec3& y, T a) { + return x * (T(1) - a) + (y * a); +} + +template +glm::detail::tvec4 lerp(const glm::detail::tvec4& x, const glm::detail::tvec4& y, T a) { + return x * (T(1) - a) + (y * a); +} + #endif // hifi_GLMHelpers_h diff --git a/tests/animation/src/AnimClipTests.cpp b/tests/animation/src/AnimClipTests.cpp index bab52f17a3..aab49aa30d 100644 --- a/tests/animation/src/AnimClipTests.cpp +++ b/tests/animation/src/AnimClipTests.cpp @@ -19,6 +19,15 @@ QTEST_MAIN(AnimClipTests) const float EPSILON = 0.001f; +void AnimClipTests::initTestCase() { + auto animationCache = DependencyManager::set(); + auto resourceCacheSharedItems = DependencyManager::set(); +} + +void AnimClipTests::cleanupTestCase() { + DependencyManager::destroy(); +} + void AnimClipTests::testAccessors() { std::string id = "my anim clip"; std::string url = "foo"; @@ -57,11 +66,6 @@ void AnimClipTests::testAccessors() { QVERIFY(clip.getLoopFlag() == loopFlag2); } -static float secsToFrames(float secs) { - const float FRAMES_PER_SECOND = 30.0f; - return secs * FRAMES_PER_SECOND; -} - static float framesToSec(float secs) { const float FRAMES_PER_SECOND = 30.0f; return secs / FRAMES_PER_SECOND; @@ -92,7 +96,13 @@ void AnimClipTests::testEvaulate() { void AnimClipTests::testLoader() { AnimNodeLoader loader; - auto node = loader.load("../../../tests/animation/src/test.json"); + +#ifdef Q_OS_WIN + auto node = loader.load("../../../tests/animation/src/data/test.json"); +#else + auto node = loader.load("../../../../tests/animation/src/data/test.json"); +#endif + QVERIFY((bool)node); QVERIFY(node->getID() == "idle"); QVERIFY(node->getType() == AnimNode::ClipType); diff --git a/tests/animation/src/AnimClipTests.h b/tests/animation/src/AnimClipTests.h index 08f25c324f..f70ee31aa1 100644 --- a/tests/animation/src/AnimClipTests.h +++ b/tests/animation/src/AnimClipTests.h @@ -20,6 +20,8 @@ inline float getErrorDifference(float a, float b) { class AnimClipTests : public QObject { Q_OBJECT private slots: + void initTestCase(); + void cleanupTestCase(); void testAccessors(); void testEvaulate(); void testLoader(); diff --git a/tests/animation/src/test.json b/tests/animation/src/data/test.json similarity index 100% rename from tests/animation/src/test.json rename to tests/animation/src/data/test.json From 91ca13c66daef7863c99e96ecf0487a6201b47e7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sat, 1 Aug 2015 12:43:03 -0700 Subject: [PATCH 06/52] Added AnimDebugDraw to render-utils --- interface/src/Application.cpp | 4 + libraries/render-utils/src/AnimDebugDraw.cpp | 144 +++++++++++++++++++ libraries/render-utils/src/AnimDebugDraw.h | 36 +++++ libraries/render-utils/src/animdebugdraw.slf | 17 +++ libraries/render-utils/src/animdebugdraw.slv | 24 ++++ tests/animation/src/AnimClipTests.cpp | 1 - tests/animation/src/AnimClipTests.h | 2 +- 7 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 libraries/render-utils/src/AnimDebugDraw.cpp create mode 100644 libraries/render-utils/src/AnimDebugDraw.h create mode 100644 libraries/render-utils/src/animdebugdraw.slf create mode 100644 libraries/render-utils/src/animdebugdraw.slv diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index acdfd8cfc9..e43465ad75 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -142,6 +142,8 @@ #include "ui/AddressBarDialog.h" #include "ui/UpdateDialog.h" +#include "AnimDebugDraw.h" + // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU #if defined(Q_OS_WIN) @@ -645,6 +647,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket"); + + AnimDebugDraw& add = AnimDebugDraw::getInstance(); } void Application::aboutToQuit() { diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp new file mode 100644 index 0000000000..80a8824d5a --- /dev/null +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -0,0 +1,144 @@ +// +// AnimDebugDraw.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 "animdebugdraw_vert.h" +#include "animdebugdraw_frag.h" +#include + +#include "AnimDebugDraw.h" +#include "AbstractViewStateInterface.h" + +struct Vertex { + glm::vec3 pos; + uint32_t rgba; +}; + +class AnimDebugDrawData { +public: + typedef render::Payload Payload; + typedef Payload::DataPointer Pointer; + + AnimDebugDrawData() { + + _vertexFormat = std::make_shared(); + _vertexBuffer = std::make_shared(); + _indexBuffer = std::make_shared(); + + _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element::VEC3F_XYZ, 0); + _vertexFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::COLOR_RGBA_32, offsetof(Vertex, rgba)); + } + + void render(RenderArgs* args) { + auto& batch = *args->_batch; + batch.setPipeline(_pipeline); + auto transform = Transform{}; + batch.setModelTransform(transform); + + batch.setInputFormat(_vertexFormat); + batch.setInputBuffer(0, _vertexBuffer, 0, sizeof(Vertex)); + batch.setIndexBuffer(gpu::UINT16, _indexBuffer, 0); + + auto numIndices = _indexBuffer->getSize() / sizeof(uint16_t); + batch.drawIndexed(gpu::LINES, numIndices); + } + + gpu::PipelinePointer _pipeline; + render::ItemID _item; + gpu::Stream::FormatPointer _vertexFormat; + gpu::BufferPointer _vertexBuffer; + gpu::BufferPointer _indexBuffer; +}; + +typedef render::Payload AnimDebugDrawPayload; + +namespace render { + template <> const ItemKey payloadGetKey(const AnimDebugDrawData::Pointer& data) { return ItemKey::Builder::transparentShape(); } + template <> const Item::Bound payloadGetBound(const AnimDebugDrawData::Pointer& data) { return Item::Bound(); } + template <> void payloadRender(const AnimDebugDrawData::Pointer& data, RenderArgs* args) { + data->render(args); + } +} + +static AnimDebugDraw* instance = nullptr; + +AnimDebugDraw& AnimDebugDraw::getInstance() { + if (!instance) { + instance = new AnimDebugDraw(); + } + return *instance; +} + +static uint32_t toRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return ((uint32_t)r | (uint32_t)g << 8 | (uint32_t)b << 16 | (uint32_t)a << 24); +} + +gpu::PipelinePointer AnimDebugDraw::_pipeline; + +AnimDebugDraw::AnimDebugDraw() : + _itemID(0) { + + auto state = std::make_shared(); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, + gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, + gpu::State::BLEND_OP_ADD, gpu::State::ONE); + auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(animdebugdraw_vert))); + auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(animdebugdraw_frag))); + auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + + _animDebugDrawData = std::make_shared(); + _animDebugDrawPayload = std::make_shared(_animDebugDrawData); + + _animDebugDrawData->_pipeline = _pipeline; + + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + if (scene) { + _itemID = scene->allocateID(); + render::PendingChanges pendingChanges; + pendingChanges.resetItem(_itemID, _animDebugDrawPayload); + scene->enqueuePendingChanges(pendingChanges); + } + + // HACK: add red, green and blue axis at (1,1,1) + _animDebugDrawData->_vertexBuffer->resize(sizeof(Vertex) * 6); + Vertex* data = (Vertex*)_animDebugDrawData->_vertexBuffer->editData(); + + data[0].pos = glm::vec3(1.0, 1.0f, 1.0f); + data[0].rgba = toRGBA(255, 0, 0, 255); + data[1].pos = glm::vec3(2.0, 1.0f, 1.0f); + data[1].rgba = toRGBA(255, 0, 0, 255); + + data[2].pos = glm::vec3(1.0, 1.0f, 1.0f); + data[2].rgba = toRGBA(0, 255, 0, 255); + data[3].pos = glm::vec3(1.0, 2.0f, 1.0f); + data[3].rgba = toRGBA(0, 255, 0, 255); + + data[4].pos = glm::vec3(1.0, 1.0f, 1.0f); + data[4].rgba = toRGBA(0, 0, 255, 255); + data[5].pos = glm::vec3(1.0, 1.0f, 2.0f); + data[5].rgba = toRGBA(0, 0, 255, 255); + + _animDebugDrawData->_indexBuffer->resize(sizeof(uint16_t) * 6); + uint16_t* indices = (uint16_t*)_animDebugDrawData->_indexBuffer->editData(); + for (int i = 0; i < 6; i++) { + indices[i] = i; + } + +} + +AnimDebugDraw::~AnimDebugDraw() { + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + if (scene && _itemID) { + render::PendingChanges pendingChanges; + pendingChanges.removeItem(_itemID); + scene->enqueuePendingChanges(pendingChanges); + } +} diff --git a/libraries/render-utils/src/AnimDebugDraw.h b/libraries/render-utils/src/AnimDebugDraw.h new file mode 100644 index 0000000000..ea3ac9ced1 --- /dev/null +++ b/libraries/render-utils/src/AnimDebugDraw.h @@ -0,0 +1,36 @@ +// +// AnimDebugDraw.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_AnimDebugDraw_h +#define hifi_AnimDebugDraw_h + +#include "AnimNode.h" +#include +#include + +class AnimDebugDrawData; +typedef render::Payload AnimDebugDrawPayload; + + +class AnimDebugDraw { +public: + static AnimDebugDraw& getInstance(); + + AnimDebugDraw(); + ~AnimDebugDraw(); + +protected: + std::shared_ptr _animDebugDrawData; + std::shared_ptr _animDebugDrawPayload; + render::ItemID _itemID; + + static gpu::PipelinePointer _pipeline; +}; + +#endif // hifi_AnimDebugDraw diff --git a/libraries/render-utils/src/animdebugdraw.slf b/libraries/render-utils/src/animdebugdraw.slf new file mode 100644 index 0000000000..aa34c9bfba --- /dev/null +++ b/libraries/render-utils/src/animdebugdraw.slf @@ -0,0 +1,17 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// unlit untextured fragment shader +// +// 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 +// + +varying vec4 varColor; + +void main(void) { + gl_FragColor = varColor; +} diff --git a/libraries/render-utils/src/animdebugdraw.slv b/libraries/render-utils/src/animdebugdraw.slv new file mode 100644 index 0000000000..68749ec7cc --- /dev/null +++ b/libraries/render-utils/src/animdebugdraw.slv @@ -0,0 +1,24 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// unlit untextured vertex shader +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +varying vec4 varColor; + +void main(void) { + // pass along the diffuse color + varColor = gl_Color; + + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> +} diff --git a/tests/animation/src/AnimClipTests.cpp b/tests/animation/src/AnimClipTests.cpp index aab49aa30d..58c7311b2d 100644 --- a/tests/animation/src/AnimClipTests.cpp +++ b/tests/animation/src/AnimClipTests.cpp @@ -1,6 +1,5 @@ // // AnimClipTests.cpp -// tests/rig/src // // Copyright 2015 High Fidelity, Inc. // diff --git a/tests/animation/src/AnimClipTests.h b/tests/animation/src/AnimClipTests.h index f70ee31aa1..d4590ef27d 100644 --- a/tests/animation/src/AnimClipTests.h +++ b/tests/animation/src/AnimClipTests.h @@ -27,4 +27,4 @@ private slots: void testLoader(); }; -#endif // hifi_TransformTests_h +#endif // hifi_AnimClipTests_h From b8bae7cc3f9b19273cd05e2280255b17ba9436c0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sat, 1 Aug 2015 15:01:25 -0700 Subject: [PATCH 07/52] =?UTF-8?q?Debug=20rendering=20of=20MyAvatar?= =?UTF-8?q?=E2=80=99s=20skeletonModel=20skeleton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface/src/Application.cpp | 7 +- interface/src/avatar/MyAvatar.cpp | 11 +++ libraries/animation/src/AnimSkeleton.cpp | 12 ++- libraries/animation/src/AnimSkeleton.h | 1 + libraries/render-utils/src/AnimDebugDraw.cpp | 87 ++++++++++++++++++++ libraries/render-utils/src/AnimDebugDraw.h | 15 +++- libraries/shared/src/GLMHelpers.h | 3 + libraries/shared/src/Transform.h | 7 ++ 8 files changed, 135 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e43465ad75..ed5cba6ac1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -647,8 +647,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket"); - - AnimDebugDraw& add = AnimDebugDraw::getInstance(); } void Application::aboutToQuit() { @@ -2622,6 +2620,11 @@ void Application::update(float deltaTime) { loadViewFrustum(_myCamera, _viewFrustum); } + // Update animation debug draw renderer + { + AnimDebugDraw::getInstance().update(); + } + quint64 now = usecTimestampNow(); // Update my voxel servers with my current voxel query... diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2d7cf4ca5e..80da07b966 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -49,6 +49,8 @@ #include "gpu/GLBackend.h" +#include "AnimDebugDraw.h" +#include "AnimSkeleton.h" using namespace std; @@ -1203,6 +1205,15 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { if (_skeletonModel.initWhenReady(scene)) { initHeadBones(); _skeletonModel.setCauterizeBoneSet(_headBoneSet); + + // create a skeleton and hand it over to the debug draw instance + auto geom = _skeletonModel.getGeometry()->getFBXGeometry(); + std::vector joints; + for (auto& joint : geom.joints) { + joints.push_back(joint); + } + auto skeleton = make_shared(joints); + AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, Transform()); } if (shouldDrawHead != _prevShouldDrawHead) { diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 695dea0752..96b90b8f46 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -8,6 +8,7 @@ // #include "AnimSkeleton.h" +#include "glmHelpers.h" AnimSkeleton::AnimSkeleton(const std::vector& joints) { _joints = joints; @@ -27,10 +28,15 @@ int AnimSkeleton::getNumJoints() const { } AnimPose AnimSkeleton::getBindPose(int jointIndex) const { - // TODO: what coordinate frame is the bindTransform in? local to the bones parent frame? or model? - return AnimPose(glm::vec3(1.0f, 1.0f, 1.0f), + // TODO: perhaps cache these, it's expensive to de-compose the matrix + // on every call. + return AnimPose(extractScale(_joints[jointIndex].bindTransform), glm::quat_cast(_joints[jointIndex].bindTransform), - glm::vec3(0.0f, 0.0f, 0.0f)); + extractTranslation(_joints[jointIndex].bindTransform)); +} + +int AnimSkeleton::getParentIndex(int jointIndex) const { + return _joints[jointIndex].parentIndex; } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 0966c2b5f7..0b50e60b65 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -31,6 +31,7 @@ public: int nameToJointIndex(const QString& jointName) const; int getNumJoints() const; AnimPose getBindPose(int jointIndex) const; + int getParentIndex(int jointIndex) const; protected: std::vector _joints; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 80a8824d5a..263241ca0e 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -142,3 +142,90 @@ AnimDebugDraw::~AnimDebugDraw() { scene->enqueuePendingChanges(pendingChanges); } } + +void AnimDebugDraw::addSkeleton(std::string key, AnimSkeleton::Pointer skeleton, const Transform& worldTransform) { + _skeletons[key] = SkeletonInfo(skeleton, worldTransform); +} + +void AnimDebugDraw::removeSkeleton(std::string key) { + _skeletons.erase(key); +} + +void AnimDebugDraw::update() { + + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + if (!scene) { + return; + } + + render::PendingChanges pendingChanges; + pendingChanges.updateItem(_itemID, [&](AnimDebugDrawData& data) { + + // figure out how many verts we will need. + int numVerts = 0; + for (auto&& iter : _skeletons) { + AnimSkeleton::Pointer& skeleton = iter.second.first; + numVerts += skeleton->getNumJoints() * 6; + for (int i = 0; i < skeleton->getNumJoints(); i++) { + if (skeleton->getParentIndex(i) >= 0) { + numVerts += 2; + } + } + } + + const uint32_t red = toRGBA(255, 0, 0, 255); + const uint32_t green = toRGBA(0, 255, 0, 255); + const uint32_t blue = toRGBA(0, 0, 255, 255); + const uint32_t gray = toRGBA(64, 64, 64, 255); + + data._vertexBuffer->resize(sizeof(Vertex) * numVerts); + Vertex* verts = (Vertex*)data._vertexBuffer->editData(); + Vertex* v = verts; + for (auto&& iter : _skeletons) { + AnimSkeleton::Pointer& skeleton = iter.second.first; + Transform& xform = iter.second.second; + for (int i = 0; i < skeleton->getNumJoints(); i++) { + AnimPose pose = skeleton->getBindPose(i); + + v->pos = xform.transform(pose.trans); + v->rgba = red; + v++; + v->pos = xform.transform(pose.trans + pose.rot * glm::vec3(pose.scale.x, 0.0f, 0.0f)); + v->rgba = red; + v++; + + v->pos = xform.transform(pose.trans); + v->rgba = green; + v++; + v->pos = xform.transform(pose.trans + pose.rot * glm::vec3(0.0f, pose.scale.y, 0.0f)); + v->rgba = green; + v++; + + v->pos = xform.transform(pose.trans); + v->rgba = blue; + v++; + v->pos = xform.transform(pose.trans + pose.rot * glm::vec3(0.0f, 0.0f, pose.scale.z)); + v->rgba = blue; + v++; + + if (skeleton->getParentIndex(i) >= 0) { + AnimPose parentPose = skeleton->getBindPose(skeleton->getParentIndex(i)); + v->pos = xform.transform(pose.trans); + v->rgba = gray; + v++; + v->pos = xform.transform(parentPose.trans); + v->rgba = gray; + v++; + } + } + } + + data._indexBuffer->resize(sizeof(uint16_t) * numVerts); + uint16_t* indices = (uint16_t*)data._indexBuffer->editData(); + for (int i = 0; i < numVerts; i++) { + indices[i] = i; + } + + }); + scene->enqueuePendingChanges(pendingChanges); +} diff --git a/libraries/render-utils/src/AnimDebugDraw.h b/libraries/render-utils/src/AnimDebugDraw.h index ea3ac9ced1..136c27d1aa 100644 --- a/libraries/render-utils/src/AnimDebugDraw.h +++ b/libraries/render-utils/src/AnimDebugDraw.h @@ -10,14 +10,14 @@ #ifndef hifi_AnimDebugDraw_h #define hifi_AnimDebugDraw_h +#include "render/Scene.h" +#include "gpu/Pipeline.h" #include "AnimNode.h" -#include -#include +#include "AnimSkeleton.h" class AnimDebugDrawData; typedef render::Payload AnimDebugDrawPayload; - class AnimDebugDraw { public: static AnimDebugDraw& getInstance(); @@ -25,12 +25,21 @@ public: AnimDebugDraw(); ~AnimDebugDraw(); + void addSkeleton(std::string key, AnimSkeleton::Pointer skeleton, const Transform& worldTransform); + void removeSkeleton(std::string key); + + void update(); + protected: std::shared_ptr _animDebugDrawData; std::shared_ptr _animDebugDrawPayload; render::ItemID _itemID; static gpu::PipelinePointer _pipeline; + + typedef std::pair SkeletonInfo; + + std::unordered_map _skeletons; }; #endif // hifi_AnimDebugDraw diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 1a61c426e5..accf4246c6 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -150,16 +150,19 @@ T toNormalizedDeviceScale(const T& value, const T& size) { #define PITCH(euler) euler.x #define ROLL(euler) euler.z +// vec2 lerp - linear interpolate template glm::detail::tvec2 lerp(const glm::detail::tvec2& x, const glm::detail::tvec2& y, T a) { return x * (T(1) - a) + (y * a); } +// vec3 lerp - linear interpolate template glm::detail::tvec3 lerp(const glm::detail::tvec3& x, const glm::detail::tvec3& y, T a) { return x * (T(1) - a) + (y * a); } +// vec4 lerp - linear interpolate template glm::detail::tvec4 lerp(const glm::detail::tvec4& x, const glm::detail::tvec4& y, T a) { return x * (T(1) - a) + (y * a); diff --git a/libraries/shared/src/Transform.h b/libraries/shared/src/Transform.h index 3064e18471..699eb8ef0a 100644 --- a/libraries/shared/src/Transform.h +++ b/libraries/shared/src/Transform.h @@ -129,6 +129,7 @@ public: static Transform& inverseMult(Transform& result, const Transform& left, const Transform& right); Vec4 transform(const Vec4& pos) const; + Vec3 transform(const Vec3& pos) const; protected: @@ -504,6 +505,12 @@ inline Transform::Vec4 Transform::transform(const Vec4& pos) const { return m * pos; } +inline Transform::Vec3 Transform::transform(const Vec3& pos) const { + Mat4 m; + getMatrix(m); + Vec4 result = m * Vec4(pos, 1.0f); + return Vec3(result.x / result.w, result.y / result.w, result.z / result.w); +} inline Transform::Mat4& Transform::getCachedMatrix(Transform::Mat4& result) const { updateCache(); From d1fdbe32d2591a2fcb2ce86dc2eb907455a7e6fe Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sat, 1 Aug 2015 15:24:58 -0700 Subject: [PATCH 08/52] optimizations for debug rendering of AnimSkeleton --- libraries/animation/src/AnimSkeleton.cpp | 14 +++++++++----- libraries/animation/src/AnimSkeleton.h | 1 + libraries/render-utils/src/AnimDebugDraw.cpp | 18 ++++++++++++------ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 96b90b8f46..e30be784ca 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -12,6 +12,14 @@ AnimSkeleton::AnimSkeleton(const std::vector& joints) { _joints = joints; + + // build a cache of bind poses + _bindPoses.reserve(joints.size()); + for (size_t i = 0; i < joints.size(); i++) { + _bindPoses.push_back(AnimPose(extractScale(_joints[i].bindTransform), + glm::quat_cast(_joints[i].bindTransform), + extractTranslation(_joints[i].bindTransform))); + } } int AnimSkeleton::nameToJointIndex(const QString& jointName) const { @@ -28,11 +36,7 @@ int AnimSkeleton::getNumJoints() const { } AnimPose AnimSkeleton::getBindPose(int jointIndex) const { - // TODO: perhaps cache these, it's expensive to de-compose the matrix - // on every call. - return AnimPose(extractScale(_joints[jointIndex].bindTransform), - glm::quat_cast(_joints[jointIndex].bindTransform), - extractTranslation(_joints[jointIndex].bindTransform)); + return _bindPoses[jointIndex]; } int AnimSkeleton::getParentIndex(int jointIndex) const { diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 0b50e60b65..7afc419e6b 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -35,6 +35,7 @@ public: protected: std::vector _joints; + std::vector _bindPoses; }; #endif diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 263241ca0e..9439e8314f 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -187,30 +187,36 @@ void AnimDebugDraw::update() { for (int i = 0; i < skeleton->getNumJoints(); i++) { AnimPose pose = skeleton->getBindPose(i); - v->pos = xform.transform(pose.trans); + glm::vec3 base = xform.transform(pose.trans); + + // x-axis + v->pos = base; v->rgba = red; v++; - v->pos = xform.transform(pose.trans + pose.rot * glm::vec3(pose.scale.x, 0.0f, 0.0f)); + v->pos = base + pose.rot * glm::vec3(1.0f, 0.0f, 0.0f); v->rgba = red; v++; + // y-axis v->pos = xform.transform(pose.trans); v->rgba = green; v++; - v->pos = xform.transform(pose.trans + pose.rot * glm::vec3(0.0f, pose.scale.y, 0.0f)); + v->pos = base + pose.rot * glm::vec3(0.0f, 1.0f, 0.0f); v->rgba = green; v++; - v->pos = xform.transform(pose.trans); + // z-axis + v->pos = base; v->rgba = blue; v++; - v->pos = xform.transform(pose.trans + pose.rot * glm::vec3(0.0f, 0.0f, pose.scale.z)); + v->pos = base + pose.rot * glm::vec3(0.0f, 0.0f, 1.0f); v->rgba = blue; v++; + // line to parent. if (skeleton->getParentIndex(i) >= 0) { AnimPose parentPose = skeleton->getBindPose(skeleton->getParentIndex(i)); - v->pos = xform.transform(pose.trans); + v->pos = base; v->rgba = gray; v++; v->pos = xform.transform(parentPose.trans); From df794637504e4693830cdc65d9e686a9efb2245a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sun, 2 Aug 2015 12:50:14 -0700 Subject: [PATCH 09/52] WIP, animNode rendering --- interface/src/avatar/MyAvatar.cpp | 14 ++- interface/src/avatar/MyAvatar.h | 3 + libraries/animation/src/AnimClip.cpp | 12 +- libraries/animation/src/AnimClip.h | 7 +- libraries/animation/src/AnimNode.h | 5 + libraries/animation/src/AnimSkeleton.cpp | 4 + libraries/animation/src/AnimSkeleton.h | 5 + libraries/render-utils/src/AnimDebugDraw.cpp | 123 +++++++++++++------ libraries/render-utils/src/AnimDebugDraw.h | 9 +- 9 files changed, 136 insertions(+), 46 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 80da07b966..86d5d06719 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -51,6 +51,7 @@ #include "AnimDebugDraw.h" #include "AnimSkeleton.h" +#include "AnimClip.h" using namespace std; @@ -147,6 +148,11 @@ void MyAvatar::reset() { } void MyAvatar::update(float deltaTime) { + + if (_animNode) { + _animNode->evaluate(deltaTime); + } + if (_referential) { _referential->update(); } @@ -1206,6 +1212,8 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { initHeadBones(); _skeletonModel.setCauterizeBoneSet(_headBoneSet); + // AJT: SETUP DEBUG RENDERING OF NEW ANIMATION SYSTEM + // create a skeleton and hand it over to the debug draw instance auto geom = _skeletonModel.getGeometry()->getFBXGeometry(); std::vector joints; @@ -1213,7 +1221,11 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { joints.push_back(joint); } auto skeleton = make_shared(joints); - AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, Transform()); + //AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, AnimPose::identity); + + _animNode = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); + _animNode->setSkeleton(skeleton); + AnimDebugDraw::getInstance().addAnimNode("clip", _animNode, AnimPose::identity); } if (shouldDrawHead != _prevShouldDrawHead) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 129a05f93b..dc7c9e164f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -19,6 +19,7 @@ #include "Avatar.h" class ModelItemID; +class AnimNode; enum eyeContactTarget { LEFT_EYE, @@ -281,6 +282,8 @@ private: RigPointer _rig; bool _prevShouldDrawHead; std::unordered_set _headBoneSet; + + std::shared_ptr _animNode; }; #endif // hifi_MyAvatar_h diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index d10430dec3..b76c8d8ef3 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -13,14 +13,13 @@ AnimClip::AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : AnimNode(AnimNode::ClipType, id), - _url(url), _startFrame(startFrame), _endFrame(endFrame), _timeScale(timeScale), _loopFlag(loopFlag), _frame(startFrame) { - + setURL(url); } AnimClip::~AnimClip() { @@ -128,6 +127,9 @@ void AnimClip::copyFromNetworkAnim() { jointMap.reserve(animJointCount); for (int i = 0; i < animJointCount; i++) { int skeletonJoint = _skeleton->nameToJointIndex(joints.at(i).name); + if (skeletonJoint == -1) { + qCWarning(animation) << "animation contains joint =" << joints.at(i).name << " which is not in the skeleton, url =" << _url.c_str(); + } jointMap.push_back(skeletonJoint); } @@ -151,4 +153,10 @@ void AnimClip::copyFromNetworkAnim() { } } } + _poses.resize(skeletonJointCount); +} + + +const std::vector& AnimClip::getPosesInternal() const { + return _poses; } diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 9858e5b524..59b286b95f 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -19,7 +19,7 @@ public: friend class AnimClipTests; AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag); - virtual ~AnimClip(); + virtual ~AnimClip() override; void setURL(const std::string& url); const std::string& getURL() const { return _url; } @@ -36,12 +36,15 @@ public: void setLoopFlag(bool loopFlag); bool getLoopFlag() const { return _loopFlag; } - virtual const std::vector& evaluate(float dt); + virtual const std::vector& evaluate(float dt) override; protected: float accumulateTime(float frame, float dt) const; void copyFromNetworkAnim(); + // for AnimDebugDraw rendering + virtual const std::vector& getPosesInternal() const override; + AnimationPointer _networkAnim; std::vector _poses; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index a801b8b2bc..97249e2649 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -23,6 +23,8 @@ class QJsonObject; class AnimNode { public: + friend class AnimDebugDraw; + enum Type { ClipType = 0, NumTypes @@ -55,6 +57,9 @@ public: virtual const std::vector& evaluate(float dt) = 0; protected: + // for AnimDebugDraw rendering + virtual const std::vector& getPosesInternal() const = 0; + Type _type; std::string _id; std::vector _children; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index e30be784ca..9112ae301c 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -10,6 +10,10 @@ #include "AnimSkeleton.h" #include "glmHelpers.h" +const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), + glm::quat(), + glm::vec3(0.0f)); + AnimSkeleton::AnimSkeleton(const std::vector& joints) { _joints = joints; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 7afc419e6b..4a1124901e 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -17,6 +17,11 @@ struct AnimPose { AnimPose() {} AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {} + static const AnimPose identity; + + glm::vec3 operator*(const glm::vec3& pos) const { + return trans + (rot * (scale * pos)); + } glm::vec3 scale; glm::quat rot; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 9439e8314f..13c71ec2e9 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -58,7 +58,7 @@ public: typedef render::Payload AnimDebugDrawPayload; namespace render { - template <> const ItemKey payloadGetKey(const AnimDebugDrawData::Pointer& data) { return ItemKey::Builder::transparentShape(); } + template <> const ItemKey payloadGetKey(const AnimDebugDrawData::Pointer& data) { return ItemKey::Builder::opaqueShape(); } template <> const Item::Bound payloadGetBound(const AnimDebugDrawData::Pointer& data) { return Item::Bound(); } template <> void payloadRender(const AnimDebugDrawData::Pointer& data, RenderArgs* args) { data->render(args); @@ -143,14 +143,65 @@ AnimDebugDraw::~AnimDebugDraw() { } } -void AnimDebugDraw::addSkeleton(std::string key, AnimSkeleton::Pointer skeleton, const Transform& worldTransform) { - _skeletons[key] = SkeletonInfo(skeleton, worldTransform); +void AnimDebugDraw::addSkeleton(std::string key, AnimSkeleton::Pointer skeleton, const AnimPose& rootPose) { + _skeletons[key] = SkeletonInfo(skeleton, rootPose); } void AnimDebugDraw::removeSkeleton(std::string key) { _skeletons.erase(key); } +void AnimDebugDraw::addAnimNode(std::string key, AnimNode::Pointer animNode, const AnimPose& rootPose) { + _animNodes[key] = AnimNodeInfo(animNode, rootPose); +} + +void AnimDebugDraw::removeAnimNode(std::string key) { + _animNodes.erase(key); +} + +static const uint32_t red = toRGBA(255, 0, 0, 255); +static const uint32_t green = toRGBA(0, 255, 0, 255); +static const uint32_t blue = toRGBA(0, 0, 255, 255); +static const uint32_t cyan = toRGBA(0, 128, 128, 255); + +static void addWireframeSphereWithAxes(const AnimPose& rootPose, const AnimPose& pose, float radius, Vertex*& v) { + + // x-axis + v->pos = rootPose * pose.trans; + v->rgba = red; + v++; + v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(radius * 2.0f, 0.0f, 0.0f)); + v->rgba = red; + v++; + + // y-axis + v->pos = rootPose * pose.trans; + v->rgba = green; + v++; + v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(0.0f, radius * 2.0f, 0.0f)); + v->rgba = green; + v++; + + // z-axis + v->pos = rootPose * pose.trans; + v->rgba = blue; + v++; + v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(0.0f, 0.0f, radius * 2.0f)); + v->rgba = blue; + v++; +} + +static void addWireframeBoneAxis(const AnimPose& rootPose, const AnimPose& pose, + const AnimPose& parentPose, float radius, Vertex*& v) { + v->pos = rootPose * pose.trans; + v->rgba = cyan; + v++; + v->pos = rootPose * parentPose.trans; + v->rgba = cyan; + v++; +} + + void AnimDebugDraw::update() { render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); @@ -173,59 +224,53 @@ void AnimDebugDraw::update() { } } - const uint32_t red = toRGBA(255, 0, 0, 255); - const uint32_t green = toRGBA(0, 255, 0, 255); - const uint32_t blue = toRGBA(0, 0, 255, 255); - const uint32_t gray = toRGBA(64, 64, 64, 255); + for (auto&& iter : _animNodes) { + AnimNode::Pointer& animNode = iter.second.first; + auto poses = animNode->getPosesInternal(); + numVerts += poses.size() * 6; + + /* + for (int i = 0; i < poses.size(); i++) { + // TODO: need skeleton! + if (skeleton->getParentIndex(i) >= 0) { + numVerts += 2; + } + } + */ + } data._vertexBuffer->resize(sizeof(Vertex) * numVerts); Vertex* verts = (Vertex*)data._vertexBuffer->editData(); Vertex* v = verts; for (auto&& iter : _skeletons) { AnimSkeleton::Pointer& skeleton = iter.second.first; - Transform& xform = iter.second.second; + AnimPose& rootPose = iter.second.second; for (int i = 0; i < skeleton->getNumJoints(); i++) { AnimPose pose = skeleton->getBindPose(i); - glm::vec3 base = xform.transform(pose.trans); - - // x-axis - v->pos = base; - v->rgba = red; - v++; - v->pos = base + pose.rot * glm::vec3(1.0f, 0.0f, 0.0f); - v->rgba = red; - v++; - - // y-axis - v->pos = xform.transform(pose.trans); - v->rgba = green; - v++; - v->pos = base + pose.rot * glm::vec3(0.0f, 1.0f, 0.0f); - v->rgba = green; - v++; - - // z-axis - v->pos = base; - v->rgba = blue; - v++; - v->pos = base + pose.rot * glm::vec3(0.0f, 0.0f, 1.0f); - v->rgba = blue; - v++; + // draw axes + const float radius = 1.0f; + addWireframeSphereWithAxes(rootPose, pose, radius, v); // line to parent. if (skeleton->getParentIndex(i) >= 0) { AnimPose parentPose = skeleton->getBindPose(skeleton->getParentIndex(i)); - v->pos = base; - v->rgba = gray; - v++; - v->pos = xform.transform(parentPose.trans); - v->rgba = gray; - v++; + addWireframeBoneAxis(rootPose, pose, parentPose, radius, v); } } } + for (auto&& iter : _animNodes) { + AnimNode::Pointer& animNode = iter.second.first; + AnimPose& rootPose = iter.second.second; + auto poses = animNode->getPosesInternal(); + for (size_t i = 0; i < poses.size(); i++) { + // draw axes + const float radius = 1.0f; + addWireframeSphereWithAxes(rootPose, poses[i], radius, v); + } + } + data._indexBuffer->resize(sizeof(uint16_t) * numVerts); uint16_t* indices = (uint16_t*)data._indexBuffer->editData(); for (int i = 0; i < numVerts; i++) { diff --git a/libraries/render-utils/src/AnimDebugDraw.h b/libraries/render-utils/src/AnimDebugDraw.h index 136c27d1aa..6957696184 100644 --- a/libraries/render-utils/src/AnimDebugDraw.h +++ b/libraries/render-utils/src/AnimDebugDraw.h @@ -25,9 +25,12 @@ public: AnimDebugDraw(); ~AnimDebugDraw(); - void addSkeleton(std::string key, AnimSkeleton::Pointer skeleton, const Transform& worldTransform); + void addSkeleton(std::string key, AnimSkeleton::Pointer skeleton, const AnimPose& rootPose); void removeSkeleton(std::string key); + void addAnimNode(std::string key, AnimNode::Pointer skeleton, const AnimPose& rootPose); + void removeAnimNode(std::string key); + void update(); protected: @@ -37,9 +40,11 @@ protected: static gpu::PipelinePointer _pipeline; - typedef std::pair SkeletonInfo; + typedef std::pair SkeletonInfo; + typedef std::pair AnimNodeInfo; std::unordered_map _skeletons; + std::unordered_map _animNodes; }; #endif // hifi_AnimDebugDraw From 55da34f713c263d2c4365edbe98132ce95c25683 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sun, 2 Aug 2015 15:22:30 -0700 Subject: [PATCH 10/52] Better debug rendering of animations. * added mat4 cast and mat4 ctors to AnimPose. --- interface/src/avatar/MyAvatar.cpp | 6 ++- libraries/animation/src/AnimClip.cpp | 8 ++-- libraries/animation/src/AnimSkeleton.cpp | 39 +++++++++++++++++--- libraries/animation/src/AnimSkeleton.h | 22 +++++++++-- libraries/render-utils/src/AnimDebugDraw.cpp | 31 ++++++++++++---- 5 files changed, 83 insertions(+), 23 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 86d5d06719..18d01e5d2c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1221,11 +1221,13 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { joints.push_back(joint); } auto skeleton = make_shared(joints); - //AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, AnimPose::identity); + AnimPose xform(glm::vec3(0.01f), glm::quat(), glm::vec3(0.0)); + AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform); _animNode = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); _animNode->setSkeleton(skeleton); - AnimDebugDraw::getInstance().addAnimNode("clip", _animNode, AnimPose::identity); + xform.trans.z += 1.0f; + AnimDebugDraw::getInstance().addAnimNode("clip", _animNode, xform); } if (shouldDrawHead != _prevShouldDrawHead) { diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index b76c8d8ef3..6f16a8d884 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -138,18 +138,18 @@ void AnimClip::copyFromNetworkAnim() { _anim.resize(frameCount); for (int i = 0; i < frameCount; i++) { - // init all joints in animation to bind pose + // init all joints in animation to relative bind pose _anim[i].reserve(skeletonJointCount); for (int j = 0; j < skeletonJointCount; j++) { - _anim[i].push_back(_skeleton->getBindPose(j)); + _anim[i].push_back(_skeleton->getRelativeBindPose(j)); } // init over all joint animations for (int j = 0; j < animJointCount; j++) { int k = jointMap[j]; if (k >= 0 && k < skeletonJointCount) { - // currently animations only have rotation. - _anim[i][k].rot = geom.animationFrames[i].rotations[j]; + // currently FBX animations only have rotation. + _anim[i][k].rot = _skeleton->getRelativeBindPose(k).rot * geom.animationFrames[i].rotations[j]; } } } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 9112ae301c..e966203d22 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -14,15 +14,38 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), glm::quat(), glm::vec3(0.0f)); +AnimPose::AnimPose(const glm::mat4& mat) { + scale = extractScale(mat); + rot = glm::quat_cast(mat); + trans = extractTranslation(mat); +} + +AnimPose::operator glm::mat4() const { + glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f); + glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f); + glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z); + return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f), + glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); +} + AnimSkeleton::AnimSkeleton(const std::vector& joints) { _joints = joints; // build a cache of bind poses - _bindPoses.reserve(joints.size()); + _absoluteBindPoses.reserve(joints.size()); + _relativeBindPoses.reserve(joints.size()); for (size_t i = 0; i < joints.size(); i++) { - _bindPoses.push_back(AnimPose(extractScale(_joints[i].bindTransform), - glm::quat_cast(_joints[i].bindTransform), - extractTranslation(_joints[i].bindTransform))); + glm::mat4 absBindMat = _joints[i].bindTransform; + AnimPose absBindPose(_joints[i].bindTransform); + _absoluteBindPoses.push_back(absBindPose); + int parentIndex = getParentIndex(i); + if (parentIndex >= 0) { + glm::mat4 invParentAbsBindMat = glm::inverse(_joints[parentIndex].bindTransform); + glm::mat4 relBindMat = invParentAbsBindMat * absBindMat; + _relativeBindPoses.push_back(AnimPose(relBindMat)); + } else { + _relativeBindPoses.push_back(absBindPose); + } } } @@ -39,8 +62,12 @@ int AnimSkeleton::getNumJoints() const { return _joints.size(); } -AnimPose AnimSkeleton::getBindPose(int jointIndex) const { - return _bindPoses[jointIndex]; +AnimPose AnimSkeleton::getAbsoluteBindPose(int jointIndex) const { + return _absoluteBindPoses[jointIndex]; +} + +AnimPose AnimSkeleton::getRelativeBindPose(int jointIndex) const { + return _relativeBindPoses[jointIndex]; } int AnimSkeleton::getParentIndex(int jointIndex) const { diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 4a1124901e..9019a1b356 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -16,13 +16,20 @@ struct AnimPose { AnimPose() {} + AnimPose(const glm::mat4& mat); AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {} static const AnimPose identity; - glm::vec3 operator*(const glm::vec3& pos) const { - return trans + (rot * (scale * pos)); + glm::vec3 operator*(const glm::vec3& rhs) const { + return trans + (rot * (scale * rhs)); } + AnimPose operator*(const AnimPose& rhs) const { + return AnimPose(static_cast(*this) * static_cast(rhs)); + } + + operator glm::mat4() const; + glm::vec3 scale; glm::quat rot; glm::vec3 trans; @@ -35,12 +42,19 @@ public: AnimSkeleton(const std::vector& joints); int nameToJointIndex(const QString& jointName) const; int getNumJoints() const; - AnimPose getBindPose(int jointIndex) const; + + // absolute pose, not relative to parent + AnimPose getAbsoluteBindPose(int jointIndex) const; + + // relative to parent pose + AnimPose getRelativeBindPose(int jointIndex) const; + int getParentIndex(int jointIndex) const; protected: std::vector _joints; - std::vector _bindPoses; + std::vector _absoluteBindPoses; + std::vector _relativeBindPoses; }; #endif diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 13c71ec2e9..5c646d9052 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -228,15 +228,12 @@ void AnimDebugDraw::update() { AnimNode::Pointer& animNode = iter.second.first; auto poses = animNode->getPosesInternal(); numVerts += poses.size() * 6; - - /* + auto skeleton = animNode->getSkeleton(); for (int i = 0; i < poses.size(); i++) { - // TODO: need skeleton! if (skeleton->getParentIndex(i) >= 0) { numVerts += 2; } } - */ } data._vertexBuffer->resize(sizeof(Vertex) * numVerts); @@ -245,8 +242,9 @@ void AnimDebugDraw::update() { for (auto&& iter : _skeletons) { AnimSkeleton::Pointer& skeleton = iter.second.first; AnimPose& rootPose = iter.second.second; + for (int i = 0; i < skeleton->getNumJoints(); i++) { - AnimPose pose = skeleton->getBindPose(i); + AnimPose pose = skeleton->getAbsoluteBindPose(i); // draw axes const float radius = 1.0f; @@ -254,7 +252,7 @@ void AnimDebugDraw::update() { // line to parent. if (skeleton->getParentIndex(i) >= 0) { - AnimPose parentPose = skeleton->getBindPose(skeleton->getParentIndex(i)); + AnimPose parentPose = skeleton->getAbsoluteBindPose(skeleton->getParentIndex(i)); addWireframeBoneAxis(rootPose, pose, parentPose, radius, v); } } @@ -264,10 +262,29 @@ void AnimDebugDraw::update() { AnimNode::Pointer& animNode = iter.second.first; AnimPose& rootPose = iter.second.second; auto poses = animNode->getPosesInternal(); + + auto skeleton = animNode->getSkeleton(); + + std::vector absAnimPose; + absAnimPose.reserve(skeleton->getNumJoints()); + for (size_t i = 0; i < poses.size(); i++) { + + auto parentIndex = skeleton->getParentIndex(i); + if (parentIndex >= 0) { + absAnimPose[i] = absAnimPose[parentIndex] * poses[i]; + } else { + absAnimPose[i] = poses[i]; + } + // draw axes const float radius = 1.0f; - addWireframeSphereWithAxes(rootPose, poses[i], radius, v); + addWireframeSphereWithAxes(rootPose, absAnimPose[i], radius, v); + + if (parentIndex >= 0) { + // draw line to parent + addWireframeBoneAxis(rootPose, absAnimPose[i], absAnimPose[parentIndex], radius, v); + } } } From 69cc27080220296fd0d94614cfed5239c3812bfa Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 3 Aug 2015 10:02:05 -0700 Subject: [PATCH 11/52] removed scale for fight club model --- interface/src/avatar/MyAvatar.cpp | 2 +- libraries/render-utils/src/AnimDebugDraw.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 18d01e5d2c..99b634dec1 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1221,7 +1221,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { joints.push_back(joint); } auto skeleton = make_shared(joints); - AnimPose xform(glm::vec3(0.01f), glm::quat(), glm::vec3(0.0)); + AnimPose xform(glm::vec3(1.0f), glm::quat(), glm::vec3(0.0)); AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform); _animNode = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 5c646d9052..6e3bb4bc8a 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -247,7 +247,7 @@ void AnimDebugDraw::update() { AnimPose pose = skeleton->getAbsoluteBindPose(i); // draw axes - const float radius = 1.0f; + const float radius = 0.01f; addWireframeSphereWithAxes(rootPose, pose, radius, v); // line to parent. @@ -278,7 +278,7 @@ void AnimDebugDraw::update() { } // draw axes - const float radius = 1.0f; + const float radius = 0.01f; addWireframeSphereWithAxes(rootPose, absAnimPose[i], radius, v); if (parentIndex >= 0) { From d8a20340a043cf8840d254a006799217abe7b308 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 3 Aug 2015 18:31:32 -0700 Subject: [PATCH 12/52] Found and fix source of memory corruption. std::vector.reserve() and raw access do not mix. raw access will only work if you push_back elements onto the vector first. However this worked fine on MacOSX, probably due to differences in STL implementations. Some code clean up and some commented out debugging lines. Debug rendering of animaions of fight club model is not working. Not sure what frame these transformations are in. --- libraries/animation/src/AnimClip.cpp | 14 +++--- libraries/animation/src/AnimSkeleton.cpp | 49 ++++++++++++++++---- libraries/animation/src/AnimSkeleton.h | 19 ++++---- libraries/render-utils/src/AnimDebugDraw.cpp | 25 +++++++--- 4 files changed, 75 insertions(+), 32 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 6f16a8d884..dbb241d484 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -121,14 +121,14 @@ void AnimClip::copyFromNetworkAnim() { // build a mapping from animation joint indices to skeleton joint indices. // by matching joints with the same name. const FBXGeometry& geom = _networkAnim->getGeometry(); - const QVector& joints = geom.joints; + const QVector& animJoints = geom.joints; std::vector jointMap; - const int animJointCount = joints.count(); + const int animJointCount = animJoints.count(); jointMap.reserve(animJointCount); for (int i = 0; i < animJointCount; i++) { - int skeletonJoint = _skeleton->nameToJointIndex(joints.at(i).name); + int skeletonJoint = _skeleton->nameToJointIndex(animJoints.at(i).name); if (skeletonJoint == -1) { - qCWarning(animation) << "animation contains joint =" << joints.at(i).name << " which is not in the skeleton, url =" << _url.c_str(); + qCWarning(animation) << "animation contains joint =" << animJoints.at(i).name << " which is not in the skeleton, url =" << _url.c_str(); } jointMap.push_back(skeletonJoint); } @@ -138,10 +138,10 @@ void AnimClip::copyFromNetworkAnim() { _anim.resize(frameCount); for (int i = 0; i < frameCount; i++) { - // init all joints in animation to relative bind pose + // init all joints in animation to identity _anim[i].reserve(skeletonJointCount); for (int j = 0; j < skeletonJointCount; j++) { - _anim[i].push_back(_skeleton->getRelativeBindPose(j)); + _anim[i].push_back(AnimPose::identity); } // init over all joint animations @@ -149,7 +149,7 @@ void AnimClip::copyFromNetworkAnim() { int k = jointMap[j]; if (k >= 0 && k < skeletonJointCount) { // currently FBX animations only have rotation. - _anim[i][k].rot = _skeleton->getRelativeBindPose(k).rot * geom.animationFrames[i].rotations[j]; + _anim[i][k].rot = geom.animationFrames[i].rotations[j]; } } } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index e966203d22..feaa1828ce 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -8,6 +8,7 @@ // #include "AnimSkeleton.h" +#include "AnimationLogging.h" #include "glmHelpers.h" const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), @@ -16,10 +17,22 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), AnimPose::AnimPose(const glm::mat4& mat) { scale = extractScale(mat); - rot = glm::quat_cast(mat); + rot = glm::normalize(glm::quat_cast(mat)); trans = extractTranslation(mat); } +glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const { + return trans + (rot * (scale * rhs)); +} + +AnimPose AnimPose::operator*(const AnimPose& rhs) const { + return AnimPose(static_cast(*this) * static_cast(rhs)); +} + +AnimPose AnimPose::inverse() const { + return AnimPose(glm::inverse(static_cast(*this))); +} + AnimPose::operator glm::mat4() const { glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f); glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f); @@ -28,6 +41,7 @@ AnimPose::operator glm::mat4() const { glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); } + AnimSkeleton::AnimSkeleton(const std::vector& joints) { _joints = joints; @@ -35,17 +49,32 @@ AnimSkeleton::AnimSkeleton(const std::vector& joints) { _absoluteBindPoses.reserve(joints.size()); _relativeBindPoses.reserve(joints.size()); for (size_t i = 0; i < joints.size(); i++) { - glm::mat4 absBindMat = _joints[i].bindTransform; - AnimPose absBindPose(_joints[i].bindTransform); - _absoluteBindPoses.push_back(absBindPose); + + AnimPose absoluteBindPose(_joints[i].bindTransform); + _absoluteBindPoses.push_back(absoluteBindPose); + int parentIndex = getParentIndex(i); if (parentIndex >= 0) { - glm::mat4 invParentAbsBindMat = glm::inverse(_joints[parentIndex].bindTransform); - glm::mat4 relBindMat = invParentAbsBindMat * absBindMat; - _relativeBindPoses.push_back(AnimPose(relBindMat)); + AnimPose inverseParentAbsoluteBindPose = _absoluteBindPoses[parentIndex].inverse(); + _relativeBindPoses.push_back(inverseParentAbsoluteBindPose * absoluteBindPose); } else { - _relativeBindPoses.push_back(absBindPose); + _relativeBindPoses.push_back(absoluteBindPose); } + + // AJT: + // Attempt to use relative bind pose.. but it's not working. + /* + AnimPose relBindPose(glm::vec3(1.0f), _joints[i].rotation, _joints[i].translation); + _relativeBindPoses.push_back(relBindPose); + + int parentIndex = getParentIndex(i); + if (parentIndex >= 0) { + AnimPose parentAbsBindPose = _absoluteBindPoses[parentIndex]; + _absoluteBindPoses.push_back(parentAbsBindPose * relBindPose); + } else { + _absoluteBindPoses.push_back(relBindPose); + } + */ } } @@ -74,4 +103,6 @@ int AnimSkeleton::getParentIndex(int jointIndex) const { return _joints[jointIndex].parentIndex; } - +const QString& AnimSkeleton::getJointName(int jointIndex) const { + return _joints[jointIndex].name; +} diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 9019a1b356..b51daadae7 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -16,18 +16,13 @@ struct AnimPose { AnimPose() {} - AnimPose(const glm::mat4& mat); + explicit AnimPose(const glm::mat4& mat); AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {} static const AnimPose identity; - glm::vec3 operator*(const glm::vec3& rhs) const { - return trans + (rot * (scale * rhs)); - } - - AnimPose operator*(const AnimPose& rhs) const { - return AnimPose(static_cast(*this) * static_cast(rhs)); - } - + glm::vec3 operator*(const glm::vec3& rhs) const; + AnimPose operator*(const AnimPose& rhs) const; + AnimPose inverse() const; operator glm::mat4() const; glm::vec3 scale; @@ -35,12 +30,18 @@ struct AnimPose { glm::vec3 trans; }; +inline QDebug operator<<(QDebug debug, const AnimPose& pose) { + debug << "AnimPose, trans = (" << pose.trans.x << pose.trans.y << pose.trans.z << "), rot = (" << pose.rot.x << pose.rot.y << pose.rot.z << pose.rot.w << "), scale = (" << pose.scale.x << pose.scale.y << pose.scale.z << ")"; + return debug; +} + class AnimSkeleton { public: typedef std::shared_ptr Pointer; AnimSkeleton(const std::vector& joints); int nameToJointIndex(const QString& jointName) const; + const QString& getJointName(int jointIndex) const; int getNumJoints() const; // absolute pose, not relative to parent diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 6e3bb4bc8a..8a9877ca12 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -13,6 +13,7 @@ #include "AnimDebugDraw.h" #include "AbstractViewStateInterface.h" +#include "RenderUtilsLogging.h" struct Vertex { glm::vec3 pos; @@ -218,7 +219,8 @@ void AnimDebugDraw::update() { AnimSkeleton::Pointer& skeleton = iter.second.first; numVerts += skeleton->getNumJoints() * 6; for (int i = 0; i < skeleton->getNumJoints(); i++) { - if (skeleton->getParentIndex(i) >= 0) { + auto parentIndex = skeleton->getParentIndex(i); + if (parentIndex >= 0) { numVerts += 2; } } @@ -229,8 +231,9 @@ void AnimDebugDraw::update() { auto poses = animNode->getPosesInternal(); numVerts += poses.size() * 6; auto skeleton = animNode->getSkeleton(); - for (int i = 0; i < poses.size(); i++) { - if (skeleton->getParentIndex(i) >= 0) { + for (size_t i = 0; i < poses.size(); i++) { + auto parentIndex = skeleton->getParentIndex(i); + if (parentIndex >= 0) { numVerts += 2; } } @@ -251,8 +254,14 @@ void AnimDebugDraw::update() { addWireframeSphereWithAxes(rootPose, pose, radius, v); // line to parent. - if (skeleton->getParentIndex(i) >= 0) { - AnimPose parentPose = skeleton->getAbsoluteBindPose(skeleton->getParentIndex(i)); + auto parentIndex = skeleton->getParentIndex(i); + //qCDebug(renderutils) << skeleton->getJointName(i) << " index = " << i; + //qCDebug(renderutils) << " absPose =" << skeleton->getAbsoluteBindPose(i); + //qCDebug(renderutils) << " relPose =" << skeleton->getRelativeBindPose(i); + if (parentIndex >= 0) { + //qCDebug(renderutils) << " parent =" << parentIndex; + assert(parentIndex < skeleton->getNumJoints()); + AnimPose parentPose = skeleton->getAbsoluteBindPose(parentIndex); addWireframeBoneAxis(rootPose, pose, parentPose, radius, v); } } @@ -266,7 +275,7 @@ void AnimDebugDraw::update() { auto skeleton = animNode->getSkeleton(); std::vector absAnimPose; - absAnimPose.reserve(skeleton->getNumJoints()); + absAnimPose.resize(skeleton->getNumJoints()); for (size_t i = 0; i < poses.size(); i++) { @@ -282,18 +291,20 @@ void AnimDebugDraw::update() { addWireframeSphereWithAxes(rootPose, absAnimPose[i], radius, v); if (parentIndex >= 0) { + assert((size_t)parentIndex < poses.size()); // draw line to parent addWireframeBoneAxis(rootPose, absAnimPose[i], absAnimPose[parentIndex], radius, v); } } } + assert(numVerts == (v - verts)); + data._indexBuffer->resize(sizeof(uint16_t) * numVerts); uint16_t* indices = (uint16_t*)data._indexBuffer->editData(); for (int i = 0; i < numVerts; i++) { indices[i] = i; } - }); scene->enqueuePendingChanges(pendingChanges); } From 559367db4a138ba789493ec3a47f0e0b01c7da3f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 4 Aug 2015 16:03:08 -0700 Subject: [PATCH 13/52] Got to the bottom of the bind pose question. When TRUST_BIND_TRANSFORM is defined in AnimSkeleton, the bind pose is taken from the FBXJoint::bindTransform field, which is correct for all joints bound to actual geometry. Basically it's not trust worthy for bones NOT bound to anything. When TRUST_BIND_TRANSFORM is not defined, the bind pose is taken from the other FBXJoint fields. Unfortunatly these poses are NOT the bind pose, but instead are taken from the FBX files 'resting' pose. i.e. frame 0. --- interface/src/avatar/MyAvatar.cpp | 2 +- libraries/animation/src/AnimClip.cpp | 6 +-- libraries/animation/src/AnimSkeleton.cpp | 45 +++++++++++++++----- libraries/render-utils/src/AnimDebugDraw.cpp | 22 +++++----- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 99b634dec1..88f8a64830 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1221,7 +1221,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { joints.push_back(joint); } auto skeleton = make_shared(joints); - AnimPose xform(glm::vec3(1.0f), glm::quat(), glm::vec3(0.0)); + AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform); _animNode = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index dbb241d484..9f97602ed1 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -138,10 +138,10 @@ void AnimClip::copyFromNetworkAnim() { _anim.resize(frameCount); for (int i = 0; i < frameCount; i++) { - // init all joints in animation to identity + // init all joints in animation to bind pose _anim[i].reserve(skeletonJointCount); for (int j = 0; j < skeletonJointCount; j++) { - _anim[i].push_back(AnimPose::identity); + _anim[i].push_back(_skeleton->getRelativeBindPose(j)); } // init over all joint animations @@ -149,7 +149,7 @@ void AnimClip::copyFromNetworkAnim() { int k = jointMap[j]; if (k >= 0 && k < skeletonJointCount) { // currently FBX animations only have rotation. - _anim[i][k].rot = geom.animationFrames[i].rotations[j]; + _anim[i][k].rot = _skeleton->getRelativeBindPose(j).rot * geom.animationFrames[i].rotations[j]; } } } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index feaa1828ce..27556c6930 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -10,6 +10,8 @@ #include "AnimSkeleton.h" #include "AnimationLogging.h" #include "glmHelpers.h" +#include +#include const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), glm::quat(), @@ -17,7 +19,7 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), AnimPose::AnimPose(const glm::mat4& mat) { scale = extractScale(mat); - rot = glm::normalize(glm::quat_cast(mat)); + rot = extractRotation(mat); trans = extractTranslation(mat); } @@ -41,6 +43,7 @@ AnimPose::operator glm::mat4() const { glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); } +//#define TRUST_BIND_TRANSFORM AnimSkeleton::AnimSkeleton(const std::vector& joints) { _joints = joints; @@ -50,9 +53,31 @@ AnimSkeleton::AnimSkeleton(const std::vector& joints) { _relativeBindPoses.reserve(joints.size()); for (size_t i = 0; i < joints.size(); i++) { + /* + // AJT: dump the skeleton, because wtf. + qCDebug(animation) << getJointName(i); + qCDebug(animation) << " isFree =" << _joints[i].isFree; + qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage; + qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex; + qCDebug(animation) << " boneRadius =" << _joints[i].boneRadius; + qCDebug(animation) << " translation =" << _joints[i].translation; + qCDebug(animation) << " preTransform =" << _joints[i].preTransform; + qCDebug(animation) << " preRotation =" << _joints[i].preRotation; + qCDebug(animation) << " rotation =" << _joints[i].rotation; + qCDebug(animation) << " postRotation =" << _joints[i].postRotation; + qCDebug(animation) << " postTransform =" << _joints[i].postTransform; + qCDebug(animation) << " transform =" << _joints[i].transform; + qCDebug(animation) << " rotationMin =" << _joints[i].rotationMin << ", rotationMax =" << _joints[i].rotationMax; + qCDebug(animation) << " inverseDefaultRotation" << _joints[i].inverseDefaultRotation; + qCDebug(animation) << " inverseBindRotation" << _joints[i].inverseBindRotation; + qCDebug(animation) << " bindTransform" << _joints[i].bindTransform; + qCDebug(animation) << " isSkeletonJoint" << _joints[i].isSkeletonJoint; + */ + +#ifdef TRUST_BIND_TRANSFORM + // trust FBXJoint::bindTransform (which is wrong for joints NOT bound to anything) AnimPose absoluteBindPose(_joints[i].bindTransform); _absoluteBindPoses.push_back(absoluteBindPose); - int parentIndex = getParentIndex(i); if (parentIndex >= 0) { AnimPose inverseParentAbsoluteBindPose = _absoluteBindPoses[parentIndex].inverse(); @@ -60,24 +85,24 @@ AnimSkeleton::AnimSkeleton(const std::vector& joints) { } else { _relativeBindPoses.push_back(absoluteBindPose); } - - // AJT: - // Attempt to use relative bind pose.. but it's not working. - /* - AnimPose relBindPose(glm::vec3(1.0f), _joints[i].rotation, _joints[i].translation); +#else + // trust FBXJoint's local transforms (which is not really the bind pose, but the default pose in the fbx) + glm::mat4 rotTransform = glm::mat4_cast(_joints[i].preRotation * _joints[i].rotation * _joints[i].postRotation); + glm::mat4 relBindMat = glm::translate(_joints[i].translation) * _joints[i].preTransform * rotTransform * _joints[i].postTransform; + AnimPose relBindPose(relBindMat); _relativeBindPoses.push_back(relBindPose); int parentIndex = getParentIndex(i); if (parentIndex >= 0) { - AnimPose parentAbsBindPose = _absoluteBindPoses[parentIndex]; - _absoluteBindPoses.push_back(parentAbsBindPose * relBindPose); + _absoluteBindPoses.push_back(_absoluteBindPoses[parentIndex] * relBindPose); } else { _absoluteBindPoses.push_back(relBindPose); } - */ +#endif } } + int AnimSkeleton::nameToJointIndex(const QString& jointName) const { for (size_t i = 0; i < _joints.size(); i++) { if (_joints[i].name == jointName) { diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 8a9877ca12..7bfe7be98d 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -167,27 +167,29 @@ static const uint32_t cyan = toRGBA(0, 128, 128, 255); static void addWireframeSphereWithAxes(const AnimPose& rootPose, const AnimPose& pose, float radius, Vertex*& v) { + glm::vec3 base = rootPose * pose.trans; + // x-axis - v->pos = rootPose * pose.trans; + v->pos = base; v->rgba = red; v++; - v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(radius * 2.0f, 0.0f, 0.0f)); + v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(radius, 0.0f, 0.0f)); v->rgba = red; v++; // y-axis - v->pos = rootPose * pose.trans; + v->pos = base; v->rgba = green; v++; - v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(0.0f, radius * 2.0f, 0.0f)); + v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(0.0f, radius, 0.0f)); v->rgba = green; v++; // z-axis - v->pos = rootPose * pose.trans; + v->pos = base; v->rgba = blue; v++; - v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(0.0f, 0.0f, radius * 2.0f)); + v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(0.0f, 0.0f, radius)); v->rgba = blue; v++; } @@ -250,16 +252,12 @@ void AnimDebugDraw::update() { AnimPose pose = skeleton->getAbsoluteBindPose(i); // draw axes - const float radius = 0.01f; + const float radius = 0.1f; addWireframeSphereWithAxes(rootPose, pose, radius, v); // line to parent. auto parentIndex = skeleton->getParentIndex(i); - //qCDebug(renderutils) << skeleton->getJointName(i) << " index = " << i; - //qCDebug(renderutils) << " absPose =" << skeleton->getAbsoluteBindPose(i); - //qCDebug(renderutils) << " relPose =" << skeleton->getRelativeBindPose(i); if (parentIndex >= 0) { - //qCDebug(renderutils) << " parent =" << parentIndex; assert(parentIndex < skeleton->getNumJoints()); AnimPose parentPose = skeleton->getAbsoluteBindPose(parentIndex); addWireframeBoneAxis(rootPose, pose, parentPose, radius, v); @@ -287,7 +285,7 @@ void AnimDebugDraw::update() { } // draw axes - const float radius = 0.01f; + const float radius = 0.1f; addWireframeSphereWithAxes(rootPose, absAnimPose[i], radius, v); if (parentIndex >= 0) { From da3d35cdfcebd2567fe7d50fc589b7302aa6cc77 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 4 Aug 2015 16:06:40 -0700 Subject: [PATCH 14/52] Added scaleOffset to FBXJoint::postTransform as it was missing. Also added some documentation/comments to the FBXJoint struct. --- libraries/fbx/src/FBXReader.cpp | 14 ++++++++------ libraries/fbx/src/FBXReader.h | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 3b7d18f1d8..bd3f050a55 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1706,7 +1706,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, glm::vec3 rotationOffset; glm::vec3 preRotation, rotation, postRotation; glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f); - glm::vec3 scalePivot, rotationPivot; + glm::vec3 scalePivot, rotationPivot, scaleOffset; bool rotationMinX = false, rotationMinY = false, rotationMinZ = false; bool rotationMaxX = false, rotationMaxY = false, rotationMaxZ = false; glm::vec3 rotationMin, rotationMax; @@ -1755,12 +1755,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } else if (property.properties.at(0) == "Lcl Scaling") { scale = getVec3(property.properties, index); + } else if (property.properties.at(0) == "ScalingOffset") { + scaleOffset = getVec3(property.properties, index); + + // NOTE: these rotation limits are stored in degrees (NOT radians) } else if (property.properties.at(0) == "RotationMin") { rotationMin = getVec3(property.properties, index); - } - // NOTE: these rotation limits are stored in degrees (NOT radians) - else if (property.properties.at(0) == "RotationMax") { + } else if (property.properties.at(0) == "RotationMax") { rotationMax = getVec3(property.properties, index); } else if (property.properties.at(0) == "RotationMinX") { @@ -1827,8 +1829,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, model.preRotation = glm::quat(glm::radians(preRotation)); model.rotation = glm::quat(glm::radians(rotation)); model.postRotation = glm::quat(glm::radians(postRotation)); - model.postTransform = glm::translate(-rotationPivot) * glm::translate(scalePivot) * - glm::scale(scale) * glm::translate(-scalePivot); + model.postTransform = glm::translate(-rotationPivot) * glm::translate(scaleOffset) * + glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot); // NOTE: angles from the FBX file are in degrees // so we convert them to radians for the FBXModel class model.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f, diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index b8a22b0b80..aaefdae252 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -64,12 +64,18 @@ public: int parentIndex; float distanceToParent; float boneRadius; - glm::vec3 translation; - glm::mat4 preTransform; - glm::quat preRotation; - glm::quat rotation; - glm::quat postRotation; - glm::mat4 postTransform; + + // http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/SDKRef/a00209.html + + glm::vec3 translation; // T + glm::mat4 preTransform; // Roff * Rp + glm::quat preRotation; // Rpre + glm::quat rotation; // R + glm::quat postRotation; // Rpost + glm::mat4 postTransform; // Rp-1 * Soff * Sp * S * Sp-1 + + // World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1) + glm::mat4 transform; glm::vec3 rotationMin; // radians glm::vec3 rotationMax; // radians From 5d83976e2abd6c46a094483b6b6a810206b0ee12 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 4 Aug 2015 18:13:13 -0700 Subject: [PATCH 15/52] Added AnimBlendLinear + tests. MyAvatar now does a sine wave blend between a walk and a walk animation. --- interface/src/avatar/MyAvatar.cpp | 46 ++++++++++------ interface/src/avatar/MyAvatar.h | 1 + libraries/animation/src/AnimBlendLinear.cpp | 59 +++++++++++++++++++++ libraries/animation/src/AnimBlendLinear.h | 39 ++++++++++++++ libraries/animation/src/AnimClip.cpp | 10 ++-- libraries/animation/src/AnimNode.h | 14 ++++- libraries/animation/src/AnimNodeLoader.cpp | 26 +++++++-- libraries/animation/src/AnimNodeLoader.h | 5 ++ libraries/animation/src/AnimSkeleton.h | 4 ++ libraries/render-utils/src/AnimDebugDraw.h | 4 ++ tests/animation/src/AnimClipTests.cpp | 31 +++++++---- tests/animation/src/data/test.json | 16 +++--- 12 files changed, 211 insertions(+), 44 deletions(-) create mode 100644 libraries/animation/src/AnimBlendLinear.cpp create mode 100644 libraries/animation/src/AnimBlendLinear.h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 88f8a64830..ecfa848630 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -52,6 +52,7 @@ #include "AnimDebugDraw.h" #include "AnimSkeleton.h" #include "AnimClip.h" +#include "AnimBlendLinear.h" using namespace std; @@ -150,7 +151,11 @@ void MyAvatar::reset() { void MyAvatar::update(float deltaTime) { if (_animNode) { - _animNode->evaluate(deltaTime); + static float t = 0.0f; + auto blend = std::static_pointer_cast(_animNode); + blend->setAlpha(0.5f * sin(t) + 0.5f); + t += deltaTime; + _animNode->evaluate(deltaTime); } if (_referential) { @@ -1203,6 +1208,30 @@ void MyAvatar::initHeadBones() { } } +void MyAvatar::setupNewAnimationSystem() { + + // create a skeleton and hand it over to the debug draw instance + auto geom = _skeletonModel.getGeometry()->getFBXGeometry(); + std::vector joints; + for (auto& joint : geom.joints) { + joints.push_back(joint); + } + auto skeleton = make_shared(joints); + AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); + AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform); + + // create a blend node + auto blend = make_shared("blend", 0.5f); + auto idle = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); + auto walk = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx", 0.0f, 29.0f, 1.0f, true); + blend->addChild(idle); + blend->addChild(walk); + _animNode = blend; + _animNode->setSkeleton(skeleton); + xform.trans.z += 1.0f; + AnimDebugDraw::getInstance().addAnimNode("blend", _animNode, xform); +} + void MyAvatar::preRender(RenderArgs* renderArgs) { render::ScenePointer scene = Application::getInstance()->getMain3DScene(); @@ -1214,20 +1243,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { // AJT: SETUP DEBUG RENDERING OF NEW ANIMATION SYSTEM - // create a skeleton and hand it over to the debug draw instance - auto geom = _skeletonModel.getGeometry()->getFBXGeometry(); - std::vector joints; - for (auto& joint : geom.joints) { - joints.push_back(joint); - } - auto skeleton = make_shared(joints); - AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); - AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform); - - _animNode = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); - _animNode->setSkeleton(skeleton); - xform.trans.z += 1.0f; - AnimDebugDraw::getInstance().addAnimNode("clip", _animNode, xform); + setupNewAnimationSystem(); } if (shouldDrawHead != _prevShouldDrawHead) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index dc7c9e164f..7da36a6884 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -189,6 +189,7 @@ signals: private: + void setupNewAnimationSystem(); QByteArray toByteArray(); void simulate(float deltaTime); void updateFromTrackers(float deltaTime); diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp new file mode 100644 index 0000000000..3471e4e8d1 --- /dev/null +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -0,0 +1,59 @@ +// +// AnimBlendLinear.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 "AnimBlendLinear.h" +#include "GLMHelpers.h" +#include "AnimationLogging.h" + +AnimBlendLinear::AnimBlendLinear(const std::string& id, float alpha) : + AnimNode(AnimNode::BlendLinearType, id), + _alpha(alpha) { + +} + +AnimBlendLinear::~AnimBlendLinear() { + +} + +const std::vector& AnimBlendLinear::evaluate(float dt) { + + if (_children.size() == 0) { + for (auto&& pose : _poses) { + pose = AnimPose::identity; + } + } else if (_children.size() == 1) { + _poses = _children[0]->evaluate(dt); + } else { + float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); + size_t prevPoseIndex = glm::floor(clampedAlpha); + size_t nextPoseIndex = glm::ceil(clampedAlpha); + float alpha = glm::fract(clampedAlpha); + if (prevPoseIndex != nextPoseIndex) { + auto prevPoses = _children[prevPoseIndex]->evaluate(dt); + auto nextPoses = _children[nextPoseIndex]->evaluate(dt); + + if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { + _poses.resize(prevPoses.size()); + for (size_t i = 0; i < _poses.size(); i++) { + const AnimPose& prevPose = prevPoses[i]; + const AnimPose& nextPose = nextPoses[i]; + _poses[i].scale = lerp(prevPose.scale, nextPose.scale, alpha); + _poses[i].rot = glm::normalize(glm::lerp(prevPose.rot, nextPose.rot, alpha)); + _poses[i].trans = lerp(prevPose.trans, nextPose.trans, alpha); + } + } + } + } + return _poses; +} + +// for AnimDebugDraw rendering +const std::vector& AnimBlendLinear::getPosesInternal() const { + return _poses; +} diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h new file mode 100644 index 0000000000..db217a25ee --- /dev/null +++ b/libraries/animation/src/AnimBlendLinear.h @@ -0,0 +1,39 @@ +// +// AnimBlendLinear.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 +// + +#include "AnimNode.h" + +#ifndef hifi_AnimBlendLinear_h +#define hifi_AnimBlendLinear_h + +class AnimBlendLinear : public AnimNode { +public: + + AnimBlendLinear(const std::string& id, float alpha); + virtual ~AnimBlendLinear() override; + + virtual const std::vector& evaluate(float dt) override; + + void setAlpha(float alpha) { _alpha = alpha; } + float getAlpha() const { return _alpha; } + +protected: + // for AnimDebugDraw rendering + virtual const std::vector& getPosesInternal() const override; + + std::vector _poses; + + float _alpha; + + // no copies + AnimBlendLinear(const AnimBlendLinear&) = delete; + AnimBlendLinear& operator=(const AnimBlendLinear&) = delete; +}; + +#endif // hifi_AnimBlendLinear_h diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 9f97602ed1..8c223013bf 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -103,11 +103,11 @@ const std::vector& AnimClip::evaluate(float dt) { float alpha = glm::fract(_frame); for (size_t i = 0; i < _poses.size(); i++) { - const AnimPose& prevBone = prevFrame[i]; - const AnimPose& nextBone = nextFrame[i]; - _poses[i].scale = lerp(prevBone.scale, nextBone.scale, alpha); - _poses[i].rot = glm::normalize(glm::lerp(prevBone.rot, nextBone.rot, alpha)); - _poses[i].trans = lerp(prevBone.trans, nextBone.trans, alpha); + const AnimPose& prevPose = prevFrame[i]; + const AnimPose& nextPose = nextFrame[i]; + _poses[i].scale = lerp(prevPose.scale, nextPose.scale, alpha); + _poses[i].rot = glm::normalize(glm::lerp(prevPose.rot, nextPose.rot, alpha)); + _poses[i].trans = lerp(prevPose.trans, nextPose.trans, alpha); } } diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 97249e2649..6ee7ff89c8 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -27,6 +27,7 @@ public: enum Type { ClipType = 0, + BlendLinearType, NumTypes }; typedef std::shared_ptr Pointer; @@ -49,7 +50,13 @@ public: } int getChildCount() const { return (int)_children.size(); } - void setSkeleton(AnimSkeleton::Pointer skeleton) { _skeleton = skeleton; } + void setSkeleton(AnimSkeleton::Pointer skeleton) { + setSkeletonInternal(skeleton); + for (auto&& child : _children) { + child->setSkeletonInternal(skeleton); + } + } + AnimSkeleton::Pointer getSkeleton() const { return _skeleton; } virtual ~AnimNode() {} @@ -57,6 +64,11 @@ public: virtual const std::vector& evaluate(float dt) = 0; protected: + + virtual void setSkeletonInternal(AnimSkeleton::Pointer skeleton) { + _skeleton = skeleton; + } + // for AnimDebugDraw rendering virtual const std::vector& getPosesInternal() const = 0; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 2d3a5a010c..811b2f76db 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -14,6 +14,7 @@ #include "AnimNode.h" #include "AnimClip.h" +#include "AnimBlendLinear.h" #include "AnimationLogging.h" #include "AnimNodeLoader.h" @@ -25,15 +26,18 @@ struct TypeInfo { // This will result in a compile error if someone adds a new // item to the AnimNode::Type enum. This is by design. static TypeInfo typeInfoArray[AnimNode::NumTypes] = { - { AnimNode::ClipType, "clip" } + { AnimNode::ClipType, "clip" }, + { AnimNode::BlendLinearType, "blendLinear" } }; typedef AnimNode::Pointer (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); +static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { - loadClipNode + loadClipNode, + loadBlendLinearNode }; #define READ_STRING(NAME, JSON_OBJ, ID, URL) \ @@ -132,6 +136,17 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& return std::make_shared(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag); } +static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl) { + + READ_FLOAT(alpha, jsonObj, id, jsonUrl); + + return std::make_shared(id.toStdString(), alpha); +} + +AnimNodeLoader::AnimNodeLoader() { + +} + AnimNode::Pointer AnimNodeLoader::load(const std::string& filename) const { // load entire file into a string. QString jsonUrl = QString::fromStdString(filename); @@ -145,7 +160,12 @@ AnimNode::Pointer AnimNodeLoader::load(const std::string& filename) const { file.close(); // convert string into a json doc - auto doc = QJsonDocument::fromJson(contents.toUtf8()); + QJsonParseError error; + auto doc = QJsonDocument::fromJson(contents.toUtf8(), &error); + if (error.error != QJsonParseError::NoError) { + qCCritical(animation) << "AnimNodeLoader, failed to parse json, error =" << error.errorString() << ", url =" << jsonUrl; + return nullptr; + } QJsonObject obj = doc.object(); // version diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index 941ea32f02..26fb4fc9d5 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -16,8 +16,13 @@ class AnimNode; class AnimNodeLoader { public: + AnimNodeLoader(); // TODO: load from url std::shared_ptr load(const std::string& filename) const; + + // no copies + AnimNodeLoader(const AnimNodeLoader&) = delete; + AnimNodeLoader& operator=(const AnimNodeLoader&) = delete; }; #endif // hifi_AnimNodeLoader diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index b51daadae7..ee3e55c115 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -56,6 +56,10 @@ protected: std::vector _joints; std::vector _absoluteBindPoses; std::vector _relativeBindPoses; + + // no copies + AnimSkeleton(const AnimSkeleton&) = delete; + AnimSkeleton& operator=(const AnimSkeleton&) = delete; }; #endif diff --git a/libraries/render-utils/src/AnimDebugDraw.h b/libraries/render-utils/src/AnimDebugDraw.h index 6957696184..34ee9c50c0 100644 --- a/libraries/render-utils/src/AnimDebugDraw.h +++ b/libraries/render-utils/src/AnimDebugDraw.h @@ -45,6 +45,10 @@ protected: std::unordered_map _skeletons; std::unordered_map _animNodes; + + // no copies + AnimDebugDraw(const AnimDebugDraw&) = delete; + AnimDebugDraw& operator=(const AnimDebugDraw&) = delete; }; #endif // hifi_AnimDebugDraw diff --git a/tests/animation/src/AnimClipTests.cpp b/tests/animation/src/AnimClipTests.cpp index 58c7311b2d..5c91d4a617 100644 --- a/tests/animation/src/AnimClipTests.cpp +++ b/tests/animation/src/AnimClipTests.cpp @@ -10,6 +10,7 @@ #include "AnimClipTests.h" #include "AnimNodeLoader.h" #include "AnimClip.h" +#include "AnimBlendLinear.h" #include "AnimationLogging.h" #include <../QTestExtensions.h> @@ -103,24 +104,34 @@ void AnimClipTests::testLoader() { #endif QVERIFY((bool)node); - QVERIFY(node->getID() == "idle"); - QVERIFY(node->getType() == AnimNode::ClipType); + QVERIFY(node->getID() == "blend"); + QVERIFY(node->getType() == AnimNode::BlendLinearType); - auto clip = std::static_pointer_cast(node); + auto blend = std::static_pointer_cast(node); + QVERIFY(blend->getAlpha() == 0.5f); - QVERIFY(clip->getURL() == "idle.fbx"); - QVERIFY(clip->getStartFrame() == 0.0f); - QVERIFY(clip->getEndFrame() == 30.0f); - QVERIFY(clip->getTimeScale() == 1.0f); - QVERIFY(clip->getLoopFlag() == true); + QVERIFY(node->getChildCount() == 3); - QVERIFY(clip->getChildCount() == 3); + std::shared_ptr nodes[3] = { node->getChild(0), node->getChild(1), node->getChild(2) }; - std::shared_ptr nodes[3] = { clip->getChild(0), clip->getChild(1), clip->getChild(2) }; QVERIFY(nodes[0]->getID() == "test01"); QVERIFY(nodes[0]->getChildCount() == 0); QVERIFY(nodes[1]->getID() == "test02"); QVERIFY(nodes[1]->getChildCount() == 0); QVERIFY(nodes[2]->getID() == "test03"); QVERIFY(nodes[2]->getChildCount() == 0); + + auto test01 = std::static_pointer_cast(nodes[0]); + QVERIFY(test01->getURL() == "test01.fbx"); + QVERIFY(test01->getStartFrame() == 1.0f); + QVERIFY(test01->getEndFrame() == 20.0f); + QVERIFY(test01->getTimeScale() == 1.0f); + QVERIFY(test01->getLoopFlag() == false); + + auto test02 = std::static_pointer_cast(nodes[1]); + QVERIFY(test02->getURL() == "test02.fbx"); + QVERIFY(test02->getStartFrame() == 2.0f); + QVERIFY(test02->getEndFrame() == 21.0f); + QVERIFY(test02->getTimeScale() == 0.9f); + QVERIFY(test02->getLoopFlag() == true); } diff --git a/tests/animation/src/data/test.json b/tests/animation/src/data/test.json index 0bcf91201a..765617fa2f 100644 --- a/tests/animation/src/data/test.json +++ b/tests/animation/src/data/test.json @@ -1,14 +1,10 @@ { "version": "1.0", "root": { - "id": "idle", - "type": "clip", + "id": "blend", + "type": "blendLinear", "data": { - "url": "idle.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.5 }, "children": [ { @@ -29,9 +25,9 @@ "data": { "url": "test02.fbx", "startFrame": 2.0, - "endFrame": 20.0, - "timeScale": 1.0, - "loopFlag": false + "endFrame": 21.0, + "timeScale": 0.9, + "loopFlag": true }, "children": [] }, From 2154f762029c33bd6efb79bac9a8dfcc2a28f064 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 5 Aug 2015 12:03:20 -0700 Subject: [PATCH 16/52] Added some comments to AnimNode, AnimClip & AnimBlendLinear. --- interface/src/avatar/MyAvatar.cpp | 2 +- libraries/animation/src/AnimBlendLinear.h | 10 ++++++++++ libraries/animation/src/AnimClip.h | 6 ++++++ libraries/animation/src/AnimNode.h | 13 +++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ecfa848630..b50e550deb 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1223,7 +1223,7 @@ void MyAvatar::setupNewAnimationSystem() { // create a blend node auto blend = make_shared("blend", 0.5f); auto idle = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); - auto walk = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx", 0.0f, 29.0f, 1.0f, true); + auto walk = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx", 0.0f, 28.0f, 1.0f, true); blend->addChild(idle); blend->addChild(walk); _animNode = blend; diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index db217a25ee..c1c1c928e5 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -12,6 +12,16 @@ #ifndef hifi_AnimBlendLinear_h #define hifi_AnimBlendLinear_h +// Linear blend between two AnimNodes. +// the amount of blending is determined by the alpha parameter. +// If the number of children is 2, then the alpha parameters should be between +// 0 and 1. The first animation will have a (1 - alpha) factor, and the second +// will have factor of alpha. +// This node supports more then 2 children. In this case the alpha should be +// between 0 and n - 1. This alpha can be used to linearly interpolate between +// the closest two children poses. This can be used to sweep through a series +// of animation poses. + class AnimBlendLinear : public AnimNode { public: diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 59b286b95f..1868ac0e03 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -14,6 +14,12 @@ #include "AnimationCache.h" #include "AnimNode.h" +// Playback a single animation timeline. +// url determines the location of the fbx file to use within this clip. +// startFrame and endFrame are in frames 1/30th of a second. +// timescale can be used to speed-up or slow-down the animation. +// loop flag, when true, will loop the animation as it reaches the end frame. + class AnimClip : public AnimNode { public: friend class AnimClipTests; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 6ee7ff89c8..debc423e20 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -21,6 +21,15 @@ class QJsonObject; +// Base class for all elements in the animation blend tree. +// It provides the following categories of functions: +// +// * id getter, id is a string name useful for debugging and searching. +// * type getter, helpful for determining the derived type of this node. +// * hierarchy accessors, for adding, removing and iterating over child AnimNodes +// * skeleton accessors, the skeleton is from the model whose bones we are going to manipulate +// * evaluate method, perform actual joint manipulations here and return result by reference. + class AnimNode { public: friend class AnimDebugDraw; @@ -37,6 +46,7 @@ public: const std::string& getID() const { return _id; } Type getType() const { return _type; } + // hierarchy accessors void addChild(Pointer child) { _children.push_back(child); } void removeChild(Pointer child) { auto iter = std::find(_children.begin(), _children.end(), child); @@ -62,6 +72,9 @@ public: virtual ~AnimNode() {} virtual const std::vector& evaluate(float dt) = 0; + virtual const std::vector& overlay(float dt, const std::vector& underPoses) { + return evaluate(dt); + } protected: From 2d0315978ed6b4bbcb78d53ca828ec0187146ce4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 5 Aug 2015 12:32:15 -0700 Subject: [PATCH 17/52] Fix for compilation on linux. --- libraries/animation/src/AnimSkeleton.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 27556c6930..7c813a053e 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -9,7 +9,7 @@ #include "AnimSkeleton.h" #include "AnimationLogging.h" -#include "glmHelpers.h" +#include "GLMHelpers.h" #include #include From 1f8c8adbd652e58087ac7d9e8e138ca6ff266ece Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 5 Aug 2015 17:05:53 -0700 Subject: [PATCH 18/52] Added AnimOverlay node, moved blend sub-routine into AnimUtil. --- libraries/animation/src/AnimBlendLinear.cpp | 10 ++-- libraries/animation/src/AnimClip.cpp | 9 +--- libraries/animation/src/AnimNode.h | 1 + libraries/animation/src/AnimNodeLoader.cpp | 43 ++++++++++++++- libraries/animation/src/AnimOverlay.cpp | 36 +++++++++++++ libraries/animation/src/AnimOverlay.h | 58 +++++++++++++++++++++ libraries/animation/src/AnimUtil.cpp | 21 ++++++++ libraries/animation/src/AnimUtil.h | 23 ++++++++ 8 files changed, 185 insertions(+), 16 deletions(-) create mode 100644 libraries/animation/src/AnimOverlay.cpp create mode 100644 libraries/animation/src/AnimOverlay.h create mode 100644 libraries/animation/src/AnimUtil.cpp create mode 100644 libraries/animation/src/AnimUtil.h diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index 3471e4e8d1..76d92cd264 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -10,6 +10,7 @@ #include "AnimBlendLinear.h" #include "GLMHelpers.h" #include "AnimationLogging.h" +#include "AnimUtil.h" AnimBlendLinear::AnimBlendLinear(const std::string& id, float alpha) : AnimNode(AnimNode::BlendLinearType, id), @@ -40,13 +41,8 @@ const std::vector& AnimBlendLinear::evaluate(float dt) { if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { _poses.resize(prevPoses.size()); - for (size_t i = 0; i < _poses.size(); i++) { - const AnimPose& prevPose = prevPoses[i]; - const AnimPose& nextPose = nextPoses[i]; - _poses[i].scale = lerp(prevPose.scale, nextPose.scale, alpha); - _poses[i].rot = glm::normalize(glm::lerp(prevPose.rot, nextPose.rot, alpha)); - _poses[i].trans = lerp(prevPose.trans, nextPose.trans, alpha); - } + + blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); } } } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 8c223013bf..9863d71882 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -10,6 +10,7 @@ #include "GLMHelpers.h" #include "AnimClip.h" #include "AnimationLogging.h" +#include "AnimUtil.h" AnimClip::AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : AnimNode(AnimNode::ClipType, id), @@ -102,13 +103,7 @@ const std::vector& AnimClip::evaluate(float dt) { const std::vector& nextFrame = _anim[nextIndex]; float alpha = glm::fract(_frame); - for (size_t i = 0; i < _poses.size(); i++) { - const AnimPose& prevPose = prevFrame[i]; - const AnimPose& nextPose = nextFrame[i]; - _poses[i].scale = lerp(prevPose.scale, nextPose.scale, alpha); - _poses[i].rot = glm::normalize(glm::lerp(prevPose.rot, nextPose.rot, alpha)); - _poses[i].trans = lerp(prevPose.trans, nextPose.trans, alpha); - } + blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]); } return _poses; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index debc423e20..d1adc1fc6d 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -37,6 +37,7 @@ public: enum Type { ClipType = 0, BlendLinearType, + OverlayType, NumTypes }; typedef std::shared_ptr Pointer; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 811b2f76db..9a4634a462 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -16,6 +16,7 @@ #include "AnimClip.h" #include "AnimBlendLinear.h" #include "AnimationLogging.h" +#include "AnimOverlay.h" #include "AnimNodeLoader.h" struct TypeInfo { @@ -27,17 +28,20 @@ struct TypeInfo { // item to the AnimNode::Type enum. This is by design. static TypeInfo typeInfoArray[AnimNode::NumTypes] = { { AnimNode::ClipType, "clip" }, - { AnimNode::BlendLinearType, "blendLinear" } + { AnimNode::BlendLinearType, "blendLinear" }, + { AnimNode::OverlayType, "overlay" } }; typedef AnimNode::Pointer (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); +static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { loadClipNode, - loadBlendLinearNode + loadBlendLinearNode, + loadOverlayNode }; #define READ_STRING(NAME, JSON_OBJ, ID, URL) \ @@ -143,6 +147,41 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q return std::make_shared(id.toStdString(), alpha); } +static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { + "fullBody", + "upperBody", + "lowerBody", + "rightArm", + "leftArm", + "aboveTheHead", + "belowTheHead", + "headOnly", + "spineOnly", + "empty" +}; + +static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { + for (int i = 0; i < (int)AnimOverlay::NumBoneSets; i++) { + if (str == boneSetStrings[i]) { + return (AnimOverlay::BoneSet)i; + } + } + return AnimOverlay::NumBoneSets; +} + +static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl) { + + READ_STRING(boneSet, jsonObj, id, jsonUrl); + + auto boneSetEnum = stringToBoneSetEnum(boneSet); + if (boneSetEnum == AnimOverlay::NumBoneSets) { + qCCritical(animation) << "AnimNodeLoader, unknown bone set =" << boneSet << ", defaulting to \"fullBody\", url =" << jsonUrl; + boneSetEnum = AnimOverlay::FullBody; + } + + return std::make_shared(id.toStdString(), boneSetEnum); +} + AnimNodeLoader::AnimNodeLoader() { } diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp new file mode 100644 index 0000000000..6f39ab8876 --- /dev/null +++ b/libraries/animation/src/AnimOverlay.cpp @@ -0,0 +1,36 @@ +// +// AnimOverlay.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 "AnimOverlay.h" +#include "AnimUtil.h" + +AnimOverlay::AnimOverlay(const std::string& id, BoneSet boneSet) : + AnimNode(AnimNode::OverlayType, id), + _boneSet(boneSet) { +} + +AnimOverlay::~AnimOverlay() { +} + +const std::vector& AnimOverlay::evaluate(float dt) { + if (_children.size() >= 2) { + auto underPoses = _children[1]->evaluate(dt); + auto overPoses = _children[0]->overlay(dt, underPoses); + + if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) { + _poses.resize(underPoses.size()); + + for (size_t i = 0; i < _poses.size(); i++) { + float alpha = 1.0f; // TODO: PULL from boneSet + blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]); + } + } + } + return _poses; +} diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h new file mode 100644 index 0000000000..7f38f53297 --- /dev/null +++ b/libraries/animation/src/AnimOverlay.h @@ -0,0 +1,58 @@ +// +// AnimOverlay.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 +// + +#include "AnimNode.h" + +#ifndef hifi_AnimOverlay_h +#define hifi_AnimOverlay_h + +#include "AnimNode.h" + +// Overlay the AnimPoses from one AnimNode on top of another AnimNode. +// child[0] is overlayed on top of child[1]. The boneset is used +// to control blending on a per-bone bases. + +class AnimOverlay : public AnimNode { +public: + + enum BoneSet { + FullBody = 0, + UpperBodyBoneSet, + LowerBodyBoneSet, + RightArmBoneSet, + LeftArmBoneSet, + AboveTheHeadBoneSet, + BelowTheHeadBoneSet, + HeadOnlyBoneSet, + SpineOnlyBoneSet, + EmptyBoneSet, + NumBoneSets, + }; + + AnimOverlay(const std::string& id, BoneSet boneSet); + virtual ~AnimOverlay() override; + + void setBoneSet(BoneSet boneSet) { _boneSet = boneSet; } + BoneSet getBoneSet() const { return _boneSet; } + + virtual const std::vector& evaluate(float dt) override; + +protected: + // for AnimDebugDraw rendering + virtual const std::vector& getPosesInternal() const override; + + std::vector _poses; + BoneSet _boneSet; + + // no copies + AnimOverlay(const AnimOverlay&) = delete; + AnimOverlay& operator=(const AnimOverlay&) = delete; +}; + +#endif // hifi_AnimOverlay_h diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp new file mode 100644 index 0000000000..4423b5c933 --- /dev/null +++ b/libraries/animation/src/AnimUtil.cpp @@ -0,0 +1,21 @@ +// +// AnimUtil.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 "AnimUtil.h" +#include "GLMHelpers.h" + +void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) { + for (size_t i = 0; i < numPoses; i++) { + const AnimPose& aPose = a[i]; + const AnimPose& bPose = b[i]; + result[i].scale = lerp(aPose.scale, bPose.scale, alpha); + result[i].rot = glm::normalize(glm::lerp(aPose.rot, bPose.rot, alpha)); + result[i].trans = lerp(aPose.trans, bPose.trans, alpha); + } +} diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h new file mode 100644 index 0000000000..556eba8989 --- /dev/null +++ b/libraries/animation/src/AnimUtil.h @@ -0,0 +1,23 @@ +// +// AnimUtil.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_AnimUtil_h +#define hifi_AnimUtil_h + +#include "AnimNode.h" + +// TODO: use restrict keyword +// TODO: excellent candidate for simd vectorization. + +// this is where the magic happens +void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result); + +#endif + + From c3fc37df18d3e36100dc087032911ef715049a1a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 5 Aug 2015 18:07:56 -0700 Subject: [PATCH 19/52] Added true boneSets to AnimOverlay. --- interface/src/avatar/MyAvatar.cpp | 15 ++- libraries/animation/src/AnimNode.h | 8 +- libraries/animation/src/AnimNodeLoader.cpp | 2 +- libraries/animation/src/AnimOverlay.cpp | 141 ++++++++++++++++++++- libraries/animation/src/AnimOverlay.h | 17 ++- libraries/animation/src/AnimSkeleton.h | 1 + 6 files changed, 168 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b50e550deb..a7416e05f0 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -53,6 +53,7 @@ #include "AnimSkeleton.h" #include "AnimClip.h" #include "AnimBlendLinear.h" +#include "AnimOverlay.h" using namespace std; @@ -152,10 +153,12 @@ void MyAvatar::update(float deltaTime) { if (_animNode) { static float t = 0.0f; + /* auto blend = std::static_pointer_cast(_animNode); blend->setAlpha(0.5f * sin(t) + 0.5f); + */ t += deltaTime; - _animNode->evaluate(deltaTime); + _animNode->evaluate(deltaTime); } if (_referential) { @@ -1220,13 +1223,13 @@ void MyAvatar::setupNewAnimationSystem() { AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform); - // create a blend node - auto blend = make_shared("blend", 0.5f); + // create a overlay node + auto overlay = make_shared("overlay", AnimOverlay::UpperBodyBoneSet); auto idle = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); auto walk = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx", 0.0f, 28.0f, 1.0f, true); - blend->addChild(idle); - blend->addChild(walk); - _animNode = blend; + overlay->addChild(idle); + overlay->addChild(walk); + _animNode = overlay; _animNode->setSkeleton(skeleton); xform.trans.z += 1.0f; AnimDebugDraw::getInstance().addAnimNode("blend", _animNode, xform); diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index d1adc1fc6d..cae3d1805f 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -61,14 +61,14 @@ public: } int getChildCount() const { return (int)_children.size(); } - void setSkeleton(AnimSkeleton::Pointer skeleton) { + void setSkeleton(const AnimSkeleton::Pointer skeleton) { setSkeletonInternal(skeleton); for (auto&& child : _children) { child->setSkeletonInternal(skeleton); } } - AnimSkeleton::Pointer getSkeleton() const { return _skeleton; } + AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; } virtual ~AnimNode() {} @@ -79,7 +79,7 @@ public: protected: - virtual void setSkeletonInternal(AnimSkeleton::Pointer skeleton) { + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; } @@ -89,7 +89,7 @@ protected: Type _type; std::string _id; std::vector _children; - AnimSkeleton::Pointer _skeleton; + AnimSkeleton::ConstPointer _skeleton; // no copies AnimNode(const AnimNode&) = delete; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 9a4634a462..cd635af8df 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -176,7 +176,7 @@ static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QStri auto boneSetEnum = stringToBoneSetEnum(boneSet); if (boneSetEnum == AnimOverlay::NumBoneSets) { qCCritical(animation) << "AnimNodeLoader, unknown bone set =" << boneSet << ", defaulting to \"fullBody\", url =" << jsonUrl; - boneSetEnum = AnimOverlay::FullBody; + boneSetEnum = AnimOverlay::FullBodyBoneSet; } return std::make_shared(id.toStdString(), boneSetEnum); diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index 6f39ab8876..c02c83d69f 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -9,13 +9,32 @@ #include "AnimOverlay.h" #include "AnimUtil.h" +#include AnimOverlay::AnimOverlay(const std::string& id, BoneSet boneSet) : - AnimNode(AnimNode::OverlayType, id), - _boneSet(boneSet) { + AnimNode(AnimNode::OverlayType, id), _boneSet(boneSet) { } AnimOverlay::~AnimOverlay() { + +} + +void AnimOverlay::setBoneSet(BoneSet boneSet) { + _boneSet = boneSet; + assert(_skeleton); + switch (boneSet) { + case FullBodyBoneSet: buildFullBodyBoneSet(); break; + case UpperBodyBoneSet: buildUpperBodyBoneSet(); break; + case LowerBodyBoneSet: buildLowerBodyBoneSet(); break; + case RightArmBoneSet: buildRightArmBoneSet(); break; + case LeftArmBoneSet: buildLeftArmBoneSet(); break; + case AboveTheHeadBoneSet: buildAboveTheHeadBoneSet(); break; + case BelowTheHeadBoneSet: buildBelowTheHeadBoneSet(); break; + case HeadOnlyBoneSet: buildHeadOnlyBoneSet(); break; + case SpineOnlyBoneSet: buildSpineOnlyBoneSet(); break; + default: + case EmptyBoneSet: buildEmptyBoneSet(); break; + } } const std::vector& AnimOverlay::evaluate(float dt) { @@ -25,12 +44,128 @@ const std::vector& AnimOverlay::evaluate(float dt) { if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) { _poses.resize(underPoses.size()); + assert(_boneSetVec.size() == _poses.size()); for (size_t i = 0; i < _poses.size(); i++) { - float alpha = 1.0f; // TODO: PULL from boneSet + float alpha = _boneSetVec[i]; // TODO: PULL from boneSet blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]); } } } return _poses; } + +template +void for_each_child_joint(AnimSkeleton::ConstPointer skeleton, int startJoint, Func f) { + std::queue q; + q.push(startJoint); + while(q.size() > 0) { + int jointIndex = q.front(); + for (int i = 0; i < skeleton->getNumJoints(); i++) { + if (jointIndex == skeleton->getParentIndex(i)) { + f(i); + q.push(i); + } + } + q.pop(); + } +} + +void AnimOverlay::buildFullBodyBoneSet() { + assert(_skeleton); + _boneSetVec.resize(_skeleton->getNumJoints()); + for (int i = 0; i < _skeleton->getNumJoints(); i++) { + _boneSetVec[i] = 1.0f; + } +} + +void AnimOverlay::buildUpperBodyBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int spineJoint = _skeleton->nameToJointIndex("Spine"); + for_each_child_joint(_skeleton, spineJoint, [&](int i) { + _boneSetVec[i] = 1.0f; + }); +} + +void AnimOverlay::buildLowerBodyBoneSet() { + assert(_skeleton); + buildFullBodyBoneSet(); + int hipsJoint = _skeleton->nameToJointIndex("Hips"); + int spineJoint = _skeleton->nameToJointIndex("Spine"); + _boneSetVec.resize(_skeleton->getNumJoints()); + for_each_child_joint(_skeleton, spineJoint, [&](int i) { + _boneSetVec[i] = 0.0f; + }); + _boneSetVec[hipsJoint] = 0.0f; +} + +void AnimOverlay::buildRightArmBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int rightShoulderJoint = _skeleton->nameToJointIndex("RightShoulder"); + for_each_child_joint(_skeleton, rightShoulderJoint, [&](int i) { + _boneSetVec[i] = 1.0f; + }); +} + +void AnimOverlay::buildLeftArmBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int leftShoulderJoint = _skeleton->nameToJointIndex("LeftShoulder"); + for_each_child_joint(_skeleton, leftShoulderJoint, [&](int i) { + _boneSetVec[i] = 1.0f; + }); +} + +void AnimOverlay::buildAboveTheHeadBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int headJoint = _skeleton->nameToJointIndex("Head"); + for_each_child_joint(_skeleton, headJoint, [&](int i) { + _boneSetVec[i] = 1.0f; + }); +} + +void AnimOverlay::buildBelowTheHeadBoneSet() { + assert(_skeleton); + buildFullBodyBoneSet(); + int headJoint = _skeleton->nameToJointIndex("Head"); + for_each_child_joint(_skeleton, headJoint, [&](int i) { + _boneSetVec[i] = 0.0f; + }); +} + +void AnimOverlay::buildHeadOnlyBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int headJoint = _skeleton->nameToJointIndex("Head"); + _boneSetVec[headJoint] = 1.0f; +} + +void AnimOverlay::buildSpineOnlyBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int spineJoint = _skeleton->nameToJointIndex("Spine"); + _boneSetVec[spineJoint] = 1.0f; +} + +void AnimOverlay::buildEmptyBoneSet() { + assert(_skeleton); + _boneSetVec.resize(_skeleton->getNumJoints()); + for (int i = 0; i < _skeleton->getNumJoints(); i++) { + _boneSetVec[i] = 0.0f; + } +} + +// for AnimDebugDraw rendering +const std::vector& AnimOverlay::getPosesInternal() const { + return _poses; +} + +void AnimOverlay::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { + _skeleton = skeleton; + + // we have to re-build the bone set when the skeleton changes. + setBoneSet(_boneSet); +} diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index 7f38f53297..82990514af 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -22,7 +22,7 @@ class AnimOverlay : public AnimNode { public: enum BoneSet { - FullBody = 0, + FullBodyBoneSet = 0, UpperBodyBoneSet, LowerBodyBoneSet, RightArmBoneSet, @@ -38,7 +38,7 @@ public: AnimOverlay(const std::string& id, BoneSet boneSet); virtual ~AnimOverlay() override; - void setBoneSet(BoneSet boneSet) { _boneSet = boneSet; } + void setBoneSet(BoneSet boneSet); BoneSet getBoneSet() const { return _boneSet; } virtual const std::vector& evaluate(float dt) override; @@ -46,9 +46,22 @@ public: protected: // for AnimDebugDraw rendering virtual const std::vector& getPosesInternal() const override; + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; std::vector _poses; BoneSet _boneSet; + std::vector _boneSetVec; + + void buildFullBodyBoneSet(); + void buildUpperBodyBoneSet(); + void buildLowerBodyBoneSet(); + void buildRightArmBoneSet(); + void buildLeftArmBoneSet(); + void buildAboveTheHeadBoneSet(); + void buildBelowTheHeadBoneSet(); + void buildHeadOnlyBoneSet(); + void buildSpineOnlyBoneSet(); + void buildEmptyBoneSet(); // no copies AnimOverlay(const AnimOverlay&) = delete; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index ee3e55c115..468abdc359 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -38,6 +38,7 @@ inline QDebug operator<<(QDebug debug, const AnimPose& pose) { class AnimSkeleton { public: typedef std::shared_ptr Pointer; + typedef std::shared_ptr ConstPointer; AnimSkeleton(const std::vector& joints); int nameToJointIndex(const QString& jointName) const; From 9457794d9e73082bf1fcfc4d1574851ce7852f33 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Aug 2015 14:50:02 -0700 Subject: [PATCH 20/52] fix whitespace offset due to merge conflict --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1ee3a3beff..1e296c1066 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -155,7 +155,7 @@ void MyAvatar::reset() { setOrientation(glm::quat(eulers)); } - void MyAvatar::update(float deltaTime) { +void MyAvatar::update(float deltaTime) { if (_goToPending) { setPosition(_goToPosition); From 5a73aef1f8beba1b22d027dd68368c471e48828b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Aug 2015 16:19:16 -0700 Subject: [PATCH 21/52] AnimSkeleton more accurate bind pose generation. --- libraries/animation/src/AnimSkeleton.cpp | 74 +++++++++--------------- libraries/fbx/src/FBXReader.cpp | 7 ++- libraries/fbx/src/FBXReader.h | 1 + 3 files changed, 34 insertions(+), 48 deletions(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 7c813a053e..38c8bf8166 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -43,7 +43,10 @@ AnimPose::operator glm::mat4() const { glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); } -//#define TRUST_BIND_TRANSFORM +static const mat4 IDENTITY = mat4(); +static bool matrixIsIdentity(const glm::mat4& m) { + return m == IDENTITY; +} AnimSkeleton::AnimSkeleton(const std::vector& joints) { _joints = joints; @@ -51,58 +54,37 @@ AnimSkeleton::AnimSkeleton(const std::vector& joints) { // build a cache of bind poses _absoluteBindPoses.reserve(joints.size()); _relativeBindPoses.reserve(joints.size()); + for (size_t i = 0; i < joints.size(); i++) { - - /* - // AJT: dump the skeleton, because wtf. - qCDebug(animation) << getJointName(i); - qCDebug(animation) << " isFree =" << _joints[i].isFree; - qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage; - qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex; - qCDebug(animation) << " boneRadius =" << _joints[i].boneRadius; - qCDebug(animation) << " translation =" << _joints[i].translation; - qCDebug(animation) << " preTransform =" << _joints[i].preTransform; - qCDebug(animation) << " preRotation =" << _joints[i].preRotation; - qCDebug(animation) << " rotation =" << _joints[i].rotation; - qCDebug(animation) << " postRotation =" << _joints[i].postRotation; - qCDebug(animation) << " postTransform =" << _joints[i].postTransform; - qCDebug(animation) << " transform =" << _joints[i].transform; - qCDebug(animation) << " rotationMin =" << _joints[i].rotationMin << ", rotationMax =" << _joints[i].rotationMax; - qCDebug(animation) << " inverseDefaultRotation" << _joints[i].inverseDefaultRotation; - qCDebug(animation) << " inverseBindRotation" << _joints[i].inverseBindRotation; - qCDebug(animation) << " bindTransform" << _joints[i].bindTransform; - qCDebug(animation) << " isSkeletonJoint" << _joints[i].isSkeletonJoint; - */ - -#ifdef TRUST_BIND_TRANSFORM - // trust FBXJoint::bindTransform (which is wrong for joints NOT bound to anything) - AnimPose absoluteBindPose(_joints[i].bindTransform); - _absoluteBindPoses.push_back(absoluteBindPose); - int parentIndex = getParentIndex(i); - if (parentIndex >= 0) { - AnimPose inverseParentAbsoluteBindPose = _absoluteBindPoses[parentIndex].inverse(); - _relativeBindPoses.push_back(inverseParentAbsoluteBindPose * absoluteBindPose); + if (_joints[i].bindTransformIsValid) { + // Use the FBXJoint::bindTransform, which is absolute model coordinates + // i.e. not relative to it's parent. + AnimPose absoluteBindPose(_joints[i].bindTransform); + _absoluteBindPoses.push_back(absoluteBindPose); + int parentIndex = getParentIndex(i); + if (parentIndex >= 0) { + AnimPose inverseParentAbsoluteBindPose = _absoluteBindPoses[parentIndex].inverse(); + _relativeBindPoses.push_back(inverseParentAbsoluteBindPose * absoluteBindPose); + } else { + _relativeBindPoses.push_back(absoluteBindPose); + } } else { - _relativeBindPoses.push_back(absoluteBindPose); - } -#else - // trust FBXJoint's local transforms (which is not really the bind pose, but the default pose in the fbx) - glm::mat4 rotTransform = glm::mat4_cast(_joints[i].preRotation * _joints[i].rotation * _joints[i].postRotation); - glm::mat4 relBindMat = glm::translate(_joints[i].translation) * _joints[i].preTransform * rotTransform * _joints[i].postTransform; - AnimPose relBindPose(relBindMat); - _relativeBindPoses.push_back(relBindPose); + // use FBXJoint's local transform, instead + glm::mat4 rotTransform = glm::mat4_cast(_joints[i].preRotation * _joints[i].rotation * _joints[i].postRotation); + glm::mat4 relBindMat = glm::translate(_joints[i].translation) * _joints[i].preTransform * rotTransform * _joints[i].postTransform; + AnimPose relBindPose(relBindMat); + _relativeBindPoses.push_back(relBindPose); - int parentIndex = getParentIndex(i); - if (parentIndex >= 0) { - _absoluteBindPoses.push_back(_absoluteBindPoses[parentIndex] * relBindPose); - } else { - _absoluteBindPoses.push_back(relBindPose); + int parentIndex = getParentIndex(i); + if (parentIndex >= 0) { + _absoluteBindPoses.push_back(_absoluteBindPoses[parentIndex] * relBindPose); + } else { + _absoluteBindPoses.push_back(relBindPose); + } } -#endif } } - int AnimSkeleton::nameToJointIndex(const QString& jointName) const { for (size_t i = 0; i < _joints.size(); i++) { if (_joints[i].name == jointName) { diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 7d4979e08f..47a8ee2f07 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -2307,7 +2307,9 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping break; } } - + + joint.bindTransformIsValid = false; + geometry.joints.append(joint); geometry.jointIndices.insert(model.name, geometry.joints.size()); @@ -2535,7 +2537,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping FBXJoint& joint = geometry.joints[fbxCluster.jointIndex]; joint.inverseBindRotation = glm::inverse(extractRotation(cluster.transformLink)); joint.bindTransform = cluster.transformLink; - + joint.bindTransformIsValid = true; + // update the bind pose extents glm::vec3 bindTranslation = extractTranslation(geometry.offset * joint.bindTransform); geometry.bindExtents.addPoint(bindTranslation); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index ae8adeb348..d8e1ae59e6 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -84,6 +84,7 @@ public: glm::mat4 bindTransform; QString name; bool isSkeletonJoint; + bool bindTransformIsValid; }; From bde75e9e516da4e8bfd5ef9b61c32e337d5be160 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Aug 2015 17:36:21 -0700 Subject: [PATCH 22/52] AnimDebugDraw rendering works again. I had to port AnimDebugDraw shader to GLSL 4.1. --- libraries/render-utils/src/animdebugdraw.slf | 6 ++++-- libraries/render-utils/src/animdebugdraw.slv | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/libraries/render-utils/src/animdebugdraw.slf b/libraries/render-utils/src/animdebugdraw.slf index aa34c9bfba..8a3aca055e 100644 --- a/libraries/render-utils/src/animdebugdraw.slf +++ b/libraries/render-utils/src/animdebugdraw.slf @@ -10,8 +10,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -varying vec4 varColor; +in vec4 _color; + +out vec4 _fragColor; void main(void) { - gl_FragColor = varColor; + _fragColor = _color; } diff --git a/libraries/render-utils/src/animdebugdraw.slv b/libraries/render-utils/src/animdebugdraw.slv index 68749ec7cc..f3117714b0 100644 --- a/libraries/render-utils/src/animdebugdraw.slv +++ b/libraries/render-utils/src/animdebugdraw.slv @@ -8,17 +8,19 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/Inputs.slh@> + <@include gpu/Transform.slh@> <$declareStandardTransform()$> -varying vec4 varColor; +out vec4 _color; void main(void) { // pass along the diffuse color - varColor = gl_Color; + _color = inColor.rgba; TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> } From 7a2ca047cbcde94f5e87c0aa54e0dbf9dd79f976 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Aug 2015 19:00:12 -0700 Subject: [PATCH 23/52] Added network resource download support to AnimNodeLoader. --- libraries/animation/src/AnimNodeLoader.cpp | 74 ++++++++++--------- libraries/animation/src/AnimNodeLoader.h | 33 +++++++-- tests/animation/src/AnimClipTests.cpp | 85 +++++++++++++--------- tests/animation/src/AnimClipTests.h | 4 - tests/animation/src/RigTests.cpp | 13 ++-- 5 files changed, 125 insertions(+), 84 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index cd635af8df..615dde1627 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -32,11 +32,11 @@ static TypeInfo typeInfoArray[AnimNode::NumTypes] = { { AnimNode::OverlayType, "overlay" } }; -typedef AnimNode::Pointer (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); +typedef AnimNode::Pointer (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); -static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); -static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); -static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl); +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 NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { loadClipNode, @@ -49,7 +49,7 @@ static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { if (!NAME##_VAL.isString()) { \ qCCritical(animation) << "AnimNodeLoader, error reading string" \ << #NAME << ", id =" << ID \ - << ", url =" << URL; \ + << ", url =" << URL.toDisplayString(); \ return nullptr; \ } \ QString NAME = NAME##_VAL.toString() @@ -59,7 +59,7 @@ static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { if (!NAME##_VAL.isBool()) { \ qCCritical(animation) << "AnimNodeLoader, error reading bool" \ << #NAME << ", id =" << ID \ - << ", url =" << URL; \ + << ", url =" << URL.toDisplayString(); \ return nullptr; \ } \ bool NAME = NAME##_VAL.toBool() @@ -69,7 +69,7 @@ static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { if (!NAME##_VAL.isDouble()) { \ qCCritical(animation) << "AnimNodeLoader, error reading double" \ << #NAME << "id =" << ID \ - << ", url =" << URL; \ + << ", url =" << URL.toDisplayString(); \ return nullptr; \ } \ float NAME = (float)NAME##_VAL.toDouble() @@ -83,29 +83,29 @@ static AnimNode::Type stringToEnum(const QString& str) { return AnimNode::NumTypes; } -static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QString& jsonUrl) { +static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) { auto idVal = jsonObj.value("id"); if (!idVal.isString()) { - qCCritical(animation) << "AnimNodeLoader, bad string \"id\", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, bad string \"id\", url =" << jsonUrl.toDisplayString(); return nullptr; } QString id = idVal.toString(); auto typeVal = jsonObj.value("type"); if (!typeVal.isString()) { - qCCritical(animation) << "AnimNodeLoader, bad object \"type\", id =" << id << ", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, bad object \"type\", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } QString typeStr = typeVal.toString(); AnimNode::Type type = stringToEnum(typeStr); if (type == AnimNode::NumTypes) { - qCCritical(animation) << "AnimNodeLoader, unknown node type" << typeStr << ", id =" << id << ", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, unknown node type" << typeStr << ", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } auto dataValue = jsonObj.value("data"); if (!dataValue.isObject()) { - qCCritical(animation) << "AnimNodeLoader, bad string \"data\", id =" << id << ", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, bad string \"data\", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } auto dataObj = dataValue.toObject(); @@ -115,13 +115,13 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QString& jso auto childrenValue = jsonObj.value("children"); if (!childrenValue.isArray()) { - qCCritical(animation) << "AnimNodeLoader, bad array \"children\", id =" << id << ", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, bad array \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } auto childrenAry = childrenValue.toArray(); for (const auto& childValue : childrenAry) { if (!childValue.isObject()) { - qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } node->addChild(loadNode(childValue.toObject(), jsonUrl)); @@ -129,7 +129,7 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QString& jso return node; } -static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl) { +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); @@ -140,7 +140,7 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& return std::make_shared(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag); } -static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl) { +static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl); @@ -169,40 +169,35 @@ static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { return AnimOverlay::NumBoneSets; } -static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl) { +static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_STRING(boneSet, jsonObj, id, jsonUrl); auto boneSetEnum = stringToBoneSetEnum(boneSet); if (boneSetEnum == AnimOverlay::NumBoneSets) { - qCCritical(animation) << "AnimNodeLoader, unknown bone set =" << boneSet << ", defaulting to \"fullBody\", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, unknown bone set =" << boneSet << ", defaulting to \"fullBody\", url =" << jsonUrl.toDisplayString(); boneSetEnum = AnimOverlay::FullBodyBoneSet; } return std::make_shared(id.toStdString(), boneSetEnum); } -AnimNodeLoader::AnimNodeLoader() { +AnimNodeLoader::AnimNodeLoader(const QUrl& url) : + _url(url), + _resource(nullptr) { + _resource = new Resource(url); + connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(onRequestDone(QNetworkReply&))); + connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError))); } -AnimNode::Pointer AnimNodeLoader::load(const std::string& filename) const { - // load entire file into a string. - QString jsonUrl = QString::fromStdString(filename); - QFile file; - file.setFileName(jsonUrl); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qCCritical(animation) << "AnimNodeLoader, could not open url =" << jsonUrl; - return nullptr; - } - QString contents = file.readAll(); - file.close(); +AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& jsonUrl) { // convert string into a json doc QJsonParseError error; - auto doc = QJsonDocument::fromJson(contents.toUtf8(), &error); + auto doc = QJsonDocument::fromJson(contents, &error); if (error.error != QJsonParseError::NoError) { - qCCritical(animation) << "AnimNodeLoader, failed to parse json, error =" << error.errorString() << ", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, failed to parse json, error =" << error.errorString() << ", url =" << jsonUrl.toDisplayString(); return nullptr; } QJsonObject obj = doc.object(); @@ -210,23 +205,32 @@ AnimNode::Pointer AnimNodeLoader::load(const std::string& filename) const { // version QJsonValue versionVal = obj.value("version"); if (!versionVal.isString()) { - qCCritical(animation) << "AnimNodeLoader, bad string \"version\", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, bad string \"version\", url =" << jsonUrl.toDisplayString(); return nullptr; } QString version = versionVal.toString(); // check version if (version != "1.0") { - qCCritical(animation) << "AnimNodeLoader, bad version number" << version << "expected \"1.0\", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, bad version number" << version << "expected \"1.0\", url =" << jsonUrl.toDisplayString(); return nullptr; } // root QJsonValue rootVal = obj.value("root"); if (!rootVal.isObject()) { - qCCritical(animation) << "AnimNodeLoader, bad object \"root\", url =" << jsonUrl; + qCCritical(animation) << "AnimNodeLoader, bad object \"root\", url =" << jsonUrl.toDisplayString(); return nullptr; } return loadNode(rootVal.toObject(), jsonUrl); } + +void AnimNodeLoader::onRequestDone(QNetworkReply& request) { + auto node = load(request.readAll(), _url); + emit success(node); +} + +void AnimNodeLoader::onRequestError(QNetworkReply::NetworkError netError) { + emit error((int)netError, "Resource download error"); +} diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index 26fb4fc9d5..095c05cf7e 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -12,13 +12,36 @@ #include -class AnimNode; +#include +#include +#include + +#include "AnimNode.h" + +class Resource; + +class AnimNodeLoader : public QObject { + Q_OBJECT -class AnimNodeLoader { public: - AnimNodeLoader(); - // TODO: load from url - std::shared_ptr load(const std::string& filename) const; + AnimNodeLoader(const QUrl& url); + +signals: + void success(AnimNode::Pointer node); + void error(int error, QString str); + +protected: + // synchronous + static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl); + +protected slots: + void onRequestDone(QNetworkReply& request); + void onRequestError(QNetworkReply::NetworkError error); + +protected: + QUrl _url; + Resource* _resource; +private: // no copies AnimNodeLoader(const AnimNodeLoader&) = delete; diff --git a/tests/animation/src/AnimClipTests.cpp b/tests/animation/src/AnimClipTests.cpp index 5c91d4a617..9a7841947b 100644 --- a/tests/animation/src/AnimClipTests.cpp +++ b/tests/animation/src/AnimClipTests.cpp @@ -30,7 +30,7 @@ void AnimClipTests::cleanupTestCase() { void AnimClipTests::testAccessors() { std::string id = "my anim clip"; - std::string url = "foo"; + std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; float endFrame = 20.0f; float timeScale = 1.1f; @@ -47,7 +47,7 @@ void AnimClipTests::testAccessors() { QVERIFY(clip.getTimeScale() == timeScale); QVERIFY(clip.getLoopFlag() == loopFlag); - std::string url2 = "bar"; + std::string url2 = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx"; float startFrame2 = 22.0f; float endFrame2 = 100.0f; float timeScale2 = 1.2f; @@ -73,7 +73,7 @@ static float framesToSec(float secs) { void AnimClipTests::testEvaulate() { std::string id = "my clip node"; - std::string url = "foo"; + std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; float endFrame = 22.0f; float timeScale = 1.0f; @@ -95,43 +95,60 @@ void AnimClipTests::testEvaulate() { } void AnimClipTests::testLoader() { - AnimNodeLoader loader; + auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/8ba57a8f0a76f88b39a11f77f8d9df04af9cec95/test.json"); + AnimNodeLoader loader(url); -#ifdef Q_OS_WIN - auto node = loader.load("../../../tests/animation/src/data/test.json"); -#else - auto node = loader.load("../../../../tests/animation/src/data/test.json"); -#endif + const int timeout = 1000; + QEventLoop loop; + QTimer timer; + timer.setInterval(timeout); + timer.setSingleShot(true); - QVERIFY((bool)node); - QVERIFY(node->getID() == "blend"); - QVERIFY(node->getType() == AnimNode::BlendLinearType); + bool done = false; + connect(&loader, &AnimNodeLoader::success, [&](AnimNode::Pointer node) { + QVERIFY((bool)node); + QVERIFY(node->getID() == "blend"); + QVERIFY(node->getType() == AnimNode::BlendLinearType); - auto blend = std::static_pointer_cast(node); - QVERIFY(blend->getAlpha() == 0.5f); + QVERIFY((bool)node); + QVERIFY(node->getID() == "blend"); + QVERIFY(node->getType() == AnimNode::BlendLinearType); - QVERIFY(node->getChildCount() == 3); + auto blend = std::static_pointer_cast(node); + QVERIFY(blend->getAlpha() == 0.5f); - std::shared_ptr nodes[3] = { node->getChild(0), node->getChild(1), node->getChild(2) }; + QVERIFY(node->getChildCount() == 3); - QVERIFY(nodes[0]->getID() == "test01"); - QVERIFY(nodes[0]->getChildCount() == 0); - QVERIFY(nodes[1]->getID() == "test02"); - QVERIFY(nodes[1]->getChildCount() == 0); - QVERIFY(nodes[2]->getID() == "test03"); - QVERIFY(nodes[2]->getChildCount() == 0); + std::shared_ptr nodes[3] = { node->getChild(0), node->getChild(1), node->getChild(2) }; - auto test01 = std::static_pointer_cast(nodes[0]); - QVERIFY(test01->getURL() == "test01.fbx"); - QVERIFY(test01->getStartFrame() == 1.0f); - QVERIFY(test01->getEndFrame() == 20.0f); - QVERIFY(test01->getTimeScale() == 1.0f); - QVERIFY(test01->getLoopFlag() == false); + QVERIFY(nodes[0]->getID() == "test01"); + QVERIFY(nodes[0]->getChildCount() == 0); + QVERIFY(nodes[1]->getID() == "test02"); + QVERIFY(nodes[1]->getChildCount() == 0); + QVERIFY(nodes[2]->getID() == "test03"); + QVERIFY(nodes[2]->getChildCount() == 0); - auto test02 = std::static_pointer_cast(nodes[1]); - QVERIFY(test02->getURL() == "test02.fbx"); - QVERIFY(test02->getStartFrame() == 2.0f); - QVERIFY(test02->getEndFrame() == 21.0f); - QVERIFY(test02->getTimeScale() == 0.9f); - QVERIFY(test02->getLoopFlag() == true); + auto test01 = std::static_pointer_cast(nodes[0]); + QVERIFY(test01->getURL() == "test01.fbx"); + QVERIFY(test01->getStartFrame() == 1.0f); + QVERIFY(test01->getEndFrame() == 20.0f); + QVERIFY(test01->getTimeScale() == 1.0f); + QVERIFY(test01->getLoopFlag() == false); + + auto test02 = std::static_pointer_cast(nodes[1]); + QVERIFY(test02->getURL() == "test02.fbx"); + QVERIFY(test02->getStartFrame() == 2.0f); + QVERIFY(test02->getEndFrame() == 21.0f); + QVERIFY(test02->getTimeScale() == 0.9f); + QVERIFY(test02->getLoopFlag() == true); + done = true; + }); + + loop.connect(&loader, SIGNAL(success(AnimNode::Pointer)), SLOT(quit())); + loop.connect(&loader, SIGNAL(error(int, QString)), SLOT(quit())); + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + timer.start(); + loop.exec(); + + QVERIFY(done); } diff --git a/tests/animation/src/AnimClipTests.h b/tests/animation/src/AnimClipTests.h index d4590ef27d..6239a88be8 100644 --- a/tests/animation/src/AnimClipTests.h +++ b/tests/animation/src/AnimClipTests.h @@ -13,10 +13,6 @@ #include #include -inline float getErrorDifference(float a, float b) { - return fabs(a - b); -} - class AnimClipTests : public QObject { Q_OBJECT private slots: diff --git a/tests/animation/src/RigTests.cpp b/tests/animation/src/RigTests.cpp index b0e0a53ee5..ff457ff804 100644 --- a/tests/animation/src/RigTests.cpp +++ b/tests/animation/src/RigTests.cpp @@ -78,24 +78,25 @@ void RigTests::initTestCase() { #ifdef FROM_FILE QFile file(FROM_FILE); QCOMPARE(file.open(QIODevice::ReadOnly), true); - FBXGeometry geometry = readFBX(file.readAll(), QVariantHash()); + FBXGeometry* geometry = readFBX(file.readAll(), QVariantHash()); #else QUrl fbxUrl("https://s3.amazonaws.com/hifi-public/models/skeletons/Zack/Zack.fbx"); QNetworkReply* reply = OBJReader().request(fbxUrl, false); // Just a convenience hack for synchronoud http request auto fbxHttpCode = !reply->isFinished() ? -1 : reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QCOMPARE(fbxHttpCode, 200); - FBXGeometry geometry = readFBX(reply->readAll(), QVariantHash()); + FBXGeometry* geometry = readFBX(reply->readAll(), QVariantHash()); #endif - + QVERIFY((bool)geometry); + QVector jointStates; - for (int i = 0; i < geometry.joints.size(); ++i) { - JointState state(geometry.joints[i]); + for (int i = 0; i < geometry->joints.size(); ++i) { + JointState state(geometry->joints[i]); jointStates.append(state); } _rig = std::make_shared(); _rig->initJointStates(jointStates, glm::mat4(), 0, 41, 40, 39, 17, 16, 15); // FIXME? get by name? do we really want to exclude the shoulder blades? - std::cout << "Rig is ready " << geometry.joints.count() << " joints " << std::endl; + std::cout << "Rig is ready " << geometry->joints.count() << " joints " << std::endl; reportAll(_rig); } From 4bdb00bbc5f3d0ccec115ba6a2f71c91088b8a58 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Aug 2015 09:58:36 -0700 Subject: [PATCH 24/52] Added setCurrentFrame interface to AnimClip. This will recurse the tree and call setCurrentFrameInternal on each node. This method can be overriden, currently the only meaningful implementation is AnimClip. --- libraries/animation/src/AnimClip.cpp | 69 +++++++++++++++------------- libraries/animation/src/AnimClip.h | 2 + libraries/animation/src/AnimNode.h | 9 +++- 3 files changed, 47 insertions(+), 33 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 9863d71882..dca43cb735 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -45,38 +45,6 @@ void AnimClip::setLoopFlag(bool loopFlag) { _loopFlag = loopFlag; } -float AnimClip::accumulateTime(float frame, float dt) const { - const float startFrame = std::min(_startFrame, _endFrame); - if (startFrame == _endFrame) { - // when startFrame >= endFrame - frame = _endFrame; - } else if (_timeScale > 0.0f) { - // accumulate time, keeping track of loops and end of animation events. - const float FRAMES_PER_SECOND = 30.0f; - float framesRemaining = (dt * _timeScale) * FRAMES_PER_SECOND; - while (framesRemaining > 0.0f) { - float framesTillEnd = _endFrame - _frame; - if (framesRemaining >= framesTillEnd) { - if (_loopFlag) { - // anim loop - // TODO: trigger onLoop event - framesRemaining -= framesTillEnd; - frame = startFrame; - } else { - // anim end - // TODO: trigger onDone event - frame = _endFrame; - framesRemaining = 0.0f; - } - } else { - frame += framesRemaining; - framesRemaining = 0.0f; - } - } - } - return frame; -} - const std::vector& AnimClip::evaluate(float dt) { _frame = accumulateTime(_frame, dt); @@ -109,6 +77,43 @@ const std::vector& AnimClip::evaluate(float dt) { return _poses; } +void AnimClip::setCurrentFrameInternal(float frame) { + const float dt = 0.0f; + _frame = accumulateTime(frame, dt); +} + +float AnimClip::accumulateTime(float frame, float dt) const { + const float startFrame = std::min(_startFrame, _endFrame); + if (startFrame == _endFrame) { + // when startFrame >= endFrame + frame = _endFrame; + } else if (_timeScale > 0.0f) { + // accumulate time, keeping track of loops and end of animation events. + const float FRAMES_PER_SECOND = 30.0f; + float framesRemaining = (dt * _timeScale) * FRAMES_PER_SECOND; + while (framesRemaining > 0.0f) { + float framesTillEnd = _endFrame - _frame; + if (framesRemaining >= framesTillEnd) { + if (_loopFlag) { + // anim loop + // TODO: trigger onLoop event + framesRemaining -= framesTillEnd; + frame = startFrame; + } else { + // anim end + // TODO: trigger onDone event + frame = _endFrame; + framesRemaining = 0.0f; + } + } else { + frame += framesRemaining; + framesRemaining = 0.0f; + } + } + } + return frame; +} + void AnimClip::copyFromNetworkAnim() { assert(_networkAnim && _networkAnim->isLoaded() && _skeleton); _anim.clear(); diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 1868ac0e03..3480fdc3cf 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -45,6 +45,8 @@ public: virtual const std::vector& evaluate(float dt) override; protected: + virtual void setCurrentFrameInternal(float frame) override; + float accumulateTime(float frame, float dt) const; void copyFromNetworkAnim(); diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index cae3d1805f..9063488e9b 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -43,6 +43,7 @@ public: typedef std::shared_ptr Pointer; AnimNode(Type type, const std::string& id) : _type(type), _id(id) {} + virtual ~AnimNode() {} const std::string& getID() const { return _id; } Type getType() const { return _type; } @@ -70,7 +71,12 @@ public: AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; } - virtual ~AnimNode() {} + void setCurrentFrame(float frame) { + setCurrentFrameInternal(frame); + for (auto&& child : _children) { + child->setCurrentFrameInternal(frame); + } + } virtual const std::vector& evaluate(float dt) = 0; virtual const std::vector& overlay(float dt, const std::vector& underPoses) { @@ -79,6 +85,7 @@ public: protected: + virtual void setCurrentFrameInternal(float frame) {} virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; } From 62f86e6a462cbba67cf7c70760c3af361ebcf460 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Aug 2015 11:33:57 -0700 Subject: [PATCH 25/52] Added AnimVariant, renamed AnimClipTests to AnimTests. * Added test for AnimVariant. --- libraries/animation/src/AnimClip.h | 2 +- libraries/animation/src/AnimVariant.h | 63 +++++++++ .../src/{AnimClipTests.cpp => AnimTests.cpp} | 133 +++++++++++------- .../src/{AnimClipTests.h => AnimTests.h} | 13 +- 4 files changed, 156 insertions(+), 55 deletions(-) create mode 100644 libraries/animation/src/AnimVariant.h rename tests/animation/src/{AnimClipTests.cpp => AnimTests.cpp} (53%) rename tests/animation/src/{AnimClipTests.h => AnimTests.h} (65%) diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 3480fdc3cf..2cce40d98a 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -22,7 +22,7 @@ class AnimClip : public AnimNode { public: - friend class AnimClipTests; + friend class AnimTests; AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag); virtual ~AnimClip() override; diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h new file mode 100644 index 0000000000..13e364045b --- /dev/null +++ b/libraries/animation/src/AnimVariant.h @@ -0,0 +1,63 @@ +// +// AnimVariant.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_AnimVariant_h +#define hifi_AnimVariant_h + +#include +#include +#include + +class AnimVariant { +public: + enum Type { + BoolType = 0, + FloatType, + Vec3Type, + QuatType, + Mat4Type, + NumTypes + }; + + AnimVariant() : _type(BoolType) { memset(&_val, 0, sizeof(_val)); } + AnimVariant(bool value) : _type(BoolType) { _val.boolVal = value; } + AnimVariant(float value) : _type(FloatType) { _val.floats[0] = value; } + AnimVariant(const glm::vec3& value) : _type(Vec3Type) { *reinterpret_cast(&_val) = value; } + AnimVariant(const glm::quat& value) : _type(QuatType) { *reinterpret_cast(&_val) = value; } + AnimVariant(const glm::mat4& value) : _type(Mat4Type) { *reinterpret_cast(&_val) = value; } + + bool isBool() const { return _type == BoolType; } + bool isFloat() const { return _type == FloatType; } + bool isVec3() const { return _type == Vec3Type; } + bool isQuat() const { return _type == QuatType; } + bool isMat4() const { return _type == Mat4Type; } + + void setBool(bool value) { assert(_type == BoolType); _val.boolVal = value; } + void setFloat(float value) { assert(_type == FloatType); _val.floats[0] = value; } + void setVec3(const glm::vec3& value) { assert(_type == Vec3Type); *reinterpret_cast(&_val) = value; } + void setQuat(const glm::quat& value) { assert(_type == QuatType); *reinterpret_cast(&_val) = value; } + void setMat4(const glm::mat4& value) { assert(_type == Mat4Type); *reinterpret_cast(&_val) = value; } + + bool getBool() { assert(_type == BoolType); return _val.boolVal; } + float getFloat() { assert(_type == FloatType); return _val.floats[0]; } + const glm::vec3& getVec3() { assert(_type == Vec3Type); return *reinterpret_cast(&_val); } + const glm::quat& getQuat() { assert(_type == QuatType); return *reinterpret_cast(&_val); } + const glm::mat4& getMat4() { assert(_type == Mat4Type); return *reinterpret_cast(&_val); } + +protected: + Type _type; + union { + bool boolVal; + float floats[16]; + } _val; +}; + +typedef std::map AnimVarantMap; + +#endif // hifi_AnimVariant_h diff --git a/tests/animation/src/AnimClipTests.cpp b/tests/animation/src/AnimTests.cpp similarity index 53% rename from tests/animation/src/AnimClipTests.cpp rename to tests/animation/src/AnimTests.cpp index 9a7841947b..82d805d515 100644 --- a/tests/animation/src/AnimClipTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -1,5 +1,5 @@ // -// AnimClipTests.cpp +// AnimTests.cpp // // Copyright 2015 High Fidelity, Inc. // @@ -7,28 +7,29 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "AnimClipTests.h" +#include "AnimTests.h" #include "AnimNodeLoader.h" #include "AnimClip.h" #include "AnimBlendLinear.h" #include "AnimationLogging.h" +#include "AnimVariant.h" #include <../QTestExtensions.h> -QTEST_MAIN(AnimClipTests) +QTEST_MAIN(AnimTests) const float EPSILON = 0.001f; -void AnimClipTests::initTestCase() { +void AnimTests::initTestCase() { auto animationCache = DependencyManager::set(); auto resourceCacheSharedItems = DependencyManager::set(); } -void AnimClipTests::cleanupTestCase() { +void AnimTests::cleanupTestCase() { DependencyManager::destroy(); } -void AnimClipTests::testAccessors() { +void AnimTests::testAccessors() { std::string id = "my anim clip"; std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; @@ -71,7 +72,7 @@ static float framesToSec(float secs) { return secs / FRAMES_PER_SECOND; } -void AnimClipTests::testEvaulate() { +void AnimTests::testEvaulate() { std::string id = "my clip node"; std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; @@ -94,7 +95,7 @@ void AnimClipTests::testEvaulate() { QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, EPSILON); } -void AnimClipTests::testLoader() { +void AnimTests::testLoader() { auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/8ba57a8f0a76f88b39a11f77f8d9df04af9cec95/test.json"); AnimNodeLoader loader(url); @@ -104,45 +105,8 @@ void AnimClipTests::testLoader() { timer.setInterval(timeout); timer.setSingleShot(true); - bool done = false; - connect(&loader, &AnimNodeLoader::success, [&](AnimNode::Pointer node) { - QVERIFY((bool)node); - QVERIFY(node->getID() == "blend"); - QVERIFY(node->getType() == AnimNode::BlendLinearType); - - QVERIFY((bool)node); - QVERIFY(node->getID() == "blend"); - QVERIFY(node->getType() == AnimNode::BlendLinearType); - - auto blend = std::static_pointer_cast(node); - QVERIFY(blend->getAlpha() == 0.5f); - - QVERIFY(node->getChildCount() == 3); - - std::shared_ptr nodes[3] = { node->getChild(0), node->getChild(1), node->getChild(2) }; - - QVERIFY(nodes[0]->getID() == "test01"); - QVERIFY(nodes[0]->getChildCount() == 0); - QVERIFY(nodes[1]->getID() == "test02"); - QVERIFY(nodes[1]->getChildCount() == 0); - QVERIFY(nodes[2]->getID() == "test03"); - QVERIFY(nodes[2]->getChildCount() == 0); - - auto test01 = std::static_pointer_cast(nodes[0]); - QVERIFY(test01->getURL() == "test01.fbx"); - QVERIFY(test01->getStartFrame() == 1.0f); - QVERIFY(test01->getEndFrame() == 20.0f); - QVERIFY(test01->getTimeScale() == 1.0f); - QVERIFY(test01->getLoopFlag() == false); - - auto test02 = std::static_pointer_cast(nodes[1]); - QVERIFY(test02->getURL() == "test02.fbx"); - QVERIFY(test02->getStartFrame() == 2.0f); - QVERIFY(test02->getEndFrame() == 21.0f); - QVERIFY(test02->getTimeScale() == 0.9f); - QVERIFY(test02->getLoopFlag() == true); - done = true; - }); + AnimNode::Pointer node = nullptr; + connect(&loader, &AnimNodeLoader::success, [&](AnimNode::Pointer nodeIn) { node = nodeIn; }); loop.connect(&loader, SIGNAL(success(AnimNode::Pointer)), SLOT(quit())); loop.connect(&loader, SIGNAL(error(int, QString)), SLOT(quit())); @@ -150,5 +114,78 @@ void AnimClipTests::testLoader() { timer.start(); loop.exec(); - QVERIFY(done); + QVERIFY((bool)node); + + QVERIFY(node->getID() == "blend"); + QVERIFY(node->getType() == AnimNode::BlendLinearType); + + QVERIFY((bool)node); + QVERIFY(node->getID() == "blend"); + QVERIFY(node->getType() == AnimNode::BlendLinearType); + + auto blend = std::static_pointer_cast(node); + QVERIFY(blend->getAlpha() == 0.5f); + + QVERIFY(node->getChildCount() == 3); + + std::shared_ptr nodes[3] = { node->getChild(0), node->getChild(1), node->getChild(2) }; + + QVERIFY(nodes[0]->getID() == "test01"); + QVERIFY(nodes[0]->getChildCount() == 0); + QVERIFY(nodes[1]->getID() == "test02"); + QVERIFY(nodes[1]->getChildCount() == 0); + QVERIFY(nodes[2]->getID() == "test03"); + QVERIFY(nodes[2]->getChildCount() == 0); + + auto test01 = std::static_pointer_cast(nodes[0]); + QVERIFY(test01->getURL() == "test01.fbx"); + QVERIFY(test01->getStartFrame() == 1.0f); + QVERIFY(test01->getEndFrame() == 20.0f); + QVERIFY(test01->getTimeScale() == 1.0f); + QVERIFY(test01->getLoopFlag() == false); + + auto test02 = std::static_pointer_cast(nodes[1]); + QVERIFY(test02->getURL() == "test02.fbx"); + QVERIFY(test02->getStartFrame() == 2.0f); + QVERIFY(test02->getEndFrame() == 21.0f); + QVERIFY(test02->getTimeScale() == 0.9f); + QVERIFY(test02->getLoopFlag() == true); +} + +void AnimTests::testVariant() { + auto defaultVar = AnimVariant(); + auto boolVar = AnimVariant(true); + auto floatVar = AnimVariant(1.0f); + auto vec3Var = AnimVariant(glm::vec3(1.0f, 2.0f, 3.0f)); + auto quatVar = AnimVariant(glm::quat(1.0f, 2.0f, 3.0f, 4.0f)); + auto mat4Var = AnimVariant(glm::mat4(glm::vec4(1.0f, 2.0f, 3.0f, 4.0f), + glm::vec4(5.0f, 6.0f, 7.0f, 8.0f), + glm::vec4(9.0f, 10.0f, 11.0f, 12.0f), + glm::vec4(13.0f, 14.0f, 15.0f, 16.0f))); + QVERIFY(defaultVar.isBool()); + QVERIFY(defaultVar.getBool() == false); + + QVERIFY(boolVar.isBool()); + QVERIFY(boolVar.getBool() == true); + + QVERIFY(floatVar.isFloat()); + QVERIFY(floatVar.getFloat() == 1.0f); + + QVERIFY(vec3Var.isVec3()); + auto v = vec3Var.getVec3(); + QVERIFY(v.x == 1.0f); + QVERIFY(v.y == 2.0f); + QVERIFY(v.z == 3.0f); + + QVERIFY(quatVar.isQuat()); + auto q = quatVar.getQuat(); + QVERIFY(q.w == 1.0f); + QVERIFY(q.x == 2.0f); + QVERIFY(q.y == 3.0f); + QVERIFY(q.z == 4.0f); + + QVERIFY(mat4Var.isMat4()); + auto m = mat4Var.getMat4(); + QVERIFY(m[0].x == 1.0f); + QVERIFY(m[3].w == 16.0f); } diff --git a/tests/animation/src/AnimClipTests.h b/tests/animation/src/AnimTests.h similarity index 65% rename from tests/animation/src/AnimClipTests.h rename to tests/animation/src/AnimTests.h index 6239a88be8..460caa067d 100644 --- a/tests/animation/src/AnimClipTests.h +++ b/tests/animation/src/AnimTests.h @@ -1,19 +1,19 @@ // -// AnimClipTests.h +// AnimTests.h // -// Copyright 2015 High Fidelity, Inc. +// 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_AnimClipTests_h -#define hifi_AnimClipTests_h +#ifndef hifi_AnimTests_h +#define hifi_AnimTests_h #include #include -class AnimClipTests : public QObject { +class AnimTests : public QObject { Q_OBJECT private slots: void initTestCase(); @@ -21,6 +21,7 @@ private slots: void testAccessors(); void testEvaulate(); void testLoader(); + void testVariant(); }; -#endif // hifi_AnimClipTests_h +#endif // hifi_AnimTests_h From 496c706bba9783af051380a3e26cdc9db1879c2e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Aug 2015 11:59:24 -0700 Subject: [PATCH 26/52] Added AnimVariantMap argument to evaluate. --- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/avatar/MyAvatar.h | 2 ++ libraries/animation/src/AnimBlendLinear.cpp | 10 ++++++---- libraries/animation/src/AnimBlendLinear.h | 2 +- libraries/animation/src/AnimClip.cpp | 5 ++++- libraries/animation/src/AnimClip.h | 2 +- libraries/animation/src/AnimNode.h | 7 ++++--- libraries/animation/src/AnimOverlay.cpp | 9 +++++---- libraries/animation/src/AnimOverlay.h | 2 +- libraries/animation/src/AnimVariant.h | 8 +++++++- tests/animation/src/AnimTests.cpp | 12 +++++++++--- 11 files changed, 41 insertions(+), 20 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1e296c1066..15bde7fe2c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -170,7 +170,7 @@ void MyAvatar::update(float deltaTime) { blend->setAlpha(0.5f * sin(t) + 0.5f); */ t += deltaTime; - _animNode->evaluate(deltaTime); + _animNode->evaluate(_animVars, deltaTime); } if (_referential) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c1812a5e96..0adc901398 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -17,6 +17,7 @@ #include #include "Avatar.h" +#include "AnimVariant.h" class ModelItemID; class AnimNode; @@ -315,6 +316,7 @@ private: bool _prevShouldDrawHead; std::shared_ptr _animNode; + AnimVariantMap _animVars; }; #endif // hifi_MyAvatar_h diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index 76d92cd264..8ce6845e6f 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -22,22 +22,24 @@ AnimBlendLinear::~AnimBlendLinear() { } -const std::vector& AnimBlendLinear::evaluate(float dt) { +const std::vector& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt) { + + // TODO: update _alpha from animVars if (_children.size() == 0) { for (auto&& pose : _poses) { pose = AnimPose::identity; } } else if (_children.size() == 1) { - _poses = _children[0]->evaluate(dt); + _poses = _children[0]->evaluate(animVars, dt); } else { float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); size_t prevPoseIndex = glm::floor(clampedAlpha); size_t nextPoseIndex = glm::ceil(clampedAlpha); float alpha = glm::fract(clampedAlpha); if (prevPoseIndex != nextPoseIndex) { - auto prevPoses = _children[prevPoseIndex]->evaluate(dt); - auto nextPoses = _children[nextPoseIndex]->evaluate(dt); + auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt); + auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt); if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { _poses.resize(prevPoses.size()); diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index c1c1c928e5..784d89b04c 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -28,7 +28,7 @@ public: AnimBlendLinear(const std::string& id, float alpha); virtual ~AnimBlendLinear() override; - virtual const std::vector& evaluate(float dt) override; + virtual const std::vector& evaluate(const AnimVariantMap& animVars, float dt) override; void setAlpha(float alpha) { _alpha = alpha; } float getAlpha() const { return _alpha; } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index dca43cb735..7653aabde8 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -45,7 +45,10 @@ void AnimClip::setLoopFlag(bool loopFlag) { _loopFlag = loopFlag; } -const std::vector& AnimClip::evaluate(float dt) { +const std::vector& AnimClip::evaluate(const AnimVariantMap& animVars, float dt) { + + // TODO: update _frame, _startFrame, _endFrame, _timeScale, _loopFlag from animVars. + _frame = accumulateTime(_frame, dt); if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 2cce40d98a..0d4f104678 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -42,7 +42,7 @@ public: void setLoopFlag(bool loopFlag); bool getLoopFlag() const { return _loopFlag; } - virtual const std::vector& evaluate(float dt) override; + virtual const std::vector& evaluate(const AnimVariantMap& animVars, float dt) override; protected: virtual void setCurrentFrameInternal(float frame) override; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 9063488e9b..c4edd9bb06 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -18,6 +18,7 @@ #include #include "AnimSkeleton.h" +#include "AnimVariant.h" class QJsonObject; @@ -78,9 +79,9 @@ public: } } - virtual const std::vector& evaluate(float dt) = 0; - virtual const std::vector& overlay(float dt, const std::vector& underPoses) { - return evaluate(dt); + virtual const std::vector& evaluate(const AnimVariantMap& animVars, float dt) = 0; + virtual const std::vector& overlay(const AnimVariantMap& animVars, float dt, const std::vector& underPoses) { + return evaluate(animVars, dt); } protected: diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index c02c83d69f..3a76903d0f 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -37,17 +37,18 @@ void AnimOverlay::setBoneSet(BoneSet boneSet) { } } -const std::vector& AnimOverlay::evaluate(float dt) { +const std::vector& AnimOverlay::evaluate(const AnimVariantMap& animVars, float dt) { + if (_children.size() >= 2) { - auto underPoses = _children[1]->evaluate(dt); - auto overPoses = _children[0]->overlay(dt, underPoses); + auto underPoses = _children[1]->evaluate(animVars, dt); + auto overPoses = _children[0]->overlay(animVars, dt, underPoses); if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) { _poses.resize(underPoses.size()); assert(_boneSetVec.size() == _poses.size()); for (size_t i = 0; i < _poses.size(); i++) { - float alpha = _boneSetVec[i]; // TODO: PULL from boneSet + float alpha = _boneSetVec[i]; blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]); } } diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index 82990514af..87d05e2b96 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -41,7 +41,7 @@ public: void setBoneSet(BoneSet boneSet); BoneSet getBoneSet() const { return _boneSet; } - virtual const std::vector& evaluate(float dt) override; + virtual const std::vector& evaluate(const AnimVariantMap& animVars, float dt) override; protected: // for AnimDebugDraw rendering diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 13e364045b..627e3210f4 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -18,6 +18,7 @@ class AnimVariant { public: enum Type { BoolType = 0, + IntType, FloatType, Vec3Type, QuatType, @@ -27,24 +28,28 @@ public: AnimVariant() : _type(BoolType) { memset(&_val, 0, sizeof(_val)); } AnimVariant(bool value) : _type(BoolType) { _val.boolVal = value; } + AnimVariant(int value) : _type(IntType) { _val.intVal = value; } AnimVariant(float value) : _type(FloatType) { _val.floats[0] = value; } AnimVariant(const glm::vec3& value) : _type(Vec3Type) { *reinterpret_cast(&_val) = value; } AnimVariant(const glm::quat& value) : _type(QuatType) { *reinterpret_cast(&_val) = value; } AnimVariant(const glm::mat4& value) : _type(Mat4Type) { *reinterpret_cast(&_val) = value; } bool isBool() const { return _type == BoolType; } + bool isInt() const { return _type == IntType; } bool isFloat() const { return _type == FloatType; } bool isVec3() const { return _type == Vec3Type; } bool isQuat() const { return _type == QuatType; } bool isMat4() const { return _type == Mat4Type; } void setBool(bool value) { assert(_type == BoolType); _val.boolVal = value; } + void setInt(int value) { assert(_type == IntType); _val.intVal = value; } void setFloat(float value) { assert(_type == FloatType); _val.floats[0] = value; } void setVec3(const glm::vec3& value) { assert(_type == Vec3Type); *reinterpret_cast(&_val) = value; } void setQuat(const glm::quat& value) { assert(_type == QuatType); *reinterpret_cast(&_val) = value; } void setMat4(const glm::mat4& value) { assert(_type == Mat4Type); *reinterpret_cast(&_val) = value; } bool getBool() { assert(_type == BoolType); return _val.boolVal; } + int getInt() { assert(_type == IntType); return _val.intVal; } float getFloat() { assert(_type == FloatType); return _val.floats[0]; } const glm::vec3& getVec3() { assert(_type == Vec3Type); return *reinterpret_cast(&_val); } const glm::quat& getQuat() { assert(_type == QuatType); return *reinterpret_cast(&_val); } @@ -54,10 +59,11 @@ protected: Type _type; union { bool boolVal; + int intVal; float floats[16]; } _val; }; -typedef std::map AnimVarantMap; +typedef std::map AnimVariantMap; #endif // hifi_AnimVariant_h diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 82d805d515..a25ce4bc0a 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -80,18 +80,20 @@ void AnimTests::testEvaulate() { float timeScale = 1.0f; float loopFlag = true; + auto vars = AnimVariantMap(); + AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag); - clip.evaluate(framesToSec(10.0f)); + clip.evaluate(vars, framesToSec(10.0f)); QCOMPARE_WITH_ABS_ERROR(clip._frame, 12.0f, EPSILON); // does it loop? - clip.evaluate(framesToSec(11.0f)); + clip.evaluate(vars, framesToSec(11.0f)); QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); // does it pause at end? clip.setLoopFlag(false); - clip.evaluate(framesToSec(20.0f)); + clip.evaluate(vars, framesToSec(20.0f)); QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, EPSILON); } @@ -155,6 +157,7 @@ void AnimTests::testLoader() { void AnimTests::testVariant() { auto defaultVar = AnimVariant(); auto boolVar = AnimVariant(true); + auto intVar = AnimVariant(1); auto floatVar = AnimVariant(1.0f); auto vec3Var = AnimVariant(glm::vec3(1.0f, 2.0f, 3.0f)); auto quatVar = AnimVariant(glm::quat(1.0f, 2.0f, 3.0f, 4.0f)); @@ -168,6 +171,9 @@ void AnimTests::testVariant() { QVERIFY(boolVar.isBool()); QVERIFY(boolVar.getBool() == true); + QVERIFY(intVar.isInt()); + QVERIFY(intVar.getInt() == 1); + QVERIFY(floatVar.isFloat()); QVERIFY(floatVar.getFloat() == 1.0f); From 4abf0cbd63a5c7f178632cf493bdff892cba8450 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Aug 2015 20:28:17 -0700 Subject: [PATCH 27/52] AnimVariantMap is used in eval, MyAvatar loads avatar.json via url --- interface/src/avatar/MyAvatar.cpp | 37 +++++---- interface/src/avatar/MyAvatar.h | 6 +- libraries/animation/src/AnimBlendLinear.cpp | 12 ++- libraries/animation/src/AnimBlendLinear.h | 20 +++-- libraries/animation/src/AnimClip.cpp | 45 +++++----- libraries/animation/src/AnimClip.h | 38 ++++----- libraries/animation/src/AnimNode.h | 17 ++-- libraries/animation/src/AnimOverlay.cpp | 24 ++++-- libraries/animation/src/AnimOverlay.h | 26 ++++-- libraries/animation/src/AnimSkeleton.h | 6 +- libraries/animation/src/AnimStateMachine.h | 66 +++++++++++++++ libraries/animation/src/AnimVariant.h | 81 ++++++++++++++++-- tests/animation/src/AnimTests.cpp | 92 ++++++++++++--------- tests/animation/src/AnimTests.h | 5 +- tests/animation/src/data/avatar.json | 37 +++++++++ 15 files changed, 361 insertions(+), 151 deletions(-) create mode 100644 libraries/animation/src/AnimStateMachine.h create mode 100644 tests/animation/src/data/avatar.json diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 15bde7fe2c..48fb2c1801 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -165,10 +165,7 @@ void MyAvatar::update(float deltaTime) { if (_animNode) { static float t = 0.0f; - /* - auto blend = std::static_pointer_cast(_animNode); - blend->setAlpha(0.5f * sin(t) + 0.5f); - */ + _animVars.set("sine", 0.5f * sin(t) + 0.5f); t += deltaTime; _animNode->evaluate(_animVars, deltaTime); } @@ -1222,26 +1219,30 @@ void MyAvatar::initHeadBones() { void MyAvatar::setupNewAnimationSystem() { - // create a skeleton and hand it over to the debug draw instance + // create a skeleton auto geom = _skeletonModel.getGeometry()->getFBXGeometry(); std::vector joints; for (auto& joint : geom.joints) { joints.push_back(joint); } - auto skeleton = make_shared(joints); - AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); - AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform); + _animSkeleton = make_shared(joints); - // create a overlay node - auto overlay = make_shared("overlay", AnimOverlay::UpperBodyBoneSet); - auto idle = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", 0.0f, 90.0f, 1.0f, true); - auto walk = make_shared("clip", "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx", 0.0f, 28.0f, 1.0f, true); - overlay->addChild(idle); - overlay->addChild(walk); - _animNode = overlay; - _animNode->setSkeleton(skeleton); - xform.trans.z += 1.0f; - AnimDebugDraw::getInstance().addAnimNode("blend", _animNode, xform); + // add it to the debug renderer, so we can see it. + //AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); + //AnimDebugDraw::getInstance().addSkeleton("my-avatar", _animSkeleton, xform); + + // load the anim graph + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/a939eadee4f36248776913d42891954a8d009158/avatar.json"); + _animLoader.reset(new AnimNodeLoader(graphUrl)); + connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { + _animNode = nodeIn; + _animNode->setSkeleton(_animSkeleton); + AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset() + glm::vec3(0, 0, 1)); + AnimDebugDraw::getInstance().addAnimNode("node", _animNode, xform); + }); + connect(_animLoader.get(), &AnimNodeLoader::error, [this, graphUrl](int error, QString str) { + qCCritical(interfaceapp) << "Error loading" << graphUrl << "code = " << error << "str =" << str; + }); } void MyAvatar::preRender(RenderArgs* renderArgs) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0adc901398..3946edbaba 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -16,8 +16,10 @@ #include #include +#include "AnimNode.h" +#include "AnimNodeLoader.h" + #include "Avatar.h" -#include "AnimVariant.h" class ModelItemID; class AnimNode; @@ -316,6 +318,8 @@ private: bool _prevShouldDrawHead; std::shared_ptr _animNode; + std::shared_ptr _animSkeleton; + std::unique_ptr _animLoader; AnimVariantMap _animVars; }; diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index 8ce6845e6f..90dd85f313 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -22,9 +22,9 @@ AnimBlendLinear::~AnimBlendLinear() { } -const std::vector& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt) { +const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt) { - // TODO: update _alpha from animVars + _alpha = animVars.lookup(_alphaVar, _alpha); if (_children.size() == 0) { for (auto&& pose : _poses) { @@ -37,7 +37,11 @@ const std::vector& AnimBlendLinear::evaluate(const AnimVariantMap& ani size_t prevPoseIndex = glm::floor(clampedAlpha); size_t nextPoseIndex = glm::ceil(clampedAlpha); float alpha = glm::fract(clampedAlpha); - if (prevPoseIndex != nextPoseIndex) { + if (prevPoseIndex == nextPoseIndex) { + // this can happen if alpha is on an integer boundary + _poses = _children[prevPoseIndex]->evaluate(animVars, dt); + } else { + // need to eval and blend between two children. auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt); auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt); @@ -52,6 +56,6 @@ const std::vector& AnimBlendLinear::evaluate(const AnimVariantMap& ani } // for AnimDebugDraw rendering -const std::vector& AnimBlendLinear::getPosesInternal() const { +const AnimPoseVec& AnimBlendLinear::getPosesInternal() const { return _poses; } diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index 784d89b04c..85799ad3e3 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -7,11 +7,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "AnimNode.h" - #ifndef hifi_AnimBlendLinear_h #define hifi_AnimBlendLinear_h +#include "AnimNode.h" + // Linear blend between two AnimNodes. // the amount of blending is determined by the alpha parameter. // If the number of children is 2, then the alpha parameters should be between @@ -24,23 +24,25 @@ class AnimBlendLinear : public AnimNode { public: + friend class AnimTests; AnimBlendLinear(const std::string& id, float alpha); virtual ~AnimBlendLinear() override; - virtual const std::vector& evaluate(const AnimVariantMap& animVars, float dt) override; - - void setAlpha(float alpha) { _alpha = alpha; } - float getAlpha() const { return _alpha; } + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; protected: - // for AnimDebugDraw rendering - virtual const std::vector& getPosesInternal() const override; + void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; } - std::vector _poses; + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + + AnimPoseVec _poses; float _alpha; + std::string _alphaVar; + // no copies AnimBlendLinear(const AnimBlendLinear&) = delete; AnimBlendLinear& operator=(const AnimBlendLinear&) = delete; diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 7653aabde8..51e32143be 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -20,38 +20,25 @@ AnimClip::AnimClip(const std::string& id, const std::string& url, float startFra _loopFlag(loopFlag), _frame(startFrame) { - setURL(url); + loadURL(url); } AnimClip::~AnimClip() { } -void AnimClip::setURL(const std::string& url) { - auto animCache = DependencyManager::get(); - _networkAnim = animCache->getAnimation(QString::fromStdString(url)); - _url = url; -} +const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt) { -void AnimClip::setStartFrame(float startFrame) { - _startFrame = startFrame; -} - -void AnimClip::setEndFrame(float endFrame) { - _endFrame = endFrame; -} - -void AnimClip::setLoopFlag(bool loopFlag) { - _loopFlag = loopFlag; -} - -const std::vector& AnimClip::evaluate(const AnimVariantMap& animVars, float dt) { - - // TODO: update _frame, _startFrame, _endFrame, _timeScale, _loopFlag from animVars. - - _frame = accumulateTime(_frame, dt); + // lookup parameters from animVars, using current instance variables as defaults. + _startFrame = animVars.lookup(_startFrameVar, _startFrame); + _endFrame = animVars.lookup(_endFrameVar, _endFrame); + _timeScale = animVars.lookup(_timeScaleVar, _timeScale); + _loopFlag = animVars.lookup(_loopFlagVar, _loopFlag); + _frame = accumulateTime(animVars.lookup(_frameVar, _frame), dt); + // poll network anim to see if it's finished loading yet. if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { + // loading is complete, copy animation frames from network animation, then throw it away. copyFromNetworkAnim(); _networkAnim.reset(); } @@ -70,8 +57,8 @@ const std::vector& AnimClip::evaluate(const AnimVariantMap& animVars, prevIndex = std::min(std::max(0, prevIndex), frameCount - 1); nextIndex = std::min(std::max(0, nextIndex), frameCount - 1); - const std::vector& prevFrame = _anim[prevIndex]; - const std::vector& nextFrame = _anim[nextIndex]; + const AnimPoseVec& prevFrame = _anim[prevIndex]; + const AnimPoseVec& nextFrame = _anim[nextIndex]; float alpha = glm::fract(_frame); blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]); @@ -80,6 +67,12 @@ const std::vector& AnimClip::evaluate(const AnimVariantMap& animVars, return _poses; } +void AnimClip::loadURL(const std::string& url) { + auto animCache = DependencyManager::get(); + _networkAnim = animCache->getAnimation(QString::fromStdString(url)); + _url = url; +} + void AnimClip::setCurrentFrameInternal(float frame) { const float dt = 0.0f; _frame = accumulateTime(frame, dt); @@ -160,6 +153,6 @@ void AnimClip::copyFromNetworkAnim() { } -const std::vector& AnimClip::getPosesInternal() const { +const AnimPoseVec& AnimClip::getPosesInternal() const { return _poses; } diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 0d4f104678..e4651e0ece 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -27,46 +27,44 @@ public: AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag); virtual ~AnimClip() override; - void setURL(const std::string& url); - const std::string& getURL() const { return _url; } - - void setStartFrame(float startFrame); - float getStartFrame() const { return _startFrame; } - - void setEndFrame(float endFrame); - float getEndFrame() const { return _endFrame; } - - void setTimeScale(float timeScale) { _timeScale = timeScale; } - float getTimeScale() const { return _timeScale; } - - void setLoopFlag(bool loopFlag); - bool getLoopFlag() const { return _loopFlag; } - - virtual const std::vector& evaluate(const AnimVariantMap& animVars, float dt) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; protected: + void loadURL(const std::string& url); + + void setStartFrameVar(const std::string& startFrameVar) { _startFrameVar = startFrameVar; } + void setEndFrameVar(const std::string& endFrameVar) { _endFrameVar = endFrameVar; } + void setTimeScaleVar(const std::string& timeScaleVar) { _timeScaleVar = timeScaleVar; } + void setLoopFlagVar(const std::string& loopFlagVar) { _loopFlagVar = loopFlagVar; } + void setFrameVar(const std::string& frameVar) { _frameVar = frameVar; } + virtual void setCurrentFrameInternal(float frame) override; float accumulateTime(float frame, float dt) const; void copyFromNetworkAnim(); // for AnimDebugDraw rendering - virtual const std::vector& getPosesInternal() const override; + virtual const AnimPoseVec& getPosesInternal() const override; AnimationPointer _networkAnim; - std::vector _poses; + AnimPoseVec _poses; // _anim[frame][joint] - std::vector> _anim; + std::vector _anim; std::string _url; float _startFrame; float _endFrame; float _timeScale; bool _loopFlag; - float _frame; + std::string _startFrameVar; + std::string _endFrameVar; + std::string _timeScaleVar; + std::string _loopFlagVar; + std::string _frameVar; + // no copies AnimClip(const AnimClip&) = delete; AnimClip& operator=(const AnimClip&) = delete; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index c4edd9bb06..b7c7826540 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -63,6 +63,7 @@ public: } int getChildCount() const { return (int)_children.size(); } + // pair this AnimNode graph with a skeleton. void setSkeleton(const AnimSkeleton::Pointer skeleton) { setSkeletonInternal(skeleton); for (auto&& child : _children) { @@ -72,6 +73,13 @@ public: AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; } + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) = 0; + virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, const AnimPoseVec& underPoses) { + return evaluate(animVars, dt); + } + +protected: + void setCurrentFrame(float frame) { setCurrentFrameInternal(frame); for (auto&& child : _children) { @@ -79,20 +87,13 @@ public: } } - virtual const std::vector& evaluate(const AnimVariantMap& animVars, float dt) = 0; - virtual const std::vector& overlay(const AnimVariantMap& animVars, float dt, const std::vector& underPoses) { - return evaluate(animVars, dt); - } - -protected: - virtual void setCurrentFrameInternal(float frame) {} virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; } // for AnimDebugDraw rendering - virtual const std::vector& getPosesInternal() const = 0; + virtual const AnimPoseVec& getPosesInternal() const = 0; Type _type; std::string _id; diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index 3a76903d0f..97db1711ca 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -11,16 +11,15 @@ #include "AnimUtil.h" #include -AnimOverlay::AnimOverlay(const std::string& id, BoneSet boneSet) : - AnimNode(AnimNode::OverlayType, id), _boneSet(boneSet) { +AnimOverlay::AnimOverlay(const std::string& id, BoneSet boneSet, float alpha) : + AnimNode(AnimNode::OverlayType, id), _boneSet(boneSet), _alpha(alpha) { } AnimOverlay::~AnimOverlay() { } -void AnimOverlay::setBoneSet(BoneSet boneSet) { - _boneSet = boneSet; +void AnimOverlay::buildBoneSet(BoneSet boneSet) { assert(_skeleton); switch (boneSet) { case FullBodyBoneSet: buildFullBodyBoneSet(); break; @@ -37,7 +36,16 @@ void AnimOverlay::setBoneSet(BoneSet boneSet) { } } -const std::vector& AnimOverlay::evaluate(const AnimVariantMap& animVars, float dt) { +const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, float dt) { + + // lookup parameters from animVars, using current instance variables as defaults. + // NOTE: switching bonesets can be an expensive operation, let's try to avoid it. + auto prevBoneSet = _boneSet; + _boneSet = (BoneSet)animVars.lookup(_boneSetVar, (int)_boneSet); + if (_boneSet != prevBoneSet && _skeleton) { + buildBoneSet(_boneSet); + } + _alpha = animVars.lookup(_alphaVar, _alpha); if (_children.size() >= 2) { auto underPoses = _children[1]->evaluate(animVars, dt); @@ -48,7 +56,7 @@ const std::vector& AnimOverlay::evaluate(const AnimVariantMap& animVar assert(_boneSetVec.size() == _poses.size()); for (size_t i = 0; i < _poses.size(); i++) { - float alpha = _boneSetVec[i]; + float alpha = _boneSetVec[i] * _alpha; blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]); } } @@ -160,7 +168,7 @@ void AnimOverlay::buildEmptyBoneSet() { } // for AnimDebugDraw rendering -const std::vector& AnimOverlay::getPosesInternal() const { +const AnimPoseVec& AnimOverlay::getPosesInternal() const { return _poses; } @@ -168,5 +176,5 @@ void AnimOverlay::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; // we have to re-build the bone set when the skeleton changes. - setBoneSet(_boneSet); + buildBoneSet(_boneSet); } diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index 87d05e2b96..2fe25d430e 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -7,8 +7,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "AnimNode.h" - #ifndef hifi_AnimOverlay_h #define hifi_AnimOverlay_h @@ -17,9 +15,13 @@ // Overlay the AnimPoses from one AnimNode on top of another AnimNode. // child[0] is overlayed on top of child[1]. The boneset is used // to control blending on a per-bone bases. +// alpha gives the ability to fade in and fade out overlays. +// alpha of 0, will have no overlay, final pose will be 100% from child[1]. +// alpha of 1, will be a full overlay. class AnimOverlay : public AnimNode { public: + friend class AnimDebugDraw; enum BoneSet { FullBodyBoneSet = 0, @@ -35,23 +37,29 @@ public: NumBoneSets, }; - AnimOverlay(const std::string& id, BoneSet boneSet); + AnimOverlay(const std::string& id, BoneSet boneSet, float alpha); virtual ~AnimOverlay() override; - void setBoneSet(BoneSet boneSet); - BoneSet getBoneSet() const { return _boneSet; } + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; - virtual const std::vector& evaluate(const AnimVariantMap& animVars, float dt) override; + protected: + void buildBoneSet(BoneSet boneSet); + + void setBoneSetVar(const std::string& boneSetVar) { _boneSetVar = boneSetVar; } + void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; } -protected: // for AnimDebugDraw rendering - virtual const std::vector& getPosesInternal() const override; + virtual const AnimPoseVec& getPosesInternal() const override; virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; - std::vector _poses; + AnimPoseVec _poses; BoneSet _boneSet; + float _alpha; std::vector _boneSetVec; + std::string _boneSetVar; + std::string _alphaVar; + void buildFullBodyBoneSet(); void buildUpperBodyBoneSet(); void buildLowerBodyBoneSet(); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 468abdc359..02ecd5ea7e 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -35,6 +35,8 @@ inline QDebug operator<<(QDebug debug, const AnimPose& pose) { return debug; } +typedef std::vector AnimPoseVec; + class AnimSkeleton { public: typedef std::shared_ptr Pointer; @@ -55,8 +57,8 @@ public: protected: std::vector _joints; - std::vector _absoluteBindPoses; - std::vector _relativeBindPoses; + AnimPoseVec _absoluteBindPoses; + AnimPoseVec _relativeBindPoses; // no copies AnimSkeleton(const AnimSkeleton&) = delete; diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h new file mode 100644 index 0000000000..65c1bafdca --- /dev/null +++ b/libraries/animation/src/AnimStateMachine.h @@ -0,0 +1,66 @@ + +#ifndef hifi_AnimStateMachine_h +#define hifi_AnimStateMachine_h + +// AJT: post-pone state machine work. +#if 0 +class AnimStateMachine : public AnimNode { +public: + friend class AnimDebugDraw; + + AnimStateMachine(const std::string& id, float alpha); + virtual ~AnimStateMachine() override; + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; + + class AnimTransition; + + class AnimState { + public: + typedef std::shared_ptr Pointer; + AnimState(const std::string& name, AnimNode::Pointer node, float interpFrame, float interpDuration) : + _name(name), + _node(node), + _interpFrame(interpFrame), + _interpDuration(interpDuration) {} + protected: + std::string _name; + AnimNode::Pointer _node; + float _interpFrame; + float _interpDuration; + std::vector _transitions; + private: + // no copies + AnimState(const AnimState&) = delete; + AnimState& operator=(const AnimState&) = delete; + }; + + class AnimTransition { + AnimTransition(const std::string& trigger, AnimState::Pointer state) : _trigger(trigger), _state(state) {} + protected: + std::string _trigger; + AnimState::Pointer _state; + }; + + void addState(AnimState::Pointer state); + void removeState(AnimState::Pointer state); + void setCurrentState(AnimState::Pointer state); + +protected: + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + + AnimPoseVec _poses; + + std::vector _states; + AnimState::Pointer _currentState; + AnimState::Pointer _defaultState; + +private: + // no copies + AnimStateMachine(const AnimBlendLinear&) = delete; + AnimStateMachine& operator=(const AnimBlendLinear&) = delete; +}; +#endif + +#endif // hifi_AnimBlendLinear_h diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 627e3210f4..36d189f85c 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -13,6 +13,7 @@ #include #include #include +#include class AnimVariant { public: @@ -48,12 +49,12 @@ public: void setQuat(const glm::quat& value) { assert(_type == QuatType); *reinterpret_cast(&_val) = value; } void setMat4(const glm::mat4& value) { assert(_type == Mat4Type); *reinterpret_cast(&_val) = value; } - bool getBool() { assert(_type == BoolType); return _val.boolVal; } - int getInt() { assert(_type == IntType); return _val.intVal; } - float getFloat() { assert(_type == FloatType); return _val.floats[0]; } - const glm::vec3& getVec3() { assert(_type == Vec3Type); return *reinterpret_cast(&_val); } - const glm::quat& getQuat() { assert(_type == QuatType); return *reinterpret_cast(&_val); } - const glm::mat4& getMat4() { assert(_type == Mat4Type); return *reinterpret_cast(&_val); } + bool getBool() const { assert(_type == BoolType); return _val.boolVal; } + int getInt() const { assert(_type == IntType); return _val.intVal; } + float getFloat() const { assert(_type == FloatType); return _val.floats[0]; } + const glm::vec3& getVec3() const { assert(_type == Vec3Type); return *reinterpret_cast(&_val); } + const glm::quat& getQuat() const { assert(_type == QuatType); return *reinterpret_cast(&_val); } + const glm::mat4& getMat4() const { assert(_type == Mat4Type); return *reinterpret_cast(&_val); } protected: Type _type; @@ -64,6 +65,72 @@ protected: } _val; }; -typedef std::map AnimVariantMap; +class AnimVariantMap { +public: + + bool lookup(const std::string& key, bool defaultValue) const { + if (key.empty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? iter->second.getBool() : defaultValue; + } + } + + int lookup(const std::string& key, int defaultValue) const { + if (key.empty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? iter->second.getInt() : defaultValue; + } + } + + float lookup(const std::string& key, float defaultValue) const { + if (key.empty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? iter->second.getFloat() : defaultValue; + } + } + + const glm::vec3& lookup(const std::string& key, const glm::vec3& defaultValue) const { + if (key.empty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? iter->second.getVec3() : defaultValue; + } + } + + const glm::quat& lookup(const std::string& key, const glm::quat& defaultValue) const { + if (key.empty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? iter->second.getQuat() : defaultValue; + } + } + + const glm::mat4& lookup(const std::string& key, const glm::mat4& defaultValue) const { + if (key.empty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? iter->second.getMat4() : defaultValue; + } + } + + void set(const std::string& key, bool value) { _map[key] = AnimVariant(value); } + void set(const std::string& key, int value) { _map[key] = AnimVariant(value); } + void set(const std::string& key, float value) { _map[key] = AnimVariant(value); } + void set(const std::string& key, const glm::vec3& value) { _map[key] = AnimVariant(value); } + void set(const std::string& key, const glm::quat& value) { _map[key] = AnimVariant(value); } + void set(const std::string& key, const glm::mat4& value) { _map[key] = AnimVariant(value); } + +protected: + std::map _map; +}; #endif // hifi_AnimVariant_h diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index a25ce4bc0a..91bb1ce5bb 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -29,7 +29,7 @@ void AnimTests::cleanupTestCase() { DependencyManager::destroy(); } -void AnimTests::testAccessors() { +void AnimTests::testClipInternalState() { std::string id = "my anim clip"; std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; @@ -42,29 +42,11 @@ void AnimTests::testAccessors() { QVERIFY(clip.getID() == id); QVERIFY(clip.getType() == AnimNode::ClipType); - QVERIFY(clip.getURL() == url); - QVERIFY(clip.getStartFrame() == startFrame); - QVERIFY(clip.getEndFrame() == endFrame); - QVERIFY(clip.getTimeScale() == timeScale); - QVERIFY(clip.getLoopFlag() == loopFlag); - - std::string url2 = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx"; - float startFrame2 = 22.0f; - float endFrame2 = 100.0f; - float timeScale2 = 1.2f; - bool loopFlag2 = false; - - clip.setURL(url2); - clip.setStartFrame(startFrame2); - clip.setEndFrame(endFrame2); - clip.setTimeScale(timeScale2); - clip.setLoopFlag(loopFlag2); - - QVERIFY(clip.getURL() == url2); - QVERIFY(clip.getStartFrame() == startFrame2); - QVERIFY(clip.getEndFrame() == endFrame2); - QVERIFY(clip.getTimeScale() == timeScale2); - QVERIFY(clip.getLoopFlag() == loopFlag2); + QVERIFY(clip._url == url); + QVERIFY(clip._startFrame == startFrame); + QVERIFY(clip._endFrame == endFrame); + QVERIFY(clip._timeScale == timeScale); + QVERIFY(clip._loopFlag == loopFlag); } static float framesToSec(float secs) { @@ -72,7 +54,7 @@ static float framesToSec(float secs) { return secs / FRAMES_PER_SECOND; } -void AnimTests::testEvaulate() { +void AnimTests::testClipEvaulate() { std::string id = "my clip node"; std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; @@ -81,6 +63,7 @@ void AnimTests::testEvaulate() { float loopFlag = true; auto vars = AnimVariantMap(); + vars.set("FalseVar", false); AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag); @@ -92,11 +75,46 @@ void AnimTests::testEvaulate() { QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); // does it pause at end? - clip.setLoopFlag(false); + clip.setLoopFlagVar("FalseVar"); clip.evaluate(vars, framesToSec(20.0f)); QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, EPSILON); } +void AnimTests::testClipEvaulateWithVars() { + std::string id = "my clip node"; + std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; + float startFrame = 2.0f; + float endFrame = 22.0f; + float timeScale = 1.0f; + float loopFlag = true; + + float startFrame2 = 22.0f; + float endFrame2 = 100.0f; + float timeScale2 = 1.2f; + bool loopFlag2 = false; + + auto vars = AnimVariantMap(); + vars.set("startFrame2", startFrame2); + vars.set("endFrame2", endFrame2); + vars.set("timeScale2", timeScale2); + vars.set("loopFlag2", loopFlag2); + + AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag); + clip.setStartFrameVar("startFrame2"); + clip.setEndFrameVar("endFrame2"); + clip.setTimeScaleVar("timeScale2"); + clip.setLoopFlagVar("loopFlag2"); + + clip.evaluate(vars, framesToSec(0.1f)); + + // verify that the values from the AnimVariantMap made it into the clipNode's + // internal state + QVERIFY(clip._startFrame == startFrame2); + QVERIFY(clip._endFrame == endFrame2); + QVERIFY(clip._timeScale == timeScale2); + QVERIFY(clip._loopFlag == loopFlag2); +} + void AnimTests::testLoader() { auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/8ba57a8f0a76f88b39a11f77f8d9df04af9cec95/test.json"); AnimNodeLoader loader(url); @@ -126,7 +144,7 @@ void AnimTests::testLoader() { QVERIFY(node->getType() == AnimNode::BlendLinearType); auto blend = std::static_pointer_cast(node); - QVERIFY(blend->getAlpha() == 0.5f); + QVERIFY(blend->_alpha == 0.5f); QVERIFY(node->getChildCount() == 3); @@ -140,18 +158,18 @@ void AnimTests::testLoader() { QVERIFY(nodes[2]->getChildCount() == 0); auto test01 = std::static_pointer_cast(nodes[0]); - QVERIFY(test01->getURL() == "test01.fbx"); - QVERIFY(test01->getStartFrame() == 1.0f); - QVERIFY(test01->getEndFrame() == 20.0f); - QVERIFY(test01->getTimeScale() == 1.0f); - QVERIFY(test01->getLoopFlag() == false); + QVERIFY(test01->_url == "test01.fbx"); + QVERIFY(test01->_startFrame == 1.0f); + QVERIFY(test01->_endFrame == 20.0f); + QVERIFY(test01->_timeScale == 1.0f); + QVERIFY(test01->_loopFlag == false); auto test02 = std::static_pointer_cast(nodes[1]); - QVERIFY(test02->getURL() == "test02.fbx"); - QVERIFY(test02->getStartFrame() == 2.0f); - QVERIFY(test02->getEndFrame() == 21.0f); - QVERIFY(test02->getTimeScale() == 0.9f); - QVERIFY(test02->getLoopFlag() == true); + QVERIFY(test02->_url == "test02.fbx"); + QVERIFY(test02->_startFrame == 2.0f); + QVERIFY(test02->_endFrame == 21.0f); + QVERIFY(test02->_timeScale == 0.9f); + QVERIFY(test02->_loopFlag == true); } void AnimTests::testVariant() { diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index 460caa067d..e667444657 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -18,8 +18,9 @@ class AnimTests : public QObject { private slots: void initTestCase(); void cleanupTestCase(); - void testAccessors(); - void testEvaulate(); + void testClipInternalState(); + void testClipEvaulate(); + void testClipEvaulateWithVars(); void testLoader(); void testVariant(); }; diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json new file mode 100644 index 0000000000..5dca9d3e16 --- /dev/null +++ b/tests/animation/src/data/avatar.json @@ -0,0 +1,37 @@ +{ + "version": "1.0", + "root": { + "id": "root", + "type": "overlay", + "data": { + "boneSet": "upperBody", + "alpha": 1.0 + }, + "children": [ + { + "id": "idle", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walk", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } +} From 0c02a338f220dbcceb607cca29563393ad45e80b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Aug 2015 20:57:01 -0700 Subject: [PATCH 28/52] Added support for setting Variants in the json file. For example: the avatar.json file was updated to use the "sine" Variant to drive the Overlay alpha parameter. --- interface/src/avatar/MyAvatar.cpp | 3 +- libraries/animation/src/AnimBlendLinear.h | 2 +- libraries/animation/src/AnimClip.h | 6 +-- libraries/animation/src/AnimNodeLoader.cpp | 54 ++++++++++++++++++++-- libraries/animation/src/AnimOverlay.h | 8 ++-- tests/animation/src/data/avatar.json | 3 +- 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 48fb2c1801..d19254892d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1232,7 +1232,8 @@ void MyAvatar::setupNewAnimationSystem() { //AnimDebugDraw::getInstance().addSkeleton("my-avatar", _animSkeleton, xform); // load the anim graph - auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/a939eadee4f36248776913d42891954a8d009158/avatar.json"); + // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/c4a9223e97b1d00b423b87542a2a57895ca72d21/avatar.json"); _animLoader.reset(new AnimNodeLoader(graphUrl)); connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { _animNode = nodeIn; diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index 85799ad3e3..2df1965064 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -31,9 +31,9 @@ public: virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; -protected: void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; } +protected: // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index e4651e0ece..45924c1eed 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -29,15 +29,15 @@ public: virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; -protected: - void loadURL(const std::string& url); - void setStartFrameVar(const std::string& startFrameVar) { _startFrameVar = startFrameVar; } void setEndFrameVar(const std::string& endFrameVar) { _endFrameVar = endFrameVar; } void setTimeScaleVar(const std::string& timeScaleVar) { _timeScaleVar = timeScaleVar; } void setLoopFlagVar(const std::string& loopFlagVar) { _loopFlagVar = loopFlagVar; } void setFrameVar(const std::string& frameVar) { _frameVar = frameVar; } +protected: + void loadURL(const std::string& url); + virtual void setCurrentFrameInternal(float frame) override; float accumulateTime(float frame, float dt) const; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 615dde1627..74bafb8630 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -54,6 +54,13 @@ static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { } \ QString NAME = NAME##_VAL.toString() +#define READ_OPTIONAL_STRING(NAME, JSON_OBJ) \ + auto NAME##_VAL = JSON_OBJ.value(#NAME); \ + QString NAME; \ + if (NAME##_VAL.isString()) { \ + NAME = NAME##_VAL.toString(); \ + } + #define READ_BOOL(NAME, JSON_OBJ, ID, URL) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isBool()) { \ @@ -137,14 +144,42 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& READ_FLOAT(timeScale, jsonObj, id, jsonUrl); READ_BOOL(loopFlag, jsonObj, id, jsonUrl); - return std::make_shared(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag); + READ_OPTIONAL_STRING(startFrameVar, jsonObj); + READ_OPTIONAL_STRING(endFrameVar, jsonObj); + READ_OPTIONAL_STRING(timeScaleVar, jsonObj); + READ_OPTIONAL_STRING(loopFlagVar, jsonObj); + + auto node = std::make_shared(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag); + + if (!startFrameVar.isEmpty()) { + node->setStartFrameVar(startFrameVar.toStdString()); + } + if (!endFrameVar.isEmpty()) { + node->setEndFrameVar(endFrameVar.toStdString()); + } + if (!timeScaleVar.isEmpty()) { + node->setTimeScaleVar(timeScaleVar.toStdString()); + } + if (!loopFlagVar.isEmpty()) { + node->setLoopFlagVar(loopFlagVar.toStdString()); + } + + return node; } static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_FLOAT(alpha, jsonObj, id, jsonUrl); - return std::make_shared(id.toStdString(), alpha); + READ_OPTIONAL_STRING(alphaVar, jsonObj); + + auto node = std::make_shared(id.toStdString(), alpha); + + if (!alphaVar.isEmpty()) { + node->setAlphaVar(alphaVar.toStdString()); + } + + return node; } static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { @@ -172,6 +207,7 @@ 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); auto boneSetEnum = stringToBoneSetEnum(boneSet); if (boneSetEnum == AnimOverlay::NumBoneSets) { @@ -179,7 +215,19 @@ static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QStri boneSetEnum = AnimOverlay::FullBodyBoneSet; } - return std::make_shared(id.toStdString(), boneSetEnum); + READ_OPTIONAL_STRING(boneSetVar, jsonObj); + READ_OPTIONAL_STRING(alphaVar, jsonObj); + + auto node = std::make_shared(id.toStdString(), boneSetEnum, alpha); + + if (!boneSetVar.isEmpty()) { + node->setBoneSetVar(boneSetVar.toStdString()); + } + if (!alphaVar.isEmpty()) { + node->setAlphaVar(alphaVar.toStdString()); + } + + return node; } AnimNodeLoader::AnimNodeLoader(const QUrl& url) : diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index 2fe25d430e..5940f0b2b3 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -21,7 +21,7 @@ class AnimOverlay : public AnimNode { public: - friend class AnimDebugDraw; + friend class AnimTests; enum BoneSet { FullBodyBoneSet = 0, @@ -42,12 +42,12 @@ public: virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; - protected: - void buildBoneSet(BoneSet boneSet); - void setBoneSetVar(const std::string& boneSetVar) { _boneSetVar = boneSetVar; } void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; } + protected: + void buildBoneSet(BoneSet boneSet); + // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 5dca9d3e16..f19ac04d56 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -5,7 +5,8 @@ "type": "overlay", "data": { "boneSet": "upperBody", - "alpha": 1.0 + "alpha": 1.0, + "alphaVar": "sine" }, "children": [ { From b7a9b54628690e4988434fca87133eaef8d796bf Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 26 Aug 2015 16:42:08 -0700 Subject: [PATCH 29/52] Better AnimDebugDraw rendering --- interface/src/avatar/MyAvatar.cpp | 15 +- interface/src/avatar/MyAvatar.h | 1 + libraries/animation/src/AnimClip.cpp | 2 +- libraries/animation/src/AnimSkeleton.cpp | 14 ++ libraries/animation/src/AnimSkeleton.h | 6 +- libraries/render-utils/src/AnimDebugDraw.cpp | 137 +++++++++++++++---- libraries/shared/src/GLMHelpers.cpp | 18 +++ libraries/shared/src/GLMHelpers.h | 8 ++ 8 files changed, 168 insertions(+), 33 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4c452dafc8..91eeb0c0d7 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1021,6 +1021,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _billboardValid = false; _skeletonModel.setVisibleInScene(true, scene); _headBoneSet.clear(); + teardownNewAnimationSystem(); } void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) { @@ -1227,9 +1228,11 @@ void MyAvatar::setupNewAnimationSystem() { } _animSkeleton = make_shared(joints); - // add it to the debug renderer, so we can see it. - //AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); - //AnimDebugDraw::getInstance().addSkeleton("my-avatar", _animSkeleton, xform); + // add skeleton to the debug renderer, so we can see it. + /* + AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); + AnimDebugDraw::getInstance().addSkeleton("my-avatar", _animSkeleton, xform); + */ // load the anim graph // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 @@ -1246,6 +1249,12 @@ void MyAvatar::setupNewAnimationSystem() { }); } +void MyAvatar::teardownNewAnimationSystem() { + _animSkeleton = nullptr; + _animLoader = nullptr; + _animNode = nullptr; +} + void MyAvatar::preRender(RenderArgs* renderArgs) { render::ScenePointer scene = Application::getInstance()->getMain3DScene(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3946edbaba..305b1ee05a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -202,6 +202,7 @@ signals: private: void setupNewAnimationSystem(); + void teardownNewAnimationSystem(); glm::vec3 getWorldBodyPosition() const; glm::quat getWorldBodyOrientation() const; diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 51e32143be..4dd89dd51a 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -145,7 +145,7 @@ void AnimClip::copyFromNetworkAnim() { int k = jointMap[j]; if (k >= 0 && k < skeletonJointCount) { // currently FBX animations only have rotation. - _anim[i][k].rot = _skeleton->getRelativeBindPose(j).rot * geom.animationFrames[i].rotations[j]; + _anim[i][k].rot = _skeleton->getRelativeBindPose(k).rot * geom.animationFrames[i].rotations[j]; } } } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 38c8bf8166..3d16f99473 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -27,6 +27,20 @@ glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const { return trans + (rot * (scale * rhs)); } +glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const { + return *this * rhs; +} + +// really slow +glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { + glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f); + glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f); + glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z); + glm::mat3 mat(xAxis, yAxis, zAxis); + glm::mat3 transInvMat = glm::inverse(glm::transpose(mat)); + return transInvMat * rhs; +} + AnimPose AnimPose::operator*(const AnimPose& rhs) const { return AnimPose(static_cast(*this) * static_cast(rhs)); } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 02ecd5ea7e..ef2d90fcea 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -20,8 +20,12 @@ struct AnimPose { AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {} static const AnimPose identity; - glm::vec3 operator*(const glm::vec3& rhs) const; + glm::vec3 xformPoint(const glm::vec3& rhs) const; + glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow + + glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint AnimPose operator*(const AnimPose& rhs) const; + AnimPose inverse() const; operator glm::mat4() const; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 7bfe7be98d..4f62a8407f 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -10,10 +10,11 @@ #include "animdebugdraw_vert.h" #include "animdebugdraw_frag.h" #include - -#include "AnimDebugDraw.h" #include "AbstractViewStateInterface.h" #include "RenderUtilsLogging.h" +#include "GLMHelpers.h" + +#include "AnimDebugDraw.h" struct Vertex { glm::vec3 pos; @@ -165,45 +166,120 @@ static const uint32_t green = toRGBA(0, 255, 0, 255); static const uint32_t blue = toRGBA(0, 0, 255, 255); static const uint32_t cyan = toRGBA(0, 128, 128, 255); -static void addWireframeSphereWithAxes(const AnimPose& rootPose, const AnimPose& pose, float radius, Vertex*& v) { +const int NUM_CIRCLE_SLICES = 24; +static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius, Vertex*& v) { + + AnimPose finalPose = rootPose * pose; glm::vec3 base = rootPose * pose.trans; + glm::vec3 xRing[NUM_CIRCLE_SLICES + 1]; // one extra for last index. + glm::vec3 yRing[NUM_CIRCLE_SLICES + 1]; + glm::vec3 zRing[NUM_CIRCLE_SLICES + 1]; + const float dTheta = (2.0f * (float)M_PI) / NUM_CIRCLE_SLICES; + for (int i = 0; i < NUM_CIRCLE_SLICES + 1; i++) { + float rCosTheta = radius * cos(dTheta * i); + float rSinTheta = radius * sin(dTheta * i); + xRing[i] = finalPose * glm::vec3(0.0f, rCosTheta, rSinTheta); + yRing[i] = finalPose * glm::vec3(rCosTheta, 0.0f, rSinTheta); + zRing[i] = finalPose *glm::vec3(rCosTheta, rSinTheta, 0.0f); + } + // x-axis v->pos = base; v->rgba = red; v++; - v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(radius, 0.0f, 0.0f)); + v->pos = finalPose * glm::vec3(radius * 2.0f, 0.0f, 0.0f); v->rgba = red; v++; + // x-ring + for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { + v->pos = xRing[i]; + v->rgba = red; + v++; + v->pos = xRing[i + 1]; + v->rgba = red; + v++; + } + // y-axis v->pos = base; v->rgba = green; v++; - v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(0.0f, radius, 0.0f)); + v->pos = finalPose * glm::vec3(0.0f, radius * 2.0f, 0.0f); v->rgba = green; v++; + // y-ring + for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { + v->pos = yRing[i]; + v->rgba = green; + v++; + v->pos = yRing[i + 1]; + v->rgba = green; + v++; + } + // z-axis v->pos = base; v->rgba = blue; v++; - v->pos = rootPose * (pose.trans + pose.rot * glm::vec3(0.0f, 0.0f, radius)); + v->pos = finalPose * glm::vec3(0.0f, 0.0f, radius * 2.0f); v->rgba = blue; v++; + + // z-ring + for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { + v->pos = zRing[i]; + v->rgba = blue; + v++; + v->pos = zRing[i + 1]; + v->rgba = blue; + v++; + } } -static void addWireframeBoneAxis(const AnimPose& rootPose, const AnimPose& pose, - const AnimPose& parentPose, float radius, Vertex*& v) { - v->pos = rootPose * pose.trans; - v->rgba = cyan; - v++; - v->pos = rootPose * parentPose.trans; - v->rgba = cyan; - v++; -} +static void addLink(const AnimPose& rootPose, const AnimPose& pose, + const AnimPose& parentPose, float radius, Vertex*& v) { + AnimPose pose0 = rootPose * parentPose; + AnimPose pose1 = rootPose * pose; + + glm::vec3 boneAxisWorld = glm::normalize(pose1.trans - pose0.trans); + glm::vec3 boneAxis0 = glm::normalize(pose0.inverse().xformVector(boneAxisWorld)); + glm::vec3 boneAxis1 = glm::normalize(pose1.inverse().xformVector(boneAxisWorld)); + + glm::vec3 uAxis, vAxis, wAxis; + generateBasisVectors(boneAxis0, glm::vec3(1, 0, 0), uAxis, vAxis, wAxis); + + const int NUM_BASE_CORNERS = 4; + glm::vec3 boneBaseCorners[NUM_BASE_CORNERS]; + boneBaseCorners[0] = pose0 * ((uAxis * radius) + (vAxis * radius) + (wAxis * radius)); + boneBaseCorners[1] = pose0 * ((uAxis * radius) - (vAxis * radius) + (wAxis * radius)); + boneBaseCorners[2] = pose0 * ((uAxis * radius) - (vAxis * radius) - (wAxis * radius)); + boneBaseCorners[3] = pose0 * ((uAxis * radius) + (vAxis * radius) - (wAxis * radius)); + + glm::vec3 boneTip = pose1 * (boneAxis1 * -radius); + + for (int i = 0; i < NUM_BASE_CORNERS; i++) { + v->pos = boneBaseCorners[i]; + v->rgba = cyan; + v++; + v->pos = boneBaseCorners[(i + 1) % NUM_BASE_CORNERS]; + v->rgba = cyan; + v++; + } + + for (int i = 0; i < NUM_BASE_CORNERS; i++) { + v->pos = boneBaseCorners[i]; + v->rgba = cyan; + v++; + v->pos = boneTip; + v->rgba = cyan; + v++; + } +} void AnimDebugDraw::update() { @@ -215,15 +291,20 @@ void AnimDebugDraw::update() { render::PendingChanges pendingChanges; pendingChanges.updateItem(_itemID, [&](AnimDebugDrawData& data) { + const size_t VERTICES_PER_BONE = (6 + (NUM_CIRCLE_SLICES * 2) * 3); + const size_t VERTICES_PER_LINK = 8 * 2; + + const float BONE_RADIUS = 0.0075f; + // figure out how many verts we will need. int numVerts = 0; for (auto&& iter : _skeletons) { AnimSkeleton::Pointer& skeleton = iter.second.first; - numVerts += skeleton->getNumJoints() * 6; + numVerts += skeleton->getNumJoints() * VERTICES_PER_BONE; for (int i = 0; i < skeleton->getNumJoints(); i++) { auto parentIndex = skeleton->getParentIndex(i); if (parentIndex >= 0) { - numVerts += 2; + numVerts += VERTICES_PER_LINK; } } } @@ -231,12 +312,12 @@ void AnimDebugDraw::update() { for (auto&& iter : _animNodes) { AnimNode::Pointer& animNode = iter.second.first; auto poses = animNode->getPosesInternal(); - numVerts += poses.size() * 6; + numVerts += poses.size() * VERTICES_PER_BONE; auto skeleton = animNode->getSkeleton(); for (size_t i = 0; i < poses.size(); i++) { auto parentIndex = skeleton->getParentIndex(i); if (parentIndex >= 0) { - numVerts += 2; + numVerts += VERTICES_PER_LINK; } } } @@ -251,16 +332,17 @@ void AnimDebugDraw::update() { for (int i = 0; i < skeleton->getNumJoints(); i++) { AnimPose pose = skeleton->getAbsoluteBindPose(i); - // draw axes - const float radius = 0.1f; - addWireframeSphereWithAxes(rootPose, pose, radius, v); + const float radius = BONE_RADIUS / (pose.scale.x * rootPose.scale.x); - // line to parent. + // draw bone + addBone(rootPose, pose, radius, v); + + // draw link to parent auto parentIndex = skeleton->getParentIndex(i); if (parentIndex >= 0) { assert(parentIndex < skeleton->getNumJoints()); AnimPose parentPose = skeleton->getAbsoluteBindPose(parentIndex); - addWireframeBoneAxis(rootPose, pose, parentPose, radius, v); + addLink(rootPose, pose, parentPose, radius, v); } } } @@ -284,14 +366,13 @@ void AnimDebugDraw::update() { absAnimPose[i] = poses[i]; } - // draw axes - const float radius = 0.1f; - addWireframeSphereWithAxes(rootPose, absAnimPose[i], radius, v); + const float radius = BONE_RADIUS / (absAnimPose[i].scale.x * rootPose.scale.x); + addBone(rootPose, absAnimPose[i], radius, v); if (parentIndex >= 0) { assert((size_t)parentIndex < poses.size()); // draw line to parent - addWireframeBoneAxis(rootPose, absAnimPose[i], absAnimPose[parentIndex], radius, v); + addLink(rootPose, absAnimPose[i], absAnimPose[parentIndex], radius, v); } } } diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index afb3f1b38c..a81abc62ae 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -411,3 +411,21 @@ glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p) { return glm::vec3(temp.x / temp.w, temp.y / temp.w, temp.z / temp.w); } +glm::vec3 transformVector(const glm::mat4& m, const glm::vec3& v) { + glm::mat3 rot(m); + return glm::inverse(glm::transpose(rot)) * v; +} + +void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& secondaryAxis, + glm::vec3& uAxisOut, glm::vec3& vAxisOut, glm::vec3& wAxisOut) { + + uAxisOut = glm::normalize(primaryAxis); + wAxisOut = glm::cross(uAxisOut, secondaryAxis); + if (glm::length(wAxisOut) > 0.0f) { + wAxisOut = glm::normalize(wAxisOut); + } else { + wAxisOut = glm::normalize(glm::cross(uAxisOut, glm::vec3(0, 1, 0))); + } + vAxisOut = glm::cross(wAxisOut, uAxisOut); +} + diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index d6e6b5b676..91f487d1fd 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -198,5 +198,13 @@ glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p); glm::quat cancelOutRollAndPitch(const glm::quat& q); glm::mat4 cancelOutRollAndPitch(const glm::mat4& m); glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p); +glm::vec3 transformVector(const glm::mat4& m, const glm::vec3& v); + +// Calculate an orthogonal basis from a primary and secondary axis. +// The uAxis, vAxis & wAxis will form an orthognal basis. +// The primary axis will be the uAxis. +// The vAxis will be as close as possible to to the secondary axis. +void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& secondaryAxis, + glm::vec3& uAxisOut, glm::vec3& vAxisOut, glm::vec3& wAxisOut); #endif // hifi_GLMHelpers_h From 778521f66410436077b51efddb4670e8b982e171 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 27 Aug 2015 10:21:23 -0700 Subject: [PATCH 30/52] Improvement to bone link rendering when bones are large. --- libraries/render-utils/src/AnimDebugDraw.cpp | 70 +++++++++++++------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 4f62a8407f..7bbf656f35 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -250,34 +250,54 @@ static void addLink(const AnimPose& rootPose, const AnimPose& pose, glm::vec3 boneAxis0 = glm::normalize(pose0.inverse().xformVector(boneAxisWorld)); glm::vec3 boneAxis1 = glm::normalize(pose1.inverse().xformVector(boneAxisWorld)); - glm::vec3 uAxis, vAxis, wAxis; - generateBasisVectors(boneAxis0, glm::vec3(1, 0, 0), uAxis, vAxis, wAxis); - - const int NUM_BASE_CORNERS = 4; - glm::vec3 boneBaseCorners[NUM_BASE_CORNERS]; - boneBaseCorners[0] = pose0 * ((uAxis * radius) + (vAxis * radius) + (wAxis * radius)); - boneBaseCorners[1] = pose0 * ((uAxis * radius) - (vAxis * radius) + (wAxis * radius)); - boneBaseCorners[2] = pose0 * ((uAxis * radius) - (vAxis * radius) - (wAxis * radius)); - boneBaseCorners[3] = pose0 * ((uAxis * radius) + (vAxis * radius) - (wAxis * radius)); - + glm::vec3 boneBase = pose0 * (boneAxis0 * radius); glm::vec3 boneTip = pose1 * (boneAxis1 * -radius); - for (int i = 0; i < NUM_BASE_CORNERS; i++) { - v->pos = boneBaseCorners[i]; - v->rgba = cyan; - v++; - v->pos = boneBaseCorners[(i + 1) % NUM_BASE_CORNERS]; - v->rgba = cyan; - v++; - } + const int NUM_BASE_CORNERS = 4; - for (int i = 0; i < NUM_BASE_CORNERS; i++) { - v->pos = boneBaseCorners[i]; - v->rgba = cyan; - v++; - v->pos = boneTip; - v->rgba = cyan; - v++; + // make sure there's room between the two bones to draw a nice bone link. + if (glm::dot(boneTip - pose0.trans, boneAxisWorld) > glm::dot(boneBase - pose0.trans, boneAxisWorld)) { + + // there is room, so lets draw a nice bone + + glm::vec3 uAxis, vAxis, wAxis; + generateBasisVectors(boneAxis0, glm::vec3(1, 0, 0), uAxis, vAxis, wAxis); + + glm::vec3 boneBaseCorners[NUM_BASE_CORNERS]; + boneBaseCorners[0] = pose0 * ((uAxis * radius) + (vAxis * radius) + (wAxis * radius)); + boneBaseCorners[1] = pose0 * ((uAxis * radius) - (vAxis * radius) + (wAxis * radius)); + boneBaseCorners[2] = pose0 * ((uAxis * radius) - (vAxis * radius) - (wAxis * radius)); + boneBaseCorners[3] = pose0 * ((uAxis * radius) + (vAxis * radius) - (wAxis * radius)); + + for (int i = 0; i < NUM_BASE_CORNERS; i++) { + v->pos = boneBaseCorners[i]; + v->rgba = cyan; + v++; + v->pos = boneBaseCorners[(i + 1) % NUM_BASE_CORNERS]; + v->rgba = cyan; + v++; + } + + for (int i = 0; i < NUM_BASE_CORNERS; i++) { + v->pos = boneBaseCorners[i]; + v->rgba = cyan; + v++; + v->pos = boneTip; + v->rgba = cyan; + v++; + } + } else { + // There's no room between the bones to draw the link. + // just draw a line between the two bone centers. + // We add the same line multiple times, so the vertex count is correct. + for (int i = 0; i < NUM_BASE_CORNERS * 2; i++) { + v->pos = pose0.trans; + v->rgba = cyan; + v++; + v->pos = pose1.trans; + v->rgba = cyan; + v++; + } } } From 637e3b0a155a8f85491ca8bdd2f97aeff2f0a393 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 27 Aug 2015 10:41:01 -0700 Subject: [PATCH 31/52] Added triggers to AnimVariantMap. --- libraries/animation/src/AnimVariant.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 36d189f85c..7c14bfd096 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -14,6 +14,7 @@ #include #include #include +#include class AnimVariant { public: @@ -69,8 +70,11 @@ class AnimVariantMap { public: bool lookup(const std::string& key, bool defaultValue) const { + // check triggers first, then map if (key.empty()) { return defaultValue; + } else if (_triggers.find(key) != _triggers.end()) { + return true; } else { auto iter = _map.find(key); return iter != _map.end() ? iter->second.getBool() : defaultValue; @@ -129,8 +133,12 @@ public: void set(const std::string& key, const glm::quat& value) { _map[key] = AnimVariant(value); } void set(const std::string& key, const glm::mat4& value) { _map[key] = AnimVariant(value); } + void setTrigger(const std::string& key) { _triggers.insert(key); } + void clearTirggers() { _triggers.clear(); } + protected: std::map _map; + std::set _triggers; }; #endif // hifi_AnimVariant_h From 3286a32afc543c9b2d1682f5ddf3e415054a45f9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 27 Aug 2015 20:41:53 -0700 Subject: [PATCH 32/52] Initial version of AnimStateMachine. No interpolation support, but basic avatar.json is working with two states and two transitions between them. --- interface/src/avatar/MyAvatar.cpp | 11 +- libraries/animation/src/AnimBlendLinear.cpp | 2 +- libraries/animation/src/AnimClip.cpp | 2 +- libraries/animation/src/AnimNode.h | 18 +- libraries/animation/src/AnimNodeLoader.cpp | 200 ++++++++++++++++--- libraries/animation/src/AnimOverlay.cpp | 2 +- libraries/animation/src/AnimSkeleton.h | 6 +- libraries/animation/src/AnimStateMachine.cpp | 94 +++++++++ libraries/animation/src/AnimStateMachine.h | 137 +++++++++---- libraries/animation/src/AnimVariant.h | 80 +++++--- tests/animation/src/data/avatar.json | 24 ++- 11 files changed, 453 insertions(+), 123 deletions(-) create mode 100644 libraries/animation/src/AnimStateMachine.cpp diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 91eeb0c0d7..4e6aba85f9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -166,6 +166,15 @@ void MyAvatar::update(float deltaTime) { if (_animNode) { static float t = 0.0f; _animVars.set("sine", 0.5f * sin(t) + 0.5f); + + if (glm::length(getVelocity()) > 0.01) { + _animVars.set("isMoving", true); + _animVars.set("isNotMoving", false); + } else { + _animVars.set("isMoving", false); + _animVars.set("isNotMoving", true); + } + t += deltaTime; _animNode->evaluate(_animVars, deltaTime); } @@ -1236,7 +1245,7 @@ void MyAvatar::setupNewAnimationSystem() { // load the anim graph // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 - auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/c4a9223e97b1d00b423b87542a2a57895ca72d21/avatar.json"); + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/c684000794675bc84ed63efefc21870e47c58d6a/avatar.json"); _animLoader.reset(new AnimNodeLoader(graphUrl)); connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { _animNode = nodeIn; diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index 90dd85f313..c505984b87 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -13,7 +13,7 @@ #include "AnimUtil.h" AnimBlendLinear::AnimBlendLinear(const std::string& id, float alpha) : - AnimNode(AnimNode::BlendLinearType, id), + AnimNode(AnimNode::Type::BlendLinear, id), _alpha(alpha) { } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 4dd89dd51a..e4eb63f23a 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -13,7 +13,7 @@ #include "AnimUtil.h" AnimClip::AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : - AnimNode(AnimNode::ClipType, id), + AnimNode(AnimNode::Type::Clip, id), _startFrame(startFrame), _endFrame(endFrame), _timeScale(timeScale), diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index b7c7826540..b6eedf9b2e 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -25,7 +25,7 @@ class QJsonObject; // Base class for all elements in the animation blend tree. // It provides the following categories of functions: // -// * id getter, id is a string name useful for debugging and searching. +// * id getter, id is used to identify a node, useful for debugging and node searching. // * type getter, helpful for determining the derived type of this node. // * hierarchy accessors, for adding, removing and iterating over child AnimNodes // * skeleton accessors, the skeleton is from the model whose bones we are going to manipulate @@ -33,15 +33,17 @@ class QJsonObject; class AnimNode { public: - friend class AnimDebugDraw; - - enum Type { - ClipType = 0, - BlendLinearType, - OverlayType, + enum class Type { + Clip = 0, + BlendLinear, + Overlay, + StateMachine, NumTypes }; - typedef std::shared_ptr Pointer; + using Pointer = std::shared_ptr; + + friend class AnimDebugDraw; + friend void buildChildMap(std::map& map, Pointer node); AnimNode(Type type, const std::string& id) : _type(type), _id(id) {} virtual ~AnimNode() {} diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 74bafb8630..e41c3550ad 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -18,31 +18,52 @@ #include "AnimationLogging.h" #include "AnimOverlay.h" #include "AnimNodeLoader.h" +#include "AnimStateMachine.h" -struct TypeInfo { - AnimNode::Type type; - const char* str; -}; - -// This will result in a compile error if someone adds a new -// item to the AnimNode::Type enum. This is by design. -static TypeInfo typeInfoArray[AnimNode::NumTypes] = { - { AnimNode::ClipType, "clip" }, - { AnimNode::BlendLinearType, "blendLinear" }, - { AnimNode::OverlayType, "overlay" } -}; - -typedef AnimNode::Pointer (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +// 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 NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { - loadClipNode, - loadBlendLinearNode, - loadOverlayNode -}; +// 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; } +static bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); + +static const char* animNodeTypeToString(AnimNode::Type type) { + switch (type) { + case AnimNode::Type::Clip: return "clip"; + case AnimNode::Type::BlendLinear: return "blendLinear"; + case AnimNode::Type::Overlay: return "overlay"; + case AnimNode::Type::StateMachine: return "stateMachine"; + }; + return nullptr; +} + +static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { + switch (type) { + case AnimNode::Type::Clip: return loadClipNode; + case AnimNode::Type::BlendLinear: return loadBlendLinearNode; + case AnimNode::Type::Overlay: return loadOverlayNode; + case AnimNode::Type::StateMachine: return loadStateMachineNode; + }; + return nullptr; +} + +static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { + switch (type) { + case AnimNode::Type::Clip: return processClipNode; + case AnimNode::Type::BlendLinear: return processBlendLinearNode; + case AnimNode::Type::Overlay: return processOverlayNode; + case AnimNode::Type::StateMachine: return processStateMachineNode; + }; + return nullptr; +} #define READ_STRING(NAME, JSON_OBJ, ID, URL) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ @@ -82,12 +103,15 @@ static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = { float NAME = (float)NAME##_VAL.toDouble() static AnimNode::Type stringToEnum(const QString& str) { - for (int i = 0; i < AnimNode::NumTypes; i++ ) { - if (str == typeInfoArray[i].str) { - return typeInfoArray[i].type; + // O(n), move to map when number of types becomes large. + const int NUM_TYPES = static_cast(AnimNode::Type::NumTypes); + for (int i = 0; i < NUM_TYPES; i++ ) { + AnimNode::Type type = static_cast(i); + if (str == animNodeTypeToString(type)) { + return type; } } - return AnimNode::NumTypes; + return AnimNode::Type::NumTypes; } static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) { @@ -105,7 +129,7 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr } QString typeStr = typeVal.toString(); AnimNode::Type type = stringToEnum(typeStr); - if (type == AnimNode::NumTypes) { + if (type == AnimNode::Type::NumTypes) { qCCritical(animation) << "AnimNodeLoader, unknown node type" << typeStr << ", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } @@ -117,23 +141,28 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr } auto dataObj = dataValue.toObject(); - assert((int)type >= 0 && type < AnimNode::NumTypes); - auto node = nodeLoaderFuncs[type](dataObj, id, jsonUrl); + assert((int)type >= 0 && type < AnimNode::Type::NumTypes); + auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl); auto childrenValue = jsonObj.value("children"); if (!childrenValue.isArray()) { qCCritical(animation) << "AnimNodeLoader, bad array \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } - auto childrenAry = childrenValue.toArray(); - for (const auto& childValue : childrenAry) { + auto childrenArray = childrenValue.toArray(); + for (const auto& childValue : childrenArray) { if (!childValue.isObject()) { qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } node->addChild(loadNode(childValue.toObject(), jsonUrl)); } - return node; + + if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) { + return node; + } else { + return nullptr; + } } static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { @@ -230,6 +259,119 @@ static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QStri return node; } +static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + auto node = std::make_shared(id.toStdString()); + return node; +} + +static void buildChildMap(std::map& map, AnimNode::Pointer node) { + for ( auto child : node->_children ) { + map.insert(std::pair(child->_id, child)); + } +} + +static bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) { + auto smNode = std::static_pointer_cast(node); + assert(smNode); + + READ_STRING(currentState, jsonObj, nodeId, jsonUrl); + + auto statesValue = jsonObj.value("states"); + if (!statesValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in stateMachine node, id =" << nodeId << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + + // build a map for all children by name. + std::map childMap; + buildChildMap(childMap, node); + + // first pass parse all the states and build up the state and transition map. + using StringPair = std::pair; + using TransitionMap = std::multimap; + TransitionMap transitionMap; + + using StateMap = std::map; + StateMap stateMap; + + auto statesArray = statesValue.toArray(); + for (const auto& stateValue : statesArray) { + if (!stateValue.isObject()) { + qCCritical(animation) << "AnimNodeLoader, bad state object in \"states\", id =" << nodeId << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + auto stateObj = stateValue.toObject(); + + READ_STRING(id, stateObj, nodeId, jsonUrl); + READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl); + READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl); + + READ_OPTIONAL_STRING(interpTargetVar, stateObj); + READ_OPTIONAL_STRING(interpDurationVar, stateObj); + + auto stdId = id.toStdString(); + + 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; + } + + auto statePtr = std::make_shared(stdId, iter->second, interpTarget, interpDuration); + assert(statePtr); + + if (!interpTargetVar.isEmpty()) { + statePtr->setInterpTargetVar(interpTargetVar.toStdString()); + } + if (!interpDurationVar.isEmpty()) { + statePtr->setInterpDurationVar(interpDurationVar.toStdString()); + } + + smNode->addState(statePtr); + stateMap.insert(StateMap::value_type(statePtr->getID(), statePtr)); + + 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; + } + + 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; + } + auto transitionObj = transitionValue.toObject(); + + READ_STRING(var, transitionObj, nodeId, jsonUrl); + READ_STRING(state, transitionObj, nodeId, jsonUrl); + + transitionMap.insert(TransitionMap::value_type(statePtr, StringPair(var.toStdString(), state.toStdString()))); + } + } + + // second pass: now iterate thru all transitions and add them to the appropriate states. + for (auto& transition : transitionMap) { + AnimStateMachine::State::Pointer srcState = transition.first; + auto iter = stateMap.find(transition.second.second); + if (iter != stateMap.end()) { + 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; + } + } + + auto iter = stateMap.find(currentState.toStdString()); + if (iter == stateMap.end()) { + qCCritical(animation) << "AnimNodeLoader, bad currentState =" << currentState << "could not find child node" << "id =" << nodeId << "url = " << jsonUrl.toDisplayString(); + } + smNode->setCurrentState(iter->second); + + return true; +} + AnimNodeLoader::AnimNodeLoader(const QUrl& url) : _url(url), _resource(nullptr) { diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index 97db1711ca..21c893810a 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -12,7 +12,7 @@ #include AnimOverlay::AnimOverlay(const std::string& id, BoneSet boneSet, float alpha) : - AnimNode(AnimNode::OverlayType, id), _boneSet(boneSet), _alpha(alpha) { + AnimNode(AnimNode::Type::Overlay, id), _boneSet(boneSet), _alpha(alpha) { } AnimOverlay::~AnimOverlay() { diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index ef2d90fcea..3faade0dbd 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -39,12 +39,12 @@ inline QDebug operator<<(QDebug debug, const AnimPose& pose) { return debug; } -typedef std::vector AnimPoseVec; +using AnimPoseVec = std::vector; class AnimSkeleton { public: - typedef std::shared_ptr Pointer; - typedef std::shared_ptr ConstPointer; + using Pointer = std::shared_ptr; + using ConstPointer = std::shared_ptr; AnimSkeleton(const std::vector& joints); int nameToJointIndex(const QString& jointName) const; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp new file mode 100644 index 0000000000..2faea905de --- /dev/null +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -0,0 +1,94 @@ +// +// AnimStateMachine.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 "AnimStateMachine.h" +#include "AnimationLogging.h" + +AnimStateMachine::AnimStateMachine(const std::string& id) : + AnimNode(AnimNode::Type::StateMachine, id) { + +} + +AnimStateMachine::~AnimStateMachine() { + +} + + + +const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, float dt) { + + std::string desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); + if (_currentState->getID() != desiredStateID) { + // switch states + bool foundState = false; + for (auto& state : _states) { + if (state->getID() == desiredStateID) { + switchState(state); + foundState = true; + break; + } + } + if (!foundState) { + qCCritical(animation) << "AnimStateMachine could not find state =" << desiredStateID.c_str() << ", referenced by _currentStateVar =" << _currentStateVar.c_str(); + } + } + + // evaluate currentState transitions + auto desiredState = evaluateTransitions(animVars); + if (desiredState != _currentState) { + switchState(desiredState); + } + + if (_duringInterp) { + // TODO: do interpolation + /* + const float FRAMES_PER_SECOND = 30.0f; + // blend betwen poses + blend(_poses.size(), _prevPose, _nextPose, _alpha, &_poses[0]); + */ + } else { + // eval current state + assert(_currentState); + auto currentStateNode = _currentState->getNode(); + assert(currentStateNode); + _poses = currentStateNode->evaluate(animVars, dt); + } + + qCDebug(animation) << "StateMachine::evalute"; + + return _poses; +} + +void AnimStateMachine::setCurrentState(State::Pointer state) { + _currentState = state; +} + +void AnimStateMachine::addState(State::Pointer state) { + _states.push_back(state); +} + +void AnimStateMachine::switchState(State::Pointer desiredState) { + qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID().c_str() << "->" << desiredState->getID().c_str(); + // TODO: interp. + _currentState = desiredState; +} + +AnimStateMachine::State::Pointer AnimStateMachine::evaluateTransitions(const AnimVariantMap& animVars) const { + assert(_currentState); + for (auto& transition : _currentState->_transitions) { + if (animVars.lookup(transition._var, false)) { + return transition._state; + } + } + return _currentState; +} + +const AnimPoseVec& AnimStateMachine::getPosesInternal() const { + return _poses; +} diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index 65c1bafdca..148429e722 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -1,66 +1,117 @@ +// +// AnimStateMachine.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_AnimStateMachine_h #define hifi_AnimStateMachine_h -// AJT: post-pone state machine work. -#if 0 +#include +#include +#include "AnimNode.h" + class AnimStateMachine : public AnimNode { public: - friend class AnimDebugDraw; + friend class AnimNodeLoader; + friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); - AnimStateMachine(const std::string& id, float alpha); +protected: + class State { + public: + friend AnimStateMachine; + 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; + + class Transition { + public: + friend AnimStateMachine; + Transition(const std::string& var, State::Pointer state) : _var(var), _state(state) {} + protected: + std::string _var; + State::Pointer _state; + }; + + State(const std::string& id, AnimNode::Pointer node, float interpTarget, float interpDuration) : + _id(id), + _node(node), + _interpTarget(interpTarget), + _interpDuration(interpDuration) {} + + void setInterpTargetVar(const std::string& interpTargetVar) { _interpTargetVar = interpTargetVar; } + void setInterpDurationVar(const std::string& interpDurationVar) { _interpDurationVar = interpDurationVar; } + + AnimNode::Pointer getNode() const { return _node; } + const std::string& getID() const { return _id; } + + protected: + + void setInterpTarget(float interpTarget) { _interpTarget = interpTarget; } + void setInterpDuration(float interpDuration) { _interpDuration = interpDuration; } + + void addTransition(const Transition& transition) { _transitions.push_back(transition); } + + std::string _id; + AnimNode::Pointer _node; + float _interpTarget; // frames + float _interpDuration; // frames + + std::string _interpTargetVar; + std::string _interpDurationVar; + + std::vector _transitions; + + private: + // no copies + State(const State&) = delete; + State& operator=(const State&) = delete; + }; + +public: + + AnimStateMachine(const std::string& id); virtual ~AnimStateMachine() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; - class AnimTransition; - - class AnimState { - public: - typedef std::shared_ptr Pointer; - AnimState(const std::string& name, AnimNode::Pointer node, float interpFrame, float interpDuration) : - _name(name), - _node(node), - _interpFrame(interpFrame), - _interpDuration(interpDuration) {} - protected: - std::string _name; - AnimNode::Pointer _node; - float _interpFrame; - float _interpDuration; - std::vector _transitions; - private: - // no copies - AnimState(const AnimState&) = delete; - AnimState& operator=(const AnimState&) = delete; - }; - - class AnimTransition { - AnimTransition(const std::string& trigger, AnimState::Pointer state) : _trigger(trigger), _state(state) {} - protected: - std::string _trigger; - AnimState::Pointer _state; - }; - - void addState(AnimState::Pointer state); - void removeState(AnimState::Pointer state); - void setCurrentState(AnimState::Pointer state); + void setCurrentStateVar(std::string& currentStateVar) { _currentStateVar = currentStateVar; } protected: + + void setCurrentState(State::Pointer state); + + void addState(State::Pointer state); + + void switchState(State::Pointer desiredState); + State::Pointer evaluateTransitions(const AnimVariantMap& animVars) const; + // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; AnimPoseVec _poses; - std::vector _states; - AnimState::Pointer _currentState; - AnimState::Pointer _defaultState; + // interpolation state + bool _duringInterp = false; + float _interpFrame; + float _interpDuration; + float _alpha; + AnimPoseVec _prevPoses; + AnimPoseVec _nextPoses; + + State::Pointer _currentState; + std::vector _states; + + std::string _currentStateVar; private: // no copies - AnimStateMachine(const AnimBlendLinear&) = delete; - AnimStateMachine& operator=(const AnimBlendLinear&) = delete; + AnimStateMachine(const AnimStateMachine&) = delete; + AnimStateMachine& operator=(const AnimStateMachine&) = delete; }; -#endif -#endif // hifi_AnimBlendLinear_h +#endif // hifi_AnimStateMachine_h diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 7c14bfd096..15ab7e94b1 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -18,47 +18,53 @@ class AnimVariant { public: - enum Type { - BoolType = 0, - IntType, - FloatType, - Vec3Type, - QuatType, - Mat4Type, + enum class Type { + Bool = 0, + Int, + Float, + Vec3, + Quat, + Mat4, + String, NumTypes }; - AnimVariant() : _type(BoolType) { memset(&_val, 0, sizeof(_val)); } - AnimVariant(bool value) : _type(BoolType) { _val.boolVal = value; } - AnimVariant(int value) : _type(IntType) { _val.intVal = value; } - AnimVariant(float value) : _type(FloatType) { _val.floats[0] = value; } - AnimVariant(const glm::vec3& value) : _type(Vec3Type) { *reinterpret_cast(&_val) = value; } - AnimVariant(const glm::quat& value) : _type(QuatType) { *reinterpret_cast(&_val) = value; } - AnimVariant(const glm::mat4& value) : _type(Mat4Type) { *reinterpret_cast(&_val) = value; } + AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); } + AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; } + AnimVariant(int value) : _type(Type::Int) { _val.intVal = value; } + AnimVariant(float value) : _type(Type::Float) { _val.floats[0] = value; } + AnimVariant(const glm::vec3& value) : _type(Type::Vec3) { *reinterpret_cast(&_val) = value; } + AnimVariant(const glm::quat& value) : _type(Type::Quat) { *reinterpret_cast(&_val) = value; } + AnimVariant(const glm::mat4& value) : _type(Type::Mat4) { *reinterpret_cast(&_val) = value; } + AnimVariant(const std::string& value) : _type(Type::String) { _stringVal = value; } - bool isBool() const { return _type == BoolType; } - bool isInt() const { return _type == IntType; } - bool isFloat() const { return _type == FloatType; } - bool isVec3() const { return _type == Vec3Type; } - bool isQuat() const { return _type == QuatType; } - bool isMat4() const { return _type == Mat4Type; } + bool isBool() const { return _type == Type::Bool; } + bool isInt() const { return _type == Type::Int; } + bool isFloat() const { return _type == Type::Float; } + bool isVec3() const { return _type == Type::Vec3; } + bool isQuat() const { return _type == Type::Quat; } + bool isMat4() const { return _type == Type::Mat4; } + bool isString() const { return _type == Type::String; } - void setBool(bool value) { assert(_type == BoolType); _val.boolVal = value; } - void setInt(int value) { assert(_type == IntType); _val.intVal = value; } - void setFloat(float value) { assert(_type == FloatType); _val.floats[0] = value; } - void setVec3(const glm::vec3& value) { assert(_type == Vec3Type); *reinterpret_cast(&_val) = value; } - void setQuat(const glm::quat& value) { assert(_type == QuatType); *reinterpret_cast(&_val) = value; } - void setMat4(const glm::mat4& value) { assert(_type == Mat4Type); *reinterpret_cast(&_val) = value; } + void setBool(bool value) { assert(_type == Type::Bool); _val.boolVal = value; } + void setInt(int value) { assert(_type == Type::Int); _val.intVal = value; } + void setFloat(float value) { assert(_type == Type::Float); _val.floats[0] = value; } + void setVec3(const glm::vec3& value) { assert(_type == Type::Vec3); *reinterpret_cast(&_val) = value; } + void setQuat(const glm::quat& value) { assert(_type == Type::Quat); *reinterpret_cast(&_val) = value; } + void setMat4(const glm::mat4& value) { assert(_type == Type::Mat4); *reinterpret_cast(&_val) = value; } + void setString(const std::string& value) { assert(_type == Type::String); _stringVal = value; } - bool getBool() const { assert(_type == BoolType); return _val.boolVal; } - int getInt() const { assert(_type == IntType); return _val.intVal; } - float getFloat() const { assert(_type == FloatType); return _val.floats[0]; } - const glm::vec3& getVec3() const { assert(_type == Vec3Type); return *reinterpret_cast(&_val); } - const glm::quat& getQuat() const { assert(_type == QuatType); return *reinterpret_cast(&_val); } - const glm::mat4& getMat4() const { assert(_type == Mat4Type); return *reinterpret_cast(&_val); } + bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; } + int getInt() const { assert(_type == Type::Int); return _val.intVal; } + float getFloat() const { assert(_type == Type::Float); return _val.floats[0]; } + const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast(&_val); } + const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast(&_val); } + const glm::mat4& getMat4() const { assert(_type == Type::Mat4); return *reinterpret_cast(&_val); } + const std::string& getString() const { assert(_type == Type::String); return _stringVal; } protected: Type _type; + std::string _stringVal; union { bool boolVal; int intVal; @@ -126,12 +132,22 @@ public: } } + const std::string& lookup(const std::string& key, const std::string& defaultValue) const { + if (key.empty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? iter->second.getString() : defaultValue; + } + } + void set(const std::string& key, bool value) { _map[key] = AnimVariant(value); } void set(const std::string& key, int value) { _map[key] = AnimVariant(value); } void set(const std::string& key, float value) { _map[key] = AnimVariant(value); } void set(const std::string& key, const glm::vec3& value) { _map[key] = AnimVariant(value); } void set(const std::string& key, const glm::quat& value) { _map[key] = AnimVariant(value); } void set(const std::string& key, const glm::mat4& value) { _map[key] = AnimVariant(value); } + void set(const std::string& key, const std::string& value) { _map[key] = AnimVariant(value); } void setTrigger(const std::string& key) { _triggers.insert(key); } void clearTirggers() { _triggers.clear(); } diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index f19ac04d56..7184e67a44 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -2,11 +2,27 @@ "version": "1.0", "root": { "id": "root", - "type": "overlay", + "type": "stateMachine", "data": { - "boneSet": "upperBody", - "alpha": 1.0, - "alphaVar": "sine" + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isMoving", "state": "walk" } + ] + }, + { + "id": "walk", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isNotMoving", "state": "idle" } + ] + } + ] }, "children": [ { From 19e91bb392bb0e720a271c6bbbdec51e0f914b13 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 27 Aug 2015 21:26:31 -0700 Subject: [PATCH 33/52] Added basic interpolation support to AnimStateMachine --- interface/src/avatar/MyAvatar.cpp | 2 +- libraries/animation/src/AnimBlendLinear.cpp | 2 +- libraries/animation/src/AnimClip.cpp | 2 +- libraries/animation/src/AnimNode.h | 1 + libraries/animation/src/AnimOverlay.cpp | 2 +- libraries/animation/src/AnimStateMachine.cpp | 54 ++++++++++++-------- libraries/animation/src/AnimStateMachine.h | 7 ++- 7 files changed, 42 insertions(+), 28 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4e6aba85f9..d1d62333a7 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1245,7 +1245,7 @@ void MyAvatar::setupNewAnimationSystem() { // load the anim graph // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 - auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/c684000794675bc84ed63efefc21870e47c58d6a/avatar.json"); + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/250ce1f207e23c74694351f04367063cf1269f94/avatar.json"); _animLoader.reset(new AnimNodeLoader(graphUrl)); connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { _animNode = nodeIn; diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index c505984b87..102d0e4df6 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -48,7 +48,7 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { _poses.resize(prevPoses.size()); - blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); + ::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); } } } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index e4eb63f23a..4f3ea54e34 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -61,7 +61,7 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt) const AnimPoseVec& nextFrame = _anim[nextIndex]; float alpha = glm::fract(_frame); - blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]); + ::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]); } return _poses; diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index b6eedf9b2e..0b521211a2 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -44,6 +44,7 @@ public: friend class AnimDebugDraw; friend void buildChildMap(std::map& map, Pointer node); + friend class AnimStateMachine; AnimNode(Type type, const std::string& id) : _type(type), _id(id) {} virtual ~AnimNode() {} diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index 21c893810a..81df5811d7 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -57,7 +57,7 @@ const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, float d for (size_t i = 0; i < _poses.size(); i++) { float alpha = _boneSetVec[i] * _alpha; - blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]); + ::blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]); } } } diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 2faea905de..43bb305797 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -8,6 +8,7 @@ // #include "AnimStateMachine.h" +#include "AnimUtil.h" #include "AnimationLogging.h" AnimStateMachine::AnimStateMachine(const std::string& id) : @@ -19,8 +20,6 @@ AnimStateMachine::~AnimStateMachine() { } - - const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, float dt) { std::string desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); @@ -29,7 +28,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl bool foundState = false; for (auto& state : _states) { if (state->getID() == desiredStateID) { - switchState(state); + switchState(animVars, state); foundState = true; break; } @@ -42,26 +41,27 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl // evaluate currentState transitions auto desiredState = evaluateTransitions(animVars); if (desiredState != _currentState) { - switchState(desiredState); + switchState(animVars, desiredState); } + + assert(_currentState); + auto currentStateNode = _currentState->getNode(); + assert(currentStateNode); + if (_duringInterp) { - // TODO: do interpolation - /* - const float FRAMES_PER_SECOND = 30.0f; - // blend betwen poses - blend(_poses.size(), _prevPose, _nextPose, _alpha, &_poses[0]); - */ - } else { - // eval current state - assert(_currentState); - auto currentStateNode = _currentState->getNode(); - assert(currentStateNode); + _alpha += _alphaVel * dt; + if (_alpha < 1.0f) { + ::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]); + } else { + _duringInterp = false; + _prevPoses.clear(); + _nextPoses.clear(); + } + } + if (!_duringInterp) { _poses = currentStateNode->evaluate(animVars, dt); } - - qCDebug(animation) << "StateMachine::evalute"; - return _poses; } @@ -73,9 +73,23 @@ void AnimStateMachine::addState(State::Pointer state) { _states.push_back(state); } -void AnimStateMachine::switchState(State::Pointer desiredState) { +void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointer desiredState) { + qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID().c_str() << "->" << desiredState->getID().c_str(); - // TODO: interp. + + const float FRAMES_PER_SECOND = 30.0f; + + auto prevStateNode = _currentState->getNode(); + auto nextStateNode = desiredState->getNode(); + + _duringInterp = true; + _alpha = 0.0f; + float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration)); + _alphaVel = FRAMES_PER_SECOND / duration; + _prevPoses = _poses; + nextStateNode->setCurrentFrame(desiredState->_interpTarget); + _nextPoses = nextStateNode->evaluate(animVars, 0.0f); + _currentState = desiredState; } diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index 148429e722..4e9a4b3214 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -87,7 +87,7 @@ protected: void addState(State::Pointer state); - void switchState(State::Pointer desiredState); + void switchState(const AnimVariantMap& animVars, State::Pointer desiredState); State::Pointer evaluateTransitions(const AnimVariantMap& animVars) const; // for AnimDebugDraw rendering @@ -97,9 +97,8 @@ protected: // interpolation state bool _duringInterp = false; - float _interpFrame; - float _interpDuration; - float _alpha; + float _alphaVel = 0.0f; + float _alpha = 0.0f; AnimPoseVec _prevPoses; AnimPoseVec _nextPoses; From 21c6ba9bdf0447b788bfea1d3e20f7e2cd35c0cb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 27 Aug 2015 21:27:37 -0700 Subject: [PATCH 34/52] updated avatar.json to match current gist. --- tests/animation/src/data/avatar.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 7184e67a44..c8d507d03e 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -8,16 +8,16 @@ "states": [ { "id": "idle", - "interpTarget": 3, - "interpDuration": 3, + "interpTarget": 6, + "interpDuration": 6, "transitions": [ { "var": "isMoving", "state": "walk" } ] }, { "id": "walk", - "interpTarget": 3, - "interpDuration": 3, + "interpTarget": 6, + "interpDuration": 6, "transitions": [ { "var": "isNotMoving", "state": "idle" } ] From 9d983e0614099c42d86fc0e897377fec6e4b6e81 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 28 Aug 2015 17:16:32 -0700 Subject: [PATCH 35/52] Bug fix to AnimNode::setSkeletonModel and AnimNodeLoader. Also updated avatar.json to test nested graphs under a SM. --- interface/src/avatar/MyAvatar.cpp | 8 +- libraries/animation/src/AnimNode.h | 2 +- libraries/animation/src/AnimNodeLoader.cpp | 6 +- tests/animation/src/data/avatar.json | 94 ++++++++++++++++++++-- 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d1d62333a7..9fe0d5f845 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -167,7 +167,7 @@ void MyAvatar::update(float deltaTime) { static float t = 0.0f; _animVars.set("sine", 0.5f * sin(t) + 0.5f); - if (glm::length(getVelocity()) > 0.01) { + if (glm::length(getVelocity()) > 0.07f) { _animVars.set("isMoving", true); _animVars.set("isNotMoving", false); } else { @@ -1245,12 +1245,14 @@ void MyAvatar::setupNewAnimationSystem() { // load the anim graph // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 - auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/250ce1f207e23c74694351f04367063cf1269f94/avatar.json"); + // python2 -m SimpleHTTPServer& + //auto graphUrl = QUrl("http://localhost:8000/avatar.json"); + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/403651948de088ca4dcdda4f873e225b091c779a/avatar.json"); _animLoader.reset(new AnimNodeLoader(graphUrl)); connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { _animNode = nodeIn; _animNode->setSkeleton(_animSkeleton); - AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset() + glm::vec3(0, 0, 1)); + AnimPose xform(_skeletonModel.getScale() / 10.0f, glm::quat(), _skeletonModel.getOffset() + glm::vec3(0, 0, 1)); AnimDebugDraw::getInstance().addAnimNode("node", _animNode, xform); }); connect(_animLoader.get(), &AnimNodeLoader::error, [this, graphUrl](int error, QString str) { diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 0b521211a2..733428f188 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -70,7 +70,7 @@ public: void setSkeleton(const AnimSkeleton::Pointer skeleton) { setSkeletonInternal(skeleton); for (auto&& child : _children) { - child->setSkeletonInternal(skeleton); + child->setSkeleton(skeleton); } } diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index e41c3550ad..abb5b1b5f2 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -418,7 +418,11 @@ AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& j void AnimNodeLoader::onRequestDone(QNetworkReply& request) { auto node = load(request.readAll(), _url); - emit success(node); + if (node) { + emit success(node); + } else { + emit error(0, "json parse error"); + } } void AnimNodeLoader::onRequestError(QNetworkReply::NetworkError netError) { diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index c8d507d03e..3856588c42 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -11,11 +11,11 @@ "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "isMoving", "state": "walk" } + { "var": "isMoving", "state": "walk_fwd" } ] }, { - "id": "walk", + "id": "walk_fwd", "interpTarget": 6, "interpDuration": 6, "transitions": [ @@ -27,27 +27,109 @@ "children": [ { "id": "idle", + "type": "blendLinear", + "data": { + "alpha": 0.5, + "alphaVar": "sine" + }, + "children": [ + { + "id": "normal_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": "other_idle", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", + "startFrame": 20.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "walk_fwd", "type": "clip", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx", + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", "startFrame": 0.0, - "endFrame": 90.0, + "endFrame": 35.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "walk", + "id": "walk_bwd", "type": "clip", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_walk.fbx", + "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 + }, + "children": [] + }, + { + "id": "turn_left", + "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": "turn_right", + "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": "strafe_left", + "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": "strafe_right", + "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 77b857031b2a0e94f97a860061fa96c32fde84c6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 31 Aug 2015 11:01:15 -0700 Subject: [PATCH 36/52] Take timeScale into account during interps & setCurrentFrame() --- libraries/animation/src/AnimClip.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 4f3ea54e34..c4294495e7 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -75,7 +75,7 @@ void AnimClip::loadURL(const std::string& url) { void AnimClip::setCurrentFrameInternal(float frame) { const float dt = 0.0f; - _frame = accumulateTime(frame, dt); + _frame = accumulateTime(frame * _timeScale, dt); } float AnimClip::accumulateTime(float frame, float dt) const { From 9786954585c63c55a2628c85b0f265ead6f44ddd Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 31 Aug 2015 12:13:05 -0700 Subject: [PATCH 37/52] Added support for onDone and onLoop triggers. --- interface/src/avatar/MyAvatar.cpp | 7 ++++++- libraries/animation/src/AnimBlendLinear.cpp | 10 +++++----- libraries/animation/src/AnimBlendLinear.h | 2 +- libraries/animation/src/AnimClip.cpp | 14 ++++++++------ libraries/animation/src/AnimClip.h | 4 ++-- libraries/animation/src/AnimNode.h | 8 +++++--- libraries/animation/src/AnimOverlay.cpp | 6 +++--- libraries/animation/src/AnimOverlay.h | 2 +- libraries/animation/src/AnimStateMachine.cpp | 10 +++++++--- libraries/animation/src/AnimStateMachine.h | 2 +- libraries/animation/src/AnimVariant.h | 2 +- tests/animation/src/data/avatar.json | 20 ++++++++++---------- 12 files changed, 50 insertions(+), 37 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fa5f4da702..c5435c590b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -176,7 +176,12 @@ void MyAvatar::update(float deltaTime) { } t += deltaTime; - _animNode->evaluate(_animVars, deltaTime); + AnimNode::Triggers triggers; + _animNode->evaluate(_animVars, deltaTime, triggers); + _animVars.clearTriggers(); + for (auto& trigger : triggers) { + _animVars.setTrigger(trigger); + } } if (_referential) { diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index 102d0e4df6..499579ae67 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -22,7 +22,7 @@ AnimBlendLinear::~AnimBlendLinear() { } -const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt) { +const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) { _alpha = animVars.lookup(_alphaVar, _alpha); @@ -31,7 +31,7 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo pose = AnimPose::identity; } } else if (_children.size() == 1) { - _poses = _children[0]->evaluate(animVars, dt); + _poses = _children[0]->evaluate(animVars, dt, triggersOut); } else { float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); size_t prevPoseIndex = glm::floor(clampedAlpha); @@ -39,11 +39,11 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo float alpha = glm::fract(clampedAlpha); if (prevPoseIndex == nextPoseIndex) { // this can happen if alpha is on an integer boundary - _poses = _children[prevPoseIndex]->evaluate(animVars, dt); + _poses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut); } else { // need to eval and blend between two children. - auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt); - auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt); + auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut); + auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt, triggersOut); if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { _poses.resize(prevPoses.size()); diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index 2df1965064..8016f7994f 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -29,7 +29,7 @@ public: AnimBlendLinear(const std::string& id, float alpha); virtual ~AnimBlendLinear() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index c4294495e7..12ba97f377 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -27,14 +27,14 @@ AnimClip::~AnimClip() { } -const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt) { +const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) { // lookup parameters from animVars, using current instance variables as defaults. _startFrame = animVars.lookup(_startFrameVar, _startFrame); _endFrame = animVars.lookup(_endFrameVar, _endFrame); _timeScale = animVars.lookup(_timeScaleVar, _timeScale); _loopFlag = animVars.lookup(_loopFlagVar, _loopFlag); - _frame = accumulateTime(animVars.lookup(_frameVar, _frame), dt); + _frame = accumulateTime(animVars.lookup(_frameVar, _frame), dt, triggersOut); // poll network anim to see if it's finished loading yet. if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { @@ -74,11 +74,13 @@ void AnimClip::loadURL(const std::string& url) { } void AnimClip::setCurrentFrameInternal(float frame) { + // because dt is 0, we should not encounter any triggers const float dt = 0.0f; - _frame = accumulateTime(frame * _timeScale, dt); + Triggers triggers; + _frame = accumulateTime(frame * _timeScale, dt, triggers); } -float AnimClip::accumulateTime(float frame, float dt) const { +float AnimClip::accumulateTime(float frame, float dt, Triggers& triggersOut) const { const float startFrame = std::min(_startFrame, _endFrame); if (startFrame == _endFrame) { // when startFrame >= endFrame @@ -92,12 +94,12 @@ float AnimClip::accumulateTime(float frame, float dt) const { if (framesRemaining >= framesTillEnd) { if (_loopFlag) { // anim loop - // TODO: trigger onLoop event + triggersOut.push_back(_id + "OnLoop"); framesRemaining -= framesTillEnd; frame = startFrame; } else { // anim end - // TODO: trigger onDone event + triggersOut.push_back(_id + "OnDone"); frame = _endFrame; framesRemaining = 0.0f; } diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 45924c1eed..86e6cf7733 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -27,7 +27,7 @@ public: AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag); virtual ~AnimClip() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; void setStartFrameVar(const std::string& startFrameVar) { _startFrameVar = startFrameVar; } void setEndFrameVar(const std::string& endFrameVar) { _endFrameVar = endFrameVar; } @@ -40,7 +40,7 @@ protected: virtual void setCurrentFrameInternal(float frame) override; - float accumulateTime(float frame, float dt) const; + float accumulateTime(float frame, float dt, Triggers& triggersOut) const; void copyFromNetworkAnim(); // for AnimDebugDraw rendering diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 733428f188..06085a471a 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -30,6 +30,7 @@ class QJsonObject; // * hierarchy accessors, for adding, removing and iterating over child AnimNodes // * skeleton accessors, the skeleton is from the model whose bones we are going to manipulate // * evaluate method, perform actual joint manipulations here and return result by reference. +// Also, append any triggers that are detected during evaluation. class AnimNode { public: @@ -41,6 +42,7 @@ public: NumTypes }; using Pointer = std::shared_ptr; + using Triggers = std::vector; friend class AnimDebugDraw; friend void buildChildMap(std::map& map, Pointer node); @@ -76,9 +78,9 @@ public: AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; } - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) = 0; - virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, const AnimPoseVec& underPoses) { - return evaluate(animVars, dt); + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) = 0; + virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { + return evaluate(animVars, dt, triggersOut); } protected: diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index 81df5811d7..dcdd9f5b4b 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -36,7 +36,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) { } } -const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, float dt) { +const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) { // lookup parameters from animVars, using current instance variables as defaults. // NOTE: switching bonesets can be an expensive operation, let's try to avoid it. @@ -48,8 +48,8 @@ const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, float d _alpha = animVars.lookup(_alphaVar, _alpha); if (_children.size() >= 2) { - auto underPoses = _children[1]->evaluate(animVars, dt); - auto overPoses = _children[0]->overlay(animVars, dt, underPoses); + auto underPoses = _children[1]->evaluate(animVars, dt, triggersOut); + auto overPoses = _children[0]->overlay(animVars, dt, triggersOut, underPoses); if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) { _poses.resize(underPoses.size()); diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index 5940f0b2b3..eb11510f74 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -40,7 +40,7 @@ public: AnimOverlay(const std::string& id, BoneSet boneSet, float alpha); virtual ~AnimOverlay() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; void setBoneSetVar(const std::string& boneSetVar) { _boneSetVar = boneSetVar; } void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; } diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 43bb305797..ccb3dcd91f 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -20,7 +20,7 @@ AnimStateMachine::~AnimStateMachine() { } -const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, float dt) { +const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) { std::string desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); if (_currentState->getID() != desiredStateID) { @@ -60,7 +60,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl } } if (!_duringInterp) { - _poses = currentStateNode->evaluate(animVars, dt); + _poses = currentStateNode->evaluate(animVars, dt, triggersOut); } return _poses; } @@ -88,7 +88,11 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe _alphaVel = FRAMES_PER_SECOND / duration; _prevPoses = _poses; nextStateNode->setCurrentFrame(desiredState->_interpTarget); - _nextPoses = nextStateNode->evaluate(animVars, 0.0f); + + // because dt is 0, we should not encounter any triggers + const float dt = 0.0f; + Triggers triggers; + _nextPoses = nextStateNode->evaluate(animVars, dt, triggers); _currentState = desiredState; } diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index 4e9a4b3214..e48e08e96e 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -77,7 +77,7 @@ public: AnimStateMachine(const std::string& id); virtual ~AnimStateMachine() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; void setCurrentStateVar(std::string& currentStateVar) { _currentStateVar = currentStateVar; } diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 15ab7e94b1..849b6a436a 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -150,7 +150,7 @@ public: void set(const std::string& key, const std::string& value) { _map[key] = AnimVariant(value); } void setTrigger(const std::string& key) { _triggers.insert(key); } - void clearTirggers() { _triggers.clear(); } + void clearTriggers() { _triggers.clear(); } protected: std::map _map; diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 3856588c42..5647f74a7f 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -11,11 +11,11 @@ "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "isMoving", "state": "walk_fwd" } + { "var": "isMoving", "state": "walkFwd" } ] }, { - "id": "walk_fwd", + "id": "walkFwd", "interpTarget": 6, "interpDuration": 6, "transitions": [ @@ -34,7 +34,7 @@ }, "children": [ { - "id": "normal_idle", + "id": "normalIdle", "type": "clip", "data": { "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", @@ -46,7 +46,7 @@ "children": [] }, { - "id": "other_idle", + "id": "otherIdle", "type": "clip", "data": { "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", @@ -60,7 +60,7 @@ ] }, { - "id": "walk_fwd", + "id": "walkFwd", "type": "clip", "data": { "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", @@ -72,7 +72,7 @@ "children": [] }, { - "id": "walk_bwd", + "id": "walkBwd", "type": "clip", "data": { "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", @@ -84,7 +84,7 @@ "children": [] }, { - "id": "turn_left", + "id": "turnLeft", "type": "clip", "data": { "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx", @@ -96,7 +96,7 @@ "children": [] }, { - "id": "turn_right", + "id": "turnRight", "type": "clip", "data": { "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx", @@ -108,7 +108,7 @@ "children": [] }, { - "id": "strafe_left", + "id": "strafeLeft", "type": "clip", "data": { "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", @@ -120,7 +120,7 @@ "children": [] }, { - "id": "strafe_right", + "id": "strafeRight", "type": "clip", "data": { "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", From 0d98ab336582cb78e37073008fe2363c5cefb588 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 31 Aug 2015 17:27:23 -0700 Subject: [PATCH 38/52] Normalize scale on AnimSkeleton bind pose. --- interface/src/avatar/MyAvatar.cpp | 21 ++++++----- libraries/animation/src/AnimSkeleton.cpp | 37 +++++++++++++++++++- libraries/animation/src/AnimSkeleton.h | 6 +++- libraries/animation/src/AnimStateMachine.cpp | 1 - 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c5435c590b..45c22dc35c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1240,25 +1240,26 @@ void MyAvatar::setupNewAnimationSystem() { for (auto& joint : geom.joints) { joints.push_back(joint); } - _animSkeleton = make_shared(joints); + AnimPose geometryOffset(_skeletonModel.getGeometry()->getFBXGeometry().offset); + _animSkeleton = make_shared(joints, geometryOffset); // add skeleton to the debug renderer, so we can see it. - /* - AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset()); - AnimDebugDraw::getInstance().addSkeleton("my-avatar", _animSkeleton, xform); - */ + AnimDebugDraw::getInstance().addSkeleton("my-avatar-skeleton", _animSkeleton, AnimPose::identity); + //_animSkeleton->dump(); + + qCDebug(interfaceapp) << "geomOffset =" << geometryOffset; // load the anim graph // 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/403651948de088ca4dcdda4f873e225b091c779a/avatar.json"); + auto graphUrl = QUrl("http://localhost:8000/avatar.json"); + //auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/403651948de088ca4dcdda4f873e225b091c779a/avatar.json"); _animLoader.reset(new AnimNodeLoader(graphUrl)); connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { _animNode = nodeIn; _animNode->setSkeleton(_animSkeleton); - AnimPose xform(_skeletonModel.getScale() / 10.0f, glm::quat(), _skeletonModel.getOffset() + glm::vec3(0, 0, 1)); - AnimDebugDraw::getInstance().addAnimNode("node", _animNode, xform); + AnimPose xform(glm::vec3(1), glm::quat(), glm::vec3(0, 0, -1)); + AnimDebugDraw::getInstance().addAnimNode("my-avatar-animation", _animNode, xform); }); connect(_animLoader.get(), &AnimNodeLoader::error, [this, graphUrl](int error, QString str) { qCCritical(interfaceapp) << "Error loading" << graphUrl << "code = " << error << "str =" << str; @@ -1266,6 +1267,8 @@ void MyAvatar::setupNewAnimationSystem() { } void MyAvatar::teardownNewAnimationSystem() { + AnimDebugDraw::getInstance().removeSkeleton("my-avatar-skeleton"); + AnimDebugDraw::getInstance().removeAnimNode("my-avatar-animation"); _animSkeleton = nullptr; _animLoader = nullptr; _animNode = nullptr; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 3d16f99473..d650c0b964 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -62,13 +62,14 @@ static bool matrixIsIdentity(const glm::mat4& m) { return m == IDENTITY; } -AnimSkeleton::AnimSkeleton(const std::vector& joints) { +AnimSkeleton::AnimSkeleton(const std::vector& joints, const AnimPose& geometryOffset) { _joints = joints; // build a cache of bind poses _absoluteBindPoses.reserve(joints.size()); _relativeBindPoses.reserve(joints.size()); + // iterate over FBXJoints and extract the bind pose information. for (size_t i = 0; i < joints.size(); i++) { if (_joints[i].bindTransformIsValid) { // Use the FBXJoint::bindTransform, which is absolute model coordinates @@ -97,6 +98,23 @@ AnimSkeleton::AnimSkeleton(const std::vector& joints) { } } } + + // now we want to normalize scale from geometryOffset to all poses. + // This will ensure our bone translations will be in meters, even if the model was authored with some other unit of mesure. + for (auto& absPose : _absoluteBindPoses) { + absPose.trans = (geometryOffset * absPose).trans; + absPose.scale = vec3(1, 1, 1); + } + + // re-compute relative poses based on the modified absolute poses. + for (size_t i = 0; i < _relativeBindPoses.size(); i++) { + int parentIndex = getParentIndex(i); + if (parentIndex >= 0) { + _relativeBindPoses[i] = _absoluteBindPoses[parentIndex].inverse() * _absoluteBindPoses[i]; + } else { + _relativeBindPoses[i] = _absoluteBindPoses[i]; + } + } } int AnimSkeleton::nameToJointIndex(const QString& jointName) const { @@ -127,3 +145,20 @@ int AnimSkeleton::getParentIndex(int jointIndex) const { const QString& AnimSkeleton::getJointName(int jointIndex) const { return _joints[jointIndex].name; } + +#ifndef NDEBUG +void AnimSkeleton::dump() const { + qCDebug(animation) << "["; + for (int i = 0; i < getNumJoints(); i++) { + qCDebug(animation) << " {"; + qCDebug(animation) << " name =" << getJointName(i); + qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i); + qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i); + if (getParentIndex(i) >= 0) { + qCDebug(animation) << " parent =" << getJointName(getParentIndex(i)); + } + qCDebug(animation) << " },"; + } + qCDebug(animation) << "]"; +} +#endif diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 3faade0dbd..cc0d0b1c70 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -46,7 +46,7 @@ public: using Pointer = std::shared_ptr; using ConstPointer = std::shared_ptr; - AnimSkeleton(const std::vector& joints); + AnimSkeleton(const std::vector& joints, const AnimPose& geometryOffset); int nameToJointIndex(const QString& jointName) const; const QString& getJointName(int jointIndex) const; int getNumJoints() const; @@ -59,6 +59,10 @@ public: int getParentIndex(int jointIndex) const; +#ifndef NDEBUG + void dump() const; +#endif + protected: std::vector _joints; AnimPoseVec _absoluteBindPoses; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index ccb3dcd91f..7815381ca3 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -44,7 +44,6 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl switchState(animVars, desiredState); } - assert(_currentState); auto currentStateNode = _currentState->getNode(); assert(currentStateNode); From 7b4cb8655c7909220f597dac0cd65d5b2ed9e5d9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 1 Sep 2015 17:57:01 -0700 Subject: [PATCH 39/52] First pass integration of new anim system into rig. --- interface/src/Menu.cpp | 2 + interface/src/Menu.h | 1 + interface/src/avatar/MyAvatar.cpp | 84 ++-------- interface/src/avatar/MyAvatar.h | 11 +- interface/src/avatar/SkeletonModel.cpp | 4 + interface/src/avatar/SkeletonModel.h | 2 + libraries/animation/src/AnimSkeleton.cpp | 16 ++ libraries/animation/src/AnimSkeleton.h | 1 + libraries/animation/src/Rig.cpp | 187 +++++++++++++++++------ libraries/animation/src/Rig.h | 12 ++ tests/animation/src/data/avatar.json | 36 +---- 11 files changed, 198 insertions(+), 158 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4696463181..854f6fefe9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -433,6 +433,8 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, false, avatar, SLOT(setEnableRigAnimations(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, false, + avatar, SLOT(setEnableAnimGraph(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Connexion, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 94e49abcc7..c050e0b7a3 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -187,6 +187,7 @@ namespace MenuOption { const QString EchoServerAudio = "Echo Server Audio"; const QString EditEntitiesHelp = "Edit Entities Help..."; const QString Enable3DTVMode = "Enable 3DTV Mode"; + const QString EnableAnimGraph = "Enable Anim Graph"; const QString EnableCharacterController = "Enable avatar collisions"; const QString EnableRigAnimations = "Enable Rig Animations"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 45c22dc35c..b72dd5c813 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -36,11 +36,6 @@ #include "devices/Faceshift.h" -#include "AnimDebugDraw.h" -#include "AnimSkeleton.h" -#include "AnimClip.h" -#include "AnimBlendLinear.h" -#include "AnimOverlay.h" #include "Application.h" #include "AvatarManager.h" #include "Environment.h" @@ -163,27 +158,6 @@ void MyAvatar::update(float deltaTime) { _goToPending = false; } - if (_animNode) { - static float t = 0.0f; - _animVars.set("sine", 0.5f * sin(t) + 0.5f); - - if (glm::length(getVelocity()) > 0.07f) { - _animVars.set("isMoving", true); - _animVars.set("isNotMoving", false); - } else { - _animVars.set("isMoving", false); - _animVars.set("isNotMoving", true); - } - - t += deltaTime; - AnimNode::Triggers triggers; - _animNode->evaluate(_animVars, deltaTime, triggers); - _animVars.clearTriggers(); - for (auto& trigger : triggers) { - _animVars.setTrigger(trigger); - } - } - if (_referential) { _referential->update(); } @@ -737,6 +711,13 @@ void MyAvatar::setEnableRigAnimations(bool isEnabled) { } } +void MyAvatar::setEnableAnimGraph(bool isEnabled) { + _rig->setEnableAnimGraph(isEnabled); + if (!isEnabled) { + // AJT: TODO: FIXME: currently setEnableAnimGraph menu item only works on startup + } +} + void MyAvatar::loadData() { Settings settings; settings.beginGroup("Avatar"); @@ -1035,7 +1016,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _billboardValid = false; _skeletonModel.setVisibleInScene(true, scene); _headBoneSet.clear(); - teardownNewAnimationSystem(); } void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) { @@ -1232,48 +1212,6 @@ void MyAvatar::initHeadBones() { } } -void MyAvatar::setupNewAnimationSystem() { - - // create a skeleton - auto geom = _skeletonModel.getGeometry()->getFBXGeometry(); - std::vector joints; - for (auto& joint : geom.joints) { - joints.push_back(joint); - } - AnimPose geometryOffset(_skeletonModel.getGeometry()->getFBXGeometry().offset); - _animSkeleton = make_shared(joints, geometryOffset); - - // add skeleton to the debug renderer, so we can see it. - AnimDebugDraw::getInstance().addSkeleton("my-avatar-skeleton", _animSkeleton, AnimPose::identity); - //_animSkeleton->dump(); - - qCDebug(interfaceapp) << "geomOffset =" << geometryOffset; - - // load the anim graph - // 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/403651948de088ca4dcdda4f873e225b091c779a/avatar.json"); - _animLoader.reset(new AnimNodeLoader(graphUrl)); - connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { - _animNode = nodeIn; - _animNode->setSkeleton(_animSkeleton); - AnimPose xform(glm::vec3(1), glm::quat(), glm::vec3(0, 0, -1)); - AnimDebugDraw::getInstance().addAnimNode("my-avatar-animation", _animNode, xform); - }); - connect(_animLoader.get(), &AnimNodeLoader::error, [this, graphUrl](int error, QString str) { - qCCritical(interfaceapp) << "Error loading" << graphUrl << "code = " << error << "str =" << str; - }); -} - -void MyAvatar::teardownNewAnimationSystem() { - AnimDebugDraw::getInstance().removeSkeleton("my-avatar-skeleton"); - AnimDebugDraw::getInstance().removeAnimNode("my-avatar-animation"); - _animSkeleton = nullptr; - _animLoader = nullptr; - _animNode = nullptr; -} - void MyAvatar::preRender(RenderArgs* renderArgs) { render::ScenePointer scene = Application::getInstance()->getMain3DScene(); @@ -1281,11 +1219,15 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { if (_skeletonModel.initWhenReady(scene)) { initHeadBones(); + _skeletonModel.setCauterizeBoneSet(_headBoneSet); - // AJT: SETUP DEBUG RENDERING OF NEW ANIMATION SYSTEM + // 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/403651948de088ca4dcdda4f873e225b091c779a/avatar.json"); - setupNewAnimationSystem(); + _skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } if (shouldDrawHead != _prevShouldDrawHead) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b48964d02c..0283310e71 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -16,9 +16,6 @@ #include #include -#include "AnimNode.h" -#include "AnimNodeLoader.h" - #include "Avatar.h" class ModelItemID; @@ -194,6 +191,7 @@ public slots: virtual void rebuildSkeletonBody(); void setEnableRigAnimations(bool isEnabled); + void setEnableAnimGraph(bool isEnabled); signals: void transformChanged(); @@ -201,8 +199,6 @@ signals: void collisionWithEntity(const Collision& collision); private: - void setupNewAnimationSystem(); - void teardownNewAnimationSystem(); glm::vec3 getWorldBodyPosition() const; glm::quat getWorldBodyOrientation() const; @@ -317,11 +313,6 @@ private: std::unordered_set _headBoneSet; RigPointer _rig; bool _prevShouldDrawHead; - - std::shared_ptr _animNode; - std::shared_ptr _animSkeleton; - std::unique_ptr _animLoader; - AnimVariantMap _animVars; }; #endif // hifi_MyAvatar_h diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index acd2f038f4..0e29986bd1 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -571,3 +571,7 @@ bool SkeletonModel::hasSkeleton() { void SkeletonModel::onInvalidate() { } + +void SkeletonModel::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) { + _rig->initAnimGraph(url, fbxGeometry); +} diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 4ae615eadd..6b04d36de0 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -106,6 +106,8 @@ public: virtual void onInvalidate() override; + void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry); + signals: void skeletonLoaded(); diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index d650c0b964..d8efe55f5d 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -161,4 +161,20 @@ void AnimSkeleton::dump() const { } qCDebug(animation) << "]"; } + +void AnimSkeleton::dump(const AnimPoseVec& poses) const { + qCDebug(animation) << "["; + for (int i = 0; i < getNumJoints(); i++) { + qCDebug(animation) << " {"; + qCDebug(animation) << " name =" << getJointName(i); + qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i); + qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i); + qCDebug(animation) << " pose =" << poses[i]; + if (getParentIndex(i) >= 0) { + qCDebug(animation) << " parent =" << getJointName(getParentIndex(i)); + } + qCDebug(animation) << " },"; + } + qCDebug(animation) << "]"; +} #endif diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index cc0d0b1c70..f266fe04b3 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -61,6 +61,7 @@ public: #ifndef NDEBUG void dump() const; + void dump(const AnimPoseVec& poses) const; #endif protected: diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 8adb53024c..356afe6620 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -14,6 +14,9 @@ #include "AnimationHandle.h" #include "AnimationLogging.h" + +#include "AnimSkeleton.h" + #include "Rig.h" void Rig::HeadParameters::dump() const { @@ -183,6 +186,12 @@ void Rig::deleteAnimations() { removeAnimationHandle(animation); } _animationHandles.clear(); + + if (_enableAnimGraph) { + _animSkeleton = nullptr; + _animLoader = nullptr; + _animNode = nullptr; + } } void Rig::initJointStates(QVector states, glm::mat4 parentTransform, @@ -406,6 +415,22 @@ glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const { } void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) { + + if (_enableAnimGraph) { + static float t = 0.0f; + _animVars.set("sine", 0.5f * sin(t) + 0.5f); + + if (glm::length(worldVelocity) > 0.07f) { + _animVars.set("isMoving", true); + _animVars.set("isNotMoving", false); + } else { + _animVars.set("isMoving", false); + _animVars.set("isNotMoving", true); + } + + t += deltaTime; + } + if (!_enableRig) { return; } @@ -442,55 +467,85 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) { - - // First normalize the fades so that they sum to 1.0. - // update the fade data in each animation (not normalized as they are an independent propert of animation) - foreach (const AnimationHandlePointer& handle, _runningAnimations) { - float fadePerSecond = handle->getFadePerSecond(); - float fade = handle->getFade(); - if (fadePerSecond != 0.0f) { - fade += fadePerSecond * deltaTime; - if ((0.0f >= fade) || (fade >= 1.0f)) { - fade = glm::clamp(fade, 0.0f, 1.0f); - handle->setFadePerSecond(0.0f); + + if (_enableAnimGraph) { + if (!_animNode) { + return; + } + + // evaluate the animation + AnimNode::Triggers triggersOut; + AnimPoseVec poses = _animNode->evaluate(_animVars, deltaTime, triggersOut); + _animVars.clearTriggers(); + for (auto& trigger : triggersOut) { + _animVars.setTrigger(trigger); + } + + // copy poses into jointStates + const float PRIORITY = 1.0f; + const float MIX = 1.0f; + for (size_t i = 0; i < poses.size(); i++) { + setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false); + } + + for (int i = 0; i < _jointStates.size(); i++) { + updateJointState(i, parentTransform); + } + for (int i = 0; i < _jointStates.size(); i++) { + _jointStates[i].resetTransformChanged(); + } + + } else { + + // First normalize the fades so that they sum to 1.0. + // update the fade data in each animation (not normalized as they are an independent propert of animation) + foreach (const AnimationHandlePointer& handle, _runningAnimations) { + float fadePerSecond = handle->getFadePerSecond(); + float fade = handle->getFade(); + if (fadePerSecond != 0.0f) { + fade += fadePerSecond * deltaTime; + if ((0.0f >= fade) || (fade >= 1.0f)) { + fade = glm::clamp(fade, 0.0f, 1.0f); + handle->setFadePerSecond(0.0f); + } + handle->setFade(fade); + if (fade <= 0.0f) { // stop any finished animations now + handle->setRunning(false, false); // but do not restore joints as it causes a flicker + } } - handle->setFade(fade); - if (fade <= 0.0f) { // stop any finished animations now - handle->setRunning(false, false); // but do not restore joints as it causes a flicker - } - } - } - // sum the remaining fade data - float fadeTotal = 0.0f; - foreach (const AnimationHandlePointer& handle, _runningAnimations) { - fadeTotal += handle->getFade(); - } - float fadeSumSoFar = 0.0f; - foreach (const AnimationHandlePointer& handle, _runningAnimations) { - handle->setPriority(1.0f); - // if no fadeTotal, everyone's (typically just one running) is starting at zero. In that case, blend equally. - float normalizedFade = (fadeTotal != 0.0f) ? (handle->getFade() / fadeTotal) : (1.0f / _runningAnimations.count()); - assert(normalizedFade != 0.0f); - // simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step. - // i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result. - // The formula here for mix is based on the idea that, at each step: - // fadeSum is to normalizedFade, as (1 - mix) is to mix - // i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix - // Then we solve for mix. - // Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1. - // Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++ - float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f); - assert((0.0f <= mix) && (mix <= 1.0f)); - fadeSumSoFar += normalizedFade; - handle->setMix(mix); - handle->simulate(deltaTime); - } - - for (int i = 0; i < _jointStates.size(); i++) { - updateJointState(i, parentTransform); - } - for (int i = 0; i < _jointStates.size(); i++) { - _jointStates[i].resetTransformChanged(); + } + // sum the remaining fade data + float fadeTotal = 0.0f; + foreach (const AnimationHandlePointer& handle, _runningAnimations) { + fadeTotal += handle->getFade(); + } + float fadeSumSoFar = 0.0f; + foreach (const AnimationHandlePointer& handle, _runningAnimations) { + handle->setPriority(1.0f); + // if no fadeTotal, everyone's (typically just one running) is starting at zero. In that case, blend equally. + float normalizedFade = (fadeTotal != 0.0f) ? (handle->getFade() / fadeTotal) : (1.0f / _runningAnimations.count()); + assert(normalizedFade != 0.0f); + // simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step. + // i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result. + // The formula here for mix is based on the idea that, at each step: + // fadeSum is to normalizedFade, as (1 - mix) is to mix + // i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix + // Then we solve for mix. + // Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1. + // Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++ + float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f); + assert((0.0f <= mix) && (mix <= 1.0f)); + fadeSumSoFar += normalizedFade; + handle->setMix(mix); + handle->simulate(deltaTime); + } + + for (int i = 0; i < _jointStates.size(); i++) { + updateJointState(i, parentTransform); + } + for (int i = 0; i < _jointStates.size(); i++) { + _jointStates[i].resetTransformChanged(); + } } } @@ -824,6 +879,7 @@ void Rig::updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& updateEyeJoint(leftEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade); updateEyeJoint(rightEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade); } + void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) { if (index >= 0 && _jointStates[index].getParentIndex() >= 0) { auto& state = _jointStates[index]; @@ -842,3 +898,38 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm state.getDefaultRotation(), DEFAULT_PRIORITY); } } + +void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) { + if (!_enableAnimGraph) { + return; + } + + // convert to std::vector of joints + std::vector joints; + joints.reserve(fbxGeometry.joints.size()); + for (auto& joint : fbxGeometry.joints) { + joints.push_back(joint); + } + + // create skeleton + AnimPose geometryOffset(fbxGeometry.offset); + _animSkeleton = std::make_shared(joints, geometryOffset); + + // add skeleton to the debug renderer, so we can see it. + // AnimDebugDraw::getInstance().addSkeleton("my-avatar-skeleton", _animSkeleton, AnimPose::identity); + // _animSkeleton->dump(); + + // load the anim graph + _animLoader.reset(new AnimNodeLoader(url)); + connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { + _animNode = nodeIn; + _animNode->setSkeleton(_animSkeleton); + + // add node to debug renderer, for debugging + // AnimPose xform(glm::vec3(1), glm::quat(), glm::vec3(0, 0, -1)); + // AnimDebugDraw::getInstance().addAnimNode("my-avatar-animation", _animNode, xform); + }); + connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) { + qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + }); +} diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index a574807eb4..1b7bf72e88 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -40,6 +40,9 @@ #include "JointState.h" // We might want to change this (later) to something that doesn't depend on gpu, fbx and model. -HRS +#include "AnimNode.h" +#include "AnimNodeLoader.h" + class AnimationHandle; typedef std::shared_ptr AnimationHandlePointer; @@ -155,6 +158,7 @@ public: virtual void updateJointState(int index, glm::mat4 parentTransform) = 0; void setEnableRig(bool isEnabled) { _enableRig = isEnabled; } + void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; } void updateFromHeadParameters(const HeadParameters& params); void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation, @@ -163,6 +167,8 @@ public: virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, float scale, float priority) = 0; + void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry); + protected: void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); @@ -184,8 +190,14 @@ public: QList _runningAnimations; bool _enableRig; + bool _enableAnimGraph; glm::vec3 _lastFront; glm::vec3 _lastPosition; + + std::shared_ptr _animNode; + std::shared_ptr _animSkeleton; + std::unique_ptr _animLoader; + AnimVariantMap _animVars; }; #endif /* defined(__hifi__Rig__) */ diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 5647f74a7f..0988e40ca8 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -27,37 +27,15 @@ "children": [ { "id": "idle", - "type": "blendLinear", + "type": "clip", "data": { - "alpha": 0.5, - "alphaVar": "sine" + "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": "normalIdle", - "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": "otherIdle", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", - "startFrame": 20.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] + "children": [] }, { "id": "walkFwd", From d13a188dde74c805e406b0d36560cea74dd58fb9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 10:31:45 -0700 Subject: [PATCH 40/52] Compile fixes and added test case to verify onDone and onLoop triggers --- tests/animation/src/AnimTests.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 91bb1ce5bb..806560d96f 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -40,7 +40,7 @@ void AnimTests::testClipInternalState() { AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag); QVERIFY(clip.getID() == id); - QVERIFY(clip.getType() == AnimNode::ClipType); + QVERIFY(clip.getType() == AnimNode::Type::Clip); QVERIFY(clip._url == url); QVERIFY(clip._startFrame == startFrame); @@ -55,7 +55,7 @@ static float framesToSec(float secs) { } void AnimTests::testClipEvaulate() { - std::string id = "my clip node"; + std::string id = "myClipNode"; std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; float endFrame = 22.0f; @@ -67,21 +67,30 @@ void AnimTests::testClipEvaulate() { AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag); - clip.evaluate(vars, framesToSec(10.0f)); + AnimNode::Triggers triggers; + clip.evaluate(vars, framesToSec(10.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 12.0f, EPSILON); // does it loop? - clip.evaluate(vars, framesToSec(11.0f)); + triggers.clear(); + clip.evaluate(vars, framesToSec(11.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); + // did we receive a loop trigger? + QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnLoop") != triggers.end()); + // does it pause at end? + triggers.clear(); clip.setLoopFlagVar("FalseVar"); - clip.evaluate(vars, framesToSec(20.0f)); + clip.evaluate(vars, framesToSec(20.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, EPSILON); + + // did we receive a done trigger? + QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnDone") != triggers.end()); } void AnimTests::testClipEvaulateWithVars() { - std::string id = "my clip node"; + std::string id = "myClipNode"; std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; float endFrame = 22.0f; @@ -105,7 +114,8 @@ void AnimTests::testClipEvaulateWithVars() { clip.setTimeScaleVar("timeScale2"); clip.setLoopFlagVar("loopFlag2"); - clip.evaluate(vars, framesToSec(0.1f)); + AnimNode::Triggers triggers; + clip.evaluate(vars, framesToSec(0.1f), triggers); // verify that the values from the AnimVariantMap made it into the clipNode's // internal state @@ -137,11 +147,11 @@ void AnimTests::testLoader() { QVERIFY((bool)node); QVERIFY(node->getID() == "blend"); - QVERIFY(node->getType() == AnimNode::BlendLinearType); + QVERIFY(node->getType() == AnimNode::Type::BlendLinear); QVERIFY((bool)node); QVERIFY(node->getID() == "blend"); - QVERIFY(node->getType() == AnimNode::BlendLinearType); + QVERIFY(node->getType() == AnimNode::Type::BlendLinear); auto blend = std::static_pointer_cast(node); QVERIFY(blend->_alpha == 0.5f); From 4a749a040379711f26061de5905e88d4ed9850fe Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 10:41:04 -0700 Subject: [PATCH 41/52] Updated MyAvatar to use animGraph from gist instead of local host. --- interface/src/avatar/MyAvatar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7d9d68714a..8e07aa3442 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1224,8 +1224,8 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { // 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/403651948de088ca4dcdda4f873e225b091c779a/avatar.json"); + //auto graphUrl = QUrl("http://localhost:8000/avatar.json"); + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/a6e3754beef1524f95bae178066bae3b2f839952/avatar.json"); _skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } From 1ae22268ac1e091811440ebad709f5cb230c1424 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 11:04:52 -0700 Subject: [PATCH 42/52] Added comment to AnimStateMachine header. --- libraries/animation/src/AnimNodeLoader.cpp | 2 +- libraries/animation/src/AnimStateMachine.h | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index abb5b1b5f2..5863380d49 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -1,5 +1,5 @@ // -// AnimNodeLoader.h +// AnimNodeLoader.cpp // // Copyright 2015 High Fidelity, Inc. // diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index e48e08e96e..066dfabbbb 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -14,6 +14,23 @@ #include #include "AnimNode.h" +// State Machine for transitioning between children AnimNodes +// +// This is mechinisim for playing animations and smoothly interpolating/fading +// between them. A StateMachine has a set of States, which typically reference +// child AnimNodes. Each State has a list of Transitions, which are evaluated +// to determine when we should switch to a new State. Parameters for the smooth +// interpolation/fading are read from the State that you are transitioning to. +// +// The currentState can be set directly via the setCurrentStateVar() and will override +// any State transitions. +// +// Each State has two parameters that can be changed via AnimVars, +// * interpTarget - (frames) The destination frame of the interpolation. i.e. the first frame of the animation that will +// visible after interpolation is complete. +// * interpDuration - (frames) The total length of time it will take to interp between the current pose and the +// interpTarget frame. + class AnimStateMachine : public AnimNode { public: friend class AnimNodeLoader; From fea030b9a0474374936b453631dc35a342ba0a81 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 12:18:17 -0700 Subject: [PATCH 43/52] Compile and warning fixes for MacOSX. --- libraries/animation/src/AnimNodeLoader.cpp | 9 ++++++--- libraries/animation/src/AnimSkeleton.cpp | 5 ----- libraries/animation/src/Rig.cpp | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 5863380d49..e96ce37ed1 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -33,7 +33,7 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const 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; } -static bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { @@ -41,6 +41,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::NumTypes: return nullptr; }; return nullptr; } @@ -51,6 +52,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::NumTypes: return nullptr; }; return nullptr; } @@ -61,6 +63,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::NumTypes: return nullptr; }; return nullptr; } @@ -264,13 +267,13 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const return node; } -static void buildChildMap(std::map& map, AnimNode::Pointer node) { +void buildChildMap(std::map& map, AnimNode::Pointer node) { for ( auto child : node->_children ) { map.insert(std::pair(child->_id, child)); } } -static bool 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); diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index d8efe55f5d..07ab485c45 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -57,11 +57,6 @@ AnimPose::operator glm::mat4() const { glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); } -static const mat4 IDENTITY = mat4(); -static bool matrixIsIdentity(const glm::mat4& m) { - return m == IDENTITY; -} - AnimSkeleton::AnimSkeleton(const std::vector& joints, const AnimPose& geometryOffset) { _joints = joints; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 4f0257b4dc..89ab062c02 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -418,7 +418,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos if (_enableAnimGraph) { static float t = 0.0f; - _animVars.set("sine", 0.5f * sin(t) + 0.5f); + _animVars.set("sine", static_cast(0.5 * sin(t) + 0.5)); if (glm::length(worldVelocity) > 0.07f) { _animVars.set("isMoving", true); @@ -483,7 +483,6 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { // copy poses into jointStates const float PRIORITY = 1.0f; - const float MIX = 1.0f; for (size_t i = 0; i < poses.size(); i++) { setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false); } From df26f182227ef8492b96932edb73b9c1eb4990a0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 13:29:29 -0700 Subject: [PATCH 44/52] Fix for compilation errors on linux --- libraries/animation/src/AnimNodeLoader.cpp | 8 ++++---- libraries/animation/src/AnimNodeLoader.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index e96ce37ed1..3ac3f3909a 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -7,10 +7,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include -#include +#include +#include +#include +#include #include "AnimNode.h" #include "AnimClip.h" diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index 095c05cf7e..8082a1e84b 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -12,9 +12,9 @@ #include -#include -#include -#include +#include +#include +#include #include "AnimNode.h" From 91fbbf7d4eae82bfd7ac8c7e21558ec9ae123a6a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 13:35:26 -0700 Subject: [PATCH 45/52] Updated copyright boiler plate. --- libraries/animation/src/AnimBlendLinear.cpp | 3 ++- libraries/animation/src/AnimBlendLinear.h | 3 ++- libraries/animation/src/AnimClip.cpp | 3 ++- libraries/animation/src/AnimClip.h | 3 ++- libraries/animation/src/AnimNode.h | 3 ++- libraries/animation/src/AnimNodeLoader.cpp | 3 ++- libraries/animation/src/AnimNodeLoader.h | 3 ++- libraries/animation/src/AnimOverlay.cpp | 3 ++- libraries/animation/src/AnimOverlay.h | 3 ++- libraries/animation/src/AnimSkeleton.cpp | 3 ++- libraries/animation/src/AnimSkeleton.h | 3 ++- libraries/animation/src/AnimStateMachine.cpp | 3 ++- libraries/animation/src/AnimStateMachine.h | 3 ++- libraries/animation/src/AnimUtil.cpp | 3 ++- libraries/animation/src/AnimUtil.h | 3 ++- libraries/animation/src/AnimVariant.h | 3 ++- 16 files changed, 32 insertions(+), 16 deletions(-) diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index 499579ae67..63c66a2b9d 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -1,7 +1,8 @@ // // AnimBlendLinear.cpp // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index 8016f7994f..3a09245575 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -1,7 +1,8 @@ // // AnimBlendLinear.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 12ba97f377..fdc5fc504a 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -1,7 +1,8 @@ // // AnimClip.cpp // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 86e6cf7733..1b9649cc3e 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -1,7 +1,8 @@ // // AnimClip.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 06085a471a..3a54710b39 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -1,7 +1,8 @@ // // AnimNode.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 3ac3f3909a..5f0260db6d 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -1,7 +1,8 @@ // // AnimNodeLoader.cpp // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index 8082a1e84b..71b5552879 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -1,7 +1,8 @@ // // AnimNodeLoader.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index dcdd9f5b4b..52026f7711 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -1,7 +1,8 @@ // // AnimOverlay.cpp // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index eb11510f74..de563cc403 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -1,7 +1,8 @@ // // AnimOverlay.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 07ab485c45..9e600c4e15 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -1,7 +1,8 @@ // // AnimSkeleton.cpp // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index f266fe04b3..c0c5036cc7 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -1,7 +1,8 @@ // // AnimSkeleton.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 7815381ca3..eccfcfa6e6 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -1,7 +1,8 @@ // // AnimStateMachine.cpp // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index 066dfabbbb..f2d941a568 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -1,7 +1,8 @@ // // AnimStateMachine.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index 4423b5c933..81b294e66c 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -1,7 +1,8 @@ // // AnimUtil.cpp // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 556eba8989..23c02b6183 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -1,7 +1,8 @@ // // AnimUtil.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 849b6a436a..1d720ba565 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -1,7 +1,8 @@ // // AnimVariant.h // -// Copyright 2015 High Fidelity, Inc. +// Created by Anthony J. Thibault on 9/2/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 From 99586f259c394eaec1a6790f2689965fdf9f8eee Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 13:44:34 -0700 Subject: [PATCH 46/52] Renamed bindTransformIsValid to bindTransformFoundInCluster --- libraries/animation/src/AnimSkeleton.cpp | 2 +- libraries/fbx/src/FBXReader.cpp | 4 ++-- libraries/fbx/src/FBXReader.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 9e600c4e15..3f11607f26 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -67,7 +67,7 @@ AnimSkeleton::AnimSkeleton(const std::vector& joints, const AnimPose& // iterate over FBXJoints and extract the bind pose information. for (size_t i = 0; i < joints.size(); i++) { - if (_joints[i].bindTransformIsValid) { + if (_joints[i].bindTransformFoundInCluster) { // Use the FBXJoint::bindTransform, which is absolute model coordinates // i.e. not relative to it's parent. AnimPose absoluteBindPose(_joints[i].bindTransform); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 7c80b97117..c885f56b55 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -2309,7 +2309,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping } } - joint.bindTransformIsValid = false; + joint.bindTransformFoundInCluster = false; geometry.joints.append(joint); geometry.jointIndices.insert(model.name, geometry.joints.size()); @@ -2538,7 +2538,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping FBXJoint& joint = geometry.joints[fbxCluster.jointIndex]; joint.inverseBindRotation = glm::inverse(extractRotation(cluster.transformLink)); joint.bindTransform = cluster.transformLink; - joint.bindTransformIsValid = true; + joint.bindTransformFoundInCluster = true; // update the bind pose extents glm::vec3 bindTranslation = extractTranslation(geometry.offset * joint.bindTransform); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index d8e1ae59e6..158b5581c6 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -84,7 +84,7 @@ public: glm::mat4 bindTransform; QString name; bool isSkeletonJoint; - bool bindTransformIsValid; + bool bindTransformFoundInCluster; }; From 46b3a7fd2313a811d1d8618ce42fe8a1ba364ec3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 17:28:06 -0700 Subject: [PATCH 47/52] Improved default avatar.json state machine. Now triggers 7 states. Idle, WalkFwd, WalkBwd, StrafeLeft, StrafeRight, TurnLeft & TurnRight. As well as variable speed walking to match current velocity. --- interface/src/avatar/MyAvatar.cpp | 2 +- libraries/animation/src/AnimStateMachine.cpp | 4 +- libraries/animation/src/Rig.cpp | 144 ++++++++++++------- tests/animation/src/data/avatar.json | 85 ++++++++++- 4 files changed, 181 insertions(+), 54 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8e07aa3442..c89a583f32 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1225,7 +1225,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { // 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/a6e3754beef1524f95bae178066bae3b2f839952/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/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index eccfcfa6e6..5de379dd33 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -52,7 +52,9 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl if (_duringInterp) { _alpha += _alphaVel * dt; if (_alpha < 1.0f) { - ::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]); + if (_poses.size() > 0) { + ::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]); + } } else { _duringInterp = false; _prevPoses.clear(); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 89ab062c02..c96a588e5c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -416,52 +416,107 @@ glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const { void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) { + glm::vec3 front = worldRotation * IDENTITY_FRONT; + + // at the moment worldVelocity comes from the Avatar physics body, which is not always correct when + // moving in the HMD, so let's compute our own veloicty. + glm::vec3 worldVel = (worldPosition - _lastPosition) / deltaTime; + glm::vec3 localVel = glm::inverse(worldRotation) * worldVel; + float forwardSpeed = glm::dot(localVel, IDENTITY_FRONT); + float lateralSpeed = glm::dot(localVel, IDENTITY_RIGHT); + float turningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime; + if (_enableAnimGraph) { + + // sine wave LFO var for testing. static float t = 0.0f; _animVars.set("sine", static_cast(0.5 * sin(t) + 0.5)); - if (glm::length(worldVelocity) > 0.07f) { - _animVars.set("isMoving", true); - _animVars.set("isNotMoving", false); + // default anim vars to notMoving and notTurning + _animVars.set("isMovingForward", false); + _animVars.set("isMovingBackward", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRight", false); + _animVars.set("isNotMoving", true); + _animVars.set("isTurningLeft", false); + _animVars.set("isTurningRight", false); + _animVars.set("isNotTurning", true); + + const float ANIM_WALK_SPEED = 1.4f; // m/s + _animVars.set("walkTimeScale", glm::clamp(0.1f, 2.0f, glm::length(localVel) / ANIM_WALK_SPEED)); + + const float MOVE_SPEED_THRESHOLD = 0.01f; // m/sec + const float TURN_SPEED_THRESHOLD = 0.5f; // rad/sec + if (glm::length(localVel) > MOVE_SPEED_THRESHOLD) { + if (fabs(forwardSpeed) > fabs(lateralSpeed)) { + if (forwardSpeed > 0.0f) { + // forward + _animVars.set("isMovingForward", true); + _animVars.set("isNotMoving", false); + + } else { + // backward + _animVars.set("isMovingBackward", true); + _animVars.set("isNotMoving", false); + } + } else { + if (lateralSpeed > 0.0f) { + // right + _animVars.set("isMovingRight", true); + _animVars.set("isNotMoving", false); + } else { + // left + _animVars.set("isMovingLeft", true); + _animVars.set("isNotMoving", false); + } + } } else { - _animVars.set("isMoving", false); - _animVars.set("isNotMoving", true); + if (fabs(turningSpeed) > TURN_SPEED_THRESHOLD) { + if (turningSpeed > 0.0f) { + // turning right + _animVars.set("isTurningRight", true); + _animVars.set("isNotTurning", false); + } else { + // turning left + _animVars.set("isTurningLeft", true); + _animVars.set("isNotTurning", false); + } + } else { + // idle + } } t += deltaTime; } - if (!_enableRig) { - return; + if (_enableRig) { + bool isMoving = false; + auto updateRole = [&](const QString& role, bool isOn) { + isMoving = isMoving || isOn; + if (isOn) { + if (!isRunningRole(role)) { + qCDebug(animation) << "Rig STARTING" << role; + startAnimationByRole(role); + } + } else { + if (isRunningRole(role)) { + qCDebug(animation) << "Rig stopping" << role; + stopAnimationByRole(role); + } + } + }; + + updateRole("walk", forwardSpeed > 0.01f); + updateRole("backup", forwardSpeed < -0.01f); + bool isTurning = std::abs(turningSpeed) > 0.5f; + updateRole("rightTurn", isTurning && (turningSpeed > 0)); + updateRole("leftTurn", isTurning && (turningSpeed < 0)); + bool isStrafing = !isTurning && (std::abs(lateralSpeed) > 0.01f); + updateRole("rightStrafe", isStrafing && (lateralSpeed > 0.0f)); + updateRole("leftStrafe", isStrafing && (lateralSpeed < 0.0f)); + updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus. } - bool isMoving = false; - glm::vec3 front = worldRotation * IDENTITY_FRONT; - float forwardSpeed = glm::dot(worldVelocity, front); - float rightLateralSpeed = glm::dot(worldVelocity, worldRotation * IDENTITY_RIGHT); - float rightTurningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime; - auto updateRole = [&](const QString& role, bool isOn) { - isMoving = isMoving || isOn; - if (isOn) { - if (!isRunningRole(role)) { - qCDebug(animation) << "Rig STARTING" << role; - startAnimationByRole(role); - } - } else { - if (isRunningRole(role)) { - qCDebug(animation) << "Rig stopping" << role; - stopAnimationByRole(role); - } - } - }; - updateRole("walk", forwardSpeed > 0.01f); - updateRole("backup", forwardSpeed < -0.01f); - bool isTurning = std::abs(rightTurningSpeed) > 0.5f; - updateRole("rightTurn", isTurning && (rightTurningSpeed > 0)); - updateRole("leftTurn", isTurning && (rightTurningSpeed < 0)); - bool isStrafing = !isTurning && (std::abs(rightLateralSpeed) > 0.01f); - updateRole("rightStrafe", isStrafing && (rightLateralSpeed > 0.0f)); - updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f)); - updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus. + _lastFront = front; _lastPosition = worldPosition; } @@ -487,13 +542,6 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false); } - for (int i = 0; i < _jointStates.size(); i++) { - updateJointState(i, rootTransform); - } - for (int i = 0; i < _jointStates.size(); i++) { - _jointStates[i].resetTransformChanged(); - } - } else { // First normalize the fades so that they sum to 1.0. @@ -538,13 +586,13 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { handle->setMix(mix); handle->simulate(deltaTime); } + } - for (int i = 0; i < _jointStates.size(); i++) { - updateJointState(i, rootTransform); - } - for (int i = 0; i < _jointStates.size(); i++) { - _jointStates[i].resetTransformChanged(); - } + for (int i = 0; i < _jointStates.size(); i++) { + updateJointState(i, rootTransform); + } + for (int i = 0; i < _jointStates.size(); i++) { + _jointStates[i].resetTransformChanged(); } } diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 0988e40ca8..24967979ea 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -11,7 +11,12 @@ "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "isMoving", "state": "walkFwd" } + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } ] }, { @@ -19,7 +24,77 @@ "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "isNotMoving", "state": "idle" } + { "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" } ] } ] @@ -45,7 +120,8 @@ "startFrame": 0.0, "endFrame": 35.0, "timeScale": 1.0, - "loopFlag": true + "loopFlag": true, + "timeScaleVar": "walkTimeScale" }, "children": [] }, @@ -57,7 +133,8 @@ "startFrame": 0.0, "endFrame": 37.0, "timeScale": 1.0, - "loopFlag": true + "loopFlag": true, + "timeScaleVar": "walkTimeScale" }, "children": [] }, From 471400e5955df489f8fe59c3f0819f6e478106f4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 19:59:05 -0700 Subject: [PATCH 48/52] Fix for jerky behavior when positionDelta is zero. This can occur with vsync disabled. Possibly due to two avatar updates occurring within a single physics time-step. --- libraries/animation/src/Rig.cpp | 38 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index cac5ad3a70..b4652d24ef 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -419,12 +419,21 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos glm::vec3 front = worldRotation * IDENTITY_FRONT; + // It can be more accurate/smooth to use velocity rather than position, + // but some modes (e.g., hmd standing) update position without updating velocity. + // It's very hard to debug hmd standing. (Look down at yourself, or have a second person observe. HMD third person is a bit undefined...) + // So, let's create our own workingVelocity from the worldPosition... + glm::vec3 positionDelta = worldPosition - _lastPosition; + glm::vec3 workingVelocity = positionDelta / deltaTime; + + // But for smoothest (non-hmd standing) results, go ahead and use velocity: + if (!positionDelta.x && !positionDelta.y && !positionDelta.z) { + workingVelocity = worldVelocity; + } + if (_enableAnimGraph) { - // at the moment worldVelocity comes from the Avatar physics body, which is not always correct when - // moving in the HMD, so let's compute our own veloicty. - glm::vec3 worldVel = (worldPosition - _lastPosition) / deltaTime; - glm::vec3 localVel = glm::inverse(worldRotation) * worldVel; + glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity; float forwardSpeed = glm::dot(localVel, IDENTITY_FRONT); float lateralSpeed = glm::dot(localVel, IDENTITY_RIGHT); float turningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime; @@ -496,20 +505,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos glm::vec3 right = worldRotation * IDENTITY_RIGHT; const float PERCEPTIBLE_DELTA = 0.001f; const float PERCEPTIBLE_SPEED = 0.1f; - // It can be more accurate/smooth to use velocity rather than position, - // but some modes (e.g., hmd standing) update position without updating velocity. - // It's very hard to debug hmd standing. (Look down at yourself, or have a second person observe. HMD third person is a bit undefined...) - // So, let's create our own workingVelocity from the worldPosition... - glm::vec3 positionDelta = worldPosition - _lastPosition; - glm::vec3 workingVelocity = positionDelta / deltaTime; - // But for smoothest (non-hmd standing) results, go ahead and use velocity: -#if !WANT_DEBUG + // Note: Separately, we've arranged for starting/stopping animations by role (as we've done here) to pick up where they've left off when fading, // so that you wouldn't notice the start/stop if it happens fast enough (e.g., one frame). But the print below would still be noisy. - if (!positionDelta.x && !positionDelta.y && !positionDelta.z) { - workingVelocity = worldVelocity; - } -#endif float forwardSpeed = glm::dot(workingVelocity, front); float rightLateralSpeed = glm::dot(workingVelocity, right); @@ -1001,19 +999,11 @@ void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) { AnimPose geometryOffset(fbxGeometry.offset); _animSkeleton = std::make_shared(joints, geometryOffset); - // add skeleton to the debug renderer, so we can see it. - // AnimDebugDraw::getInstance().addSkeleton("my-avatar-skeleton", _animSkeleton, AnimPose::identity); - // _animSkeleton->dump(); - // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { _animNode = nodeIn; _animNode->setSkeleton(_animSkeleton); - - // add node to debug renderer, for debugging - // AnimPose xform(glm::vec3(1), glm::quat(), glm::vec3(0, 0, -1)); - // AnimDebugDraw::getInstance().addAnimNode("my-avatar-animation", _animNode, xform); }); connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) { qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; From 30264e9c3dd89d5f6fe36abaf93390b229ee1f34 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 2 Sep 2015 21:04:29 -0700 Subject: [PATCH 49/52] Added animation debug draw items to menu. * Debug Draw Bind Pose - used to display the current avatar's bind pose * Debug Draw Animation - used to display the current avatar's AnimGraph animation. Currently does not work with old animation so it's only valid when Enable Anim Graph is true. * Draw Mesh - used to hide or display the avatar mesh. --- interface/src/Menu.cpp | 6 ++ interface/src/Menu.h | 3 + interface/src/avatar/MyAvatar.cpp | 43 +++++++++++++ interface/src/avatar/MyAvatar.h | 7 +++ libraries/animation/src/AnimNode.h | 1 + libraries/animation/src/Rig.h | 4 ++ libraries/render-utils/src/AnimDebugDraw.cpp | 65 +++++++++++++------- libraries/render-utils/src/AnimDebugDraw.h | 10 +-- 8 files changed, 113 insertions(+), 26 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8f6ce50eab..216fa70121 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -435,6 +435,12 @@ Menu::Menu() { avatar, SLOT(setEnableRigAnimations(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, false, avatar, SLOT(setEnableAnimGraph(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawBindPose, 0, false, + avatar, SLOT(setEnableDebugDrawBindPose(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawAnimPose, 0, false, + avatar, SLOT(setEnableDebugDrawAnimPose(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::MeshVisible, 0, true, + avatar, SLOT(setEnableMeshVisible(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Connexion, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 4a3059259b..3b87de2c43 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -132,6 +132,8 @@ namespace MenuOption { const QString AddRemoveFriends = "Add/Remove Friends..."; const QString AddressBar = "Show Address Bar"; const QString Animations = "Animations..."; + const QString AnimDebugDrawAnimPose = "Debug Draw Animation"; + const QString AnimDebugDrawBindPose = "Debug Draw Bind Pose"; const QString Atmosphere = "Atmosphere"; const QString Attachments = "Attachments..."; const QString AudioNoiseReduction = "Audio Noise Reduction"; @@ -215,6 +217,7 @@ namespace MenuOption { const QString Log = "Log"; const QString LogExtraTimings = "Log Extra Timing Details"; const QString LowVelocityFilter = "Low Velocity Filter"; + const QString MeshVisible = "Draw Mesh"; const QString Mirror = "Mirror"; const QString MuteAudio = "Mute Microphone"; const QString MuteEnvironment = "Mute Environment"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c89a583f32..f6ec1242f2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include "devices/Faceshift.h" @@ -718,6 +719,27 @@ void MyAvatar::setEnableAnimGraph(bool isEnabled) { } } +void MyAvatar::setEnableDebugDrawBindPose(bool isEnabled) { + _enableDebugDrawBindPose = isEnabled; + + if (!isEnabled) { + AnimDebugDraw::getInstance().removeSkeleton("myAvatar"); + } +} + +void MyAvatar::setEnableDebugDrawAnimPose(bool isEnabled) { + _enableDebugDrawAnimPose = isEnabled; + + if (!isEnabled) { + AnimDebugDraw::getInstance().removeAnimNode("myAvatar"); + } +} + +void MyAvatar::setEnableMeshVisible(bool isEnabled) { + render::ScenePointer scene = Application::getInstance()->getMain3DScene(); + _skeletonModel.setVisibleInScene(isEnabled, scene); +} + void MyAvatar::loadData() { Settings settings; settings.beginGroup("Avatar"); @@ -1230,6 +1252,27 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { _skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } + if (_enableDebugDrawBindPose || _enableDebugDrawAnimPose) { + + AnimSkeleton::ConstPointer animSkeleton = _rig->getAnimSkeleton(); + AnimNode::ConstPointer animNode = _rig->getAnimNode(); + + // bones space is rotated + glm::quat rotY180 = glm::angleAxis((float)M_PI, glm::vec3(0.0f, 1.0f, 0.0f)); + AnimPose xform(glm::vec3(1), rotY180 * getOrientation(), getPosition()); + + if (animSkeleton && _enableDebugDrawBindPose) { + glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f); + AnimDebugDraw::getInstance().addSkeleton("myAvatar", animSkeleton, xform, gray); + } + + // This only works for when new anim system is enabled, at the moment. + if (animNode && animSkeleton && _enableDebugDrawAnimPose && _rig->getEnableAnimGraph()) { + glm::vec4 cyan(0.1f, 0.6f, 0.6f, 1.0f); + AnimDebugDraw::getInstance().addAnimNode("myAvatar", animNode, xform, cyan); + } + } + if (shouldDrawHead != _prevShouldDrawHead) { _skeletonModel.setCauterizeBones(!shouldDrawHead); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 25e25e0960..1774eec71f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -190,8 +190,12 @@ public slots: void loadLastRecording(); virtual void rebuildSkeletonBody(); + void setEnableRigAnimations(bool isEnabled); void setEnableAnimGraph(bool isEnabled); + void setEnableDebugDrawBindPose(bool isEnabled); + void setEnableDebugDrawAnimPose(bool isEnabled); + void setEnableMeshVisible(bool isEnabled); signals: void transformChanged(); @@ -314,6 +318,9 @@ private: std::unordered_set _headBoneSet; RigPointer _rig; bool _prevShouldDrawHead; + + bool _enableDebugDrawBindPose = false; + bool _enableDebugDrawAnimPose = false; }; #endif // hifi_MyAvatar_h diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 3a54710b39..4675ae358f 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -43,6 +43,7 @@ public: NumTypes }; using Pointer = std::shared_ptr; + using ConstPointer = std::shared_ptr; using Triggers = std::vector; friend class AnimDebugDraw; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 845878f8a2..109605587e 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -159,6 +159,7 @@ public: void setEnableRig(bool isEnabled) { _enableRig = isEnabled; } void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; } + bool getEnableAnimGraph() const { return _enableAnimGraph; } void updateFromHeadParameters(const HeadParameters& params); void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation, @@ -169,6 +170,9 @@ public: void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry); + AnimNode::ConstPointer getAnimNode() const { return _animNode; } + AnimSkeleton::ConstPointer getAnimSkeleton() const { return _animSkeleton; } + protected: void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 7bbf656f35..1c0f7e0054 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -80,6 +80,13 @@ static uint32_t toRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return ((uint32_t)r | (uint32_t)g << 8 | (uint32_t)b << 16 | (uint32_t)a << 24); } +static uint32_t toRGBA(const glm::vec4& v) { + return toRGBA(static_cast(v.r * 255.0f), + static_cast(v.g * 255.0f), + static_cast(v.b * 255.0f), + static_cast(v.a * 255.0f)); +} + gpu::PipelinePointer AnimDebugDraw::_pipeline; AnimDebugDraw::AnimDebugDraw() : @@ -145,16 +152,16 @@ AnimDebugDraw::~AnimDebugDraw() { } } -void AnimDebugDraw::addSkeleton(std::string key, AnimSkeleton::Pointer skeleton, const AnimPose& rootPose) { - _skeletons[key] = SkeletonInfo(skeleton, rootPose); +void AnimDebugDraw::addSkeleton(std::string key, AnimSkeleton::ConstPointer skeleton, const AnimPose& rootPose, const glm::vec4& color) { + _skeletons[key] = SkeletonInfo(skeleton, rootPose, color); } void AnimDebugDraw::removeSkeleton(std::string key) { _skeletons.erase(key); } -void AnimDebugDraw::addAnimNode(std::string key, AnimNode::Pointer animNode, const AnimPose& rootPose) { - _animNodes[key] = AnimNodeInfo(animNode, rootPose); +void AnimDebugDraw::addAnimNode(std::string key, AnimNode::ConstPointer animNode, const AnimPose& rootPose, const glm::vec4& color) { + _animNodes[key] = AnimNodeInfo(animNode, rootPose, color); } void AnimDebugDraw::removeAnimNode(std::string key) { @@ -164,7 +171,6 @@ void AnimDebugDraw::removeAnimNode(std::string key) { static const uint32_t red = toRGBA(255, 0, 0, 255); static const uint32_t green = toRGBA(0, 255, 0, 255); static const uint32_t blue = toRGBA(0, 0, 255, 255); -static const uint32_t cyan = toRGBA(0, 128, 128, 255); const int NUM_CIRCLE_SLICES = 24; @@ -182,7 +188,7 @@ static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius float rSinTheta = radius * sin(dTheta * i); xRing[i] = finalPose * glm::vec3(0.0f, rCosTheta, rSinTheta); yRing[i] = finalPose * glm::vec3(rCosTheta, 0.0f, rSinTheta); - zRing[i] = finalPose *glm::vec3(rCosTheta, rSinTheta, 0.0f); + zRing[i] = finalPose * glm::vec3(rCosTheta, rSinTheta, 0.0f); } // x-axis @@ -240,8 +246,10 @@ static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius } } -static void addLink(const AnimPose& rootPose, const AnimPose& pose, - const AnimPose& parentPose, float radius, Vertex*& v) { +static void addLink(const AnimPose& rootPose, const AnimPose& pose, const AnimPose& parentPose, + float radius, const glm::vec4& colorVec, Vertex*& v) { + + uint32_t color = toRGBA(colorVec); AnimPose pose0 = rootPose * parentPose; AnimPose pose1 = rootPose * pose; @@ -271,19 +279,19 @@ static void addLink(const AnimPose& rootPose, const AnimPose& pose, for (int i = 0; i < NUM_BASE_CORNERS; i++) { v->pos = boneBaseCorners[i]; - v->rgba = cyan; + v->rgba = color; v++; v->pos = boneBaseCorners[(i + 1) % NUM_BASE_CORNERS]; - v->rgba = cyan; + v->rgba = color; v++; } for (int i = 0; i < NUM_BASE_CORNERS; i++) { v->pos = boneBaseCorners[i]; - v->rgba = cyan; + v->rgba = color; v++; v->pos = boneTip; - v->rgba = cyan; + v->rgba = color; v++; } } else { @@ -292,10 +300,10 @@ static void addLink(const AnimPose& rootPose, const AnimPose& pose, // We add the same line multiple times, so the vertex count is correct. for (int i = 0; i < NUM_BASE_CORNERS * 2; i++) { v->pos = pose0.trans; - v->rgba = cyan; + v->rgba = color; v++; v->pos = pose1.trans; - v->rgba = cyan; + v->rgba = color; v++; } } @@ -319,7 +327,7 @@ void AnimDebugDraw::update() { // figure out how many verts we will need. int numVerts = 0; for (auto&& iter : _skeletons) { - AnimSkeleton::Pointer& skeleton = iter.second.first; + AnimSkeleton::ConstPointer& skeleton = std::get<0>(iter.second); numVerts += skeleton->getNumJoints() * VERTICES_PER_BONE; for (int i = 0; i < skeleton->getNumJoints(); i++) { auto parentIndex = skeleton->getParentIndex(i); @@ -330,7 +338,7 @@ void AnimDebugDraw::update() { } for (auto&& iter : _animNodes) { - AnimNode::Pointer& animNode = iter.second.first; + AnimNode::ConstPointer& animNode = std::get<0>(iter.second); auto poses = animNode->getPosesInternal(); numVerts += poses.size() * VERTICES_PER_BONE; auto skeleton = animNode->getSkeleton(); @@ -346,8 +354,13 @@ void AnimDebugDraw::update() { Vertex* verts = (Vertex*)data._vertexBuffer->editData(); Vertex* v = verts; for (auto&& iter : _skeletons) { - AnimSkeleton::Pointer& skeleton = iter.second.first; - AnimPose& rootPose = iter.second.second; + AnimSkeleton::ConstPointer& skeleton = std::get<0>(iter.second); + AnimPose rootPose = std::get<1>(iter.second); + int hipsIndex = skeleton->nameToJointIndex("Hips"); + if (hipsIndex >= 0) { + rootPose.trans -= skeleton->getRelativeBindPose(hipsIndex).trans; + } + glm::vec4 color = std::get<2>(iter.second); for (int i = 0; i < skeleton->getNumJoints(); i++) { AnimPose pose = skeleton->getAbsoluteBindPose(i); @@ -362,14 +375,22 @@ void AnimDebugDraw::update() { if (parentIndex >= 0) { assert(parentIndex < skeleton->getNumJoints()); AnimPose parentPose = skeleton->getAbsoluteBindPose(parentIndex); - addLink(rootPose, pose, parentPose, radius, v); + addLink(rootPose, pose, parentPose, radius, color, v); } } } for (auto&& iter : _animNodes) { - AnimNode::Pointer& animNode = iter.second.first; - AnimPose& rootPose = iter.second.second; + AnimNode::ConstPointer& animNode = std::get<0>(iter.second); + AnimPose rootPose = std::get<1>(iter.second); + if (animNode->_skeleton) { + int hipsIndex = animNode->_skeleton->nameToJointIndex("Hips"); + if (hipsIndex >= 0) { + rootPose.trans -= animNode->_skeleton->getRelativeBindPose(hipsIndex).trans; + } + } + glm::vec4 color = std::get<2>(iter.second); + auto poses = animNode->getPosesInternal(); auto skeleton = animNode->getSkeleton(); @@ -392,7 +413,7 @@ void AnimDebugDraw::update() { if (parentIndex >= 0) { assert((size_t)parentIndex < poses.size()); // draw line to parent - addLink(rootPose, absAnimPose[i], absAnimPose[parentIndex], radius, v); + addLink(rootPose, absAnimPose[i], absAnimPose[parentIndex], radius, color, v); } } } diff --git a/libraries/render-utils/src/AnimDebugDraw.h b/libraries/render-utils/src/AnimDebugDraw.h index 34ee9c50c0..489213b80b 100644 --- a/libraries/render-utils/src/AnimDebugDraw.h +++ b/libraries/render-utils/src/AnimDebugDraw.h @@ -10,6 +10,8 @@ #ifndef hifi_AnimDebugDraw_h #define hifi_AnimDebugDraw_h +#include + #include "render/Scene.h" #include "gpu/Pipeline.h" #include "AnimNode.h" @@ -25,10 +27,10 @@ public: AnimDebugDraw(); ~AnimDebugDraw(); - void addSkeleton(std::string key, AnimSkeleton::Pointer skeleton, const AnimPose& rootPose); + void addSkeleton(std::string key, AnimSkeleton::ConstPointer skeleton, const AnimPose& rootPose, const glm::vec4& color); void removeSkeleton(std::string key); - void addAnimNode(std::string key, AnimNode::Pointer skeleton, const AnimPose& rootPose); + void addAnimNode(std::string key, AnimNode::ConstPointer animNode, const AnimPose& rootPose, const glm::vec4& color); void removeAnimNode(std::string key); void update(); @@ -40,8 +42,8 @@ protected: static gpu::PipelinePointer _pipeline; - typedef std::pair SkeletonInfo; - typedef std::pair AnimNodeInfo; + typedef std::tuple SkeletonInfo; + typedef std::tuple AnimNodeInfo; std::unordered_map _skeletons; std::unordered_map _animNodes; From 85cb5031525429e6ec34425ca18e1e5c5db645d4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 3 Sep 2015 14:46:44 -0700 Subject: [PATCH 50/52] Made Enable Anim Graph menu option dynamic. It will load the anim graph when enabled and destroy the anim graph when disabled. --- interface/src/avatar/MyAvatar.cpp | 31 +++++++++++++++++++++---------- interface/src/avatar/MyAvatar.h | 2 ++ libraries/animation/src/Rig.cpp | 11 ++++++----- libraries/animation/src/Rig.h | 5 +++-- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f6ec1242f2..d35a1a08f6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -714,8 +714,12 @@ void MyAvatar::setEnableRigAnimations(bool isEnabled) { void MyAvatar::setEnableAnimGraph(bool isEnabled) { _rig->setEnableAnimGraph(isEnabled); - if (!isEnabled) { - // AJT: TODO: FIXME: currently setEnableAnimGraph menu item only works on startup + if (isEnabled) { + if (_skeletonModel.readyToAddToScene()) { + initAnimGraph(); + } + } else { + destroyAnimGraph(); } } @@ -1234,6 +1238,20 @@ 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"); + _skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); +} + +void MyAvatar::destroyAnimGraph() { + _rig->destroyAnimGraph(); + AnimDebugDraw::getInstance().removeSkeleton("myAvatar"); + AnimDebugDraw::getInstance().removeAnimNode("myAvatar"); +} + void MyAvatar::preRender(RenderArgs* renderArgs) { render::ScenePointer scene = Application::getInstance()->getMain3DScene(); @@ -1241,15 +1259,8 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { if (_skeletonModel.initWhenReady(scene)) { initHeadBones(); - _skeletonModel.setCauterizeBoneSet(_headBoneSet); - - // 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"); - - _skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); + initAnimGraph(); } if (_enableDebugDrawBindPose || _enableDebugDrawAnimPose) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1774eec71f..83bfb0f808 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -291,6 +291,8 @@ private: void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void maybeUpdateBillboard(); void initHeadBones(); + void initAnimGraph(); + void destroyAnimGraph(); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b4652d24ef..4abef61ddc 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -187,12 +187,13 @@ void Rig::deleteAnimations() { removeAnimationHandle(animation); } _animationHandles.clear(); + destroyAnimGraph(); +} - if (_enableAnimGraph) { - _animSkeleton = nullptr; - _animLoader = nullptr; - _animNode = nullptr; - } +void Rig::destroyAnimGraph() { + _animSkeleton = nullptr; + _animLoader = nullptr; + _animNode = nullptr; } void Rig::initJointStates(QVector states, glm::mat4 rootTransform, diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 109605587e..c264d348f4 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -83,6 +83,7 @@ public: bool isRunningRole(const QString& role); // There can be multiple animations per role, so this is more general than isRunningAnimation. const QList& getRunningAnimations() const { return _runningAnimations; } void deleteAnimations(); + void destroyAnimGraph(); const QList& getAnimationHandles() const { return _animationHandles; } void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); @@ -193,8 +194,8 @@ public: QList _animationHandles; QList _runningAnimations; - bool _enableRig; - bool _enableAnimGraph; + bool _enableRig = false; + bool _enableAnimGraph = false; glm::vec3 _lastFront; glm::vec3 _lastPosition; From dcecd7b73a7c97ac51fe1a49daf244b2c08c3c11 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 3 Sep 2015 19:13:39 -0700 Subject: [PATCH 51/52] Added hysteresis to the state machine for turning and moving states. This should help a bit with the jerkiness, as state transitions occur less often due to noise or the user straddling the boundary between two states. --- libraries/animation/src/Rig.cpp | 34 +++++++++++++++++++++++++++------ libraries/animation/src/Rig.h | 6 ++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 4abef61ddc..9f22a54c29 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -427,10 +427,12 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos glm::vec3 positionDelta = worldPosition - _lastPosition; glm::vec3 workingVelocity = positionDelta / deltaTime; +#if !WANT_DEBUG // But for smoothest (non-hmd standing) results, go ahead and use velocity: if (!positionDelta.x && !positionDelta.y && !positionDelta.z) { workingVelocity = worldVelocity; } +#endif if (_enableAnimGraph) { @@ -454,12 +456,29 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotTurning", true); const float ANIM_WALK_SPEED = 1.4f; // m/s - _animVars.set("walkTimeScale", glm::clamp(0.1f, 2.0f, glm::length(localVel) / ANIM_WALK_SPEED)); + _animVars.set("walkTimeScale", glm::clamp(0.5f, 2.0f, glm::length(localVel) / ANIM_WALK_SPEED)); - const float MOVE_SPEED_THRESHOLD = 0.01f; // m/sec - const float TURN_SPEED_THRESHOLD = 0.5f; // rad/sec - if (glm::length(localVel) > MOVE_SPEED_THRESHOLD) { - if (fabs(forwardSpeed) > fabs(lateralSpeed)) { + const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec + const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec + const float TURN_ENTER_SPEED_THRESHOLD = 0.5f; // rad/sec + const float TURN_EXIT_SPEED_THRESHOLD = 0.2f; // rad/sec + + float moveThresh; + if (_state != RigRole::Move) { + moveThresh = MOVE_ENTER_SPEED_THRESHOLD; + } else { + moveThresh = MOVE_EXIT_SPEED_THRESHOLD; + } + + float turnThresh; + if (_state != RigRole::Turn) { + turnThresh = TURN_ENTER_SPEED_THRESHOLD; + } else { + turnThresh = TURN_EXIT_SPEED_THRESHOLD; + } + + if (glm::length(localVel) > moveThresh) { + if (fabs(forwardSpeed) > 0.5f * fabs(lateralSpeed)) { if (forwardSpeed > 0.0f) { // forward _animVars.set("isMovingForward", true); @@ -481,8 +500,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotMoving", false); } } + _state = RigRole::Move; } else { - if (fabs(turningSpeed) > TURN_SPEED_THRESHOLD) { + if (fabs(turningSpeed) > turnThresh) { if (turningSpeed > 0.0f) { // turning right _animVars.set("isTurningRight", true); @@ -492,8 +512,10 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isTurningLeft", true); _animVars.set("isNotTurning", false); } + _state = RigRole::Turn; } else { // idle + _state = RigRole::Idle; } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index c264d348f4..0bf0645b4d 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -203,6 +203,12 @@ public: std::shared_ptr _animSkeleton; std::unique_ptr _animLoader; AnimVariantMap _animVars; + enum class RigRole { + Idle = 0, + Turn, + Move + }; + RigRole _state = RigRole::Idle; }; #endif /* defined(__hifi__Rig__) */ From 3f258c89b42ea64722506f46bbe629b09878d375 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 4 Sep 2015 11:03:59 -0700 Subject: [PATCH 52/52] Fix transition from AG+Rig to AG only. --- libraries/animation/src/Rig.cpp | 1 - libraries/render-utils/src/Model.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 9f22a54c29..f2ea922ab7 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -187,7 +187,6 @@ void Rig::deleteAnimations() { removeAnimationHandle(animation); } _animationHandles.clear(); - destroyAnimGraph(); } void Rig::destroyAnimGraph() { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 14b1c58321..3f0516b415 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1419,6 +1419,7 @@ void Model::deleteGeometry() { _rig->clearJointStates(); _meshStates.clear(); _rig->deleteAnimations(); + _rig->destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); }