#include "physic_bullet/bullet_character_controller.h" #include "LinearMath/btIDebugDraw.h" #include "nstring/nstring.h" using namespace GS; //------------------------------------------------------------------------------ void btCustomCharacterController::debugDraw(btIDebugDraw *idebug) { String output; output << "Vertical Velocity: " << mVerticalVelocity << "\n"; output << "OnGround: " << (mGroundContact ? "Yes" : "No") << "\n"; output << "Ground.y: " << mGroundNormal.y() << "\n"; output << "Step high: " << dbg_step_high << "\n"; output << "Down sweep: " << dbg_down_sweep_hit << "\n"; idebug->draw3dText(mCurrentPosition, output.c_str()); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ btVector3 btCustomCharacterController::computeReflectionDirection(const btVector3 & direction, const btVector3 & normal) { return direction - (btScalar(2) * direction.dot(normal)) * normal; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ btCustomCharacterController::btCustomCharacterController(btPairCachingGhostObject * ghostObject, btConvexShape * convexShape, btScalar stepHeight, btCollisionWorld * collisionWorld, int upAxis) { mUpAxis = upAxis; mAddedMargin = 0.02; mWalkDirection.setValue(0, 0, 0); // mUseGhostObjectSweepTest = true; mGhostObject = ghostObject; mStepHeight = stepHeight; mTurnAngle = 0; mConvexShape = mStandingConvexShape = convexShape; mUseWalkDirection = true; mVelocityTimeInterval = 0; mVerticalOffset = 0; mVerticalVelocity = 0; mGravity = 9.8 * 3.0; mFallSpeed = 9.8; mJumpSpeed = 10; // mWasOnGround = false; // mWasJumping = false; setMaxSlope(btRadians(45)); mCollisionWorld = collisionWorld; // mCanStand = true; mCurrentPosition.setValue(0, 0, 0); mMass = 20; mGroundContact = false; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::setDuckingConvexShape(btConvexShape * shape) { mDuckingConvexShape = shape; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::setRBForceImpulseBasedOnCollision() { if (mWalkDirection.isZero()) return; for (int i = 0; i < mGhostObject->getOverlappingPairCache()->getNumOverlappingPairs(); ++i) { btBroadphasePair *collisionPair = &mGhostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; btRigidBody *rb = (btRigidBody*)collisionPair->m_pProxy1->m_clientObject; if (mMass > rb->getInvMass()) { btScalar resultMass = mMass - rb->getInvMass(); btVector3 reflection = computeReflectionDirection(mWalkDirection * resultMass, getNormalizedVector(mWalkDirection)); rb->applyCentralImpulse(reflection * -1); } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::setVelocityForTimeInterval(const btVector3 & velocity, btScalar timeInterval) { mUseWalkDirection = false; mWalkDirection = velocity; mNormalizedDirection = getNormalizedVector(mWalkDirection); mVelocityTimeInterval = timeInterval; } void btCustomCharacterController::warp(const btVector3 & origin) { btTransform xform; xform.setIdentity(); xform.setOrigin(origin); mGhostObject->setWorldTransform(xform); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool btCustomCharacterController::SweepTest(const btVector3 &src, const btVector3 &dst, btScalar &hitFraction, btVector3 *normal, btVector3 *hit) { btTransform start, end; start.setIdentity(); end.setIdentity(); start.setOrigin(src); end.setOrigin(dst); ClosestNotMeConvexResultCallback callback(mGhostObject, getUpAxisDirection(), 0); callback.m_collisionFilterGroup = mGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = mGhostObject->getBroadphaseHandle()->m_collisionFilterMask; mGhostObject->convexSweepTest(mConvexShape, start, end, callback, mCollisionWorld->getDispatchInfo().m_allowedCcdPenetration); hitFraction = callback.hasHit() ? callback.m_closestHitFraction : btScalar(1); if (normal) *normal = callback.m_hitNormalWorld; if (hit) *hit = callback.m_hitPointWorld; return callback.hasHit(); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool btCustomCharacterController::recoverFromPenetration(const btVector3 &step_direction) { return false; mCollisionWorld->getDispatcher()->dispatchAllCollisionPairs(mGhostObject->getOverlappingPairCache(), mCollisionWorld->getDispatchInfo(), mCollisionWorld->getDispatcher()); bool penetration = false; for (int i = 0; i < mGhostObject->getOverlappingPairCache()->getNumOverlappingPairs(); ++i) { btBroadphasePair *collisionPair = &mGhostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; mManifoldArray.resize(0); if (collisionPair->m_algorithm) collisionPair->m_algorithm->getAllContactManifolds(mManifoldArray); for (int j = 0; j < mManifoldArray.size(); ++j) { btPersistentManifold *manifold = mManifoldArray[j]; btScalar directionSign = manifold->getBody0() == mGhostObject ? btScalar(1) : btScalar(-1); for (int p = 0; p < manifold->getNumContacts(); ++p) { const btManifoldPoint &pt = manifold->getContactPoint(p); btScalar dist = pt.getDistance(); btVector3 normal = pt.m_normalWorldOnB * directionSign; if (dist < 0.0) { penetration = true; if (normal.y() > 0.9) normal = btVector3(0, 1, 0); // prevent sliding down slopes mCurrentPosition -= normal * dist * btScalar(0.1); } } } } return penetration; } //------------------------------------------------------------------------------ #include "log/log.h" enum { UpSweep, ForwardSweep, DownSweep }; //------------------------------------------------------------------------------ bool btCustomCharacterController::SweepAndSlide(btVector3 &from, btVector3 &to, int sweep) { bool hit = false; for (int it = 0; it < 4; ++it) { btScalar hit_fraction; btVector3 hit_normal; if (!SweepTest(from, to, hit_fraction, &hit_normal)) return hit; switch (sweep) { case ForwardSweep: if (hit_normal.y() < 0.75) { hit_normal.setY(0.0); hit_normal.normalize(); } else hit = true; break; } from += (to - from) * hit_fraction; to -= hit_normal * (to - from).dot(hit_normal); if ((to - from).length2() < btScalar(0.0001)) break; } return hit; } //------------------------------------------------------------------------------ void btCustomCharacterController::performStep(btScalar dt) { btScalar hit_fraction; btVector3 hit_normal, hit_point; mStepHeight = 0.25; // up sweep btVector3 step_height = getUpAxisDirection() * mStepHeight; btVector3 wpos = mGhostObject->getWorldTransform().getOrigin(); btVector3 tpos = wpos + step_height; SweepTest(wpos, tpos, hit_fraction, &hit_normal); tpos = wpos + (tpos - wpos) * hit_fraction; // forward sweep bool forward_hit = false; if (mWalkDirection.length2() > 0.0) { wpos = tpos; tpos += mWalkDirection; forward_hit = SweepAndSlide(wpos, tpos, ForwardSweep); } // down sweep btVector3 g = btVector3(0, -9, 0) * dt; wpos = tpos; tpos -= step_height; tpos += g; bool down_hit = SweepTest(wpos, tpos, hit_fraction, &hit_normal); tpos = wpos + (tpos - wpos) * hit_fraction; // landing on a steep slope higher than we starter, revert height change. if (down_hit && (hit_normal.y() < 0.75)) { tpos += hit_normal * 0.2; wpos = tpos; tpos = wpos - btVector3(0, 4, 0); SweepTest(wpos, tpos, hit_fraction); tpos = wpos + (tpos - wpos) * hit_fraction; } mCurrentPosition = tpos; return; // } /* // perform high sweep to step above small obstacle btVector3 step_height = getUpAxisDirection() * mStepHeight; wpos = mGhostObject->getWorldTransform().getOrigin() + step_height; tpos = wpos + mWalkDirection; if (!SweepAndSlide(wpos, tpos, HighSweep)) return; // high sweep is not cutting it either... drop its result entirely // step down from the high sweep wpos = tpos; tpos -= step_height; if (!SweepAndSlide(wpos, tpos, DownSweep)) { mVerticalVelocity = 0; return; } // else { mVerticalVelocity = btClamped(mVerticalVelocity - mGravity * dt, -mFallSpeed, mJumpSpeed); wpos = tpos; tpos += btVector3(0, mVerticalVelocity, 0) * dt; SweepAndSlide(wpos, tpos, GravitySweep); } // commit mCurrentPosition = tpos; */ } //------------------------------------------------------------------------------ void btCustomCharacterController::preStep(btCollisionWorld *collisionWorld) {} void btCustomCharacterController::playerStep(btCollisionWorld * collisionWorld, btScalar dt) { if (!mUseWalkDirection && mVelocityTimeInterval <= 0) return; performStep(dt); #if 0 mCurrentPosition = mGhostObject->getWorldTransform().getOrigin(); // Apply gravity. mVerticalVelocity = btClamped(mVerticalVelocity - mGravity * dt, -mFallSpeed, mJumpSpeed); // Compute total step velocity. mTouchingContact = false; for (int n = 0; (n < 4) && recoverFromPenetration(mWalkDirection + btVector3(0, -1, 0) * mVerticalVelocity); ++n) mTouchingContact = true; // Perform sweep tests. btVector3 stepHeight = getUpAxisDirection() * mStepHeight; btVector3 stepHigh = mCurrentPosition + stepHeight; btScalar hitFraction; mGroundContact = false; if (SweepTest(stepHigh, stepHigh + mWalkDirection, hitFraction, &mGroundNormal)) { btVector3 hit = stepHigh + mWalkDirection * hitFraction; if (mGroundNormal.y() > 0.6) // on ground, take step mCurrentPosition = hit; } else { stepHigh += mWalkDirection; // take the whole walk step btVector3 g = stepHeight - mVerticalVelocity * getUpAxisDirection(); bool down_sweep_hit = SweepTest(stepHigh, stepHigh - g, hitFraction, &mGroundNormal); if (!down_sweep_hit) { mCurrentPosition += mVerticalVelocity * getUpAxisDirection(); // free-falling } else { mGroundContact = mGroundNormal.y() > 0.6; btVector3 dp = stepHigh - g * hitFraction; if (dp.y() > mCurrentPosition.y()) // going up a slope { if (mGroundContact) mCurrentPosition = dp; // ok if on ground } else mCurrentPosition = dp; } } #endif // if (mGroundContact) mVerticalVelocity = 0.0; // btTransform xform = mGhostObject->getWorldTransform(); xform.setOrigin(mCurrentPosition); mGhostObject->setWorldTransform(xform); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::setFallSpeed(btScalar fallSpeed) { mFallSpeed = fallSpeed; } void btCustomCharacterController::setJumpSpeed(btScalar jumpSpeed) { mJumpSpeed = jumpSpeed; } void btCustomCharacterController::setMaxJumpHeight(btScalar maxJumpHeight) { mMaxJumpHeight = maxJumpHeight; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool btCustomCharacterController::canJump() const { return onGround(); } void btCustomCharacterController::jump() { if (!canJump()) return; mVerticalVelocity = mJumpSpeed; // mWasJumping = true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::duck() { mConvexShape = mDuckingConvexShape; mGhostObject->setCollisionShape(mDuckingConvexShape); btTransform xform; xform.setIdentity(); xform.setOrigin(mCurrentPosition + btVector3(0, 0.1, 0)); mGhostObject->setWorldTransform(xform); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::stand() { mConvexShape = mStandingConvexShape; mGhostObject->setCollisionShape(mStandingConvexShape); } bool btCustomCharacterController::canStand() { return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::setGravity(const btScalar gravity) { mGravity = gravity; } btScalar btCustomCharacterController::getGravity() const { return mGravity; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::setMaxSlope(btScalar slopeRadians) { mMaxSlopeRadians = slopeRadians; mMaxSlopeCosine = btCos(slopeRadians); } btScalar btCustomCharacterController::getMaxSlope() const { return mMaxSlopeRadians; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool btCustomCharacterController::onGround() const { return mGroundContact; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::setWalkDirection(const btVector3 & walkDirection) { mUseWalkDirection = true; mWalkDirection = walkDirection; mNormalizedDirection = getNormalizedVector(mWalkDirection); } void btCustomCharacterController::setWalkDirection(const btScalar x, const btScalar y, const btScalar z) { mUseWalkDirection = true; mWalkDirection.setValue(x, y, z); mNormalizedDirection = getNormalizedVector(mWalkDirection); } btVector3 btCustomCharacterController::getWalkDirection() const { return mWalkDirection; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ btVector3 btCustomCharacterController::getPosition() const { return mCurrentPosition; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::setOrientation(const btQuaternion &orientation) { btTransform xform; xform = mGhostObject->getWorldTransform(); xform.setRotation(orientation); mGhostObject->setWorldTransform(xform); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void btCustomCharacterController::updateAction(btCollisionWorld *collisionWorld, btScalar dt) { preStep(collisionWorld); playerStep(collisionWorld, dt); } //------------------------------------------------------------------------------