diff --git a/Demos/CharacterDemo/CharacterControllerInterface.h b/Demos/CharacterDemo/CharacterControllerInterface.h new file mode 100644 index 000000000..f56aa0952 --- /dev/null +++ b/Demos/CharacterDemo/CharacterControllerInterface.h @@ -0,0 +1,32 @@ +#ifndef CHARACTER_CONTROLLER_INTERFACE_H +#define CHARACTER_CONTROLLER_INTERFACE_H + +#include "LinearMath/btVector3.h" + +class btCollisionShape; +class btRigidBody; +class btDynamicsWorld; + +class CharacterControllerInterface +{ +public: + CharacterControllerInterface () {}; + virtual ~CharacterControllerInterface () {}; + virtual void setup (btDynamicsWorld* dynamicsWorld, btScalar height = 2.0, btScalar width = 0.25, btScalar stepHeight = 0.25) = 0; + virtual void destroy (btDynamicsWorld* dynamicsWorld) = 0; + + virtual btRigidBody* getRigidBody () = 0; + + virtual void preStep (btDynamicsWorld* dynamicsWorld) = 0; + virtual void playerStep (btDynamicsWorld* dynamicsWorld, btScalar dt, + int forward, + int backward, + int left, + int right) = 0; + virtual bool canJump () const = 0; + virtual void jump () = 0; + + virtual bool onGround () const = 0; +}; + +#endif diff --git a/Demos/CharacterDemo/CharacterDemo.cpp b/Demos/CharacterDemo/CharacterDemo.cpp index c852d4ba1..f611d7b50 100644 --- a/Demos/CharacterDemo/CharacterDemo.cpp +++ b/Demos/CharacterDemo/CharacterDemo.cpp @@ -28,7 +28,11 @@ subject to the following restrictions: #include "GlutStuff.h" #include "CharacterDemo.h" -#include "CharacterController.h" +#ifdef DYNAMIC_CHARACTER_CONTROLLER +#include "DynamicCharacterController.h" +#else +#include "KinematicCharacterController.h" +#endif const int maxProxies = 32766; const int maxOverlap = 65535; @@ -39,6 +43,55 @@ static int gLeft = 0; static int gRight = 0; static int gJump = 0; +#define QUAKE_BSP_IMPORTING 1 + +#ifdef QUAKE_BSP_IMPORTING +#include "Demos/BspDemo/BspLoader.h" +#include "Demos/BspDemo/BspConverter.h" +#endif //QUAKE_BSP_IMPORTING + + +class BspToBulletConverter : public BspConverter +{ + CharacterDemo* m_demoApp; + +public: + + BspToBulletConverter(CharacterDemo* demoApp) + :m_demoApp(demoApp) + { + } + + virtual void addConvexVerticesCollider(btAlignedObjectArray& vertices, bool isEntity, const btVector3& entityTargetLocation) + { + ///perhaps we can do something special with entities (isEntity) + ///like adding a collision Triggering (as example) + + if (vertices.size() > 0) + { + float mass = 0.f; + btTransform startTransform; + //can use a shift + startTransform.setIdentity(); + startTransform.setOrigin(btVector3(0,-10.0f,0.0f)); + //this create an internal copy of the vertices + for (int i = 0; i < vertices.size(); i++) + { + vertices[i] *= btScalar(0.5); + float t = vertices[i].getZ() * btScalar(0.75); + vertices[i].setZ(-vertices[i].getY()); + vertices[i].setY(t); + } + + btCollisionShape* shape = new btConvexHullShape(&(vertices[0].getX()),vertices.size()); + m_demoApp->m_collisionShapes.push_back(shape); + + //btRigidBody* body = m_demoApp->localCreateRigidBody(mass, startTransform,shape); + m_demoApp->localCreateRigidBody(mass, startTransform,shape); + } + } +}; + CharacterDemo::CharacterDemo() : m_cameraHeight(4.f), @@ -57,6 +110,7 @@ CharacterDemo::~CharacterDemo() if (m_character) m_character->destroy (m_dynamicsWorld); + //remove the rigidbodies from the dynamics world and delete them int i; for (i=m_dynamicsWorld->getNumCollisionObjects()-1; i>=0 ;i--) @@ -153,11 +207,15 @@ public: return m_hashPairCache->getOverlappingPairArray(); } + btOverlappingPairCache* getOverlappingPairCache() + { + return m_hashPairCache; + } + }; void CharacterDemo::initPhysics() { - btCollisionShape* groundShape = new btBoxShape(btVector3(50,3,50)); m_collisionShapes.push_back(groundShape); m_collisionConfiguration = new btDefaultCollisionConfiguration(); @@ -173,7 +231,53 @@ void CharacterDemo::initPhysics() btTransform tr; tr.setIdentity(); -//either use heightfield or triangle mesh +#define USE_BSP_STAGE +#ifdef USE_BSP_STAGE +#ifdef QUAKE_BSP_IMPORTING + char* bspfilename = "BspDemo.bsp"; + void* memoryBuffer = 0; + + FILE* file = fopen(bspfilename,"r"); + if (!file) + { + //try again other path, + //sight... visual studio leaves the current working directory in the projectfiles folder + //instead of executable folder. who wants this default behaviour?!? + bspfilename = "../../BspDemo.bsp"; + file = fopen(bspfilename,"r"); + } + if (!file) + { + //try again other path, + //sight... visual studio leaves the current working directory in the projectfiles folder + //instead of executable folder. who wants this default behaviour?!? + bspfilename = "BspDemo.bsp"; + file = fopen(bspfilename,"r"); + } + + if (file) + { + BspLoader bspLoader; + int size=0; + if (fseek(file, 0, SEEK_END) || (size = ftell(file)) == EOF || fseek(file, 0, SEEK_SET)) { /* File operations denied? ok, just close and return failure */ + printf("Error: cannot get filesize from %s\n", bspfilename); + } else + { + //how to detect file size? + memoryBuffer = malloc(size+1); + fread(memoryBuffer,1,size,file); + bspLoader.loadBSPFile( memoryBuffer); + + BspToBulletConverter bsp2bullet(this); + float bspScaling = 0.1f; + bsp2bullet.convertBsp(bspLoader,bspScaling); + + } + fclose(file); + } + +#endif +#else #define USE_TRIMESH_GROUND 1 #ifdef USE_TRIMESH_GROUND int i; @@ -297,15 +401,6 @@ const float TRIANGLE_SIZE=20.f; //create ground object localCreateRigidBody(0,tr,groundShape); - m_character = new CharacterController (); - m_character->setup (m_dynamicsWorld); - - //we need to remove the rigid body from the broadphase in order to register all collisions - m_dynamicsWorld->removeRigidBody(m_character->getRigidBody()); - //some custom callback sample - m_customPairCallback = new MyCustomOverlappingPairCallback(this,m_character->getRigidBody()); - sweepBP->setOverlappingPairUserCallback(m_customPairCallback); - m_dynamicsWorld->addRigidBody(m_character->getRigidBody()); #define CUBE_HALF_EXTENTS 0.5 @@ -364,7 +459,23 @@ const float TRIANGLE_SIZE=20.f; } #endif - + +#endif + +#ifdef DYNAMIC_CHARACTER_CONTROLLER + m_character = new DynamicCharacterController (); +#else + m_character = new KinematicCharacterController (); +#endif + m_character->setup (m_dynamicsWorld); + + //we need to remove the rigid body from the broadphase in order to register all collisions + m_dynamicsWorld->removeRigidBody(m_character->getRigidBody()); + //some custom callback sample + m_customPairCallback = new MyCustomOverlappingPairCallback(this,m_character->getRigidBody()); + sweepBP->setOverlappingPairUserCallback(m_customPairCallback); + m_dynamicsWorld->addRigidBody(m_character->getRigidBody()); + m_character->registerPairCache (m_customPairCallback->getOverlappingPairCache()); clientResetScene(); setCameraDistance(26.f); @@ -391,7 +502,7 @@ void CharacterDemo::clientMoveAndDisplay() if (m_character) { m_character->preStep (m_dynamicsWorld); - m_character->playerStep (dt, gForward, gBackward, gLeft, gRight); + m_character->playerStep (m_dynamicsWorld, dt, gForward, gBackward, gLeft, gRight); if (gJump) { gJump = 0; @@ -616,8 +727,8 @@ void CharacterDemo::updateCamera() //update OpenGL camera settings glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 10000.0); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); gluLookAt(m_cameraPosition[0],m_cameraPosition[1],m_cameraPosition[2], m_cameraTargetPosition[0],m_cameraTargetPosition[1], m_cameraTargetPosition[2], diff --git a/Demos/CharacterDemo/CharacterDemo.h b/Demos/CharacterDemo/CharacterDemo.h index 10fe7722f..c18a8857f 100644 --- a/Demos/CharacterDemo/CharacterDemo.h +++ b/Demos/CharacterDemo/CharacterDemo.h @@ -16,6 +16,7 @@ subject to the following restrictions: #define CHARACTER_DEMO_H class CharacterController; +class KinematicCharacterController; class btCollisionShape; @@ -27,7 +28,11 @@ class CharacterDemo : public DemoApplication { public: +#ifdef DYNAMIC_CHARACTER_CONTROLLER CharacterController* m_character; +#else + KinematicCharacterController* m_character; +#endif btAlignedObjectArray m_collisionShapes; diff --git a/Demos/CharacterDemo/CharacterController.cpp b/Demos/CharacterDemo/DynamicCharacterController.cpp similarity index 83% rename from Demos/CharacterDemo/CharacterController.cpp rename to Demos/CharacterDemo/DynamicCharacterController.cpp index 3f6a3108c..cddb2c416 100644 --- a/Demos/CharacterDemo/CharacterController.cpp +++ b/Demos/CharacterDemo/DynamicCharacterController.cpp @@ -2,9 +2,9 @@ #include "BulletDynamics/Dynamics/btRigidBody.h" #include "BulletDynamics/Dynamics/btDynamicsWorld.h" #include "LinearMath/btDefaultMotionState.h" -#include "CharacterController.h" +#include "DynamicCharacterController.h" -CharacterController::CharacterController () +DynamicCharacterController::DynamicCharacterController () { m_rayLambda[0] = 1.0; m_rayLambda[1] = 1.0; @@ -17,11 +17,11 @@ CharacterController::CharacterController () m_rigidBody = NULL; } -CharacterController::~CharacterController () +DynamicCharacterController::~DynamicCharacterController () { } -void CharacterController::setup (btDynamicsWorld* dynamicsWorld, btScalar height, btScalar width) +void DynamicCharacterController::setup (btDynamicsWorld* dynamicsWorld, btScalar height, btScalar width, btScalar stepHeight) { btVector3 spherePositions[2]; btScalar sphereRadii[2]; @@ -48,7 +48,7 @@ void CharacterController::setup (btDynamicsWorld* dynamicsWorld, btScalar height dynamicsWorld->addRigidBody (m_rigidBody); } -void CharacterController::destroy (btDynamicsWorld* dynamicsWorld) +void DynamicCharacterController::destroy (btDynamicsWorld* dynamicsWorld) { if (m_shape) { @@ -62,12 +62,12 @@ void CharacterController::destroy (btDynamicsWorld* dynamicsWorld) } } -btRigidBody* CharacterController::getRigidBody () +btRigidBody* DynamicCharacterController::getRigidBody () { return m_rigidBody; } -void CharacterController::preStep (btDynamicsWorld* dynamicsWorld) +void DynamicCharacterController::preStep (btDynamicsWorld* dynamicsWorld) { btTransform xform; m_rigidBody->getMotionState()->getWorldTransform (xform); @@ -118,7 +118,7 @@ void CharacterController::preStep (btDynamicsWorld* dynamicsWorld) } } -void CharacterController::playerStep (btScalar dt, +void DynamicCharacterController::playerStep (btScalar dt, int forward, int backward, int left, @@ -167,12 +167,12 @@ void CharacterController::playerStep (btScalar dt, m_rigidBody->setCenterOfMassTransform (xform); } -bool CharacterController::canJump () const +bool DynamicCharacterController::canJump () const { return onGround(); } -void CharacterController::jump () +void DynamicCharacterController::jump () { if (!canJump()) return; @@ -185,7 +185,7 @@ void CharacterController::jump () m_rigidBody->applyCentralImpulse (up * magnitude); } -bool CharacterController::onGround () const +bool DynamicCharacterController::onGround () const { return m_rayLambda[0] < btScalar(1.0); } diff --git a/Demos/CharacterDemo/CharacterController.h b/Demos/CharacterDemo/DynamicCharacterController.h similarity index 75% rename from Demos/CharacterDemo/CharacterController.h rename to Demos/CharacterDemo/DynamicCharacterController.h index b18c068d6..0933317fa 100644 --- a/Demos/CharacterDemo/CharacterController.h +++ b/Demos/CharacterDemo/DynamicCharacterController.h @@ -3,11 +3,13 @@ #include "LinearMath/btVector3.h" +#include "CharacterControllerInterface.h" + class btCollisionShape; class btRigidBody; class btDynamicsWorld; -class CharacterController +class DynamicCharacterController : public CharacterControllerInterface { protected: btScalar m_halfHeight; @@ -25,9 +27,9 @@ protected: btScalar m_walkVelocity; btScalar m_turnVelocity; public: - CharacterController (); - ~CharacterController (); - void setup (btDynamicsWorld* dynamicsWorld, btScalar height = 2.0, btScalar width = 0.25); + DynamicCharacterController (); + ~DynamicCharacterController (); + void setup (btDynamicsWorld* dynamicsWorld, btScalar height = 2.0, btScalar width = 0.25, btScalar stepHeight = 0.25); void destroy (btDynamicsWorld* dynamicsWorld); btRigidBody* getRigidBody (); diff --git a/Demos/CharacterDemo/KinematicCharacterController.cpp b/Demos/CharacterDemo/KinematicCharacterController.cpp new file mode 100644 index 000000000..e80aa2187 --- /dev/null +++ b/Demos/CharacterDemo/KinematicCharacterController.cpp @@ -0,0 +1,384 @@ +#include + +#include "GLDebugDrawer.h" + +#include "BulletCollision/CollisionShapes/btMultiSphereShape.h" +#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" +#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" +#include "BulletDynamics/Dynamics/btRigidBody.h" +#include "BulletDynamics/Dynamics/btDynamicsWorld.h" +#include "LinearMath/btDefaultMotionState.h" +#include "KinematicCharacterController.h" + +/* TODO: + * Handle projecting/slide along surfaces + * Deal with starting in penetration + * Interact with dynamic objects + * Ride kinematicly animated platforms properly + * Step climbing + */ +class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback +{ +public: + ClosestNotMeRayResultCallback (btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + { + m_me = me; + } + + virtual btScalar AddSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) + { + if (rayResult.m_collisionObject == m_me) + return 1.0; + + return ClosestRayResultCallback::AddSingleResult (rayResult, normalInWorldSpace); + } +protected: + btRigidBody* m_me; +}; + +class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback +{ +public: + ClosestNotMeConvexResultCallback (btRigidBody* me) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + { + m_me = me; + } + + virtual btScalar AddSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + { + if (convexResult.m_hitCollisionObject == m_me) + return 1.0; + + return ClosestConvexResultCallback::AddSingleResult (convexResult, normalInWorldSpace); + } +protected: + btRigidBody* m_me; +}; + +/* + * Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal' + * + * from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html + */ +btVector3 computeReflectionDirection (const btVector3& direction, const btVector3& normal) +{ + return direction - (btScalar(2.0) * direction.dot(normal)) * normal; +} + +/* + * Returns the portion of 'direction' that is parallel to 'normal' + */ +btVector3 parallelComponent (const btVector3& direction, const btVector3& normal) +{ + btScalar magnitude = direction.dot(normal); + return normal * magnitude; +} + +/* + * Returns the portion of 'direction' that is perpindicular to 'normal' + */ +btVector3 perpindicularComponent (const btVector3& direction, const btVector3& normal) +{ + return direction - parallelComponent(direction, normal); +} + +KinematicCharacterController::KinematicCharacterController () +{ + m_turnAngle = btScalar(0.0); + m_walkVelocity = btScalar(1.1) * 4.0; // 4 km/h -> 1.1 m/s + m_shape = NULL; + m_pairCache = NULL; + m_rigidBody = NULL; +} + +KinematicCharacterController::~KinematicCharacterController () +{ +} + +void KinematicCharacterController::setup (btDynamicsWorld* dynamicsWorld, btScalar height, btScalar width, btScalar stepHeight) +{ + btVector3 spherePositions[2]; + btScalar sphereRadii[2]; + + sphereRadii[0] = width; + sphereRadii[1] = width; + spherePositions[0] = btVector3 (0.0, (height/btScalar(2.0) - width), 0.0); + spherePositions[1] = btVector3 (0.0, (-height/btScalar(2.0) + width), 0.0); + + m_halfHeight = height/btScalar(2.0); + + m_shape = new btMultiSphereShape (btVector3(width/btScalar(2.0), height/btScalar(2.0), width/btScalar(2.0)), &spherePositions[0], &sphereRadii[0], 2); + m_stepHeight = stepHeight; + m_height = height; + m_width = width; + btTransform startTransform; + startTransform.setIdentity (); + startTransform.setOrigin (btVector3(0.0, 4.0, 0.0)); + btDefaultMotionState* myMotionState = new btDefaultMotionState(startTransform); + btRigidBody::btRigidBodyConstructionInfo cInfo(1.0, myMotionState, m_shape); + m_rigidBody = new btRigidBody(cInfo); + m_rigidBody->setCollisionFlags( m_rigidBody->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_NO_CONTACT_RESPONSE); + m_rigidBody->setSleepingThresholds (0.0, 0.0); + m_rigidBody->setAngularFactor (0.0); + dynamicsWorld->addRigidBody (m_rigidBody); +} + +void KinematicCharacterController::destroy (btDynamicsWorld* dynamicsWorld) +{ + if (m_rigidBody) + { + dynamicsWorld->removeRigidBody (m_rigidBody); + delete m_rigidBody; + } + + if (m_shape) + { + delete m_shape; + } +} + +btRigidBody* KinematicCharacterController::getRigidBody () +{ + return m_rigidBody; +} + +void KinematicCharacterController::recoverFromPenetration (btDynamicsWorld* dynamicsWorld) +{ + if (m_pairCache == NULL) + return; + + printf("%d\n", m_pairCache->getNumOverlappingPairs()); + dynamicsWorld->getDispatcher()->dispatchAllCollisionPairs (m_pairCache, dynamicsWorld->getDispatchInfo(), dynamicsWorld->getDispatcher()); + + btManifoldArray manifoldArray; + for (int i = 0; i < m_pairCache->getNumOverlappingPairs(); i++) + { + printf("%d\n",i); + manifoldArray.clear(); + + btBroadphasePair* collisionPair = &m_pairCache->getOverlappingPairArray()[i]; + + if (collisionPair->m_algorithm) + collisionPair->m_algorithm->getAllContactManifolds(manifoldArray); + + for (int j=0;jgetNumContacts();p++) + { + const btManifoldPoint&pt = manifold->getContactPoint(p); + + if (pt.getDistance() < 0.0) + { + printf("penetration %f\n", pt.getDistance()); + } else { + printf("touching %f\n", pt.getDistance()); + } + } + } + } +} + +void KinematicCharacterController::stepUp (btDynamicsWorld* dynamicsWorld) +{ + // phase 1: up + btTransform start, end; + m_targetPosition = m_currentPosition + btVector3 (btScalar(0.0), m_stepHeight, btScalar(0.0)); + + start.setIdentity (); + end.setIdentity (); + + /* FIXME: Handle penetration properly */ + start.setOrigin (m_currentPosition + btVector3(btScalar(0.0), btScalar(0.1), btScalar(0.0))); + end.setOrigin (m_targetPosition); + + ClosestNotMeConvexResultCallback callback (m_rigidBody); + + dynamicsWorld->convexSweepTest (m_shape, start, end, callback); + + if (callback.HasHit()) + { + // we moved up only a fraction of the step height + m_currentStepOffset = m_stepHeight * callback.m_closestHitFraction; + m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); + } else { + m_currentStepOffset = m_stepHeight; + m_currentPosition = m_targetPosition; + } +} + +void KinematicCharacterController::updateTargetPositionBasedOnCollision (const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) +{ + btVector3 movementDirection = m_targetPosition - m_currentPosition; + btScalar movementLength = movementDirection.length(); + movementDirection.normalize(); + + btVector3 reflectDir = computeReflectionDirection (movementDirection, hitNormal); + reflectDir.normalize(); + + btVector3 parallelDir, perpindicularDir; + + parallelDir = parallelComponent (reflectDir, hitNormal); + perpindicularDir = perpindicularComponent (reflectDir, hitNormal); + + m_targetPosition = m_currentPosition; + if (tangentMag != 0.0) + { + m_targetPosition += parallelDir * btScalar (tangentMag*movementLength); + } + + if (normalMag != 0.0) + { + m_targetPosition += perpindicularDir * btScalar (normalMag*movementLength); + } +} + +void KinematicCharacterController::stepForwardAndStrafe (btDynamicsWorld* dynamicsWorld, const btVector3& walkMove) +{ + // phase 2: forward and strafe + btTransform start, end; + m_targetPosition = m_currentPosition + walkMove; + start.setIdentity (); + end.setIdentity (); + + btScalar fraction = 1.0; + btScalar distance2 = (m_currentPosition-m_targetPosition).length2(); + + while (fraction > btScalar(0.01)) + { + start.setOrigin (m_currentPosition); + end.setOrigin (m_targetPosition); + + ClosestNotMeConvexResultCallback callback (m_rigidBody); + dynamicsWorld->convexSweepTest (m_shape, start, end, callback); + + fraction -= callback.m_closestHitFraction; + + if (callback.HasHit()) + { + // we moved only a fraction + m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); + updateTargetPositionBasedOnCollision (callback.m_hitNormalWorld); + distance2 = (m_currentPosition-m_targetPosition).length2(); + } else { + // we moved whole way + m_currentPosition = m_targetPosition; + } + } +} + +void KinematicCharacterController::stepDown (btDynamicsWorld* dynamicsWorld, btScalar dt) +{ + btTransform start, end; + + // phase 3: down + btVector3 step_drop = btVector3(btScalar(0.0), m_currentStepOffset, btScalar(0.0)); + btVector3 gravity_drop = btVector3(btScalar(0.0), m_stepHeight, btScalar(0.0)); + m_targetPosition -= (step_drop + gravity_drop); + + start.setIdentity (); + end.setIdentity (); + + start.setOrigin (m_currentPosition); + end.setOrigin (m_targetPosition); + + ClosestNotMeConvexResultCallback callback (m_rigidBody); + + dynamicsWorld->convexSweepTest (m_shape, start, end, callback); + + if (callback.HasHit()) + { + // we dropped a fraction of the height -> hit floor + m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); + } else { + // we dropped the full height + + m_currentPosition = m_targetPosition; + } +} + +void KinematicCharacterController::registerPairCache (btOverlappingPairCache* pairCache) +{ + m_pairCache = pairCache; +} + +void KinematicCharacterController::preStep (btDynamicsWorld* dynamicsWorld) +{ + btTransform xform; + m_rigidBody->getMotionState()->getWorldTransform (xform); + + btVector3 forwardDir = xform.getBasis()[2]; + btVector3 upDir = xform.getBasis()[1]; + btVector3 strafeDir = xform.getBasis()[0]; + forwardDir.normalize (); + upDir.normalize (); + strafeDir.normalize (); + + m_upDirection = upDir; + m_forwardDirection = forwardDir; + m_strafeDirection = strafeDir; + + m_currentPosition = xform.getOrigin(); + m_targetPosition = m_currentPosition; + recoverFromPenetration (dynamicsWorld); +} + +void KinematicCharacterController::playerStep (btDynamicsWorld* dynamicsWorld, + btScalar dt, + int forward, + int backward, + int left, + int right) +{ + btVector3 walkDirection = btVector3(0.0, 0.0, 0.0); + btScalar walkSpeed = m_walkVelocity * dt; + + if (left) + walkDirection += m_strafeDirection; + + if (right) + walkDirection -= m_strafeDirection; + + if (forward) + walkDirection += m_forwardDirection; + + if (backward) + walkDirection -= m_forwardDirection; + + btTransform xform; + m_rigidBody->getMotionState()->getWorldTransform (xform); + + stepUp (dynamicsWorld); + stepForwardAndStrafe (dynamicsWorld, walkDirection * walkSpeed); + stepDown (dynamicsWorld, dt); + + xform.setOrigin (m_currentPosition); + m_rigidBody->getMotionState()->setWorldTransform (xform); + m_rigidBody->setCenterOfMassTransform (xform); +} + +bool KinematicCharacterController::canJump () const +{ + return onGround(); +} + +void KinematicCharacterController::jump () +{ + if (!canJump()) + return; + +#if 0 + currently no jumping. + btTransform xform; + m_rigidBody->getMotionState()->getWorldTransform (xform); + btVector3 up = xform.getBasis()[1]; + up.normalize (); + btScalar magnitude = (btScalar(1.0)/m_rigidBody->getInvMass()) * btScalar(8.0); + m_rigidBody->applyCentralImpulse (up * magnitude); +#endif +} + +bool KinematicCharacterController::onGround () const +{ + return true; +} diff --git a/Demos/CharacterDemo/KinematicCharacterController.h b/Demos/CharacterDemo/KinematicCharacterController.h new file mode 100644 index 000000000..f3a658013 --- /dev/null +++ b/Demos/CharacterDemo/KinematicCharacterController.h @@ -0,0 +1,61 @@ +#ifndef KINEMATIC_CHARACTER_CONTROLLER_H +#define KINEMATIC_CHARACTER_CONTROLLER_H + +#include "LinearMath/btVector3.h" + +#include "CharacterControllerInterface.h" + +class btCollisionShape; +class btRigidBody; +class btDynamicsWorld; + +class KinematicCharacterController : public CharacterControllerInterface +{ +protected: + btScalar m_halfHeight; + btConvexShape* m_shape; + btRigidBody* m_rigidBody; + btOverlappingPairCache* m_pairCache; + + btScalar m_turnAngle; + btScalar m_walkVelocity; + + btScalar m_height; + btScalar m_width; + btScalar m_stepHeight; + + btVector3 m_upDirection; + btVector3 m_forwardDirection; + btVector3 m_strafeDirection; + + btVector3 m_currentPosition; + btScalar m_currentStepOffset; + btVector3 m_targetPosition; + + void recoverFromPenetration (btDynamicsWorld* dynamicsWorld); + void stepUp (btDynamicsWorld* dynamicsWorld); + void updateTargetPositionBasedOnCollision (const btVector3& hit_normal, btScalar tangentMag = btScalar(1.0), btScalar normalMag = btScalar(0.0)); + void stepForwardAndStrafe (btDynamicsWorld* dynamicsWorld, const btVector3& walkMove); + void stepDown (btDynamicsWorld* dynamicsWorld, btScalar dt); +public: + KinematicCharacterController (); + ~KinematicCharacterController (); + void setup (btDynamicsWorld* dynamicsWorld, btScalar height = btScalar(1.75), btScalar width = btScalar(0.4), btScalar stepHeight = btScalar(0.35)); + void destroy (btDynamicsWorld* dynamicsWorld); + + btRigidBody* getRigidBody (); + + void registerPairCache (btOverlappingPairCache* pairCache); + void preStep (btDynamicsWorld* dynamicsWorld); + void playerStep (btDynamicsWorld* dynamicsWorld, btScalar dt, + int forward, + int backward, + int left, + int right); + bool canJump () const; + void jump (); + + bool onGround () const; +}; + +#endif // KINEMATIC_CHARACTER_CONTROLLER_H