From a8e092272c6a7c9e401562c991dba25f9ffda734 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 4 Feb 2016 10:32:58 -0800 Subject: [PATCH 1/5] AnimStateMachine: added new State parameter interpType interpType defines how the interpolation between two states is performed. * SnapshotBoth: Stores two snapshots, the previous animation before interpolation begins and the target state at the interTarget frame. Then during the interpolation period the two snapshots are interpolated to produce smooth motion between them. * SnapshotPrev: Stores a snapshot of the previous animation before interpolation begins. However the target state is evaluated dynamically. During the interpolation period the previous snapshot is interpolated with the target pose to produce smooth motion between them. This mode is useful for interping into a blended animation where the actual blend factor is not known at the start of the interp or is might change dramatically during the interp. --- libraries/animation/src/AnimNodeLoader.cpp | 26 ++++++++++- libraries/animation/src/AnimStateMachine.cpp | 45 +++++++++++++++++--- libraries/animation/src/AnimStateMachine.h | 23 +++++++++- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 568da8dd63..723d4deb29 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -67,6 +67,16 @@ static AnimNode::Type stringToAnimNodeType(const QString& str) { return AnimNode::Type::NumTypes; } +static AnimStateMachine::InterpType stringToInterpType(const QString& str) { + if (str == "snapshotBoth") { + return AnimStateMachine::InterpType::SnapshotBoth; + } else if (str == "snapshotPrev") { + return AnimStateMachine::InterpType::SnapshotPrev; + } else { + return AnimStateMachine::InterpType::NumTypes; + } +} + static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { switch (type) { case AnimManipulator::JointVar::Type::AbsoluteRotation: return "absoluteRotation"; @@ -465,9 +475,11 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, READ_STRING(id, stateObj, nodeId, jsonUrl, false); READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false); READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false); + READ_OPTIONAL_STRING(interpType, stateObj); READ_OPTIONAL_STRING(interpTargetVar, stateObj); READ_OPTIONAL_STRING(interpDurationVar, stateObj); + READ_OPTIONAL_STRING(interpTypeVar, stateObj); auto iter = childMap.find(id); if (iter == childMap.end()) { @@ -475,7 +487,16 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, return false; } - auto statePtr = std::make_shared(id, iter->second, interpTarget, interpDuration); + AnimStateMachine::InterpType interpTypeEnum = AnimStateMachine::InterpType::SnapshotBoth; // default value + if (!interpType.isEmpty()) { + interpTypeEnum = stringToInterpType(interpType); + if (interpTypeEnum == AnimStateMachine::InterpType::NumTypes) { + qCCritical(animation) << "AnimNodeLoader, bad interpType on stateMachine state, nodeId = " << nodeId << "stateId =" << id << "url = " << jsonUrl.toDisplayString(); + return false; + } + } + + auto statePtr = std::make_shared(id, iter->second, interpTarget, interpDuration, interpTypeEnum); assert(statePtr); if (!interpTargetVar.isEmpty()) { @@ -484,6 +505,9 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, if (!interpDurationVar.isEmpty()) { statePtr->setInterpDurationVar(interpDurationVar); } + if (!interpTypeVar.isEmpty()) { + statePtr->setInterpTypeVar(interpTypeVar); + } smNode->addState(statePtr); stateMap.insert(StateMap::value_type(statePtr->getID(), statePtr)); diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 3f5a81a861..e8f9c944b7 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -52,8 +52,25 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl if (_duringInterp) { _alpha += _alphaVel * dt; if (_alpha < 1.0f) { - if (_poses.size() > 0 && _nextPoses.size() > 0 && _prevPoses.size() > 0) { - ::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]); + AnimPoseVec* nextPoses = nullptr; + AnimPoseVec* prevPoses = nullptr; + AnimPoseVec localNextPoses; + if (_interpType == InterpType::SnapshotBoth) { + // interp between both snapshots + prevPoses = &_prevPoses; + nextPoses = &_nextPoses; + } else if (_interpType == InterpType::SnapshotPrev) { + // interp between the prev snapshot and evaluated next target. + // this is useful for interping into a blend + localNextPoses = currentStateNode->evaluate(animVars, dt, triggersOut); + prevPoses = &_prevPoses; + nextPoses = &localNextPoses; + } else { + assert(false); + } + + if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) { + ::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]); } } else { _duringInterp = false; @@ -86,16 +103,32 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe _alpha = 0.0f; float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration)); _alphaVel = FRAMES_PER_SECOND / duration; - _prevPoses = _poses; - nextStateNode->setCurrentFrame(desiredState->_interpTarget); + _interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType); // because dt is 0, we should not encounter any triggers const float dt = 0.0f; Triggers triggers; - _nextPoses = nextStateNode->evaluate(animVars, dt, triggers); + + if (_interpType == InterpType::SnapshotBoth) { + // snapshot previous pose. + _prevPoses = _poses; + // snapshot next pose at the target frame. + nextStateNode->setCurrentFrame(desiredState->_interpTarget); + _nextPoses = nextStateNode->evaluate(animVars, dt, triggers); + } else if (_interpType == InterpType::SnapshotPrev) { + // snapshot previoius pose + _prevPoses = _poses; + // no need to evaluate _nextPoses we will do it dynamically during the interp, + // however we need to set the current frame. + nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration); + } else { + assert(false); + } + #if WANT_DEBUG - qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget; + qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType; #endif + _currentState = desiredState; } diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index dda56235d5..6a28ef1825 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -31,13 +31,27 @@ // visible after interpolation is complete. // * interpDuration - (frames) The total length of time it will take to interp between the current pose and the // interpTarget frame. +// * interpType - How the interpolation is performed. +// * SnapshotBoth: Stores two snapshots, the previous animation before interpolation begins and the target state at the +// interTarget frame. Then during the interpolation period the two snapshots are interpolated to produce smooth motion between them. +// * SnapshotPrev: Stores a snapshot of the previous animation before interpolation begins. However the target state is +// evaluated dynamically. During the interpolation period the previous snapshot is interpolated with the target pose +// to produce smooth motion between them. This mode is useful for interping into a blended animation where the actual +// blend factor is not known at the start of the interp or is might change dramatically during the interp. class AnimStateMachine : public AnimNode { public: friend class AnimNodeLoader; friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl); + enum class InterpType { + SnapshotBoth = 0, + SnapshotPrev, + NumTypes + }; + protected: + class State { public: friend AnimStateMachine; @@ -55,14 +69,16 @@ protected: State::Pointer _state; }; - State(const QString& id, int childIndex, float interpTarget, float interpDuration) : + State(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType) : _id(id), _childIndex(childIndex), _interpTarget(interpTarget), - _interpDuration(interpDuration) {} + _interpDuration(interpDuration), + _interpType(interpType) {} void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; } void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; } + void setInterpTypeVar(const QString& interpTypeVar) { _interpTypeVar = interpTypeVar; } int getChildIndex() const { return _childIndex; } const QString& getID() const { return _id; } @@ -78,9 +94,11 @@ protected: int _childIndex; float _interpTarget; // frames float _interpDuration; // frames + InterpType _interpType; QString _interpTargetVar; QString _interpDurationVar; + QString _interpTypeVar; std::vector _transitions; @@ -115,6 +133,7 @@ protected: // interpolation state bool _duringInterp = false; + InterpType _interpType { InterpType::SnapshotBoth }; float _alphaVel = 0.0f; float _alpha = 0.0f; AnimPoseVec _prevPoses; From 8ca8550f2643b23fcb61831cfdf7d5efe26c84e7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 4 Feb 2016 17:56:07 -0800 Subject: [PATCH 2/5] MyAvatar: Standing Takeoff and In-Air Animations Now there are two sets of of jump takeoff and in-air animations. * Run - Used when the character jumps or falls with a small forward velocity. * Standing - Used when the character jumps or falls in-place or backward. CharacterController * increased takeoff duration to 250 ms * increased takeoff to fly duration to 1100 ms * added standing jump and in-air animations * added 250 milisecond delay between ground and hover, to prevent going into hover when walking over cracks. * take-off to in-air transitions now use the new snapshotPrev interp type for a smoother tweening. --- .../defaultAvatar_full/avatar-animation.json | 154 ++++++++++++++---- libraries/animation/src/Rig.cpp | 53 ++++-- libraries/animation/src/Rig.h | 2 +- libraries/physics/src/CharacterController.cpp | 51 +++--- libraries/physics/src/CharacterController.h | 7 +- 5 files changed, 200 insertions(+), 67 deletions(-) diff --git a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json index a4e39969b4..8980abf740 100644 --- a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json +++ b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json @@ -248,8 +248,10 @@ { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoff", "state": "takeoff" }, - { "var": "isInAir", "state": "inAir" } + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } ] }, { @@ -266,8 +268,10 @@ { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoff", "state": "takeoff" }, - { "var": "isInAir", "state": "inAir" } + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } ] }, { @@ -283,8 +287,10 @@ { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoff", "state": "takeoff" }, - { "var": "isInAir", "state": "inAir" } + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } ] }, { @@ -300,8 +306,10 @@ { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoff", "state": "takeoff" }, - { "var": "isInAir", "state": "inAir" } + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } ] }, { @@ -317,8 +325,10 @@ { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoff", "state": "takeoff" }, - { "var": "isInAir", "state": "inAir" } + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } ] }, { @@ -334,8 +344,10 @@ { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoff", "state": "takeoff" }, - { "var": "isInAir", "state": "inAir" } + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } ] }, { @@ -351,8 +363,10 @@ { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoff", "state": "takeoff" }, - { "var": "isInAir", "state": "inAir" } + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } ] }, { @@ -368,8 +382,10 @@ { "var": "isTurningRight", "state": "turnRight" }, { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoff", "state": "takeoff" }, - { "var": "isInAir", "state": "inAir" } + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } ] }, { @@ -407,18 +423,38 @@ ] }, { - "id": "takeoff", + "id": "takeoffStand", "interpTarget": 0, "interpDuration": 6, "transitions": [ { "var": "isAway", "state": "awayIntro" }, - { "var": "isNotTakeoff", "state": "inAir" } + { "var": "isNotTakeoff", "state": "inAirStand" } ] }, { - "id": "inAir", + "id": "takeoffRun", "interpTarget": 0, "interpDuration": 6, + "transitions": [ + { "var": "isAway", "state": "awayIntro" }, + { "var": "isNotTakeoff", "state": "inAirRun" } + ] + }, + { + "id": "inAirStand", + "interpTarget": 0, + "interpDuration": 6, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isAway", "state": "awayIntro" }, + { "var": "isNotInAir", "state": "idle" } + ] + }, + { + "id": "inAirRun", + "interpTarget": 0, + "interpDuration": 6, + "interpType": "snapshotPrev", "transitions": [ { "var": "isAway", "state": "awayIntro" }, { "var": "isNotInAir", "state": "idle" } @@ -723,19 +759,31 @@ "children": [] }, { - "id": "takeoff", + "id": "takeoffStand", "type": "clip", "data": { - "url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_takeoff.fbx", - "startFrame": 1.0, - "endFrame": 2.5, + "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_standing_takeoff.fbx", + "startFrame": 17.0, + "endFrame": 25.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "inAir", + "id": "takeoffRun", + "type": "clip", + "data": { + "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_takeoff.fbx", + "startFrame": 1.0, + "endFrame": 2.5, + "timeScale": 0.01, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStand", "type": "blendLinear", "data": { "alpha": 0.0, @@ -743,10 +791,10 @@ }, "children": [ { - "id": "inAirPreApex", + "id": "inAirStandPreApex", "type": "clip", "data": { - "url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx", + "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_standing_apex.fbx", "startFrame": 0.0, "endFrame": 0.0, "timeScale": 0.0, @@ -755,10 +803,56 @@ "children": [] }, { - "id": "inAirApex", + "id": "inAirStandApex", "type": "clip", "data": { - "url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx", + "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_standing_apex.fbx", + "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "inAirRun", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 0.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunApex", + "type": "clip", + "data": { + "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx", "startFrame": 6.0, "endFrame": 6.0, "timeScale": 1.0, @@ -767,10 +861,10 @@ "children": [] }, { - "id": "inAirPostApex", + "id": "inAirRunPostApex", "type": "clip", "data": { - "url": "https://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx", + "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/jump_in_air.fbx", "startFrame": 11.0, "endFrame": 11.0, "timeScale": 1.0, diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 2ea9d782d5..5d8b6d8fc4 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -627,6 +627,8 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // Skip hystersis timer for jump transitions. if (_desiredState == RigRole::Takeoff) { _desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER; + } else if (_state == RigRole::Takeoff && _desiredState == RigRole::InAir) { + _desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER; } else if (_state == RigRole::InAir && _desiredState != RigRole::InAir) { _desiredStateAge = STATE_CHANGE_HYSTERESIS_TIMER; } @@ -679,9 +681,11 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); - _animVars.set("isTakeoff", false); + _animVars.set("isTakeoffStand", false); + _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); - _animVars.set("isInAir", false); + _animVars.set("isInAirStand", false); + _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); } } else if (_state == RigRole::Turn) { @@ -703,9 +707,11 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotMoving", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); - _animVars.set("isTakeoff", false); + _animVars.set("isTakeoffStand", false); + _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); - _animVars.set("isInAir", false); + _animVars.set("isInAirStand", false); + _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); } else if (_state == RigRole::Idle ) { @@ -720,9 +726,11 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); - _animVars.set("isTakeoff", false); + _animVars.set("isTakeoffStand", false); + _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); - _animVars.set("isInAir", false); + _animVars.set("isInAirStand", false); + _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); } else if (_state == RigRole::Hover) { @@ -737,9 +745,11 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotTurning", true); _animVars.set("isFlying", true); _animVars.set("isNotFlying", false); - _animVars.set("isTakeoff", false); + _animVars.set("isTakeoffStand", false); + _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); - _animVars.set("isInAir", false); + _animVars.set("isInAirStand", false); + _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); } else if (_state == RigRole::Takeoff) { @@ -754,9 +764,19 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); - _animVars.set("isTakeoff", true); + + bool takeOffRun = forwardSpeed > 0.1f; + if (takeOffRun) { + _animVars.set("isTakeoffStand", false); + _animVars.set("isTakeoffRun", true); + } else { + _animVars.set("isTakeoffStand", true); + _animVars.set("isTakeoffRun", false); + } + _animVars.set("isNotTakeoff", false); - _animVars.set("isInAir", true); + _animVars.set("isInAirStand", false); + _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", false); } else if (_state == RigRole::InAir) { @@ -771,9 +791,18 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); - _animVars.set("isTakeoff", false); + _animVars.set("isTakeoffStand", false); + _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); - _animVars.set("isInAir", true); + + bool inAirRun = forwardSpeed > 0.1f; + if (inAirRun) { + _animVars.set("isInAirStand", false); + _animVars.set("isInAirRun", true); + } else { + _animVars.set("isInAirStand", true); + _animVars.set("isInAirRun", false); + } _animVars.set("isNotInAir", false); // compute blend based on velocity diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index a360140b16..e4668d6c2a 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -301,7 +301,7 @@ public: std::map _origRoleAnimations; std::vector _prefetchedAnimations; - bool _lastEnableInverseKinematics { false }; + bool _lastEnableInverseKinematics { true }; bool _enableInverseKinematics { true }; private: diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index d16c406658..2b2496345b 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -60,9 +60,10 @@ CharacterController::CharacterController() { _jumpSpeed = JUMP_SPEED; _state = State::Hover; _isPushingUp = false; - _jumpButtonDownStart = 0; + _rayHitStartTime = 0; + _takeoffToInAirStartTime = 0; + _jumpButtonDownStartTime = 0; _jumpButtonDownCount = 0; - _takeoffToInAirStart = 0; _followTime = 0.0f; _followLinearDisplacement = btVector3(0, 0, 0); _followAngularDisplacement = btQuaternion::getIdentity(); @@ -417,6 +418,8 @@ glm::vec3 CharacterController::getLinearVelocity() const { void CharacterController::preSimulation() { if (_enabled && _dynamicsWorld) { + quint64 now = usecTimestampNow(); + // slam body to where it is supposed to be _rigidBody->setWorldTransform(_characterBodyTransform); btVector3 velocity = _rigidBody->getLinearVelocity(); @@ -432,27 +435,30 @@ void CharacterController::preSimulation() { btScalar rayLength = _radius + MAX_FALL_HEIGHT; btVector3 rayEnd = rayStart - rayLength * _currentUp; + const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius; + const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND; + const btScalar MIN_HOVER_HEIGHT = 2.5f; + const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND; + const btScalar MAX_WALKING_SPEED = 2.5f; + const quint64 RAY_HIT_START_PERIOD = 500 * MSECS_PER_SECOND; + ClosestNotMe rayCallback(_rigidBody); rayCallback.m_closestHitFraction = 1.0f; _dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback); - if (rayCallback.hasHit()) { + bool rayHasHit = rayCallback.hasHit(); + if (rayHasHit) { + _rayHitStartTime = now; _floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius; + } else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) { + rayHasHit = true; } else { _floorDistance = FLT_MAX; } - const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius; - const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 200 * MSECS_PER_SECOND; - const btScalar MIN_HOVER_HEIGHT = 2.5f; - const quint64 JUMP_TO_HOVER_PERIOD = 750 * MSECS_PER_SECOND; - const btScalar MAX_WALKING_SPEED = 2.5f; - - quint64 now = usecTimestampNow(); - // record a time stamp when the jump button was first pressed. if ((_previousFlags & PENDING_FLAG_JUMP) != (_pendingFlags & PENDING_FLAG_JUMP)) { if (_pendingFlags & PENDING_FLAG_JUMP) { - _jumpButtonDownStart = now; + _jumpButtonDownStartTime = now; _jumpButtonDownCount++; } } @@ -462,19 +468,22 @@ void CharacterController::preSimulation() { switch (_state) { case State::Ground: - if (!rayCallback.hasHit() && !_hasSupport) { - SET_STATE(State::Hover, "no ground"); - } else if (_pendingFlags & PENDING_FLAG_JUMP) { - _takeOffJumpButtonID = _jumpButtonDownCount; + if (!rayHasHit && !_hasSupport) { + SET_STATE(State::Hover, "no ground detected"); + } else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) { + _takeoffJumpButtonID = _jumpButtonDownCount; + _takeoffToInAirStartTime = now; SET_STATE(State::Takeoff, "jump pressed"); + } else if (rayHasHit && !_hasSupport && _floorDistance > JUMP_PROXIMITY_THRESHOLD) { + SET_STATE(State::InAir, "falling"); } break; case State::Takeoff: - if (!rayCallback.hasHit() && !_hasSupport) { + if (!rayHasHit && !_hasSupport) { SET_STATE(State::Hover, "no ground"); - } else if ((now - _takeoffToInAirStart) > TAKE_OFF_TO_IN_AIR_PERIOD) { + } else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) { SET_STATE(State::InAir, "takeoff done"); - _takeoffToInAirStart = now + USECS_PER_SECOND * 86500.0f; + _takeoffToInAirStartTime = now + USECS_PER_SECOND * 86500.0f; velocity += _jumpSpeed * _currentUp; _rigidBody->setLinearVelocity(velocity); } @@ -482,9 +491,9 @@ void CharacterController::preSimulation() { case State::InAir: { if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < JUMP_PROXIMITY_THRESHOLD) || _hasSupport)) { SET_STATE(State::Ground, "hit ground"); - } else if (jumpButtonHeld && (_takeOffJumpButtonID != _jumpButtonDownCount)) { + } else if (jumpButtonHeld && (_takeoffJumpButtonID != _jumpButtonDownCount)) { SET_STATE(State::Hover, "double jump button"); - } else if (jumpButtonHeld && (now - _jumpButtonDownStart) > JUMP_TO_HOVER_PERIOD) { + } else if (jumpButtonHeld && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { SET_STATE(State::Hover, "jump button held"); } break; diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index bb4a135ca3..86ef350812 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -114,10 +114,11 @@ protected: glm::vec3 _boxScale; // used to compute capsule shape - quint64 _takeoffToInAirStart; - quint64 _jumpButtonDownStart; + quint64 _rayHitStartTime; + quint64 _takeoffToInAirStartTime; + quint64 _jumpButtonDownStartTime; quint32 _jumpButtonDownCount; - quint32 _takeOffJumpButtonID; + quint32 _takeoffJumpButtonID; btScalar _halfHeight; btScalar _radius; From 51189cfc503aea5d8646723ad13b971d30fe8728 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 4 Feb 2016 17:56:41 -0800 Subject: [PATCH 3/5] AnimInverseKinematics: opened up UpLeg and Leg constraints This improves the quality of the jump animations, while IK is enabled. --- .../animation/src/AnimInverseKinematics.cpp | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 9f08ce455a..dc589a5f05 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -590,17 +590,24 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f); - // these directions are approximate swing limits in root-frame - // NOTE: they don't need to be normalized std::vector swungDirections; - swungDirections.push_back(glm::vec3(mirror * 0.25f, 0.0f, 1.0f)); - swungDirections.push_back(glm::vec3(mirror * -0.5f, 0.0f, 1.0f)); - swungDirections.push_back(glm::vec3(mirror * -1.0f, 0.0f, 1.0f)); - swungDirections.push_back(glm::vec3(mirror * -1.0f, 0.0f, 0.0f)); - swungDirections.push_back(glm::vec3(mirror * -0.5f, -0.5f, -1.0f)); - swungDirections.push_back(glm::vec3(mirror * 0.0f, -0.75f, -1.0f)); - swungDirections.push_back(glm::vec3(mirror * 0.25f, -1.0f, 0.0f)); - swungDirections.push_back(glm::vec3(mirror * 0.25f, -1.0f, 1.0f)); + float deltaTheta = PI / 4.0f; + float theta = 0.0f; + swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.25f, sin(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.0f, sin(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cos(theta), -0.25f, sin(theta))); // posterior + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.0f, sin(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.25f, sin(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.5f, sin(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.5f, sin(theta))); // anterior + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.5f, sin(theta))); // rotate directions into joint-frame glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot); @@ -755,7 +762,7 @@ void AnimInverseKinematics::initConstraints() { // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame // then measure the angles to swing the yAxis into alignment const float MIN_KNEE_ANGLE = 0.0f; - const float MAX_KNEE_ANGLE = 3.0f * PI / 4.0f; + const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f; glm::quat invReferenceRotation = glm::inverse(referenceRotation); glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y; glm::vec3 maxSwingAxis = invReferenceRotation * glm::angleAxis(MAX_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y; From bfeace78f71b755970013ee1d5ed7336107ae47f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 4 Feb 2016 18:51:48 -0800 Subject: [PATCH 4/5] AnimInverseKinematics: warning fixes --- .../animation/src/AnimInverseKinematics.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index dc589a5f05..92d8240510 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -593,21 +593,21 @@ void AnimInverseKinematics::initConstraints() { std::vector swungDirections; float deltaTheta = PI / 4.0f; float theta = 0.0f; - swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.25f, sin(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.0f, sin(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cos(theta), -0.25f, sin(theta))); // posterior + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); // posterior theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.0f, sin(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.25f, sin(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.5f, sin(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.5f, sin(theta))); // anterior + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); // anterior theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cos(theta), 0.5f, sin(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); // rotate directions into joint-frame glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot); From f13e31c7fc34b3ef985aec622d1cfc3e538ae06c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 5 Feb 2016 10:53:58 -0800 Subject: [PATCH 5/5] CharacterController: removed unnecessary code. --- libraries/physics/src/CharacterController.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 2b2496345b..1f61bb9a59 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -483,7 +483,6 @@ void CharacterController::preSimulation() { SET_STATE(State::Hover, "no ground"); } else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) { SET_STATE(State::InAir, "takeoff done"); - _takeoffToInAirStartTime = now + USECS_PER_SECOND * 86500.0f; velocity += _jumpSpeed * _currentUp; _rigidBody->setLinearVelocity(velocity); }