//// CharacterControllerInterface.cpp// libraries/physics/src//// Created by Andrew Meadows 2015.10.21// Copyright 2015 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "CharacterController.h" #include <NumericalConstants.h> #include "ObjectMotionState.h"#include "PhysicsHelpers.h"#include "PhysicsLogging.h" const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);const float JUMP_SPEED = 3.5f;const float MAX_FALL_HEIGHT = 20.0f; #ifdef DEBUG_STATE_CHANGE#define SET_STATE(desiredState, reason) setState(desiredState, reason)#else#define SET_STATE(desiredState, reason) setState(desiredState)#endif // helper class for simple ray-traces from characterclass ClosestNotMe : public btCollisionWorld::ClosestRayResultCallback {public: ClosestNotMe(btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) { _me = me; // the RayResultCallback's group and mask must match MY_AVATAR m_collisionFilterGroup = BULLET_COLLISION_GROUP_MY_AVATAR; m_collisionFilterMask = BULLET_COLLISION_MASK_MY_AVATAR; } virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) override { if (rayResult.m_collisionObject == _me) { return 1.0f; } return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); }protected: btRigidBody* _me;}; CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const glm::quat& rot, float horizTimescale, float vertTimescale) { velocity = glmToBullet(vel); rotation = glmToBullet(rot); hTimescale = horizTimescale; if (hTimescale < MIN_CHARACTER_MOTOR_TIMESCALE) { hTimescale = MIN_CHARACTER_MOTOR_TIMESCALE; } vTimescale = vertTimescale; if (vTimescale < 0.0f) { vTimescale = hTimescale; } else if (vTimescale < MIN_CHARACTER_MOTOR_TIMESCALE) { vTimescale = MIN_CHARACTER_MOTOR_TIMESCALE; }} CharacterController::CharacterController() { _floorDistance = MAX_FALL_HEIGHT; _targetVelocity.setValue(0.0f, 0.0f, 0.0f); _followDesiredBodyTransform.setIdentity(); _followTimeRemaining = 0.0f; _jumpSpeed = JUMP_SPEED; _state = State::Hover; _isPushingUp = false; _rayHitStartTime = 0; _takeoffToInAirStartTime = 0; _jumpButtonDownStartTime = 0; _jumpButtonDownCount = 0; _followTime = 0.0f; _followLinearDisplacement = btVector3(0, 0, 0); _followAngularDisplacement = btQuaternion::getIdentity(); _hasSupport = false; _pendingFlags = PENDING_FLAG_UPDATE_SHAPE;} CharacterController::~CharacterController() { if (_rigidBody) { btCollisionShape* shape = _rigidBody->getCollisionShape(); if (shape) { delete shape; } delete _rigidBody; _rigidBody = nullptr; }} bool CharacterController::needsRemoval() const { return ((_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION);} bool CharacterController::needsAddition() const { return ((_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION) == PENDING_FLAG_ADD_TO_SIMULATION);} void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { if (_dynamicsWorld != world) { // remove from old world if (_dynamicsWorld) { if (_rigidBody) { _dynamicsWorld->removeRigidBody(_rigidBody); _dynamicsWorld->removeAction(this); } _dynamicsWorld = nullptr; } int16_t collisionGroup = computeCollisionGroup(); if (_rigidBody) { updateMassProperties(); } if (world && _rigidBody) { // add to new world _dynamicsWorld = world; _pendingFlags &= ~PENDING_FLAG_JUMP; _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); _dynamicsWorld->addAction(this); // restore gravity settings because adding an object to the world overwrites its gravity setting _rigidBody->setGravity(_gravity * _currentUp); btCollisionShape* shape = _rigidBody->getCollisionShape(); assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape)); } _ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup)); _ghost.setCollisionWorld(_dynamicsWorld); _ghost.setRadiusAndHalfHeight(_radius, _halfHeight); if (_rigidBody) { _ghost.setWorldTransform(_rigidBody->getWorldTransform()); } } if (_dynamicsWorld) { if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { // shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION; } else { _pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION; } } else { _pendingFlags &= ~PENDING_FLAG_REMOVE_FROM_SIMULATION; }} bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { bool pushing = _targetVelocity.length2() > FLT_EPSILON; btDispatcher* dispatcher = collisionWorld->getDispatcher(); int numManifolds = dispatcher->getNumManifolds(); bool hasFloor = false; btTransform rotation = _rigidBody->getWorldTransform(); rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part for (int i = 0; i < numManifolds; i++) { btPersistentManifold* contactManifold = dispatcher->getManifoldByIndexInternal(i); if (_rigidBody == contactManifold->getBody1() || _rigidBody == contactManifold->getBody0()) { bool characterIsFirst = _rigidBody == contactManifold->getBody0(); int numContacts = contactManifold->getNumContacts(); int stepContactIndex = -1; float highestStep = _minStepHeight; for (int j = 0; j < numContacts; j++) { // check for "floor" btManifoldPoint& contact = contactManifold->getContactPoint(j); 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 < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) { hasFloor = true; if (!pushing) { // we're not pushing against anything so we can early exit // (all we need to know is that there is a floor) break; } } if (pushing && _targetVelocity.dot(normal) < 0.0f) { // remember highest step obstacle if (!_stepUpEnabled || 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) { // remember step info for later btManifoldPoint& contact = contactManifold->getContactPoint(stepContactIndex); btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame _stepNormal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character _stepHeight = highestStep; _stepPoint = rotation * pointOnCharacter; // rotate into world-frame } if (hasFloor && !(pushing && _stepUpEnabled)) { // early exit since all we need to know is that we're on a floor break; } } } return hasFloor;} void CharacterController::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) { preStep(collisionWorld); playerStep(collisionWorld, deltaTime);} void CharacterController::preStep(btCollisionWorld* collisionWorld) { // trace a ray straight down to see if we're standing on the ground const btTransform& transform = _rigidBody->getWorldTransform(); // rayStart is at center of bottom sphere btVector3 rayStart = transform.getOrigin() - _halfHeight * _currentUp; // rayEnd is some short distance outside bottom sphere const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius; btScalar rayLength = _radius + FLOOR_PROXIMITY_THRESHOLD; btVector3 rayEnd = rayStart - rayLength * _currentUp; // scan down for nearby floor ClosestNotMe rayCallback(_rigidBody); rayCallback.m_closestHitFraction = 1.0f; collisionWorld->rayTest(rayStart, rayEnd, rayCallback); if (rayCallback.hasHit()) { _floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius; }} const btScalar MIN_TARGET_SPEED = 0.001f;const btScalar MIN_TARGET_SPEED_SQUARED = MIN_TARGET_SPEED * MIN_TARGET_SPEED; void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) { _stepHeight = _minStepHeight; // clears memory of last step obstacle _hasSupport = checkForSupport(collisionWorld); btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity; computeNewVelocity(dt, velocity); const float MINIMUM_TIME_REMAINING = 0.005f; const float MAX_DISPLACEMENT = 0.5f * _radius; _followTimeRemaining -= dt; if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) { btTransform bodyTransform = _rigidBody->getWorldTransform(); btVector3 startPos = bodyTransform.getOrigin(); btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos; btVector3 vel = deltaPos / _followTimeRemaining; btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling. btVector3 endPos = startPos + linearDisplacement; btQuaternion startRot = bodyTransform.getRotation(); glm::vec2 currentFacing = getFacingDir2D(bulletToGLM(startRot)); glm::vec2 currentRight(currentFacing.y, -currentFacing.x); glm::vec2 desiredFacing = getFacingDir2D(bulletToGLM(_followDesiredBodyTransform.getRotation())); float deltaAngle = acosf(glm::clamp(glm::dot(currentFacing, desiredFacing), -1.0f, 1.0f)); float angularSpeed = deltaAngle / _followTimeRemaining; float sign = copysignf(1.0f, glm::dot(desiredFacing, currentRight)); btQuaternion angularDisplacement = btQuaternion(btVector3(0.0f, 1.0f, 0.0f), sign * angularSpeed * dt); btQuaternion endRot = angularDisplacement * startRot; // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); _followLinearDisplacement = linearDisplacement + swingDisplacement + _followLinearDisplacement; _followAngularDisplacement = angularDisplacement * _followAngularDisplacement; _rigidBody->setWorldTransform(btTransform(endRot, endPos)); } _followTime += dt; if (_steppingUp) { float horizontalTargetSpeed = (_targetVelocity - _targetVelocity.dot(_currentUp) * _currentUp).length(); if (horizontalTargetSpeed > FLT_EPSILON) { // compute a stepUpSpeed that will reach the top of the step in the time it would take // to move over the _stepPoint at target speed float horizontalDistance = (_stepPoint - _stepPoint.dot(_currentUp) * _currentUp).length(); float timeToStep = horizontalDistance / horizontalTargetSpeed; float stepUpSpeed = _stepHeight / timeToStep; // magically clamp stepUpSpeed to a fraction of horizontalTargetSpeed // to prevent the avatar from moving unreasonably fast according to human eye const float MAX_STEP_UP_SPEED = 0.65f * horizontalTargetSpeed; if (stepUpSpeed > MAX_STEP_UP_SPEED) { stepUpSpeed = MAX_STEP_UP_SPEED; } // add minimum velocity to counteract gravity's displacement during one step // Note: the 0.5 factor comes from the fact that we really want the // average velocity contribution from gravity during the step stepUpSpeed -= 0.5f * _gravity * timeToStep; // remember: _gravity is negative scalar btScalar vDotUp = velocity.dot(_currentUp); if (vDotUp < stepUpSpeed) { // character doesn't have enough upward velocity to cover the step so we help using a "sky hook" // which uses micro-teleports rather than applying real velocity // to prevent the avatar from popping up after the step is done btTransform transform = _rigidBody->getWorldTransform(); transform.setOrigin(transform.getOrigin() + (dt * stepUpSpeed) * _currentUp); _rigidBody->setWorldTransform(transform); } // don't allow the avatar to fall downward when stepping up // since otherwise this would tend to defeat the step-up behavior if (vDotUp < 0.0f) { velocity -= vDotUp * _currentUp; } } } _rigidBody->setLinearVelocity(velocity + _parentVelocity); _ghost.setWorldTransform(_rigidBody->getWorldTransform());} void CharacterController::jump() { _pendingFlags |= PENDING_FLAG_JUMP;} bool CharacterController::onGround() const { const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius; return _floorDistance < FLOOR_PROXIMITY_THRESHOLD || _hasSupport;} #ifdef DEBUG_STATE_CHANGEstatic const char* stateToStr(CharacterController::State state) { switch (state) { case CharacterController::State::Ground: return "Ground"; case CharacterController::State::Takeoff: return "Takeoff"; case CharacterController::State::InAir: return "InAir"; case CharacterController::State::Hover: return "Hover"; default: return "Unknown"; }}#endif // #ifdef DEBUG_STATE_CHANGE #ifdef DEBUG_STATE_CHANGEvoid CharacterController::setState(State desiredState, const char* reason) {#elsevoid CharacterController::setState(State desiredState) {#endif if (!_flyingAllowed && desiredState == State::Hover) { desiredState = State::InAir; } if (desiredState != _state) {#ifdef DEBUG_STATE_CHANGE qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason;#endif _state = desiredState; updateGravity(); }} void CharacterController::updateGravity() { int16_t collisionGroup = computeCollisionGroup(); if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { _gravity = 0.0f; } else { const float DEFAULT_CHARACTER_GRAVITY = -5.0f; _gravity = DEFAULT_CHARACTER_GRAVITY; } if (_rigidBody) { _rigidBody->setGravity(_gravity * _currentUp); }} void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale) { float x = scale.x; float z = scale.z; float radius = 0.5f * sqrtf(0.5f * (x * x + z * z)); float halfHeight = 0.5f * scale.y - radius; float MIN_HALF_HEIGHT = 0.1f; if (halfHeight < MIN_HALF_HEIGHT) { halfHeight = MIN_HALF_HEIGHT; } // compare dimensions if (glm::abs(radius - _radius) > FLT_EPSILON || glm::abs(halfHeight - _halfHeight) > FLT_EPSILON) { _radius = radius; _halfHeight = halfHeight; const btScalar DEFAULT_MIN_STEP_HEIGHT_FACTOR = 0.005f; const btScalar DEFAULT_MAX_STEP_HEIGHT_FACTOR = 0.65f; _minStepHeight = DEFAULT_MIN_STEP_HEIGHT_FACTOR * (_halfHeight + _radius); _maxStepHeight = DEFAULT_MAX_STEP_HEIGHT_FACTOR * (_halfHeight + _radius); if (_dynamicsWorld) { // must REMOVE from world prior to shape update _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; } _pendingFlags |= PENDING_FLAG_UPDATE_SHAPE; _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; } // it's ok to change offset immediately -- there are no thread safety issues here _shapeLocalOffset = minCorner + 0.5f * scale;} void CharacterController::setCollisionless(bool collisionless) { if (collisionless != _collisionless) { _collisionless = collisionless; _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; }} int16_t CharacterController::computeCollisionGroup() const { if (_collisionless) { return _collisionlessAllowed ? BULLET_COLLISION_GROUP_COLLISIONLESS : BULLET_COLLISION_GROUP_MY_AVATAR; } else { return BULLET_COLLISION_GROUP_MY_AVATAR; }} void CharacterController::handleChangedCollisionGroup() { if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_GROUP) { // ATM the easiest way to update collision groups is to remove/re-add the RigidBody if (_dynamicsWorld) { _dynamicsWorld->removeRigidBody(_rigidBody); int16_t collisionGroup = computeCollisionGroup(); _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); } _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP; updateGravity(); }} void CharacterController::updateUpAxis(const glm::quat& rotation) { _currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS); if (_state != State::Hover && _rigidBody) { _rigidBody->setGravity(_gravity * _currentUp); }} void CharacterController::setPositionAndOrientation( const glm::vec3& position, const glm::quat& orientation) { updateUpAxis(orientation); _rotation = glmToBullet(orientation); _position = glmToBullet(position + orientation * _shapeLocalOffset);} void CharacterController::getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const { if (_rigidBody) { const btTransform& avatarTransform = _rigidBody->getWorldTransform(); rotation = bulletToGLM(avatarTransform.getRotation()); position = bulletToGLM(avatarTransform.getOrigin()) - rotation * _shapeLocalOffset; }} void CharacterController::setParentVelocity(const glm::vec3& velocity) { _parentVelocity = glmToBullet(velocity);} void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) { _followTimeRemaining = timeRemaining; _followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset));} glm::vec3 CharacterController::getFollowLinearDisplacement() const { return bulletToGLM(_followLinearDisplacement);} glm::quat CharacterController::getFollowAngularDisplacement() const { return bulletToGLM(_followAngularDisplacement);} glm::vec3 CharacterController::getFollowVelocity() const { if (_followTime > 0.0f) { return bulletToGLM(_followLinearDisplacement) / _followTime; } else { return glm::vec3(); }} glm::vec3 CharacterController::getLinearVelocity() const { glm::vec3 velocity(0.0f); if (_rigidBody) { velocity = bulletToGLM(_rigidBody->getLinearVelocity()); } return velocity;} glm::vec3 CharacterController::getVelocityChange() const { if (_rigidBody) { return bulletToGLM(_velocityChange); } return glm::vec3(0.0f);} void CharacterController::clearMotors() { _motors.clear();Lines 1–500 of 805. Load more
// // CharacterControllerInterface.cpp // libraries/physics/src // // Created by Andrew Meadows 2015.10.21 // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "CharacterController.h" #include <NumericalConstants.h> #include "ObjectMotionState.h" #include "PhysicsHelpers.h" #include "PhysicsLogging.h" const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f); const float JUMP_SPEED = 3.5f; const float MAX_FALL_HEIGHT = 20.0f; #ifdef DEBUG_STATE_CHANGE #define SET_STATE(desiredState, reason) setState(desiredState, reason) #else #define SET_STATE(desiredState, reason) setState(desiredState) #endif // helper class for simple ray-traces from character class ClosestNotMe : public btCollisionWorld::ClosestRayResultCallback { public: ClosestNotMe(btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) { _me = me; // the RayResultCallback's group and mask must match MY_AVATAR m_collisionFilterGroup = BULLET_COLLISION_GROUP_MY_AVATAR; m_collisionFilterMask = BULLET_COLLISION_MASK_MY_AVATAR; } virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) override { if (rayResult.m_collisionObject == _me) { return 1.0f; } return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } protected: btRigidBody* _me; }; CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const glm::quat& rot, float horizTimescale, float vertTimescale) { velocity = glmToBullet(vel); rotation = glmToBullet(rot); hTimescale = horizTimescale; if (hTimescale < MIN_CHARACTER_MOTOR_TIMESCALE) { hTimescale = MIN_CHARACTER_MOTOR_TIMESCALE; } vTimescale = vertTimescale; if (vTimescale < 0.0f) { vTimescale = hTimescale; } else if (vTimescale < MIN_CHARACTER_MOTOR_TIMESCALE) { vTimescale = MIN_CHARACTER_MOTOR_TIMESCALE; } } CharacterController::CharacterController() { _floorDistance = MAX_FALL_HEIGHT; _targetVelocity.setValue(0.0f, 0.0f, 0.0f); _followDesiredBodyTransform.setIdentity(); _followTimeRemaining = 0.0f; _jumpSpeed = JUMP_SPEED; _state = State::Hover; _isPushingUp = false; _rayHitStartTime = 0; _takeoffToInAirStartTime = 0; _jumpButtonDownStartTime = 0; _jumpButtonDownCount = 0; _followTime = 0.0f; _followLinearDisplacement = btVector3(0, 0, 0); _followAngularDisplacement = btQuaternion::getIdentity(); _hasSupport = false; _pendingFlags = PENDING_FLAG_UPDATE_SHAPE; } CharacterController::~CharacterController() { if (_rigidBody) { btCollisionShape* shape = _rigidBody->getCollisionShape(); if (shape) { delete shape; } delete _rigidBody; _rigidBody = nullptr; } } bool CharacterController::needsRemoval() const { return ((_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION); } bool CharacterController::needsAddition() const { return ((_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION) == PENDING_FLAG_ADD_TO_SIMULATION); } void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { if (_dynamicsWorld != world) { // remove from old world if (_dynamicsWorld) { if (_rigidBody) { _dynamicsWorld->removeRigidBody(_rigidBody); _dynamicsWorld->removeAction(this); } _dynamicsWorld = nullptr; } int16_t collisionGroup = computeCollisionGroup(); if (_rigidBody) { updateMassProperties(); } if (world && _rigidBody) { // add to new world _dynamicsWorld = world; _pendingFlags &= ~PENDING_FLAG_JUMP; _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); _dynamicsWorld->addAction(this); // restore gravity settings because adding an object to the world overwrites its gravity setting _rigidBody->setGravity(_gravity * _currentUp); btCollisionShape* shape = _rigidBody->getCollisionShape(); assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape)); } _ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup)); _ghost.setCollisionWorld(_dynamicsWorld); _ghost.setRadiusAndHalfHeight(_radius, _halfHeight); if (_rigidBody) { _ghost.setWorldTransform(_rigidBody->getWorldTransform()); } } if (_dynamicsWorld) { if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { // shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION; } else { _pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION; } } else { _pendingFlags &= ~PENDING_FLAG_REMOVE_FROM_SIMULATION; } } bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { bool pushing = _targetVelocity.length2() > FLT_EPSILON; btDispatcher* dispatcher = collisionWorld->getDispatcher(); int numManifolds = dispatcher->getNumManifolds(); bool hasFloor = false; btTransform rotation = _rigidBody->getWorldTransform(); rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part for (int i = 0; i < numManifolds; i++) { btPersistentManifold* contactManifold = dispatcher->getManifoldByIndexInternal(i); if (_rigidBody == contactManifold->getBody1() || _rigidBody == contactManifold->getBody0()) { bool characterIsFirst = _rigidBody == contactManifold->getBody0(); int numContacts = contactManifold->getNumContacts(); int stepContactIndex = -1; float highestStep = _minStepHeight; for (int j = 0; j < numContacts; j++) { // check for "floor" btManifoldPoint& contact = contactManifold->getContactPoint(j); 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 < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) { hasFloor = true; if (!pushing) { // we're not pushing against anything so we can early exit // (all we need to know is that there is a floor) break; } } if (pushing && _targetVelocity.dot(normal) < 0.0f) { // remember highest step obstacle if (!_stepUpEnabled || 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) { // remember step info for later btManifoldPoint& contact = contactManifold->getContactPoint(stepContactIndex); btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame _stepNormal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character _stepHeight = highestStep; _stepPoint = rotation * pointOnCharacter; // rotate into world-frame } if (hasFloor && !(pushing && _stepUpEnabled)) { // early exit since all we need to know is that we're on a floor break; } } } return hasFloor; } void CharacterController::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) { preStep(collisionWorld); playerStep(collisionWorld, deltaTime); } void CharacterController::preStep(btCollisionWorld* collisionWorld) { // trace a ray straight down to see if we're standing on the ground const btTransform& transform = _rigidBody->getWorldTransform(); // rayStart is at center of bottom sphere btVector3 rayStart = transform.getOrigin() - _halfHeight * _currentUp; // rayEnd is some short distance outside bottom sphere const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius; btScalar rayLength = _radius + FLOOR_PROXIMITY_THRESHOLD; btVector3 rayEnd = rayStart - rayLength * _currentUp; // scan down for nearby floor ClosestNotMe rayCallback(_rigidBody); rayCallback.m_closestHitFraction = 1.0f; collisionWorld->rayTest(rayStart, rayEnd, rayCallback); if (rayCallback.hasHit()) { _floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius; } } const btScalar MIN_TARGET_SPEED = 0.001f; const btScalar MIN_TARGET_SPEED_SQUARED = MIN_TARGET_SPEED * MIN_TARGET_SPEED; void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) { _stepHeight = _minStepHeight; // clears memory of last step obstacle _hasSupport = checkForSupport(collisionWorld); btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity; computeNewVelocity(dt, velocity); const float MINIMUM_TIME_REMAINING = 0.005f; const float MAX_DISPLACEMENT = 0.5f * _radius; _followTimeRemaining -= dt; if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) { btTransform bodyTransform = _rigidBody->getWorldTransform(); btVector3 startPos = bodyTransform.getOrigin(); btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos; btVector3 vel = deltaPos / _followTimeRemaining; btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling. btVector3 endPos = startPos + linearDisplacement; btQuaternion startRot = bodyTransform.getRotation(); glm::vec2 currentFacing = getFacingDir2D(bulletToGLM(startRot)); glm::vec2 currentRight(currentFacing.y, -currentFacing.x); glm::vec2 desiredFacing = getFacingDir2D(bulletToGLM(_followDesiredBodyTransform.getRotation())); float deltaAngle = acosf(glm::clamp(glm::dot(currentFacing, desiredFacing), -1.0f, 1.0f)); float angularSpeed = deltaAngle / _followTimeRemaining; float sign = copysignf(1.0f, glm::dot(desiredFacing, currentRight)); btQuaternion angularDisplacement = btQuaternion(btVector3(0.0f, 1.0f, 0.0f), sign * angularSpeed * dt); btQuaternion endRot = angularDisplacement * startRot; // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); _followLinearDisplacement = linearDisplacement + swingDisplacement + _followLinearDisplacement; _followAngularDisplacement = angularDisplacement * _followAngularDisplacement; _rigidBody->setWorldTransform(btTransform(endRot, endPos)); } _followTime += dt; if (_steppingUp) { float horizontalTargetSpeed = (_targetVelocity - _targetVelocity.dot(_currentUp) * _currentUp).length(); if (horizontalTargetSpeed > FLT_EPSILON) { // compute a stepUpSpeed that will reach the top of the step in the time it would take // to move over the _stepPoint at target speed float horizontalDistance = (_stepPoint - _stepPoint.dot(_currentUp) * _currentUp).length(); float timeToStep = horizontalDistance / horizontalTargetSpeed; float stepUpSpeed = _stepHeight / timeToStep; // magically clamp stepUpSpeed to a fraction of horizontalTargetSpeed // to prevent the avatar from moving unreasonably fast according to human eye const float MAX_STEP_UP_SPEED = 0.65f * horizontalTargetSpeed; if (stepUpSpeed > MAX_STEP_UP_SPEED) { stepUpSpeed = MAX_STEP_UP_SPEED; } // add minimum velocity to counteract gravity's displacement during one step // Note: the 0.5 factor comes from the fact that we really want the // average velocity contribution from gravity during the step stepUpSpeed -= 0.5f * _gravity * timeToStep; // remember: _gravity is negative scalar btScalar vDotUp = velocity.dot(_currentUp); if (vDotUp < stepUpSpeed) { // character doesn't have enough upward velocity to cover the step so we help using a "sky hook" // which uses micro-teleports rather than applying real velocity // to prevent the avatar from popping up after the step is done btTransform transform = _rigidBody->getWorldTransform(); transform.setOrigin(transform.getOrigin() + (dt * stepUpSpeed) * _currentUp); _rigidBody->setWorldTransform(transform); } // don't allow the avatar to fall downward when stepping up // since otherwise this would tend to defeat the step-up behavior if (vDotUp < 0.0f) { velocity -= vDotUp * _currentUp; } } } _rigidBody->setLinearVelocity(velocity + _parentVelocity); _ghost.setWorldTransform(_rigidBody->getWorldTransform()); } void CharacterController::jump() { _pendingFlags |= PENDING_FLAG_JUMP; } bool CharacterController::onGround() const { const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius; return _floorDistance < FLOOR_PROXIMITY_THRESHOLD || _hasSupport; } #ifdef DEBUG_STATE_CHANGE static const char* stateToStr(CharacterController::State state) { switch (state) { case CharacterController::State::Ground: return "Ground"; case CharacterController::State::Takeoff: return "Takeoff"; case CharacterController::State::InAir: return "InAir"; case CharacterController::State::Hover: return "Hover"; default: return "Unknown"; } } #endif // #ifdef DEBUG_STATE_CHANGE #ifdef DEBUG_STATE_CHANGE void CharacterController::setState(State desiredState, const char* reason) { #else void CharacterController::setState(State desiredState) { #endif if (!_flyingAllowed && desiredState == State::Hover) { desiredState = State::InAir; } if (desiredState != _state) { #ifdef DEBUG_STATE_CHANGE qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason; #endif _state = desiredState; updateGravity(); } } void CharacterController::updateGravity() { int16_t collisionGroup = computeCollisionGroup(); if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { _gravity = 0.0f; } else { const float DEFAULT_CHARACTER_GRAVITY = -5.0f; _gravity = DEFAULT_CHARACTER_GRAVITY; } if (_rigidBody) { _rigidBody->setGravity(_gravity * _currentUp); } } void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale) { float x = scale.x; float z = scale.z; float radius = 0.5f * sqrtf(0.5f * (x * x + z * z)); float halfHeight = 0.5f * scale.y - radius; float MIN_HALF_HEIGHT = 0.1f; if (halfHeight < MIN_HALF_HEIGHT) { halfHeight = MIN_HALF_HEIGHT; } // compare dimensions if (glm::abs(radius - _radius) > FLT_EPSILON || glm::abs(halfHeight - _halfHeight) > FLT_EPSILON) { _radius = radius; _halfHeight = halfHeight; const btScalar DEFAULT_MIN_STEP_HEIGHT_FACTOR = 0.005f; const btScalar DEFAULT_MAX_STEP_HEIGHT_FACTOR = 0.65f; _minStepHeight = DEFAULT_MIN_STEP_HEIGHT_FACTOR * (_halfHeight + _radius); _maxStepHeight = DEFAULT_MAX_STEP_HEIGHT_FACTOR * (_halfHeight + _radius); if (_dynamicsWorld) { // must REMOVE from world prior to shape update _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; } _pendingFlags |= PENDING_FLAG_UPDATE_SHAPE; _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; } // it's ok to change offset immediately -- there are no thread safety issues here _shapeLocalOffset = minCorner + 0.5f * scale; } void CharacterController::setCollisionless(bool collisionless) { if (collisionless != _collisionless) { _collisionless = collisionless; _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; } } int16_t CharacterController::computeCollisionGroup() const { if (_collisionless) { return _collisionlessAllowed ? BULLET_COLLISION_GROUP_COLLISIONLESS : BULLET_COLLISION_GROUP_MY_AVATAR; } else { return BULLET_COLLISION_GROUP_MY_AVATAR; } } void CharacterController::handleChangedCollisionGroup() { if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_GROUP) { // ATM the easiest way to update collision groups is to remove/re-add the RigidBody if (_dynamicsWorld) { _dynamicsWorld->removeRigidBody(_rigidBody); int16_t collisionGroup = computeCollisionGroup(); _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); } _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP; updateGravity(); } } void CharacterController::updateUpAxis(const glm::quat& rotation) { _currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS); if (_state != State::Hover && _rigidBody) { _rigidBody->setGravity(_gravity * _currentUp); } } void CharacterController::setPositionAndOrientation( const glm::vec3& position, const glm::quat& orientation) { updateUpAxis(orientation); _rotation = glmToBullet(orientation); _position = glmToBullet(position + orientation * _shapeLocalOffset); } void CharacterController::getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const { if (_rigidBody) { const btTransform& avatarTransform = _rigidBody->getWorldTransform(); rotation = bulletToGLM(avatarTransform.getRotation()); position = bulletToGLM(avatarTransform.getOrigin()) - rotation * _shapeLocalOffset; } } void CharacterController::setParentVelocity(const glm::vec3& velocity) { _parentVelocity = glmToBullet(velocity); } void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) { _followTimeRemaining = timeRemaining; _followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset)); } glm::vec3 CharacterController::getFollowLinearDisplacement() const { return bulletToGLM(_followLinearDisplacement); } glm::quat CharacterController::getFollowAngularDisplacement() const { return bulletToGLM(_followAngularDisplacement); } glm::vec3 CharacterController::getFollowVelocity() const { if (_followTime > 0.0f) { return bulletToGLM(_followLinearDisplacement) / _followTime; } else { return glm::vec3(); } } glm::vec3 CharacterController::getLinearVelocity() const { glm::vec3 velocity(0.0f); if (_rigidBody) { velocity = bulletToGLM(_rigidBody->getLinearVelocity()); } return velocity; } glm::vec3 CharacterController::getVelocityChange() const { if (_rigidBody) { return bulletToGLM(_velocityChange); } return glm::vec3(0.0f); } void CharacterController::clearMotors() { _motors.clear();