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/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 9f08ce455a..92d8240510 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 * cosf(theta), 0.25f, sinf(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); // posterior + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); + theta += deltaTheta; + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); // anterior + theta += deltaTheta; + 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); @@ -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; 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; 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..1f61bb9a59 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,21 @@ 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; velocity += _jumpSpeed * _currentUp; _rigidBody->setLinearVelocity(velocity); } @@ -482,9 +490,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;