mirror of
https://github.com/lubosz/overte.git
synced 2025-08-08 03:48:38 +02:00
First pass integration of new anim system into rig.
This commit is contained in:
parent
2401e6cc4b
commit
7b4cb8655c
11 changed files with 198 additions and 158 deletions
|
@ -433,6 +433,8 @@ Menu::Menu() {
|
||||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
|
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, false,
|
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, false,
|
||||||
avatar, SLOT(setEnableRigAnimations(bool)));
|
avatar, SLOT(setEnableRigAnimations(bool)));
|
||||||
|
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, false,
|
||||||
|
avatar, SLOT(setEnableAnimGraph(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
|
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
|
addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
|
||||||
MenuOption::Connexion,
|
MenuOption::Connexion,
|
||||||
|
|
|
@ -187,6 +187,7 @@ namespace MenuOption {
|
||||||
const QString EchoServerAudio = "Echo Server Audio";
|
const QString EchoServerAudio = "Echo Server Audio";
|
||||||
const QString EditEntitiesHelp = "Edit Entities Help...";
|
const QString EditEntitiesHelp = "Edit Entities Help...";
|
||||||
const QString Enable3DTVMode = "Enable 3DTV Mode";
|
const QString Enable3DTVMode = "Enable 3DTV Mode";
|
||||||
|
const QString EnableAnimGraph = "Enable Anim Graph";
|
||||||
const QString EnableCharacterController = "Enable avatar collisions";
|
const QString EnableCharacterController = "Enable avatar collisions";
|
||||||
const QString EnableRigAnimations = "Enable Rig Animations";
|
const QString EnableRigAnimations = "Enable Rig Animations";
|
||||||
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
|
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
|
||||||
|
|
|
@ -36,11 +36,6 @@
|
||||||
|
|
||||||
#include "devices/Faceshift.h"
|
#include "devices/Faceshift.h"
|
||||||
|
|
||||||
#include "AnimDebugDraw.h"
|
|
||||||
#include "AnimSkeleton.h"
|
|
||||||
#include "AnimClip.h"
|
|
||||||
#include "AnimBlendLinear.h"
|
|
||||||
#include "AnimOverlay.h"
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "AvatarManager.h"
|
#include "AvatarManager.h"
|
||||||
#include "Environment.h"
|
#include "Environment.h"
|
||||||
|
@ -163,27 +158,6 @@ void MyAvatar::update(float deltaTime) {
|
||||||
_goToPending = false;
|
_goToPending = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_animNode) {
|
|
||||||
static float t = 0.0f;
|
|
||||||
_animVars.set("sine", 0.5f * sin(t) + 0.5f);
|
|
||||||
|
|
||||||
if (glm::length(getVelocity()) > 0.07f) {
|
|
||||||
_animVars.set("isMoving", true);
|
|
||||||
_animVars.set("isNotMoving", false);
|
|
||||||
} else {
|
|
||||||
_animVars.set("isMoving", false);
|
|
||||||
_animVars.set("isNotMoving", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
t += deltaTime;
|
|
||||||
AnimNode::Triggers triggers;
|
|
||||||
_animNode->evaluate(_animVars, deltaTime, triggers);
|
|
||||||
_animVars.clearTriggers();
|
|
||||||
for (auto& trigger : triggers) {
|
|
||||||
_animVars.setTrigger(trigger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_referential) {
|
if (_referential) {
|
||||||
_referential->update();
|
_referential->update();
|
||||||
}
|
}
|
||||||
|
@ -737,6 +711,13 @@ void MyAvatar::setEnableRigAnimations(bool isEnabled) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MyAvatar::setEnableAnimGraph(bool isEnabled) {
|
||||||
|
_rig->setEnableAnimGraph(isEnabled);
|
||||||
|
if (!isEnabled) {
|
||||||
|
// AJT: TODO: FIXME: currently setEnableAnimGraph menu item only works on startup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MyAvatar::loadData() {
|
void MyAvatar::loadData() {
|
||||||
Settings settings;
|
Settings settings;
|
||||||
settings.beginGroup("Avatar");
|
settings.beginGroup("Avatar");
|
||||||
|
@ -1035,7 +1016,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
_billboardValid = false;
|
_billboardValid = false;
|
||||||
_skeletonModel.setVisibleInScene(true, scene);
|
_skeletonModel.setVisibleInScene(true, scene);
|
||||||
_headBoneSet.clear();
|
_headBoneSet.clear();
|
||||||
teardownNewAnimationSystem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) {
|
void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) {
|
||||||
|
@ -1232,48 +1212,6 @@ void MyAvatar::initHeadBones() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::setupNewAnimationSystem() {
|
|
||||||
|
|
||||||
// create a skeleton
|
|
||||||
auto geom = _skeletonModel.getGeometry()->getFBXGeometry();
|
|
||||||
std::vector<FBXJoint> joints;
|
|
||||||
for (auto& joint : geom.joints) {
|
|
||||||
joints.push_back(joint);
|
|
||||||
}
|
|
||||||
AnimPose geometryOffset(_skeletonModel.getGeometry()->getFBXGeometry().offset);
|
|
||||||
_animSkeleton = make_shared<AnimSkeleton>(joints, geometryOffset);
|
|
||||||
|
|
||||||
// add skeleton to the debug renderer, so we can see it.
|
|
||||||
AnimDebugDraw::getInstance().addSkeleton("my-avatar-skeleton", _animSkeleton, AnimPose::identity);
|
|
||||||
//_animSkeleton->dump();
|
|
||||||
|
|
||||||
qCDebug(interfaceapp) << "geomOffset =" << geometryOffset;
|
|
||||||
|
|
||||||
// load the anim graph
|
|
||||||
// https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9
|
|
||||||
// python2 -m SimpleHTTPServer&
|
|
||||||
auto graphUrl = QUrl("http://localhost:8000/avatar.json");
|
|
||||||
//auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/403651948de088ca4dcdda4f873e225b091c779a/avatar.json");
|
|
||||||
_animLoader.reset(new AnimNodeLoader(graphUrl));
|
|
||||||
connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) {
|
|
||||||
_animNode = nodeIn;
|
|
||||||
_animNode->setSkeleton(_animSkeleton);
|
|
||||||
AnimPose xform(glm::vec3(1), glm::quat(), glm::vec3(0, 0, -1));
|
|
||||||
AnimDebugDraw::getInstance().addAnimNode("my-avatar-animation", _animNode, xform);
|
|
||||||
});
|
|
||||||
connect(_animLoader.get(), &AnimNodeLoader::error, [this, graphUrl](int error, QString str) {
|
|
||||||
qCCritical(interfaceapp) << "Error loading" << graphUrl << "code = " << error << "str =" << str;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::teardownNewAnimationSystem() {
|
|
||||||
AnimDebugDraw::getInstance().removeSkeleton("my-avatar-skeleton");
|
|
||||||
AnimDebugDraw::getInstance().removeAnimNode("my-avatar-animation");
|
|
||||||
_animSkeleton = nullptr;
|
|
||||||
_animLoader = nullptr;
|
|
||||||
_animNode = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::preRender(RenderArgs* renderArgs) {
|
void MyAvatar::preRender(RenderArgs* renderArgs) {
|
||||||
|
|
||||||
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
|
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
|
||||||
|
@ -1281,11 +1219,15 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
|
||||||
|
|
||||||
if (_skeletonModel.initWhenReady(scene)) {
|
if (_skeletonModel.initWhenReady(scene)) {
|
||||||
initHeadBones();
|
initHeadBones();
|
||||||
|
|
||||||
_skeletonModel.setCauterizeBoneSet(_headBoneSet);
|
_skeletonModel.setCauterizeBoneSet(_headBoneSet);
|
||||||
|
|
||||||
// AJT: SETUP DEBUG RENDERING OF NEW ANIMATION SYSTEM
|
// https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9
|
||||||
|
// python2 -m SimpleHTTPServer&
|
||||||
|
auto graphUrl = QUrl("http://localhost:8000/avatar.json");
|
||||||
|
//auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/403651948de088ca4dcdda4f873e225b091c779a/avatar.json");
|
||||||
|
|
||||||
setupNewAnimationSystem();
|
_skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldDrawHead != _prevShouldDrawHead) {
|
if (shouldDrawHead != _prevShouldDrawHead) {
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
#include <DynamicCharacterController.h>
|
#include <DynamicCharacterController.h>
|
||||||
#include <Rig.h>
|
#include <Rig.h>
|
||||||
|
|
||||||
#include "AnimNode.h"
|
|
||||||
#include "AnimNodeLoader.h"
|
|
||||||
|
|
||||||
#include "Avatar.h"
|
#include "Avatar.h"
|
||||||
|
|
||||||
class ModelItemID;
|
class ModelItemID;
|
||||||
|
@ -194,6 +191,7 @@ public slots:
|
||||||
|
|
||||||
virtual void rebuildSkeletonBody();
|
virtual void rebuildSkeletonBody();
|
||||||
void setEnableRigAnimations(bool isEnabled);
|
void setEnableRigAnimations(bool isEnabled);
|
||||||
|
void setEnableAnimGraph(bool isEnabled);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void transformChanged();
|
void transformChanged();
|
||||||
|
@ -201,8 +199,6 @@ signals:
|
||||||
void collisionWithEntity(const Collision& collision);
|
void collisionWithEntity(const Collision& collision);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupNewAnimationSystem();
|
|
||||||
void teardownNewAnimationSystem();
|
|
||||||
|
|
||||||
glm::vec3 getWorldBodyPosition() const;
|
glm::vec3 getWorldBodyPosition() const;
|
||||||
glm::quat getWorldBodyOrientation() const;
|
glm::quat getWorldBodyOrientation() const;
|
||||||
|
@ -317,11 +313,6 @@ private:
|
||||||
std::unordered_set<int> _headBoneSet;
|
std::unordered_set<int> _headBoneSet;
|
||||||
RigPointer _rig;
|
RigPointer _rig;
|
||||||
bool _prevShouldDrawHead;
|
bool _prevShouldDrawHead;
|
||||||
|
|
||||||
std::shared_ptr<AnimNode> _animNode;
|
|
||||||
std::shared_ptr<AnimSkeleton> _animSkeleton;
|
|
||||||
std::unique_ptr<AnimNodeLoader> _animLoader;
|
|
||||||
AnimVariantMap _animVars;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_MyAvatar_h
|
#endif // hifi_MyAvatar_h
|
||||||
|
|
|
@ -571,3 +571,7 @@ bool SkeletonModel::hasSkeleton() {
|
||||||
|
|
||||||
void SkeletonModel::onInvalidate() {
|
void SkeletonModel::onInvalidate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SkeletonModel::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
|
||||||
|
_rig->initAnimGraph(url, fbxGeometry);
|
||||||
|
}
|
||||||
|
|
|
@ -106,6 +106,8 @@ public:
|
||||||
|
|
||||||
virtual void onInvalidate() override;
|
virtual void onInvalidate() override;
|
||||||
|
|
||||||
|
void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
void skeletonLoaded();
|
void skeletonLoaded();
|
||||||
|
|
|
@ -161,4 +161,20 @@ void AnimSkeleton::dump() const {
|
||||||
}
|
}
|
||||||
qCDebug(animation) << "]";
|
qCDebug(animation) << "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimSkeleton::dump(const AnimPoseVec& poses) const {
|
||||||
|
qCDebug(animation) << "[";
|
||||||
|
for (int i = 0; i < getNumJoints(); i++) {
|
||||||
|
qCDebug(animation) << " {";
|
||||||
|
qCDebug(animation) << " name =" << getJointName(i);
|
||||||
|
qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
|
||||||
|
qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
|
||||||
|
qCDebug(animation) << " pose =" << poses[i];
|
||||||
|
if (getParentIndex(i) >= 0) {
|
||||||
|
qCDebug(animation) << " parent =" << getJointName(getParentIndex(i));
|
||||||
|
}
|
||||||
|
qCDebug(animation) << " },";
|
||||||
|
}
|
||||||
|
qCDebug(animation) << "]";
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -61,6 +61,7 @@ public:
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
void dump() const;
|
void dump() const;
|
||||||
|
void dump(const AnimPoseVec& poses) const;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
|
|
||||||
#include "AnimationHandle.h"
|
#include "AnimationHandle.h"
|
||||||
#include "AnimationLogging.h"
|
#include "AnimationLogging.h"
|
||||||
|
|
||||||
|
#include "AnimSkeleton.h"
|
||||||
|
|
||||||
#include "Rig.h"
|
#include "Rig.h"
|
||||||
|
|
||||||
void Rig::HeadParameters::dump() const {
|
void Rig::HeadParameters::dump() const {
|
||||||
|
@ -183,6 +186,12 @@ void Rig::deleteAnimations() {
|
||||||
removeAnimationHandle(animation);
|
removeAnimationHandle(animation);
|
||||||
}
|
}
|
||||||
_animationHandles.clear();
|
_animationHandles.clear();
|
||||||
|
|
||||||
|
if (_enableAnimGraph) {
|
||||||
|
_animSkeleton = nullptr;
|
||||||
|
_animLoader = nullptr;
|
||||||
|
_animNode = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform,
|
void Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform,
|
||||||
|
@ -406,6 +415,22 @@ glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
|
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
|
||||||
|
|
||||||
|
if (_enableAnimGraph) {
|
||||||
|
static float t = 0.0f;
|
||||||
|
_animVars.set("sine", 0.5f * sin(t) + 0.5f);
|
||||||
|
|
||||||
|
if (glm::length(worldVelocity) > 0.07f) {
|
||||||
|
_animVars.set("isMoving", true);
|
||||||
|
_animVars.set("isNotMoving", false);
|
||||||
|
} else {
|
||||||
|
_animVars.set("isMoving", false);
|
||||||
|
_animVars.set("isNotMoving", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
t += deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_enableRig) {
|
if (!_enableRig) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -442,55 +467,85 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
|
void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
|
||||||
|
|
||||||
// First normalize the fades so that they sum to 1.0.
|
if (_enableAnimGraph) {
|
||||||
// update the fade data in each animation (not normalized as they are an independent propert of animation)
|
if (!_animNode) {
|
||||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
return;
|
||||||
float fadePerSecond = handle->getFadePerSecond();
|
}
|
||||||
float fade = handle->getFade();
|
|
||||||
if (fadePerSecond != 0.0f) {
|
// evaluate the animation
|
||||||
fade += fadePerSecond * deltaTime;
|
AnimNode::Triggers triggersOut;
|
||||||
if ((0.0f >= fade) || (fade >= 1.0f)) {
|
AnimPoseVec poses = _animNode->evaluate(_animVars, deltaTime, triggersOut);
|
||||||
fade = glm::clamp(fade, 0.0f, 1.0f);
|
_animVars.clearTriggers();
|
||||||
handle->setFadePerSecond(0.0f);
|
for (auto& trigger : triggersOut) {
|
||||||
|
_animVars.setTrigger(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy poses into jointStates
|
||||||
|
const float PRIORITY = 1.0f;
|
||||||
|
const float MIX = 1.0f;
|
||||||
|
for (size_t i = 0; i < poses.size(); i++) {
|
||||||
|
setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
|
updateJointState(i, parentTransform);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
|
_jointStates[i].resetTransformChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
handle->setFade(fade);
|
}
|
||||||
if (fade <= 0.0f) { // stop any finished animations now
|
// sum the remaining fade data
|
||||||
handle->setRunning(false, false); // but do not restore joints as it causes a flicker
|
float fadeTotal = 0.0f;
|
||||||
}
|
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||||
}
|
fadeTotal += handle->getFade();
|
||||||
}
|
}
|
||||||
// sum the remaining fade data
|
float fadeSumSoFar = 0.0f;
|
||||||
float fadeTotal = 0.0f;
|
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
handle->setPriority(1.0f);
|
||||||
fadeTotal += handle->getFade();
|
// if no fadeTotal, everyone's (typically just one running) is starting at zero. In that case, blend equally.
|
||||||
}
|
float normalizedFade = (fadeTotal != 0.0f) ? (handle->getFade() / fadeTotal) : (1.0f / _runningAnimations.count());
|
||||||
float fadeSumSoFar = 0.0f;
|
assert(normalizedFade != 0.0f);
|
||||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
// simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step.
|
||||||
handle->setPriority(1.0f);
|
// i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result.
|
||||||
// if no fadeTotal, everyone's (typically just one running) is starting at zero. In that case, blend equally.
|
// The formula here for mix is based on the idea that, at each step:
|
||||||
float normalizedFade = (fadeTotal != 0.0f) ? (handle->getFade() / fadeTotal) : (1.0f / _runningAnimations.count());
|
// fadeSum is to normalizedFade, as (1 - mix) is to mix
|
||||||
assert(normalizedFade != 0.0f);
|
// i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix
|
||||||
// simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step.
|
// Then we solve for mix.
|
||||||
// i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result.
|
// Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1.
|
||||||
// The formula here for mix is based on the idea that, at each step:
|
// Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++
|
||||||
// fadeSum is to normalizedFade, as (1 - mix) is to mix
|
float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f);
|
||||||
// i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix
|
assert((0.0f <= mix) && (mix <= 1.0f));
|
||||||
// Then we solve for mix.
|
fadeSumSoFar += normalizedFade;
|
||||||
// Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1.
|
handle->setMix(mix);
|
||||||
// Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++
|
handle->simulate(deltaTime);
|
||||||
float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f);
|
}
|
||||||
assert((0.0f <= mix) && (mix <= 1.0f));
|
|
||||||
fadeSumSoFar += normalizedFade;
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
handle->setMix(mix);
|
updateJointState(i, parentTransform);
|
||||||
handle->simulate(deltaTime);
|
}
|
||||||
}
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
|
_jointStates[i].resetTransformChanged();
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
}
|
||||||
updateJointState(i, parentTransform);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
|
||||||
_jointStates[i].resetTransformChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -824,6 +879,7 @@ void Rig::updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3&
|
||||||
updateEyeJoint(leftEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
|
updateEyeJoint(leftEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
|
||||||
updateEyeJoint(rightEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
|
updateEyeJoint(rightEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
|
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
|
||||||
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
|
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
|
||||||
auto& state = _jointStates[index];
|
auto& state = _jointStates[index];
|
||||||
|
@ -842,3 +898,38 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
|
||||||
state.getDefaultRotation(), DEFAULT_PRIORITY);
|
state.getDefaultRotation(), DEFAULT_PRIORITY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
|
||||||
|
if (!_enableAnimGraph) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to std::vector of joints
|
||||||
|
std::vector<FBXJoint> joints;
|
||||||
|
joints.reserve(fbxGeometry.joints.size());
|
||||||
|
for (auto& joint : fbxGeometry.joints) {
|
||||||
|
joints.push_back(joint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create skeleton
|
||||||
|
AnimPose geometryOffset(fbxGeometry.offset);
|
||||||
|
_animSkeleton = std::make_shared<AnimSkeleton>(joints, geometryOffset);
|
||||||
|
|
||||||
|
// add skeleton to the debug renderer, so we can see it.
|
||||||
|
// AnimDebugDraw::getInstance().addSkeleton("my-avatar-skeleton", _animSkeleton, AnimPose::identity);
|
||||||
|
// _animSkeleton->dump();
|
||||||
|
|
||||||
|
// load the anim graph
|
||||||
|
_animLoader.reset(new AnimNodeLoader(url));
|
||||||
|
connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) {
|
||||||
|
_animNode = nodeIn;
|
||||||
|
_animNode->setSkeleton(_animSkeleton);
|
||||||
|
|
||||||
|
// add node to debug renderer, for debugging
|
||||||
|
// AnimPose xform(glm::vec3(1), glm::quat(), glm::vec3(0, 0, -1));
|
||||||
|
// AnimDebugDraw::getInstance().addAnimNode("my-avatar-animation", _animNode, xform);
|
||||||
|
});
|
||||||
|
connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) {
|
||||||
|
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,9 @@
|
||||||
|
|
||||||
#include "JointState.h" // We might want to change this (later) to something that doesn't depend on gpu, fbx and model. -HRS
|
#include "JointState.h" // We might want to change this (later) to something that doesn't depend on gpu, fbx and model. -HRS
|
||||||
|
|
||||||
|
#include "AnimNode.h"
|
||||||
|
#include "AnimNodeLoader.h"
|
||||||
|
|
||||||
class AnimationHandle;
|
class AnimationHandle;
|
||||||
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
|
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
|
||||||
|
|
||||||
|
@ -155,6 +158,7 @@ public:
|
||||||
virtual void updateJointState(int index, glm::mat4 parentTransform) = 0;
|
virtual void updateJointState(int index, glm::mat4 parentTransform) = 0;
|
||||||
|
|
||||||
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
|
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
|
||||||
|
void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; }
|
||||||
|
|
||||||
void updateFromHeadParameters(const HeadParameters& params);
|
void updateFromHeadParameters(const HeadParameters& params);
|
||||||
void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation,
|
void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation,
|
||||||
|
@ -163,6 +167,8 @@ public:
|
||||||
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
||||||
float scale, float priority) = 0;
|
float scale, float priority) = 0;
|
||||||
|
|
||||||
|
void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
|
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
|
||||||
|
@ -184,8 +190,14 @@ public:
|
||||||
QList<AnimationHandlePointer> _runningAnimations;
|
QList<AnimationHandlePointer> _runningAnimations;
|
||||||
|
|
||||||
bool _enableRig;
|
bool _enableRig;
|
||||||
|
bool _enableAnimGraph;
|
||||||
glm::vec3 _lastFront;
|
glm::vec3 _lastFront;
|
||||||
glm::vec3 _lastPosition;
|
glm::vec3 _lastPosition;
|
||||||
|
|
||||||
|
std::shared_ptr<AnimNode> _animNode;
|
||||||
|
std::shared_ptr<AnimSkeleton> _animSkeleton;
|
||||||
|
std::unique_ptr<AnimNodeLoader> _animLoader;
|
||||||
|
AnimVariantMap _animVars;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__Rig__) */
|
#endif /* defined(__hifi__Rig__) */
|
||||||
|
|
|
@ -27,37 +27,15 @@
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "idle",
|
"id": "idle",
|
||||||
"type": "blendLinear",
|
"type": "clip",
|
||||||
"data": {
|
"data": {
|
||||||
"alpha": 0.5,
|
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
|
||||||
"alphaVar": "sine"
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 90.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
},
|
},
|
||||||
"children": [
|
"children": []
|
||||||
{
|
|
||||||
"id": "normalIdle",
|
|
||||||
"type": "clip",
|
|
||||||
"data": {
|
|
||||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
|
|
||||||
"startFrame": 0.0,
|
|
||||||
"endFrame": 90.0,
|
|
||||||
"timeScale": 1.0,
|
|
||||||
"loopFlag": true
|
|
||||||
},
|
|
||||||
"children": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "otherIdle",
|
|
||||||
"type": "clip",
|
|
||||||
"data": {
|
|
||||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
|
|
||||||
"startFrame": 20.0,
|
|
||||||
"endFrame": 90.0,
|
|
||||||
"timeScale": 1.0,
|
|
||||||
"loopFlag": true
|
|
||||||
},
|
|
||||||
"children": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "walkFwd",
|
"id": "walkFwd",
|
||||||
|
|
Loading…
Reference in a new issue