From 4fb6d5023b17bbef2121b81df3be5dbe2a1d867b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 28 Sep 2016 16:56:51 -0700 Subject: [PATCH] prevent avatars from walking up walls --- libraries/physics/src/CharacterController.cpp | 66 ++++++++++++++----- .../physics/src/CharacterGhostObject.cpp | 22 +++++-- libraries/physics/src/CharacterGhostObject.h | 5 +- 3 files changed, 72 insertions(+), 21 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 5058a1aa9c..0858c8a856 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -128,6 +128,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _ghost.setMaxStepHeight(0.75f * (_radius + _halfHeight)); // HACK _ghost.setMinWallAngle(PI / 4.0f); // HACK _ghost.setUpDirection(_currentUp); + _ghost.setMotorOnly(!_moveKinematically); _ghost.setWorldTransform(_rigidBody->getWorldTransform()); } if (_dynamicsWorld) { @@ -143,7 +144,24 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { } bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld, btScalar dt) { + if (_moveKinematically) { + // kinematic motion will move() the _ghost later + return _ghost.hasSupport(); + } _stepHeight = _minStepHeight; // clears last step obstacle + + btScalar targetSpeed = _targetVelocity.length(); + if (targetSpeed > FLT_EPSILON) { + // move the _ghost forward to test for step + btTransform transform = _rigidBody->getWorldTransform(); + transform.setOrigin(transform.getOrigin()); + _ghost.setWorldTransform(transform); + _ghost.setMotorVelocity(_targetVelocity); + float overshoot = _radius; + _ghost.setHovering(_state == State::Hover); + _ghost.move(dt, overshoot, _gravity); + } + btDispatcher* dispatcher = collisionWorld->getDispatcher(); int numManifolds = dispatcher->getNumManifolds(); bool hasFloor = false; @@ -165,17 +183,26 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld, btSc btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp); - if (hitHeight < _radius && normal.dot(_currentUp) > COS_PI_OVER_THREE) { + if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > COS_PI_OVER_THREE) { hasFloor = true; + if (!_ghost.isSteppingUp()) { + // early exit since all we need to know is that we're on a floor + break; + } } - // remember highest step obstacle - if (hitHeight > _maxStepHeight) { - // this manifold is invalidated by point that is too high - stepContactIndex = -1; - break; - } else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) { - highestStep = hitHeight; - stepContactIndex = j; + // analysis of the step info using manifold data is unreliable, so we only proceed + // when the _ghost has detected a steppable obstacle + if (_ghost.isSteppingUp()) { + // remember highest step obstacle + if (hitHeight > _maxStepHeight) { + // this manifold is invalidated by point that is too high + stepContactIndex = -1; + break; + } else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) { + highestStep = hitHeight; + stepContactIndex = j; + hasFloor = true; + } } } if (stepContactIndex > -1 && highestStep > _stepHeight) { @@ -187,6 +214,10 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld, btSc _stepPoint = rotation * pointOnCharacter; // rotate into world-frame _stepNormal = normal; } + if (hasFloor && !_ghost.isSteppingUp()) { + // early exit since all we need to know is that we're on a floor + break; + } } } return hasFloor; @@ -532,9 +563,13 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel } else { // compute local UP btVector3 up = _currentUp.rotate(axis, -angle); - - // add sky hook when encountering a step obstacle btVector3 motorVelocity = motor.velocity; + + // save these non-adjusted components for later + btVector3 vTargetVelocity = motorVelocity.dot(up) * up; + btVector3 hTargetVelocity = motorVelocity - vTargetVelocity; + + // adjust motorVelocity uphill when encountering a step obstacle btScalar vTimescale = motor.vTimescale; if (_stepHeight > _minStepHeight) { // there is a step @@ -557,8 +592,8 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel // split velocity into horizontal and vertical components btVector3 vVelocity = velocity.dot(up) * up; btVector3 hVelocity = velocity - vVelocity; - btVector3 vTargetVelocity = motorVelocity.dot(up) * up; - btVector3 hTargetVelocity = motorVelocity - vTargetVelocity; + btVector3 vMotorVelocity = motorVelocity.dot(up) * up; + btVector3 hMotorVelocity = motorVelocity - vMotorVelocity; // modify each component separately btScalar maxTau = 0.0f; @@ -568,7 +603,7 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel tau = 1.0f; } maxTau = tau; - hVelocity += (hTargetVelocity - hVelocity) * tau; + hVelocity += (hMotorVelocity - hVelocity) * tau; } if (vTimescale < MAX_CHARACTER_MOTOR_TIMESCALE) { btScalar tau = dt / vTimescale; @@ -578,7 +613,7 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel if (tau > maxTau) { maxTau = tau; } - vVelocity += (vTargetVelocity - vVelocity) * tau; + vVelocity += (vMotorVelocity - vVelocity) * tau; } // add components back together and rotate into world-frame @@ -819,5 +854,6 @@ void CharacterController::setMoveKinematically(bool kinematic) { if (kinematic != _moveKinematically) { _moveKinematically = kinematic; _pendingFlags |= PENDING_FLAG_UPDATE_SHAPE; + _ghost.setMotorOnly(!_moveKinematically); } } diff --git a/libraries/physics/src/CharacterGhostObject.cpp b/libraries/physics/src/CharacterGhostObject.cpp index f39b9c3995..52689a2328 100755 --- a/libraries/physics/src/CharacterGhostObject.cpp +++ b/libraries/physics/src/CharacterGhostObject.cpp @@ -54,6 +54,13 @@ void CharacterGhostObject::setUpDirection(const btVector3& up) { } } +void CharacterGhostObject::setMotorVelocity(const btVector3& velocity) { + _motorVelocity = velocity; + if (_motorOnly) { + _linearVelocity = _motorVelocity; + } +} + // override of btCollisionObject::setCollisionShape() void CharacterGhostObject::setCharacterCapsule(btCapsuleShape* capsule) { assert(capsule); @@ -74,6 +81,7 @@ void CharacterGhostObject::setCollisionWorld(btCollisionWorld* world) { } void CharacterGhostObject::move(btScalar dt, btScalar overshoot, btScalar gravity) { + bool oldOnFloor = _onFloor; _onFloor = false; _steppingUp = false; assert(_world && _inWorld); @@ -172,6 +180,7 @@ void CharacterGhostObject::move(btScalar dt, btScalar overshoot, btScalar gravit } nextTransform.setOrigin(startPosition + forwardTranslation); setWorldTransform(nextTransform); + _onFloor = _onFloor || oldOnFloor; return; } // if we get here then we hit something that might be steppable @@ -213,6 +222,7 @@ void CharacterGhostObject::move(btScalar dt, btScalar overshoot, btScalar gravit // either there is no future landing spot, or there is but we can't stand on it // in any case: we go forward as much as possible nextTransform.setOrigin(startPosition + forwardSweepHitFraction * (stepDistance / longSweepDistance) * forwardSweep); + _onFloor = _onFloor || oldOnFloor; updateTraction(nextTransform.getOrigin()); } setWorldTransform(nextTransform); @@ -352,10 +362,12 @@ void CharacterGhostObject::refreshOverlappingPairCache() { } void CharacterGhostObject::updateVelocity(btScalar dt, btScalar gravity) { - if (_hovering) { - _linearVelocity *= 0.999f; // HACK damping - } else { - _linearVelocity += (dt * gravity) * _upDirection; + if (!_motorOnly) { + if (_hovering) { + _linearVelocity *= 0.999f; // HACK damping + } else { + _linearVelocity += (dt * gravity) * _upDirection; + } } } @@ -377,7 +389,7 @@ void CharacterGhostObject::updateHoverState(const btVector3& position) { void CharacterGhostObject::updateTraction(const btVector3& position) { updateHoverState(position); - if (_hovering) { + if (_hovering || _motorOnly) { _linearVelocity = _motorVelocity; } else if (_onFloor) { // compute a velocity that swings the capsule around the _floorContact diff --git a/libraries/physics/src/CharacterGhostObject.h b/libraries/physics/src/CharacterGhostObject.h index 4b943833b3..6d6de640d5 100755 --- a/libraries/physics/src/CharacterGhostObject.h +++ b/libraries/physics/src/CharacterGhostObject.h @@ -33,7 +33,7 @@ public: void setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight); void setUpDirection(const btVector3& up); - void setMotorVelocity(const btVector3& velocity) { _motorVelocity = velocity; } + void setMotorVelocity(const btVector3& velocity); void setMinWallAngle(btScalar angle) { _maxWallNormalUpComponent = cosf(angle); } void setMaxStepHeight(btScalar height) { _maxStepHeight = height; } @@ -53,9 +53,11 @@ public: bool isHovering() const { return _hovering; } void setHovering(bool hovering) { _hovering = hovering; } + void setMotorOnly(bool motorOnly) { _motorOnly = motorOnly; } bool hasSupport() const { return _onFloor; } bool isSteppingUp() const { return _steppingUp; } + const btVector3& getFloorNormal() const { return _floorNormal; } protected: void removeFromWorld(); @@ -93,6 +95,7 @@ protected: bool _onFloor { false }; // output, is actually standing on floor bool _steppingUp { false }; // output, future sweep hit a steppable ledge bool _hasFloor { false }; // output, has floor underneath to fall on + bool _motorOnly { false }; // input, _linearVelocity slaves to _motorVelocity }; #endif // hifi_CharacterGhostObject_h