mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
Merge pull request #15992 from hyperlogic/feature/reaction-fixes
Refined reaction and sitting animations
This commit is contained in:
commit
9d3d5b7701
37 changed files with 6224 additions and 3069 deletions
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_agree_headnod.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_agree_headnod.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_agree_headnodyes.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_agree_headnodyes.fbx
Normal file
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_agree_thisorthat.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_agree_thisorthat.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_clap02_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_clap02_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_clap03_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_clap03_all.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_disagree_no.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_disagree_no.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_raisehand03_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_raisehand03_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_raisehand04_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_raisehand04_all.fbx
Normal file
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_idle03.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_idle03.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_talk02.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_talk02.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_talk03.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_talk03.fbx
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -125,16 +125,25 @@ QString userRecenterModelToString(MyAvatar::SitStandModelType model) {
|
|||
}
|
||||
}
|
||||
|
||||
static const QStringList REACTION_NAMES = {
|
||||
static const QStringList TRIGGER_REACTION_NAMES = {
|
||||
QString("positive"),
|
||||
QString("negative"),
|
||||
QString("negative")
|
||||
};
|
||||
|
||||
static const QStringList BEGIN_END_REACTION_NAMES = {
|
||||
QString("raiseHand"),
|
||||
QString("applaud"),
|
||||
QString("point")
|
||||
};
|
||||
|
||||
static int reactionNameToIndex(const QString& reactionName) {
|
||||
return REACTION_NAMES.indexOf(reactionName);
|
||||
static int triggerReactionNameToIndex(const QString& reactionName) {
|
||||
assert(NUM_AVATAR_TRIGGER_REACTIONS == TRIGGER_REACTION_NAMES.size());
|
||||
return TRIGGER_REACTION_NAMES.indexOf(reactionName);
|
||||
}
|
||||
|
||||
static int beginEndReactionNameToIndex(const QString& reactionName) {
|
||||
assert(NUM_AVATAR_BEGIN_END_REACTIONS == TRIGGER_REACTION_NAMES.size());
|
||||
return BEGIN_END_REACTION_NAMES.indexOf(reactionName);
|
||||
}
|
||||
|
||||
MyAvatar::MyAvatar(QThread* thread) :
|
||||
|
@ -5824,13 +5833,17 @@ void MyAvatar::setModelScale(float scale) {
|
|||
}
|
||||
}
|
||||
|
||||
QStringList MyAvatar::getReactions() const {
|
||||
return REACTION_NAMES;
|
||||
QStringList MyAvatar::getBeginEndReactions() const {
|
||||
return BEGIN_END_REACTION_NAMES;
|
||||
}
|
||||
|
||||
QStringList MyAvatar::getTriggerReactions() const {
|
||||
return TRIGGER_REACTION_NAMES;
|
||||
}
|
||||
|
||||
bool MyAvatar::triggerReaction(QString reactionName) {
|
||||
int reactionIndex = reactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
|
||||
int reactionIndex = triggerReactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_TRIGGER_REACTIONS) {
|
||||
std::lock_guard<std::mutex> guard(_reactionLock);
|
||||
_reactionTriggers[reactionIndex] = true;
|
||||
return true;
|
||||
|
@ -5839,8 +5852,8 @@ bool MyAvatar::triggerReaction(QString reactionName) {
|
|||
}
|
||||
|
||||
bool MyAvatar::beginReaction(QString reactionName) {
|
||||
int reactionIndex = reactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
|
||||
int reactionIndex = beginEndReactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_BEGIN_END_REACTIONS) {
|
||||
std::lock_guard<std::mutex> guard(_reactionLock);
|
||||
_reactionEnabledRefCounts[reactionIndex]++;
|
||||
return true;
|
||||
|
@ -5849,8 +5862,8 @@ bool MyAvatar::beginReaction(QString reactionName) {
|
|||
}
|
||||
|
||||
bool MyAvatar::endReaction(QString reactionName) {
|
||||
int reactionIndex = reactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
|
||||
int reactionIndex = beginEndReactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_BEGIN_END_REACTIONS) {
|
||||
std::lock_guard<std::mutex> guard(_reactionLock);
|
||||
_reactionEnabledRefCounts[reactionIndex]--;
|
||||
return true;
|
||||
|
@ -5860,12 +5873,17 @@ bool MyAvatar::endReaction(QString reactionName) {
|
|||
|
||||
void MyAvatar::updateRigControllerParameters(Rig::ControllerParameters& params) {
|
||||
std::lock_guard<std::mutex> guard(_reactionLock);
|
||||
for (int i = 0; i < NUM_AVATAR_REACTIONS; i++) {
|
||||
|
||||
for (int i = 0; i < TRIGGER_REACTION_NAMES.size(); i++) {
|
||||
params.reactionTriggers[i] = _reactionTriggers[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < BEGIN_END_REACTION_NAMES.size(); i++) {
|
||||
// copy current state into params.
|
||||
params.reactionEnabledFlags[i] = _reactionEnabledRefCounts[i] > 0;
|
||||
params.reactionTriggers[i] = _reactionTriggers[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < TRIGGER_REACTION_NAMES.size(); i++) {
|
||||
// clear reaction triggers here as well
|
||||
_reactionTriggers[i] = false;
|
||||
}
|
||||
|
|
|
@ -2213,13 +2213,23 @@ public slots:
|
|||
virtual void setModelScale(float scale) override;
|
||||
|
||||
/**jsdoc
|
||||
* MyAvatar.getReactions
|
||||
* MyAvatar.getTriggerReactions
|
||||
* Returns a list of reactions names that can be triggered using MyAvatar.triggerReaction().
|
||||
* @returns {string[]} Array of reaction names.
|
||||
*/
|
||||
QStringList getReactions() const;
|
||||
QStringList getTriggerReactions() const;
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* MyAvatar.getBeginReactions
|
||||
* Returns a list of reactions names that can be enabled using MyAvatar.beginReaction() and MyAvatar.endReaction().
|
||||
* @returns {string[]} Array of reaction names.
|
||||
*/
|
||||
QStringList getBeginEndReactions() const;
|
||||
|
||||
/**jsdoc
|
||||
* MyAvatar.triggerReaction
|
||||
* Plays the given reaction on the avatar, once the reaction is complete it will automatically complete. Only reaction names returned from MyAvatar.getTriggerReactions() are available.
|
||||
* @param {string} reactionName - reaction name
|
||||
* @returns {bool} false if the given reaction is not supported.
|
||||
*/
|
||||
|
@ -2227,6 +2237,9 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* MyAvatar.beginReaction
|
||||
* Plays the given reaction on the avatar. The avatar will continue to play the reaction until stopped via the MyAvatar.endReaction() call or superseeded by another reaction.
|
||||
* Only reaction names returned from MyAvatar.getBeginEndReactions() are available.
|
||||
* NOTE: the caller is responsible for calling the corresponding MyAvatar.endReaction(), otherwise the avatar might become stuck in the reaction forever.
|
||||
* @param {string} reactionName - reaction name
|
||||
* @returns {bool} false if the given reaction is not supported.
|
||||
*/
|
||||
|
@ -2234,6 +2247,7 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* MyAvatar.endReaction
|
||||
* Used to stop a given reaction that was started via MyAvatar.beginReaction().
|
||||
* @param {string} reactionName - reaction name
|
||||
* @returns {bool} false if the given reaction is not supported.
|
||||
*/
|
||||
|
@ -2872,8 +2886,9 @@ private:
|
|||
QScriptEngine* _scriptEngine { nullptr };
|
||||
bool _needToSaveAvatarEntitySettings { false };
|
||||
|
||||
int _reactionEnabledRefCounts[NUM_AVATAR_REACTIONS] { 0, 0, 0, 0, 0 };
|
||||
bool _reactionTriggers[NUM_AVATAR_REACTIONS] { false, false, false, false, false };
|
||||
bool _reactionTriggers[NUM_AVATAR_TRIGGER_REACTIONS] { false, false };
|
||||
int _reactionEnabledRefCounts[NUM_AVATAR_BEGIN_END_REACTIONS] { 0, 0, 0 };
|
||||
|
||||
mutable std::mutex _reactionLock;
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "AnimTwoBoneIK.h"
|
||||
#include "AnimSplineIK.h"
|
||||
#include "AnimPoleVectorConstraint.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
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);
|
||||
|
@ -91,6 +92,8 @@ static AnimStateMachine::InterpType stringToInterpType(const QString& str) {
|
|||
return AnimStateMachine::InterpType::SnapshotBoth;
|
||||
} else if (str == "snapshotPrev") {
|
||||
return AnimStateMachine::InterpType::SnapshotPrev;
|
||||
} else if (str == "evaluateBoth") {
|
||||
return AnimStateMachine::InterpType::EvaluateBoth;
|
||||
} else {
|
||||
return AnimStateMachine::InterpType::NumTypes;
|
||||
}
|
||||
|
@ -101,11 +104,63 @@ static AnimRandomSwitch::InterpType stringToRandomInterpType(const QString& str)
|
|||
return AnimRandomSwitch::InterpType::SnapshotBoth;
|
||||
} else if (str == "snapshotPrev") {
|
||||
return AnimRandomSwitch::InterpType::SnapshotPrev;
|
||||
} else if (str == "evaluateBoth") {
|
||||
return AnimRandomSwitch::InterpType::EvaluateBoth;
|
||||
} else {
|
||||
return AnimRandomSwitch::InterpType::NumTypes;
|
||||
}
|
||||
}
|
||||
|
||||
static EasingType stringToEasingType(const QString& str) {
|
||||
if (str == "linear") {
|
||||
return EasingType_Linear;
|
||||
} else if (str == "easeInSine") {
|
||||
return EasingType_EaseInSine;
|
||||
} else if (str == "easeOutSine") {
|
||||
return EasingType_EaseOutSine;
|
||||
} else if (str == "easeInOutSine") {
|
||||
return EasingType_EaseInOutSine;
|
||||
} else if (str == "easeInQuad") {
|
||||
return EasingType_EaseInQuad;
|
||||
} else if (str == "easeOutQuad") {
|
||||
return EasingType_EaseOutQuad;
|
||||
} else if (str == "easeInOutQuad") {
|
||||
return EasingType_EaseInOutQuad;
|
||||
} else if (str == "easeInCubic") {
|
||||
return EasingType_EaseInCubic;
|
||||
} else if (str == "easeOutCubic") {
|
||||
return EasingType_EaseOutCubic;
|
||||
} else if (str == "easeInOutCubic") {
|
||||
return EasingType_EaseInOutCubic;
|
||||
} else if (str == "easeInQuart") {
|
||||
return EasingType_EaseInQuart;
|
||||
} else if (str == "easeOutQuart") {
|
||||
return EasingType_EaseOutQuart;
|
||||
} else if (str == "easeInOutQuart") {
|
||||
return EasingType_EaseInOutQuart;
|
||||
} else if (str == "easeInQuint") {
|
||||
return EasingType_EaseInQuint;
|
||||
} else if (str == "easeOutQuint") {
|
||||
return EasingType_EaseOutQuint;
|
||||
} else if (str == "easeInOutQuint") {
|
||||
return EasingType_EaseInOutQuint;
|
||||
} else if (str == "easeInExpo") {
|
||||
return EasingType_EaseInExpo;
|
||||
} else if (str == "easeOutExpo") {
|
||||
return EasingType_EaseOutExpo;
|
||||
} else if (str == "easeInOutExpo") {
|
||||
return EasingType_EaseInOutExpo;
|
||||
} else if (str == "easeInCirc") {
|
||||
return EasingType_EaseInCirc;
|
||||
} else if (str == "easeOutCirc") {
|
||||
return EasingType_EaseOutCirc;
|
||||
} else if (str == "easeInOutCirc") {
|
||||
return EasingType_EaseInOutCirc;
|
||||
} else {
|
||||
return EasingType_NumTypes;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) {
|
||||
switch (type) {
|
||||
case AnimManipulator::JointVar::Type::Absolute: return "absolute";
|
||||
|
@ -723,6 +778,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
|
|||
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false);
|
||||
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false);
|
||||
READ_OPTIONAL_STRING(interpType, stateObj);
|
||||
READ_OPTIONAL_STRING(easingType, stateObj);
|
||||
|
||||
READ_OPTIONAL_STRING(interpTargetVar, stateObj);
|
||||
READ_OPTIONAL_STRING(interpDurationVar, stateObj);
|
||||
|
@ -743,7 +799,16 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
|
|||
}
|
||||
}
|
||||
|
||||
auto statePtr = std::make_shared<AnimStateMachine::State>(id, iter->second, interpTarget, interpDuration, interpTypeEnum);
|
||||
EasingType easingTypeEnum = EasingType_Linear; // default value
|
||||
if (!easingType.isEmpty()) {
|
||||
easingTypeEnum = stringToEasingType(easingType);
|
||||
if (easingTypeEnum == EasingType_NumTypes) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad easingType on stateMachine state, nodeId = " << nodeId << "stateId =" << id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto statePtr = std::make_shared<AnimStateMachine::State>(id, iter->second, interpTarget, interpDuration, interpTypeEnum, easingTypeEnum);
|
||||
assert(statePtr);
|
||||
|
||||
if (!interpTargetVar.isEmpty()) {
|
||||
|
@ -845,6 +910,7 @@ bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObje
|
|||
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false);
|
||||
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false);
|
||||
READ_OPTIONAL_STRING(interpType, stateObj);
|
||||
READ_OPTIONAL_STRING(easingType, stateObj);
|
||||
READ_FLOAT(priority, stateObj, nodeId, jsonUrl, false);
|
||||
READ_BOOL(resume, stateObj, nodeId, jsonUrl, false);
|
||||
|
||||
|
@ -867,7 +933,16 @@ bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObje
|
|||
}
|
||||
}
|
||||
|
||||
auto randomStatePtr = std::make_shared<AnimRandomSwitch::RandomSwitchState>(id, iter->second, interpTarget, interpDuration, interpTypeEnum, priority, resume);
|
||||
EasingType easingTypeEnum = EasingType_Linear; // default value
|
||||
if (!easingType.isEmpty()) {
|
||||
easingTypeEnum = stringToEasingType(easingType);
|
||||
if (easingTypeEnum == EasingType_NumTypes) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad easingType on randomSwitch state, nodeId = " << nodeId << "stateId =" << id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto randomStatePtr = std::make_shared<AnimRandomSwitch::RandomSwitchState>(id, iter->second, interpTarget, interpDuration, interpTypeEnum, easingTypeEnum, priority, resume);
|
||||
if (priority > 0.0f) {
|
||||
smNode->addToPrioritySum(priority);
|
||||
}
|
||||
|
|
|
@ -54,15 +54,20 @@ const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const A
|
|||
|
||||
if (_children.size() >= 2) {
|
||||
auto& underPoses = _children[1]->evaluate(animVars, context, dt, triggersOut);
|
||||
auto& overPoses = _children[0]->overlay(animVars, context, dt, triggersOut, underPoses);
|
||||
|
||||
if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) {
|
||||
_poses.resize(underPoses.size());
|
||||
assert(_boneSetVec.size() == _poses.size());
|
||||
if (_alpha == 0.0f) {
|
||||
_poses = underPoses;
|
||||
} else {
|
||||
auto& overPoses = _children[0]->overlay(animVars, context, dt, triggersOut, underPoses);
|
||||
|
||||
for (size_t i = 0; i < _poses.size(); i++) {
|
||||
float alpha = _boneSetVec[i] * _alpha;
|
||||
::blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]);
|
||||
if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) {
|
||||
_poses.resize(underPoses.size());
|
||||
assert(_boneSetVec.size() == _poses.size());
|
||||
|
||||
for (size_t i = 0; i < _poses.size(); i++) {
|
||||
float alpha = _boneSetVec[i] * _alpha;
|
||||
::blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, co
|
|||
|
||||
assert(_currentState);
|
||||
auto currentStateNode = _children[_currentState->getChildIndex()];
|
||||
auto previousStateNode = _children[_previousState->getChildIndex()];
|
||||
assert(currentStateNode);
|
||||
|
||||
if (_duringInterp) {
|
||||
|
@ -97,6 +98,7 @@ const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, co
|
|||
AnimPoseVec* nextPoses = nullptr;
|
||||
AnimPoseVec* prevPoses = nullptr;
|
||||
AnimPoseVec localNextPoses;
|
||||
AnimPoseVec localPrevPoses;
|
||||
if (_interpType == InterpType::SnapshotBoth) {
|
||||
// interp between both snapshots
|
||||
prevPoses = &_prevPoses;
|
||||
|
@ -107,13 +109,18 @@ const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, co
|
|||
localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
prevPoses = &_prevPoses;
|
||||
nextPoses = &localNextPoses;
|
||||
} else if (_interpType == InterpType::EvaluateBoth) {
|
||||
localPrevPoses = previousStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
prevPoses = &localPrevPoses;
|
||||
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]);
|
||||
::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), easingFunc(_alpha, _easingType), &_poses[0]);
|
||||
}
|
||||
context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
|
||||
context.setDebugAlpha(_currentState->getID(), easingFunc(_alpha, _easingType) * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
|
||||
} else {
|
||||
_duringInterp = false;
|
||||
_prevPoses.clear();
|
||||
|
@ -160,6 +167,7 @@ void AnimRandomSwitch::switchRandomState(const AnimVariantMap& animVars, const A
|
|||
float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration));
|
||||
_alphaVel = FRAMES_PER_SECOND / duration;
|
||||
_interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType);
|
||||
_easingType = desiredState->_easingType;
|
||||
|
||||
// because dt is 0, we should not encounter any triggers
|
||||
const float dt = 0.0f;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include "AnimNode.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
// Random Switch State Machine for random transitioning between children AnimNodes
|
||||
//
|
||||
|
@ -51,6 +52,7 @@ public:
|
|||
enum class InterpType {
|
||||
SnapshotBoth = 0,
|
||||
SnapshotPrev,
|
||||
EvaluateBoth,
|
||||
NumTypes
|
||||
};
|
||||
|
||||
|
@ -73,12 +75,13 @@ protected:
|
|||
RandomSwitchState::Pointer _randomSwitchState;
|
||||
};
|
||||
|
||||
RandomSwitchState(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, float priority, bool resume) :
|
||||
RandomSwitchState(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, EasingType easingType, float priority, bool resume) :
|
||||
_id(id),
|
||||
_childIndex(childIndex),
|
||||
_interpTarget(interpTarget),
|
||||
_interpDuration(interpDuration),
|
||||
_interpType(interpType),
|
||||
_easingType(easingType),
|
||||
_priority(priority),
|
||||
_resume(resume){
|
||||
}
|
||||
|
@ -106,6 +109,7 @@ protected:
|
|||
float _interpTarget; // frames
|
||||
float _interpDuration; // frames
|
||||
InterpType _interpType;
|
||||
EasingType _easingType;
|
||||
float _priority {0.0f};
|
||||
bool _resume {false};
|
||||
|
||||
|
@ -154,7 +158,8 @@ protected:
|
|||
int _randomSwitchEvaluationCount { 0 };
|
||||
// interpolation state
|
||||
bool _duringInterp = false;
|
||||
InterpType _interpType{ InterpType::SnapshotPrev };
|
||||
InterpType _interpType { InterpType::SnapshotPrev };
|
||||
EasingType _easingType { EasingType_Linear };
|
||||
float _alphaVel = 0.0f;
|
||||
float _alpha = 0.0f;
|
||||
AnimPoseVec _prevPoses;
|
||||
|
|
|
@ -48,6 +48,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
|
|||
|
||||
assert(_currentState);
|
||||
auto currentStateNode = _children[_currentState->getChildIndex()];
|
||||
auto previousStateNode = _children[_previousState->getChildIndex()];
|
||||
assert(currentStateNode);
|
||||
|
||||
if (_duringInterp) {
|
||||
|
@ -56,6 +57,8 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
|
|||
AnimPoseVec* nextPoses = nullptr;
|
||||
AnimPoseVec* prevPoses = nullptr;
|
||||
AnimPoseVec localNextPoses;
|
||||
AnimPoseVec localPrevPoses;
|
||||
|
||||
if (_interpType == InterpType::SnapshotBoth) {
|
||||
// interp between both snapshots
|
||||
prevPoses = &_prevPoses;
|
||||
|
@ -66,13 +69,18 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
|
|||
localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
prevPoses = &_prevPoses;
|
||||
nextPoses = &localNextPoses;
|
||||
} else if (_interpType == InterpType::EvaluateBoth) {
|
||||
localPrevPoses = previousStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
prevPoses = &localPrevPoses;
|
||||
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]);
|
||||
::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), easingFunc(_alpha, _easingType), &_poses[0]);
|
||||
}
|
||||
context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
|
||||
context.setDebugAlpha(_currentState->getID(), easingFunc(_alpha, _easingType) * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
|
||||
} else {
|
||||
_duringInterp = false;
|
||||
_prevPoses.clear();
|
||||
|
@ -95,6 +103,15 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
|
|||
return _poses;
|
||||
}
|
||||
|
||||
const QString& AnimStateMachine::getCurrentStateID() const {
|
||||
if (_currentState) {
|
||||
return _currentState->getID();
|
||||
} else {
|
||||
static QString emptyString;
|
||||
return emptyString;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimStateMachine::setCurrentState(State::Pointer state) {
|
||||
_previousState = _currentState ? _currentState : state;
|
||||
_currentState = state;
|
||||
|
@ -116,6 +133,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon
|
|||
float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration));
|
||||
_alphaVel = FRAMES_PER_SECOND / duration;
|
||||
_interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType);
|
||||
_easingType = desiredState->_easingType;
|
||||
|
||||
// because dt is 0, we should not encounter any triggers
|
||||
const float dt = 0.0f;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include "AnimNode.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
// State Machine for transitioning between children AnimNodes
|
||||
//
|
||||
|
@ -47,6 +48,7 @@ public:
|
|||
enum class InterpType {
|
||||
SnapshotBoth = 0,
|
||||
SnapshotPrev,
|
||||
EvaluateBoth,
|
||||
NumTypes
|
||||
};
|
||||
|
||||
|
@ -69,12 +71,13 @@ protected:
|
|||
State::Pointer _state;
|
||||
};
|
||||
|
||||
State(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType) :
|
||||
State(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, EasingType easingType) :
|
||||
_id(id),
|
||||
_childIndex(childIndex),
|
||||
_interpTarget(interpTarget),
|
||||
_interpDuration(interpDuration),
|
||||
_interpType(interpType) {}
|
||||
_interpType(interpType),
|
||||
_easingType(easingType) {}
|
||||
|
||||
void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; }
|
||||
void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; }
|
||||
|
@ -95,6 +98,7 @@ protected:
|
|||
float _interpTarget; // frames
|
||||
float _interpDuration; // frames
|
||||
InterpType _interpType;
|
||||
EasingType _easingType;
|
||||
|
||||
QString _interpTargetVar;
|
||||
QString _interpDurationVar;
|
||||
|
@ -116,6 +120,7 @@ public:
|
|||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
|
||||
|
||||
void setCurrentStateVar(QString& currentStateVar) { _currentStateVar = currentStateVar; }
|
||||
const QString& getCurrentStateID() const;
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -134,6 +139,7 @@ protected:
|
|||
// interpolation state
|
||||
bool _duringInterp = false;
|
||||
InterpType _interpType { InterpType::SnapshotPrev };
|
||||
EasingType _easingType { EasingType_Linear };
|
||||
float _alphaVel = 0.0f;
|
||||
float _alpha = 0.0f;
|
||||
AnimPoseVec _prevPoses;
|
||||
|
|
|
@ -211,3 +211,86 @@ bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// See https://easings.net/en# for a graphical visualiztion of easing types.
|
||||
float easingFunc(float alpha, EasingType type) {
|
||||
switch (type) {
|
||||
case EasingType_Linear:
|
||||
return alpha;
|
||||
case EasingType_EaseInSine:
|
||||
return sinf((alpha - 1.0f) * PI_OVER_TWO) + 1.0f;
|
||||
case EasingType_EaseOutSine:
|
||||
return sinf(alpha * PI_OVER_TWO);
|
||||
case EasingType_EaseInOutSine:
|
||||
return 0.5f * (1.0f - cosf(alpha * PI));
|
||||
case EasingType_EaseInQuad:
|
||||
return alpha * alpha;
|
||||
case EasingType_EaseOutQuad:
|
||||
return -(alpha * (alpha - 2.0f));
|
||||
case EasingType_EaseInOutQuad:
|
||||
return (alpha < 0.5f) ? (2.0f * alpha * alpha) : ((-2.0f * alpha * alpha) + (4.0f * alpha) - 1.0f);
|
||||
case EasingType_EaseInCubic:
|
||||
return alpha * alpha * alpha;
|
||||
case EasingType_EaseOutCubic: {
|
||||
float temp = alpha - 1.0f;
|
||||
return temp * temp * temp + 1.0f;
|
||||
}
|
||||
case EasingType_EaseInOutCubic:
|
||||
if (alpha < 0.5f) {
|
||||
return 4.0f * alpha * alpha * alpha;
|
||||
} else {
|
||||
float temp = ((2.0f * alpha) - 2.0f);
|
||||
return 0.5f * temp * temp * temp + 1.0f;
|
||||
}
|
||||
case EasingType_EaseInQuart:
|
||||
return alpha * alpha * alpha * alpha;
|
||||
case EasingType_EaseOutQuart: {
|
||||
float temp = alpha - 1.0f;
|
||||
return temp * temp * temp * (1.0f - alpha) + 1.0f;
|
||||
}
|
||||
case EasingType_EaseInOutQuart:
|
||||
if (alpha < 0.5f) {
|
||||
return 8.0f * alpha * alpha * alpha * alpha;
|
||||
} else {
|
||||
float temp = alpha - 1.0f;
|
||||
return -8.0f * temp * temp * temp * temp + 1.0f;
|
||||
}
|
||||
case EasingType_EaseInQuint:
|
||||
return alpha * alpha * alpha * alpha * alpha;
|
||||
case EasingType_EaseOutQuint: {
|
||||
float temp = (alpha - 1.0f);
|
||||
return temp * temp * temp * temp * temp + 1.0f;
|
||||
}
|
||||
case EasingType_EaseInOutQuint:
|
||||
if (alpha < 0.5f) {
|
||||
return 16.0f * alpha * alpha * alpha * alpha * alpha;
|
||||
} else {
|
||||
float temp = ((2.0f * alpha) - 2.0f);
|
||||
return 0.5f * temp * temp * temp * temp * temp + 1.0f;
|
||||
}
|
||||
case EasingType_EaseInExpo:
|
||||
return (alpha == 0.0f) ? alpha : powf(2.0f, 10.0f * (alpha - 1.0f));
|
||||
case EasingType_EaseOutExpo:
|
||||
return (alpha == 1.0f) ? alpha : 1.0f - powf(2.0f, -10.0f * alpha);
|
||||
case EasingType_EaseInOutExpo:
|
||||
if (alpha == 0.0f || alpha == 1.0f)
|
||||
return alpha;
|
||||
else if (alpha < 0.5f) {
|
||||
return 0.5f * powf(2.0f, (20.0f * alpha) - 10.0f);
|
||||
} else {
|
||||
return -0.5f * powf(2.0f, (-20.0f * alpha) + 10.0f) + 1.0f;
|
||||
}
|
||||
case EasingType_EaseInCirc:
|
||||
return 1.0f - sqrtf(1.0f - (alpha * alpha));
|
||||
case EasingType_EaseOutCirc:
|
||||
return sqrtf((2.0f - alpha) * alpha);
|
||||
case EasingType_EaseInOutCirc:
|
||||
if (alpha < 0.5f) {
|
||||
return 0.5f * (1.0f - sqrtf(1.0f - 4.0f * (alpha * alpha)));
|
||||
} else {
|
||||
return 0.5f * (sqrtf(-((2.0f * alpha) - 3.0f) * ((2.0f * alpha) - 1.0f)) + 1.0f);
|
||||
}
|
||||
default:
|
||||
return alpha;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,10 +128,37 @@ protected:
|
|||
bool _snapshotValid { false };
|
||||
};
|
||||
|
||||
|
||||
// returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose.
|
||||
// if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward
|
||||
// such that it lies on the surface of the kdop.
|
||||
bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const HFMJointShapeInfo& shapeInfo, glm::vec3& displacementOut);
|
||||
|
||||
enum EasingType {
|
||||
EasingType_Linear,
|
||||
EasingType_EaseInSine,
|
||||
EasingType_EaseOutSine,
|
||||
EasingType_EaseInOutSine,
|
||||
EasingType_EaseInQuad,
|
||||
EasingType_EaseOutQuad,
|
||||
EasingType_EaseInOutQuad,
|
||||
EasingType_EaseInCubic,
|
||||
EasingType_EaseOutCubic,
|
||||
EasingType_EaseInOutCubic,
|
||||
EasingType_EaseInQuart,
|
||||
EasingType_EaseOutQuart,
|
||||
EasingType_EaseInOutQuart,
|
||||
EasingType_EaseInQuint,
|
||||
EasingType_EaseOutQuint,
|
||||
EasingType_EaseInOutQuint,
|
||||
EasingType_EaseInExpo,
|
||||
EasingType_EaseOutExpo,
|
||||
EasingType_EaseInOutExpo,
|
||||
EasingType_EaseInCirc,
|
||||
EasingType_EaseOutCirc,
|
||||
EasingType_EaseInOutCirc,
|
||||
EasingType_NumTypes
|
||||
};
|
||||
|
||||
float easingFunc(float alpha, EasingType type);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "AnimInverseKinematics.h"
|
||||
#include "AnimOverlay.h"
|
||||
#include "AnimSkeleton.h"
|
||||
#include "AnimStateMachine.h"
|
||||
#include "AnimUtil.h"
|
||||
#include "AvatarConstants.h"
|
||||
#include "IKTarget.h"
|
||||
|
@ -1906,28 +1907,7 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabl
|
|||
|
||||
void Rig::updateReactions(const ControllerParameters& params) {
|
||||
|
||||
// enable/disable animVars
|
||||
bool enabled = params.reactionEnabledFlags[AVATAR_REACTION_POSITIVE];
|
||||
_animVars.set("reactionPositiveEnabled", enabled);
|
||||
_animVars.set("reactionPositiveDisabled", !enabled);
|
||||
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_NEGATIVE];
|
||||
_animVars.set("reactionNegativeEnabled", enabled);
|
||||
_animVars.set("reactionNegativeDisabled", !enabled);
|
||||
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_RAISE_HAND];
|
||||
_animVars.set("reactionRaiseHandEnabled", enabled);
|
||||
_animVars.set("reactionRaiseHandDisabled", !enabled);
|
||||
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_APPLAUD];
|
||||
_animVars.set("reactionApplaudEnabled", enabled);
|
||||
_animVars.set("reactionApplaudDisabled", !enabled);
|
||||
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_POINT];
|
||||
_animVars.set("reactionPointEnabled", enabled);
|
||||
_animVars.set("reactionPointDisabled", !enabled);
|
||||
|
||||
// trigger animVars
|
||||
// trigger reactions
|
||||
if (params.reactionTriggers[AVATAR_REACTION_POSITIVE]) {
|
||||
_animVars.set("reactionPositiveTrigger", true);
|
||||
} else {
|
||||
|
@ -1940,22 +1920,38 @@ void Rig::updateReactions(const ControllerParameters& params) {
|
|||
_animVars.set("reactionNegativeTrigger", false);
|
||||
}
|
||||
|
||||
if (params.reactionTriggers[AVATAR_REACTION_RAISE_HAND]) {
|
||||
_animVars.set("reactionRaiseHandTrigger", true);
|
||||
} else {
|
||||
_animVars.set("reactionRaiseHandTrigger", false);
|
||||
}
|
||||
// begin end reactions
|
||||
bool enabled = params.reactionEnabledFlags[AVATAR_REACTION_RAISE_HAND];
|
||||
_animVars.set("reactionRaiseHandEnabled", enabled);
|
||||
_animVars.set("reactionRaiseHandDisabled", !enabled);
|
||||
|
||||
if (params.reactionTriggers[AVATAR_REACTION_APPLAUD]) {
|
||||
_animVars.set("reactionApplaudTrigger", true);
|
||||
} else {
|
||||
_animVars.set("reactionApplaudTrigger", false);
|
||||
}
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_APPLAUD];
|
||||
_animVars.set("reactionApplaudEnabled", enabled);
|
||||
_animVars.set("reactionApplaudDisabled", !enabled);
|
||||
|
||||
if (params.reactionTriggers[AVATAR_REACTION_POINT]) {
|
||||
_animVars.set("reactionPointTrigger", true);
|
||||
} else {
|
||||
_animVars.set("reactionPointTrigger", false);
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_POINT];
|
||||
_animVars.set("reactionPointEnabled", enabled);
|
||||
_animVars.set("reactionPointDisabled", !enabled);
|
||||
|
||||
// determine if we should ramp off IK
|
||||
if (_enableInverseKinematics) {
|
||||
bool reactionPlaying = false;
|
||||
std::shared_ptr<AnimStateMachine> mainStateMachine = std::dynamic_pointer_cast<AnimStateMachine>(_animNode->findByName("mainStateMachine"));
|
||||
std::shared_ptr<AnimStateMachine> idleStateMachine = std::dynamic_pointer_cast<AnimStateMachine>(_animNode->findByName("idle"));
|
||||
if (mainStateMachine && mainStateMachine->getCurrentStateID() == "idle" && idleStateMachine) {
|
||||
reactionPlaying = idleStateMachine->getCurrentStateID().startsWith("reaction");
|
||||
}
|
||||
|
||||
bool isSeated = _state == RigRole::Seated;
|
||||
bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled;
|
||||
bool hipsEstimated = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated;
|
||||
bool hmdMode = hipsEnabled && !hipsEstimated;
|
||||
|
||||
if ((reactionPlaying || isSeated) && !hmdMode) {
|
||||
// TODO: make this smooth.
|
||||
// disable head IK while reaction is playing, but only in "desktop" mode.
|
||||
_animVars.set("headType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2115,9 +2111,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
_talkIdleInterpTime = 1.0f;
|
||||
}
|
||||
float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f;
|
||||
_animVars.set("idleOverlayAlpha", easeOutInValue);
|
||||
_animVars.set("talkOverlayAlpha", easeOutInValue);
|
||||
} else {
|
||||
_animVars.set("idleOverlayAlpha", 1.0f);
|
||||
_animVars.set("talkOverlayAlpha", 1.0f);
|
||||
}
|
||||
} else {
|
||||
if (_talkIdleInterpTime < 1.0f) {
|
||||
|
@ -2127,9 +2123,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
}
|
||||
float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f;
|
||||
float talkAlpha = 1.0f - easeOutInValue;
|
||||
_animVars.set("idleOverlayAlpha", talkAlpha);
|
||||
_animVars.set("talkOverlayAlpha", talkAlpha);
|
||||
} else {
|
||||
_animVars.set("idleOverlayAlpha", 0.0f);
|
||||
_animVars.set("talkOverlayAlpha", 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,8 +88,8 @@ public:
|
|||
AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space
|
||||
uint8_t secondaryControllerFlags[NumSecondaryControllerTypes];
|
||||
bool isTalking;
|
||||
bool reactionEnabledFlags[NUM_AVATAR_REACTIONS];
|
||||
bool reactionTriggers[NUM_AVATAR_REACTIONS];
|
||||
bool reactionEnabledFlags[NUM_AVATAR_BEGIN_END_REACTIONS];
|
||||
bool reactionTriggers[NUM_AVATAR_TRIGGER_REACTIONS];
|
||||
HFMJointShapeInfo hipsShapeInfo;
|
||||
HFMJointShapeInfo spineShapeInfo;
|
||||
HFMJointShapeInfo spine1ShapeInfo;
|
||||
|
|
|
@ -106,13 +106,17 @@ static const float AVATAR_WALK_SPEED_SCALAR = 1.0f;
|
|||
static const float AVATAR_DESKTOP_SPRINT_SPEED_SCALAR = 3.0f;
|
||||
static const float AVATAR_HMD_SPRINT_SPEED_SCALAR = 2.0f;
|
||||
|
||||
enum AvatarReaction {
|
||||
enum AvatarTriggerReaction {
|
||||
AVATAR_REACTION_POSITIVE = 0,
|
||||
AVATAR_REACTION_NEGATIVE,
|
||||
NUM_AVATAR_TRIGGER_REACTIONS
|
||||
};
|
||||
|
||||
enum AvatarBeginEndReaction {
|
||||
AVATAR_REACTION_RAISE_HAND,
|
||||
AVATAR_REACTION_APPLAUD,
|
||||
AVATAR_REACTION_POINT,
|
||||
NUM_AVATAR_REACTIONS
|
||||
NUM_AVATAR_BEGIN_END_REACTIONS
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarConstants_h
|
||||
|
|
Loading…
Reference in a new issue