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:
parent
da3d35cdfc
commit
5d83976e2a
12 changed files with 211 additions and 44 deletions
interface/src/avatar
libraries
animation/src
AnimBlendLinear.cppAnimBlendLinear.hAnimClip.cppAnimNode.hAnimNodeLoader.cppAnimNodeLoader.hAnimSkeleton.h
render-utils/src
tests/animation/src
|
@ -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) {
|
||||
|
|
|
@ -189,6 +189,7 @@ signals:
|
|||
|
||||
private:
|
||||
|
||||
void setupNewAnimationSystem();
|
||||
QByteArray toByteArray();
|
||||
void simulate(float deltaTime);
|
||||
void updateFromTrackers(float deltaTime);
|
||||
|
|
59
libraries/animation/src/AnimBlendLinear.cpp
Normal file
59
libraries/animation/src/AnimBlendLinear.cpp
Normal 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;
|
||||
}
|
39
libraries/animation/src/AnimBlendLinear.h
Normal file
39
libraries/animation/src/AnimBlendLinear.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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": []
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue