mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 08:03:34 +02:00
Added AnimNodeLoader which loads AnimClip nodes from json
* added tests and sample json file
This commit is contained in:
parent
35196a0059
commit
343b2ccf9d
8 changed files with 334 additions and 7 deletions
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,13 +10,51 @@
|
|||
#ifndef hifi_AnimNode_h
|
||||
#define hifi_AnimNode_h
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
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<AnimNode> child) { _children.push_back(child); }
|
||||
void removeChild(std::shared_ptr<AnimNode> child) {
|
||||
auto iter = std::find(_children.begin(), _children.end(), child);
|
||||
if (iter != _children.end()) {
|
||||
_children.erase(iter);
|
||||
}
|
||||
}
|
||||
const std::shared_ptr<AnimNode>& 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<std::shared_ptr<AnimNode>> _children;
|
||||
|
||||
// no copies
|
||||
AnimNode(const AnimNode&) = delete;
|
||||
AnimNode& operator=(const AnimNode&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimNode_h
|
||||
|
|
173
libraries/animation/src/AnimNodeLoader.cpp
Normal file
173
libraries/animation/src/AnimNodeLoader.cpp
Normal file
|
@ -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 <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qfile.h>
|
||||
|
||||
#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<AnimNode> (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QString& jsonUrl);
|
||||
|
||||
static std::shared_ptr<AnimNode> 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<AnimNode> 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<AnimNode> 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<AnimClip>(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag);
|
||||
}
|
||||
|
||||
std::shared_ptr<AnimNode> 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);
|
||||
}
|
23
libraries/animation/src/AnimNodeLoader.h
Normal file
23
libraries/animation/src/AnimNodeLoader.h
Normal file
|
@ -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 <memory>
|
||||
|
||||
class AnimNode;
|
||||
|
||||
class AnimNodeLoader {
|
||||
public:
|
||||
// TODO: load from url
|
||||
std::shared_ptr<AnimNode> load(const std::string& filename) const;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimNodeLoader
|
|
@ -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<AnimClip>(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<AnimNode> 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);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ class AnimClipTests : public QObject {
|
|||
private slots:
|
||||
void testAccessors();
|
||||
void testEvaulate();
|
||||
void testLoader();
|
||||
};
|
||||
|
||||
#endif // hifi_TransformTests_h
|
||||
|
|
52
tests/animation/src/test.json
Normal file
52
tests/animation/src/test.json
Normal file
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue