3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-27 00:55:42 +02:00

Added AnimBlendLinear + tests.

MyAvatar now does a sine wave blend between a walk
and a walk animation.
This commit is contained in:
Anthony J. Thibault 2015-08-04 18:13:13 -07:00
parent da3d35cdfc
commit 5d83976e2a
12 changed files with 211 additions and 44 deletions

View file

@ -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<AnimBlendLinear>(_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<FBXJoint> joints;
for (auto& joint : geom.joints) {
joints.push_back(joint);
}
auto skeleton = make_shared<AnimSkeleton>(joints);
AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset());
AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform);
// create a blend node
auto blend = make_shared<AnimBlendLinear>("blend", 0.5f);
auto idle = make_shared<AnimClip>("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<AnimClip>("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<FBXJoint> joints;
for (auto& joint : geom.joints) {
joints.push_back(joint);
}
auto skeleton = make_shared<AnimSkeleton>(joints);
AnimPose xform(_skeletonModel.getScale(), glm::quat(), _skeletonModel.getOffset());
AnimDebugDraw::getInstance().addSkeleton("my-avatar", skeleton, xform);
_animNode = make_shared<AnimClip>("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) {

View file

@ -189,6 +189,7 @@ signals:
private:
void setupNewAnimationSystem();
QByteArray toByteArray();
void simulate(float deltaTime);
void updateFromTrackers(float deltaTime);

View file

@ -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<AnimPose>& 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<AnimPose>& AnimBlendLinear::getPosesInternal() const {
return _poses;
}

View file

@ -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<AnimPose>& evaluate(float dt) override;
void setAlpha(float alpha) { _alpha = alpha; }
float getAlpha() const { return _alpha; }
protected:
// for AnimDebugDraw rendering
virtual const std::vector<AnimPose>& getPosesInternal() const override;
std::vector<AnimPose> _poses;
float _alpha;
// no copies
AnimBlendLinear(const AnimBlendLinear&) = delete;
AnimBlendLinear& operator=(const AnimBlendLinear&) = delete;
};
#endif // hifi_AnimBlendLinear_h

View file

@ -103,11 +103,11 @@ const std::vector<AnimPose>& 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);
}
}

View file

@ -27,6 +27,7 @@ public:
enum Type {
ClipType = 0,
BlendLinearType,
NumTypes
};
typedef std::shared_ptr<AnimNode> 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<AnimPose>& evaluate(float dt) = 0;
protected:
virtual void setSkeletonInternal(AnimSkeleton::Pointer skeleton) {
_skeleton = skeleton;
}
// for AnimDebugDraw rendering
virtual const std::vector<AnimPose>& getPosesInternal() const = 0;

View file

@ -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<AnimClip>(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<AnimBlendLinear>(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

View file

@ -16,8 +16,13 @@ class AnimNode;
class AnimNodeLoader {
public:
AnimNodeLoader();
// TODO: load from url
std::shared_ptr<AnimNode> load(const std::string& filename) const;
// no copies
AnimNodeLoader(const AnimNodeLoader&) = delete;
AnimNodeLoader& operator=(const AnimNodeLoader&) = delete;
};
#endif // hifi_AnimNodeLoader

View file

@ -56,6 +56,10 @@ protected:
std::vector<FBXJoint> _joints;
std::vector<AnimPose> _absoluteBindPoses;
std::vector<AnimPose> _relativeBindPoses;
// no copies
AnimSkeleton(const AnimSkeleton&) = delete;
AnimSkeleton& operator=(const AnimSkeleton&) = delete;
};
#endif

View file

@ -45,6 +45,10 @@ protected:
std::unordered_map<std::string, SkeletonInfo> _skeletons;
std::unordered_map<std::string, AnimNodeInfo> _animNodes;
// no copies
AnimDebugDraw(const AnimDebugDraw&) = delete;
AnimDebugDraw& operator=(const AnimDebugDraw&) = delete;
};
#endif // hifi_AnimDebugDraw

View file

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

View file

@ -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": []
},