Initial version of AnimStateMachine.

No interpolation support, but basic avatar.json is working
with two states and two transitions between them.
This commit is contained in:
Anthony J. Thibault 2015-08-27 20:41:53 -07:00
parent 637e3b0a15
commit 3286a32afc
11 changed files with 453 additions and 123 deletions

View file

@ -166,6 +166,15 @@ void MyAvatar::update(float deltaTime) {
if (_animNode) {
static float t = 0.0f;
_animVars.set("sine", 0.5f * sin(t) + 0.5f);
if (glm::length(getVelocity()) > 0.01) {
_animVars.set("isMoving", true);
_animVars.set("isNotMoving", false);
} else {
_animVars.set("isMoving", false);
_animVars.set("isNotMoving", true);
}
t += deltaTime;
_animNode->evaluate(_animVars, deltaTime);
}
@ -1236,7 +1245,7 @@ void MyAvatar::setupNewAnimationSystem() {
// load the anim graph
// https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/c4a9223e97b1d00b423b87542a2a57895ca72d21/avatar.json");
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/c684000794675bc84ed63efefc21870e47c58d6a/avatar.json");
_animLoader.reset(new AnimNodeLoader(graphUrl));
connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) {
_animNode = nodeIn;

View file

@ -13,7 +13,7 @@
#include "AnimUtil.h"
AnimBlendLinear::AnimBlendLinear(const std::string& id, float alpha) :
AnimNode(AnimNode::BlendLinearType, id),
AnimNode(AnimNode::Type::BlendLinear, id),
_alpha(alpha) {
}

View file

@ -13,7 +13,7 @@
#include "AnimUtil.h"
AnimClip::AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) :
AnimNode(AnimNode::ClipType, id),
AnimNode(AnimNode::Type::Clip, id),
_startFrame(startFrame),
_endFrame(endFrame),
_timeScale(timeScale),

View file

@ -25,7 +25,7 @@ class QJsonObject;
// Base class for all elements in the animation blend tree.
// It provides the following categories of functions:
//
// * id getter, id is a string name useful for debugging and searching.
// * id getter, id is used to identify a node, useful for debugging and node searching.
// * type getter, helpful for determining the derived type of this node.
// * hierarchy accessors, for adding, removing and iterating over child AnimNodes
// * skeleton accessors, the skeleton is from the model whose bones we are going to manipulate
@ -33,15 +33,17 @@ class QJsonObject;
class AnimNode {
public:
friend class AnimDebugDraw;
enum Type {
ClipType = 0,
BlendLinearType,
OverlayType,
enum class Type {
Clip = 0,
BlendLinear,
Overlay,
StateMachine,
NumTypes
};
typedef std::shared_ptr<AnimNode> Pointer;
using Pointer = std::shared_ptr<AnimNode>;
friend class AnimDebugDraw;
friend void buildChildMap(std::map<std::string, Pointer>& map, Pointer node);
AnimNode(Type type, const std::string& id) : _type(type), _id(id) {}
virtual ~AnimNode() {}

View file

@ -18,31 +18,52 @@
#include "AnimationLogging.h"
#include "AnimOverlay.h"
#include "AnimNodeLoader.h"
#include "AnimStateMachine.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" },
{ AnimNode::BlendLinearType, "blendLinear" },
{ AnimNode::OverlayType, "overlay" }
};
typedef AnimNode::Pointer (*NodeLoaderFunc)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
// factory functions
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 AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = {
loadClipNode,
loadBlendLinearNode,
loadOverlayNode
};
// called after children have been loaded
static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
static bool processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
static bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static const char* animNodeTypeToString(AnimNode::Type type) {
switch (type) {
case AnimNode::Type::Clip: return "clip";
case AnimNode::Type::BlendLinear: return "blendLinear";
case AnimNode::Type::Overlay: return "overlay";
case AnimNode::Type::StateMachine: return "stateMachine";
};
return nullptr;
}
static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
switch (type) {
case AnimNode::Type::Clip: return loadClipNode;
case AnimNode::Type::BlendLinear: return loadBlendLinearNode;
case AnimNode::Type::Overlay: return loadOverlayNode;
case AnimNode::Type::StateMachine: return loadStateMachineNode;
};
return nullptr;
}
static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
switch (type) {
case AnimNode::Type::Clip: return processClipNode;
case AnimNode::Type::BlendLinear: return processBlendLinearNode;
case AnimNode::Type::Overlay: return processOverlayNode;
case AnimNode::Type::StateMachine: return processStateMachineNode;
};
return nullptr;
}
#define READ_STRING(NAME, JSON_OBJ, ID, URL) \
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
@ -82,12 +103,15 @@ static NodeLoaderFunc nodeLoaderFuncs[AnimNode::NumTypes] = {
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;
// O(n), move to map when number of types becomes large.
const int NUM_TYPES = static_cast<int>(AnimNode::Type::NumTypes);
for (int i = 0; i < NUM_TYPES; i++ ) {
AnimNode::Type type = static_cast<AnimNode::Type>(i);
if (str == animNodeTypeToString(type)) {
return type;
}
}
return AnimNode::NumTypes;
return AnimNode::Type::NumTypes;
}
static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) {
@ -105,7 +129,7 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
}
QString typeStr = typeVal.toString();
AnimNode::Type type = stringToEnum(typeStr);
if (type == AnimNode::NumTypes) {
if (type == AnimNode::Type::NumTypes) {
qCCritical(animation) << "AnimNodeLoader, unknown node type" << typeStr << ", id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
@ -117,23 +141,28 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
}
auto dataObj = dataValue.toObject();
assert((int)type >= 0 && type < AnimNode::NumTypes);
auto node = nodeLoaderFuncs[type](dataObj, id, jsonUrl);
assert((int)type >= 0 && type < AnimNode::Type::NumTypes);
auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl);
auto childrenValue = jsonObj.value("children");
if (!childrenValue.isArray()) {
qCCritical(animation) << "AnimNodeLoader, bad array \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto childrenAry = childrenValue.toArray();
for (const auto& childValue : childrenAry) {
auto childrenArray = childrenValue.toArray();
for (const auto& childValue : childrenArray) {
if (!childValue.isObject()) {
qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
node->addChild(loadNode(childValue.toObject(), jsonUrl));
}
return node;
if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) {
return node;
} else {
return nullptr;
}
}
static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
@ -230,6 +259,119 @@ static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QStri
return node;
}
static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
auto node = std::make_shared<AnimStateMachine>(id.toStdString());
return node;
}
static void buildChildMap(std::map<std::string, AnimNode::Pointer>& map, AnimNode::Pointer node) {
for ( auto child : node->_children ) {
map.insert(std::pair<std::string, AnimNode::Pointer>(child->_id, child));
}
}
static bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) {
auto smNode = std::static_pointer_cast<AnimStateMachine>(node);
assert(smNode);
READ_STRING(currentState, jsonObj, nodeId, jsonUrl);
auto statesValue = jsonObj.value("states");
if (!statesValue.isArray()) {
qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in stateMachine node, id =" << nodeId << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
// build a map for all children by name.
std::map<std::string, AnimNode::Pointer> childMap;
buildChildMap(childMap, node);
// first pass parse all the states and build up the state and transition map.
using StringPair = std::pair<std::string, std::string>;
using TransitionMap = std::multimap<AnimStateMachine::State::Pointer, StringPair>;
TransitionMap transitionMap;
using StateMap = std::map<std::string, AnimStateMachine::State::Pointer>;
StateMap stateMap;
auto statesArray = statesValue.toArray();
for (const auto& stateValue : statesArray) {
if (!stateValue.isObject()) {
qCCritical(animation) << "AnimNodeLoader, bad state object in \"states\", id =" << nodeId << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto stateObj = stateValue.toObject();
READ_STRING(id, stateObj, nodeId, jsonUrl);
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl);
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl);
READ_OPTIONAL_STRING(interpTargetVar, stateObj);
READ_OPTIONAL_STRING(interpDurationVar, stateObj);
auto stdId = id.toStdString();
auto iter = childMap.find(stdId);
if (iter == childMap.end()) {
qCCritical(animation) << "AnimNodeLoader, could not find stateMachine child (state) with nodeId =" << nodeId << "stateId =" << id << "url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto statePtr = std::make_shared<AnimStateMachine::State>(stdId, iter->second, interpTarget, interpDuration);
assert(statePtr);
if (!interpTargetVar.isEmpty()) {
statePtr->setInterpTargetVar(interpTargetVar.toStdString());
}
if (!interpDurationVar.isEmpty()) {
statePtr->setInterpDurationVar(interpDurationVar.toStdString());
}
smNode->addState(statePtr);
stateMap.insert(StateMap::value_type(statePtr->getID(), statePtr));
auto transitionsValue = stateObj.value("transitions");
if (!transitionsValue.isArray()) {
qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in stateMachine node, stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto transitionsArray = transitionsValue.toArray();
for (const auto& transitionValue : transitionsArray) {
if (!transitionValue.isObject()) {
qCritical(animation) << "AnimNodeLoader, bad transition object in \"transtions\", stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto transitionObj = transitionValue.toObject();
READ_STRING(var, transitionObj, nodeId, jsonUrl);
READ_STRING(state, transitionObj, nodeId, jsonUrl);
transitionMap.insert(TransitionMap::value_type(statePtr, StringPair(var.toStdString(), state.toStdString())));
}
}
// second pass: now iterate thru all transitions and add them to the appropriate states.
for (auto& transition : transitionMap) {
AnimStateMachine::State::Pointer srcState = transition.first;
auto iter = stateMap.find(transition.second.second);
if (iter != stateMap.end()) {
srcState->addTransition(AnimStateMachine::State::Transition(transition.second.first, iter->second));
} else {
qCCritical(animation) << "AnimNodeLoader, bad state machine transtion from srcState =" << srcState->_id.c_str() << "dstState =" << transition.second.second.c_str() << "nodeId =" << nodeId << "url = " << jsonUrl.toDisplayString();
return nullptr;
}
}
auto iter = stateMap.find(currentState.toStdString());
if (iter == stateMap.end()) {
qCCritical(animation) << "AnimNodeLoader, bad currentState =" << currentState << "could not find child node" << "id =" << nodeId << "url = " << jsonUrl.toDisplayString();
}
smNode->setCurrentState(iter->second);
return true;
}
AnimNodeLoader::AnimNodeLoader(const QUrl& url) :
_url(url),
_resource(nullptr) {

View file

@ -12,7 +12,7 @@
#include <queue>
AnimOverlay::AnimOverlay(const std::string& id, BoneSet boneSet, float alpha) :
AnimNode(AnimNode::OverlayType, id), _boneSet(boneSet), _alpha(alpha) {
AnimNode(AnimNode::Type::Overlay, id), _boneSet(boneSet), _alpha(alpha) {
}
AnimOverlay::~AnimOverlay() {

View file

@ -39,12 +39,12 @@ inline QDebug operator<<(QDebug debug, const AnimPose& pose) {
return debug;
}
typedef std::vector<AnimPose> AnimPoseVec;
using AnimPoseVec = std::vector<AnimPose>;
class AnimSkeleton {
public:
typedef std::shared_ptr<AnimSkeleton> Pointer;
typedef std::shared_ptr<const AnimSkeleton> ConstPointer;
using Pointer = std::shared_ptr<AnimSkeleton>;
using ConstPointer = std::shared_ptr<const AnimSkeleton>;
AnimSkeleton(const std::vector<FBXJoint>& joints);
int nameToJointIndex(const QString& jointName) const;

View file

@ -0,0 +1,94 @@
//
// AnimStateMachine.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 "AnimStateMachine.h"
#include "AnimationLogging.h"
AnimStateMachine::AnimStateMachine(const std::string& id) :
AnimNode(AnimNode::Type::StateMachine, id) {
}
AnimStateMachine::~AnimStateMachine() {
}
const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, float dt) {
std::string desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID());
if (_currentState->getID() != desiredStateID) {
// switch states
bool foundState = false;
for (auto& state : _states) {
if (state->getID() == desiredStateID) {
switchState(state);
foundState = true;
break;
}
}
if (!foundState) {
qCCritical(animation) << "AnimStateMachine could not find state =" << desiredStateID.c_str() << ", referenced by _currentStateVar =" << _currentStateVar.c_str();
}
}
// evaluate currentState transitions
auto desiredState = evaluateTransitions(animVars);
if (desiredState != _currentState) {
switchState(desiredState);
}
if (_duringInterp) {
// TODO: do interpolation
/*
const float FRAMES_PER_SECOND = 30.0f;
// blend betwen poses
blend(_poses.size(), _prevPose, _nextPose, _alpha, &_poses[0]);
*/
} else {
// eval current state
assert(_currentState);
auto currentStateNode = _currentState->getNode();
assert(currentStateNode);
_poses = currentStateNode->evaluate(animVars, dt);
}
qCDebug(animation) << "StateMachine::evalute";
return _poses;
}
void AnimStateMachine::setCurrentState(State::Pointer state) {
_currentState = state;
}
void AnimStateMachine::addState(State::Pointer state) {
_states.push_back(state);
}
void AnimStateMachine::switchState(State::Pointer desiredState) {
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID().c_str() << "->" << desiredState->getID().c_str();
// TODO: interp.
_currentState = desiredState;
}
AnimStateMachine::State::Pointer AnimStateMachine::evaluateTransitions(const AnimVariantMap& animVars) const {
assert(_currentState);
for (auto& transition : _currentState->_transitions) {
if (animVars.lookup(transition._var, false)) {
return transition._state;
}
}
return _currentState;
}
const AnimPoseVec& AnimStateMachine::getPosesInternal() const {
return _poses;
}

View file

@ -1,66 +1,117 @@
//
// AnimStateMachine.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_AnimStateMachine_h
#define hifi_AnimStateMachine_h
// AJT: post-pone state machine work.
#if 0
#include <string>
#include <vector>
#include "AnimNode.h"
class AnimStateMachine : public AnimNode {
public:
friend class AnimDebugDraw;
friend class AnimNodeLoader;
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
AnimStateMachine(const std::string& id, float alpha);
protected:
class State {
public:
friend AnimStateMachine;
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
using Pointer = std::shared_ptr<State>;
using ConstPointer = std::shared_ptr<const State>;
class Transition {
public:
friend AnimStateMachine;
Transition(const std::string& var, State::Pointer state) : _var(var), _state(state) {}
protected:
std::string _var;
State::Pointer _state;
};
State(const std::string& id, AnimNode::Pointer node, float interpTarget, float interpDuration) :
_id(id),
_node(node),
_interpTarget(interpTarget),
_interpDuration(interpDuration) {}
void setInterpTargetVar(const std::string& interpTargetVar) { _interpTargetVar = interpTargetVar; }
void setInterpDurationVar(const std::string& interpDurationVar) { _interpDurationVar = interpDurationVar; }
AnimNode::Pointer getNode() const { return _node; }
const std::string& getID() const { return _id; }
protected:
void setInterpTarget(float interpTarget) { _interpTarget = interpTarget; }
void setInterpDuration(float interpDuration) { _interpDuration = interpDuration; }
void addTransition(const Transition& transition) { _transitions.push_back(transition); }
std::string _id;
AnimNode::Pointer _node;
float _interpTarget; // frames
float _interpDuration; // frames
std::string _interpTargetVar;
std::string _interpDurationVar;
std::vector<Transition> _transitions;
private:
// no copies
State(const State&) = delete;
State& operator=(const State&) = delete;
};
public:
AnimStateMachine(const std::string& id);
virtual ~AnimStateMachine() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt) override;
class AnimTransition;
class AnimState {
public:
typedef std::shared_ptr<AnimState> Pointer;
AnimState(const std::string& name, AnimNode::Pointer node, float interpFrame, float interpDuration) :
_name(name),
_node(node),
_interpFrame(interpFrame),
_interpDuration(interpDuration) {}
protected:
std::string _name;
AnimNode::Pointer _node;
float _interpFrame;
float _interpDuration;
std::vector<AnimTransition> _transitions;
private:
// no copies
AnimState(const AnimState&) = delete;
AnimState& operator=(const AnimState&) = delete;
};
class AnimTransition {
AnimTransition(const std::string& trigger, AnimState::Pointer state) : _trigger(trigger), _state(state) {}
protected:
std::string _trigger;
AnimState::Pointer _state;
};
void addState(AnimState::Pointer state);
void removeState(AnimState::Pointer state);
void setCurrentState(AnimState::Pointer state);
void setCurrentStateVar(std::string& currentStateVar) { _currentStateVar = currentStateVar; }
protected:
void setCurrentState(State::Pointer state);
void addState(State::Pointer state);
void switchState(State::Pointer desiredState);
State::Pointer evaluateTransitions(const AnimVariantMap& animVars) const;
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
AnimPoseVec _poses;
std::vector<AnimState::Pointer> _states;
AnimState::Pointer _currentState;
AnimState::Pointer _defaultState;
// interpolation state
bool _duringInterp = false;
float _interpFrame;
float _interpDuration;
float _alpha;
AnimPoseVec _prevPoses;
AnimPoseVec _nextPoses;
State::Pointer _currentState;
std::vector<State::Pointer> _states;
std::string _currentStateVar;
private:
// no copies
AnimStateMachine(const AnimBlendLinear&) = delete;
AnimStateMachine& operator=(const AnimBlendLinear&) = delete;
AnimStateMachine(const AnimStateMachine&) = delete;
AnimStateMachine& operator=(const AnimStateMachine&) = delete;
};
#endif
#endif // hifi_AnimBlendLinear_h
#endif // hifi_AnimStateMachine_h

View file

@ -18,47 +18,53 @@
class AnimVariant {
public:
enum Type {
BoolType = 0,
IntType,
FloatType,
Vec3Type,
QuatType,
Mat4Type,
enum class Type {
Bool = 0,
Int,
Float,
Vec3,
Quat,
Mat4,
String,
NumTypes
};
AnimVariant() : _type(BoolType) { memset(&_val, 0, sizeof(_val)); }
AnimVariant(bool value) : _type(BoolType) { _val.boolVal = value; }
AnimVariant(int value) : _type(IntType) { _val.intVal = value; }
AnimVariant(float value) : _type(FloatType) { _val.floats[0] = value; }
AnimVariant(const glm::vec3& value) : _type(Vec3Type) { *reinterpret_cast<glm::vec3*>(&_val) = value; }
AnimVariant(const glm::quat& value) : _type(QuatType) { *reinterpret_cast<glm::quat*>(&_val) = value; }
AnimVariant(const glm::mat4& value) : _type(Mat4Type) { *reinterpret_cast<glm::mat4*>(&_val) = value; }
AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); }
AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; }
AnimVariant(int value) : _type(Type::Int) { _val.intVal = value; }
AnimVariant(float value) : _type(Type::Float) { _val.floats[0] = value; }
AnimVariant(const glm::vec3& value) : _type(Type::Vec3) { *reinterpret_cast<glm::vec3*>(&_val) = value; }
AnimVariant(const glm::quat& value) : _type(Type::Quat) { *reinterpret_cast<glm::quat*>(&_val) = value; }
AnimVariant(const glm::mat4& value) : _type(Type::Mat4) { *reinterpret_cast<glm::mat4*>(&_val) = value; }
AnimVariant(const std::string& value) : _type(Type::String) { _stringVal = value; }
bool isBool() const { return _type == BoolType; }
bool isInt() const { return _type == IntType; }
bool isFloat() const { return _type == FloatType; }
bool isVec3() const { return _type == Vec3Type; }
bool isQuat() const { return _type == QuatType; }
bool isMat4() const { return _type == Mat4Type; }
bool isBool() const { return _type == Type::Bool; }
bool isInt() const { return _type == Type::Int; }
bool isFloat() const { return _type == Type::Float; }
bool isVec3() const { return _type == Type::Vec3; }
bool isQuat() const { return _type == Type::Quat; }
bool isMat4() const { return _type == Type::Mat4; }
bool isString() const { return _type == Type::String; }
void setBool(bool value) { assert(_type == BoolType); _val.boolVal = value; }
void setInt(int value) { assert(_type == IntType); _val.intVal = value; }
void setFloat(float value) { assert(_type == FloatType); _val.floats[0] = value; }
void setVec3(const glm::vec3& value) { assert(_type == Vec3Type); *reinterpret_cast<glm::vec3*>(&_val) = value; }
void setQuat(const glm::quat& value) { assert(_type == QuatType); *reinterpret_cast<glm::quat*>(&_val) = value; }
void setMat4(const glm::mat4& value) { assert(_type == Mat4Type); *reinterpret_cast<glm::mat4*>(&_val) = value; }
void setBool(bool value) { assert(_type == Type::Bool); _val.boolVal = value; }
void setInt(int value) { assert(_type == Type::Int); _val.intVal = value; }
void setFloat(float value) { assert(_type == Type::Float); _val.floats[0] = value; }
void setVec3(const glm::vec3& value) { assert(_type == Type::Vec3); *reinterpret_cast<glm::vec3*>(&_val) = value; }
void setQuat(const glm::quat& value) { assert(_type == Type::Quat); *reinterpret_cast<glm::quat*>(&_val) = value; }
void setMat4(const glm::mat4& value) { assert(_type == Type::Mat4); *reinterpret_cast<glm::mat4*>(&_val) = value; }
void setString(const std::string& value) { assert(_type == Type::String); _stringVal = value; }
bool getBool() const { assert(_type == BoolType); return _val.boolVal; }
int getInt() const { assert(_type == IntType); return _val.intVal; }
float getFloat() const { assert(_type == FloatType); return _val.floats[0]; }
const glm::vec3& getVec3() const { assert(_type == Vec3Type); return *reinterpret_cast<const glm::vec3*>(&_val); }
const glm::quat& getQuat() const { assert(_type == QuatType); return *reinterpret_cast<const glm::quat*>(&_val); }
const glm::mat4& getMat4() const { assert(_type == Mat4Type); return *reinterpret_cast<const glm::mat4*>(&_val); }
bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; }
int getInt() const { assert(_type == Type::Int); return _val.intVal; }
float getFloat() const { assert(_type == Type::Float); return _val.floats[0]; }
const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast<const glm::vec3*>(&_val); }
const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast<const glm::quat*>(&_val); }
const glm::mat4& getMat4() const { assert(_type == Type::Mat4); return *reinterpret_cast<const glm::mat4*>(&_val); }
const std::string& getString() const { assert(_type == Type::String); return _stringVal; }
protected:
Type _type;
std::string _stringVal;
union {
bool boolVal;
int intVal;
@ -126,12 +132,22 @@ public:
}
}
const std::string& lookup(const std::string& key, const std::string& defaultValue) const {
if (key.empty()) {
return defaultValue;
} else {
auto iter = _map.find(key);
return iter != _map.end() ? iter->second.getString() : defaultValue;
}
}
void set(const std::string& key, bool value) { _map[key] = AnimVariant(value); }
void set(const std::string& key, int value) { _map[key] = AnimVariant(value); }
void set(const std::string& key, float value) { _map[key] = AnimVariant(value); }
void set(const std::string& key, const glm::vec3& value) { _map[key] = AnimVariant(value); }
void set(const std::string& key, const glm::quat& value) { _map[key] = AnimVariant(value); }
void set(const std::string& key, const glm::mat4& value) { _map[key] = AnimVariant(value); }
void set(const std::string& key, const std::string& value) { _map[key] = AnimVariant(value); }
void setTrigger(const std::string& key) { _triggers.insert(key); }
void clearTirggers() { _triggers.clear(); }

View file

@ -2,11 +2,27 @@
"version": "1.0",
"root": {
"id": "root",
"type": "overlay",
"type": "stateMachine",
"data": {
"boneSet": "upperBody",
"alpha": 1.0,
"alphaVar": "sine"
"currentState": "idle",
"states": [
{
"id": "idle",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "isMoving", "state": "walk" }
]
},
{
"id": "walk",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "isNotMoving", "state": "idle" }
]
}
]
},
"children": [
{