remove kinematic character controller implemention

This commit is contained in:
Andrew Meadows 2017-03-30 18:18:01 -07:00
parent 0aa579225c
commit d34b667e82
5 changed files with 0 additions and 515 deletions

View file

@ -201,158 +201,6 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm:
return result.hitFraction < 1.0f;
}
glm::vec3 MyCharacterController::computeHMDStep(const glm::vec3& position, const glm::vec3& step) {
btVector3 stepDirection = glmToBullet(step);
btScalar stepLength = stepDirection.length();
if (stepLength < FLT_EPSILON) {
return glm::vec3(0.0f);
}
stepDirection /= stepLength;
// get _ghost ready for ray traces
btTransform transform = _rigidBody->getWorldTransform();
btVector3 newPosition = glmToBullet(position);
transform.setOrigin(newPosition);
btMatrix3x3 rotation = transform.getBasis();
_ghost.setWorldTransform(transform);
_ghost.refreshOverlappingPairCache();
// compute rotation that will orient local ray start points to face stepDirection
btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f);
btVector3 horizontalDirection = stepDirection - stepDirection.dot(_currentUp) * _currentUp;
btVector3 axis = forward.cross(horizontalDirection);
btScalar lengthAxis = axis.length();
if (lengthAxis > FLT_EPSILON) {
// non-zero sideways component
btScalar angle = asinf(lengthAxis / horizontalDirection.length());
if (stepDirection.dot(forward) < 0.0f) {
angle = PI - angle;
}
axis /= lengthAxis;
rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation;
} else if (stepDirection.dot(forward) < 0.0f) {
// backwards
rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation;
}
CharacterRayResult rayResult(&_ghost);
btVector3 rayStart;
btVector3 rayEnd;
btVector3 penetration = btVector3(0.0f, 0.0f, 0.0f);
int32_t numPenetrations = 0;
{ // first we scan straight out from capsule center to see if we're stuck on anything
btScalar forwardRatio = 0.5f;
btScalar backRatio = 0.25f;
btVector3 radial;
bool stuck = false;
for (int32_t i = 0; i < _topPoints.size(); ++i) {
rayStart = rotation * _topPoints[i];
radial = rayStart - rayStart.dot(_currentUp) * _currentUp;
rayEnd = newPosition + rayStart + forwardRatio * radial;
rayStart += newPosition - backRatio * radial;
// reset rayResult for next test
rayResult.m_closestHitFraction = 1.0f;
rayResult.m_collisionObject = nullptr;
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
btScalar totalRatio = backRatio + forwardRatio;
btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * totalRatio - backRatio) / forwardRatio;
if (adjustedHitFraction < 0.0f) {
penetration += adjustedHitFraction * radial;
++numPenetrations;
} else {
stuck = true;
}
}
}
if (numPenetrations > 0) {
if (numPenetrations > 1) {
penetration /= (btScalar)numPenetrations;
}
return bulletToGLM(penetration);
} else if (stuck) {
return glm::vec3(0.0f);
}
}
// if we get here then we're not stuck pushing into any surface
// so now we scan to see if the way before us is "walkable"
// scan the top
// NOTE: if we scan an extra distance forward we can detect flat surfaces that are too steep to walk on.
// The approximate extra distance can be derived with trigonometry.
//
// minimumForward = [ (maxStepHeight + radius / cosTheta - radius) * (cosTheta / sinTheta) - radius ]
//
// where: theta = max angle between floor normal and vertical
//
// if stepLength is not long enough we can add the difference.
//
btScalar cosTheta = _minFloorNormalDotUp;
btScalar sinTheta = sqrtf(1.0f - cosTheta * cosTheta);
const btScalar MIN_FORWARD_SLOP = 0.10f; // HACK: not sure why this is necessary to detect steepest walkable slope
btScalar forwardSlop = (_maxStepHeight + _radius / cosTheta - _radius) * (cosTheta / sinTheta) - (_radius + stepLength) + MIN_FORWARD_SLOP;
if (forwardSlop < 0.0f) {
// BIG step, no slop necessary
forwardSlop = 0.0f;
}
// we push the step forward by stepMargin to help reduce accidental penetration
const btScalar MIN_STEP_MARGIN = 0.04f;
btScalar stepMargin = glm::max(_radius, MIN_STEP_MARGIN);
btScalar expandedStepLength = stepLength + forwardSlop + stepMargin;
// loop over topPoints
bool walkable = true;
for (int32_t i = 0; i < _topPoints.size(); ++i) {
rayStart = newPosition + rotation * _topPoints[i];
rayEnd = rayStart + expandedStepLength * stepDirection;
// reset rayResult for next test
rayResult.m_closestHitFraction = 1.0f;
rayResult.m_collisionObject = nullptr;
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
walkable = false;
break;
}
}
}
// scan the bottom
// TODO: implement sliding along sloped floors
bool steppingUp = false;
expandedStepLength = stepLength + MIN_FORWARD_SLOP + MIN_STEP_MARGIN;
for (int32_t i = _bottomPoints.size() - 1; i > -1; --i) {
rayStart = newPosition + rotation * _bottomPoints[i] - MIN_STEP_MARGIN * stepDirection;
rayEnd = rayStart + expandedStepLength * stepDirection;
// reset rayResult for next test
rayResult.m_closestHitFraction = 1.0f;
rayResult.m_collisionObject = nullptr;
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * expandedStepLength - MIN_STEP_MARGIN) / (stepLength + MIN_FORWARD_SLOP);
if (adjustedHitFraction < 1.0f) {
steppingUp = true;
break;
}
}
}
if (!walkable && steppingUp ) {
return glm::vec3(0.0f);
}
// else it might not be walkable, but we aren't steppingUp yet which means we can still move forward
// TODO: slide up ramps and fall off edges (then we can remove the vertical follow of Avatar's RigidBody)
return step;
}
btConvexHullShape* MyCharacterController::computeShape() const {
// HACK: the avatar collides using convex hull with a collision margin equal to
// the old capsule radius. Two points define a capsule and additional points are

View file

@ -40,8 +40,6 @@ public:
/// return true if RayShotgun hits anything
bool testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result);
glm::vec3 computeHMDStep(const glm::vec3& position, const glm::vec3& step);
protected:
void initRayShotgun(const btCollisionWorld* world);

