diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7bc35ebd6d..0c37b6505c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -46,6 +46,9 @@ const float PITCH_SPEED = 100.0f; // degrees/sec const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions const float COLLISION_RADIUS_SCALE = 0.125f; +const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f; +const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED; + const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f; // TODO: normalize avatar speed for standard avatar size, then scale all motion logic @@ -64,7 +67,7 @@ MyAvatar::MyAvatar() : _distanceToNearestAvatar(std::numeric_limits::max()), _wasPushing(false), _isPushing(false), - _wasStuck(false), + _trapDuration(0.0f), _thrust(0.0f), _motorVelocity(0.0f), _motorTimescale(DEFAULT_MOTOR_TIMESCALE), @@ -226,7 +229,7 @@ void MyAvatar::simulate(float deltaTime) { if (_collisionGroups & COLLISION_GROUP_VOXELS) { updateCollisionWithVoxels(deltaTime, radius); } else { - _wasStuck = false; + _trapDuration = 0.0f; } if (_collisionGroups & COLLISION_GROUP_AVATARS) { updateCollisionWithAvatars(deltaTime); @@ -718,8 +721,6 @@ void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) { if (directionLength > EPSILON) { direction /= directionLength; // the finalMotorSpeed depends on whether we are walking or not - const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f; - const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED; float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed; float motorLength = glm::length(_motorVelocity); @@ -949,48 +950,102 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { static CollisionList myCollisions(64); void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { - const float MIN_STUCK_SPEED = 100.0f; + const float MAX_VOXEL_COLLISION_SPEED = 100.0f; float speed = glm::length(_velocity); - if (speed > MIN_STUCK_SPEED) { + if (speed > MAX_VOXEL_COLLISION_SPEED) { // don't even bother to try to collide against voxles when moving very fast + _trapDuration = 0.0f; return; } + bool isTrapped = false; myCollisions.clear(); const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions)) { + if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions, Octree::TryLock)) { const float VOXEL_ELASTICITY = 0.0f; const float VOXEL_DAMPING = 0.0f; float capsuleRadius = boundingShape.getRadius(); - + float capsuleHalfHeight = boundingShape.getHalfHeight(); + const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight; + const float MIN_STEP_HEIGHT = 0.0f; + glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection; + float highestStep = 0.0f; + glm::vec3 stepPenetration(0.0f); glm::vec3 totalPenetration(0.0f); - bool isStuck = false; + for (int i = 0; i < myCollisions.size(); ++i) { CollisionInfo* collision = myCollisions[i]; - float depth = glm::length(collision->_penetration); - if (depth > capsuleRadius) { - isStuck = true; - if (_wasStuck) { - glm::vec3 cubeCenter = collision->_vecData; - float cubeSide = collision->_floatData; + glm::vec3 cubeCenter = collision->_vecData; + float cubeSide = collision->_floatData; + float verticalDepth = glm::dot(collision->_penetration, _worldUpDirection); + float horizontalDepth = glm::length(collision->_penetration - verticalDepth * _worldUpDirection); + const float MAX_TRAP_PERIOD = 0.125f; + if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) { + isTrapped = true; + if (_trapDuration > MAX_TRAP_PERIOD) { float distance = glm::dot(boundingShape.getPosition() - cubeCenter, _worldUpDirection); if (distance < 0.0f) { distance = fabsf(distance) + 0.5f * cubeSide; } - distance += capsuleRadius + boundingShape.getHalfHeight(); + distance += capsuleRadius + capsuleHalfHeight; totalPenetration = addPenetrations(totalPenetration, - distance * _worldUpDirection); continue; } + } else if (_trapDuration > MAX_TRAP_PERIOD) { + // we're trapped, ignore this collision + continue; } totalPenetration = addPenetrations(totalPenetration, collision->_penetration); + if (glm::dot(collision->_penetration, _velocity) >= 0.0f) { + glm::vec3 cubeTop = cubeCenter + (0.5f * cubeSide) * _worldUpDirection; + float stepHeight = glm::dot(_worldUpDirection, cubeTop - footBase); + if (stepHeight > highestStep) { + highestStep = stepHeight; + stepPenetration = collision->_penetration; + } + } + } + + float penetrationLength = glm::length(totalPenetration); + if (penetrationLength < EPSILON) { + _trapDuration = 0.0f; + return; + } + float verticalPenetration = glm::dot(totalPenetration, _worldUpDirection); + if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) { + // we're colliding against an edge + glm::vec3 targetVelocity = _motorVelocity; + if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { + // rotate _motorVelocity into world frame + glm::quat rotation = getHead()->getCameraOrientation(); + targetVelocity = rotation * _motorVelocity; + } + if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) { + // we're puhing into the edge, so we want to lift + + // remove unhelpful horizontal component of the step's penetration + totalPenetration -= stepPenetration - (glm::dot(stepPenetration, _worldUpDirection) * _worldUpDirection); + + // further adjust penetration to help lift + float liftSpeed = glm::max(MAX_WALKING_SPEED, speed); + float thisStep = glm::min(liftSpeed * deltaTime, highestStep); + float extraStep = glm::dot(totalPenetration, _worldUpDirection) + thisStep; + if (extraStep > 0.0f) { + totalPenetration -= extraStep * _worldUpDirection; + } + + _position -= totalPenetration; + } else { + // we're not pushing into the edge, so let the avatar fall + applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); + } + } else { + applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); } - applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); - _wasStuck = isStuck; const float VOXEL_COLLISION_FREQUENCY = 0.5f; updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); - } else { - _wasStuck = false; - } + } + _trapDuration = isTrapped ? _trapDuration + deltaTime : 0.0f; } void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity, float damping) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 885d56b61f..af4f3566d6 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -126,7 +126,7 @@ private: bool _wasPushing; bool _isPushing; - bool _wasStuck; + float _trapDuration; // seconds that avatar has been trapped by collisions glm::vec3 _thrust; // final acceleration from outside sources for the current frame glm::vec3 _motorVelocity; // intended velocity of avatar motion