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;
}
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) {
switch (type) {
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_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false);
READ_OPTIONAL_STRING(interpType, stateObj);
READ_OPTIONAL_STRING(interpTargetVar, stateObj);
READ_OPTIONAL_STRING(interpDurationVar, stateObj);
READ_OPTIONAL_STRING(interpTypeVar, stateObj);
auto iter = childMap.find(id);
if (iter == childMap.end()) {
@ -475,7 +487,16 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
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);
if (!interpTargetVar.isEmpty()) {
@ -484,6 +505,9 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
if (!interpDurationVar.isEmpty()) {
statePtr->setInterpDurationVar(interpDurationVar);
}
if (!interpTypeVar.isEmpty()) {
statePtr->setInterpTypeVar(interpTypeVar);
}
smNode->addState(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) {
_alpha += _alphaVel * dt;
if (_alpha < 1.0f) {
if (_poses.size() > 0 && _nextPoses.size() > 0 && _prevPoses.size() > 0) {
::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]);
AnimPoseVec* nextPoses = nullptr;
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 {
_duringInterp = false;
@ -86,16 +103,32 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe
_alpha = 0.0f;
float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration));
_alphaVel = FRAMES_PER_SECOND / duration;
_prevPoses = _poses;
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
_interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType);
// because dt is 0, we should not encounter any triggers
const float dt = 0.0f;
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
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
_currentState = desiredState;
}

View file

@ -31,13 +31,27 @@
// visible after interpolation is complete.
// * interpDuration - (frames) The total length of time it will take to interp between the current pose and the
// 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 {
public:
friend class AnimNodeLoader;
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
enum class InterpType {
SnapshotBoth = 0,
SnapshotPrev,
NumTypes
};
protected:
class State {
public:
friend AnimStateMachine;
@ -55,14 +69,16 @@ protected:
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),
_childIndex(childIndex),
_interpTarget(interpTarget),
_interpDuration(interpDuration) {}
_interpDuration(interpDuration),
_interpType(interpType) {}
void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; }
void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; }
void setInterpTypeVar(const QString& interpTypeVar) { _interpTypeVar = interpTypeVar; }
int getChildIndex() const { return _childIndex; }
const QString& getID() const { return _id; }
@ -78,9 +94,11 @@ protected:
int _childIndex;
float _interpTarget; // frames
float _interpDuration; // frames
InterpType _interpType;
QString _interpTargetVar;
QString _interpDurationVar;
QString _interpTypeVar;
std::vector<Transition> _transitions;
@ -115,6 +133,7 @@ protected:
// interpolation state
bool _duringInterp = false;
InterpType _interpType { InterpType::SnapshotBoth };
float _alphaVel = 0.0f;
float _alpha = 0.0f;
AnimPoseVec _prevPoses;