AnimStateMachine: added new State parameter interpType

interpType defines how the interpolation between two states is performed.

   * SnapshotBoth: Stores two snapshots, the previous animation before interpolation begins and the target state at the
     interTarget frame.  Then during the interpolation period the two snapshots are interpolated to produce smooth motion between them.
   * SnapshotPrev: Stores a snapshot of the previous animation before interpolation begins.  However the target state is
     evaluated dynamically.  During the interpolation period the previous snapshot is interpolated with the target pose
     to produce smooth motion between them.  This mode is useful for interping into a blended animation where the actual
     blend factor is not known at the start of the interp or is might change dramatically during the interp.
This commit is contained in:
Anthony J. Thibault 2016-02-04 10:32:58 -08:00
parent 63df34541d
commit a8e092272c
3 changed files with 85 additions and 9 deletions

View file

@ -67,6 +67,16 @@ static AnimNode::Type stringToAnimNodeType(const QString& str) {
return AnimNode::Type::NumTypes; return AnimNode::Type::NumTypes;
} }
static AnimStateMachine::InterpType stringToInterpType(const QString& str) {
if (str == "snapshotBoth") {
return AnimStateMachine::InterpType::SnapshotBoth;
} else if (str == "snapshotPrev") {
return AnimStateMachine::InterpType::SnapshotPrev;
} else {
return AnimStateMachine::InterpType::NumTypes;
}
}
static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) {
switch (type) { switch (type) {
case AnimManipulator::JointVar::Type::AbsoluteRotation: return "absoluteRotation"; case AnimManipulator::JointVar::Type::AbsoluteRotation: return "absoluteRotation";
@ -465,9 +475,11 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
READ_STRING(id, stateObj, nodeId, jsonUrl, false); READ_STRING(id, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false); READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false); READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false);
READ_OPTIONAL_STRING(interpType, stateObj);
READ_OPTIONAL_STRING(interpTargetVar, stateObj); READ_OPTIONAL_STRING(interpTargetVar, stateObj);
READ_OPTIONAL_STRING(interpDurationVar, stateObj); READ_OPTIONAL_STRING(interpDurationVar, stateObj);
READ_OPTIONAL_STRING(interpTypeVar, stateObj);
auto iter = childMap.find(id); auto iter = childMap.find(id);
if (iter == childMap.end()) { if (iter == childMap.end()) {
@ -475,7 +487,16 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
return false; return false;
} }
auto statePtr = std::make_shared<AnimStateMachine::State>(id, iter->second, interpTarget, interpDuration); AnimStateMachine::InterpType interpTypeEnum = AnimStateMachine::InterpType::SnapshotBoth; // default value
if (!interpType.isEmpty()) {
interpTypeEnum = stringToInterpType(interpType);
if (interpTypeEnum == AnimStateMachine::InterpType::NumTypes) {
qCCritical(animation) << "AnimNodeLoader, bad interpType on stateMachine state, nodeId = " << nodeId << "stateId =" << id << "url = " << jsonUrl.toDisplayString();
return false;
}
}
auto statePtr = std::make_shared<AnimStateMachine::State>(id, iter->second, interpTarget, interpDuration, interpTypeEnum);
assert(statePtr); assert(statePtr);
if (!interpTargetVar.isEmpty()) { if (!interpTargetVar.isEmpty()) {
@ -484,6 +505,9 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
if (!interpDurationVar.isEmpty()) { if (!interpDurationVar.isEmpty()) {
statePtr->setInterpDurationVar(interpDurationVar); statePtr->setInterpDurationVar(interpDurationVar);
} }
if (!interpTypeVar.isEmpty()) {
statePtr->setInterpTypeVar(interpTypeVar);
}
smNode->addState(statePtr); smNode->addState(statePtr);
stateMap.insert(StateMap::value_type(statePtr->getID(), statePtr)); stateMap.insert(StateMap::value_type(statePtr->getID(), statePtr));

View file

@ -52,8 +52,25 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl
if (_duringInterp) { if (_duringInterp) {
_alpha += _alphaVel * dt; _alpha += _alphaVel * dt;
if (_alpha < 1.0f) { if (_alpha < 1.0f) {
if (_poses.size() > 0 && _nextPoses.size() > 0 && _prevPoses.size() > 0) { AnimPoseVec* nextPoses = nullptr;
::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]); AnimPoseVec* prevPoses = nullptr;
AnimPoseVec localNextPoses;
if (_interpType == InterpType::SnapshotBoth) {
// interp between both snapshots
prevPoses = &_prevPoses;
nextPoses = &_nextPoses;
} else if (_interpType == InterpType::SnapshotPrev) {
// interp between the prev snapshot and evaluated next target.
// this is useful for interping into a blend
localNextPoses = currentStateNode->evaluate(animVars, dt, triggersOut);
prevPoses = &_prevPoses;
nextPoses = &localNextPoses;
} else {
assert(false);
}
if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) {
::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]);
} }
} else { } else {
_duringInterp = false; _duringInterp = false;
@ -86,16 +103,32 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe
_alpha = 0.0f; _alpha = 0.0f;
float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration)); float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration));
_alphaVel = FRAMES_PER_SECOND / duration; _alphaVel = FRAMES_PER_SECOND / duration;
_prevPoses = _poses; _interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType);
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
// because dt is 0, we should not encounter any triggers // because dt is 0, we should not encounter any triggers
const float dt = 0.0f; const float dt = 0.0f;
Triggers triggers; Triggers triggers;
_nextPoses = nextStateNode->evaluate(animVars, dt, triggers);
if (_interpType == InterpType::SnapshotBoth) {
// snapshot previous pose.
_prevPoses = _poses;
// snapshot next pose at the target frame.
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
_nextPoses = nextStateNode->evaluate(animVars, dt, triggers);
} else if (_interpType == InterpType::SnapshotPrev) {
// snapshot previoius pose
_prevPoses = _poses;
// no need to evaluate _nextPoses we will do it dynamically during the interp,
// however we need to set the current frame.
nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration);
} else {
assert(false);
}
#if WANT_DEBUG #if WANT_DEBUG
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget; qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType;
#endif #endif
_currentState = desiredState; _currentState = desiredState;
} }

View file

@ -31,13 +31,27 @@
// visible after interpolation is complete. // visible after interpolation is complete.
// * interpDuration - (frames) The total length of time it will take to interp between the current pose and the // * interpDuration - (frames) The total length of time it will take to interp between the current pose and the
// interpTarget frame. // interpTarget frame.
// * interpType - How the interpolation is performed.
// * SnapshotBoth: Stores two snapshots, the previous animation before interpolation begins and the target state at the
// interTarget frame. Then during the interpolation period the two snapshots are interpolated to produce smooth motion between them.
// * SnapshotPrev: Stores a snapshot of the previous animation before interpolation begins. However the target state is
// evaluated dynamically. During the interpolation period the previous snapshot is interpolated with the target pose
// to produce smooth motion between them. This mode is useful for interping into a blended animation where the actual
// blend factor is not known at the start of the interp or is might change dramatically during the interp.
class AnimStateMachine : public AnimNode { class AnimStateMachine : public AnimNode {
public: public:
friend class AnimNodeLoader; friend class AnimNodeLoader;
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
enum class InterpType {
SnapshotBoth = 0,
SnapshotPrev,
NumTypes
};
protected: protected:
class State { class State {
public: public:
friend AnimStateMachine; friend AnimStateMachine;
@ -55,14 +69,16 @@ protected:
State::Pointer _state; State::Pointer _state;
}; };
State(const QString& id, int childIndex, float interpTarget, float interpDuration) : State(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType) :
_id(id), _id(id),
_childIndex(childIndex), _childIndex(childIndex),
_interpTarget(interpTarget), _interpTarget(interpTarget),
_interpDuration(interpDuration) {} _interpDuration(interpDuration),
_interpType(interpType) {}
void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; } void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; }
void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; } void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; }
void setInterpTypeVar(const QString& interpTypeVar) { _interpTypeVar = interpTypeVar; }
int getChildIndex() const { return _childIndex; } int getChildIndex() const { return _childIndex; }
const QString& getID() const { return _id; } const QString& getID() const { return _id; }
@ -78,9 +94,11 @@ protected:
int _childIndex; int _childIndex;
float _interpTarget; // frames float _interpTarget; // frames
float _interpDuration; // frames float _interpDuration; // frames
InterpType _interpType;
QString _interpTargetVar; QString _interpTargetVar;
QString _interpDurationVar; QString _interpDurationVar;
QString _interpTypeVar;
std::vector<Transition> _transitions; std::vector<Transition> _transitions;
@ -115,6 +133,7 @@ protected:
// interpolation state // interpolation state
bool _duringInterp = false; bool _duringInterp = false;
InterpType _interpType { InterpType::SnapshotBoth };
float _alphaVel = 0.0f; float _alphaVel = 0.0f;
float _alpha = 0.0f; float _alpha = 0.0f;
AnimPoseVec _prevPoses; AnimPoseVec _prevPoses;