mirror of
https://github.com/overte-org/overte.git
synced 2025-04-19 15:03:53 +02:00
Added network resource download support to AnimNodeLoader.
This commit is contained in:
parent
bde75e9e51
commit
7a2ca047cb
5 changed files with 125 additions and 84 deletions
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue