Преглед изворни кода

Adds support for vehicle physics #171. Racing sample to follow. Lua bindings need to be generated for new files.

Ken Whatmough пре 13 година
родитељ
комит
fc1b3da872

+ 4 - 0
gameplay/gameplay.vcxproj

@@ -241,6 +241,8 @@
     <ClCompile Include="src\PhysicsRigidBody.cpp" />
     <ClCompile Include="src\PhysicsSocketConstraint.cpp" />
     <ClCompile Include="src\PhysicsSpringConstraint.cpp" />
+    <ClCompile Include="src\PhysicsVehicle.cpp" />
+    <ClCompile Include="src\PhysicsVehicleWheel.cpp" />
     <ClCompile Include="src\Plane.cpp" />
     <ClCompile Include="src\PlatformAndroid.cpp" />
     <ClCompile Include="src\PlatformLinux.cpp" />
@@ -504,6 +506,8 @@
     <ClInclude Include="src\PhysicsRigidBody.h" />
     <ClInclude Include="src\PhysicsSocketConstraint.h" />
     <ClInclude Include="src\PhysicsSpringConstraint.h" />
+    <ClInclude Include="src\PhysicsVehicle.h" />
+    <ClInclude Include="src\PhysicsVehicleWheel.h" />
     <ClInclude Include="src\Plane.h" />
     <ClInclude Include="src\Platform.h" />
     <ClInclude Include="src\Properties.h" />

+ 12 - 0
gameplay/gameplay.vcxproj.filters

@@ -792,6 +792,12 @@
     <ClCompile Include="src\lua\lua_GestureGestureEvent.cpp">
       <Filter>lua</Filter>
     </ClCompile>
+    <ClCompile Include="src\PhysicsVehicle.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\PhysicsVehicleWheel.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -1574,6 +1580,12 @@
     <ClInclude Include="src\lua\lua_GestureGestureEvent.h">
       <Filter>lua</Filter>
     </ClInclude>
+    <ClInclude Include="src\PhysicsVehicleWheel.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\PhysicsVehicle.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\gameplay-main-macosx.mm">

+ 55 - 0
gameplay/src/Node.cpp

@@ -4,6 +4,8 @@
 #include "Scene.h"
 #include "Joint.h"
 #include "PhysicsRigidBody.h"
+#include "PhysicsVehicle.h"
+#include "PhysicsVehicleWheel.h"
 #include "PhysicsGhostObject.h"
 #include "PhysicsCharacter.h"
 #include "Game.h"
@@ -1034,6 +1036,27 @@ PhysicsCollisionObject* Node::setCollisionObject(PhysicsCollisionObject::Type ty
         }
         break;
 
+    case PhysicsCollisionObject::VEHICLE:
+        {
+            _collisionObject = new PhysicsVehicle(this, shape, rigidBodyParameters ? *rigidBodyParameters : PhysicsRigidBody::Parameters());
+        }
+        break;
+
+    case PhysicsCollisionObject::VEHICLE_WHEEL:
+        {
+            //
+            // PhysicsVehicleWheel is special because this call will traverse up the scene graph for the
+            // first ancestor node that is shared with another node of collision type VEHICLE, and then
+            // proceed to add itself as a wheel onto that vehicle. This is by design, and allows the
+            // visual scene hierarchy to be the sole representation of the relationship between physics
+            // objects rather than forcing that upon the otherwise-flat ".physics" (properties) file.
+            //
+            // IMPORTANT: The VEHICLE must come before the VEHICLE_WHEEL in the ".scene" (properties) file!
+            //
+            _collisionObject = new PhysicsVehicleWheel(this, shape, rigidBodyParameters ? *rigidBodyParameters : PhysicsRigidBody::Parameters());
+        }
+        break;
+
     case PhysicsCollisionObject::NONE:
         break;  // Already deleted, Just don't add a new collision object back.
     }
@@ -1082,6 +1105,23 @@ PhysicsCollisionObject* Node::setCollisionObject(Properties* properties)
         {
             _collisionObject = PhysicsRigidBody::create(this, properties);
         }
+        else if (strcmp(type, "VEHICLE") == 0)
+        {
+            _collisionObject = PhysicsVehicle::create(this, properties);
+        }
+        else if (strcmp(type, "VEHICLE_WHEEL") == 0)
+        {
+            //
+            // PhysicsVehicleWheel is special because this call will traverse up the scene graph for the
+            // first ancestor node that is shared with another node of collision type VEHICLE, and then
+            // proceed to add itself as a wheel onto that vehicle. This is by design, and allows the
+            // visual scene hierarchy to be the sole representation of the relationship between physics
+            // objects rather than forcing that upon the otherwise-flat ".physics" (properties) file.
+            //
+            // IMPORTANT: The VEHICLE must come before the VEHICLE_WHEEL in the ".scene" (properties) file!
+            //
+            _collisionObject = PhysicsVehicleWheel::create(this, properties);
+        }
         else
         {
             GP_ERROR("Unsupported collision object type '%s'.", type);
@@ -1097,6 +1137,21 @@ PhysicsCollisionObject* Node::setCollisionObject(Properties* properties)
     return _collisionObject;
 }
 
+unsigned int Node::getNumAdvertisedDescendants() const
+{
+    return _advertisedDescendants.size();
+}
+
+Node* Node::getAdvertisedDescendant(unsigned int i) const
+{
+    return _advertisedDescendants.at(i);
+}
+
+void Node::addAdvertisedDescendant(Node* node)
+{
+    _advertisedDescendants.push_back(node);
+}
+
 AIAgent* Node::getAgent() const
 {
     return _agent;

+ 52 - 5
gameplay/src/Node.h

@@ -486,8 +486,8 @@ public:
     /**
      * Sets (or disables) the physics collision object for this node.
      *
-     * The supported collision object types include rigid bodies, ghost objects and 
-     * characters.
+     * The supported collision object types include rigid bodies, ghost objects, 
+     * characters, vehicles, and vehicle wheels.
      *
      * Rigid bodies are used to represent most physical objects in a game. The important
      * feature of rigid bodies is that they can be simulated by the physics system as other
@@ -496,6 +496,9 @@ public:
      * define their physical features. These parameters can be passed into the
      * 'rigidBodyParameters' parameter.
      *
+     * Vehicles consist of a rigid body with wheels. The rigid body parameters can be passed-in
+     * via the 'rigidBodyParameters' parameter, and wheels can be added to the vehicle.
+     *
      * Ghost objects are a simple type of collision object that are not simulated. By default
      * they pass through other objects in the scene without affecting them. Ghost objects do
      * receive collision events however, which makes them useful for representing non-simulated
@@ -516,9 +519,10 @@ public:
      * @param shape Definition of a physics collision shape to be used for this collision object.
      *        Use the static shape methods on the PhysicsCollisionShape class to specificy a shape
      *        definition, such as PhysicsCollisionShape::box().
-     * @param rigidBodyParameters If type is PhysicsCollisionObject::RIGID_BODY, this
-     *        must point to a valid rigid body parameters object containing information
-     *        about the rigid body; otherwise, this parmater may be NULL.
+     * @param rigidBodyParameters If type is PhysicsCollisionObject::RIGID_BODY or
+     *        PhysicsCollisionObject::VEHICLE, this must point to a valid rigid body
+     *        parameters object containing information about the rigid body;
+     *        otherwise, this parmater may be NULL.
      */
     PhysicsCollisionObject* setCollisionObject(PhysicsCollisionObject::Type type, const PhysicsCollisionShape::Definition& shape = PhysicsCollisionShape::box(), 
                                                PhysicsRigidBody::Parameters* rigidBodyParameters = NULL);
@@ -539,6 +543,41 @@ public:
      */
     PhysicsCollisionObject* setCollisionObject(Properties* properties);
 
+    /**
+     * Returns the number of advertised descendants held in this node.
+     *
+     * Descendant nodes can advertise themselves to others using this
+     * mechanism, such as how the wheels are bound to a physics vehicle
+     * via their common ancestor.
+     *
+     * @return the number of advertised descendants held in this node.
+     */
+    unsigned int getNumAdvertisedDescendants() const;
+
+    /**
+     * Returns the advertised descendant at the specified index.
+     *
+     * Descendant nodes can advertise themselves to others using this
+     * mechanism, such as how the wheels are bound to a physics vehicle
+     * via their common ancestor.
+     *
+     * @param i the index to look-up.
+     *
+     * @return the advertised descendant at the specified index.
+     */
+    Node* getAdvertisedDescendant(unsigned int i) const;
+
+    /**
+     * Adds the specified node to the list of advertised descendants.
+     *
+     * Descendant nodes can advertise themselves to others using this
+     * mechanism, such as how the wheels are bound to a physics vehicle
+     * via their common ancestor.
+     *
+     * @param node the node reference to add.
+     */
+    void addAdvertisedDescendant(Node* node);
+
     /**
      * Returns the AI agent assigned to this node.
      *
@@ -779,6 +818,14 @@ protected:
      * Pointer to custom UserData and cleanup call back that can be stored in a Node.
      */
     UserData* _userData;
+
+    /**
+     * A linear collection of descendants who wish to advertise themselves, typically
+     * to other descendants. This allows nodes of common ancestry to bond. One example
+     * of this is a physics vehicle and its wheels, which are associated via their
+     * lowest common ancestor.
+     */
+    std::vector<Node*> _advertisedDescendants;
 };
 
 /**

+ 10 - 0
gameplay/src/PhysicsCollisionObject.h

@@ -39,6 +39,16 @@ public:
          */
         GHOST_OBJECT,
 
+        /** 
+         * PhysicsVehicle type.
+         */
+        VEHICLE,
+
+        /** 
+         * PhysicsVehicleWheel type.
+         */
+        VEHICLE_WHEEL,
+
         /**
          * No collision object.
          */

+ 1 - 0
gameplay/src/PhysicsController.h

@@ -25,6 +25,7 @@ class PhysicsController : public ScriptTarget
     friend class PhysicsConstraint;
     friend class PhysicsRigidBody;
     friend class PhysicsCharacter;
+    friend class PhysicsVehicle;
     friend class PhysicsCollisionObject;
     friend class PhysicsGhostObject;
 

+ 3 - 3
gameplay/src/PhysicsRigidBody.cpp

@@ -151,7 +151,7 @@ void PhysicsRigidBody::applyTorqueImpulse(const Vector3& torque)
     }
 }
 
-PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
+PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties, const char* nspace)
 {
     // Check if the properties is valid and has a valid namespace.
     if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
@@ -167,9 +167,9 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
         GP_ERROR("Failed to load physics rigid body from properties object; required attribute 'type' is missing.");
         return NULL;
     }
-    if (strcmp(type, "RIGID_BODY") != 0)
+    if (strcmp(type, nspace) != 0)
     {
-        GP_ERROR("Failed to load physics rigid body from properties object; attribute 'type' must be equal to 'RIGID_BODY'.");
+        GP_ERROR("Failed to load physics rigid body from properties object; attribute 'type' must be equal to '%s'.", nspace);
         return NULL;
     }
 

+ 5 - 2
gameplay/src/PhysicsRigidBody.h

@@ -20,6 +20,8 @@ class PhysicsRigidBody : public PhysicsCollisionObject, public Transform::Listen
 {
     friend class Node;
     friend class PhysicsCharacter;
+    friend class PhysicsVehicle;
+    friend class PhysicsVehicleWheel;
     friend class PhysicsConstraint;
     friend class PhysicsController;
     friend class PhysicsFixedConstraint;
@@ -344,10 +346,11 @@ private:
      * 
      * @param node The node to create a rigid body for; note that the node must have
      *      a model attached to it prior to creating a rigid body for it.
-     * @param properties The properties object defining the rigid body (must have namespace equal to 'rigidBody').
+     * @param properties The properties object defining the rigid body.
+     * @param namespace The namespace expected (default is "RIGID_BODY").
      * @return The newly created rigid body, or <code>NULL</code> if the rigid body failed to load.
      */
-    static PhysicsRigidBody* create(Node* node, Properties* properties);
+    static PhysicsRigidBody* create(Node* node, Properties* properties, const char* nspace = "RIGID_BODY");
 
     // Adds a constraint to this rigid body.
     void addConstraint(PhysicsConstraint* constraint);

+ 257 - 0
gameplay/src/PhysicsVehicle.cpp

@@ -0,0 +1,257 @@
+#include "Base.h"
+#include "Game.h" // for access to physics controller
+#include "Node.h"
+#include "PhysicsVehicle.h"
+#include "PhysicsVehicleWheel.h"
+//TODO: move inline implementations into a separate INL file
+
+namespace gameplay
+{
+
+//
+// The default vehicle raycaster in Bullet currently does not filter out the vehicle's own
+// rigid body from the ray test which can result in unexpected behavior. These implementations
+// are intended to fix that.
+//
+class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
+{
+private:
+    btCollisionObject* _me;
+
+public:
+    ClosestNotMeRayResultCallback(const btVector3& from, const btVector3& to, btCollisionObject* me)
+        : btCollisionWorld::ClosestRayResultCallback(from, to), _me(me)
+    {
+    }
+
+    btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
+    {
+        if (rayResult.m_collisionObject == _me)
+            return 1.0f;
+
+        return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
+    }
+};
+
+class VehicleNotMeRaycaster : public btVehicleRaycaster
+{
+    btDynamicsWorld* _dynamicsWorld;
+    btCollisionObject* _me;
+
+public:
+    VehicleNotMeRaycaster(btDynamicsWorld* world, btCollisionObject* me)
+        : _dynamicsWorld(world), _me(me)
+    {
+    }
+
+    void* castRay(const btVector3& from, const btVector3& to, btVehicleRaycasterResult& result)
+    {
+        ClosestNotMeRayResultCallback rayCallback(from, to, _me);
+
+        _dynamicsWorld->rayTest(from, to, rayCallback);
+
+        if (rayCallback.hasHit())
+        {
+            const btRigidBody* body = btRigidBody::upcast(rayCallback.m_collisionObject);
+            if (body && body->hasContactResponse())
+            {
+                result.m_hitPointInWorld = rayCallback.m_hitPointWorld;
+                result.m_hitNormalInWorld = rayCallback.m_hitNormalWorld;
+                result.m_hitNormalInWorld.normalize();
+                result.m_distFraction = rayCallback.m_closestHitFraction;
+                return (void*)body;
+            }
+        }
+        return 0;
+    }
+};
+
+PhysicsVehicle::PhysicsVehicle(Node* node, const PhysicsCollisionShape::Definition& shape, const PhysicsRigidBody::Parameters& parameters)
+    : PhysicsCollisionObject(node)
+{
+    //
+    // Note that the constructor for PhysicsRigidBody calls addCollisionObject and so
+    // that is where the rigid body gets added to the dynamics world.
+    //
+    _rigidBody = new PhysicsRigidBody(node, shape, parameters);
+
+    initialize();
+}
+
+PhysicsVehicle::PhysicsVehicle(Node* node, PhysicsRigidBody* rigidBody)
+    : PhysicsCollisionObject(node)
+{
+    _rigidBody = rigidBody;
+
+    initialize();
+}
+
+PhysicsVehicle* PhysicsVehicle::create(Node* node, Properties* properties)
+{
+    //
+    // Note that the constructor for PhysicsRigidBody calls addCollisionObject and so
+    // that is where the rigid body gets added to the dynamics world.
+    //
+    PhysicsRigidBody* rigidBody = PhysicsRigidBody::create(node, properties, "VEHICLE");
+
+    PhysicsVehicle* vehicle = new PhysicsVehicle(node, rigidBody);
+
+    // Load the defined vehicle parameters.
+    properties->rewind();
+    const char* name;
+    while ((name = properties->getNextProperty()) != NULL)
+    {
+        if (strcmp(name, "steeringGain") == 0)
+        {
+            vehicle->setSteeringGain(properties->getFloat());
+        }
+        else if (strcmp(name, "brakingForce") == 0)
+        {
+            vehicle->setBrakingForce(properties->getFloat());
+        }
+        else if (strcmp(name, "drivingForce") == 0)
+        {
+            vehicle->setDrivingForce(properties->getFloat());
+        }
+        else
+        {
+            // Ignore this case (we've already parsed the rigid body parameters).
+        }
+    }
+
+    return vehicle;
+}
+
+void PhysicsVehicle::initialize()
+{
+    GP_ASSERT(getNode());
+
+    // Safe default values
+    setSteeringGain(0.5f);
+    setBrakingForce(350.0f);
+    setDrivingForce(2000.0f);
+
+    // Create the vehicle and add it to world
+    btRigidBody* body = static_cast<btRigidBody*>(_rigidBody->getCollisionObject());
+    btDynamicsWorld* dynamicsWorld = Game::getInstance()->getPhysicsController()->_world;
+    _vehicleRaycaster = new VehicleNotMeRaycaster(dynamicsWorld, body);
+    _vehicle = new btRaycastVehicle(_vehicleTuning, body, _vehicleRaycaster);
+    body->setActivationState(DISABLE_DEACTIVATION);
+    dynamicsWorld->addVehicle(_vehicle);
+    _vehicle->setCoordinateSystem(0, 1, 2);
+
+    // Advertise self among ancestor nodes so that wheels can bind to self.
+    // (See PhysicsVehicleWheel and Node for more details).
+    for (Node* n = getNode()->getParent(); n; n = n->getParent())
+    {
+        n->addAdvertisedDescendant(getNode());
+    }
+}
+
+PhysicsVehicle::~PhysicsVehicle()
+{
+    //
+    // Note that the destructor for PhysicsRigidBody calls removeCollisionObject and so
+    // that is where the rigid body gets removed from the dynamics world. The vehicle
+    // itself is just an action interface in the dynamics world.
+    //
+
+    SAFE_DELETE(_vehicle);
+    SAFE_DELETE(_vehicleRaycaster);
+    SAFE_DELETE(_rigidBody);
+}
+
+btCollisionObject* PhysicsVehicle::getCollisionObject() const
+{
+    GP_ASSERT(_rigidBody);
+
+    return _rigidBody->getCollisionObject();
+}
+
+PhysicsCollisionObject::Type PhysicsVehicle::getType() const
+{
+    return PhysicsCollisionObject::VEHICLE;
+}
+
+PhysicsRigidBody* PhysicsVehicle::getRigidBody() const
+{
+    GP_ASSERT(_rigidBody);
+
+    return _rigidBody;
+}
+
+unsigned int PhysicsVehicle::getNumWheels() const
+{
+    return _wheels.size();
+}
+
+PhysicsVehicleWheel* PhysicsVehicle::getWheel(unsigned int i)
+{
+    return _wheels.at(i);
+}
+
+void PhysicsVehicle::addWheel(PhysicsVehicleWheel* wheel)
+{
+    unsigned int i = _wheels.size();
+    _wheels.push_back(wheel);
+    wheel->setHost(this, i);
+    wheel->addToVehicle(_vehicle);
+}
+
+float PhysicsVehicle::getSpeedKph() const
+{
+    return _vehicle->getCurrentSpeedKmHour();
+}
+
+void PhysicsVehicle::update(float steering, float braking, float driving)
+{
+    PhysicsVehicleWheel* wheel;
+    for (int i = 0; i < _vehicle->getNumWheels(); i++)
+    {
+        wheel = getWheel(i);
+
+        if (wheel->isFront())
+        {
+            _vehicle->setSteeringValue(steering * _steeringGain, i);
+        }
+        else
+        {
+            _vehicle->applyEngineForce(driving * _drivingForce, i);
+            _vehicle->setBrake(braking * _brakingForce, i);
+        }
+
+        wheel->transform(wheel->getNode());
+    }
+}
+
+float PhysicsVehicle::getSteeringGain() const
+{
+    return _steeringGain;
+}
+
+void PhysicsVehicle::setSteeringGain(float steeringGain)
+{
+    _steeringGain = steeringGain;
+}
+
+float PhysicsVehicle::getBrakingForce() const
+{
+    return _brakingForce;
+}
+
+void PhysicsVehicle::setBrakingForce(float brakingForce)
+{
+    _brakingForce = brakingForce;
+}
+
+float PhysicsVehicle::getDrivingForce() const
+{
+    return _drivingForce;
+}
+
+void PhysicsVehicle::setDrivingForce(float drivingForce)
+{
+    _drivingForce = drivingForce;
+}
+
+}

+ 205 - 0
gameplay/src/PhysicsVehicle.h

@@ -0,0 +1,205 @@
+#ifndef PHYSICSVEHICLE_H_
+#define PHYSICSVEHICLE_H_
+
+#include "PhysicsCollisionObject.h"
+#include "PhysicsRigidBody.h"
+
+namespace gameplay
+{
+
+class Node;
+class PhysicsVehicleWheel;
+
+/**
+ * Defines a class for vehicle physics.
+ *
+ * In addition to its own properties defined below, a vehicle has available
+ * to it all of the properties of a rigid body such as shape, mass, friction,
+ * etc which correspond to the vehicle body:
+
+ @verbatim
+    collisionObject <vehicleID>
+    {
+        type           = VEHICLE
+
+        shape          = BOX        // collision shape for vehicle body
+        mass           = <float>    // mass of vehicle body
+        friction       = <float>    // friction of vehicle body
+        restitution    = <float>    // restitution of vehicle body
+        linearDamping  = <float>    // linear damping of vehicle body
+        angularDamping = <float>    // angular damping of vehicle body
+
+        // Vehicle steering, braking, and powertrain
+        steeringGain   = <float>    // steering at full deflection
+        brakingForce   = <float>    // braking force at full braking
+        drivingForce   = <float>    // driving force at full throttle
+    }
+ @endverbatim
+ */
+class PhysicsVehicle : public PhysicsCollisionObject
+{
+    friend class Node;
+    friend class PhysicsVehicleWheel;
+
+public:
+
+    /**
+     * @see PhysicsCollisionObject#getType
+     */
+    PhysicsCollisionObject::Type getType() const;
+
+    /**
+     * Returns the rigid body associated with this vehicle.
+     */
+    /*TODO: inline*/ PhysicsRigidBody* getRigidBody() const;
+
+    /**
+     * Returns the number of wheels on this vehicle.
+     *
+     * @return the number of wheels on this vehicle.
+     */
+    /*TODO: inline*/ unsigned int getNumWheels() const;
+
+    /**
+     * Gets the wheel at the specified index.
+     * 
+     * @param i index of wheel.
+     * @return the wheel at the specified index.
+     */
+    /*TODO: inline*/ PhysicsVehicleWheel* getWheel(unsigned int i);
+
+    /**
+     * Permanently adds a wheel to this vehicle.
+     *
+     * @param wheel the wheel to add.
+     */
+    void addWheel(PhysicsVehicleWheel* wheel);
+
+    /**
+     * Returns an indication of vehicle speed in kilometers per hour.
+     */
+    /*TODO: inline*/ float getSpeedKph() const;
+
+    /**
+     * Updates the vehicle state using the specified normalized command
+     * inputs, and updates the transform on the visual node for each wheel.
+     *
+     * @param steering steering command (-1 to 1).
+     * @param braking braking command (0 to 1).
+     * @param driving net drivetrain command (0 to 1).
+     */
+    void update(float steering, float braking, float driving);
+
+    /**
+     * Gets steering gain at full deflection.
+     *
+     * @return steering gain at full deflection.
+     */
+    /*TODO: inline*/ float getSteeringGain() const;
+
+    /**
+     * Sets steering gain at full deflection.
+     *
+     * @param steeringGain steering gain at full deflection.
+     */
+    /*TODO: inline*/ void setSteeringGain(float steeringGain);
+
+    /**
+     * Gets braking force at full braking.
+     *
+     * @return braking force at full braking.
+     */
+    /*TODO: inline*/ float getBrakingForce() const;
+
+    /**
+     * Sets braking force at full braking.
+     *
+     * @param brakingForce braking force at full braking.
+     */
+    /*TODO: inline*/ void setBrakingForce(float brakingForce);
+
+    /**
+     * Gets driving force at full throttle.
+     *
+     * @return driving force at full throttle.
+     */
+    /*TODO: inline*/ float getDrivingForce() const;
+
+    /**
+     * Sets driving force at full throttle.
+     *
+     * @param drivingForce driving force at full throttle.
+     */
+    /*TODO: inline*/ void setDrivingForce(float drivingForce);
+
+protected:
+
+    /**
+     * @see PhysicsCollisionObject::getCollisionObject
+     */
+    btCollisionObject* getCollisionObject() const;
+
+private:
+
+    /**
+     * Creates a vehicle based on the specified rigid body parameters and some 'safe' defaults.
+     * 
+     * @param node The node to create a rigid body for; note that the node must have
+     *      a model attached to it prior to creating a rigid body for it.
+     * @param shape The rigid body shape construction information.
+     * @param parameters The rigid body construction parameters.
+     */
+    PhysicsVehicle(Node* node, const PhysicsCollisionShape::Definition& shape, const PhysicsRigidBody::Parameters& parameters);
+
+    /**
+     * Creates a vehicle based on the given rigid body and some 'safe' defaults.
+     * 
+     * @param node The node to create a rigid body for; note that the node must have
+     *      a model attached to it prior to creating a rigid body for it.
+     * @param rigidBody The rigid body.
+     */
+    PhysicsVehicle(Node* node, PhysicsRigidBody* rigidBody);
+
+    /**
+     * Private copy constructor to prevent copying.
+     */
+    PhysicsVehicle(const PhysicsVehicle& src);
+
+    /**
+     * Private copy assignment operator.
+     */
+    PhysicsVehicle& operator=(const PhysicsVehicle&);
+
+    /**
+     * Creates a vehicle physics object from the specified properties object.
+     * 
+     * @param node The node to create a vehicle for; note that the node must have
+     *      a model attached to it prior to creating a vehicle for it.
+     * @param properties The properties object defining the vehicle (must have type equal to 'VEHICLE').
+     * @return The newly created vehicle, or <code>NULL</code> if the vehicle failed to load.
+     */
+    static PhysicsVehicle* create(Node* node, Properties* properties);
+
+    /**
+     * Initializes this vehicle and advertises itself among its ancestor nodes.
+     */
+    void initialize();
+
+    /**
+     * Destructor.
+     */
+    ~PhysicsVehicle();
+
+    float _steeringGain;
+    float _brakingForce;
+    float _drivingForce;
+    PhysicsRigidBody* _rigidBody;
+    btRaycastVehicle::btVehicleTuning _vehicleTuning;
+    btVehicleRaycaster* _vehicleRaycaster;
+    btRaycastVehicle* _vehicle;
+    std::vector<PhysicsVehicleWheel*> _wheels;
+};
+
+}
+
+#endif /* PHYSICSVEHICLE_H_ */

+ 417 - 0
gameplay/src/PhysicsVehicleWheel.cpp

@@ -0,0 +1,417 @@
+#include "Base.h"
+#include "Node.h"
+#include "PhysicsVehicle.h"
+#include "PhysicsVehicleWheel.h"
+//TODO: move inline implementations into a separate INL file
+
+namespace gameplay
+{
+
+PhysicsVehicleWheel::PhysicsVehicleWheel(Node* node, const PhysicsCollisionShape::Definition& shape, const PhysicsRigidBody::Parameters& parameters)
+    : PhysicsCollisionObject(node)
+{
+    //
+    // Note that the constructor for PhysicsRigidBody calls addCollisionObject and so
+    // that is where the rigid body gets added to the dynamics world.
+    //
+    _rigidBody = new PhysicsRigidBody(node, shape, parameters);
+
+    findAncestorAndBind();
+}
+
+PhysicsVehicleWheel::PhysicsVehicleWheel(Node* node, PhysicsRigidBody* rigidBody)
+    : PhysicsCollisionObject(node)
+{
+    _rigidBody = rigidBody;
+
+    findAncestorAndBind();
+}
+
+PhysicsVehicleWheel* PhysicsVehicleWheel::create(Node* node, Properties* properties)
+{
+    //
+    // Note that the constructor for PhysicsRigidBody calls addCollisionObject and so
+    // that is where the rigid body gets added to the dynamics world.
+    //
+    PhysicsRigidBody* rigidBody = PhysicsRigidBody::create(node, properties, "VEHICLE_WHEEL");
+
+    PhysicsVehicleWheel* wheel = new PhysicsVehicleWheel(node, rigidBody);
+
+    // Load the defined wheel parameters.
+    properties->rewind();
+    Vector3 v;
+    const char* name;
+    while ((name = properties->getNextProperty()) != NULL)
+    {
+        if (strcmp(name, "isFront") == 0)
+        {
+            wheel->setFront(properties->getBool(name));
+        }
+        else if (strcmp(name, "wheelDirection") == 0 && properties->getVector3(name, &v))
+        {
+            wheel->setWheelDirection(v);
+        }
+        else if (strcmp(name, "wheelAxle") == 0 && properties->getVector3(name, &v))
+        {
+            wheel->setWheelAxle(v);
+        }
+        else if (strcmp(name, "strutConnectionPoint") == 0 && properties->getVector3(name, &v))
+        {
+            wheel->setStrutConnectionPoint(v);
+        }
+        else if (strcmp(name, "strutRestLength") == 0)
+        {
+            wheel->setStrutRestLength(properties->getFloat(name));
+        }
+        else if (strcmp(name, "strutTravelMax") == 0)
+        {
+            wheel->setStrutTravelMax(properties->getFloat(name));
+        }
+        else if (strcmp(name, "strutStiffness") == 0)
+        {
+            wheel->setStrutStiffness(properties->getFloat(name));
+        }
+        else if (strcmp(name, "strutDampingCompression") == 0)
+        {
+            wheel->setStrutDampingCompression(properties->getFloat(name));
+        }
+        else if (strcmp(name, "strutDampingRelaxation") == 0)
+        {
+            wheel->setStrutDampingRelaxation(properties->getFloat(name));
+        }
+        else if (strcmp(name, "strutForceMax") == 0)
+        {
+            wheel->setStrutForceMax(properties->getFloat(name));
+        }
+        else if (strcmp(name, "frictionBreakout") == 0)
+        {
+            wheel->setFrictionBreakout(properties->getFloat(name));
+        }
+        else if (strcmp(name, "wheelRadius") == 0)
+        {
+            wheel->setWheelRadius(properties->getFloat(name));
+        }
+        else if (strcmp(name, "rollInfluence") == 0)
+        {
+            wheel->setRollInfluence(properties->getFloat(name));
+        }
+        else
+        {
+            // Ignore this case (we've already parsed the rigid body parameters).
+        }
+    }
+
+    return wheel;
+}
+
+PhysicsVehicleWheel::~PhysicsVehicleWheel()
+{
+    SAFE_DELETE(_rigidBody);
+}
+
+btCollisionObject* PhysicsVehicleWheel::getCollisionObject() const
+{
+    GP_ASSERT(_rigidBody);
+
+    return _rigidBody->getCollisionObject();
+}
+
+PhysicsCollisionObject::Type PhysicsVehicleWheel::getType() const
+{
+    return PhysicsCollisionObject::VEHICLE_WHEEL;
+}
+
+void PhysicsVehicleWheel::findAncestorAndBind()
+{
+    GP_ASSERT(getNode());
+
+    //
+    // This is not an efficient algorithm if the number of advertised
+    // descendants gets large. In fact, this search is O(n*m) in the
+    // worst case with n nodes and m advertised descendants per node.
+    // But (1) we are only visiting ancestor nodes, and (2) the number
+    // of advertised descendants is expected to be small since this
+    // mechanism is currently only used for binding wheels onto a
+    // vehicle.
+    //TODO: revisit if the advertised descendants mechanism becomes popular.
+    //
+    PhysicsVehicle* host = NULL;
+    PhysicsCollisionObject* collisionObject;
+    Node* m;
+    for (Node* n = getNode()->getParent(); n && !host; n = n->getParent())
+    {
+        for (unsigned int i = 0; i < n->getNumAdvertisedDescendants() && !host; i++)
+        {
+            m = n->getAdvertisedDescendant(i);
+
+            collisionObject = m->getCollisionObject();
+            if (collisionObject && collisionObject->getType() == PhysicsCollisionObject::VEHICLE)
+            {
+                host = static_cast<PhysicsVehicle*>(collisionObject);
+            }
+        }
+    }
+
+    // Note: Currently this method is silent on failure to find a host.
+    if (host)
+    {
+        host->addWheel(this);
+    }
+}
+
+void PhysicsVehicleWheel::setHost(PhysicsVehicle* host, unsigned int indexInHost)
+{
+    _host = host;
+    _indexInHost = indexInHost;
+}
+
+void PhysicsVehicleWheel::addToVehicle(btRaycastVehicle* vehicle)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->getNumWheels() == vehicle->getNumWheels() + 1);
+
+    // Use safe defaults for now. Properties are assigned elsewhere.
+    btRaycastVehicle::btVehicleTuning tuning;
+    vehicle->addWheel(
+        btVector3(0, 0, 0),
+        btVector3(0, -1, 0),
+        btVector3(-1, 0, 0),
+        0.6f,
+        0.5f,
+        tuning,
+        false);
+}
+
+void PhysicsVehicleWheel::transform(Node* node) const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    const btTransform& trans = _host->_vehicle->getWheelInfo(_indexInHost).m_worldTransform;
+    const btQuaternion& rot = trans.getRotation();
+    const btVector3& pos = trans.getOrigin();
+    node->setRotation(rot.x(), rot.y(), rot.z(), rot.w());
+    node->setTranslation(pos.x(), pos.y(), pos.z());
+}
+
+bool PhysicsVehicleWheel::isFront() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_bIsFrontWheel;
+}
+
+void PhysicsVehicleWheel::setFront(bool front)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_bIsFrontWheel = front;
+}
+
+void PhysicsVehicleWheel::getWheelDirection(Vector3* wheelDirection) const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    const btVector3& v = _host->_vehicle->getWheelInfo(_indexInHost).m_wheelDirectionCS;
+    wheelDirection->set(v.x(), v.y(), v.z());
+}
+
+void PhysicsVehicleWheel::setWheelDirection(const Vector3& wheelDirection)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_wheelDirectionCS.setValue(
+        wheelDirection.x,
+        wheelDirection.y,
+        wheelDirection.z);
+}
+
+void PhysicsVehicleWheel::getWheelAxle(Vector3* wheelAxle) const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    const btVector3& v = _host->_vehicle->getWheelInfo(_indexInHost).m_wheelAxleCS;
+    wheelAxle->set(v.x(), v.y(), v.z());
+}
+
+void PhysicsVehicleWheel::setWheelAxle(const Vector3& wheelAxle)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_wheelAxleCS.setValue(
+        wheelAxle.x,
+        wheelAxle.y,
+        wheelAxle.z);
+}
+
+void PhysicsVehicleWheel::getStrutConnectionPoint(Vector3* strutConnectionPoint) const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    const btVector3& v = _host->_vehicle->getWheelInfo(_indexInHost).m_chassisConnectionPointCS;
+    strutConnectionPoint->set(v.x(), v.y(), v.z());
+}
+
+void PhysicsVehicleWheel::setStrutConnectionPoint(const Vector3& strutConnectionPoint)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_chassisConnectionPointCS.setValue(
+        strutConnectionPoint.x,
+        strutConnectionPoint.y,
+        strutConnectionPoint.z);
+}
+
+float PhysicsVehicleWheel::getStrutRestLength() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_suspensionRestLength1;
+}
+
+void PhysicsVehicleWheel::setStrutRestLength(float strutRestLength)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_suspensionRestLength1 = strutRestLength;
+}
+
+float PhysicsVehicleWheel::getStrutTravelMax() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_maxSuspensionTravelCm / 100.0f;
+}
+
+void PhysicsVehicleWheel::setStrutTravelMax(float strutTravelMax)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_maxSuspensionTravelCm = strutTravelMax * 100.0f;
+}
+
+float PhysicsVehicleWheel::getStrutStiffness() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_suspensionStiffness;
+}
+
+void PhysicsVehicleWheel::setStrutStiffness(float strutStiffness)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_suspensionStiffness = strutStiffness;
+}
+
+float PhysicsVehicleWheel::getStrutDampingCompression() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_wheelsDampingCompression;
+}
+
+void PhysicsVehicleWheel::setStrutDampingCompression(float strutDampingCompression)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_wheelsDampingCompression = strutDampingCompression;
+}
+
+float PhysicsVehicleWheel::getStrutDampingRelaxation() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_wheelsDampingRelaxation;
+}
+
+void PhysicsVehicleWheel::setStrutDampingRelaxation(float strutDampingRelaxation)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_wheelsDampingRelaxation = strutDampingRelaxation;
+}
+
+float PhysicsVehicleWheel::getStrutForceMax() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_maxSuspensionForce;
+}
+
+void PhysicsVehicleWheel::setStrutForceMax(float strutForceMax)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_maxSuspensionForce = strutForceMax;
+}
+
+float PhysicsVehicleWheel::getFrictionBreakout() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_frictionSlip;
+}
+
+void PhysicsVehicleWheel::setFrictionBreakout(float frictionBreakout)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_frictionSlip = frictionBreakout;
+}
+
+float PhysicsVehicleWheel::getWheelRadius() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_wheelsRadius;
+}
+
+void PhysicsVehicleWheel::setWheelRadius(float wheelRadius)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_wheelsRadius = wheelRadius;
+}
+
+float PhysicsVehicleWheel::getRollInfluence() const
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    return _host->_vehicle->getWheelInfo(_indexInHost).m_rollInfluence;
+}
+
+void PhysicsVehicleWheel::setRollInfluence(float rollInfluence)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+
+    _host->_vehicle->getWheelInfo(_indexInHost).m_rollInfluence = rollInfluence;
+}
+
+}

+ 336 - 0
gameplay/src/PhysicsVehicleWheel.h

@@ -0,0 +1,336 @@
+#ifndef PHYSICSVEHICLEWHEEL_H_
+#define PHYSICSVEHICLEWHEEL_H_
+
+#include "PhysicsCollisionObject.h"
+#include "PhysicsRigidBody.h"
+
+namespace gameplay
+{
+
+class Node;
+class PhysicsVehicle;
+
+/**
+ * Defines a class for vehicle wheel physics which represents the individual wheel
+ * itself as well as the tire and suspension.
+ *
+ * The following properties are available for wheels:
+
+ @verbatim
+    collisionObject <wheelID>
+    {
+        type                     = VEHICLE_WHEEL
+
+        isFront                  = <bool>                // indicates whether this is a front wheel
+        wheelDirection           = <float, float, float> // direction strut extension, in chassis space
+        wheelAxle                = <float, float, float> // direction of axle (spin axis), in chassis space
+        strutConnectionPoint     = <float, float, float> // strut connection point, in chassis space
+        strutRestLength          = <float>               // strut rest length
+        strutTravelMax           = <float>               // maximum strut travel
+        strutStiffness           = <float>               // strut stiffness, normalized to chassis mass
+        strutDampingCompression  = <float>               // strut damping under compression, normalized to chassis mass
+        strutDampingRelaxation   = <float>               // strut damping under relaxation, normalized to chassis mass
+        strutForceMax            = <float>               // maximum strut force
+        frictionBreakout         = <float>               // breakout friction
+        wheelRadius              = <float>               // wheel radius
+        rollInfluence            = <float>               // how side friction affects chassis roll, normalized
+    }
+ @endverbatim
+ */
+
+class PhysicsVehicleWheel : public PhysicsCollisionObject
+{
+    friend class Node;
+    friend class PhysicsVehicle;
+
+public:
+
+    /**
+     * @see PhysicsCollisionObject#getType
+     */
+    PhysicsCollisionObject::Type getType() const;
+
+    /**
+     * Apply this wheel's world transform to the specified node.
+     * Useful for updating the specified visual node with the current
+     * transform.
+     *
+     * @param node the node to be transformed; (typically a visual
+     * representation of this wheel).
+     */
+    void transform(Node* node) const;
+
+    /**
+     * Returns true if this is a front wheel, false otherwise.
+     *
+     * @return true if this is a front wheel, false otherwise.
+     */
+    /*TODO: inline*/ bool isFront() const;
+
+    /**
+     * Sets whether this is a front wheel.
+     *
+     * @param front true if this is a front wheel, false otherwise.
+     */
+    /*TODO: inline*/ void setFront(bool front);
+
+    /**
+     * Gets direction of strut extension, in chassis space.
+     *
+     * @param wheelDirection address of where to store the result.
+     */
+    /*TODO: inline*/ void getWheelDirection(Vector3* wheelDirection) const;
+
+    /**
+     * Sets direction of strut extension, in chassis space.
+     *
+     * @param wheelDirection direction of strut extension.
+     */
+    /*TODO: inline*/ void setWheelDirection(const Vector3& wheelDirection);
+
+    /**
+     * Gets direction of axle (the spin axis), in chassis space.
+     *
+     * @param wheelAxle address of where to store the result.
+     */
+    /*TODO: inline*/ void getWheelAxle(Vector3* wheelAxle) const;
+
+    /**
+     * Sets direction of axle (the spin axis), in chassis space.
+     *
+     * @param wheelAxle direction of axle (the spin axis).
+     */
+    /*TODO: inline*/ void setWheelAxle(const Vector3& wheelAxle);
+
+    /**
+     * Gets strut connection point, in chassis space.
+     *
+     * @param strutConnectionPoint address of where to store the result.
+     */
+    /*TODO: inline*/ void getStrutConnectionPoint(Vector3* strutConnectionPoint) const;
+
+    /**
+     * Sets strut connection point, in chassis space.
+     *
+     * @param strutConnectionPoint strut connection point.
+     */
+    /*TODO: inline*/ void setStrutConnectionPoint(const Vector3& strutConnectionPoint);
+
+    /**
+     * Gets the strut rest length.
+     *
+     * @return the strut rest length.
+     */
+    /*TODO: inline*/ float getStrutRestLength() const;
+
+    /**
+     * Sets the strut rest length.
+     *
+     * @param strutRestLength the strut rest length.
+     */
+    /*TODO: inline*/ void setStrutRestLength(float strutRestLength);
+
+    /**
+     * Gets the maximum strut travel.
+     *
+     * @return the maximum strut travel.
+     */
+    /*TODO: inline*/ float getStrutTravelMax() const;
+
+    /**
+     * Sets the maximum strut travel.
+     *
+     * @param strutTravelMax the maximum strut travel.
+     */
+    /*TODO: inline*/ void setStrutTravelMax(float strutTravelMax);
+
+    /**
+     * Gets the strut stiffness, normalized to chassis mass.
+     *
+     * @return the strut stiffness, normalized to chassis mass.
+     */
+    /*TODO: inline*/ float getStrutStiffness() const;
+
+    /**
+     * Sets the strut stiffness, normalized to chassis mass.
+     *
+     * @param strutStiffness the strut stiffness, normalized to chassis mass.
+     */
+    /*TODO: inline*/ void setStrutStiffness(float strutStiffness);
+
+    /**
+     * Gets strut damping under compression, normalized to chassis mass.
+     *
+     * @return strut damping under compression, normalized to chassis mass.
+     */
+    /*TODO: inline*/ float getStrutDampingCompression() const;
+
+    /**
+     * Sets strut damping under compression, normalized to chassis mass.
+     *
+     * @param strutDampingCompression strut damping under compression, normalized to chassis mass.
+     */
+    /*TODO: inline*/ void setStrutDampingCompression(float strutDampingCompression);
+
+    /**
+     * Gets strut damping under relaxation, normalized to chassis mass.
+     *
+     * @return strut damping under relaxation, normalized to chassis mass.
+     */
+    /*TODO: inline*/ float getStrutDampingRelaxation() const;
+
+    /**
+     * Sets strut damping under relaxation, normalized to chassis mass.
+     *
+     * @param strutDampingRelaxation strut damping under relaxation, normalized to chassis mass.
+     */
+    /*TODO: inline*/ void setStrutDampingRelaxation(float strutDampingRelaxation);
+
+    /**
+     * Gets the maximum strut force.
+     *
+     * @return the maximum strut force.
+     */
+    /*TODO: inline*/ float getStrutForceMax() const;
+
+    /**
+     * Sets the maximum strut force.
+     *
+     * @param strutForceMax the maximum strut force.
+     */
+    /*TODO: inline*/ void setStrutForceMax(float strutForceMax);
+
+    /**
+     * Gets the breakout friction.
+     *
+     * @return the breakout friction.
+     */
+    /*TODO: inline*/ float getFrictionBreakout() const;
+
+    /**
+     * Sets the breakout friction.
+     *
+     * @param frictionBreakout the breakout friction.
+     */
+    /*TODO: inline*/ void setFrictionBreakout(float frictionBreakout);
+
+    /**
+     * Gets the wheel radius.
+     *
+     * @return the wheel radius.
+     */
+    /*TODO: inline*/ float getWheelRadius() const;
+
+    /**
+     * Sets the wheel radius.
+     *
+     * @param wheelRadius the wheel radius.
+     */
+    /*TODO: inline*/ void setWheelRadius(float wheelRadius);
+
+    /**
+     * Gets roll influence which determines how side friction affects chassis roll.
+     *
+     * @return roll influence, normalized factor.
+     */
+    /*TODO: inline*/ float getRollInfluence() const;
+
+    /**
+     * Sets roll influence which determines how side friction affects chassis roll.
+     *
+     * @param rollInfluence roll influence, normalized factor.
+     */
+    /*TODO: inline*/ void setRollInfluence(float rollInfluence);
+
+protected:
+
+    /**
+     * @see PhysicsCollisionObject::getCollisionObject
+     */
+    btCollisionObject* getCollisionObject() const;
+
+private:
+
+    /**
+     * Creates a vehicle wheel based on the specified rigid body parameters and some 'safe' defaults.
+     * Also, traverse up the scene graph until we find the first common ancestor with another node
+     * of collision type VEHICLE and add ourself as a wheel onto that vehicle. This assumes that the
+     * VEHICLE comes before the VEHICLE_WHEEL in the ".scene" (properties) file.
+     * 
+     * @param node The node to create a rigid body for; note that the node must have
+     *      a model attached to it prior to creating a rigid body for it.
+     * @param shape The rigid body shape construction information.
+     * @param parameters The rigid body construction parameters.
+     */
+    PhysicsVehicleWheel(Node* node, const PhysicsCollisionShape::Definition& shape, const PhysicsRigidBody::Parameters& parameters);
+
+    /**
+     * Creates a vehicle wheel based on the given rigid body and some 'safe' defaults.
+     * Also, traverse up the scene graph until we find the first common ancestor with another node
+     * of collision type VEHICLE and add ourself as a wheel onto that vehicle. This assumes that the
+     * VEHICLE comes before the VEHICLE_WHEEL in the ".scene" (properties) file.
+     * 
+     * @param node The node to create a rigid body for; note that the node must have
+     *      a model attached to it prior to creating a rigid body for it.
+     * @param rigidBody The rigid body.
+     */
+    PhysicsVehicleWheel(Node* node, PhysicsRigidBody* rigidBody);
+
+    /**
+     * Private copy constructor to prevent copying.
+     */
+    PhysicsVehicleWheel(const PhysicsVehicleWheel& src);
+
+    /**
+     * Private copy assignment operator.
+     */
+    PhysicsVehicleWheel& operator=(const PhysicsVehicleWheel&);
+
+    /**
+     * Creates a vehicle wheel physics object from the specified properties object.
+     * Also, traverse up the scene graph until we find the first common ancestor with another node
+     * of collision type VEHICLE and add ourself as a wheel onto that vehicle. This assumes that the
+     * VEHICLE comes before the VEHICLE_WHEEL in the ".scene" (properties) file.
+     * 
+     * @param node The node to create a wheel for; note that the node must have
+     *      a model attached to it prior to creating a vehicle wheel for it.
+     * @param properties The properties object defining the vehicle wheel (must have type equal to 'VEHICLE_WHEEL').
+     * @return The newly created wheel, or <code>NULL</code> if the vehicle wheel failed to load.
+     */
+    static PhysicsVehicleWheel* create(Node* node, Properties* properties);
+
+    /**
+     * Destructor.
+     */
+    ~PhysicsVehicleWheel();
+
+    /**
+     * Traverse up the visual scene graph. Upon finding the first ancestor node with an
+     * advertised descendant of collsion type VEHICLE, add this wheel onto the vehicle.
+     */
+    // Note: Currently this method is silent on failure to find a host.
+    void findAncestorAndBind();
+
+    /**
+     * Sets the host vehicle for this wheel.
+     *
+     * @param host the host vehicle.
+     * @param indexInHost the index of this wheel within the host vehicle.
+     */
+    /*TODO: inline*/ void setHost(PhysicsVehicle* host, unsigned int indexInHost);
+
+    /**
+     * Adds this wheel to the specified Bullet vehicle.
+     *
+     * @param vehicle Bullet vehicle to add self to.
+     */
+    void addToVehicle(btRaycastVehicle* vehicle);
+
+    PhysicsRigidBody* _rigidBody;
+    PhysicsVehicle* _host;
+    unsigned int _indexInHost;
+};
+
+}
+
+#endif /* PHYSICSVEHICLEWHEEL_H_ */

+ 2 - 0
gameplay/src/gameplay.h

@@ -74,6 +74,8 @@
 #include "PhysicsRigidBody.h"
 #include "PhysicsGhostObject.h"
 #include "PhysicsCharacter.h"
+#include "PhysicsVehicle.h"
+#include "PhysicsVehicleWheel.h"
 
 // AI
 #include "AIController.h"