Smooth fading of animations in and out.

Also, turn on the secret rig animations from Javascript with MyAvatar.setEnableRigAnimations(true). (persists)
This commit is contained in:
Howard Stearns 2015-08-04 17:04:15 -07:00
parent 3f5f75d6d2
commit 61198a658c
6 changed files with 148 additions and 49 deletions

View file

@ -625,6 +625,12 @@ float loadSetting(QSettings& settings, const char* name, float defaultValue) {
return value;
}
void MyAvatar::setEnableRigAnimations(bool isEnabled) {
Settings settings;
settings.setValue("enableRig", isEnabled);
_rig->setEnableRig(isEnabled);
}
void MyAvatar::loadData() {
Settings settings;
settings.beginGroup("Avatar");

View file

@ -72,6 +72,7 @@ public:
Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role);
Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url);
void clearJointAnimationPriorities();
Q_INVOKABLE void setEnableRigAnimations(bool isEnabled);
// get/set avatar data
void saveData();

View file

@ -49,7 +49,7 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) {
_jointMappings.clear();
}
void AnimationHandle::setRunning(bool running) {
void AnimationHandle::setRunning(bool running, bool doRestoreJoints) {
if (running && isRunning()) {
// if we're already running, this is the same as a restart
setFrameIndex(getFirstFrame());
@ -62,7 +62,9 @@ void AnimationHandle::setRunning(bool running) {
}
} else {
_rig->removeRunningAnimation(getAnimationHandlePointer());
restoreJoints();
if (doRestoreJoints) {
restoreJoints();
}
replaceMatchingPriorities(0.0f);
}
emit runningChanged(isRunning());
@ -71,7 +73,9 @@ void AnimationHandle::setRunning(bool running) {
AnimationHandle::AnimationHandle(RigPointer rig) :
QObject(rig.get()),
_rig(rig),
_priority(1.0f)
_priority(1.0f),
_fade(0.0f),
_fadePerSecond(0.0f)
{
}

View file

@ -64,6 +64,10 @@ public:
void setPriority(float priority);
float getPriority() const { return _priority; }
void setMix(float mix) { _mix = mix; }
void setFade(float fade) { _fade = fade; }
float getFade() { return _fade; }
void setFadePerSecond(float fadePerSecond) { _fadePerSecond = fadePerSecond; }
float getFadePerSecond() { return _fadePerSecond; }
void setMaskedJoints(const QStringList& maskedJoints);
const QStringList& getMaskedJoints() const { return _maskedJoints; }
@ -87,7 +91,7 @@ public:
void setLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); }
float getLastFrame() const { return _animationLoop.getLastFrame(); }
void setRunning(bool running);
void setRunning(bool running, bool restoreJoints = true);
bool isRunning() const { return _animationLoop.isRunning(); }
void setFrameIndex(float frameIndex) { _animationLoop.setFrameIndex(frameIndex); }
@ -111,7 +115,7 @@ signals:
public slots:
void start() { setRunning(true); }
void stop() { setRunning(false); }
void stop() { setRunning(false); _fadePerSecond = _fade = 0.0f; }
private:
@ -120,7 +124,9 @@ private:
QString _role;
QUrl _url;
float _priority;
float _mix;
float _mix; // How much of this animation to blend against what is already there. 1.0 sets to just this animation.
float _fade; // How far are we into full strength. 0.0 uses none of this animation, 1.0 (the max) is as much as possible.
float _fadePerSecond; // How fast should _fade change? +1.0 means _fade is increasing to 1.0 in 1 second. Negative is fading out.
QStringList _maskedJoints;
QVector<int> _jointMappings;

View file

@ -77,8 +77,8 @@ void Rig::startAnimation(const QString& url, float fps, float priority,
handle->start();
}
void Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints, bool startAutomatically) {
AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints, bool startAutomatically) {
// check for a configured animation for the role
//qCDebug(animation) << "addAnimationByRole" << role << url << fps << priority << loop << hold << firstFrame << lastFrame << maskedJoints << startAutomatically;
foreach (const AnimationHandlePointer& candidate, _animationHandles) {
@ -86,12 +86,38 @@ void Rig::addAnimationByRole(const QString& role, const QString& url, float fps,
if (startAutomatically) {
candidate->start();
}
return;
return candidate;
}
}
AnimationHandlePointer handle = createAnimationHandle();
QString standard = "";
if (url.isEmpty()) { // Default animations for fight club
const QString& base = "https://hifi-public.s3.amazonaws.com/ozan/";
if (role == "walk") {
standard = base + "support/FightClubBotTest1/Animations/standard_walk.fbx";
lastFrame = 60;
} else if (role == "leftTurn") {
standard = base + "support/FightClubBotTest1/Animations/left_turn_noHipRotation.fbx";
lastFrame = 29;
} else if (role == "rightTurn") {
standard = base + "support/FightClubBotTest1/Animations/right_turn_noHipRotation.fbx";
lastFrame = 31;
} else if (role == "leftStrafe") {
standard = base + "animations/fightclub_bot_anims/side_step_left_inPlace.fbx";
lastFrame = 31;
} else if (role == "rightStrafe") {
standard = base + "animations/fightclub_bot_anims/side_step_right_inPlace.fbx";
lastFrame = 31;
} else if (role == "idle") {
standard = base + "support/FightClubBotTest1/Animations/standard_idle.fbx";
fps = 25.0f;
}
if (!standard.isEmpty()) {
loop = true;
}
}
handle->setRole(role);
handle->setURL(url);
handle->setURL(url.isEmpty() ? standard : url);
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
@ -102,16 +128,18 @@ void Rig::addAnimationByRole(const QString& role, const QString& url, float fps,
if (startAutomatically) {
handle->start();
}
return handle;
}
void Rig::startAnimationByRole(const QString& role, const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true);
AnimationHandlePointer handle = addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true);
handle->setFadePerSecond(1.0f); // For now. Could be individualized later.
}
void Rig::stopAnimationByRole(const QString& role) {
foreach (const AnimationHandlePointer& handle, getRunningAnimations()) {
if (handle->getRole() == role) {
handle->stop();
handle->setFadePerSecond(-1.0f); // For now. Could be individualized later.
}
}
}
@ -135,6 +163,14 @@ void Rig::addRunningAnimation(AnimationHandlePointer animationHandle) {
bool Rig::isRunningAnimation(AnimationHandlePointer animationHandle) {
return _runningAnimations.contains(animationHandle);
}
bool Rig::isRunningRole(const QString& role) { //obviously, there are more efficient ways to do this
for (auto animation : _runningAnimations) {
if ((animation->getRole() == role) && (animation->getFadePerSecond() >= 0.0f)) { // Don't count those being faded out
return true;
}
}
return false;
}
void Rig::deleteAnimations() {
for (auto animation : _animationHandles) {
@ -356,47 +392,92 @@ glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const {
}
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
if (_enableRig) {
glm::vec3 front = worldRotation * IDENTITY_FRONT;
float forwardSpeed = glm::dot(worldVelocity, front);
float rotationalSpeed = glm::angle(front, _lastFront) / deltaTime;
bool isWalking = std::abs(forwardSpeed) > 0.01f;
bool isTurning = std::abs(rotationalSpeed) > 0.5f;
// Crude, until we have blending:
isTurning = isTurning && !isWalking; // Only one of walk/turn, walk wins.
isTurning = false; // FIXME
bool isIdle = !isWalking && !isTurning;
auto singleRole = [](bool walking, bool turning, bool idling) {
return walking ? "walk" : (turning ? "turn" : (idling ? "idle" : ""));
};
QString toStop = singleRole(_isWalking && !isWalking, _isTurning && !isTurning, _isIdle && !isIdle);
if (!toStop.isEmpty()) {
//qCDebug(animation) << "isTurning" << isTurning << "fronts" << front << _lastFront << glm::angle(front, _lastFront) << rotationalSpeed;
stopAnimationByRole(toStop);
}
QString newRole = singleRole(isWalking && !_isWalking, isTurning && !_isTurning, isIdle && !_isIdle);
if (!newRole.isEmpty()) {
startAnimationByRole(newRole);
qCDebug(animation) << deltaTime << ":" << worldVelocity << "." << front << "=> " << forwardSpeed << newRole;
}
_lastPosition = worldPosition;
_lastFront = front;
_isWalking = isWalking;
_isTurning = isTurning;
_isIdle = isIdle;
if (!_enableRig) {
return;
}
bool isMoving = false;
glm::vec3 front = worldRotation * IDENTITY_FRONT;
float forwardSpeed = glm::dot(worldVelocity, front);
float rightLateralSpeed = glm::dot(worldVelocity, worldRotation * IDENTITY_RIGHT);
float rightTurningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime;
auto updateRole = [&](const QString& role, bool isOn) {
isMoving = isMoving || isOn;
if (isOn) {
if (!isRunningRole(role)) {
qCDebug(animation) << "Rig STARTING" << role;
startAnimationByRole(role);
}
} else {
if (isRunningRole(role)) {
qCDebug(animation) << "Rig stopping" << role;
stopAnimationByRole(role);
}
}
};
updateRole("walk", std::abs(forwardSpeed) > 0.01f);
bool isTurning = std::abs(rightTurningSpeed) > 0.5f;
updateRole("rightTurn", isTurning && (rightTurningSpeed > 0));
updateRole("leftTurn", isTurning && (rightTurningSpeed < 0));
bool isStrafing = std::abs(rightLateralSpeed) > 0.01f;
updateRole("rightStrafe", isStrafing && (rightLateralSpeed > 0.0f));
updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f));
updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus.
_lastFront = front;
_lastPosition = worldPosition;
}
void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
int nAnimationsSoFar = 0;
// First normalize the fades so that they sum to 1.0.
// update the fade data in each animation (not normalized as they are an independent propert of animation)
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
handle->setMix(1.0f / ++nAnimationsSoFar);
handle->setPriority(1.0);
float fadePerSecond = handle->getFadePerSecond();
float fade = handle->getFade();
if (fadePerSecond != 0.0f) {
fade += fadePerSecond * deltaTime;
if ((0.0f >= fade) || (fade >= 1.0f)) {
fade = glm::clamp(fade, 0.0f, 1.0f);
handle->setFadePerSecond(0.0f);
}
handle->setFade(fade);
if (fade <= 0.0f) { // stop any finished animations now
handle->setRunning(false, false); // but do not restore joints as it causes a flicker
}
}
}
// collect the remaining fade data
float fadeSum = 0.0f;
float normalizedFades[_runningAnimations.count()];
int animationIndex = 0;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
float fade = handle->getFade();
normalizedFades[animationIndex++] = fade;
fadeSum += fade;
}
// normalize our copy of the fade data
for (int i = 0; i < _runningAnimations.count(); i++) {
normalizedFades[i] /= fadeSum;
}
// set the mix based on the normalized fade data
animationIndex = 0;
fadeSum = 0.0f;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
handle->setPriority(1.0f);
float normalizedFade = normalizedFades[animationIndex++];
// simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step.
// i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result.
// The formula here for mix is based on the idea that, at each step:
// fadeSum is to normalizedFade, as (1 - mix) is to mix
// i.e., fadeSum/normalizedFade = (1 - mix)/mix
// Then we solve for mix.
// Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1.
// Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++
float mix = 1.0f / ((fadeSum / normalizedFade) + 1.0f);
fadeSum += normalizedFade;
handle->setMix(mix);
handle->simulate(deltaTime);
}
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i, parentTransform);
}

View file

@ -76,6 +76,7 @@ public:
bool removeRunningAnimation(AnimationHandlePointer animationHandle);
void addRunningAnimation(AnimationHandlePointer animationHandle);
bool isRunningAnimation(AnimationHandlePointer animationHandle);
bool isRunningRole(const QString& role);
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
void deleteAnimations();
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
@ -86,9 +87,9 @@ public:
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
void stopAnimationByRole(const QString& role);
void addAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f,
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
AnimationHandlePointer addAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f,
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
float initJointStates(QVector<JointState> states, glm::mat4 parentTransform);
bool jointStatesEmpty() { return _jointStates.isEmpty(); };