AnimGraph support for start and stop animation from JavaScript

Follows the same model as the existing startAnimation and stopAnimation calls.
See kneel.js for an example.
This commit is contained in:
Anthony J. Thibault 2015-11-09 12:19:01 -08:00
parent 53b231987c
commit a0f21228f6
6 changed files with 807 additions and 591 deletions

89
examples/kneel.js Normal file
View file

@ -0,0 +1,89 @@
//
// kneel.js
// examples
//
// Created by Anthony Thibault on 11/9/2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Example of how to play an animation on an avatar.
//
var buttonImageUrl = "https://s3.amazonaws.com/hifi-public/images/tools/kneel.svg";
var windowDimensions = Controller.getViewportDimensions();
var buttonWidth = 37;
var buttonHeight = 46;
var buttonPadding = 10;
var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth;
var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 - (buttonHeight + buttonPadding);
var kneelDownImageOverlay = {
x: buttonPositionX,
y: buttonPositionY,
width: buttonWidth,
height: buttonHeight,
subImage: { x: 0, y: buttonHeight, width: buttonWidth, height: buttonHeight },
imageURL: buttonImageUrl,
visible: true,
alpha: 1.0
};
var standUpImageOverlay = {
x: buttonPositionX,
y: buttonPositionY,
width: buttonWidth,
height: buttonHeight,
subImage: { x: buttonWidth, y: buttonHeight, width: buttonWidth, height: buttonHeight },
imageURL: buttonImageUrl,
visible: false,
alpha: 1.0
};
var kneelDownButton = Overlays.addOverlay("image", kneelDownImageOverlay);
var standUpButton = Overlays.addOverlay("image", standUpImageOverlay);
var kneeling = false;
var KNEEL_ANIM_URL = "https://hifi-public.s3.amazonaws.com/ozan/anim/kneel/kneel.fbx";
function kneelDown() {
kneeling = true;
var playbackRate = 30; // 30 fps is normal speed.
var priority = 0; // obsolete
var loopFlag = false;
var holdFlag = false; // obsolete
var startFrame = 0;
var endFrame = 82;
var maskedJoints = []; // obsolete
MyAvatar.startAnimation(KNEEL_ANIM_URL, playbackRate, priority, loopFlag, holdFlag, startFrame, endFrame, maskedJoints);
Overlays.editOverlay(kneelDownButton, { visible: false });
Overlays.editOverlay(standUpButton, { visible: true });
}
function standUp() {
kneeling = false;
MyAvatar.stopAnimation(KNEEL_ANIM_URL);
Overlays.editOverlay(standUpButton, { visible: false });
Overlays.editOverlay(kneelDownButton, { visible: true });
}
Controller.mousePressEvent.connect(function (event) {
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (clickedOverlay == kneelDownButton) {
kneelDown();
} else if (clickedOverlay == standUpButton) {
standUp();
}
});
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(kneelDownButton);
Overlays.deleteOverlay(standUpButton);
});

View file

@ -37,13 +37,18 @@ public:
void setFrameVar(const QString& frameVar) { _frameVar = frameVar; }
float getStartFrame() const { return _startFrame; }
void setStartFrame(float startFrame) { _startFrame = startFrame; }
float getEndFrame() const { return _endFrame; }
void setEndFrame(float endFrame) { _endFrame = endFrame; }
void setTimeScale(float timeScale) { _timeScale = timeScale; }
float getTimeScale() const { return _timeScale; }
protected:
bool getLoopFlag() const { return _loopFlag; }
void setLoopFlag(bool loopFlag) { _loopFlag = loopFlag; }
void loadURL(const QString& url);
protected:
virtual void setCurrentFrameInternal(float frame) override;

View file

@ -18,8 +18,17 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A
for (size_t i = 0; i < numPoses; i++) {
const AnimPose& aPose = a[i];
const AnimPose& bPose = b[i];
// adjust signs if necessary
const glm::quat& q1 = aPose.rot;
glm::quat q2 = bPose.rot;
float dot = glm::dot(q1, q2);
if (dot < 0.0f) {
q2 = -q2;
}
result[i].scale = lerp(aPose.scale, bPose.scale, alpha);
result[i].rot = glm::normalize(glm::lerp(aPose.rot, bPose.rot, alpha));
result[i].rot = glm::normalize(glm::lerp(aPose.rot, q2, alpha));
result[i].trans = lerp(aPose.trans, bPose.trans, alpha);
}
}

View file

