mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-03 22:45:33 +02:00
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:
parent
53b231987c
commit
a0f21228f6
6 changed files with 807 additions and 591 deletions
89
examples/kneel.js
Normal file
89
examples/kneel.js
Normal 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);
|
||||
});
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue