From 5d83976e2abd6c46a094483b6b6a810206b0ee12 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 4 Aug 2015 18:13:13 -0700 Subject: [PATCH] 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": [] },