From 08b525ef91e3301069cc414085bc2a1e2435311b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 22 May 2014 17:54:32 -0700 Subject: [PATCH 1/3] prevent walking avatar from bouncing on floor --- interface/src/avatar/MyAvatar.cpp | 113 ++++++++++++++++++------------ interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 71 insertions(+), 44 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 85c1734e56..6a4ababa25 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -134,45 +134,7 @@ void MyAvatar::simulate(float deltaTime) { _handState = HAND_STATE_NULL; updateOrientation(deltaTime); - - float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + - fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) + - fabsf(_driveKeys[UP] - _driveKeys[DOWN]); - - bool walkingOnFloor = false; - float gravityLength = glm::length(_gravity); - if (gravityLength > EPSILON) { - const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - glm::vec3 startCap; - boundingShape.getStartPoint(startCap); - glm::vec3 bottomOfBoundingCapsule = startCap + (boundingShape.getRadius() / gravityLength) * _gravity; - - float fallThreshold = 2.0f * deltaTime * gravityLength; - walkingOnFloor = (glm::distance(bottomOfBoundingCapsule, _lastFloorContactPoint) < fallThreshold); - } - - if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f || - ! walkingOnFloor) { - // apply gravity - _velocity += _scale * _gravity * (GRAVITY_EARTH * deltaTime); - - // update motor and thrust - updateMotorFromKeyboard(deltaTime, walkingOnFloor); - applyMotor(deltaTime); - applyThrust(deltaTime); - - // update position - if (glm::length2(_velocity) < EPSILON) { - _velocity = glm::vec3(0.0f); - } else { - _position += _velocity * deltaTime; - } - } - - // update moving flag based on speed - const float MOVING_SPEED_THRESHOLD = 0.01f; - _moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD; - updateChatCircle(deltaTime); + updatePosition(deltaTime); // update avatar skeleton and simulate hand and head getHand()->collideAgainstOurself(); @@ -833,8 +795,7 @@ void MyAvatar::updateOrientation(float deltaTime) { // We must adjust the body orientation using a delta rotation (rather than // doing yaw math) because the body's yaw ranges are not the same // as what the Oculus API provides. - glm::vec3 UP_AXIS = glm::vec3(0.0f, 1.0f, 0.0f); - glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), UP_AXIS); + glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), _worldUpDirection); orientation = orientation * bodyCorrection; } Head* head = getHead(); @@ -848,6 +809,62 @@ void MyAvatar::updateOrientation(float deltaTime) { setOrientation(orientation); } +void MyAvatar::updatePosition(float deltaTime) { + float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + + fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) + + fabsf(_driveKeys[UP] - _driveKeys[DOWN]); + + bool walkingOnFloor = false; + float gravityLength = glm::length(_gravity) * GRAVITY_EARTH; + if (gravityLength > EPSILON) { + const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); + glm::vec3 startCap; + boundingShape.getStartPoint(startCap); + glm::vec3 bottomOfBoundingCapsule = startCap - boundingShape.getRadius() * _worldUpDirection; + + float speedFromGravity = _scale * deltaTime * gravityLength; + float distanceToFall = glm::distance(bottomOfBoundingCapsule, _lastFloorContactPoint); + walkingOnFloor = (distanceToFall < 2.0f * deltaTime * speedFromGravity); + + if (walkingOnFloor) { + // BEGIN HACK: to prevent the avatar from bouncing on a floor surface + if (distanceToFall < deltaTime * speedFromGravity) { + float verticalSpeed = glm::dot(_velocity, _worldUpDirection); + if (fabs(verticalSpeed) < speedFromGravity) { + // we're standing on a floor, and nearly at rest so we zero the vertical velocity component + _velocity -= verticalSpeed * _worldUpDirection; + } + } else { + // fall with gravity against floor + _velocity -= speedFromGravity * _worldUpDirection; + } + // END HACK + } else { + _velocity -= speedFromGravity * _worldUpDirection; + } + } + + if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f || ! walkingOnFloor) { + // update motor and thrust + updateMotorFromKeyboard(deltaTime, walkingOnFloor); + applyMotor(deltaTime); + applyThrust(deltaTime); + + // update position + if (glm::length2(_velocity) < EPSILON) { + _velocity = glm::vec3(0.0f); + } else { + _position += _velocity * deltaTime; + } + } + + // update moving flag based on speed + const float MOVING_SPEED_THRESHOLD = 0.01f; + _moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD; + + updateChatCircle(deltaTime); +} + void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) { // Increase motor velocity until its length is equal to _maxMotorSpeed. if (!(_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED)) { @@ -1121,6 +1138,8 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { const float MIN_STEP_HEIGHT = 0.0f; glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection; float highestStep = 0.0f; + float lowestStep = MAX_STEP_HEIGHT; + glm::vec3 floorPoint; glm::vec3 stepPenetration(0.0f); glm::vec3 totalPenetration(0.0f); @@ -1154,8 +1173,15 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { highestStep = stepHeight; stepPenetration = collision->_penetration; } + if (stepHeight < lowestStep) { + lowestStep = stepHeight; + floorPoint = collision->_contactPoint - collision->_penetration; + } } } + if (lowestStep < MAX_STEP_HEIGHT) { + _lastFloorContactPoint = floorPoint; + } float penetrationLength = glm::length(totalPenetration); if (penetrationLength < EPSILON) { @@ -1194,8 +1220,9 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); } - const float VOXEL_COLLISION_FREQUENCY = 0.5f; - updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); + // Don't make a collision sound against voxlels by default -- too annoying when walking + //const float VOXEL_COLLISION_FREQUENCY = 0.5f; + //updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); } _trapDuration = isTrapped ? _trapDuration + deltaTime : 0.0f; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9d6f22264f..89606858e6 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -141,7 +141,6 @@ private: bool _shouldJump; float _driveKeys[MAX_DRIVE_KEYS]; glm::vec3 _gravity; - glm::vec3 _environmentGravity; float _distanceToNearestAvatar; // How close is the nearest avatar? bool _wasPushing; @@ -166,6 +165,7 @@ private: // private methods void updateOrientation(float deltaTime); + void updatePosition(float deltaTime); void updateMotorFromKeyboard(float deltaTime, bool walking); float computeMotorTimescale(); void applyMotor(float deltaTime); From b875144e2d4cb68581a75b02c1d39e2b06e2cd21 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 23 May 2014 09:55:45 -0700 Subject: [PATCH 2/3] Don't repeat check for non-zero collision groups --- interface/src/avatar/MyAvatar.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6a4ababa25..43f577d878 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -168,19 +168,17 @@ void MyAvatar::simulate(float deltaTime) { radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f)); radius *= COLLISION_RADIUS_SCALAR; } - if (_collisionGroups) { - updateShapePositions(); - if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) { - updateCollisionWithEnvironment(deltaTime, radius); - } - if (_collisionGroups & COLLISION_GROUP_VOXELS) { - updateCollisionWithVoxels(deltaTime, radius); - } else { - _trapDuration = 0.0f; - } - if (_collisionGroups & COLLISION_GROUP_AVATARS) { - updateCollisionWithAvatars(deltaTime); - } + updateShapePositions(); + if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) { + updateCollisionWithEnvironment(deltaTime, radius); + } + if (_collisionGroups & COLLISION_GROUP_VOXELS) { + updateCollisionWithVoxels(deltaTime, radius); + } else { + _trapDuration = 0.0f; + } + if (_collisionGroups & COLLISION_GROUP_AVATARS) { + updateCollisionWithAvatars(deltaTime); } } From d425b5b322bbc5140652606394d66e472fc220ea Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 23 May 2014 15:59:27 -0700 Subject: [PATCH 3/3] Enable local gravity when there is a floor nearby. --- interface/src/Menu.cpp | 3 +- interface/src/Menu.h | 1 + interface/src/avatar/MyAvatar.cpp | 63 ++++++++++++++++++++++++------ libraries/avatars/src/AvatarData.h | 20 ++++++---- 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 96bfa106f4..856e1efae7 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -200,7 +200,8 @@ Menu::Menu() : QObject* avatar = appInstance->getAvatar(); addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, avatar, SLOT(updateMotionBehaviorsFromMenu())); - + addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::StandOnNearbyFloors, 0, true, + avatar, SLOT(updateMotionBehaviorsFromMenu())); addAvatarCollisionSubMenu(editMenu); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b12f989ed6..279d2151c9 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -376,6 +376,7 @@ namespace MenuOption { const QString ShowBordersModelNodes = "Show Model Nodes"; const QString ShowBordersParticleNodes = "Show Particle Nodes"; const QString ShowIKConstraints = "Show IK Constraints"; + const QString StandOnNearbyFloors = "Stand on nearby floors"; const QString Stars = "Stars"; const QString Stats = "Stats"; const QString StopAllScripts = "Stop All Scripts"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 43f577d878..79c721e896 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -383,9 +383,9 @@ void MyAvatar::setGravity(const glm::vec3& gravity) { float gravityLength = glm::length(gravity); if (gravityLength > EPSILON) { _worldUpDirection = _gravity / -gravityLength; - } else { - _worldUpDirection = DEFAULT_UP_DIRECTION; } + // NOTE: the else case here it to leave _worldUpDirection unchanged + // so it continues to point opposite to the previous gravity setting. } AnimationHandlePointer MyAvatar::addAnimationHandle() { @@ -814,12 +814,13 @@ void MyAvatar::updatePosition(float deltaTime) { bool walkingOnFloor = false; float gravityLength = glm::length(_gravity) * GRAVITY_EARTH; - if (gravityLength > EPSILON) { - const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - glm::vec3 startCap; - boundingShape.getStartPoint(startCap); - glm::vec3 bottomOfBoundingCapsule = startCap - boundingShape.getRadius() * _worldUpDirection; + const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); + glm::vec3 startCap; + boundingShape.getStartPoint(startCap); + glm::vec3 bottomOfBoundingCapsule = startCap - boundingShape.getRadius() * _worldUpDirection; + + if (gravityLength > EPSILON) { float speedFromGravity = _scale * deltaTime * gravityLength; float distanceToFall = glm::distance(bottomOfBoundingCapsule, _lastFloorContactPoint); walkingOnFloor = (distanceToFall < 2.0f * deltaTime * speedFromGravity); @@ -840,6 +841,32 @@ void MyAvatar::updatePosition(float deltaTime) { } else { _velocity -= speedFromGravity * _worldUpDirection; } + if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { + const float MAX_VERTICAL_FLOOR_DETECTION_SPEED = _scale * MAX_WALKING_SPEED; + if (keyboardInput && glm::dot(_motorVelocity, _worldUpDirection) > 0.0f && + glm::dot(_velocity, _worldUpDirection) > MAX_VERTICAL_FLOOR_DETECTION_SPEED) { + // disable gravity because we're pushing with keyboard + setLocalGravity(glm::vec3(0.0f)); + } + } + } else { + if ((_collisionGroups & COLLISION_GROUP_VOXELS) && + _motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { + const float MIN_FLOOR_DETECTION_SPEED = _scale * 1.0f; + if (glm::length(_velocity) < MIN_FLOOR_DETECTION_SPEED ) { + // scan for floor + glm::vec3 direction = -_worldUpDirection; + OctreeElement* elementHit; // output from findRayIntersection + float distance; // output from findRayIntersection + BoxFace face; // output from findRayIntersection + Application::getInstance()->getVoxelTree()->findRayIntersection(bottomOfBoundingCapsule, direction, elementHit, distance, face); + const float NEARBY_FLOOR_THRESHOLD = _scale * 2.0f; + if (elementHit && distance < NEARBY_FLOOR_THRESHOLD) { + // turn on local gravity + setLocalGravity(-_worldUpDirection); + } + } + } } if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f || ! walkingOnFloor) { @@ -888,12 +915,12 @@ void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) { if (directionLength > EPSILON) { direction /= directionLength; // the finalMotorSpeed depends on whether we are walking or not - float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed; + float finalMaxMotorSpeed = walking ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed; float motorLength = glm::length(_motorVelocity); - if (motorLength < MIN_KEYBOARD_CONTROL_SPEED) { + if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) { // an active keyboard motor should never be slower than this - _motorVelocity = MIN_KEYBOARD_CONTROL_SPEED * direction; + _motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction; } else { float MOTOR_LENGTH_TIMESCALE = 1.5f; float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f); @@ -1566,7 +1593,8 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { } void MyAvatar::updateMotionBehaviorsFromMenu() { - if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) { + Menu* menu = Menu::getInstance(); + if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) { _motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; // Environmental and Local gravities are incompatible. Environmental setting trumps local. _motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY; @@ -1576,6 +1604,14 @@ void MyAvatar::updateMotionBehaviorsFromMenu() { if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) { setGravity(glm::vec3(0.0f)); } + if (menu->isOptionChecked(MenuOption::StandOnNearbyFloors)) { + _motionBehaviors |= AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + // standing on floors requires collision with voxels + _collisionGroups |= COLLISION_GROUP_VOXELS; + menu->setIsOptionChecked(MenuOption::CollideWithVoxels, true); + } else { + _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + } } void MyAvatar::renderAttachments(RenderMode renderMode) { @@ -1602,6 +1638,11 @@ void MyAvatar::setCollisionGroups(quint32 collisionGroups) { menu->setIsOptionChecked(MenuOption::CollideWithAvatars, (bool)(_collisionGroups & COLLISION_GROUP_AVATARS)); menu->setIsOptionChecked(MenuOption::CollideWithVoxels, (bool)(_collisionGroups & COLLISION_GROUP_VOXELS)); menu->setIsOptionChecked(MenuOption::CollideWithParticles, (bool)(_collisionGroups & COLLISION_GROUP_PARTICLES)); + if (! (_collisionGroups & COLLISION_GROUP_VOXELS)) { + // no collision with voxels --> disable standing on floors + _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + menu->setIsOptionChecked(MenuOption::StandOnNearbyFloors, false); + } } void MyAvatar::setMotionBehaviorsByScript(quint32 flags) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 072070e98c..8f658678b5 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -53,23 +53,27 @@ typedef unsigned long long quint64; #include "HandData.h" // avatar motion behaviors -const quint32 AVATAR_MOTION_MOTOR_ENABLED = 1U << 0; -const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 1; -const quint32 AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME = 1U << 2; -const quint32 AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY = 1U << 3; +const quint32 AVATAR_MOTION_MOTOR_ENABLED = 1U << 0; +const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 1; +const quint32 AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME = 1U << 2; +const quint32 AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY = 1U << 3; -const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 4; -const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 5; +const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 4; +const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 5; + +const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 6; const quint32 AVATAR_MOTION_DEFAULTS = AVATAR_MOTION_MOTOR_ENABLED | AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED | - AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME; + AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME | + AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; // these bits will be expanded as features are exposed const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | - AVATAR_MOTION_OBEY_LOCAL_GRAVITY; + AVATAR_MOTION_OBEY_LOCAL_GRAVITY | + AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; // First bitset