// // Lol Engine // // Copyright: (c) 2010-2012 Sam Hocevar <sam@hocevar.net> // (c) 2009-2012 Cédric Lecacheur <jordx@free.fr> // (c) 2009-2012 Benjamin Huet <huet.benjamin@gmail.com> // This program is free software; you can redistribute it and/or // modify it under the terms of the Do What The Fuck You Want To // Public License, Version 2, as published by Sam Hocevar. See // http://sam.zoy.org/projects/COPYING.WTFPL for more details. // #if defined HAVE_CONFIG_H # include "config.h" #endif #include "../Include/LolBtPhysicsIntegration.h" #include "../Include/LolPhysics.h" #include "../Include/EasyCharacterController.h" #include "../Include/BulletCharacterController.h" //#include "LinearMath/btIDebugDraw.h" //#include "BulletCollision/CollisionDispatch/btGhostObject.h" //#include "BulletCollision/CollisionShapes/btMultiSphereShape.h" //#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" //#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" //#include "BulletCollision/CollisionDispatch/btCollisionWorld.h" //#include "LinearMath/btDefaultMotionState.h" namespace lol { namespace phys { #define 0 #ifdef HAVE_PHYS_USE_BULLET // static helper method static btVector3 getNormalizedVector(const btVector3& v) { btVector3 n = v.normalized(); if (n.length() < SIMD_EPSILON) { n.setValue(0, 0, 0); } return n; } ///@todo Interact with dynamic objects, ///Ride kinematicly animated platforms properly ///More realistic (or maybe just a config option) falling /// -> Should integrate falling velocity manually and use that in stepDown() ///Support jumping ///Support ducking class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: btKinematicClosestNotMeRayResultCallback (btCollisionObject* 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: btCollisionObject* m_me; }; class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: btKinematicClosestNotMeConvexResultCallback (btCollisionObject* me, const btVector3& up, btScalar minSlopeDot) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) , m_me(me) , m_up(up) , m_minSlopeDot(minSlopeDot) { } virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == m_me) return btScalar(1.0); btVector3 hitNormalWorld; if (normalInWorldSpace) { hitNormalWorld = convexResult.m_hitNormalLocal; } else { ///need to transform normal into worldspace hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; } btScalar dotUp = m_up.dot(hitNormalWorld); if (dotUp < m_minSlopeDot) { return btScalar(1.0); } return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace); } protected: btCollisionObject* m_me; const btVector3 m_up; btScalar m_minSlopeDot; }; /* * 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 BulletKinematicCharacterController::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 BulletKinematicCharacterController::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 BulletKinematicCharacterController::perpindicularComponent (const btVector3& direction, const btVector3& normal) { return direction - parallelComponent(direction, normal); } BulletKinematicCharacterController::BulletKinematicCharacterController (btPairCachingGhostObject* ghostObject,btConvexShape* convexShape,btScalar stepHeight, int upAxis) { m_upAxis = upAxis; m_addedMargin = 0.02; m_walkDirection.setValue(0,0,0); m_useGhostObjectSweepTest = true; m_ghostObject = ghostObject; m_stepHeight = stepHeight; m_turnAngle = btScalar(0.0); m_convexShape=convexShape; m_useWalkDirection = true; // use walk direction by default, legacy behavior m_velocityTimeInterval = 0.0; m_verticalVelocity = 0.0; m_verticalOffset = 0.0; m_gravity = 9.8 * 3 ; // 3G acceleration. m_fallSpeed = 55.0; // Terminal velocity of a sky diver in m/s. m_jumpSpeed = 10.0; // ? m_wasOnGround = false; m_wasJumping = false; setMaxSlope(btRadians(45.0)); } BulletKinematicCharacterController::~BulletKinematicCharacterController () { } btPairCachingGhostObject* BulletKinematicCharacterController::getGhostObject() { return m_ghostObject; } bool BulletKinematicCharacterController::recoverFromPenetration ( btCollisionWorld* collisionWorld) { bool penetration = false; collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher()); m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); btScalar maxPen = btScalar(0.0); for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) { m_manifoldArray.resize(0); btBroadphasePair* collisionPair = &m_ghostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; if (collisionPair->m_algorithm) collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray); for (int j=0;j<m_manifoldArray.size();j++) { btPersistentManifold* manifold = m_manifoldArray[j]; btScalar directionSign = manifold->getBody0() == m_ghostObject ? btScalar(-1.0) : btScalar(1.0); for (int p=0;p<manifold->getNumContacts();p++) { const btManifoldPoint&pt = manifold->getContactPoint(p); btScalar dist = pt.getDistance(); if (dist < 0.0) { if (dist < maxPen) { maxPen = dist; m_touchingNormal = pt.m_normalWorldOnB * directionSign;//?? } m_currentPosition += pt.m_normalWorldOnB * directionSign * dist * btScalar(0.2); penetration = true; } else { //printf("touching %f\n", dist); } } //manifold->clearManifold(); } } btTransform newTrans = m_ghostObject->getWorldTransform(); newTrans.setOrigin(m_currentPosition); m_ghostObject->setWorldTransform(newTrans); // printf("m_touchingNormal = %f,%f,%f\n",m_touchingNormal[0],m_touchingNormal[1],m_touchingNormal[2]); return penetration; } void BulletKinematicCharacterController::stepUp ( btCollisionWorld* world) { // phase 1: up btTransform start, end; m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.f?m_verticalOffset:0.f)); start.setIdentity (); end.setIdentity (); /* FIXME: Handle penetration properly */ start.setOrigin (m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin)); end.setOrigin (m_targetPosition); btKinematicClosestNotMeConvexResultCallback callback (m_ghostObject, -getUpAxisDirections()[m_upAxis], btScalar(0.7071)); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; if (m_useGhostObjectSweepTest) { m_ghostObject->convexSweepTest (m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration); } else { world->convexSweepTest (m_convexShape, start, end, callback); } if (callback.hasHit()) { // Only modify the position if the hit was a slope and not a wall or ceiling. if(callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > 0.0) { // 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); } m_verticalVelocity = 0.0; m_verticalOffset = 0.0; } else { m_currentStepOffset = m_stepHeight; m_currentPosition = m_targetPosition; } } void BulletKinematicCharacterController::updateTargetPositionBasedOnCollision (const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) { btVector3 movementDirection = m_targetPosition - m_currentPosition; btScalar movementLength = movementDirection.length(); if (movementLength>SIMD_EPSILON) { 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 (0)//tangentMag != 0.0) { btVector3 parComponent = parallelDir * btScalar (tangentMag*movementLength); // printf("parComponent=%f,%f,%f\n",parComponent[0],parComponent[1],parComponent[2]); m_targetPosition += parComponent; } if (normalMag != 0.0) { btVector3 perpComponent = perpindicularDir * btScalar (normalMag*movementLength); // printf("perpComponent=%f,%f,%f\n",perpComponent[0],perpComponent[1],perpComponent[2]); m_targetPosition += perpComponent; } } else { // printf("movementLength don't normalize a zero vector\n"); } } void BulletKinematicCharacterController::stepForwardAndStrafe ( btCollisionWorld* collisionWorld, const btVector3& walkMove) { // printf("m_normalizedDirection=%f,%f,%f\n", // m_normalizedDirection[0],m_normalizedDirection[1],m_normalizedDirection[2]); // 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(); // printf("distance2=%f\n",distance2); if (m_touchingContact) { if (m_normalizedDirection.dot(m_touchingNormal) > btScalar(0.0)) { updateTargetPositionBasedOnCollision (m_touchingNormal); } } int maxIter = 10; while (fraction > btScalar(0.01) && maxIter-- > 0) { start.setOrigin (m_currentPosition); end.setOrigin (m_targetPosition); btVector3 sweepDirNegative(m_currentPosition - m_targetPosition); btKinematicClosestNotMeConvexResultCallback callback (m_ghostObject, sweepDirNegative, btScalar(0.0)); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; btScalar margin = m_convexShape->getMargin(); m_convexShape->setMargin(margin + m_addedMargin); if (m_useGhostObjectSweepTest) { m_ghostObject->convexSweepTest (m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); } else { collisionWorld->convexSweepTest (m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); } m_convexShape->setMargin(margin); fraction -= callback.m_closestHitFraction; if (callback.hasHit()) { // we moved only a fraction btScalar hitDistance; hitDistance = (callback.m_hitPointWorld - m_currentPosition).length(); // m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); updateTargetPositionBasedOnCollision (callback.m_hitNormalWorld); btVector3 currentDir = m_targetPosition - m_currentPosition; distance2 = currentDir.length2(); if (distance2 > SIMD_EPSILON) { currentDir.normalize(); /* See Quake2: "If velocity is against original velocity, stop ead to avoid tiny oscilations in sloping corners." */ if (currentDir.dot(m_normalizedDirection) <= btScalar(0.0)) { break; } } else { // printf("currentDir: don't normalize a zero vector\n"); break; } } else { // we moved whole way m_currentPosition = m_targetPosition; } // if (callback.m_closestHitFraction == 0.f) // break; } } void BulletKinematicCharacterController::stepDown ( btCollisionWorld* collisionWorld, btScalar dt) { btTransform start, end; // phase 3: down /*btScalar additionalDownStep = (m_wasOnGround && !onGround()) ? m_stepHeight : 0.0; btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + additionalDownStep); btScalar downVelocity = (additionalDownStep == 0.0 && m_verticalVelocity<0.0?-m_verticalVelocity:0.0) * dt; btVector3 gravity_drop = getUpAxisDirections()[m_upAxis] * downVelocity; m_targetPosition -= (step_drop + gravity_drop);*/ btScalar downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt; if(downVelocity > 0.0 && downVelocity < m_stepHeight && (m_wasOnGround || !m_wasJumping)) { downVelocity = m_stepHeight; } btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity); m_targetPosition -= step_drop; start.setIdentity (); end.setIdentity (); start.setOrigin (m_currentPosition); end.setOrigin (m_targetPosition); btKinematicClosestNotMeConvexResultCallback callback (m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; if (m_useGhostObjectSweepTest) { m_ghostObject->convexSweepTest (m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); } else { collisionWorld->convexSweepTest (m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); } if (callback.hasHit()) { // we dropped a fraction of the height -> hit floor m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); m_verticalVelocity = 0.0; m_verticalOffset = 0.0; m_wasJumping = false; } else { // we dropped the full height m_currentPosition = m_targetPosition; } } void BulletKinematicCharacterController::setWalkDirection ( const btVector3& walkDirection ) { m_useWalkDirection = true; m_walkDirection = walkDirection; m_normalizedDirection = getNormalizedVector(m_walkDirection); } void BulletKinematicCharacterController::setVelocityForTimeInterval ( const btVector3& velocity, btScalar timeInterval ) { // printf("setVelocity!\n"); // printf(" interval: %f\n", timeInterval); // printf(" velocity: (%f, %f, %f)\n", // velocity.x(), velocity.y(), velocity.z()); m_useWalkDirection = false; m_walkDirection = velocity; m_normalizedDirection = getNormalizedVector(m_walkDirection); m_velocityTimeInterval = timeInterval; } void BulletKinematicCharacterController::reset () { } void BulletKinematicCharacterController::warp (const btVector3& origin) { btTransform xform; xform.setIdentity(); xform.setOrigin (origin); m_ghostObject->setWorldTransform (xform); } void BulletKinematicCharacterController::preStep ( btCollisionWorld* collisionWorld) { int numPenetrationLoops = 0; m_touchingContact = false; while (recoverFromPenetration (collisionWorld)) { numPenetrationLoops++; m_touchingContact = true; if (numPenetrationLoops > 4) { //printf("character could not recover from penetration = %d\n", numPenetrationLoops); break; } } m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); m_targetPosition = m_currentPosition; // printf("m_targetPosition=%f,%f,%f\n",m_targetPosition[0],m_targetPosition[1],m_targetPosition[2]); } #include <stdio.h> void BulletKinematicCharacterController::playerStep ( btCollisionWorld* collisionWorld, btScalar dt) { // printf("playerStep(): "); // printf(" dt = %f", dt); // quick check... if (!m_useWalkDirection && m_velocityTimeInterval <= 0.0) { // printf("\n"); return; // no motion } m_wasOnGround = onGround(); // Update fall velocity. m_verticalVelocity -= m_gravity * dt; if(m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed) { m_verticalVelocity = m_jumpSpeed; } if(m_verticalVelocity < 0.0 && btFabs(m_verticalVelocity) > btFabs(m_fallSpeed)) { m_verticalVelocity = -btFabs(m_fallSpeed); } m_verticalOffset = m_verticalVelocity * dt; btTransform xform; xform = m_ghostObject->getWorldTransform (); // printf("walkDirection(%f,%f,%f)\n",walkDirection[0],walkDirection[1],walkDirection[2]); // printf("walkSpeed=%f\n",walkSpeed); stepUp (collisionWorld); if (m_useWalkDirection) { stepForwardAndStrafe (collisionWorld, m_walkDirection); } else { //printf(" time: %f", m_velocityTimeInterval); // still have some time left for moving! btScalar dtMoving = (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval; m_velocityTimeInterval -= dt; // how far will we move while we are moving? btVector3 move = m_walkDirection * dtMoving; //printf(" dtMoving: %f", dtMoving); // okay, step stepForwardAndStrafe(collisionWorld, move); } stepDown (collisionWorld, dt); // printf("\n"); xform.setOrigin (m_currentPosition); m_ghostObject->setWorldTransform (xform); } void BulletKinematicCharacterController::setFallSpeed (btScalar fallSpeed) { m_fallSpeed = fallSpeed; } void BulletKinematicCharacterController::setJumpSpeed (btScalar jumpSpeed) { m_jumpSpeed = jumpSpeed; } void BulletKinematicCharacterController::setMaxJumpHeight (btScalar maxJumpHeight) { m_maxJumpHeight = maxJumpHeight; } bool BulletKinematicCharacterController::canJump () const { return onGround(); } void BulletKinematicCharacterController::jump () { if (!canJump()) return; m_verticalVelocity = m_jumpSpeed; m_wasJumping = true; #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 } void BulletKinematicCharacterController::setGravity(btScalar gravity) { m_gravity = gravity; } btScalar BulletKinematicCharacterController::getGravity() const { return m_gravity; } void BulletKinematicCharacterController::setMaxSlope(btScalar slopeRadians) { m_maxSlopeRadians = slopeRadians; m_maxSlopeCosine = btCos(slopeRadians); } btScalar BulletKinematicCharacterController::getMaxSlope() const { return m_maxSlopeRadians; } bool BulletKinematicCharacterController::onGround () const { return m_verticalVelocity == 0.0 && m_verticalOffset == 0.0; } btVector3* BulletKinematicCharacterController::getUpAxisDirections() { static btVector3 sUpAxisDirection[3] = { btVector3(1.0f, 0.0f, 0.0f), btVector3(0.0f, 1.0f, 0.0f), btVector3(0.0f, 0.0f, 1.0f) }; return sUpAxisDirection; } void BulletKinematicCharacterController::debugDraw(btIDebugDraw* debugDrawer) { } #endif // HAVE_PHYS_USE_BULLET #endif // 0 } /* namespace phys */ } /* namespace lol */