From 821ac605f5dc8cd93cf8460f456defe05b099041 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 26 Mar 2015 19:48:52 -0700 Subject: [PATCH] smoother motion on steps faster motion when "flying" cleanup of MyAvatar::updatePosition() --- interface/src/avatar/MyAvatar.cpp | 136 +++--------------- interface/src/avatar/MyAvatar.h | 1 - libraries/physics/src/CharacterController.cpp | 55 ++++--- libraries/physics/src/CharacterController.h | 6 +- 4 files changed, 63 insertions(+), 135 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2f42544f28..aa36748f0c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -174,11 +174,7 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("transform"); updateOrientation(deltaTime); - if (isPhysicsEnabled()) { - updatePositionWithPhysics(deltaTime); - } else { - updatePosition(deltaTime); - } + updatePosition(deltaTime); } { @@ -1258,128 +1254,38 @@ glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVe return localVelocity + motorEfficiency * deltaVelocity; } -const float NEARBY_FLOOR_THRESHOLD = 5.0f; - void MyAvatar::updatePosition(float deltaTime) { - - // check for floor by casting a ray straight down from avatar's position - float heightAboveFloor = FLT_MAX; - bool hasFloor = false; - const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD; - - RayIntersectionInfo intersection; - // NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast - intersection._rayStart = glm::vec3(0.0f); - intersection._rayDirection = - _worldUpDirection; - intersection._rayLength = 4.0f * boundingShape.getBoundingRadius(); - - // velocity is initialized to the measured _velocity but will be modified by friction, external thrust, etc - glm::vec3 velocity = _velocity; - - bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f) || _scriptedMotorVelocity.y > 0.0f; - if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { - const float MAX_SPEED_UNDER_GRAVITY = 2.0f * _scale * MAX_WALKING_SPEED; - if (pushingUp || glm::length2(velocity) > MAX_SPEED_UNDER_GRAVITY * MAX_SPEED_UNDER_GRAVITY) { - // we're pushing up or moving quickly, so disable gravity - setLocalGravity(glm::vec3(0.0f)); - hasFloor = false; - } else { - if (heightAboveFloor > maxFloorDistance) { - // disable local gravity when floor is too far away - setLocalGravity(glm::vec3(0.0f)); - hasFloor = false; - } else { - // enable gravity - setLocalGravity(-_worldUpDirection); - } - } - } - - bool zeroDownwardVelocity = false; - bool gravityEnabled = (glm::length2(_gravity) > EPSILON); - if (gravityEnabled) { - const float SLOP = 0.002f; - if (heightAboveFloor < SLOP) { - if (heightAboveFloor < 0.0) { - // Gravity is in effect so we assume that the avatar is colliding against the world and we need - // to lift avatar out of floor, but we don't want to do it too fast (keep it smooth). - float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime); - - // We don't use applyPositionDelta() for this lift distance because we don't want the avatar - // to come flying out of the floor. Instead we update position directly, and set a boolean - // that will remind us later to zero any downward component of the velocity. - _position += distanceToLift * _worldUpDirection; - } - zeroDownwardVelocity = true; - } - if (!zeroDownwardVelocity) { - velocity += (deltaTime * GRAVITY_EARTH) * _gravity; - } - } - - // rotate velocity into camera frame - glm::quat rotation = getHead()->getCameraOrientation(); - glm::vec3 localVelocity = glm::inverse(rotation) * velocity; - - // apply motors in camera frame - glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor); - newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); - - // rotate back into world-frame - velocity = rotation * newLocalVelocity; - - // apply thrust - velocity += _thrust * deltaTime; - _thrust = glm::vec3(0.0f); - - // remove downward velocity so we don't push into floor - if (zeroDownwardVelocity) { - float verticalSpeed = glm::dot(velocity, _worldUpDirection); - if (verticalSpeed < 0.0f || !pushingUp) { - velocity -= verticalSpeed * _worldUpDirection; - } - } - - // cap avatar speed - float speed = glm::length(velocity); - if (speed > MAX_AVATAR_SPEED) { - velocity *= MAX_AVATAR_SPEED / speed; - speed = MAX_AVATAR_SPEED; - } - - // update position - if (speed > MIN_AVATAR_SPEED) { - applyPositionDelta(deltaTime * velocity); - } - - // update _moving flag based on speed - const float MOVING_SPEED_THRESHOLD = 0.01f; - _moving = speed > MOVING_SPEED_THRESHOLD; - - measureMotionDerivatives(deltaTime); -} - -void MyAvatar::updatePositionWithPhysics(float deltaTime) { // rotate velocity into camera frame glm::quat rotation = getHead()->getCameraOrientation(); glm::vec3 localVelocity = glm::inverse(rotation) * _velocity; - bool hasFloor = false; - glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor); + bool isOnGround = _characterController.onGround(); + glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isOnGround); newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); - // cap avatar speed - float speed = glm::length(newLocalVelocity); - if (speed > MAX_WALKING_SPEED) { - newLocalVelocity *= MAX_WALKING_SPEED / speed; - } - // rotate back into world-frame _velocity = rotation * newLocalVelocity; _velocity += _thrust * deltaTime; _thrust = glm::vec3(0.0f); + + // cap avatar speed + float speed = glm::length(_velocity); + if (speed > MAX_AVATAR_SPEED) { + _velocity *= MAX_AVATAR_SPEED / speed; + speed = MAX_AVATAR_SPEED; + } + + if (speed > MIN_AVATAR_SPEED && !isPhysicsEnabled()) { + // update position ourselves + applyPositionDelta(deltaTime * _velocity); + measureMotionDerivatives(deltaTime); + } // else physics will move avatar later + + // update _moving flag based on speed + const float MOVING_SPEED_THRESHOLD = 0.01f; + _moving = speed > MOVING_SPEED_THRESHOLD; + } void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a37d1c6a30..320a3179bc 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -234,7 +234,6 @@ private: glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool walkingOnFloor); glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity); void updatePosition(float deltaTime); - void updatePositionWithPhysics(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void maybeUpdateBillboard(); void setGravity(const glm::vec3& gravity); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 34c2f51b03..aa0d109732 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -231,8 +231,8 @@ CharacterController::CharacterController(AvatarData* avatarData) { _gravity = 5.0f; // slower than Earth's _maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s. _jumpSpeed = 5.0f; - _wasOnGround = false; - _wasJumping = false; + _isOnGround = false; + _isJumping = false; _isHovering = true; setMaxSlope(btRadians(45.0f)); _lastStepUp = 0.0f; @@ -450,12 +450,12 @@ void CharacterController::stepForward(btCollisionWorld* collisionWorld, const bt btScalar margin = _convexShape->getMargin(); _convexShape->setMargin(margin + _addedMargin); - const btScalar MIN_STEP_DISTANCE = 0.0001f; + const btScalar MIN_STEP_DISTANCE_SQUARED = 1.0e-6f; btVector3 step = _targetPosition - _currentPosition; btScalar stepLength2 = step.length2(); int maxIter = 10; - while (stepLength2 > MIN_STEP_DISTANCE && maxIter-- > 0) { + while (stepLength2 > MIN_STEP_DISTANCE_SQUARED && maxIter-- > 0) { start.setOrigin(_currentPosition); end.setOrigin(_targetPosition); @@ -516,12 +516,14 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt end.setOrigin(_targetPosition); _ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + _isOnGround = false; if (callback.hasHit()) { _currentPosition += callback.m_closestHitFraction * step; _verticalVelocity = 0.0f; _verticalOffset = 0.0f; - _wasJumping = false; - } else if (!_wasJumping) { + _isJumping = false; + _isOnGround = true; + } else if (!_isJumping) { // sweep again for floor within downStep threshold step = -_stepDownHeight * up; StepDownConvexResultCallback callback2 (_ghostObject, @@ -545,9 +547,10 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt _currentPosition += callback2.m_closestHitFraction * step; _verticalVelocity = 0.0f; _verticalOffset = 0.0f; - _wasJumping = false; + _isJumping = false; + _isOnGround = true; } else { - // nothing to step down on, so remove the stepUp effect + // nothing to step down on _lastStepUp = 0.0f; } } else { @@ -571,8 +574,8 @@ void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, void CharacterController::reset(btCollisionWorld* collisionWorld) { _verticalVelocity = 0.0; _verticalOffset = 0.0; - _wasOnGround = false; - _wasJumping = false; + _isOnGround = false; + _isJumping = false; _walkDirection.setValue(0,0,0); _velocityTimeInterval = 0.0; @@ -620,11 +623,9 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar // Update fall velocity. if (_isHovering) { - _wasOnGround = false; const btScalar HOVER_RELAXATION_TIMESCALE = 1.0f; _verticalVelocity *= (1.0f - dt / HOVER_RELAXATION_TIMESCALE); } else { - _wasOnGround = onGround(); _verticalVelocity -= _gravity * dt; if (_verticalVelocity > _jumpSpeed) { _verticalVelocity = _jumpSpeed; @@ -649,6 +650,7 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar // compute substep and decrement total interval btScalar dtMoving = (dt < _velocityTimeInterval) ? dt : _velocityTimeInterval; _velocityTimeInterval -= dt; + _stepDt += dt; // stepForward substep btVector3 move = _walkDirection * dtMoving; @@ -673,7 +675,7 @@ void CharacterController::setMaxJumpHeight(btScalar maxJumpHeight) { } bool CharacterController::canJump() const { - return onGround(); + return _isOnGround; } void CharacterController::jump() { @@ -698,7 +700,7 @@ btScalar CharacterController::getMaxSlope() const { } bool CharacterController::onGround() const { - return _enabled && _verticalVelocity == 0.0f && _verticalOffset == 0.0f; + return _isOnGround; } void CharacterController::debugDraw(btIDebugDraw* debugDrawer) { @@ -761,6 +763,7 @@ void CharacterController::setEnabled(bool enabled) { // it was previously set by something else (e.g. an UPDATE_SHAPE event). _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; _pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION; + _isOnGround = false; } _enabled = enabled; } @@ -842,9 +845,12 @@ void CharacterController::preSimulation(btScalar timeStep) { _pendingFlags &= ~ PENDING_FLAG_JUMP; if (canJump()) { _verticalVelocity = _jumpSpeed; - _wasJumping = true; + _isJumping = true; } } + // remember last position so we can throttle the total motion from the next step + _lastPosition = position; + _stepDt = 0.0f; } } @@ -852,9 +858,24 @@ void CharacterController::postSimulation() { if (_enabled) { const btTransform& avatarTransform = _ghostObject->getWorldTransform(); glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); - glm::vec3 offset = rotation * _shapeLocalOffset; + glm::vec3 position = bulletToGLM(avatarTransform.getOrigin()); + + // cap the velocity of the step so that the character doesn't POP! so hard on steps + glm::vec3 finalStep = position - _lastPosition; + btVector3 finalVelocity = _walkDirection; + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + finalVelocity += _verticalVelocity * up; + const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec + btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt; + btScalar stepLength = glm::length(finalStep); + if (stepLength > maxStepLength) { + position = _lastPosition + (maxStepLength / stepLength) * finalStep; + // NOTE: we don't need to move ghostObject to throttled position unless + // we want to support do async ray-traces/collision-queries against character + } + _avatarData->setOrientation(rotation); - _avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset); + _avatarData->setPosition(position - rotation * _shapeLocalOffset); } } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index b31c4855ea..ec7d9426f4 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -72,6 +72,7 @@ protected: btVector3 _currentPosition; btQuaternion _currentRotation; btVector3 _targetPosition; + glm::vec3 _lastPosition; btScalar _lastStepUp; ///keep track of the contact manifolds @@ -81,10 +82,11 @@ protected: btVector3 _floorNormal; // points from object to character bool _enabled; - bool _wasOnGround; - bool _wasJumping; + bool _isOnGround; + bool _isJumping; bool _isHovering; btScalar _velocityTimeInterval; + btScalar _stepDt; uint32_t _pendingFlags; glm::vec3 _shapeLocalOffset;