Added network resource download support to AnimNodeLoader.

This commit is contained in:
Anthony J. Thibault 2015-08-24 19:00:12 -07:00
parent bde75e9e51
commit 7a2ca047cb
5 changed files with 125 additions and 84 deletions

View file

@ -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<AnimClip>(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<AnimBlendLinear>(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");
}

View file

@ -12,13 +12,36 @@
#include <memory>
class AnimNode;
#include <QString.h>
#include <QUrl.h>
#include <QNetworkReply.h>
#include "AnimNode.h"
class Resource;
class AnimNodeLoader : public QObject {
Q_OBJECT
class AnimNodeLoader {
public:
AnimNodeLoader();
// TODO: load from url
std::shared_ptr<AnimNode> 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;

View file

@ -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<AnimBlendLinear>(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<AnimBlendLinear>(node);
QVERIFY(blend->getAlpha() == 0.5f);
std::shared_ptr<AnimNode> 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<AnimNode> nodes[3] = { node->getChild(0), node->getChild(1), node->getChild(2) };
auto test01 = std::static_pointer_cast<AnimClip>(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<AnimClip>(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<AnimClip>(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<AnimClip>(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);
}

View file

@ -13,10 +13,6 @@
#include <QtTest/QtTest>
#include <glm/glm.hpp>
inline float getErrorDifference(float a, float b) {
return fabs(a - b);
}
class AnimClipTests : public QObject {
Q_OBJECT
private slots:

View file

@ -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<JointState> 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<AvatarRig>();
_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);
}