From 8ca8550f2643b23fcb61831cfdf7d5efe26c84e7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 4 Feb 2016 17:56:07 -0800 Subject: [PATCH] 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;