Merge pull request #15992 from hyperlogic/feature/reaction-fixes

Refined reaction and sitting animations
This commit is contained in:
Shannon Romano 2019-08-06 09:15:44 -07:00 committed by GitHub
commit 9d3d5b7701
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 6224 additions and 3069 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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);
}

View file

@ -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]);
}
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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