View file

@ -126,10 +126,6 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
_ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup));
_ghost.setCollisionWorld(_dynamicsWorld);
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
_ghost.setMaxStepHeight(0.75f * (_radius + _halfHeight));
_ghost.setMinWallAngle(PI / 4.0f);
_ghost.setUpDirection(_currentUp);
_ghost.setMotorOnly(true);
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
}
if (_dynamicsWorld) {

View file

@ -45,22 +45,6 @@ void CharacterGhostObject::setRadiusAndHalfHeight(btScalar radius, btScalar half
_halfHeight = halfHeight;
}
void CharacterGhostObject::setUpDirection(const btVector3& up) {
btScalar length = up.length();
if (length > FLT_EPSILON) {
_upDirection /= length;
} else {
_upDirection = btVector3(0.0f, 1.0f, 0.0f);
}
}
void CharacterGhostObject::setMotorVelocity(const btVector3& velocity) {
_motorVelocity = velocity;
if (_motorOnly) {
_linearVelocity = _motorVelocity;
}
}
// override of btCollisionObject::setCollisionShape()
void CharacterGhostObject::setCharacterShape(btConvexHullShape* shape) {
assert(shape);
@ -81,164 +65,6 @@ void CharacterGhostObject::setCollisionWorld(btCollisionWorld* world) {
}
}
void CharacterGhostObject::move(btScalar dt, btScalar overshoot, btScalar gravity) {
bool oldOnFloor = _onFloor;
_onFloor = false;
_steppingUp = false;
assert(_world && _inWorld);
updateVelocity(dt, gravity);
// resolve any penetrations before sweeping
int32_t MAX_LOOPS = 4;
int32_t numExtractions = 0;
btVector3 totalPosition(0.0f, 0.0f, 0.0f);
while (numExtractions < MAX_LOOPS) {
if (resolvePenetration(numExtractions)) {
numExtractions = 0;
break;
}
totalPosition += getWorldTransform().getOrigin();
++numExtractions;
}
if (numExtractions > 1) {
// penetration resolution was probably oscillating between opposing objects
// so we use the average of the solutions
totalPosition /= btScalar(numExtractions);
btTransform transform = getWorldTransform();
transform.setOrigin(totalPosition);
setWorldTransform(transform);
// TODO: figure out how to untrap character
}
btTransform startTransform = getWorldTransform();
btVector3 startPosition = startTransform.getOrigin();
if (_onFloor) {
// resolvePenetration() pushed the avatar out of a floor so
// we must updateTraction() before using _linearVelocity
updateTraction(startPosition);
}
btScalar speed = _linearVelocity.length();
btVector3 forwardSweep = dt * _linearVelocity;
btScalar stepDistance = dt * speed;
btScalar MIN_SWEEP_DISTANCE = 0.0001f;
if (stepDistance < MIN_SWEEP_DISTANCE) {
// not moving, no need to sweep
updateTraction(startPosition);
return;
}
// augment forwardSweep to help slow moving sweeps get over steppable ledges
const btScalar MIN_OVERSHOOT = 0.04f; // default margin
if (overshoot < MIN_OVERSHOOT) {
overshoot = MIN_OVERSHOOT;
}
btScalar longSweepDistance = stepDistance + overshoot;
forwardSweep *= longSweepDistance / stepDistance;
// step forward
CharacterSweepResult result(this);
btTransform nextTransform = startTransform;
nextTransform.setOrigin(startPosition + forwardSweep);
sweepTest(_characterShape, startTransform, nextTransform, result); // forward
if (!result.hasHit()) {
nextTransform.setOrigin(startPosition + (stepDistance / longSweepDistance) * forwardSweep);
setWorldTransform(nextTransform);
updateTraction(nextTransform.getOrigin());
return;
}
bool verticalOnly = btFabs(btFabs(_linearVelocity.dot(_upDirection)) - speed) < MIN_OVERSHOOT;
if (verticalOnly) {
// no need to step
nextTransform.setOrigin(startPosition + (result.m_closestHitFraction * stepDistance / longSweepDistance) * forwardSweep);
setWorldTransform(nextTransform);
if (result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) {
_floorNormal = result.m_hitNormalWorld;
_floorContact = result.m_hitPointWorld;
_steppingUp = false;
_onFloor = true;
_hovering = false;
}
updateTraction(nextTransform.getOrigin());
return;
}
// check if this hit is obviously unsteppable
btVector3 hitFromBase = result.m_hitPointWorld - (startPosition - ((_radius + _halfHeight) * _upDirection));
btScalar hitHeight = hitFromBase.dot(_upDirection);
if (hitHeight > _maxStepHeight) {
// shape can't step over the obstacle so move forward as much as possible before we bail
btVector3 forwardTranslation = result.m_closestHitFraction * forwardSweep;
btScalar forwardDistance = forwardTranslation.length();
if (forwardDistance > stepDistance) {
forwardTranslation *= stepDistance / forwardDistance;
}
nextTransform.setOrigin(startPosition + forwardTranslation);
setWorldTransform(nextTransform);
_onFloor = _onFloor || oldOnFloor;
return;
}
// if we get here then we hit something that might be steppable
// remember the forward sweep hit fraction for later
btScalar forwardSweepHitFraction = result.m_closestHitFraction;
// figure out how high we can step up
btScalar availableStepHeight = measureAvailableStepHeight();
// raise by availableStepHeight before sweeping forward
result.resetHitHistory();
startTransform.setOrigin(startPosition + availableStepHeight * _upDirection);
nextTransform.setOrigin(startTransform.getOrigin() + forwardSweep);
sweepTest(_characterShape, startTransform, nextTransform, result);
if (result.hasHit()) {
startTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * forwardSweep);
} else {
startTransform = nextTransform;
}
// sweep down in search of future landing spot
result.resetHitHistory();
btVector3 downSweep = (- availableStepHeight) * _upDirection;
nextTransform.setOrigin(startTransform.getOrigin() + downSweep);
sweepTest(_characterShape, startTransform, nextTransform, result);
if (result.hasHit() && result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) {
// can stand on future landing spot, so we interpolate toward it
_floorNormal = result.m_hitNormalWorld;
_floorContact = result.m_hitPointWorld;
_steppingUp = true;
_onFloor = true;
_hovering = false;
nextTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * downSweep);
btVector3 totalStep = nextTransform.getOrigin() - startPosition;
nextTransform.setOrigin(startPosition + (stepDistance / totalStep.length()) * totalStep);
updateTraction(nextTransform.getOrigin());
} else {
// 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);
}
bool CharacterGhostObject::sweepTest(
const btConvexShape* shape,
const btTransform& start,
const btTransform& end,
CharacterSweepResult& result) const {
if (_world && _inWorld) {
assert(shape);
btScalar allowedPenetration = _world->getDispatchInfo().m_allowedCcdPenetration;
convexSweepTest(shape, start, end, result, allowedPenetration);
return result.hasHit();
}
return false;
}
bool CharacterGhostObject::rayTest(const btVector3& start,
const btVector3& end,
CharacterRayResult& result) const {
@ -248,82 +74,6 @@ bool CharacterGhostObject::rayTest(const btVector3& start,
return result.hasHit();
}
void CharacterGhostObject::measurePenetration(btVector3& minBoxOut, btVector3& maxBoxOut) {
// minBoxOut and maxBoxOut will be updated with penetration envelope.
// If one of the corner points is <0,0,0> then the penetration is resolvable in a single step,
// but if the space spanned by the two corners extends in both directions along at least one
// component then we the object is sandwiched between two opposing objects.
// We assume this object has just been moved to its current location, or else objects have been
// moved around it since the last step so we must update the overlapping pairs.
refreshOverlappingPairCache();
// compute collision details
btHashedOverlappingPairCache* pairCache = getOverlappingPairCache();
_world->getDispatcher()->dispatchAllCollisionPairs(pairCache, _world->getDispatchInfo(), _world->getDispatcher());
// loop over contact manifolds to compute the penetration box
minBoxOut = btVector3(0.0f, 0.0f, 0.0f);
maxBoxOut = btVector3(0.0f, 0.0f, 0.0f);
btManifoldArray manifoldArray;
int numPairs = pairCache->getNumOverlappingPairs();
for (int i = 0; i < numPairs; i++) {
manifoldArray.resize(0);
btBroadphasePair* collisionPair = &(pairCache->getOverlappingPairArray()[i]);
btCollisionObject* obj0 = static_cast<btCollisionObject*>(collisionPair->m_pProxy0->m_clientObject);
btCollisionObject* obj1 = static_cast<btCollisionObject*>(collisionPair->m_pProxy1->m_clientObject);
if ((obj0 && !obj0->hasContactResponse()) && (obj1 && !obj1->hasContactResponse())) {
// we know this probe has no contact response
// but neither does the other object so skip this manifold
continue;
}
if (!collisionPair->m_algorithm) {
// null m_algorithm means the two shape types don't know how to collide!
// shouldn't fall in here but just in case
continue;
}
btScalar mostFloorPenetration = 0.0f;
collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);
for (int j = 0; j < manifoldArray.size(); j++) {
btPersistentManifold* manifold = manifoldArray[j];
btScalar directionSign = (manifold->getBody0() == this) ? btScalar(1.0) : btScalar(-1.0);
for (int p = 0; p < manifold->getNumContacts(); p++) {
const btManifoldPoint& pt = manifold->getContactPoint(p);
if (pt.getDistance() > 0.0f) {
continue;
}
// normal always points from object to character
btVector3 normal = directionSign * pt.m_normalWorldOnB;
btScalar penetrationDepth = pt.getDistance();
if (penetrationDepth < mostFloorPenetration) { // remember penetrationDepth is negative
btScalar normalDotUp = normal.dot(_upDirection);
if (normalDotUp > _maxWallNormalUpComponent) {
mostFloorPenetration = penetrationDepth;
_floorNormal = normal;
if (directionSign > 0.0f) {
_floorContact = pt.m_positionWorldOnA;
} else {
_floorContact = pt.m_positionWorldOnB;
}
_onFloor = true;
}
}
btVector3 penetration = (-penetrationDepth) * normal;
minBoxOut.setMin(penetration);
maxBoxOut.setMax(penetration);
}
}
}
}
void CharacterGhostObject::refreshOverlappingPairCache() {
assert(_world && _inWorld);
btVector3 minAabb, maxAabb;
@ -347,69 +97,3 @@ void CharacterGhostObject::addToWorld() {
_inWorld = true;
}
}
bool CharacterGhostObject::resolvePenetration(int numTries) {
btVector3 minBox, maxBox;
measurePenetration(minBox, maxBox);
btVector3 restore = maxBox + minBox;
if (restore.length2() > 0.0f) {
btTransform transform = getWorldTransform();
transform.setOrigin(transform.getOrigin() + restore);
setWorldTransform(transform);
return false;
}
return true;
}
void CharacterGhostObject::updateVelocity(btScalar dt, btScalar gravity) {
if (!_motorOnly) {
if (_hovering) {
_linearVelocity *= 0.999f; // HACK damping
} else {
_linearVelocity += (dt * gravity) * _upDirection;
}
}
}
void CharacterGhostObject::updateHoverState(const btVector3& position) {
if (_onFloor) {
_hovering = false;
} else {
// cast a ray down looking for floor support
CharacterRayResult rayResult(this);
btScalar distanceToFeet = _radius + _halfHeight;
btScalar slop = 2.0f * getCollisionShape()->getMargin(); // slop to help ray start OUTSIDE the floor object
btVector3 startPos = position - ((distanceToFeet - slop) * _upDirection);
btVector3 endPos = startPos - (2.0f * distanceToFeet) * _upDirection;
rayTest(startPos, endPos, rayResult);
// we're hovering if the ray didn't hit anything or hit unstandable slope
_hovering = !rayResult.hasHit() || rayResult.m_hitNormalWorld.dot(_upDirection) < _maxWallNormalUpComponent;
}
}
void CharacterGhostObject::updateTraction(const btVector3& position) {
updateHoverState(position);
if (_hovering || _motorOnly) {
_linearVelocity = _motorVelocity;
} else if (_onFloor) {
// compute a velocity that swings the shape around the _floorContact
btVector3 leverArm = _floorContact - position;
btVector3 pathDirection = leverArm.cross(_motorVelocity.cross(leverArm));
btScalar pathLength = pathDirection.length();
if (pathLength > FLT_EPSILON) {
_linearVelocity = (_motorVelocity.length() / pathLength) * pathDirection;
} else {
_linearVelocity = btVector3(0.0f, 0.0f, 0.0f);
}
}
}
btScalar CharacterGhostObject::measureAvailableStepHeight() const {
CharacterSweepResult result(this);
btTransform transform = getWorldTransform();
btTransform nextTransform = transform;
nextTransform.setOrigin(transform.getOrigin() + _maxStepHeight * _upDirection);
sweepTest(_characterShape, transform, nextTransform, result);
return result.m_closestHitFraction * _maxStepHeight;
}

View file

@ -33,71 +33,30 @@ public:
void setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight);
void setUpDirection(const btVector3& up);
void setMotorVelocity(const btVector3& velocity);
void setMinWallAngle(btScalar angle) { _maxWallNormalUpComponent = cosf(angle); }
void setMaxStepHeight(btScalar height) { _maxStepHeight = height; }
void setLinearVelocity(const btVector3& velocity) { _linearVelocity = velocity; }
const btVector3& getLinearVelocity() const { return _linearVelocity; }
void setCharacterShape(btConvexHullShape* shape);
void setCollisionWorld(btCollisionWorld* world);
void move(btScalar dt, btScalar overshoot, btScalar gravity);
bool sweepTest(const btConvexShape* shape,
const btTransform& start,
const btTransform& end,
CharacterSweepResult& result) const;
bool rayTest(const btVector3& start,
const btVector3& end,
CharacterRayResult& result) const;
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; }
void measurePenetration(btVector3& minBoxOut, btVector3& maxBoxOut);
void refreshOverlappingPairCache();
protected:
void removeFromWorld();
void addToWorld();
bool resolvePenetration(int numTries);
void updateVelocity(btScalar dt, btScalar gravity);
void updateTraction(const btVector3& position);
btScalar measureAvailableStepHeight() const;
void updateHoverState(const btVector3& position);
protected:
btVector3 _upDirection { 0.0f, 1.0f, 0.0f }; // input, up in world-frame
btVector3 _motorVelocity { 0.0f, 0.0f, 0.0f }; // input, velocity character is trying to achieve
btVector3 _linearVelocity { 0.0f, 0.0f, 0.0f }; // internal, actual character velocity
btVector3 _floorNormal { 0.0f, 0.0f, 0.0f }; // internal, probable floor normal
btVector3 _floorContact { 0.0f, 0.0f, 0.0f }; // internal, last floor contact point
btCollisionWorld* _world { nullptr }; // input, pointer to world
//btScalar _distanceToFeet { 0.0f }; // input, distance from object center to lowest point on shape
btScalar _halfHeight { 0.0f };
btScalar _radius { 0.0f };
btScalar _maxWallNormalUpComponent { 0.0f }; // input: max vertical component of wall normal
btScalar _maxStepHeight { 0.0f }; // input, max step height the character can climb
btConvexHullShape* _characterShape { nullptr }; // input, shape of character
CharacterGhostShape* _ghostShape { nullptr }; // internal, shape whose Aabb is used for overlap cache
int16_t _collisionFilterGroup { 0 };
int16_t _collisionFilterMask { 0 };
bool _inWorld { false }; // internal, was added to world
bool _hovering { false }; // internal,
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