From 7a2ca047cbcde94f5e87c0aa54e0dbf9dd79f976 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Aug 2015 19:00:12 -0700 Subject: [PATCH] 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); }