diff --git a/interface/resources/avatar/animations/sitting_idle.fbx b/interface/resources/avatar/animations/sitting_idle.fbx new file mode 100644 index 0000000000..ee03d942cd Binary files /dev/null and b/interface/resources/avatar/animations/sitting_idle.fbx differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index dd6e75a20f..2f063554bb 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -583,6 +583,28 @@ "outputJoints": ["LeftFoot", "RightFoot"], "currentState": "idle", "states": [ + { + "id": "seated", + "interpTarget": 20, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, { "id": "idle", "interpTarget": 20, @@ -601,7 +623,8 @@ { "var": "isInAirStand", "state": "inAirStand" }, { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -622,7 +645,8 @@ { "var": "isInAirStand", "state": "inAirStand" }, { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -644,7 +668,8 @@ { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" } + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -665,7 +690,8 @@ { "var": "isInAirStand", "state": "inAirStand" }, { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -686,7 +712,8 @@ { "var": "isInAirStand", "state": "inAirStand" }, { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -707,7 +734,8 @@ { "var": "isInAirStand", "state": "inAirStand" }, { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -728,7 +756,8 @@ { "var": "isInAirStand", "state": "inAirStand" }, { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -748,7 +777,8 @@ { "var": "isInAirStand", "state": "inAirStand" }, { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -768,7 +798,8 @@ { "var": "isInAirStand", "state": "inAirStand" }, { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -789,7 +820,8 @@ { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" } + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -810,7 +842,8 @@ { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "INAIRRUN" } + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -884,7 +917,8 @@ { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "landStandOnDone", "state": "idle" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "var": "isSeated", "state": "seated" } ] }, { @@ -901,6 +935,18 @@ ] }, "children": [ + { + "id": "seated", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/sitting_idle.fbx", + "startFrame": 1.0, + "endFrame": 350.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, { "id": "idle", "type": "stateMachine", diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e2f14b8708..d495077a87 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6207,3 +6207,51 @@ void MyAvatar::sendPacket(const QUuid& entityID) const { }); } } + +void MyAvatar::setSitDriveKeysStatus(bool enabled) { + const std::vector DISABLED_DRIVE_KEYS_DURING_SIT = { + DriveKeys::TRANSLATE_X, + DriveKeys::TRANSLATE_Y, + DriveKeys::TRANSLATE_Z, + DriveKeys::STEP_TRANSLATE_X, + DriveKeys::STEP_TRANSLATE_Y, + DriveKeys::STEP_TRANSLATE_Z + }; + for (auto key : DISABLED_DRIVE_KEYS_DURING_SIT) { + if (enabled) { + enableDriveKey(key); + } else { + disableDriveKey(key); + } + } +} + +void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) { + _characterController.setSeated(true); + setCollisionsEnabled(false); + setHMDLeanRecenterEnabled(false); + // Disable movement + setSitDriveKeysStatus(false); + centerBody(); + int hipIndex = getJointIndex("Hips"); + clearPinOnJoint(hipIndex); + goToLocation(position, true, rotation, false, false); + pinJoint(hipIndex, position, rotation); +} + +void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { + if (_characterController.getSeated()) { + clearPinOnJoint(getJointIndex("Hips")); + _characterController.setSeated(false); + setCollisionsEnabled(true); + setHMDLeanRecenterEnabled(true); + centerBody(); + goToLocation(position, true, rotation, false, false); + float TIME_BEFORE_DRIVE_ENABLED_MS = 150.0f; + QTimer::singleShot(TIME_BEFORE_DRIVE_ENABLED_MS, [this]() { + // Enable movement again + setSitDriveKeysStatus(true); + }); + } + +} \ No newline at end of file diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3f8a2a6f0e..db373c6402 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1835,6 +1835,22 @@ public: */ Q_INVOKABLE QVariantList getCollidingFlowJoints(); + /**jsdoc + * Starts a sitting action for the avatar + * @function MyAvatar.beginSit + * @param {Vec3} position - The point in space where the avatar will sit. + * @param {Quat} rotation - Initial absolute orientation of the avatar once is seated. + */ + Q_INVOKABLE void beginSit(const glm::vec3& position, const glm::quat& rotation); + + /**jsdoc + * Ends a sitting action for the avatar + * @function MyAvatar.endSit + * @param {Vec3} position - The position of the avatar when standing up. + * @param {Quat} rotation - The absolute rotation of the avatar once the sitting action ends. + */ + Q_INVOKABLE void endSit(const glm::vec3& position, const glm::quat& rotation); + int getOverrideJointCount() const; bool getFlowActive() const; bool getNetworkGraphActive() const; @@ -2521,6 +2537,7 @@ private: virtual void updatePalms() override {} void lateUpdatePalms(); + void setSitDriveKeysStatus(bool enabled); void clampTargetScaleToDomainLimits(); void clampScaleChangeToDomainLimits(float desiredScale); diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 9c555aa35b..6080911dd9 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -32,6 +32,8 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle return Rig::CharacterControllerState::InAir; case CharacterController::State::Hover: return Rig::CharacterControllerState::Hover; + case CharacterController::State::Seated: + return Rig::CharacterControllerState::Seated; }; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0f637bf082..5f4c49364e 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1092,6 +1092,11 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _desiredStateAge = 0.0f; } _desiredState = RigRole::Takeoff; + } else if (ccState == CharacterControllerState::Seated) { + if (_desiredState != RigRole::Seated) { + _desiredStateAge = 0.0f; + } + _desiredState = RigRole::Seated; } else { float moveThresh; if (_state != RigRole::Move) { @@ -1216,6 +1221,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); + _animVars.set("isSeated", false); } else if (_state == RigRole::Turn) { if (turningSpeed > 0.0f) { @@ -1244,6 +1250,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); + _animVars.set("isSeated", false); } else if (_state == RigRole::Idle) { // default anim vars to notMoving and notTurning @@ -1265,6 +1272,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); + _animVars.set("isSeated", false); } else if (_state == RigRole::Hover) { // flying. @@ -1286,6 +1294,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); + _animVars.set("isSeated", false); } else if (_state == RigRole::Takeoff) { // jumping in-air @@ -1315,6 +1324,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInAirStand", false); _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", false); + _animVars.set("isSeated", false); } else if (_state == RigRole::InAir) { // jumping in-air @@ -1333,6 +1343,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isTakeoffStand", false); _animVars.set("isTakeoffRun", false); _animVars.set("isNotTakeoff", true); + _animVars.set("isSeated", false); bool inAirRun = forwardSpeed > 0.1f; if (inAirRun) { @@ -1354,6 +1365,26 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos float alpha = glm::clamp((-workingVelocity.y * sensorToWorldScale) / jumpSpeed, -1.0f, 1.0f) + 1.0f; _animVars.set("inAirAlpha", alpha); + } else if (_state == RigRole::Seated) { + _animVars.set("isMovingForward", false); + _animVars.set("isMovingBackward", false); + _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); + _animVars.set("isNotMoving", false); + _animVars.set("isTurningRight", false); + _animVars.set("isTurningLeft", false); + _animVars.set("isNotTurning", true); + _animVars.set("isFlying", false); + _animVars.set("isNotFlying", true); + _animVars.set("isTakeoffStand", false); + _animVars.set("isTakeoffRun", false); + _animVars.set("isNotTakeoff", true); + _animVars.set("isInAirStand", false); + _animVars.set("isInAirRun", false); + _animVars.set("isNotInAir", true); + _animVars.set("isSeated", true); } t += deltaTime; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index d3fa61e5e7..7cf17ca391 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -109,7 +109,8 @@ public: Ground = 0, Takeoff, InAir, - Hover + Hover, + Seated }; Rig(); @@ -339,7 +340,8 @@ protected: Move, Hover, Takeoff, - InAir + InAir, + Seated }; RigRole _state { RigRole::Idle }; RigRole _desiredState { RigRole::Idle }; diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index f0f1b9c837..e36f97b425 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -384,6 +384,8 @@ static const char* stateToStr(CharacterController::State state) { return "InAir"; case CharacterController::State::Hover: return "Hover"; + case CharacterController::State::Seated: + return "Seated"; default: return "Unknown"; } @@ -739,9 +741,11 @@ void CharacterController::updateState() { // disable normal state transitions while collisionless const btScalar MAX_WALKING_SPEED = 2.65f; if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) { - // when collisionless: only switch between State::Ground and State::Hover + // when collisionless: only switch between State::Ground, State::Hover and State::Seated // and bypass state debugging - if (rayHasHit) { + if (_isSeated) { + _state = State::Seated; + } else if (rayHasHit) { if (velocity.length() > (MAX_WALKING_SPEED)) { _state = State::Hover; } else { @@ -802,7 +806,7 @@ void CharacterController::updateState() { } break; } - case State::Hover: + case State::Hover: { btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); if (!_zoneFlyingAllowed) { @@ -815,6 +819,11 @@ void CharacterController::updateState() { SET_STATE(State::Ground, "touching ground"); } break; + } + case State::Seated: { + SET_STATE(State::Ground, "Standing up"); + break; + } } } } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index c825f3a10b..8c65310ff1 100755 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -111,7 +111,8 @@ public: Ground = 0, Takeoff, InAir, - Hover + Hover, + Seated }; State getState() const { return _state; } @@ -135,6 +136,8 @@ public: void setCollisionlessAllowed(bool value); void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } + void setSeated(bool isSeated) { _isSeated = isSeated; } + bool getSeated() { return _isSeated; } protected: #ifdef DEBUG_STATE_CHANGE @@ -208,6 +211,7 @@ protected: State _state; bool _isPushingUp; bool _isStuck { false }; + bool _isSeated { false }; btDynamicsWorld* _dynamicsWorld { nullptr }; btRigidBody* _rigidBody { nullptr };