From 1314430f0482545636e15b559c0ab23e534b5600 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 27 Aug 2019 13:51:54 -0700 Subject: [PATCH 1/4] more checks to prevent thrusting through mesh --- libraries/physics/src/CharacterController.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 507a79607d..16b893977a 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -784,19 +784,20 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { // but we want to avoid getting stuck and tunelling through geometry so we perform // further checks and modify/abandon our velocity calculations - if (_isStuck || _stuckTransitionCount == 0) { - // we are either definitely stuck, or definitely not --> nothing to do + const float SAFE_COLLISION_SPEED = glm::abs(STUCK_PENETRATION) * (float)NUM_SUBSTEPS_PER_SECOND; + const float SAFE_COLLISION_SPEED_SQUARED = SAFE_COLLISION_SPEED * SAFE_COLLISION_SPEED; + + const float STRONG_IMPACT_THRESHOLD = -1000.0f; // this tuned manually + const float VERY_STRONG_IMPACT_THRESHOLD = -2000.0f; // this tuned manually + float velocityDotImpulse = velocity.dot(_netCollisionImpulse); + + if ((velocityDotImpulse < VERY_STRONG_IMPACT_THRESHOLD && _stuckTransitionCount == 0) || _isStuck) { + // we are either definitely NOT stuck, or definitely are --> nothing to do here return; } - const float SAFE_COLLISION_SPEED = glm::abs(STUCK_PENETRATION) * (float)NUM_SUBSTEPS_PER_SECOND; - const float SAFE_COLLISION_SPEED_SQUARED = SAFE_COLLISION_SPEED * SAFE_COLLISION_SPEED; - bool fast = velocity.length2() > SAFE_COLLISION_SPEED_SQUARED; - - const float STRONG_IMPACT_IMPULSE_DOT = -1000.0f; // this tuned manually - bool strongImpact = velocity.dot(_netCollisionImpulse) < STRONG_IMPACT_IMPULSE_DOT; - - if (fast && strongImpact) { + if (velocityDotImpulse < VERY_STRONG_IMPACT_THRESHOLD || + (velocity.length2() > SAFE_COLLISION_SPEED_SQUARED && velocityDotImpulse < STRONG_IMPACT_THRESHOLD)) { const float REFLECTION_COEFFICIENT = 1.5f; if (velocity.dot(currentVelocity) > 0.0f) { // our new velocity points in the same direction as our currentVelocity From 6be3750b38715c1d7c12b7eed5391744b9cb5687 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 27 Aug 2019 20:28:56 -0700 Subject: [PATCH 2/4] use collision brake feedback to prevent tunneling --- interface/src/avatar/MyAvatar.cpp | 2 + libraries/physics/src/CharacterController.cpp | 40 ++++++++++++++----- libraries/physics/src/CharacterController.h | 2 + 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 089ff4c2bf..4d1c20010c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3586,6 +3586,8 @@ void MyAvatar::updateActionMotor(float deltaTime) { float speedGrowthTimescale = 2.0f; float speedIncreaseFactor = 1.8f * _walkSpeedScalar; motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor; + // use feedback from CharacterController to prevent tunneling under high motorspeed + motorSpeed *= _characterController.getCollisionBrakeAttenuationFactor(); const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED; if (_isPushing) { diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 16b893977a..5cd5045d19 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -570,6 +570,11 @@ void CharacterController::setPhysicsEngine(const PhysicsEnginePointer& engine) { } } +float CharacterController::getCollisionBrakeAttenuationFactor() const { + // _collisionBrake ranges from 0.0 (no brake) to 1.0 (max brake) + return 1.0f - 0.5f * _collisionBrake; +} + void CharacterController::setCollisionless(bool collisionless) { if (collisionless != _collisionless) { _collisionless = collisionless; @@ -787,18 +792,36 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { const float SAFE_COLLISION_SPEED = glm::abs(STUCK_PENETRATION) * (float)NUM_SUBSTEPS_PER_SECOND; const float SAFE_COLLISION_SPEED_SQUARED = SAFE_COLLISION_SPEED * SAFE_COLLISION_SPEED; - const float STRONG_IMPACT_THRESHOLD = -1000.0f; // this tuned manually - const float VERY_STRONG_IMPACT_THRESHOLD = -2000.0f; // this tuned manually + // NOTE: the thresholds are negative because that indicates the vectors oppose each other + const float STRONG_OPPOSING_IMPACT_THRESHOLD = -1000.0f; + const float VERY_STRONG_OPPOSING_IMPACT_THRESHOLD = -2000.0f; float velocityDotImpulse = velocity.dot(_netCollisionImpulse); - if ((velocityDotImpulse < VERY_STRONG_IMPACT_THRESHOLD && _stuckTransitionCount == 0) || _isStuck) { - // we are either definitely NOT stuck, or definitely are --> nothing to do here + const float COLLISION_BRAKE_DECAY_TIMESCALE = 0.20f; // seconds + const float MIN_COLLISION_BRAKE = 0.05f; + if ((velocityDotImpulse > VERY_STRONG_OPPOSING_IMPACT_THRESHOLD && _stuckTransitionCount == 0) || _isStuck) { + // we are either definitely NOT stuck (in which case nothing to do) + // or definitely are (in which case we'll be temporarily disabling collisions with the offending object + // and we don't mind tunnelling as an escape route out of stuck) + if (_collisionBrake > MIN_COLLISION_BRAKE) { + _collisionBrake *= (1.0f - dt / COLLISION_BRAKE_DECAY_TIMESCALE); + if (_collisionBrake < MIN_COLLISION_BRAKE) { + _collisionBrake = 0.0f; + } + } return; } - if (velocityDotImpulse < VERY_STRONG_IMPACT_THRESHOLD || - (velocity.length2() > SAFE_COLLISION_SPEED_SQUARED && velocityDotImpulse < STRONG_IMPACT_THRESHOLD)) { - const float REFLECTION_COEFFICIENT = 1.5f; + if (velocityDotImpulse < VERY_STRONG_OPPOSING_IMPACT_THRESHOLD || + (velocityDotImpulse < STRONG_OPPOSING_IMPACT_THRESHOLD && velocity.length2() > SAFE_COLLISION_SPEED_SQUARED)) { + if (_collisionBrake < 1.0f) { + _collisionBrake += (1.0f - _collisionBrake) * (dt / COLLISION_BRAKE_DECAY_TIMESCALE); + const float MAX_COLLISION_BRAKE = 1.0f - MIN_COLLISION_BRAKE; + if (_collisionBrake > MAX_COLLISION_BRAKE) { + _collisionBrake = 1.0f; + } + } + const float REFLECTION_COEFFICIENT = 1.0f; if (velocity.dot(currentVelocity) > 0.0f) { // our new velocity points in the same direction as our currentVelocity // but strongImpact means new velocity points against netImpulse @@ -810,9 +833,6 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { // can't trust physical simulation --> reflect velocity against netImpulse btVector3 impulseDirection = _netCollisionImpulse.normalized(); velocity -= (REFLECTION_COEFFICIENT * velocity.dot(impulseDirection)) * impulseDirection; - // also attenuate the velocity to help slow down the character before its penetration gets worse - const float ATTENUATION_COEFFICIENT = 0.8f; - velocity *= ATTENUATION_COEFFICIENT; } } else { // currentVelocity points against new velocity, which means it is probably better but... diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 472dcdd003..e7ad3ddfa8 100755 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -128,6 +128,7 @@ public: void setPhysicsEngine(const PhysicsEnginePointer& engine); bool isEnabledAndReady() const { return (bool)_physicsEngine; } bool isStuck() const { return _isStuck; } + float getCollisionBrakeAttenuationFactor() const; void setCollisionless(bool collisionless); @@ -221,6 +222,7 @@ protected: bool _isPushingUp; bool _isStuck { false }; bool _isSeated { false }; + float _collisionBrake { 0.0f }; PhysicsEnginePointer _physicsEngine { nullptr }; btRigidBody* _rigidBody { nullptr }; From 1beafbdd351061b69da5f95cb43721e20ca18af2 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 28 Aug 2019 09:48:15 -0700 Subject: [PATCH 3/4] comment cleanup and final tuning --- libraries/physics/src/CharacterController.cpp | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 5cd5045d19..ba3a3de069 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -572,6 +572,7 @@ void CharacterController::setPhysicsEngine(const PhysicsEnginePointer& engine) { float CharacterController::getCollisionBrakeAttenuationFactor() const { // _collisionBrake ranges from 0.0 (no brake) to 1.0 (max brake) + // which we use to compute a corresponding attenutation factor from 1.0 to 0.5 return 1.0f - 0.5f * _collisionBrake; } @@ -792,19 +793,21 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { const float SAFE_COLLISION_SPEED = glm::abs(STUCK_PENETRATION) * (float)NUM_SUBSTEPS_PER_SECOND; const float SAFE_COLLISION_SPEED_SQUARED = SAFE_COLLISION_SPEED * SAFE_COLLISION_SPEED; - // NOTE: the thresholds are negative because that indicates the vectors oppose each other + // NOTE: the thresholds are negative which indicates the vectors oppose each other + // and which means comparison operators against them may look wrong at first glance. + // The magnitudes of the thresholds have been tuned manually. const float STRONG_OPPOSING_IMPACT_THRESHOLD = -1000.0f; const float VERY_STRONG_OPPOSING_IMPACT_THRESHOLD = -2000.0f; float velocityDotImpulse = velocity.dot(_netCollisionImpulse); - const float COLLISION_BRAKE_DECAY_TIMESCALE = 0.20f; // seconds + const float COLLISION_BRAKE_TIMESCALE = 0.20f; // must be > PHYSICS_ENGINE_FIXED_SUBSTEP for stability const float MIN_COLLISION_BRAKE = 0.05f; if ((velocityDotImpulse > VERY_STRONG_OPPOSING_IMPACT_THRESHOLD && _stuckTransitionCount == 0) || _isStuck) { // we are either definitely NOT stuck (in which case nothing to do) // or definitely are (in which case we'll be temporarily disabling collisions with the offending object // and we don't mind tunnelling as an escape route out of stuck) if (_collisionBrake > MIN_COLLISION_BRAKE) { - _collisionBrake *= (1.0f - dt / COLLISION_BRAKE_DECAY_TIMESCALE); + _collisionBrake *= (1.0f - dt / COLLISION_BRAKE_TIMESCALE); if (_collisionBrake < MIN_COLLISION_BRAKE) { _collisionBrake = 0.0f; } @@ -815,30 +818,35 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { if (velocityDotImpulse < VERY_STRONG_OPPOSING_IMPACT_THRESHOLD || (velocityDotImpulse < STRONG_OPPOSING_IMPACT_THRESHOLD && velocity.length2() > SAFE_COLLISION_SPEED_SQUARED)) { if (_collisionBrake < 1.0f) { - _collisionBrake += (1.0f - _collisionBrake) * (dt / COLLISION_BRAKE_DECAY_TIMESCALE); + _collisionBrake += (1.0f - _collisionBrake) * (dt / COLLISION_BRAKE_TIMESCALE); const float MAX_COLLISION_BRAKE = 1.0f - MIN_COLLISION_BRAKE; if (_collisionBrake > MAX_COLLISION_BRAKE) { _collisionBrake = 1.0f; } } - const float REFLECTION_COEFFICIENT = 1.0f; + + // NOTE about REFLECTION_COEFFICIENT: a value of 2.0 provides full reflection + // (zero attenuation) whereas a value of 1.0 zeros it (full attenuation). + const float REFLECTION_COEFFICIENT = 1.1f; + if (velocity.dot(currentVelocity) > 0.0f) { // our new velocity points in the same direction as our currentVelocity - // but strongImpact means new velocity points against netImpulse + // but negative "impact" means new velocity points against netCollisionImpulse if (currentVelocity.dot(_netCollisionImpulse) > 0.0f) { - // currentVelocity points positively with netImpulse - // so we will assume collisions will save us and use it for our new velocity + // currentVelocity points positively with netCollisionImpulse --> trust physics to save us velocity = currentVelocity; } else { - // can't trust physical simulation --> reflect velocity against netImpulse + // can't trust physics --> use new velocity but reflect it btVector3 impulseDirection = _netCollisionImpulse.normalized(); velocity -= (REFLECTION_COEFFICIENT * velocity.dot(impulseDirection)) * impulseDirection; } } else { // currentVelocity points against new velocity, which means it is probably better but... - // this doesn't mean it points in a good direction yet, so we must check + // when the physical simulation starts to fail (e.g. in deep penetration in mesh geometry) + // the currentVelocity can point in unhelpful directions, so we check it and reflect any component + // opposing netCollisionImpulse in hopes netCollisionImpulse points toward good exit if (currentVelocity.dot(_netCollisionImpulse) < 0.0f) { - // currentVelocity points against netImpulse, so we reflect it + // currentVelocity points against netCollisionImpulse --> reflect btVector3 impulseDirection = _netCollisionImpulse.normalized(); currentVelocity -= (REFLECTION_COEFFICIENT * currentVelocity.dot(impulseDirection)) * impulseDirection; } From b7ae55ca3efcb8c29e3caef59947b2fdd662c4ce Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 28 Aug 2019 11:33:51 -0700 Subject: [PATCH 4/4] smaller CCD proxy for MyAvatar --- libraries/physics/src/CharacterController.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index ba3a3de069..a796ea28a0 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -189,7 +189,7 @@ void CharacterController::addToWorld() { _rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); // enable CCD - _rigidBody->setCcdSweptSphereRadius(2.0f * (_radius + _halfHeight)); + _rigidBody->setCcdSweptSphereRadius(_halfHeight); _rigidBody->setCcdMotionThreshold(_radius); btCollisionShape* shape = _rigidBody->getCollisionShape(); @@ -556,7 +556,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const if (_rigidBody) { // update CCD with new _radius - _rigidBody->setCcdSweptSphereRadius(2.0f * (_radius + _halfHeight)); + _rigidBody->setCcdSweptSphereRadius(_halfHeight); _rigidBody->setCcdMotionThreshold(_radius); } }