diff --git a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json index 723176c17e..46c5cd9567 100644 --- a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json +++ b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json @@ -527,13 +527,13 @@ }, { "id": "walkFwd", - "type": "blendLinear", + "type": "blendLinearMove", "data": { "alpha": 0.0, - "sync": true, - "timeScale": 1.0, - "timeScaleVar": "moveForwardTimeScale", - "alphaVar": "moveForwardAlpha" + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.5, 1.4, 4.5], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" }, "children": [ { @@ -576,13 +576,13 @@ }, { "id": "walkBwd", - "type": "blendLinear", + "type": "blendLinearMove", "data": { - "alpha": 1.0, - "sync": true, - "timeScale": 1.0, - "timeScaleVar": "moveBackwardTimeScale", - "alphaVar": "moveBackwardAlpha" + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.6, 1.45], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" }, "children": [ { @@ -637,13 +637,13 @@ }, { "id": "strafeLeft", - "type": "blendLinear", + "type": "blendLinearMove", "data": { "alpha": 0.0, - "sync": true, - "timeScale": 1.0, - "timeScaleVar": "moveLateralTimeScale", - "alphaVar": "moveLateralAlpha" + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.2, 0.65], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" }, "children": [ { @@ -674,13 +674,13 @@ }, { "id": "strafeRight", - "type": "blendLinear", + "type": "blendLinearMove", "data": { "alpha": 0.0, - "sync": true, - "timeScale": 1.0, - "timeScaleVar": "moveLateralTimeScale", - "alphaVar": "moveLateralAlpha" + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.2, 0.65], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" }, "children": [ { diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp new file mode 100644 index 0000000000..072dc28f57 --- /dev/null +++ b/libraries/animation/src/AnimBlendLinearMove.cpp @@ -0,0 +1,126 @@ +// +// AnimBlendLinearMove.cpp +// +// Created by Anthony J. Thibault on 10/22/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimBlendLinearMove.h" +#include "GLMHelpers.h" +#include "AnimationLogging.h" +#include "AnimUtil.h" +#include "AnimClip.h" + +AnimBlendLinearMove::AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector& characteristicSpeeds) : + AnimNode(AnimNode::Type::BlendLinearMove, id), + _alpha(alpha), + _desiredSpeed(desiredSpeed), + _characteristicSpeeds(characteristicSpeeds) { + +} + +AnimBlendLinearMove::~AnimBlendLinearMove() { + +} + +const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) { + + assert(_children.size() == _characteristicSpeeds.size()); + + _alpha = animVars.lookup(_alphaVar, _alpha); + _desiredSpeed = animVars.lookup(_desiredSpeedVar, _desiredSpeed); + + if (_children.size() == 0) { + for (auto&& pose : _poses) { + pose = AnimPose::identity; + } + } else if (_children.size() == 1) { + const float alpha = 0.0f; + const int prevPoseIndex = 0; + const int nextPoseIndex = 0; + float prevDeltaTime, nextDeltaTime; + setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); + evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); + } else { + + float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); + size_t prevPoseIndex = glm::floor(clampedAlpha); + size_t nextPoseIndex = glm::ceil(clampedAlpha); + float alpha = glm::fract(clampedAlpha); + float prevDeltaTime, nextDeltaTime; + setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); + evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); + } + return _poses; +} + +// for AnimDebugDraw rendering +const AnimPoseVec& AnimBlendLinearMove::getPosesInternal() const { + return _poses; +} + +void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha, + size_t prevPoseIndex, size_t nextPoseIndex, + float prevDeltaTime, float nextDeltaTime) { + if (prevPoseIndex == nextPoseIndex) { + // this can happen if alpha is on an integer boundary + _poses = _children[prevPoseIndex]->evaluate(animVars, prevDeltaTime, triggersOut); + } else { + // need to eval and blend between two children. + auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, prevDeltaTime, triggersOut); + auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, nextDeltaTime, triggersOut); + + if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { + _poses.resize(prevPoses.size()); + + ::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); + } + } +} + +void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex, + float* prevDeltaTimeOut, float* nextDeltaTimeOut, Triggers& triggersOut) { + + const float FRAMES_PER_SECOND = 30.0f; + auto prevClipNode = std::dynamic_pointer_cast(_children[prevPoseIndex]); + assert(prevClipNode); + auto nextClipNode = std::dynamic_pointer_cast(_children[nextPoseIndex]); + assert(nextClipNode); + + float v0 = _characteristicSpeeds[prevPoseIndex]; + float n0 = (prevClipNode->getEndFrame() - prevClipNode->getStartFrame()) + 1.0f; + float v1 = _characteristicSpeeds[nextPoseIndex]; + float n1 = (nextClipNode->getEndFrame() - nextClipNode->getStartFrame()) + 1.0f; + + // rate of change in phase space, necessary to achive desired speed. + float omega = (_desiredSpeed * FRAMES_PER_SECOND) / ((1.0f - alpha) * v0 * n0 + alpha * v1 * n1); + + float f0 = prevClipNode->getStartFrame() + _phase * n0; + prevClipNode->setCurrentFrame(f0); + + float f1 = nextClipNode->getStartFrame() + _phase * n1; + nextClipNode->setCurrentFrame(f1); + + // integrate phase forward in time. + _phase += omega * dt; + + // detect loop trigger events + if (_phase >= 1.0f) { + triggersOut.push_back(_id + "Loop"); + _phase = glm::fract(_phase); + } + + *prevDeltaTimeOut = omega * dt * (n0 / FRAMES_PER_SECOND); + *nextDeltaTimeOut = omega * dt * (n1 / FRAMES_PER_SECOND); +} + +void AnimBlendLinearMove::setCurrentFrameInternal(float frame) { + assert(_children.size() > 0); + auto clipNode = std::dynamic_pointer_cast(_children.front()); + assert(clipNode); + const float NUM_FRAMES = (clipNode->getEndFrame() - clipNode->getStartFrame()) + 1.0f; + _phase = fmodf(frame, NUM_FRAMES); +} diff --git a/libraries/animation/src/AnimBlendLinearMove.h b/libraries/animation/src/AnimBlendLinearMove.h new file mode 100644 index 0000000000..4e04ce29cb --- /dev/null +++ b/libraries/animation/src/AnimBlendLinearMove.h @@ -0,0 +1,77 @@ +// +// AnimBlendLinearMove.h +// +// Created by Anthony J. Thibault on 10/22/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimBlendLinearMove_h +#define hifi_AnimBlendLinearMove_h + +#include "AnimNode.h" + +// Synced linear blend between two AnimNodes, where the playback speed of +// the animation is timeScaled to match movement speed. +// +// Each child animation is associated with a chracteristic speed. +// This defines the speed of that animation when played at the normal playback rate, 30 frames per second. +// +// The user also specifies a desired speed. This desired speed is used to timescale +// the animation to achive the desired movement velocity. +// +// Blending is determined by the alpha parameter. +// If the number of children is 2, then the alpha parameters should be between +// 0 and 1. The first animation will have a (1 - alpha) factor, and the second +// will have factor of alpha. +// +// This node supports more then 2 children. In this case the alpha should be +// between 0 and n - 1. This alpha can be used to linearly interpolate between +// the closest two children poses. This can be used to sweep through a series +// of animation poses. + +class AnimBlendLinearMove : public AnimNode { +public: + friend class AnimTests; + + AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector& characteristicSpeeds); + virtual ~AnimBlendLinearMove() override; + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; + + void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; } + void setDesiredSpeedVar(const QString& desiredSpeedVar) { _desiredSpeedVar = desiredSpeedVar; } + +protected: + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + + void evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha, + size_t prevPoseIndex, size_t nextPoseIndex, + float prevDeltaTime, float nextDeltaTime); + + void setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex, + float* prevDeltaTimeOut, float* nextDeltaTimeOut, Triggers& triggersOut); + + virtual void setCurrentFrameInternal(float frame) override; + + AnimPoseVec _poses; + + float _alpha; + float _desiredSpeed; + + float _phase = 0.0f; + + QString _alphaVar; + QString _desiredSpeedVar; + + std::vector _characteristicSpeeds; + + // no copies + AnimBlendLinearMove(const AnimBlendLinearMove&) = delete; + AnimBlendLinearMove& operator=(const AnimBlendLinearMove&) = delete; +}; + +#endif // hifi_AnimBlendLinearMove_h diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 315ee357dd..ac3365c272 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -38,6 +38,7 @@ public: enum class Type { Clip = 0, BlendLinear, + BlendLinearMove, Overlay, StateMachine, Manipulator, diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index f9ebf2a630..38296923e3 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -16,6 +16,7 @@ #include "AnimNode.h" #include "AnimClip.h" #include "AnimBlendLinear.h" +#include "AnimBlendLinearMove.h" #include "AnimationLogging.h" #include "AnimOverlay.h" #include "AnimNodeLoader.h" @@ -29,6 +30,7 @@ using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& json // factory functions static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadBlendLinearMoveNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -36,17 +38,14 @@ static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, c // called after children have been loaded // returns node on success, nullptr on failure. -static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -static bool processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } +static bool processDoNothing(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); -static bool processManipulatorNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -static bool processInverseKinematicsNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return "clip"; case AnimNode::Type::BlendLinear: return "blendLinear"; + case AnimNode::Type::BlendLinearMove: return "blendLinearMove"; case AnimNode::Type::Overlay: return "overlay"; case AnimNode::Type::StateMachine: return "stateMachine"; case AnimNode::Type::Manipulator: return "manipulator"; @@ -60,6 +59,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return loadClipNode; case AnimNode::Type::BlendLinear: return loadBlendLinearNode; + case AnimNode::Type::BlendLinearMove: return loadBlendLinearMoveNode; case AnimNode::Type::Overlay: return loadOverlayNode; case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::Manipulator: return loadManipulatorNode; @@ -71,12 +71,13 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { switch (type) { - case AnimNode::Type::Clip: return processClipNode; - case AnimNode::Type::BlendLinear: return processBlendLinearNode; - case AnimNode::Type::Overlay: return processOverlayNode; + case AnimNode::Type::Clip: return processDoNothing; + case AnimNode::Type::BlendLinear: return processDoNothing; + case AnimNode::Type::BlendLinearMove: return processDoNothing; + case AnimNode::Type::Overlay: return processDoNothing; case AnimNode::Type::StateMachine: return processStateMachineNode; - case AnimNode::Type::Manipulator: return processManipulatorNode; - case AnimNode::Type::InverseKinematics: return processInverseKinematicsNode; + case AnimNode::Type::Manipulator: return processDoNothing; + case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -243,6 +244,45 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q return node; } +static AnimNode::Pointer loadBlendLinearMoveNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(desiredSpeed, jsonObj, id, jsonUrl, nullptr); + + std::vector characteristicSpeeds; + auto speedsValue = jsonObj.value("characteristicSpeeds"); + if (!speedsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad array \"characteristicSpeeds\" in blendLinearMove node, id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + + auto speedsArray = speedsValue.toArray(); + for (const auto& speedValue : speedsArray) { + if (!speedValue.isDouble()) { + qCCritical(animation) << "AnimNodeLoader, bad number in \"characteristicSpeeds\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + float speedVal = (float)speedValue.toDouble(); + characteristicSpeeds.push_back(speedVal); + }; + + READ_OPTIONAL_STRING(alphaVar, jsonObj); + READ_OPTIONAL_STRING(desiredSpeedVar, jsonObj); + + auto node = std::make_shared(id, alpha, desiredSpeed, characteristicSpeeds); + + if (!alphaVar.isEmpty()) { + node->setAlphaVar(alphaVar); + } + + if (!desiredSpeedVar.isEmpty()) { + node->setDesiredSpeedVar(desiredSpeedVar); + } + + return node; +} + + static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { "fullBody", "upperBody", diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index cf1d1bc703..7f9908faaf 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -408,11 +408,11 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const { return _jointStates[jointIndex].getTransform(); } -void Rig::calcAnimAlphaAndTimeScale(float speed, const std::vector& referenceSpeeds, float* alphaOut, float* timeScaleOut) const { +void Rig::calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const { assert(referenceSpeeds.size() > 0); - // first calculate alpha by lerping between speeds. + // calculate alpha from linear combination of referenceSpeeds. float alpha = 0.0f; if (speed <= referenceSpeeds.front()) { alpha = 0.0f; @@ -427,20 +427,12 @@ void Rig::calcAnimAlphaAndTimeScale(float speed, const std::vector& refer } } - // now keeping the alpha fixed compute the timeScale. - // NOTE: This makes the assumption that the velocity of a linear blend between two animations is also linear. - int prevIndex = glm::floor(alpha); - int nextIndex = glm::ceil(alpha); - float animSpeed = lerp(referenceSpeeds[prevIndex], referenceSpeeds[nextIndex], (float)glm::fract(alpha)); - float timeScale = glm::clamp(0.5f, 2.0f, speed / animSpeed); - *alphaOut = alpha; - *timeScaleOut = timeScale; } // animation reference speeds. -static const std::vector FORWARD_SPEEDS = { 0.4f, 1.4f, 2.5f }; // m/s -static const std::vector BACKWARD_SPEEDS = { 0.45f, 1.4f }; // m/s +static const std::vector FORWARD_SPEEDS = { 0.4f, 1.4f, 4.5f }; // m/s +static const std::vector BACKWARD_SPEEDS = { 0.6f, 1.45f }; // m/s static const std::vector LATERAL_SPEEDS = { 0.2f, 0.65f }; // m/s void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) { @@ -477,22 +469,21 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("sine", 2.0f * static_cast(0.5 * sin(t) + 0.5)); float moveForwardAlpha = 0.0f; - float moveForwardTimeScale = 1.0f; float moveBackwardAlpha = 0.0f; - float moveBackwardTimeScale = 1.0f; float moveLateralAlpha = 0.0f; - float moveLateralTimeScale = 1.0f; // calcuate the animation alpha and timeScale values based on current speeds and animation reference speeds. - calcAnimAlphaAndTimeScale(_averageForwardSpeed.getAverage(), FORWARD_SPEEDS, &moveForwardAlpha, &moveForwardTimeScale); - calcAnimAlphaAndTimeScale(-_averageForwardSpeed.getAverage(), BACKWARD_SPEEDS, &moveBackwardAlpha, &moveBackwardTimeScale); - calcAnimAlphaAndTimeScale(fabsf(_averageLateralSpeed.getAverage()), LATERAL_SPEEDS, &moveLateralAlpha, &moveLateralTimeScale); + calcAnimAlpha(_averageForwardSpeed.getAverage(), FORWARD_SPEEDS, &moveForwardAlpha); + calcAnimAlpha(-_averageForwardSpeed.getAverage(), BACKWARD_SPEEDS, &moveBackwardAlpha); + calcAnimAlpha(fabsf(_averageLateralSpeed.getAverage()), LATERAL_SPEEDS, &moveLateralAlpha); - _animVars.set("moveFowardTimeScale", moveForwardTimeScale); + _animVars.set("moveForwardSpeed", _averageForwardSpeed.getAverage()); _animVars.set("moveForwardAlpha", moveForwardAlpha); - _animVars.set("moveBackwardTimeScale", moveBackwardTimeScale); + + _animVars.set("moveBackwardSpeed", -_averageForwardSpeed.getAverage()); _animVars.set("moveBackwardAlpha", moveBackwardAlpha); - _animVars.set("moveLateralTimeScale", moveLateralTimeScale); + + _animVars.set("moveLateralSpeed", fabsf(_averageLateralSpeed.getAverage())); _animVars.set("moveLateralAlpha", moveLateralAlpha); const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index d1031c017a..861eba40b5 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -208,7 +208,7 @@ public: void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); void updateNeckJoint(int index, const HeadParameters& params); void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); - void calcAnimAlphaAndTimeScale(float speed, const std::vector& referenceSpeeds, float* alphaOut, float* timeScaleOut) const; + void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; QVector _jointStates; int _rootJointIndex = -1; @@ -244,8 +244,8 @@ public: float _leftHandOverlayAlpha = 0.0f; float _rightHandOverlayAlpha = 0.0f; - SimpleMovingAverage _averageForwardSpeed{ 25 }; - SimpleMovingAverage _averageLateralSpeed{ 25 }; + SimpleMovingAverage _averageForwardSpeed{ 10 }; + SimpleMovingAverage _averageLateralSpeed{ 10 }; }; #endif /* defined(__hifi__Rig__) */