mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-09 13:12:40 +02:00
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:
parent
637e3b0a15
commit
3286a32afc
11 changed files with 453 additions and 123 deletions
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
94
libraries/animation/src/AnimStateMachine.cpp
Normal file
94
libraries/animation/src/AnimStateMachine.cpp
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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": [
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue