Explorar el Código

Added missing PhysicsCharacter source files.

Steve Grenier hace 14 años
padre
commit
0cf0a13075
Se han modificado 2 ficheros con 972 adiciones y 0 borrados
  1. 667 0
      gameplay/src/PhysicsCharacter.cpp
  2. 305 0
      gameplay/src/PhysicsCharacter.h

+ 667 - 0
gameplay/src/PhysicsCharacter.cpp

@@ -0,0 +1,667 @@
+/**
+ * PhysicsCharacter.cpp
+ *
+ * Much of the collision detection code for this implementation is based off the
+ * btbtKinematicCharacterController class from Bullet Physics 2.7.6.
+ */
+#include "Base.h"
+#include "PhysicsCharacter.h"
+#include "Scene.h"
+#include "Game.h"
+#include "PhysicsController.h"
+
+namespace gameplay
+{
+
+class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
+{
+public:
+
+	ClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot)
+        : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), _me(me), _up(up), _minSlopeDot(minSlopeDot)
+	{
+	}
+
+	virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
+	{
+		if (convexResult.m_hitCollisionObject == _me)
+			return btScalar(1.0);
+
+		btVector3 hitNormalWorld;
+		if (normalInWorldSpace)
+		{
+			hitNormalWorld = convexResult.m_hitNormalLocal;
+		} else
+		{
+			// transform normal into worldspace
+			hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
+		}
+
+		btScalar dotUp = _up.dot(hitNormalWorld);
+		if (dotUp < _minSlopeDot)
+        {
+			return btScalar(1.0);
+		}
+
+		return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace);
+	}
+
+protected:
+
+	btCollisionObject* _me;
+	const btVector3 _up;
+	btScalar _minSlopeDot;
+};
+
+PhysicsCharacter::PhysicsCharacter(Node* node, float radius, float height, const Vector3 center)
+    : _node(node), _forwardVelocity(0.0f), _rightVelocity(0.0f), _fallVelocity(0, 0, 0),
+    _currentVelocity(0,0,0), _colliding(false), _ghostObject(NULL), _collisionShape(NULL),
+    _ignoreTransformChanged(0), _stepHeight(0.2f), _slopeAngle(0.0f), _cosSlopeAngle(0.0f)
+{
+    setMaxSlopeAngle(45.0f);
+
+    node->addRef();
+    node->addListener(this);
+
+    // Create physics motion state for syncing transform between gameplay and bullet
+    Vector3 centerOfMassOffset(-center);
+    _motionState = new PhysicsMotionState(node, &centerOfMassOffset);
+
+    // Create ghost object, which is used as an efficient way to detect
+    // collisions between pairs of objects.
+    _ghostObject = bullet_new<btPairCachingGhostObject>();
+
+    // Set initial transform
+    _motionState->getWorldTransform(_ghostObject->getWorldTransform());
+
+    // Create a capsule collision shape
+    _collisionShape = bullet_new<btCapsuleShape>((btScalar)radius, (btScalar)(height - radius*2));
+
+    // Set the collision shape on the ghost object (get it from the node's rigid body)
+    _ghostObject->setCollisionShape(_collisionShape);
+    _ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
+
+    btDynamicsWorld* world = Game::getInstance()->getPhysicsController()->_world;
+
+    // Register the ghost object for collisions with the world.
+    // For now specify static flag only, so character does not interact with dynamic objects
+    world->addCollisionObject(_ghostObject, btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
+
+    // Register ourselves as an action on the physics world so we are called back during physics ticks
+    world->addAction(this);
+}
+
+PhysicsCharacter::~PhysicsCharacter()
+{
+    // Unregister ourself with world
+    btDynamicsWorld* world = Game::getInstance()->getPhysicsController()->_world;
+    world->removeCollisionObject(_ghostObject);
+    world->removeAction(this);
+
+    SAFE_DELETE(_ghostObject);
+    SAFE_DELETE(_collisionShape);
+
+    _node->removeListener(this);
+    SAFE_RELEASE(_node);
+}
+
+Node* PhysicsCharacter::getNode() const
+{
+    return _node;
+}
+
+float PhysicsCharacter::getMaxStepHeight() const
+{
+    return _stepHeight;
+}
+
+void PhysicsCharacter::setMaxStepHeight(float height)
+{
+    _stepHeight = height;
+}
+
+float PhysicsCharacter::getMaxSlopeAngle() const
+{
+    return _slopeAngle;
+}
+
+void PhysicsCharacter::setMaxSlopeAngle(float angle)
+{
+    _slopeAngle = angle;
+    _cosSlopeAngle = std::cos(MATH_DEG_TO_RAD(angle));
+}
+
+void PhysicsCharacter::addAnimation(const char* name, AnimationClip* clip, float moveSpeed)
+{
+    CharacterAnimation a;
+    a.name = name;
+    a.clip = clip;
+    a.moveSpeed = moveSpeed;
+    a.layer = 0;
+    a.playing = false;
+    a.animationFlags = ANIMATION_STOP;
+    a.prev = NULL;
+    _animations[name] = a;
+}
+
+void PhysicsCharacter::play(const char* name, AnimationFlags flags, float speed, unsigned int blendDuration, unsigned int layer)
+{
+    CharacterAnimation* animation = NULL;
+    if (name)
+    {
+        // Lookup the specified animation
+        std::map<const char*, CharacterAnimation>::iterator aitr = _animations.find(name);
+        if (aitr == _animations.end())
+            return; // invalid animation name
+
+        animation = &(aitr->second);
+
+        // Set animation flags
+        animation->clip->setRepeatCount(flags & ANIMATION_REPEAT ? AnimationClip::REPEAT_INDEFINITE : 1);
+        animation->clip->setSpeed(speed);
+        animation->animationFlags = flags;
+        animation->layer = layer;
+        animation->blendDuration = blendDuration;
+        animation->prev = NULL;
+
+        // If the animation is already marked playing, do nothing more
+        if (animation->playing)
+            return;
+    }
+
+    play(animation, layer);
+}
+
+void PhysicsCharacter::play(CharacterAnimation* animation, unsigned int layer)
+{
+    // Is there already an animation playing on this layer?
+    std::map<unsigned int, CharacterAnimation*>::iterator litr = _layers.find(layer);
+    CharacterAnimation* prevAnimation = (litr == _layers.end() ? NULL : litr->second);
+    if (prevAnimation && prevAnimation->playing)
+    {
+        // An animation is already playing on this layer
+        if (animation)
+        {
+            if (animation->animationFlags == ANIMATION_RESUME)
+                animation->prev = prevAnimation;
+
+            if (animation->blendDuration > 0L)
+            {
+                // Crossfade from current animation into the new one
+                prevAnimation->clip->crossFade(animation->clip, animation->blendDuration);
+            }
+            else
+            {
+                // Stop the previous animation (no blending)
+                prevAnimation->clip->stop();
+
+                // Play the new animation
+                animation->clip->play();
+            }
+        }
+        else
+        {
+            // No new animaton specified - stop current animation on this layer
+            prevAnimation->clip->stop();
+        }
+
+        prevAnimation->playing = false;
+    }
+    else if (animation)
+    {
+        // No animations currently playing - just play the new one
+        animation->clip->play();
+    }
+
+    // Update animaton and layers
+    if (animation)
+    {
+        animation->playing = true;
+
+        // Update layer to point to the new animation
+        if (litr != _layers.end())
+            litr->second = animation;
+        else
+            _layers[layer] = animation;
+    }
+    else if (litr != _layers.end())
+    {
+        // Remove layer sine we stopped the animation previously on it
+        _layers.erase(litr);
+    }
+}
+
+void PhysicsCharacter::setVelocity(const Vector3& velocity, unsigned int flags)
+{
+    _moveVelocity.setValue(velocity.x, velocity.y, velocity.z);
+    _moveFlags = flags;
+
+    if (flags & MOVE_ROTATE)
+    {
+        Vector3 dn;
+        velocity.normalize(&dn);
+
+        float angle = std::acos(Vector3::dot(Vector3(0, 0, -1), dn));
+        _node->setRotation(Vector3::unitY(), angle);
+    }
+
+    updateCurrentVelocity();
+}
+
+void PhysicsCharacter::setForwardVelocity(float velocity)
+{
+    _forwardVelocity = velocity;
+    _moveFlags = MOVE_TRANSLATE;
+
+    updateCurrentVelocity();
+}
+
+void PhysicsCharacter::setRightVelocity(float velocity)
+{
+    _rightVelocity = velocity;
+    _moveFlags = MOVE_TRANSLATE;
+
+    updateCurrentVelocity();
+}
+
+void PhysicsCharacter::jump(float height)
+{
+    // TODO
+}
+
+void PhysicsCharacter::updateCurrentVelocity()
+{
+    Vector3 temp;
+    btScalar velocity2 = 0;
+
+    // Reset velocity vector
+    _normalizedVelocity.setValue(0, 0, 0);
+
+    // Add movement velocity contribution
+    if (!_moveVelocity.isZero())
+    {
+        _normalizedVelocity = _moveVelocity;
+        velocity2 = _moveVelocity.length2();
+    }
+
+    // Add forward velocity contribution
+    if (_forwardVelocity != 0)
+    {
+        _node->getWorldMatrix().getForwardVector(&temp);
+        temp.normalize();
+        temp *= -_forwardVelocity;
+        _normalizedVelocity += btVector3(temp.x, temp.y, temp.z);
+        velocity2 = std::max(std::abs(velocity2), std::abs(_forwardVelocity*_forwardVelocity));
+    }
+
+    // Add right velocity contribution
+    if (_rightVelocity != 0)
+    {
+        _node->getWorldMatrix().getRightVector(&temp);
+        temp.normalize();
+        temp *= _rightVelocity;
+        _normalizedVelocity += btVector3(temp.x, temp.y, temp.z);
+        velocity2 = std::max(std::abs(velocity2), std::abs(_rightVelocity*_rightVelocity));
+    }
+
+    // Compute final combined movement vectors
+    _normalizedVelocity.normalize();
+    _currentVelocity = _normalizedVelocity * std::sqrt(velocity2);
+}
+
+void PhysicsCharacter::transformChanged(Transform* transform, long cookie)
+{
+    if (!_ignoreTransformChanged)
+    {
+        // Update motion state with transform from node
+        _motionState->updateTransformFromNode();
+
+        // Update transform on ghost object
+        _motionState->getWorldTransform(_ghostObject->getWorldTransform());
+    }
+}
+
+void PhysicsCharacter::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep)
+{
+    // First check for existing collisions and attempt to respond/fix them.
+    // Basically we are trying to move the character so that it does not penetrate
+    // any other collision objects in the scene. We need to do this to ensure that
+    // the following steps (movement) start from a clean slate, where the character
+    // is not colliding with anything. Also, this step handles collision between
+    // dynamic objects (i.e. objects that moved and now intersect the character).
+    _colliding = false;
+    int stepCount = 0;
+	while (fixCollision(collisionWorld))
+	{
+        _colliding = true;
+
+        // After a small number of attempts to fix a collision/penetration, give up...
+        if (++stepCount > 4)
+		{
+            WARN_VARG("Character '%s' could not recover from collision.", _node->getId());
+			break;
+		}
+	}
+
+    // Update current and target world positions
+    btTransform transform = _ghostObject->getWorldTransform();
+    _currentPosition = transform.getOrigin();
+
+    // Process movement in the up direction
+    stepUp(collisionWorld, deltaTimeStep);
+
+    // Process horizontal movement
+    stepForwardAndStrafe(collisionWorld, deltaTimeStep);
+
+    // Process movement in the down direction
+    stepDown(collisionWorld, deltaTimeStep);
+
+    // Set new position
+    transform.setOrigin(_currentPosition);
+
+    // Update world transform
+    ++_ignoreTransformChanged;
+    _motionState->setWorldTransform(transform);
+    --_ignoreTransformChanged;
+
+    // Update ghost object transform
+    _motionState->getWorldTransform(_ghostObject->getWorldTransform());
+}
+
+void PhysicsCharacter::stepUp(btCollisionWorld* collisionWorld, btScalar time)
+{
+    // Note: btKinematicCharacterController implements this by always just setting
+    // target position to currentPosition.y + stepHeight, and then checking for collisions.
+    // Don't let the character move up if it hits the ceiling (or something above).
+    // Do this WITHOUT using time in the calculation - this way you are always guarnateed
+    // to step over a step that is stepHeight high.
+    // 
+    // Note that stepDown() will be called right after this, so the character will move back
+    // down to collide with the ground so that he smoothly steps up stairs.
+}
+
+void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, float time)
+{
+    btVector3 targetPosition = _currentPosition;
+
+    // Process currently playing movements+animations and determine final move location
+    float animationMoveSpeed = 0.0f;
+    unsigned int animationCount = 0;
+    for (std::map<unsigned int, CharacterAnimation*>::iterator itr = _layers.begin(); itr != _layers.end(); ++itr)
+    {
+        CharacterAnimation* animation = itr->second;
+
+        // If the animation is not playing, ignore it
+        if (!animation->playing)
+            continue;
+
+        AnimationClip* clip = animation->clip;
+
+        // Did the clip finish playing (but we still have it marked playing)?
+        if (!clip->isPlaying())
+        {
+            // If the animaton was flaged the ANIMATION_RESUME bit, start the previously playing animation
+            if ((animation->animationFlags == ANIMATION_RESUME) && animation->prev)
+            {
+                play(animation->prev, animation->prev->layer);
+            }
+
+            animation->playing = false;
+
+            continue;
+        }
+
+        animationMoveSpeed += animation->moveSpeed;
+        ++animationCount;
+    }
+
+    // Calculate final velocity
+    btVector3 velocity(_currentVelocity);
+    if (animationCount > 0)
+    {
+        velocity *= animationMoveSpeed;
+    }
+    velocity *= time; // since velocity is in meters per second
+
+    // Translate the target position by the velocity vector (already scaled by t)
+    targetPosition += velocity;
+
+    // Check for collisions by performing a bullet convex sweep test
+    btTransform start, end;
+	start.setIdentity();
+	end.setIdentity();
+
+	btScalar fraction = 1.0;
+	btScalar distance2 = (_currentPosition-targetPosition).length2();
+
+	if (_colliding && (_normalizedVelocity.dot(_collisionNormal) > btScalar(0.0)))
+	{
+        updateTargetPositionFromCollision(targetPosition, _collisionNormal);
+	}
+
+	int maxIter = 10;
+
+	while (fraction > btScalar(0.01) && maxIter-- > 0)
+	{
+		start.setOrigin(_currentPosition);
+		end.setOrigin(targetPosition);
+		btVector3 sweepDirNegative(_currentPosition - targetPosition);
+
+		ClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.0));
+		callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
+		callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
+
+        // Temporarily increase collision margin by a bit
+        //btScalar margin = _collisionShape->getMargin();
+        //_collisionShape->setMargin(margin + m_addedMargin);
+
+        _ghostObject->convexSweepTest(_collisionShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
+
+		//m_convexShape->setMargin(margin);
+
+		fraction -= callback.m_closestHitFraction;
+
+		if (callback.hasHit())
+        {
+			// We hit something so can move only a fraction
+			//btScalar hitDistance = (callback.m_hitPointWorld - _currentPosition).length();
+
+            //_currentPosition.setInterpolate3(_currentPosition, targetPosition, callback.m_closestHitFraction);
+
+			updateTargetPositionFromCollision(targetPosition, callback.m_hitNormalWorld);
+			btVector3 currentDir = targetPosition - _currentPosition;
+			distance2 = currentDir.length2();
+			if (distance2 > FLT_EPSILON)
+			{
+				currentDir.normalize();
+
+				// If velocity is against original velocity, stop to avoid tiny oscilations in sloping corners.
+				if (currentDir.dot(_normalizedVelocity) <= btScalar(0.0))
+				{
+					break;
+				}
+			}
+        }
+        else
+        {
+            // Nothing in our way
+            //_currentPosition = targetPosition;
+            break;
+        }
+    }
+
+    _currentPosition = targetPosition;
+}
+
+void PhysicsCharacter::stepDown(btCollisionWorld* collisionWorld, btScalar time)
+{
+    // Contribute gravity to fall velocity.
+    // TODO: This simple formula assumes no air friction, which is completely unrealistic
+    // (characters fall way too fast). We should consider how to support this without much
+    // added complexity.
+    btVector3 gravity = Game::getInstance()->getPhysicsController()->_world->getGravity();
+    _fallVelocity += (gravity * time);
+
+    btVector3 targetPosition = _currentPosition + _fallVelocity;
+
+    // Perform a convex sweep test between current and target position
+    btTransform start, end;
+    start.setIdentity();
+    end.setIdentity();
+    start.setOrigin(_currentPosition);
+    end.setOrigin(targetPosition);
+
+    // TODO: We probably have to perform sweep tests separately in stepForward and stepDown (and stepUp) since
+    // combining the full move into a single targetPosition and computing sweep test between currentPosition and targetPosition
+    // is ALYWAYS going to result in a collision at almost exactly currentPosition... this is because, when you are already
+    // on the floor and applying gravity, 
+    ClosestNotMeConvexResultCallback callback(_ghostObject, btVector3(0, 1, 0), _cosSlopeAngle);
+	callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
+	callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
+    _ghostObject->convexSweepTest(_collisionShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
+	if (callback.hasHit())
+	{
+        // Collision detected, fix it
+		_currentPosition.setInterpolate3(_currentPosition, targetPosition, callback.m_closestHitFraction);
+
+        // Zero out fall velocity when we hit an object
+        _fallVelocity.setZero();
+	}
+    else
+    {
+        // We can move here
+        _currentPosition = targetPosition;
+	}
+}
+
+/*
+ * Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal'
+ */
+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);
+}
+
+void PhysicsCharacter::updateTargetPositionFromCollision(btVector3& targetPosition, const btVector3& collisionNormal)
+{
+    //btScalar tangentMag = 0.0;
+    btScalar normalMag = 1.0;
+
+	btVector3 movementDirection = targetPosition - _currentPosition;
+	btScalar movementLength = movementDirection.length();
+
+	if (movementLength > FLT_EPSILON)
+	{
+		movementDirection.normalize();
+
+		btVector3 reflectDir = computeReflectionDirection(movementDirection, collisionNormal);
+		reflectDir.normalize();
+
+		btVector3 parallelDir, perpindicularDir;
+
+		parallelDir = parallelComponent(reflectDir, collisionNormal);
+		perpindicularDir = perpindicularComponent(reflectDir, collisionNormal);
+
+		targetPosition = _currentPosition;
+		/*if (tangentMag != 0.0)
+		{
+			btVector3 parComponent = parallelDir * btScalar (tangentMag*movementLength);
+			targetPosition +=  parComponent;
+		}*/
+
+		if (normalMag != 0.0)
+		{
+			btVector3 perpComponent = perpindicularDir * btScalar (normalMag * movementLength);
+			targetPosition += perpComponent;
+		}
+	}
+}
+
+bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
+{
+	bool collision = false;
+
+    btOverlappingPairCache* pairCache = _ghostObject->getOverlappingPairCache();
+
+    // Tell the world to dispatch collision events for our ghost object
+	world->getDispatcher()->dispatchAllCollisionPairs(pairCache, world->getDispatchInfo(), world->getDispatcher());
+
+    // Store our current world position
+    btVector3 currentPosition = _ghostObject->getWorldTransform().getOrigin();
+
+    // Handle all collisions/overlappign pairs
+	btScalar maxPenetration = btScalar(0.0);
+	for (int i = 0, count = pairCache->getNumOverlappingPairs(); i < count; ++i)
+	{
+		_manifoldArray.resize(0);
+
+        // Query contacts between this overlapping pair (store in _manifoldArray)
+		btBroadphasePair* collisionPair = &pairCache->getOverlappingPairArray()[i];
+		if (collisionPair->m_algorithm)
+        {
+			collisionPair->m_algorithm->getAllContactManifolds(_manifoldArray);
+        }
+
+		for (int j = 0, manifoldCount = _manifoldArray.size(); j < manifoldCount; ++j)
+		{
+			btPersistentManifold* manifold = _manifoldArray[j];
+
+            // Get the direction of the contact points (used to scale normal vector in the correct direction)
+			btScalar directionSign = manifold->getBody0() == _ghostObject ? btScalar(-1.0) : btScalar(1.0);
+
+			for (int p = 0, contactCount = manifold->getNumContacts(); p < contactCount; ++p)
+			{
+				const btManifoldPoint& pt = manifold->getContactPoint(p);
+
+                // Get penetration distance for this contact point
+				btScalar dist = pt.getDistance();
+
+				if (dist < 0.0)
+				{
+					if (dist < maxPenetration)
+					{
+                        // Store collision normal for this point
+						maxPenetration = dist;
+                        _collisionNormal = pt.m_normalWorldOnB * directionSign;
+					}
+
+                    // Calculate new position for object, which is translated back along the collision normal
+					currentPosition += pt.m_normalWorldOnB * directionSign * dist * btScalar(0.2);
+					collision = true;
+				}
+			}
+			//manifold->clearManifold();
+		}
+	}
+
+    // Set the new world transformation to apply to fix the collision
+	btTransform newTransform = _ghostObject->getWorldTransform();
+	newTransform.setOrigin(currentPosition);
+	_ghostObject->setWorldTransform(newTransform);
+
+	return collision;
+}
+
+void PhysicsCharacter::debugDraw(btIDebugDraw* debugDrawer)
+{
+    // debug drawing handled by PhysicsController
+}
+
+}

+ 305 - 0
gameplay/src/PhysicsCharacter.h

@@ -0,0 +1,305 @@
+#ifndef PHYSICSCHARACTER_H_
+#define PHYSICSCHARACTER_H_
+
+#include "Node.h"
+#include "PhysicsRigidBody.h"
+#include "PhysicsMotionState.h"
+#include "Vector3.h"
+
+namespace gameplay
+{
+
+/**
+ * Physics controller class for a game character.
+ *
+ * This class can be used to control the movements and collisions of a character
+ * in a game. It interacts with the Physics system to apply gravity and handle
+ * collisions, however dynamics are not applied to the character directly by the
+ * physics system. Instead, the character's movement is controlled directly by the
+ * PhysicsCharacter class. This results in a more responsive and typical game
+ * character than would be possible if trying to move a character by applying
+ * physical simulation with forces.
+ *
+ * This class can also be used to control animations for a character. Animation
+ * clips can be setup for typical character animations, such as walk, run, jump,
+ * etc; and the controller will handle blending between these animations as needed.
+ *
+ * @todo Add support for collision listeners.
+ */
+class PhysicsCharacter : public Transform::Listener, public btActionInterface
+{
+    friend class PhysicsController;
+
+public:
+
+    /**
+     * Flags for controlling how a character animation is played back.
+     */
+    enum AnimationFlags
+    {
+        /**
+         * Plays an animation once and then stops.
+         */
+        ANIMATION_STOP,
+
+        /**
+         * Play an animation once and then resumes the previous playing animation.
+         */
+        ANIMATION_RESUME,
+
+        /**
+         * Plays an animation and repeats it indefinitely.
+         */
+         ANIMATION_REPEAT
+    };
+
+    /**
+     * Flags controlling how a character is moved.
+     */
+    enum MoveFlags
+    {
+        /**
+         * Translates the character.
+         */
+        MOVE_TRANSLATE = 1,
+
+        /**
+         * Rotates the character.
+         */
+        MOVE_ROTATE = 2
+    };
+
+    /**
+     * Returns the character node for this PhysicsCharacter.
+     *
+     * @return The character Node.
+     */
+    Node* getNode() const;
+
+    /**
+     * Returns the maximum step height for the character.
+     *
+     * @return The maximum step height.
+     */
+    float getMaxStepHeight() const;
+
+    /**
+     * Sets the maximum step height for the character.
+     *
+     * @param height The maximum step height.
+     */
+    void setMaxStepHeight(float height);
+
+    /**
+     * Returns the maximum slope angle for the character.
+     *
+     * The maximum slope angle determines the maximum angle of terrain
+     * that the character can walk on. Slopes with an angle larger
+     * than the specified angle will not allow the character to move on.
+     *
+     * @return The maximum slope angle.
+     */
+    float getMaxSlopeAngle() const;
+
+    /**
+     * Sets the maximum slope angle (in degrees).
+     *
+     * @param angle The maximum slope angle.
+     */
+    void setMaxSlopeAngle(float angle);
+
+    /**
+     * Configures a new animation for this character.
+     *
+     * This method registers an animation for the character, with an associated movement speed.
+     * The moveSpeed specifies how fast the character moves while the animation is playing.
+     * The final velocity of the character is the product of the current move velocity and
+     * the currently playing animation(s) moveSpeed.
+     *
+     * @param name Name of the animation.
+     * @param animationClip Animation clip associated with the new character animation.
+     * @param moveSpeed Base movement speed (meters per second) associated with the animation.
+     */
+    void addAnimation(const char* name, AnimationClip* animationClip, float moveSpeed);
+
+    /**
+     * Returns the animation with the specified name.
+     *
+     * @return The specified animation clip.
+     */
+    AnimationClip* getAnimation(const char* name) const;
+
+    /**
+     * Plays the specified animation.
+     *
+     * There are some limiations and considerations that should be ponited out when
+     * playing animations:
+     * <li>You should avoid playing multiple animations concurrently that have the same target.
+     * For example, two animations targetting the character's joints should not be played 
+     * concurrently, but it is fine to play one animation that targets the joints and another
+     * that targets the character's Node.
+     * <li>When playing an animation that targets the transform of the character's Node
+     * (such as a motion path animation), the character's velocity vector should be set to
+     * Vector3::zero() so that the PhysicsCharacter stops applying motion directly
+     * and instead relies on the motion animation to control the character.
+     *
+     * The optional animation layer can be used to group animations on separate layers.
+     * Each animation layer can have at most one active animation. Playing multiple
+     * animations concurrently can be achieved by putting the different animations
+     * on separate layers. For example, a motion path animation that targets the
+     * character's Node can be put on one layer, while a running animation that targets
+     * a character's Joints can be put on a separate layer. This allows a character's
+     * movement to be animated at the same time as the run animation is playing.
+     *
+     * @param name Animation name, or NULL to stop all character animations on the given layer.
+     * @param flags Animation flags from the AnimationFlags enumeration.
+     * @param speed Optional animation speed (default is 1.0).
+     * @param blendDuration Optional number of milliseconds to crossfade between the
+     *      currently playing animation on the given layer and the new animation.
+     * @param layer Optional animation layer.
+     */
+    void play(const char* name, AnimationFlags flags, float animationSpeed = 1.0f, unsigned int blendDuration = 0, unsigned int layer = 0);
+
+    /**
+     * Sets the velocity of the character.
+     *
+     * Calling this function sets the velocity (speed and direction) for the character.
+     * The velocity is maintained until this method is called again. The final velocity
+     * of the character is determined by product of the current velocity vector(s)
+     * and the current character animation's move speed. Therefore, specifying a
+     * normalized (unit-length) velocity vector results in the character speed being
+     * controled entirely by the current animation's velocity; whereas the speed of
+     * the character can be augmented by modifying the magnitude of the velocity vector.
+     *
+     * Note that a zero velocity vector and/or a zero animation move speed will
+     * result in no character movement (the character will be stationary). A zero
+     * velocity vector should be used when playing an animation that targets the
+     * character's transform directly (such as a motion path animation), since these
+     * animations will overwrite any transformations on the character's node.
+     *
+     * @param velocity Movement velocity.
+     * @param flags Optional bitwise combination of MoveFlags constants.
+     */
+    void setVelocity(const Vector3& velocity, unsigned int flags = MOVE_TRANSLATE | MOVE_ROTATE);
+
+    /**
+     * Moves the character forward with the given velocity vector.
+     *
+     * The forward velocity is defined by the character's current orientation
+     * (it is the forward vector from the character's current world transform).
+     *
+     * The specified velocity acts as a multiplier on the currently playing animation's
+     * velocity (or, if there is no animation playing, it directly impacts velocity).
+     *
+     * Note that a negative velocity (i.e. -1.0f) will move the character backwards.
+     *
+     * @param velocity Optional velocity modifier.
+     */
+    void setForwardVelocity(float velocity = 1.0f);
+
+    /**
+     * Moves the character right with the given velocity vector.
+     *
+     * The right velocity is defined by the character's current orientation
+     * (it is the right vector from the character's current world transform).
+     *
+     * The specified velocity acts as a multiplier on the currently playing animation's
+     * velocity (or, if there is no animation playing, it directly impacts velocity).
+     *
+     * Note that a negative velocity (i.e. -1.0f) will move the character left.
+     *
+     * @param velocity Optional velocity modifier.
+     */
+    void setRightVelocity(float velocity = 1.0f);
+
+    /**
+     * Causes the character to jump with the specified initial upwards velocity.
+     *
+     * @param velocity Initial jump velocity.
+     */
+    void jump(float height);
+
+    /**
+     * @see Transform::Listener::transformChanged
+     */
+    void transformChanged(Transform* transform, long cookie);
+
+    /**
+     * @see btActionInterface::updateAction
+     */
+    void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep);
+
+    /**
+     * @see btActionInterface::debugDraw
+     */
+	void debugDraw(btIDebugDraw* debugDrawer);
+
+private:
+
+    struct CharacterAnimation
+    {
+        const char* name;
+        AnimationClip* clip;
+        float moveSpeed;
+        unsigned int layer;
+        bool playing;
+        AnimationFlags animationFlags;
+        unsigned int blendDuration;
+        CharacterAnimation* prev;
+    };
+
+    /**
+     * Creates a new PhysicsCharacter.
+     *
+     * @param node Scene node that represents the character.
+     * @param radius Radius of capsule volume used for character collisions.
+     * @param height Height of the capsule volume used for character collisions.
+     * @param center Center point of the capsule volume for the character.
+     */
+    PhysicsCharacter(Node* node, float radius, float height, const Vector3 center = Vector3::zero());
+
+    /**
+     * Destructor.
+     */
+    virtual ~PhysicsCharacter();
+
+    void updateCurrentVelocity();
+
+    void play(CharacterAnimation* animation, unsigned int layer);
+
+    void stepUp(btCollisionWorld* collisionWorld, btScalar time);
+
+    void stepDown(btCollisionWorld* collisionWorld, btScalar time);
+
+    void stepForwardAndStrafe(btCollisionWorld* collisionWorld, float time);
+
+    void updateTargetPositionFromCollision(btVector3& targetPosition, const btVector3& collisionNormal);
+
+    bool fixCollision(btCollisionWorld* world);
+
+    Node* _node;
+    PhysicsMotionState* _motionState;
+    btVector3 _moveVelocity;
+    float _forwardVelocity;
+    float _rightVelocity;
+    btVector3 _fallVelocity;
+    btVector3 _currentVelocity;
+    btVector3 _normalizedVelocity;
+    unsigned int _moveFlags;
+    bool _colliding;
+    btVector3 _collisionNormal;
+    btVector3 _currentPosition;
+    std::map<const char*, CharacterAnimation> _animations;
+    std::map<unsigned int, CharacterAnimation*> _layers;
+    btPairCachingGhostObject* _ghostObject;
+    btConvexShape* _collisionShape;
+    btManifoldArray	_manifoldArray;
+    int _ignoreTransformChanged;
+    float _stepHeight;
+    float _slopeAngle;
+    float _cosSlopeAngle;
+};
+
+}
+
+#endif