@ -21,6 +21,7 @@
#include "AnimationHandle.h"
#include "AnimationLogging.h"
#include "AnimSkeleton.h"
#include "AnimClip.h"
#include "IKTarget.h"
void insertSorted(QList<AnimationHandlePointer>& handles, const AnimationHandlePointer& handle) {
@ -45,28 +46,61 @@ void Rig::removeAnimationHandle(const AnimationHandlePointer& handle) {
}
void Rig::startAnimation(const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
// This is different than startAnimationByRole, in which we use the existing values if the animation already exists.
// Here we reuse the animation handle if possible, but in any case, we set the values to those given (or defaulted).
AnimationHandlePointer handle = nullptr;
foreach (const AnimationHandlePointer& candidate, _animationHandles) {
if (candidate->getURL() == url) {
handle = candidate;
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
if (_enableAnimGraph) {
// NOTE: mask joints are unsupported, priority is now meaningless, hold flag is essentially always true
// when using the AnimGraph system.
// find an unused AnimClip clipNode
std::shared_ptr<AnimClip> clip;
if (_userAnimState == UserAnimState::None || _userAnimState == UserAnimState::B) {
_userAnimState = UserAnimState::A;
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->getChild((int)_userAnimState));
} else if (_userAnimState == UserAnimState::A) {
_userAnimState = UserAnimState::B;
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->getChild((int)_userAnimState));
}
// set parameters
clip->setLoopFlag(loop);
clip->setStartFrame(firstFrame);
clip->setEndFrame(lastFrame);
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
clip->setTimeScale(fps / REFERENCE_FRAMES_PER_SECOND);
clip->loadURL(url);
_currentUserAnimURL = url;
// notify the userAnimStateMachine the desired state.
_animVars.set("userAnimNone", false);
_animVars.set("userAnimA", _userAnimState == UserAnimState::A);
_animVars.set("userAnimB", _userAnimState == UserAnimState::B);
} else {
// This is different than startAnimationByRole, in which we use the existing values if the animation already exists.
// Here we reuse the animation handle if possible, but in any case, we set the values to those given (or defaulted).
AnimationHandlePointer handle = nullptr;
foreach (const AnimationHandlePointer& candidate, _animationHandles) {
if (candidate->getURL() == url) {
handle = candidate;
}
}
if (!handle) {
handle = createAnimationHandle();
handle->setURL(url);
}
handle->setFade(1.0f); // If you want to fade, use the startAnimationByRole system.
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
handle->setHold(hold);
handle->setFirstFrame(firstFrame);
handle->setLastFrame(lastFrame);
handle->setMaskedJoints(maskedJoints);
handle->start();
}
if (!handle) {
handle = createAnimationHandle();
handle->setURL(url);
}
handle->setFade(1.0f); // If you want to fade, use the startAnimationByRole system.
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
handle->setHold(hold);
handle->setFirstFrame(firstFrame);
handle->setLastFrame(lastFrame);
handle->setMaskedJoints(maskedJoints);
handle->start();
}
AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority,
@ -138,10 +172,20 @@ void Rig::stopAnimationByRole(const QString& role) {
}
void Rig::stopAnimation(const QString& url) {
foreach (const AnimationHandlePointer& handle, getRunningAnimations()) {
if (handle->getURL() == url) {
handle->setFade(0.0f); // right away. Will be remove during updateAnimations, without locking
handle->setFadePerSecond(-(FRAMES_PER_SECOND / FADE_FRAMES)); // so that the updateAnimation code notices
if (_enableAnimGraph) {
if (url == _currentUserAnimURL) {
_currentUserAnimURL = "";
// notify the userAnimStateMachine the desired state.
_animVars.set("userAnimNone", true);
_animVars.set("userAnimA", false);
_animVars.set("userAnimB", false);
}
} else {
foreach (const AnimationHandlePointer& handle, getRunningAnimations()) {
if (handle->getURL() == url) {
handle->setFade(0.0f); // right away. Will be remove during updateAnimations, without locking
handle->setFadePerSecond(-(FRAMES_PER_SECOND / FADE_FRAMES)); // so that the updateAnimation code notices
}
}
}
}

View file

@ -223,21 +223,21 @@ public:
void calcAnimAlpha(float speed, const std::vector<float>& referenceSpeeds, float* alphaOut) const;
QVector<JointState> _jointStates;
int _rootJointIndex = -1;
int _rootJointIndex { -1 };
int _leftHandJointIndex = -1;
int _leftElbowJointIndex = -1;
int _leftShoulderJointIndex = -1;
int _leftHandJointIndex { -1 };
int _leftElbowJointIndex { -1 };
int _leftShoulderJointIndex { -1 };
int _rightHandJointIndex = -1;
int _rightElbowJointIndex = -1;
int _rightShoulderJointIndex = -1;
int _rightHandJointIndex { -1 };
int _rightElbowJointIndex { -1 };
int _rightShoulderJointIndex { -1 };
QList<AnimationHandlePointer> _animationHandles;
QList<AnimationHandlePointer> _runningAnimations;
bool _enableRig = false;
bool _enableAnimGraph = false;
bool _enableRig { false };
bool _enableAnimGraph { false };
glm::vec3 _lastFront;
glm::vec3 _lastPosition;
glm::vec3 _lastVelocity;
@ -251,18 +251,25 @@ public:
Turn,
Move
};
RigRole _state = RigRole::Idle;
RigRole _desiredState = RigRole::Idle;
float _desiredStateAge = 0.0f;
float _leftHandOverlayAlpha = 0.0f;
float _rightHandOverlayAlpha = 0.0f;
RigRole _state { RigRole::Idle };
RigRole _desiredState { RigRole::Idle };
float _desiredStateAge { 0.0f };
enum class UserAnimState {
None = 0,
A,
B
};
UserAnimState _userAnimState { UserAnimState::None };
QString _currentUserAnimURL;
float _leftHandOverlayAlpha { 0.0f };
float _rightHandOverlayAlpha { 0.0f };
SimpleMovingAverage _averageForwardSpeed{ 10 };
SimpleMovingAverage _averageLateralSpeed{ 10 };
SimpleMovingAverage _averageForwardSpeed { 10 };
SimpleMovingAverage _averageLateralSpeed { 10 };
private:
QMap<int, StateHandler> _stateHandlers;
int _nextStateHandlerId {0};
int _nextStateHandlerId { 0 };
QMutex _stateMutex;
};