Răsfoiți Sursa

Merge branch 'next' of https://github.com/blackberry-gaming/GamePlay into next-rmadhavan

Conflicts:
	gameplay/gameplay.vcxproj.filters
Ramprasad Madhavan 14 ani în urmă
părinte
comite
62edd0283b

+ 1 - 0
.gitignore

@@ -25,6 +25,7 @@
 /gameplay/Device-Release
 /gameplay-encoder/Debug
 /gameplay-encoder/Release
+/gameplay-internal
 /gameplay-samples/sample00-mesh/Debug
 /gameplay-samples/sample00-mesh/DebugMem
 /gameplay-samples/sample00-mesh/Release

+ 6 - 2
gameplay/gameplay.vcxproj

@@ -69,6 +69,7 @@
     <ClCompile Include="src\Ref.cpp" />
     <ClCompile Include="src\RenderState.cpp" />
     <ClCompile Include="src\Scene.cpp" />
+    <ClCompile Include="src\SceneLoader.cpp" />
     <ClCompile Include="src\SpriteBatch.cpp" />
     <ClCompile Include="src\Technique.cpp" />
     <ClCompile Include="src\Texture.cpp" />
@@ -135,6 +136,7 @@
     <ClInclude Include="src\Ref.h" />
     <ClInclude Include="src\RenderState.h" />
     <ClInclude Include="src\Scene.h" />
+    <ClInclude Include="src\SceneLoader.h" />
     <ClInclude Include="src\SpriteBatch.h" />
     <ClInclude Include="src\Technique.h" />
     <ClInclude Include="src\Texture.h" />
@@ -246,10 +248,11 @@
       </PrecompiledHeader>
       <WarningLevel>Level3</WarningLevel>
       <Optimization>Disabled</Optimization>
-      <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=0;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories>..\external-deps\bullet\include;..\external-deps\openal\include\AL;..\external-deps\alut\include\AL;..\external-deps\oggvorbis\include;..\external-deps\glew\include;..\external-deps\libpng\include;..\external-deps\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <RuntimeTypeInfo>
       </RuntimeTypeInfo>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -262,9 +265,10 @@
       </PrecompiledHeader>
       <WarningLevel>Level3</WarningLevel>
       <Optimization>Disabled</Optimization>
-      <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;GAMEPLAY_MEM_LEAK_DETECTION;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=0;WIN32;_DEBUG;_LIB;GAMEPLAY_MEM_LEAK_DETECTION;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories>..\external-deps\bullet\include;..\external-deps\openal\include\AL;..\external-deps\alut\include\AL;..\external-deps\oggvorbis\include;..\external-deps\glew\include;..\external-deps\libpng\include;..\external-deps\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <RuntimeTypeInfo>true</RuntimeTypeInfo>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>

+ 8 - 0
gameplay/gameplay.vcxproj.filters

@@ -204,8 +204,12 @@
     <ClCompile Include="src\PhysicsMotionState.cpp">
       <Filter>src</Filter>
     </ClCompile>
+<<<<<<< HEAD
     <ClCompile Include="src\Gamepad.cpp" />
     <ClCompile Include="src\Theme.cpp">
+=======
+    <ClCompile Include="src\SceneLoader.cpp">
+>>>>>>> 187027bc8c14ffd1c894cee0793284c5a5238090
       <Filter>src</Filter>
     </ClCompile>
   </ItemGroup>
@@ -399,8 +403,12 @@
     <ClInclude Include="src\PhysicsSocketConstraint.h">
       <Filter>src</Filter>
     </ClInclude>
+<<<<<<< HEAD
     <ClInclude Include="src\Gamepad.h" />
     <ClInclude Include="src\Theme.h">
+=======
+    <ClInclude Include="src\SceneLoader.h">
+>>>>>>> 187027bc8c14ffd1c894cee0793284c5a5238090
       <Filter>src</Filter>
     </ClInclude>
   </ItemGroup>

+ 8 - 0
gameplay/gameplay.xcodeproj/project.pbxproj

@@ -9,6 +9,8 @@
 /* Begin PBXBuildFile section */
 		4220A6E8146B122B00CAEB3A /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4220A6E7146B122B00CAEB3A /* QuartzCore.framework */; };
 		4234D99E14686C52003031B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4234D99D14686C52003031B3 /* Cocoa.framework */; };
+		428390991489D6E800E2B2F5 /* SceneLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 428390971489D6E800E2B2F5 /* SceneLoader.cpp */; };
+		4283909A1489D6E800E2B2F5 /* SceneLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 428390981489D6E800E2B2F5 /* SceneLoader.h */; };
 		4299EFA9146AC94300FF4A73 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4299EFA8146AC94300FF4A73 /* OpenGL.framework */; };
 		4299EFAB146AC94B00FF4A73 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4299EFAA146AC94B00FF4A73 /* OpenAL.framework */; };
 		42CCD554146EC1DD00353661 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 42CCD553146EC1DD00353661 /* libz.dylib */; };
@@ -159,6 +161,8 @@
 		4220A6E7146B122B00CAEB3A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = SDKs/MacOSX10.7.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; };
 		4234D99A14686C52003031B3 /* libgameplay.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libgameplay.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		4234D99D14686C52003031B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
+		428390971489D6E800E2B2F5 /* SceneLoader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SceneLoader.cpp; path = src/SceneLoader.cpp; sourceTree = SOURCE_ROOT; };
+		428390981489D6E800E2B2F5 /* SceneLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SceneLoader.h; path = src/SceneLoader.h; sourceTree = SOURCE_ROOT; };
 		4299EFA8146AC94300FF4A73 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = SDKs/MacOSX10.7.sdk/System/Library/Frameworks/OpenGL.framework; sourceTree = DEVELOPER_DIR; };
 		4299EFAA146AC94B00FF4A73 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = SDKs/MacOSX10.7.sdk/System/Library/Frameworks/OpenAL.framework; sourceTree = DEVELOPER_DIR; };
 		42CCD553146EC1DD00353661 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = SDKs/MacOSX10.7.sdk/usr/lib/libz.dylib; sourceTree = DEVELOPER_DIR; };
@@ -488,6 +492,8 @@
 				42CD0E2C147D8FF50000361E /* RenderTarget.h */,
 				42CD0E2D147D8FF50000361E /* Scene.cpp */,
 				42CD0E2E147D8FF50000361E /* Scene.h */,
+				428390971489D6E800E2B2F5 /* SceneLoader.cpp */,
+				428390981489D6E800E2B2F5 /* SceneLoader.h */,
 				42CD0E2F147D8FF50000361E /* SpriteBatch.cpp */,
 				42CD0E30147D8FF50000361E /* SpriteBatch.h */,
 				42CD0E31147D8FF50000361E /* Technique.cpp */,
@@ -614,6 +620,7 @@
 				42CD0EC8147D8FF60000361E /* VertexAttributeBinding.h in Headers */,
 				42CD0ECA147D8FF60000361E /* VertexFormat.h in Headers */,
 				42CD0ECC147D8FF60000361E /* Viewport.h in Headers */,
+				4283909A1489D6E800E2B2F5 /* SceneLoader.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -736,6 +743,7 @@
 				42CD0EC7147D8FF60000361E /* VertexAttributeBinding.cpp in Sources */,
 				42CD0EC9147D8FF60000361E /* VertexFormat.cpp in Sources */,
 				42CD0ECB147D8FF60000361E /* Viewport.cpp in Sources */,
+				428390991489D6E800E2B2F5 /* SceneLoader.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 1 - 1
gameplay/src/AnimationClip.h

@@ -51,7 +51,7 @@ public:
         };
 
         /**
-         * Handles when a transform has changed.
+         * Handles when animation event occurs.
          */
         virtual void animationEvent(AnimationClip* clip, EventType type) = 0;
     };

+ 19 - 0
gameplay/src/AnimationController.cpp

@@ -47,6 +47,25 @@ Animation* AnimationController::createAnimation(const char* id, AnimationTarget*
     return animation;
 }
 
+Animation* AnimationController::createAnimation(const char* id, AnimationTarget* target, Properties* p)
+{
+    Animation* animation = getAnimation(id);
+
+    if (animation != NULL)
+        return NULL;
+    
+    // TODO: Implement loading from a properties object here.
+    /*
+    animation = new Animation(id, target, p);
+
+    addAnimation(animation);
+
+    target->addAnimation(animation);
+    */
+
+    return animation;
+}
+
 Animation* AnimationController::createAnimationFromTo(const char* id, AnimationTarget* target, int propertyId, float* from, float* to, Curve::InterpolationType type, unsigned long duration)
 {
     const unsigned int propertyComponentCount = target->getAnimationPropertyComponentCount(propertyId);

+ 12 - 0
gameplay/src/AnimationController.h

@@ -4,6 +4,7 @@
 #include "AnimationClip.h"
 #include "Animation.h"
 #include "AnimationTarget.h"
+#include "Properties.h"
 
 namespace gameplay
 {
@@ -52,6 +53,17 @@ public:
      */
     Animation* createAnimation(const char* id, AnimationTarget* target, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, float* keyInValue, float* keyOutValue, Curve::InterpolationType type);
 
+    /**
+     * Creates an animation on this target using the data from the given properties object. 
+     * 
+     * @param id The ID of the animation.
+     * @param target The animation target.
+     * @param properties The properties object defining the animation data.
+     *
+     * @return The newly created animation, or NULL if an animation with the given ID already exists.
+     */
+    Animation* createAnimation(const char* id, AnimationTarget* target, Properties* p);
+
     /**
      * Creates a simple two keyframe from-to animation.
      * Cannot use Curve::BEZIER or CURVE::HERMITE as the interpolation type since they require tangents/control points.

+ 6 - 0
gameplay/src/AudioSource.cpp

@@ -49,6 +49,12 @@ AudioSource* AudioSource::create(const char* path)
     return new AudioSource(buffer, alSource);
 }
 
+AudioSource* AudioSource::create(Properties* properties)
+{
+    // TODO: Implement this!
+    return NULL;
+}
+
 AudioSource::State AudioSource::getState() const
 {
     ALint state;

+ 8 - 0
gameplay/src/AudioSource.h

@@ -41,6 +41,14 @@ public:
      */
     static AudioSource* create(const char* path);
 
+    /**
+     * Create an audio source from the given properties object.
+     * 
+     * @param properties The properties object defining the audio source (must have namespace equal to 'audio').
+     * @return The newly created audio source, or <code>NULL</code> if the audio source failed to load.
+     */
+    static AudioSource* create(Properties* properties);
+
     /**
      * Plays the audio source.
      */

+ 10 - 2
gameplay/src/Base.h

@@ -72,7 +72,12 @@ extern void printError(const char* format, ...);
 // Since Bullet overrides new, we have to allocate objects manually using its
 // aligned allocation function when we turn on memory leak detection in GamePlay.
 #ifdef GAMEPLAY_MEM_LEAK_DETECTION
-#define BULLET_NEW(type, name, ...) \
+#define BULLET_NEW(type, name) \
+    type* name = (type*)btAlignedAlloc(sizeof(type), 16); \
+    type __##name##_tmp; \
+    memcpy(name, &__##name##_tmp, sizeof(type))
+
+#define BULLET_NEW_VARG(type, name, ...) \
     type* name = (type*)btAlignedAlloc(sizeof(type), 16); \
     type __##name##_tmp (__VA_ARGS__); \
     memcpy(name, &__##name##_tmp, sizeof(type))
@@ -85,7 +90,10 @@ extern void printError(const char* format, ...);
     }
 
 #else
-#define BULLET_NEW(type, name, ...) \
+#define BULLET_NEW(type, name) \
+    type* name = new type()
+
+#define BULLET_NEW_VARG(type, name, ...) \
     type* name = new type(__VA_ARGS__)
 
 #define BULLET_DELETE(name) SAFE_DELETE(name)

+ 8 - 6
gameplay/src/Material.cpp

@@ -43,12 +43,18 @@ Material* Material::create(const char* materialPath)
         return NULL;
     }
 
+    Material* material = create(properties->getNextNamespace());
+    SAFE_DELETE(properties);
+
+    return material;
+}
+
+Material* Material::create(Properties* materialProperties)
+{
     // Check if the Properties is valid and has a valid namespace.
-    Properties* materialProperties = properties->getNextNamespace();
     assert(materialProperties);
     if (!materialProperties || !(strcmp(materialProperties->getNamespace(), "material") == 0))
     {
-        SAFE_DELETE(properties);
         return NULL;
     }
 
@@ -64,7 +70,6 @@ Material* Material::create(const char* materialPath)
             if (!loadTechnique(material, techniqueProperties))
             {
                 SAFE_RELEASE(material);
-                SAFE_DELETE(properties);
                 return NULL;
             }
         }
@@ -73,9 +78,6 @@ Material* Material::create(const char* materialPath)
     // Load uniform value parameters for this material
     loadRenderState(material, materialProperties);
 
-    // Material properties no longer required
-    SAFE_DELETE(properties);
-
     // Set the current technique to the first found technique
     if (material->getTechniqueCount() > 0)
     {

+ 9 - 0
gameplay/src/Material.h

@@ -34,6 +34,15 @@ public:
      */
     static Material* create(const char* materialPath);
 
+    /**
+     * Creates a material from the specified properties object.
+     * 
+     * @param materialProperties The properties object defining the 
+     *      material (must have namespace equal to 'material').
+     * @return A new Material.
+     */
+    static Material* create(Properties* materialProperties);
+
     /**
      * Creates a material from the specified effect.
      *

+ 1 - 1
gameplay/src/Matrix.h

@@ -933,6 +933,6 @@ inline Vector4 operator*(const Matrix& m, const Vector4& v);
 
 }
 
-//#include "Matrix.inl"
+#include "Matrix.inl"
 
 #endif

+ 2 - 2
gameplay/src/MeshPart.cpp

@@ -39,7 +39,7 @@ MeshPart* MeshPart::create(Mesh* mesh, unsigned int meshIndex, Mesh::PrimitiveTy
         return NULL;
     }
 
-    unsigned int indexSize;
+    unsigned int indexSize = 0;
     switch (indexFormat)
     {
     case Mesh::INDEX8:
@@ -105,7 +105,7 @@ void MeshPart::setIndexData(void* indexData, unsigned int indexStart, unsigned i
 {
     GL_ASSERT( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer) );
 
-    unsigned int indexSize;
+    unsigned int indexSize = 0;
     switch (_indexFormat)
     {
     case Mesh::INDEX8:

+ 15 - 1
gameplay/src/Node.cpp

@@ -285,7 +285,7 @@ const Matrix& Node::getWorldMatrix() const
         // If we have a parent, multiply our parent world transform by our local
         // transform to obtain our final resolved world transform.
         Node* parent = getParent();
-        if (parent && !_physicsRigidBody)
+        if (parent && (!_physicsRigidBody || _physicsRigidBody->isKinematic()) )
         {
             Matrix::multiply(parent->getWorldMatrix(), getMatrix(), &_world);
         }
@@ -707,4 +707,18 @@ void Node::setPhysicsRigidBody(PhysicsRigidBody::Type type, float mass, float fr
         _physicsRigidBody = new PhysicsRigidBody(this, type, mass, friction, restitution, linearDamping, angularDamping);
 }
 
+void Node::setPhysicsRigidBody(const char* filePath)
+{
+    SAFE_DELETE(_physicsRigidBody);
+
+    _physicsRigidBody = PhysicsRigidBody::create(this, filePath);
+}
+
+void Node::setPhysicsRigidBody(Properties* properties)
+{
+    SAFE_DELETE(_physicsRigidBody);
+
+    _physicsRigidBody = PhysicsRigidBody::create(this, properties);
+}
+
 }

+ 14 - 0
gameplay/src/Node.h

@@ -369,6 +369,20 @@ public:
     void setPhysicsRigidBody(PhysicsRigidBody::Type type, float mass = 0.0f, float friction = 0.5f,
         float restitution = 0.0f, float linearDamping = 0.0f, float angularDamping = 0.0f);
 
+    /**
+     * Sets the physics rigid body for this node using the rigid body definition in the given file.
+     * 
+     * @param filePath The path to the file that contains the rigid body definition.
+     */
+    void setPhysicsRigidBody(const char* url);
+
+    /**
+     * Sets the physics rigid body for this node from the given properties object.
+     * 
+     * @param properties The properties object defining the rigid body (must have namespace equal to 'rigidbody').
+     */
+    void setPhysicsRigidBody(Properties* properties);
+
     /**
      * Returns the bounding sphere for the Node, in world space.
      *

+ 56 - 12
gameplay/src/Package.cpp

@@ -3,6 +3,7 @@
 #include "FileSystem.h"
 #include "MeshPart.h"
 #include "Scene.h"
+#include "SceneLoader.h"
 #include "Joint.h"
 
 #define GPB_PACKAGE_VERSION_MAJOR 1
@@ -118,6 +119,11 @@ Package* Package::create(const char* path)
 
     // Open the package
     FILE* fp = FileSystem::openFile(path, "rb");
+    if (!fp)
+    {
+        WARN_VARG("Failed to open file: '%s'.", path);
+        return NULL;
+    }
 
     // Read the GPG header info
     char sig[9];
@@ -279,6 +285,11 @@ bool Package::readMatrix(float* m)
 }
 
 Scene* Package::loadScene(const char* id)
+{
+    return loadScene(id, NULL);
+}
+
+Scene* Package::loadScene(const char* id, const std::vector<std::string>* nodesWithMeshRB)
 {
     clearLoadSession();
 
@@ -310,7 +321,7 @@ Scene* Package::loadScene(const char* id)
         // Read each child directly into the scene
         for (unsigned int i = 0; i < childrenCount; i++)
         {
-            Node* node = readNode(scene, NULL);
+            Node* node = readNode(scene, NULL, nodesWithMeshRB);
             if (node)
             {
                 scene->addNode(node);
@@ -372,12 +383,17 @@ Scene* Package::loadScene(const char* id)
 }
 
 Node* Package::loadNode(const char* id)
+{
+    return loadNode(id, false);
+}
+
+Node* Package::loadNode(const char* id, bool loadWithMeshRBSupport)
 {
     assert(id);
 
     clearLoadSession();
 
-    Node* node = loadNode(id, NULL, NULL);
+    Node* node = loadNode(id, NULL, NULL, loadWithMeshRBSupport);
    
     if (node)
     {
@@ -387,7 +403,7 @@ Node* Package::loadNode(const char* id)
     return node;
 }
 
-Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext)
+Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport)
 {
     assert(id);
 
@@ -413,13 +429,22 @@ Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext)
             return NULL;
         }
 
-        node = readNode(sceneContext, nodeContext);
+        if (loadWithMeshRBSupport)
+        {
+            std::vector<std::string> nodesWithMeshRBSupport;
+            nodesWithMeshRBSupport.push_back(id);
+            node = readNode(sceneContext, nodeContext, &nodesWithMeshRBSupport);
+        }
+        else
+        {
+            node = readNode(sceneContext, nodeContext, NULL);
+        }
     }
 
     return node;
 }
 
-Node* Package::readNode(Scene* sceneContext, Node* nodeContext)
+Node* Package::readNode(Scene* sceneContext, Node* nodeContext, const std::vector<std::string>* nodesWithMeshRB)
 {
     const char* id = getIdFromOffset();
 
@@ -470,7 +495,7 @@ Node* Package::readNode(Scene* sceneContext, Node* nodeContext)
         // Read each child
         for (unsigned int i = 0; i < childrenCount; i++)
         {
-            Node* child = readNode(sceneContext, nodeContext);
+            Node* child = readNode(sceneContext, nodeContext, nodesWithMeshRB);
             if (child)
             {
                 node->addChild(child);
@@ -495,8 +520,23 @@ Node* Package::readNode(Scene* sceneContext, Node* nodeContext)
         SAFE_RELEASE(light);
     }
 
+    // Check if this node's id is in the list of nodes to be loaded with
+    // mesh rigid body support so that when we load the model we keep the proper data.
+    bool loadWithMeshRBSupport = false;
+    if (nodesWithMeshRB)
+    {
+        for (unsigned int i = 0; i < nodesWithMeshRB->size(); i++)
+        {
+            if (strcmp((*nodesWithMeshRB)[i].c_str(), id) == 0)
+            {
+                loadWithMeshRBSupport = true;
+                break;
+            }
+        }
+    }
+
     // Read model
-    Model* model = readModel(sceneContext, nodeContext);
+    Model* model = readModel(sceneContext, nodeContext, loadWithMeshRBSupport, node->getId());
     if (model)
     {
         node->setModel(model);
@@ -627,14 +667,14 @@ Light* Package::readLight()
     return light;
 }
 
-Model* Package::readModel(Scene* sceneContext, Node* nodeContext)
+Model* Package::readModel(Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport, const char* nodeId)
 {
     // Read mesh
     Mesh* mesh = NULL;
     std::string xref = readString(_file);
     if (xref.length() > 1 && xref[0] == '#') // TODO: Handle full xrefs
     {
-        mesh = loadMesh(xref.c_str() + 1);
+        mesh = loadMesh(xref.c_str() + 1, loadWithMeshRBSupport, nodeId);
         if (mesh)
         {
             Model* model = Model::create(mesh);
@@ -762,7 +802,7 @@ void Package::resolveJointReferences(Scene* sceneContext, Node* nodeContext)
             {
                 jointId = jointId.substr(1, jointId.length() - 1);
 
-                Node* n = loadNode(jointId.c_str(), sceneContext, nodeContext);
+                Node* n = loadNode(jointId.c_str(), sceneContext, nodeContext, false);
                 if (n && n->getType() == Node::JOINT)
                 {
                     Joint* joint = static_cast<Joint*>(n);
@@ -956,7 +996,7 @@ Animation* Package::readAnimationChannel(Scene* scene, Animation* animation, con
     return animation;
 }
 
-Mesh* Package::loadMesh(const char* id)
+Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char* nodeId)
 {
     // save the file position
     long position = ftell(_file);
@@ -1038,6 +1078,8 @@ Mesh* Package::loadMesh(const char* id)
         return NULL;
     }
     mesh->setVertexData(vertexData, 0, vertexCount);
+    if (loadWithMeshRBSupport)
+        SceneLoader::addMeshRigidBodyData(nodeId, mesh, vertexData, vertexByteCount);
     SAFE_DELETE_ARRAY(vertexData);
 
     // Set mesh bounding volumes
@@ -1074,7 +1116,7 @@ Mesh* Package::loadMesh(const char* id)
         }
 
         Mesh::IndexFormat indexFormat = (Mesh::IndexFormat)iFormat;
-        unsigned int indexSize;
+        unsigned int indexSize = 0;
         switch (indexFormat)
         {
         case Mesh::INDEX8:
@@ -1097,6 +1139,8 @@ Mesh* Package::loadMesh(const char* id)
             return NULL;
         }
         part->setIndexData(indexData, 0, indexCount);
+        if (loadWithMeshRBSupport)
+            SceneLoader::addMeshRigidBodyData(nodeId, indexData, iByteCount);
         SAFE_DELETE_ARRAY(indexData);
     }
 

+ 40 - 5
gameplay/src/Package.h

@@ -15,6 +15,8 @@ namespace gameplay
  */
 class Package : public Ref
 {
+    friend class SceneLoader;
+
 public:
 
     /**
@@ -109,9 +111,8 @@ private:
         ~Reference();
     };
 
-    class MeshSkinData
+    struct MeshSkinData
     {
-    public:
         MeshSkin* skin;
         std::vector<std::string> joints;
         std::vector<Matrix> inverseBindPoseMatrices;
@@ -172,12 +173,46 @@ private:
      */
     Reference* seekToFirstType(unsigned int type);
 
+    /**
+     * Loads the scene with the specified ID from the package, and loads the specified nodes with mesh rigid body support.
+     * If id is NULL then the first scene found is loaded.
+     * 
+     * @param id The ID of the scene to load (NULL to load the first scene).
+     * @param nodesWithMeshRB A list of the IDs of the nodes within the scene that 
+     *      should be loaded with support for triangle mesh rigid bodies.
+     * 
+     * @return The loaded scene, or NULL if the scene could not be loaded.
+     */
+    Scene* loadScene(const char* id, const std::vector<std::string>* nodesWithMeshRB);
+
+    /**
+     * Loads a node with the specified ID from the package, optionally with mesh rigid body support.
+     *
+     * @param id The ID of the node to load in the package.
+     * @param loadWithMeshRBSupport Whether or not to load the node with mesh rigid body support.
+     * 
+     * @return The loaded node, or NULL if the node could not be loaded.
+     */
+    Node* loadNode(const char* id, bool loadWithMeshRBSupport);
+
     /**
      * Internal method to load a node.
      *
      * Only one of node or scene should be passed as non-NULL (or neither).
      */
-    Node* loadNode(const char* id, Scene* sceneContext, Node* nodeContext);
+    Node* loadNode(const char* id, Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport);
+
+    /**
+     * Loads a mesh with the specified ID from the package.
+     *
+     * @param id The ID of the mesh to load.
+     * @param loadWithMeshRBSupport Whether to load the mesh with 
+     *      support for triangle mesh rigid bodies or not.
+     * @param nodeId The id of the mesh's model's parent node.
+     * 
+     * @return The loaded mesh, or NULL if the mesh could not be loaded.
+     */
+    Mesh* loadMesh(const char* id, bool loadWithMeshRBSupport, const char* nodeId);
 
     /**
      * Reads an unsigned int from the current file position.
@@ -241,7 +276,7 @@ private:
      * 
      * @return A pointer to new node or NULL if there was an error.
      */
-    Node* readNode(Scene* sceneContext, Node* nodeContext);
+    Node* readNode(Scene* sceneContext, Node* nodeContext, const std::vector<std::string>* nodesWithMeshRB);
 
     /**
      * Reads a camera from the current file position.
@@ -262,7 +297,7 @@ private:
      * 
      * @return A pointer to a new model or NULL if there was an error.
      */
-    Model* readModel(Scene* sceneContext, Node* nodeContext);
+    Model* readModel(Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport, const char* nodeId);
 
     /**
      * Reads a mesh skin from the current file position.

+ 41 - 37
gameplay/src/ParticleEmitter.cpp

@@ -88,18 +88,24 @@ ParticleEmitter* ParticleEmitter::create(const char* particleFile)
         return NULL;
     }
 
-    // Top level namespace is "particle <particleName>"
-    Properties* particle = properties->getNextNamespace();
-    if (!particle || strcmp(particle->getNamespace(), "particle") != 0)
+    ParticleEmitter* particle = create(properties->getNextNamespace());
+    SAFE_DELETE(properties);
+
+    return particle;
+}
+
+ParticleEmitter* ParticleEmitter::create(Properties* properties)
+{
+    if (!properties || strcmp(properties->getNamespace(), "particle") != 0)
     {
-        LOG_ERROR_VARG("Error loading ParticleEmitter: No 'particle' namespace found: %s", particleFile);
+        LOG_ERROR("Error loading ParticleEmitter: No 'particle' namespace found");
         return NULL;
     }
 
-    Properties* sprite = particle->getNextNamespace();
+    Properties* sprite = properties->getNextNamespace();
     if (!sprite || strcmp(sprite->getNamespace(), "sprite") != 0)
     {
-        LOG_ERROR_VARG("Error loading ParticleEmitter: No 'sprite' namespace found: %s", particleFile);
+        LOG_ERROR("Error loading ParticleEmitter: No 'sprite' namespace found");
         return NULL;
     }
 
@@ -108,7 +114,7 @@ ParticleEmitter* ParticleEmitter::create(const char* particleFile)
     const char* texturePath = sprite->getString("path");
     if (strlen(texturePath) == 0)
     {
-        LOG_ERROR_VARG("Error loading ParticleEmitter: No texture path specified: %s, in %s", texturePath, particleFile);
+        LOG_ERROR_VARG("Error loading ParticleEmitter: No texture path specified: %s", texturePath);
         return NULL;
     }
 
@@ -123,36 +129,36 @@ ParticleEmitter* ParticleEmitter::create(const char* particleFile)
     float spriteFrameDuration = sprite->getFloat("frameDuration");
 
     // Emitter properties.
-    unsigned int particleCountMax = (unsigned int)particle->getInt("particleCountMax");
+    unsigned int particleCountMax = (unsigned int)properties->getInt("particleCountMax");
     if (particleCountMax == 0)
     {
         // Set sensible default.
         particleCountMax = PARTICLE_COUNT_MAX;
     }
 
-    unsigned int emissionRate = (unsigned int)particle->getInt("emissionRate");
+    unsigned int emissionRate = (unsigned int)properties->getInt("emissionRate");
     if (emissionRate == 0)
     {
         emissionRate = PARTICLE_EMISSION_RATE;
     }
 
-    bool ellipsoid = particle->getBool("ellipsoid");
+    bool ellipsoid = properties->getBool("ellipsoid");
 
-    float sizeStartMin = particle->getFloat("sizeStartMin");
-    float sizeStartMax = particle->getFloat("sizeStartMax");
-    float sizeEndMin = particle->getFloat("sizeEndMin");
-    float sizeEndMax = particle->getFloat("sizeEndMax");
-    long energyMin = particle->getLong("energyMin");
-    long energyMax = particle->getLong("energyMax");
+    float sizeStartMin = properties->getFloat("sizeStartMin");
+    float sizeStartMax = properties->getFloat("sizeStartMax");
+    float sizeEndMin = properties->getFloat("sizeEndMin");
+    float sizeEndMax = properties->getFloat("sizeEndMax");
+    long energyMin = properties->getLong("energyMin");
+    long energyMax = properties->getLong("energyMax");
 
     Vector4 colorStart;
     Vector4 colorStartVar;
     Vector4 colorEnd;
     Vector4 colorEndVar;
-    particle->getVector4("colorStart", &colorStart);
-    particle->getVector4("colorStartVar", &colorStartVar);
-    particle->getVector4("colorEnd", &colorEnd);
-    particle->getVector4("colorEndVar", &colorEndVar);
+    properties->getVector4("colorStart", &colorStart);
+    properties->getVector4("colorStartVar", &colorStartVar);
+    properties->getVector4("colorEnd", &colorEnd);
+    properties->getVector4("colorEndVar", &colorEndVar);
 
     Vector3 position;
     Vector3 positionVar;
@@ -162,21 +168,21 @@ ParticleEmitter* ParticleEmitter::create(const char* particleFile)
     Vector3 accelerationVar;
     Vector3 rotationAxis;
     Vector3 rotationAxisVar;
-    particle->getVector3("position", &position);
-    particle->getVector3("positionVar", &positionVar);
-    particle->getVector3("velocity", &velocity);
-    particle->getVector3("velocityVar", &velocityVar);
-    particle->getVector3("acceleration", &acceleration);
-    particle->getVector3("accelerationVar", &accelerationVar);
-    float rotationPerParticleSpeedMin = particle->getFloat("rotationPerParticleSpeedMin");
-    float rotationPerParticleSpeedMax = particle->getFloat("rotationPerParticleSpeedMax");
-    float rotationSpeedMin = particle->getFloat("rotationSpeedMin");
-    float rotationSpeedMax = particle->getFloat("rotationSpeedMax");
-    particle->getVector3("rotationAxis", &rotationAxis);
-    particle->getVector3("rotationAxisVar", &rotationAxisVar);
-    bool orbitPosition = particle->getBool("orbitPosition");
-    bool orbitVelocity = particle->getBool("orbitVelocity");
-    bool orbitAcceleration = particle->getBool("orbitAcceleration");
+    properties->getVector3("position", &position);
+    properties->getVector3("positionVar", &positionVar);
+    properties->getVector3("velocity", &velocity);
+    properties->getVector3("velocityVar", &velocityVar);
+    properties->getVector3("acceleration", &acceleration);
+    properties->getVector3("accelerationVar", &accelerationVar);
+    float rotationPerParticleSpeedMin = properties->getFloat("rotationPerParticleSpeedMin");
+    float rotationPerParticleSpeedMax = properties->getFloat("rotationPerParticleSpeedMax");
+    float rotationSpeedMin = properties->getFloat("rotationSpeedMin");
+    float rotationSpeedMax = properties->getFloat("rotationSpeedMax");
+    properties->getVector3("rotationAxis", &rotationAxis);
+    properties->getVector3("rotationAxisVar", &rotationAxisVar);
+    bool orbitPosition = properties->getBool("orbitPosition");
+    bool orbitVelocity = properties->getBool("orbitVelocity");
+    bool orbitAcceleration = properties->getBool("orbitAcceleration");
 
     // Apply all properties to a newly created ParticleEmitter.
     ParticleEmitter* emitter = ParticleEmitter::create(texturePath, textureBlending, particleCountMax);
@@ -199,8 +205,6 @@ ParticleEmitter* ParticleEmitter::create(const char* particleFile)
 
     emitter->setOrbit(orbitPosition, orbitVelocity, orbitAcceleration);
 
-    SAFE_DELETE(properties);
-
     return emitter;
 }
 

+ 12 - 1
gameplay/src/ParticleEmitter.h

@@ -8,6 +8,7 @@
 #include "Texture.h"
 #include "Rectangle.h"
 #include "SpriteBatch.h"
+#include "Properties.h"
 
 namespace gameplay
 {
@@ -158,13 +159,23 @@ public:
      */
     static ParticleEmitter* create(const char* particleFile);
 
+    /**
+     * Creates a particle emitter from the specified properties object.
+     * 
+     * @param properties The properties object defining the 
+     *      particle emitter (must have namespace equal to 'particle').
+     * @return The newly created particle emitter, or <code>NULL</code> if the particle emitter failed to load.
+     */
+    static ParticleEmitter* create(Properties* properties);
+
     /**
      * Creates an uninitialized ParticleEmitter.
      *
      * @param texturePath A path to the image to use as this ParticleEmitter's texture.
+     * @param textureBlending The type of texture blending to be used for the particles emitted.
      * @param particleCountMax The maximum number of particles that can be alive at one time in this ParticleEmitter's system.
      */
-    static ParticleEmitter* create(const char* texturePath, TextureBlending blending,  unsigned int particleCountMax);
+    static ParticleEmitter* create(const char* texturePath, TextureBlending textureBlending,  unsigned int particleCountMax);
 
     /**
      * Sets the emission rate, measured in particles per second.

+ 350 - 51
gameplay/src/PhysicsController.cpp

@@ -1,16 +1,23 @@
 #include "Base.h"
 #include "Game.h"
+#include "MeshPart.h"
 #include "PhysicsController.h"
 #include "PhysicsMotionState.h"
+#include "SceneLoader.h"
+
+// The initial capacity of the bullet debug draw's vertex batch.
+#define INITIAL_CAPACITY 280
 
 namespace gameplay
 {
 
-// Default gravity is 9.8 along the negative Y axis.
 PhysicsController::PhysicsController()
-    : _gravity(btScalar(0.0), btScalar(-9.8), btScalar(0.0)), _collisionConfiguration(NULL), _dispatcher(NULL),
-    _overlappingPairCache(NULL), _solver(NULL), _world(NULL), _status(Listener::DEACTIVATED), _listeners(NULL)
+  : _collisionConfiguration(NULL), _dispatcher(NULL),
+    _overlappingPairCache(NULL), _solver(NULL), _world(NULL), _debugDrawer(NULL), 
+    _status(PhysicsController::Listener::DEACTIVATED), _listeners(NULL),
+    _gravity(btScalar(0.0), btScalar(-9.8), btScalar(0.0))
 {
+    // Default gravity is 9.8 along the negative Y axis.
 }
 
 void PhysicsController::addStatusListener(Listener* listener)
@@ -20,23 +27,24 @@ void PhysicsController::addStatusListener(Listener* listener)
 
     _listeners->push_back(listener);
 }
-
+    
 PhysicsController::~PhysicsController()
 {
+    SAFE_DELETE(_debugDrawer);
     SAFE_DELETE(_listeners);
 }
 
 PhysicsFixedConstraint* PhysicsController::createFixedConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b)
 {
     PhysicsFixedConstraint* constraint = new PhysicsFixedConstraint(a, b);
-    setupConstraint(a, b, constraint);
+    addConstraint(a, b, constraint);
     return constraint;
 }
 
 PhysicsGenericConstraint* PhysicsController::createGenericConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b)
 {
     PhysicsGenericConstraint* constraint = new PhysicsGenericConstraint(a, b);
-    setupConstraint(a, b, constraint);
+    addConstraint(a, b, constraint);
     return constraint;
 }
 
@@ -44,9 +52,9 @@ PhysicsGenericConstraint* PhysicsController::createGenericConstraint(PhysicsRigi
     const Quaternion& rotationOffsetA, const Vector3& translationOffsetA, PhysicsRigidBody* b,
     const Quaternion& rotationOffsetB, const Vector3& translationOffsetB)
 {
-    PhysicsGenericConstraint* constraint = new PhysicsGenericConstraint(a, rotationOffsetA, 
-        translationOffsetA, b, rotationOffsetB, translationOffsetB);
-    setupConstraint(a, b, constraint);
+    PhysicsGenericConstraint* constraint = new PhysicsGenericConstraint(a, rotationOffsetA, translationOffsetA, 
+                                                                        b, rotationOffsetB, translationOffsetB);
+    addConstraint(a, b, constraint);
     return constraint;
 }
 
@@ -54,45 +62,51 @@ PhysicsHingeConstraint* PhysicsController::createHingeConstraint(PhysicsRigidBod
     const Quaternion& rotationOffsetA, const Vector3& translationOffsetA, PhysicsRigidBody* b, 
     const Quaternion& rotationOffsetB, const Vector3& translationOffsetB)
 {
-    PhysicsHingeConstraint* constraint = new PhysicsHingeConstraint(a, rotationOffsetA, 
-        translationOffsetA, b, rotationOffsetB, translationOffsetB);
-    setupConstraint(a, b, constraint);
+    PhysicsHingeConstraint* constraint = new PhysicsHingeConstraint(a, rotationOffsetA, translationOffsetA, 
+                                                                    b, rotationOffsetB, translationOffsetB);
+    addConstraint(a, b, constraint);
     return constraint;
 }
 
 PhysicsSocketConstraint* PhysicsController::createSocketConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b)
 {
     PhysicsSocketConstraint* constraint = new PhysicsSocketConstraint(a, b);
-    setupConstraint(a, b, constraint);
+    addConstraint(a, b, constraint);
     return constraint;
 }
 
 PhysicsSocketConstraint* PhysicsController::createSocketConstraint(PhysicsRigidBody* a,
     const Vector3& translationOffsetA, PhysicsRigidBody* b, const Vector3& translationOffsetB)
 {
-    PhysicsSocketConstraint* constraint = new PhysicsSocketConstraint(a,
-        translationOffsetA, b, translationOffsetB);
-    setupConstraint(a, b, constraint);
+    PhysicsSocketConstraint* constraint = new PhysicsSocketConstraint(a,translationOffsetA, 
+                                                                      b, translationOffsetB);
+    addConstraint(a, b, constraint);
     return constraint;
 }
 
 PhysicsSpringConstraint* PhysicsController::createSpringConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b)
 {
     PhysicsSpringConstraint* constraint = new PhysicsSpringConstraint(a, b);
-    setupConstraint(a, b, constraint);
+    addConstraint(a, b, constraint);
     return constraint;
 }
 
-PhysicsSpringConstraint* PhysicsController::createSpringConstraint(PhysicsRigidBody* a,
-    const Quaternion& rotationOffsetA, const Vector3& translationOffsetA, PhysicsRigidBody* b, 
-    const Quaternion& rotationOffsetB, const Vector3& translationOffsetB)
+PhysicsSpringConstraint* PhysicsController::createSpringConstraint(PhysicsRigidBody* a, const Quaternion& rotationOffsetA, const Vector3& translationOffsetA,           
+                                                                   PhysicsRigidBody* b, const Quaternion& rotationOffsetB, const Vector3& translationOffsetB)
 {
-    PhysicsSpringConstraint* constraint = new PhysicsSpringConstraint(a, rotationOffsetA, 
-        translationOffsetA, b, rotationOffsetB, translationOffsetB);
-    setupConstraint(a, b, constraint);
+    PhysicsSpringConstraint* constraint = new PhysicsSpringConstraint(a, rotationOffsetA, translationOffsetA, 
+                                                                      b, rotationOffsetB, translationOffsetB);
+    addConstraint(a, b, constraint);
     return constraint;
 }
 
+void PhysicsController::drawDebug(const Matrix& viewProjection)
+{
+    _debugDrawer->begin(viewProjection);
+    _world->debugDrawWorld();
+    _debugDrawer->end();
+}
+
 const Vector3& PhysicsController::getGravity(const Vector3& gravity) const
 {
     return _gravity;
@@ -103,9 +117,7 @@ void PhysicsController::setGravity(const Vector3& gravity)
     _gravity = gravity;
 
     if (_world)
-    {
         _world->setGravity(btVector3(_gravity.x, _gravity.y, _gravity.z));
-    }
 }
 
 void PhysicsController::initialize()
@@ -118,6 +130,10 @@ void PhysicsController::initialize()
     // Create the world.
     _world = new btDiscreteDynamicsWorld(_dispatcher, _overlappingPairCache, _solver, _collisionConfiguration);
     _world->setGravity(btVector3(_gravity.x, _gravity.y, _gravity.z));
+
+    // Set up debug drawing.
+    _debugDrawer = new DebugDrawer();
+    _world->setDebugDrawer(_debugDrawer);
 }
 
 void PhysicsController::finalize()
@@ -132,12 +148,12 @@ void PhysicsController::finalize()
 
 void PhysicsController::pause()
 {
-    // DUMMY FUNCTION
+    // Unused
 }
 
 void PhysicsController::resume()
 {
-    // DUMMY FUNCTION
+    // Unused
 }
 
 void PhysicsController::update(long elapsedTime)
@@ -189,6 +205,7 @@ void PhysicsController::update(long elapsedTime)
                 (*_listeners)[k]->statusEvent(_status);
             }
         }
+        
     }
 
     // All statuses are set with the DIRTY bit before collision processing occurs.
@@ -256,23 +273,50 @@ void PhysicsController::update(long elapsedTime)
         }
     }
 }
+    
 
 void PhysicsController::addRigidBody(PhysicsRigidBody* body)
 {
     _world->addRigidBody(body->_body);
     _bodies.push_back(body);
 }
+    
+void PhysicsController::removeRigidBody(PhysicsRigidBody* rigidBody)
+{
+    // Find the rigid body and remove it from the world.
+    for (int i = _world->getNumCollisionObjects() - 1; i >= 0 ; i--)
+    {
+        btCollisionObject* obj = _world->getCollisionObjectArray()[i];
+        if (rigidBody->_body == obj)
+        {
+            _world->removeCollisionObject(obj);
+            break;
+        }
+    }
+}
 
-btCollisionShape* PhysicsController::getBox(const Vector3& min, const Vector3& max, const btVector3& scale)
+PhysicsRigidBody* PhysicsController::getRigidBody(const btCollisionObject* collisionObject)
+{
+    // Find the rigid body and remove it from the world.
+    for (unsigned int i = 0; i < _bodies.size(); i++)
+    {
+        if (_bodies[i]->_body == collisionObject)
+            return _bodies[i];
+    }
+    
+    return NULL;
+}
+
+btCollisionShape* PhysicsController::createBox(const Vector3& min, const Vector3& max, const btVector3& scale)
 {
     btVector3 halfExtents(scale.x() * 0.5 * abs(max.x - min.x), scale.y() * 0.5 * abs(max.y - min.y), scale.z() * 0.5 * abs(max.z - min.z));
-    BULLET_NEW(btBoxShape, box, halfExtents);
+    BULLET_NEW_VARG(btBoxShape, box, halfExtents);
     _shapes.push_back(box);
 
     return box;
 }
 
-btCollisionShape* PhysicsController::getSphere(float radius, const btVector3& scale)
+btCollisionShape* PhysicsController::createSphere(float radius, const btVector3& scale)
 {
     // Since sphere shapes depend only on the radius, the best we can do is take
     // the largest dimension and apply that as the uniform scale to the rigid body.
@@ -281,25 +325,124 @@ btCollisionShape* PhysicsController::getSphere(float radius, const btVector3& sc
         uniformScale = scale.y();
     if (uniformScale < scale.z())
         uniformScale = scale.z();
-
-    BULLET_NEW(btSphereShape, sphere, uniformScale * radius);
+    
+    BULLET_NEW_VARG(btSphereShape, sphere, uniformScale * radius);
     _shapes.push_back(sphere);
-
+    
     return sphere;
 }
 
-PhysicsRigidBody* PhysicsController::getPhysicsRigidBody(const btCollisionObject* collisionObject)
+btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
 {
-    // Find the rigid body and remove it from the world.
-    for (unsigned int i = 0; i < _bodies.size(); i++)
+    // Retrieve the mesh rigid body data from the loaded scene.
+    const SceneLoader::MeshRigidBodyData* data = SceneLoader::getMeshRigidBodyData(body->_node->getId());
+
+    // Copy the scaled vertex position data to the rigid body's local buffer.
+    Matrix m;
+    Matrix::createScale(body->_node->getScaleX(), body->_node->getScaleY(), body->_node->getScaleZ(), &m);
+    unsigned int vertexCount = data->mesh->getVertexCount();
+    body->_vertexData = new float[vertexCount * 3];
+    Vector3 v;
+    int vertexStride = data->mesh->getVertexFormat()->getVertexSize();
+    for (unsigned int i = 0; i < vertexCount; i++)
     {
-        if (_bodies[i]->_body == collisionObject)
-            return _bodies[i];
+        v.set(*((float*)&data->vertexData[i * vertexStride + 0 * sizeof(float)]),
+              *((float*)&data->vertexData[i * vertexStride + 1 * sizeof(float)]),
+              *((float*)&data->vertexData[i * vertexStride + 2 * sizeof(float)]));
+        v *= m;
+        memcpy(&(body->_vertexData[i * 3]), &v, sizeof(float) * 3);
     }
+    
+    BULLET_NEW(btTriangleIndexVertexArray, meshInterface);
 
-    return NULL;
+    if (data->mesh->getPartCount() > 0)
+    {
+        PHY_ScalarType indexType = PHY_UCHAR;
+        int indexStride = 0;
+        MeshPart* meshPart = NULL;
+        for (unsigned int i = 0; i < data->mesh->getPartCount(); i++)
+        {
+            meshPart = data->mesh->getPart(i);
+
+            switch (meshPart->getIndexFormat())
+            {
+            case Mesh::INDEX8:
+                indexType = PHY_UCHAR;
+                indexStride = 1;
+                break;
+            case Mesh::INDEX16:
+                indexType = PHY_SHORT;
+                indexStride = 2;
+                break;
+            case Mesh::INDEX32:
+                indexType = PHY_INTEGER;
+                indexStride = 4;
+                break;
+            }
+
+            // Copy the index data to the rigid body's local buffer.
+            unsigned int indexDataSize = meshPart->getIndexCount() * indexStride;
+            unsigned char* indexData = new unsigned char[indexDataSize];
+            memcpy(indexData, data->indexData[i], indexDataSize);
+            body->_indexData.push_back(indexData);
+
+            // Create a btIndexedMesh object for the current mesh part.
+            btIndexedMesh indexedMesh;
+            indexedMesh.m_indexType = indexType;
+            indexedMesh.m_numTriangles = meshPart->getIndexCount() / 3;
+            indexedMesh.m_numVertices = meshPart->getIndexCount();
+            indexedMesh.m_triangleIndexBase = (const unsigned char*)body->_indexData[i];
+            indexedMesh.m_triangleIndexStride = indexStride;
+            indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
+            indexedMesh.m_vertexStride = sizeof(float)*3;
+            indexedMesh.m_vertexType = PHY_FLOAT;
+
+            // Add the indexed mesh data to the mesh interface.
+            meshInterface->addIndexedMesh(indexedMesh, indexType);
+        }
+    }
+    else
+    {
+        // Generate index data for the mesh locally in the rigid body.
+        unsigned int* indexData = new unsigned int[data->mesh->getVertexCount()];
+        for (unsigned int i = 0; i < data->mesh->getVertexCount(); i++)
+        {
+            indexData[i] = i;
+        }
+        body->_indexData.push_back((unsigned char*)indexData);
+
+        // Create a single btIndexedMesh object for the mesh interface.
+        btIndexedMesh indexedMesh;
+        indexedMesh.m_indexType = PHY_INTEGER;
+        indexedMesh.m_numTriangles = data->mesh->getVertexCount() / 3;
+        indexedMesh.m_numVertices = data->mesh->getVertexCount();
+        indexedMesh.m_triangleIndexBase = body->_indexData[0];
+        indexedMesh.m_triangleIndexStride = sizeof(unsigned int);
+        indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
+        indexedMesh.m_vertexStride = sizeof(float)*3;
+        indexedMesh.m_vertexType = PHY_FLOAT;
+
+        // Set the data in the mesh interface.
+        meshInterface->addIndexedMesh(indexedMesh, indexedMesh.m_indexType);
+    }
+
+    BULLET_NEW_VARG(btBvhTriangleMeshShape, shape, meshInterface, true);
+    _shapes.push_back(shape);
+
+    return shape;
 }
 
+void PhysicsController::addConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint)
+{
+    a->addConstraint(constraint);
+    if (b)
+    {
+        b->addConstraint(constraint);
+    }
+    
+    _world->addConstraint(constraint->_constraint);
+}
+    
 void PhysicsController::removeConstraint(PhysicsConstraint* constraint)
 {
     // Find the constraint and remove it from the physics world.
@@ -313,30 +456,186 @@ void PhysicsController::removeConstraint(PhysicsConstraint* constraint)
         }
     }
 }
+    
+PhysicsController::DebugDrawer::DebugDrawer()
+    : _mode(btIDebugDraw::DBG_DrawAabb | btIDebugDraw::DBG_DrawConstraintLimits | btIDebugDraw::DBG_DrawConstraints | 
+       btIDebugDraw::DBG_DrawContactPoints | btIDebugDraw::DBG_DrawWireframe), _program(0), _positionAttrib(0),
+       _colorAttrib(0), _viewProjectionMatrixUniform(0), _viewProjection(NULL), _vertexData(NULL), _vertexCount(0), _vertexDataSize(0)
+{
+    // Unused
+}
 
-void PhysicsController::removeRigidBody(PhysicsRigidBody* rigidBody)
+PhysicsController::DebugDrawer::~DebugDrawer()
 {
-    // Find the rigid body and remove it from the world.
-    for (int i = _world->getNumCollisionObjects() - 1; i >= 0 ; i--)
+    SAFE_DELETE_ARRAY(_vertexData);
+}
+
+void PhysicsController::DebugDrawer::begin(const Matrix& viewProjection)
+{
+    _viewProjection = &viewProjection;
+    _vertexCount = 0;
+}
+
+void PhysicsController::DebugDrawer::end()
+{
+    // Lazy load the shader program for drawing.
+    if (!_program)
     {
-        btCollisionObject* obj = _world->getCollisionObjectArray()[i];
-        if (rigidBody->_body == obj)
+        // Vertex shader for drawing colored lines.
+        const char* vs_str = 
         {
-            _world->removeCollisionObject(obj);
-            break;
+            "uniform mat4 u_viewProjectionMatrix;\n"
+            "attribute vec4 a_position;\n"
+            "attribute vec4 a_color;\n"
+            "varying vec4 v_color;\n"
+            "void main(void) {\n"
+            "    v_color = a_color;\n"
+            "    gl_Position = u_viewProjectionMatrix * a_position;\n"
+            "}"
+        };
+        
+        // Fragment shader for drawing colored lines.
+        const char* fs_str = 
+        {
+        #ifdef OPENGL_ES
+            "precision highp float;\n"
+        #endif
+            "varying vec4 v_color;\n"
+            "void main(void) {\n"
+            "   gl_FragColor = v_color;\n"
+            "}"
+        };
+        
+        // Load the vertex shader.
+        GLuint vs;
+        GL_ASSERT( vs = glCreateShader(GL_VERTEX_SHADER) );
+        GLint shader_str_len = strlen(vs_str);
+        GL_ASSERT( glShaderSource(vs, 1, &vs_str, &shader_str_len) );
+        GL_ASSERT( glCompileShader(vs) );
+        GLint status;
+        GL_ASSERT( glGetShaderiv(vs, GL_COMPILE_STATUS, &status) );
+        if (status == GL_FALSE)
+        {
+            GLchar errorMessage[512];
+            GL_ASSERT( glGetShaderInfoLog(vs, sizeof(errorMessage), 0, errorMessage) );
+            WARN_VARG("Physics debug drawing will not work; vertex shader failed to compile with error: '%s'", errorMessage);
+            return;
+        }
+        
+        // Load the fragment shader.
+        GLuint fs;
+        GL_ASSERT( fs = glCreateShader(GL_FRAGMENT_SHADER) );
+        shader_str_len = strlen(fs_str);
+        GL_ASSERT( glShaderSource(fs, 1, &fs_str, &shader_str_len) );
+        GL_ASSERT( glCompileShader(fs) );
+        GL_ASSERT( glGetShaderiv(fs, GL_COMPILE_STATUS, &status) );
+        if (status == GL_FALSE)
+        {
+            GLchar errorMessage[512];
+            GL_ASSERT( glGetShaderInfoLog(fs, sizeof(errorMessage), 0, errorMessage) );
+            WARN_VARG("Physics debug drawing will not work; fragment shader failed to compile with error: '%s'", errorMessage);
+            return;
+        }
+        
+        // Create the shader program and link it.
+        GL_ASSERT( _program = glCreateProgram() );
+        GL_ASSERT( glAttachShader(_program, vs) );
+        GL_ASSERT( glAttachShader(_program, fs) );
+        GL_ASSERT( glLinkProgram(_program) );
+        GL_ASSERT( glGetProgramiv(_program, GL_LINK_STATUS, &status) );
+        if (status == GL_FALSE)
+        {
+            GLchar errorMessage[512];
+            GL_ASSERT( glGetProgramInfoLog(_program, sizeof(errorMessage), 0, errorMessage) );
+            WARN_VARG("Physics debug drawing will not work; shader program failed to link with error: '%s'", errorMessage);
+            return;
         }
+        
+        // Get the attribute and uniform locations.
+        GL_ASSERT( glUseProgram(_program) );
+        GL_ASSERT( _positionAttrib = glGetAttribLocation(_program, "a_position") );
+        GL_ASSERT( _colorAttrib = glGetAttribLocation(_program, "a_color") );
+        GL_ASSERT( _viewProjectionMatrixUniform = glGetUniformLocation(_program, "u_viewProjectionMatrix") );
     }
+    
+    // Set the shader program and vertex attributes.
+    GL_ASSERT( glUseProgram(_program) );
+    GL_ASSERT( glEnableVertexAttribArray(_positionAttrib) );
+    GL_ASSERT( glEnableVertexAttribArray(_colorAttrib) );
+    GL_ASSERT( glVertexAttribPointer(_positionAttrib, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 7, _vertexData) );
+    GL_ASSERT( glVertexAttribPointer(_colorAttrib, 4, GL_FLOAT, GL_FALSE, sizeof(float) * 7, &_vertexData[3]) );
+    
+    // Set the camera's view projection matrix and draw.
+    GL_ASSERT( glUniformMatrix4fv(_viewProjectionMatrixUniform, 1, GL_FALSE, _viewProjection->m) );
+    GL_ASSERT( glDrawArrays(GL_LINES, 0, _vertexCount / 7) );
+    
+    // Reset shader state.
+    GL_ASSERT( glDisableVertexAttribArray(_positionAttrib) );
+    GL_ASSERT( glDisableVertexAttribArray(_colorAttrib) );
+    GL_ASSERT( glUseProgram(0) );
 }
 
-void PhysicsController::setupConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint)
+void PhysicsController::DebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& fromColor, const btVector3& toColor)
 {
-    a->addConstraint(constraint);
-    if (b)
+    // Allocate extra space in the vertex data batch if it is needed.
+    if (_vertexDataSize - _vertexCount < 14)
     {
-        b->addConstraint(constraint);
+        if (_vertexDataSize > 0)
+        {
+            unsigned int newVertexDataSize = _vertexDataSize * 2;
+            float* newVertexData = new float[newVertexDataSize];
+            memcpy(newVertexData, _vertexData, _vertexDataSize * sizeof(float));
+            SAFE_DELETE_ARRAY(_vertexData);
+            _vertexData = newVertexData;
+            _vertexDataSize = newVertexDataSize;
+        }
+        else
+        {
+            _vertexDataSize = INITIAL_CAPACITY;
+            _vertexData = new float[_vertexDataSize];
+        }
     }
+    
+    // Create the vertex data for the line and copy it into the batch.
+    float vertexData[] = 
+    {
+        from.getX(), from.getY(), from.getZ(), 
+        fromColor.getX(), fromColor.getY(), fromColor.getZ(), 1.0f,
+        to.getX(), to.getY(), to.getZ(),
+        toColor.getX(), toColor.getY(), toColor.getZ(), 1.0f
+    };
+    memcpy(&_vertexData[_vertexCount], vertexData, sizeof(float) * 14);
+    _vertexCount += 14;
+}
 
-    _world->addConstraint(constraint->_constraint);
+void PhysicsController::DebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& color)
+{
+    drawLine(from, to, color, color);
+}
+
+void PhysicsController::DebugDrawer::drawContactPoint(const btVector3& pointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color)
+{
+    drawLine(pointOnB, pointOnB + normalOnB, color);
+}
+
+void PhysicsController::DebugDrawer::reportErrorWarning(const char* warningString)
+{
+    WARN(warningString);
+}
+
+void PhysicsController::DebugDrawer::draw3dText(const btVector3& location, const char* textString)
+{
+    WARN("Physics debug drawing: 3D text is not supported.");
+}
+
+void PhysicsController::DebugDrawer::setDebugMode(int mode)
+{
+    _mode = mode;
+}
+
+int	PhysicsController::DebugDrawer::getDebugMode() const
+{
+    return _mode;
 }
 
 }

+ 73 - 27
gameplay/src/PhysicsController.h

@@ -47,17 +47,17 @@ public:
         };
 
         /**
-         * Handles when the physics world status changes.
+         * Handles when the physics world status event occurs.
          */
         virtual void statusEvent(EventType type) = 0;
     };
 
     /**
-     * Adds a status listener.
+     * Adds a listener to the physics controller.
      * 
      * @param listener The listener to add.
      */
-    void addStatusListener(Listener* listener);
+    void addStatusListener(PhysicsController::Listener* listener);
 
     /**
      * Creates a fixed constraint.
@@ -93,10 +93,8 @@ public:
      * @param translationOffsetB The translation offset for the second rigid body
      *      (in its local space) with respect to the constraint joint (optional).
      */
-    PhysicsGenericConstraint* createGenericConstraint(PhysicsRigidBody* a, const Quaternion& rotationOffsetA, 
-                                                     const Vector3& translationOffsetA, PhysicsRigidBody* b = NULL, 
-                                                     const Quaternion& rotationOffsetB = Quaternion(), 
-                                                     const Vector3& translationOffsetB = Vector3());
+    PhysicsGenericConstraint* createGenericConstraint(PhysicsRigidBody* a, const Quaternion& rotationOffsetA, const Vector3& translationOffsetA, 
+                                                      PhysicsRigidBody* b = NULL, const Quaternion& rotationOffsetB = Quaternion(), const Vector3& translationOffsetB = Vector3());
 
     /**
      * Creates a hinge constraint.
@@ -113,10 +111,8 @@ public:
      * @param translationOffsetB The translation offset for the second rigid body
      *      (in its local space) with respect to the constraint joint (optional).
      */
-    PhysicsHingeConstraint* createHingeConstraint(PhysicsRigidBody* a, const Quaternion& rotationOffsetA, 
-                                                  const Vector3& translationOffsetA, PhysicsRigidBody* b = NULL, 
-                                                  const Quaternion& rotationOffsetB = Quaternion(), 
-                                                  const Vector3& translationOffsetB = Vector3());
+    PhysicsHingeConstraint* createHingeConstraint(PhysicsRigidBody* a, const Quaternion& rotationOffsetA, const Vector3& translationOffsetA,
+                                                  PhysicsRigidBody* b = NULL, const Quaternion& rotationOffsetB = Quaternion(), const Vector3& translationOffsetB = Vector3());
 
     /**
      * Creates a socket constraint so that the rigid body (or bodies) is
@@ -169,9 +165,8 @@ public:
      * @param translationOffsetB The translation offset for the second rigid body
      *      (in its local space) with respect to the constraint joint (optional).
      */
-    PhysicsSpringConstraint* createSpringConstraint(PhysicsRigidBody* a, const Quaternion& rotationOffsetA, 
-                                                    const Vector3& translationOffsetA, PhysicsRigidBody* b, 
-                                                    const Quaternion& rotationOffsetB, const Vector3& translationOffsetB);
+    PhysicsSpringConstraint* createSpringConstraint(PhysicsRigidBody* a, const Quaternion& rotationOffsetA, const Vector3& translationOffsetA,          
+                                                    PhysicsRigidBody* b, const Quaternion& rotationOffsetB, const Vector3& translationOffsetB);
 
     /**
      * Gets the gravity vector for the simulated physics world.
@@ -186,6 +181,13 @@ public:
      * @param gravity The gravity vector.
      */
     void setGravity(const Vector3& gravity);
+    
+    /**
+     * Draws debugging information (rigid body outlines, etc.) using the given view projection matrix.
+     * 
+     * @param viewProjection The view projection matrix to use when drawing.
+     */
+    void drawDebug(const Matrix& viewProjection);
 
 private:
 
@@ -226,35 +228,79 @@ private:
 
     // Adds the given rigid body to the world.
     void addRigidBody(PhysicsRigidBody* body);
-
-    // Creates a box collision shape to be used in the creation of a rigid body.
-    btCollisionShape* getBox(const Vector3& min, const Vector3& max, const btVector3& scale);
-
+    
+    // Removes the given rigid body from the simulated physics world.
+    void removeRigidBody(PhysicsRigidBody* rigidBody);
+    
     // Gets the corresponding GamePlay object for the given Bullet object.
-    PhysicsRigidBody* getPhysicsRigidBody(const btCollisionObject* collisionObject);
+    PhysicsRigidBody* getRigidBody(const btCollisionObject* collisionObject);
+    
+    // Creates a box collision shape to be used in the creation of a rigid body.
+    btCollisionShape* createBox(const Vector3& min, const Vector3& max, const btVector3& scale);
 
     // Creates a sphere collision shape to be used in the creation of a rigid body.
-    btCollisionShape* getSphere(float radius, const btVector3& scale);
+    btCollisionShape* createSphere(float radius, const btVector3& scale);
+
+    // Creates a triangle mesh collision shape to be used in the creation of a rigid body.
+    btCollisionShape* createMesh(PhysicsRigidBody* body);
 
+    // Sets up the given constraint for the given two rigid bodies.
+    void addConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint);
+    
     // Removes the given constraint from the simulated physics world.
     void removeConstraint(PhysicsConstraint* constraint);
+    
+    // Draws bullet debug information
+    class DebugDrawer : public btIDebugDraw
+    {
+    public:
 
-    // Removes the given rigid body from the simulated physics world.
-    void removeRigidBody(PhysicsRigidBody* rigidBody);
-
-    // Sets up the given constraint for the given two rigid bodies.
-    void setupConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint);
+        DebugDrawer();
+        
+        ~DebugDrawer();
+        
+        void begin(const Matrix& viewProjection);
+        
+        void end();
+
+        void drawLine(const btVector3& from, const btVector3& to, const btVector3& fromColor, const btVector3& toColor);
+        
+        void drawLine(const btVector3& from, const btVector3& to, const btVector3& color);
+        
+        void drawContactPoint(const btVector3& pointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color);
+        
+        void reportErrorWarning(const char* warningString);
+        
+        void draw3dText(const btVector3& location, const char* textString);
+        
+        void setDebugMode(int mode);
+        
+        int	getDebugMode() const;
+        
+    private:
+        
+        int _mode;
+        GLuint _program;
+        GLuint _positionAttrib;
+        GLuint _colorAttrib;
+        GLuint _viewProjectionMatrixUniform;
+        const Matrix* _viewProjection;
+        float* _vertexData;
+        unsigned int _vertexCount;
+        unsigned int _vertexDataSize;
+    };
     
-    Vector3 _gravity;
     btDefaultCollisionConfiguration* _collisionConfiguration;
     btCollisionDispatcher* _dispatcher;
     btBroadphaseInterface* _overlappingPairCache;
     btSequentialImpulseConstraintSolver* _solver;
     btDynamicsWorld* _world;
     btAlignedObjectArray<btCollisionShape*> _shapes;
+    DebugDrawer* _debugDrawer;
     Listener::EventType _status;
-    std::vector<PhysicsRigidBody*> _bodies;
     std::vector<Listener*>* _listeners;
+    std::vector<PhysicsRigidBody*> _bodies;
+    Vector3 _gravity;
 };
 
 }

+ 1 - 1
gameplay/src/PhysicsHingeConstraint.cpp

@@ -41,7 +41,7 @@ PhysicsHingeConstraint::PhysicsHingeConstraint(PhysicsRigidBody* a, const Quater
     
 PhysicsHingeConstraint::~PhysicsHingeConstraint()
 {
-    // DUMMY FUNCTION
+    // Unused
 }
 
 }

+ 185 - 48
gameplay/src/PhysicsRigidBody.cpp

@@ -7,57 +7,50 @@
 namespace gameplay
 {
 
-const int PhysicsRigidBody::Listener::DIRTY = 0x01;
-const int PhysicsRigidBody::Listener::COLLISION = 0x02;
-const int PhysicsRigidBody::Listener::REGISTERED = 0x04;
+const int PhysicsRigidBody::Listener::DIRTY         = 0x01;
+const int PhysicsRigidBody::Listener::COLLISION     = 0x02;
+const int PhysicsRigidBody::Listener::REGISTERED    = 0x04;
+
+// Internal value used for creating mesh rigid bodies.
+#define SHAPE_MESH ((PhysicsRigidBody::Type)(PhysicsRigidBody::SHAPE_NONE + 1))
 
 PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, float mass, 
         float friction, float restitution, float linearDamping, float angularDamping)
         : _shape(NULL), _body(NULL), _node(node), _listeners(NULL), _angularVelocity(NULL),
-        _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL)
+        _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
+        _indexData(NULL)
 {
     switch (type)
     {
-        case PhysicsRigidBody::SHAPE_BOX:
+        case SHAPE_BOX:
         {
             const BoundingBox& box = node->getModel()->getMesh()->getBoundingBox();
-
-            PhysicsController* physics = Game::getInstance()->getPhysicsController();
-            _shape = physics->getBox(box.min, box.max, btVector3(node->getScaleX(), node->getScaleY(), node->getScaleZ()));
-            
-            // Use the center of the bounding box as the center of mass offset.
-            Vector3 c(box.min, box.max);
-            c.scale(0.5f);
-            c.add(box.min);
-            c.negate();
-
-            if (c.lengthSquared() > MATH_EPSILON)
-                _body = createBulletRigidBody(_shape, mass, node, friction, restitution, linearDamping, angularDamping, &c);
-            else
-                _body = createBulletRigidBody(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
-
+            _shape = Game::getInstance()->getPhysicsController()->createBox(box.min, box.max, btVector3(node->getScaleX(), node->getScaleY(), node->getScaleZ()));
             break;
         }
-        case PhysicsRigidBody::SHAPE_SPHERE:
+        case SHAPE_SPHERE:
         {
             const BoundingSphere& sphere = node->getModel()->getMesh()->getBoundingSphere();
-
-            PhysicsController* physics = Game::getInstance()->getPhysicsController();
-            _shape = physics->getSphere(sphere.radius, btVector3(node->getScaleX(), node->getScaleY(), node->getScaleZ()));
-
-            // Use the center of the bounding sphere as the center of mass offset.
-            Vector3 c(sphere.center);
-            c.negate();
-
-            if (c.lengthSquared() > MATH_EPSILON)
-                _body = createBulletRigidBody(_shape, mass, node, friction, restitution, linearDamping, angularDamping, &c);
-            else
-                _body = createBulletRigidBody(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
-
+            _shape = Game::getInstance()->getPhysicsController()->createSphere(sphere.radius, btVector3(node->getScaleX(), node->getScaleY(), node->getScaleZ()));
+            break;
+        }
+        case SHAPE_MESH:
+        {
+            _shape = Game::getInstance()->getPhysicsController()->createMesh(this);
             break;
         }
     }
 
+    // Use the center of the bounding sphere as the center of mass offset.
+    Vector3 c(node->getModel()->getMesh()->getBoundingSphere().center);
+    c.negate();
+
+    // Create the Bullet rigid body.
+    if (c.lengthSquared() > MATH_EPSILON)
+        _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping, &c);
+    else
+        _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
+
     // Add the rigid body to the physics world.
     Game::getInstance()->getPhysicsController()->addRigidBody(this);
 }
@@ -91,6 +84,11 @@ PhysicsRigidBody::~PhysicsRigidBody()
     SAFE_DELETE(_anisotropicFriction);
     SAFE_DELETE(_gravity);
     SAFE_DELETE(_linearVelocity);
+    SAFE_DELETE_ARRAY(_vertexData);
+    for (unsigned int i = 0; i < _indexData.size(); i++)
+    {
+        SAFE_DELETE_ARRAY(_indexData[i]);
+    }
 }
 
 void PhysicsRigidBody::addCollisionListener(Listener* listener, PhysicsRigidBody* body)
@@ -166,13 +164,150 @@ bool PhysicsRigidBody::collidesWith(PhysicsRigidBody* body)
     return callback.result;
 }
 
-btRigidBody* PhysicsRigidBody::createBulletRigidBody(btCollisionShape* shape, float mass, Node* node,
-    float friction, float restitution, float linearDamping, float angularDamping, const Vector3* centerOfMassOffset)
+PhysicsRigidBody* PhysicsRigidBody::create(Node* node, const char* filePath)
+{
+    assert(filePath);
+
+    // Load the rigid body properties from file.
+    Properties* properties = Properties::create(filePath);
+    assert(properties);
+    if (properties == NULL)
+    {
+        WARN_VARG("Failed to load rigid body file: %s", filePath);
+        return NULL;
+    }
+
+    PhysicsRigidBody* body = create(node, properties->getNextNamespace());
+    SAFE_DELETE(properties);
+
+    return body;
+}
+
+PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
+{
+    // Check if the properties is valid and has a valid namespace.
+    assert(properties);
+    if (!properties || !(strcmp(properties->getNamespace(), "rigidbody") == 0))
+    {
+        WARN("Failed to load rigid body from properties object: must be non-null object and have namespace equal to \'rigidbody\'.");
+        return NULL;
+    }
+
+    // Set values to their defaults.
+    PhysicsRigidBody::Type type = PhysicsRigidBody::SHAPE_NONE;
+    float mass = 0.0;
+    float friction = 0.5;
+    float restitution = 0.0;
+    float linearDamping = 0.0;
+    float angularDamping = 0.0;
+    bool kinematic = false;
+    Vector3* gravity = NULL;
+    Vector3* anisotropicFriction = NULL;
+
+    // Load the defined properties.
+    properties->rewind();
+    const char* name;
+    while (name = properties->getNextProperty())
+    {
+        if (strcmp(name, "type") == 0)
+        {
+            std::string typeStr = properties->getString();
+            if (typeStr == "BOX")
+                type = SHAPE_BOX;
+            else if (typeStr == "SPHERE")
+                type = SHAPE_SPHERE;
+            else if (typeStr == "MESH")
+                type = SHAPE_MESH;
+            else
+            {
+                WARN_VARG("Could not create rigid body; unsupported value for rigid body type: '%s'.", typeStr.c_str());
+                return NULL;
+            }
+        }
+        else if (strcmp(name, "mass") == 0)
+        {
+            mass = properties->getFloat();
+        }
+        else if (strcmp(name, "friction") == 0)
+        {
+            friction = properties->getFloat();
+        }
+        else if (strcmp(name, "restitution") == 0)
+        {
+            restitution = properties->getFloat();
+        }
+        else if (strcmp(name, "linearDamping") == 0)
+        {
+            linearDamping = properties->getFloat();
+        }
+        else if (strcmp(name, "angularDamping") == 0)
+        {
+            angularDamping = properties->getFloat();
+        }
+        else if (strcmp(name, "kinematic") == 0)
+        {
+            kinematic = properties->getBool();
+        }
+        else if (strcmp(name, "gravity") == 0)
+        {
+            gravity = new Vector3();
+            properties->getVector3(NULL, gravity);
+        }
+        else if (strcmp(name, "anisotropicFriction") == 0)
+        {
+            anisotropicFriction = new Vector3();
+            properties->getVector3(NULL, anisotropicFriction);
+        }
+    }
+
+    // If the rigid body type is equal to mesh, check that the node's mesh's primitive type is supported.
+    if (type == SHAPE_MESH)
+    {
+        Mesh* mesh = node->getModel()->getMesh();
+
+        switch (mesh->getPrimitiveType())
+        {
+        case Mesh::TRIANGLES:
+            break;
+        case Mesh::LINES:
+        case Mesh::LINE_STRIP:
+        case Mesh::POINTS:
+        case Mesh::TRIANGLE_STRIP:
+            WARN("Mesh rigid bodies are currently only supported on meshes with primitive type equal to TRIANGLES.");
+
+            SAFE_DELETE(gravity);
+            SAFE_DELETE(anisotropicFriction);
+            return NULL;
+        }
+    }
+
+    // Create the rigid body.
+    PhysicsRigidBody* body = new PhysicsRigidBody(node, type, mass, friction, restitution, linearDamping, angularDamping);
+
+    // Set any initially defined properties.
+    if (kinematic)
+        body->setKinematic(kinematic);
+    if (gravity)
+        body->setGravity(*gravity);
+    if (anisotropicFriction)
+        body->setAnisotropicFriction(*anisotropicFriction);
+
+    // Clean up any loaded properties that are on the heap.
+    SAFE_DELETE(gravity);
+    SAFE_DELETE(anisotropicFriction);
+
+    return body;
+}
+
+btRigidBody* PhysicsRigidBody::createRigidBodyInternal(btCollisionShape* shape, float mass, Node* node,
+                                                       float friction, float restitution, float linearDamping, float angularDamping, 
+                                                       const Vector3* centerOfMassOffset)
 {
-    // If the mass is non-zero, then the object is dynamic
-    // and we need to calculate the local inertia.
+    // If the mass is non-zero, then the object is dynamic so we calculate the local 
+    // inertia. However, if the collision shape is a triangle mesh, we don't calculate 
+    // inertia since Bullet doesn't currently support this.
     btVector3 localInertia(0.0, 0.0, 0.0);
-    if (mass != 0.0)
+    if (mass != 0.0 && shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE)
         shape->calculateLocalInertia(mass, localInertia);
 
     // Create the Bullet physics rigid body object.
@@ -182,7 +317,7 @@ btRigidBody* PhysicsRigidBody::createBulletRigidBody(btCollisionShape* shape, fl
     rbInfo.m_restitution = restitution;
     rbInfo.m_linearDamping = linearDamping;
     rbInfo.m_angularDamping = angularDamping;
-    BULLET_NEW(btRigidBody, body, rbInfo);
+    BULLET_NEW_VARG(btRigidBody, body, rbInfo);
 
     return body;
 }
@@ -207,20 +342,21 @@ void PhysicsRigidBody::removeConstraint(PhysicsConstraint* constraint)
 PhysicsRigidBody::CollisionPair::CollisionPair(PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
     : _rbA(rbA), _rbB(rbB)
 {
-    // Unsued
+    // Unused
 }
 
 PhysicsRigidBody::Listener::~Listener()
 {
-    // Unsued
+    // Unused
 }
 
-btScalar PhysicsRigidBody::Listener::addSingleResult(btManifoldPoint& cp, const btCollisionObject* a,
-    int partIdA, int indexA, const btCollisionObject* b, int partIdB, int indexB)
+btScalar PhysicsRigidBody::Listener::addSingleResult(btManifoldPoint& cp, 
+                                                     const btCollisionObject* a, int partIdA, int indexA, 
+                                                     const btCollisionObject* b, int partIdB, int indexB)
 {
     // Get pointers to the PhysicsRigidBody objects.
-    PhysicsRigidBody* rbA = Game::getInstance()->getPhysicsController()->getPhysicsRigidBody(a);
-    PhysicsRigidBody* rbB = Game::getInstance()->getPhysicsController()->getPhysicsRigidBody(b);
+    PhysicsRigidBody* rbA = Game::getInstance()->getPhysicsController()->getRigidBody(a);
+    PhysicsRigidBody* rbB = Game::getInstance()->getPhysicsController()->getRigidBody(b);
     
     // If the given rigid body pair has collided in the past, then
     // we notify the listener only if the pair was not colliding
@@ -244,8 +380,9 @@ btScalar PhysicsRigidBody::Listener::addSingleResult(btManifoldPoint& cp, const
     return 0.0f;
 }
 
-btScalar PhysicsRigidBody::CollidesWithCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObject* a, int partIdA,
-            int indexA, const btCollisionObject* b, int partIdB, int indexB)
+btScalar PhysicsRigidBody::CollidesWithCallback::addSingleResult(btManifoldPoint& cp, 
+                                                                 const btCollisionObject* a, int partIdA, int indexA, 
+                                                                 const btCollisionObject* b, int partIdB, int indexB)
 {
     result = true;
     return 0.0f;

+ 33 - 13
gameplay/src/PhysicsRigidBody.h

@@ -86,20 +86,18 @@ public:
          * @param contactPoint The point (in world space) where the collision occurred.
          */
         virtual void collisionEvent(const CollisionPair& collisionPair, const Vector3& contactPoint) = 0;
-
-        /**
-         * Internal function used for Bullet integration (do not use or override).
-         */
-        btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* a, int partIdA,
-            int indexA, const btCollisionObject* b, int partIdB, int indexB);
         
     protected:
-
-        /** 
-         * Holds the collision status for each pair of rigid bodies. 
+        
+        /**
+         * Internal function used for Bullet integration (do not use or override).
          */
-        std::map<CollisionPair, int> _collisionStatus;
+        btScalar addSingleResult(btManifoldPoint& cp, 
+                                 const btCollisionObject* a, int partIdA, int indexA, 
+                                 const btCollisionObject* b, int partIdB, int indexB);
 
+        std::map<CollisionPair, int> _collisionStatus;  // Holds the collision status for each pair of rigid bodies. 
+        
     private:
 
         // Internal constant.
@@ -312,11 +310,31 @@ private:
      */
     PhysicsRigidBody(const PhysicsRigidBody& body);
 
+    /**
+     * Creates a rigid body from the rigid body file at the given path.
+     * 
+     * @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 filePath The path to the rigid body file.
+     * @return The rigid body or <code>NULL</code> if the rigid body could not be loaded.
+     */
+    static PhysicsRigidBody* create(Node* node, const char* filePath);
+
+    /**
+     * Creates a rigid body from the specified properties object.
+     * 
+     * @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').
+     * @return The newly created rigid body, or <code>NULL</code> if the rigid body failed to load.
+     */
+    static PhysicsRigidBody* create(Node* node, Properties* properties);
+
     // Creates the underlying Bullet Physics rigid body object
     // for a PhysicsRigidBody object using the given parameters.
-    static btRigidBody* createBulletRigidBody(btCollisionShape* shape, float mass, Node* node,
-                                              float friction, float restitution, float linearDamping, float angularDamping,
-                                              const Vector3* centerOfMassOffset = NULL);
+    static btRigidBody* createRigidBodyInternal(btCollisionShape* shape, float mass, Node* node,
+                                                float friction, float restitution, float linearDamping, float angularDamping,
+                                                const Vector3* centerOfMassOffset = NULL);
 
     // Adds a constraint to this rigid body.
     void addConstraint(PhysicsConstraint* constraint);
@@ -343,6 +361,8 @@ private:
     mutable Vector3* _anisotropicFriction;
     mutable Vector3* _gravity;
     mutable Vector3* _linearVelocity;
+    float* _vertexData;
+    std::vector<unsigned char*> _indexData;
 };
 
 }

+ 35 - 3
gameplay/src/Properties.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "Properties.h"
 #include "FileSystem.h"
+#include "Quaternion.h"
 
 namespace gameplay
 {
@@ -48,10 +49,14 @@ void Properties::readProperties(FILE* file)
     char* value;
     char* rc;
 
-    while (!feof(file))
+    while (true)
     {
         skipWhiteSpace(file);
 
+        // Stop when we have reached the end of the file.
+        if (feof(file))
+            break;
+
         // Read the next line.
         rc = fgets(line, 2048, file);
         if (rc == NULL)
@@ -187,8 +192,10 @@ void Properties::skipWhiteSpace(FILE* file)
         c = fgetc(file);
     } while (isspace(c));
 
-    // We found a non-whitespace character; put the cursor back in front of it.
-    fseek(file, -1, SEEK_CUR);
+    // If we are not at the end of the file, then since we found a
+    // non-whitespace character, we put the cursor back in front of it.
+    if (c != EOF)
+        fseek(file, -1, SEEK_CUR);
 }
 
 char* Properties::trimWhiteSpace(char *str)
@@ -577,6 +584,31 @@ bool Properties::getVector4(const char* name, Vector4* out) const
     return false;
 }
 
+bool Properties::getQuaternionFromAxisAngle(const char* name, Quaternion* out) const
+{
+    assert(out);
+
+    const char* valueString = getString(name);
+    if (valueString)
+    {
+        float x, y, z, theta;
+        int scanned;
+        scanned = sscanf(valueString, "%f,%f,%f,%f", &x, &y, &z, &theta);
+        if (scanned != 4)
+        {
+            LOG_ERROR_VARG("Error parsing property: %s", name);
+            out->set(0.0f, 0.0f, 0.0f, 1.0f);
+            return false;
+        }
+
+        out->set(Vector3(x, y, z), MATH_DEG_TO_RAD(theta));
+        return true;
+    }
+    
+    out->set(0.0f, 0.0f, 0.0f, 1.0f);
+    return false;
+}
+
 bool Properties::getColor(const char* name, Vector3* out) const
 {
     assert(out);

+ 13 - 0
gameplay/src/Properties.h

@@ -326,6 +326,19 @@ public:
      */
     bool getVector4(const char* name, Vector4* out) const;
 
+    /**
+     * Interpret the value of the given property as a Quaternion specified as an axis angle.
+     * If the property does not exist, out will be set to Quaternion().
+     * If the property exists but could not be scanned, an error will be logged and out will be set
+     * to Quaternion().
+     *
+     * @param name The name of the property to interpret, or NULL to return the current property's value.
+     * @param out The quaternion to set to this property's interpreted value.
+     * 
+     * @return True on success, false if the property does not exist or could not be scanned.
+     */
+    bool getQuaternionFromAxisAngle(const char* name, Quaternion* out) const;
+
     /**
      * Interpret the value of the given property as an RGB color in hex and write this color to a Vector3.
      * E.g. 0xff0000 represents red and sets the vector to (1, 0, 0).

+ 6 - 0
gameplay/src/Scene.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "AudioListener.h"
 #include "Scene.h"
+#include "SceneLoader.h"
 
 namespace gameplay
 {
@@ -36,6 +37,11 @@ Scene* Scene::createScene()
     return new Scene();
 }
 
+Scene* Scene::load(const char* filePath)
+{
+    return SceneLoader::load(filePath);
+}
+
 const char* Scene::getId() const
 {
     return _id.c_str();

+ 9 - 0
gameplay/src/Scene.h

@@ -20,6 +20,15 @@ public:
      */
     static Scene* createScene();
 
+    /**
+     * Loads a scene from the given '.scene' file.
+     * 
+     * @param filePath The path to the '.scene' file to load from.
+     * @return The loaded scene or <code>NULL</code> if the scene
+     *      could not be loaded from the given file.
+     */
+    static Scene* load(const char* filePath);
+
     /**
      * Gets the identifier for the scene.
      *

+ 936 - 0
gameplay/src/SceneLoader.cpp

@@ -0,0 +1,936 @@
+#include "Base.h"
+#include "Game.h"
+#include "Package.h"
+#include "SceneLoader.h"
+
+namespace gameplay
+{
+
+// Static member variables.
+std::map<std::string, Properties*> SceneLoader::_propertiesFromFile;
+std::vector<SceneLoader::SceneAnimation> SceneLoader::_animations;
+std::vector<SceneLoader::SceneNodeProperty> SceneLoader::_nodeProperties;
+std::vector<std::string> SceneLoader::_nodesWithMeshRB;
+std::map<std::string, SceneLoader::MeshRigidBodyData>* SceneLoader::_meshRigidBodyData = NULL;
+
+Scene* SceneLoader::load(const char* filePath)
+{
+    assert(filePath);
+
+    // Load the scene properties from file.
+    Properties* properties = Properties::create(filePath);
+    assert(properties);
+    if (properties == NULL)
+    {
+        WARN_VARG("Failed to load scene file: %s", filePath);
+        return NULL;
+    }
+
+    // Check if the properties object is valid and has a valid namespace.
+    Properties* sceneProperties = properties->getNextNamespace();
+    assert(sceneProperties);
+    if (!sceneProperties || !(strcmp(sceneProperties->getNamespace(), "scene") == 0))
+    {
+        WARN("Failed to load scene from properties object: must be non-null object and have namespace equal to 'scene'.");
+        SAFE_DELETE(properties);
+        return NULL;
+    }
+    
+
+    // Build the node URL/property and animation reference tables and load the referenced files.
+    buildReferenceTables(sceneProperties);
+    loadReferencedFiles();
+
+    // Calculate the node IDs that need to be loaded with mesh rigid body support.
+    calculateNodesWithMeshRigidBodies(sceneProperties);
+
+    // Set up for storing the mesh rigid body data.
+    if (_nodesWithMeshRB.size() > 0)
+    {
+        // We do not currently support loading more than one scene simultaneously.
+        if (_meshRigidBodyData)
+        {
+            WARN("Attempting to load multiple scenes simultaneously; mesh rigid bodies will not load properly.");
+        }
+
+        _meshRigidBodyData = new std::map<std::string, MeshRigidBodyData>();
+    }
+
+    // Load the main scene data from GPB and apply the global scene properties.
+    Scene* scene = loadMainSceneData(sceneProperties);
+    if (!scene)
+    {
+        SAFE_DELETE(properties);
+        return NULL;
+    }
+
+    // First apply the node url properties. Following that,
+    // apply the normal node properties and create the animations.
+    applyNodeUrls(scene);
+    applyNodeProperties(scene, sceneProperties);
+    createAnimations(scene);
+
+    // Find the physics properties object.
+    Properties* physics = NULL;
+    Properties* ns = NULL;
+    sceneProperties->rewind();
+    while (true)
+    {
+        Properties* ns = sceneProperties->getNextNamespace();
+        if (strcmp(ns->getNamespace(), "physics") == 0)
+        {
+            physics = ns;
+            break;
+        }
+    }
+
+    // Load physics properties and constraints.
+    if (physics)
+        loadPhysics(physics, scene);
+
+    // Clean up all loaded properties objects.
+    std::map<std::string, Properties*>::iterator iter = _propertiesFromFile.begin();
+    for (; iter != _propertiesFromFile.end(); iter++)
+    {
+        SAFE_DELETE(iter->second);
+    }
+
+    // Clean up the .scene file's properties object.
+    SAFE_DELETE(properties);
+
+    // Clean up mesh rigid body data.
+    if (_meshRigidBodyData)
+    {
+        std::map<std::string, MeshRigidBodyData>::iterator iter = _meshRigidBodyData->begin();
+        for (; iter != _meshRigidBodyData->end(); iter++)
+        {
+            for (unsigned int i = 0; i < iter->second.indexData.size(); i++)
+            {
+                SAFE_DELETE_ARRAY(iter->second.indexData[i]);
+            }
+
+            SAFE_DELETE_ARRAY(iter->second.vertexData);
+        }
+
+        SAFE_DELETE(_meshRigidBodyData);
+    }
+
+    // Clear all temporary data stores.
+    _propertiesFromFile.clear();
+    _animations.clear();
+    _nodeProperties.clear();
+    _nodesWithMeshRB.clear();
+
+    return scene;
+}
+
+void SceneLoader::addMeshRigidBodyData(std::string id, Mesh* mesh, unsigned char* vertexData, unsigned int vertexByteCount)
+{
+    if (!_meshRigidBodyData)
+    {
+        WARN("Attempting to add mesh rigid body data outside of scene loading; ignoring request.");
+        return;
+    }
+
+    (*_meshRigidBodyData)[id].mesh = mesh;
+    (*_meshRigidBodyData)[id].vertexData = new unsigned char[vertexByteCount];
+    memcpy((*_meshRigidBodyData)[id].vertexData, vertexData, vertexByteCount);
+}
+
+void SceneLoader::addMeshRigidBodyData(std::string id, unsigned char* indexData, unsigned int indexByteCount)
+{
+    if (!_meshRigidBodyData)
+    {
+        WARN("Attempting to add mesh rigid body data outside of scene loading; ignoring request.");
+        return;
+    }
+
+    unsigned char* indexDataCopy = new unsigned char[indexByteCount];
+    memcpy(indexDataCopy, indexData, indexByteCount);
+    (*_meshRigidBodyData)[id].indexData.push_back(indexDataCopy);
+}
+
+void SceneLoader::addSceneAnimation(const char* animationID, const char* targetID, const char* url)
+{
+    // Calculate the file and id from the given url.
+    std::string file;
+    std::string id;
+    splitURL(url, &file, &id);
+    
+    // If there is a file that needs to be loaded later, add an 
+    // empty entry to the properties table to signify it.
+    if (file.length() > 0 && _propertiesFromFile.count(file) == 0)
+        _propertiesFromFile[file] = NULL;
+
+    // Add the animation to the list of animations to be resolved later.
+    _animations.push_back(SceneAnimation(animationID, targetID, file, id));
+}
+
+void SceneLoader::addSceneNodeProperty(SceneNodeProperty::Type type, const char* nodeID, const char* url)
+{
+    // Calculate the file and id from the given url.
+    std::string file;
+    std::string id;
+    splitURL(url, &file, &id);
+    
+    // If there is a non-GPB file that needs to be loaded later, add an 
+    // empty entry to the properties table to signify it.
+    if (file.length() > 0 && file.find(".gpb") == file.npos && _propertiesFromFile.count(file) == 0)
+        _propertiesFromFile[file] = NULL;
+
+    // Add the node property to the list of node properties to be resolved later.
+    _nodeProperties.push_back(SceneNodeProperty(type, nodeID, file, id));
+}
+
+void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* sceneProperties)
+{
+    // Apply all of the remaining scene node properties.
+    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
+    {
+        // If the referenced node doesn't exist in the scene, then we
+        // can't do anything so we skip to the next scene node property.
+        Node* node = scene->findNode(_nodeProperties[i]._nodeID);
+        if (!node)
+        {
+            WARN_VARG("Attempting to set a property for node '%s', which does not exist in the scene.", _nodeProperties[i]._nodeID);
+            continue;
+        }
+
+        if (_nodeProperties[i]._type == SceneNodeProperty::AUDIO ||
+            _nodeProperties[i]._type == SceneNodeProperty::MATERIAL ||
+            _nodeProperties[i]._type == SceneNodeProperty::PARTICLE ||
+            _nodeProperties[i]._type == SceneNodeProperty::RIGIDBODY)
+        {
+            // Check to make sure the referenced properties object was loaded properly.
+            Properties* p = _propertiesFromFile[_nodeProperties[i]._file];
+            if (!p)
+            {
+                WARN_VARG("The referenced node data in file '%s' failed to load.", _nodeProperties[i]._file.c_str());
+                continue;
+            }
+
+            // If a specific namespace within the file was specified, load that namespace.
+            if (_nodeProperties[i]._id.size() > 0)
+            {
+                p = p->getNamespace(_nodeProperties[i]._id.c_str());
+                if (!p)
+                {
+                    WARN_VARG("The referenced node data at '%s#%s' failed to load.", _nodeProperties[i]._file.c_str(), _nodeProperties[i]._id.c_str());
+                    continue;
+                }
+            }
+            else
+            {
+                // Otherwise, use the first namespace.
+                p->rewind();
+                p = p->getNextNamespace();
+            }
+
+            switch (_nodeProperties[i]._type)
+            {
+            case SceneNodeProperty::AUDIO:
+            {
+                AudioSource* audioSource = AudioSource::create(p);
+                node->setAudioSource(audioSource);
+                SAFE_RELEASE(audioSource);
+                break;
+            }
+            case SceneNodeProperty::MATERIAL:
+                if (!node->getModel())
+                    WARN_VARG("Attempting to set a material on node '%s', which has no model.", _nodeProperties[i]._nodeID);
+                else
+                {
+                    Material* material = Material::create(p);
+                    node->getModel()->setMaterial(material);
+                    SAFE_RELEASE(material);
+                }
+
+                break;
+            case SceneNodeProperty::PARTICLE:
+            {
+                ParticleEmitter* particleEmitter = ParticleEmitter::create(p);
+                node->setParticleEmitter(particleEmitter);
+                SAFE_RELEASE(particleEmitter);
+                break;
+            }
+            case SceneNodeProperty::RIGIDBODY:
+            {
+                // If the scene file specifies a rigid body model, use it for creating the rigid body.
+                Properties* np = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
+                const char* name = NULL;
+                if (np && (name = np->getString("rigidbodymodel")))
+                {
+                    Node* modelNode = scene->findNode(name);
+                    if (!modelNode)
+                        WARN_VARG("Node '%s' does not exist; attempting to use its model for rigid body creation.", name);
+                    else
+                    {
+                        if (!modelNode->getModel())
+                            WARN_VARG("Node '%s' does not have a model; attempting to use its model for rigid body creation.", name);
+                        else
+                        {
+                            // Set the specified model during physics rigid body creation.
+                            Model* model = node->getModel();
+                            node->setModel(modelNode->getModel());
+                            node->setPhysicsRigidBody(p);
+                            node->setModel(model);
+                        }
+                    }
+                }
+                else if (!node->getModel())
+                    WARN_VARG("Attempting to set a rigid body on node '%s', which has no model.", _nodeProperties[i]._nodeID);
+                else
+                    node->setPhysicsRigidBody(p);
+
+                break;
+            }
+            default:
+                // This cannot happen.
+                break;
+            }
+        }
+        else
+        {
+            Properties* np = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
+            const char* name = NULL;
+
+            switch (_nodeProperties[i]._type)
+            {
+            case SceneNodeProperty::TRANSLATE:
+            {
+                Vector3 t;
+                if (np && np->getVector3("translate", &t))
+                    node->setTranslation(t);
+                break;
+            }
+            case SceneNodeProperty::ROTATE:
+            {
+                Quaternion r;
+                if (np && np->getQuaternionFromAxisAngle("rotate", &r))
+                    node->setRotation(r);
+                break;
+            }
+            case SceneNodeProperty::SCALE:
+            {
+                Vector3 s;
+                if (np && np->getVector3("scale", &s))
+                    node->setScale(s);
+                break;
+            }
+            default:
+                WARN_VARG("Unsupported node property type: %d.", _nodeProperties[i]._type);
+                break;
+            }
+        }
+
+        
+    }
+}
+
+void SceneLoader::applyNodeUrls(Scene* scene)
+{
+    // Apply all URL node properties so that when we go to apply
+    // the other node properties, the node is in the scene.
+    for (unsigned int i = 0; i < _nodeProperties.size(); )
+    {
+        if (_nodeProperties[i]._type == SceneNodeProperty::URL)
+        {
+            // Make sure that the ID we are using to insert the node into the scene with is unique.
+            if (scene->findNode(_nodeProperties[i]._nodeID) != NULL)
+                WARN_VARG("Attempting to insert or rename a node to an ID that already exists: ID='%s'", _nodeProperties[i]._nodeID);
+            else
+            {
+                // If a file was specified, load the node from file and then insert it into the scene with the new ID.
+                if (_nodeProperties[i]._file.size() > 0)
+                {
+                    Package* tmpPackage = Package::create(_nodeProperties[i]._file.c_str());
+                    if (!tmpPackage)
+                        WARN_VARG("Failed to load GPB file '%s' for node stitching.", _nodeProperties[i]._file.c_str());
+                    else
+                    {
+                        bool loadWithMeshRBSupport = false;
+                        for (unsigned int j = 0; j < _nodesWithMeshRB.size(); j++)
+                        {
+                            if (_nodeProperties[i]._id == _nodesWithMeshRB[j])
+                            {
+                                loadWithMeshRBSupport = true;
+                                break;
+                            }
+                        }
+
+                        Node* node = tmpPackage->loadNode(_nodeProperties[i]._id.c_str(), loadWithMeshRBSupport);
+                        if (!node)
+                            WARN_VARG("Could not load node '%s' in GPB file '%s'.", _nodeProperties[i]._id.c_str(), _nodeProperties[i]._file.c_str());
+                        else
+                        {
+                            node->setId(_nodeProperties[i]._nodeID);
+                            scene->addNode(node);
+                        }
+                        
+                        SAFE_RELEASE(tmpPackage);
+                    }
+                }
+                else
+                {
+                    // TODO: Should we do all nodes with this case first to allow users to stitch in nodes with
+                    // IDs equal to IDs that were in the original GPB file but were changed in the scene file?
+
+                    // Otherwise, the node is from the main GPB and should just be renamed.
+                    Node* node = scene->findNode(_nodeProperties[i]._id.c_str());
+                    if (!node)
+                        WARN_VARG("Could not find node '%s' in main scene GPB file.", _nodeProperties[i]._id.c_str());
+                    else
+                        node->setId(_nodeProperties[i]._nodeID);
+                }
+            }
+
+            // Remove the node property since we are done applying it.
+            _nodeProperties.erase(_nodeProperties.begin() + i);
+        }
+        else
+            i++;
+    }
+}
+
+void SceneLoader::buildReferenceTables(Properties* sceneProperties)
+{
+    // Go through the child namespaces of the scene.
+    Properties* ns;
+    const char* name = NULL;
+    while (ns = sceneProperties->getNextNamespace())
+    {
+        if (strcmp(ns->getNamespace(), "node") == 0)
+        {
+            if (strlen(ns->getId()) == 0)
+            {
+                WARN("Nodes must have an ID; skipping the current node.");
+                continue;
+            }
+
+            while (name = ns->getNextProperty())
+            {
+                if (strcmp(name, "url") == 0)
+                {
+                    addSceneNodeProperty(SceneNodeProperty::URL, ns->getId(), ns->getString());
+                }
+                else if (strcmp(name, "audio") == 0)
+                {
+                    addSceneNodeProperty(SceneNodeProperty::AUDIO, ns->getId(), ns->getString());
+                }
+                else if (strcmp(name, "material") == 0)
+                {
+                    addSceneNodeProperty(SceneNodeProperty::MATERIAL, ns->getId(), ns->getString());
+                }
+                else if (strcmp(name, "particle") == 0)
+                {
+                    addSceneNodeProperty(SceneNodeProperty::PARTICLE, ns->getId(), ns->getString());
+                }
+                else if (strcmp(name, "rigidbody") == 0)
+                {
+                    addSceneNodeProperty(SceneNodeProperty::RIGIDBODY, ns->getId(), ns->getString());
+                }
+                else if (strcmp(name, "rigidbodymodel") == 0)
+                {
+                    // Ignore this for now. We process this when we do rigid body creation.
+                }
+                else if (strcmp(name, "translate") == 0)
+                {
+                    addSceneNodeProperty(SceneNodeProperty::TRANSLATE, ns->getId());
+                }
+                else if (strcmp(name, "rotate") == 0)
+                {
+                    addSceneNodeProperty(SceneNodeProperty::ROTATE, ns->getId());
+                }
+                else if (strcmp(name, "scale") == 0)
+                {
+                    addSceneNodeProperty(SceneNodeProperty::SCALE, ns->getId());
+                }
+                else
+                {
+                    WARN_VARG("Unsupported node property: %s = %s", name, ns->getString());
+                }
+            }
+        }
+        else if (strcmp(ns->getNamespace(), "animations") == 0)
+        {
+            // Load all the animations.
+            Properties* animation;
+            while (animation = ns->getNextNamespace())
+            {
+                if (strcmp(animation->getNamespace(), "animation") == 0)
+                {
+                    const char* animationID = animation->getId();
+                    if (strlen(animationID) == 0)
+                    {
+                        WARN("Animations must have an ID; skipping the current animation.");
+                        continue;
+                    }
+
+                    const char* url = animation->getString("url");
+                    if (!url)
+                    {
+                        WARN_VARG("Animations must have a URL; skipping animation '%s'.", animationID);
+                        continue;
+                    }
+                    const char* targetID = animation->getString("target");
+                    if (!targetID)
+                    {
+                        WARN_VARG("Animations must have a target; skipping animation '%s'.", animationID);
+                        continue;
+                    }
+
+                    addSceneAnimation(animationID, targetID, url);
+                }
+                else
+                {
+                    WARN_VARG("Unsupported child namespace (of 'animations'): %s", ns->getNamespace());
+                }
+            }
+        }
+        else if (strcmp(ns->getNamespace(), "physics") == 0)
+        {
+            // Note: we don't load physics until the whole scene file has been 
+            // loaded so that all node references (i.e. for constraints) can be resolved.
+        }
+        else
+        {
+            WARN_VARG("Unsupported child namespace (of 'scene'): %s", ns->getNamespace());
+        }
+    }
+}
+
+void SceneLoader::calculateNodesWithMeshRigidBodies(const Properties* sceneProperties)
+{
+    const char* name = NULL;
+
+    // Make a list of all nodes with triangle mesh rigid bodies.
+    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
+    {
+        if (_nodeProperties[i]._type == SceneNodeProperty::RIGIDBODY)
+        {
+            Properties* p = _propertiesFromFile[_nodeProperties[i]._file];
+            if (p)
+            {
+                if (_nodeProperties[i]._id.size() > 0)
+                {
+                    p = p->getNamespace(_nodeProperties[i]._id.c_str());
+                }
+                else
+                {
+                    p = p->getNextNamespace();
+                }
+
+                if (p && strcmp(p->getNamespace(), "rigidbody") == 0 &&
+                    strcmp(p->getString("type"), "MESH") == 0)
+                {
+                    // If the node specifies a rigidbodymodel, then use
+                    // that node's ID; otherwise, use its ID.
+                    Properties* p = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
+                    if (p && (name = p->getString("rigidbodymodel")))
+                        _nodesWithMeshRB.push_back(name);
+                    else
+                        _nodesWithMeshRB.push_back(_nodeProperties[i]._nodeID);
+                }
+            }
+        }
+    }
+}
+
+void SceneLoader::createAnimations(const Scene* scene)
+{
+    // Create the scene animations.
+    for (unsigned int i = 0; i < _animations.size(); i++)
+    {
+        // If the target node doesn't exist in the scene, then we
+        // can't do anything so we skip to the next animation.
+        Node* node = scene->findNode(_animations[i]._targetID);
+        if (!node)
+        {
+            WARN_VARG("Attempting to create an animation targeting node '%s', which does not exist in the scene.", _animations[i]._targetID);
+            continue;
+        }
+
+        // Check to make sure the referenced properties object was loaded properly.
+        Properties* p = _propertiesFromFile[_animations[i]._file];
+        if (!p)
+        {
+            WARN_VARG("The referenced animation data in file '%s' failed to load.", _animations[i]._file.c_str());
+            continue;
+        }
+        if (_animations[i]._id.size() > 0)
+        {
+            p = p->getNamespace(_animations[i]._id.c_str());
+            if (!p)
+            {
+                WARN_VARG("The referenced animation data at '%s#%s' failed to load.", _animations[i]._file.c_str(), _animations[i]._id.c_str());
+                continue;
+            }
+        }
+
+        Game::getInstance()->getAnimationController()->createAnimation(_animations[i]._animationID, node, p);
+    }
+}
+
+const SceneLoader::MeshRigidBodyData* SceneLoader::getMeshRigidBodyData(std::string id)
+{
+    if (!_meshRigidBodyData)
+    {
+        WARN("Attempting to get mesh rigid body data, but none has been loaded; ignoring request.");
+        return NULL;
+    }
+
+    return (_meshRigidBodyData->count(id) > 0) ? &(*_meshRigidBodyData)[id] : NULL;
+}
+
+PhysicsConstraint* SceneLoader::loadGenericConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
+{
+    PhysicsGenericConstraint* physicsConstraint;
+
+    // Create the constraint from the specified properties.
+    Quaternion roA;
+    Vector3 toA;
+    bool offsetSpecified = constraint->getQuaternionFromAxisAngle("rotationOffsetA", &roA);
+    offsetSpecified |= constraint->getVector3("translationOffsetA", &toA);
+
+    if (offsetSpecified)
+    {
+        if (rbB)
+        {
+            Quaternion roB;
+            Vector3 toB;
+            constraint->getQuaternionFromAxisAngle("rotationOffsetB", &roB);
+            constraint->getVector3("translationOffsetB", &toB);
+
+            physicsConstraint = Game::getInstance()->getPhysicsController()->createGenericConstraint(rbA, roA, toB, rbB, roB, toB);
+        }
+        else
+        {
+            physicsConstraint = Game::getInstance()->getPhysicsController()->createGenericConstraint(rbA, roA, toA);
+        }
+    }
+    else
+    {
+        physicsConstraint = Game::getInstance()->getPhysicsController()->createGenericConstraint(rbA, rbB);
+    }
+
+    // Set the optional parameters that were specified.
+    Vector3 v;
+    if (constraint->getVector3("angularLowerLimit", &v))
+        physicsConstraint->setAngularLowerLimit(v);
+    if (constraint->getVector3("angularUpperLimit", &v))
+        physicsConstraint->setAngularUpperLimit(v);
+    if (constraint->getVector3("linearLowerLimit", &v))
+        physicsConstraint->setLinearLowerLimit(v);
+    if (constraint->getVector3("linearUpperLimit", &v))
+        physicsConstraint->setLinearUpperLimit(v);
+
+    return physicsConstraint;
+}
+
+PhysicsConstraint* SceneLoader::loadHingeConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
+{
+    PhysicsHingeConstraint* physicsConstraint = NULL;
+
+    // Create the constraint from the specified properties.
+    Quaternion roA;
+    Vector3 toA;
+    constraint->getQuaternionFromAxisAngle("rotationOffsetA", &roA);
+    constraint->getVector3("translationOffsetA", &toA);
+    if (rbB)
+    {
+        Quaternion roB;
+        Vector3 toB;
+        constraint->getQuaternionFromAxisAngle("rotationOffsetB", &roB);
+        constraint->getVector3("translationOffsetB", &toB);
+
+        physicsConstraint = Game::getInstance()->getPhysicsController()->createHingeConstraint(rbA, roA, toB, rbB, roB, toB);
+    }
+    else
+    {
+        physicsConstraint = Game::getInstance()->getPhysicsController()->createHingeConstraint(rbA, roA, toA);
+    }
+
+    // Attempt to load the hinge limits first as a Vector3 and if that doesn't work, try loading as a Vector2.
+    // We do this because the user can specify just the min and max angle, or both angle along with bounciness.
+    Vector3 fullLimits;
+    Vector2 angleLimits;
+    if (constraint->getVector3("limits", &fullLimits))
+        physicsConstraint->setLimits(MATH_DEG_TO_RAD(fullLimits.x), MATH_DEG_TO_RAD(fullLimits.y), fullLimits.z);
+    else if (constraint->getVector2("limits", &angleLimits))
+        physicsConstraint->setLimits(angleLimits.x, angleLimits.y);
+
+    return physicsConstraint;
+}
+
+Scene* SceneLoader::loadMainSceneData(const Properties* sceneProperties)
+{
+    // Load the main scene from the specified path.
+    const char* path = sceneProperties->getString("path");
+    Package* package = Package::create(path);
+    if (!package)
+    {
+        WARN_VARG("Failed to load scene GPB file '%s'.", path);
+        return NULL;
+    }
+    
+    Scene* scene = package->loadScene(NULL, &_nodesWithMeshRB);
+    if (!scene)
+    {
+        WARN_VARG("Failed to load scene from '%s'.", path);
+        SAFE_RELEASE(package);
+        return NULL;
+    }
+
+    // Go through the supported scene properties and apply them to the scene.
+    const char* name = sceneProperties->getString("activeCamera");
+    if (name)
+    {
+        Node* camera = scene->findNode(name);
+        if (camera && camera->getCamera())
+            scene->setActiveCamera(camera->getCamera());
+    }
+
+    SAFE_RELEASE(package);
+    return scene;
+}
+
+void SceneLoader::loadPhysics(Properties* physics, Scene* scene)
+{
+    // Go through the supported global physics properties and apply them.
+    Vector3 gravity;
+    if (physics->getVector3("gravity", &gravity))
+        Game::getInstance()->getPhysicsController()->setGravity(gravity);
+
+    Properties* constraint;
+    const char* name;
+    while (constraint = physics->getNextNamespace())
+    {
+        if (strcmp(constraint->getNamespace(), "constraint") == 0)
+        {
+            // Get the constraint type.
+            std::string type = constraint->getString("type");
+
+            // Attempt to load the first rigid body. If the first rigid body cannot
+            // be loaded or found, then continue to the next constraint (error).
+            name = constraint->getString("rigidBodyA");
+            if (!name)
+            {
+                WARN_VARG("Missing property 'rigidBodyA' for constraint %s", constraint->getId());
+                continue;
+            }
+            Node* rbANode = scene->findNode(name);
+            if (!rbANode)
+            {
+                WARN_VARG("Node '%s' to be used as 'rigidBodyA' for constraint %s cannot be found.", name, constraint->getId());
+                continue;
+            }
+            PhysicsRigidBody* rbA = rbANode->getPhysicsRigidBody();
+            if (!rbA)
+            {
+                WARN_VARG("Node '%s' to be used as 'rigidBodyA' does not have a rigid body.", name);
+                continue;
+            }
+
+            // Attempt to load the second rigid body. If the second rigid body is not
+            // specified, that is usually okay (only spring constraints require both and
+            // we check that below), but if the second rigid body is specified and it doesn't
+            // load properly, then continue to the next constraint (error).
+            name = constraint->getString("rigidBodyB");
+            PhysicsRigidBody* rbB = NULL;
+            if (name)
+            {
+                Node* rbBNode = scene->findNode(name);
+                if (!rbBNode)
+                {
+                    WARN_VARG("Node '%s' to be used as 'rigidBodyB' for constraint %s cannot be found.", name, constraint->getId());
+                    continue;
+                }
+                rbB = rbBNode->getPhysicsRigidBody();
+                if (!rbB)
+                {
+                    WARN_VARG("Node '%s' to be used as 'rigidBodyB' does not have a rigid body.", name);
+                    continue;
+                }
+            }
+
+            PhysicsConstraint* physicsConstraint = NULL;
+
+            // Load the constraint based on its type.
+            if (type == "FIXED")
+            {
+                physicsConstraint = Game::getInstance()->getPhysicsController()->createFixedConstraint(rbA, rbB);
+            }
+            else if (type == "GENERIC")
+            {
+                physicsConstraint = loadGenericConstraint(constraint, rbA, rbB);
+            }
+            else if (type == "HINGE")
+            {
+                physicsConstraint = loadHingeConstraint(constraint, rbA, rbB);
+            }
+            else if (type == "SOCKET")
+            {
+                physicsConstraint = loadSocketConstraint(constraint, rbA, rbB);
+            }
+            else if (type == "SPRING")
+            {
+                physicsConstraint = loadSpringConstraint(constraint, rbA, rbB);
+            }
+            
+            // If the constraint failed to load, continue on to the next one.
+            if (!physicsConstraint)
+                    continue;
+
+            // If the breaking impulse was specified, apply it to the constraint.
+            if (constraint->getString("breakingImpulse"))
+                physicsConstraint->setBreakingImpulse(constraint->getFloat("breakingImpulse"));
+        }
+        else
+        {
+            WARN_VARG("Unsupported child namespace (of 'physics'): %s", physics->getNamespace());
+        }
+    }
+}
+
+void SceneLoader::loadReferencedFiles()
+{
+    // Load all referenced properties files.
+    std::map<std::string, Properties*>::iterator iter = _propertiesFromFile.begin();
+    for (; iter != _propertiesFromFile.end(); iter++)
+    {
+        Properties* p = Properties::create(iter->first.c_str());
+        assert(p);
+        if (p == NULL)
+            WARN_VARG("Failed to load referenced file: %s", iter->first.c_str());
+
+        iter->second = p;
+    }
+}
+
+PhysicsConstraint* SceneLoader::loadSocketConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
+{
+    PhysicsSocketConstraint* physicsConstraint = NULL;
+    Vector3 toA;
+    bool offsetSpecified = constraint->getVector3("translationOffsetA", &toA);
+
+    if (offsetSpecified)
+    {
+        if (rbB)
+        {
+            Vector3 toB;
+            constraint->getVector3("translationOffsetB", &toB);
+
+            physicsConstraint = Game::getInstance()->getPhysicsController()->createSocketConstraint(rbA, toA, rbB, toB);
+        }
+        else
+        {
+            physicsConstraint = Game::getInstance()->getPhysicsController()->createSocketConstraint(rbA, toA);
+        }
+    }
+    else
+    {
+        physicsConstraint = Game::getInstance()->getPhysicsController()->createSocketConstraint(rbA, rbB);
+    }
+
+    return physicsConstraint;
+}
+
+PhysicsConstraint* SceneLoader::loadSpringConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
+{
+    if (!rbB)
+    {
+        WARN("Spring constraints require two rigid bodies.");
+        return NULL;
+    }
+
+    PhysicsSpringConstraint* physicsConstraint = NULL;
+
+    // Create the constraint from the specified properties.
+    Quaternion roA, roB;
+    Vector3 toA, toB;
+    bool offsetsSpecified = constraint->getQuaternionFromAxisAngle("rotationOffsetA", &roA);
+    offsetsSpecified |= constraint->getVector3("translationOffsetA", &toA);
+    offsetsSpecified |= constraint->getQuaternionFromAxisAngle("rotationOffsetB", &roB);
+    offsetsSpecified |= constraint->getVector3("translationOffsetB", &toB);
+
+    if (offsetsSpecified)
+    {
+        physicsConstraint = Game::getInstance()->getPhysicsController()->createSpringConstraint(rbA, roA, toB, rbB, roB, toB);
+    }
+    else
+    {
+        physicsConstraint = Game::getInstance()->getPhysicsController()->createSpringConstraint(rbA, rbB);
+    }
+
+    // Set the optional parameters that were specified.
+    Vector3 v;
+    if (constraint->getVector3("angularLowerLimit", &v))
+        physicsConstraint->setAngularLowerLimit(v);
+    if (constraint->getVector3("angularUpperLimit", &v))
+        physicsConstraint->setAngularUpperLimit(v);
+    if (constraint->getVector3("linearLowerLimit", &v))
+        physicsConstraint->setLinearLowerLimit(v);
+    if (constraint->getVector3("linearUpperLimit", &v))
+        physicsConstraint->setLinearUpperLimit(v);
+    if (constraint->getString("angularDampingX"))
+        physicsConstraint->setAngularDampingX(constraint->getFloat("angularDampingX"));
+    if (constraint->getString("angularDampingY"))
+        physicsConstraint->setAngularDampingY(constraint->getFloat("angularDampingY"));
+    if (constraint->getString("angularDampingZ"))
+        physicsConstraint->setAngularDampingZ(constraint->getFloat("angularDampingZ"));
+    if (constraint->getString("angularStrengthX"))
+        physicsConstraint->setAngularStrengthX(constraint->getFloat("angularStrengthX"));
+    if (constraint->getString("angularStrengthY"))
+        physicsConstraint->setAngularStrengthY(constraint->getFloat("angularStrengthY"));
+    if (constraint->getString("angularStrengthZ"))
+        physicsConstraint->setAngularStrengthZ(constraint->getFloat("angularStrengthZ"));
+    if (constraint->getString("linearDampingX"))
+        physicsConstraint->setLinearDampingX(constraint->getFloat("linearDampingX"));
+    if (constraint->getString("linearDampingY"))
+        physicsConstraint->setLinearDampingY(constraint->getFloat("linearDampingY"));
+    if (constraint->getString("linearDampingZ"))
+        physicsConstraint->setLinearDampingZ(constraint->getFloat("linearDampingZ"));
+    if (constraint->getString("linearStrengthX"))
+        physicsConstraint->setLinearStrengthX(constraint->getFloat("linearStrengthX"));
+    if (constraint->getString("linearStrengthY"))
+        physicsConstraint->setLinearStrengthY(constraint->getFloat("linearStrengthY"));
+    if (constraint->getString("linearStrengthZ"))
+        physicsConstraint->setLinearStrengthZ(constraint->getFloat("linearStrengthZ"));
+
+    return physicsConstraint;
+}
+
+void SceneLoader::splitURL(const char* url, std::string* file, std::string* id)
+{
+    if (!url)
+        return;
+
+    std::string urlString = url;
+
+    // Check if the url references a file (otherwise, it only references a node within the main GPB).
+    unsigned int loc = urlString.rfind(".");
+    if (loc != urlString.npos)
+    {
+        // If the url references a specific namespace within the file,
+        // set the id out parameter appropriately. Otherwise, set the id out
+        // parameter to the empty string so we know to load the first namespace.
+        loc = urlString.rfind("#");
+        if (loc != urlString.npos)
+        {
+            *file = urlString.substr(0, loc);
+            *id = urlString.substr(loc + 1);
+        }
+        else
+        {
+            *file = url;
+            *id = std::string();
+        }
+    }
+    else
+    {
+        *file = std::string();
+        *id = url;
+    }
+}
+
+}

+ 100 - 0
gameplay/src/SceneLoader.h

@@ -0,0 +1,100 @@
+#ifndef SCENELOADER_H_
+#define SCENELOADER_H_
+
+#include "Base.h"
+#include "Mesh.h"
+#include "PhysicsRigidBody.h"
+#include "Properties.h"
+#include "Scene.h"
+
+namespace gameplay
+{
+
+/**
+ * Helper class for loading scenes from .scene files.
+ */
+class SceneLoader
+{
+    friend class Package;
+    friend class PhysicsController;
+    friend class Scene;
+
+private:
+
+    // Main interface to Scene::load(const char*).
+    static Scene* load(const char* filePath);
+    
+    // ------------------------------------------------------------------------
+    // Helper structures and functions for SceneLoader::load(const char*).
+
+    struct MeshRigidBodyData
+    {
+        Mesh* mesh;
+        unsigned char* vertexData;
+        std::vector<unsigned char*> indexData;
+    };
+
+    struct SceneAnimation
+    {
+        SceneAnimation(const char* animationID, const char* targetID, std::string file, std::string id)
+            : _animationID(animationID), _targetID(targetID), _file(file), _id(id) {}
+
+        const char* _animationID;
+        const char* _targetID;
+        std::string _file;
+        std::string _id;
+    };
+
+    struct SceneNodeProperty
+    {
+        enum Type { AUDIO, MATERIAL, PARTICLE, RIGIDBODY, TRANSLATE, ROTATE, SCALE, URL };
+
+        SceneNodeProperty(Type type, const char* nodeID, std::string file, std::string id)
+            : _type(type), _nodeID(nodeID), _file(file), _id(id) {}
+
+        Type _type;
+        const char* _nodeID;
+        std::string _file;
+        std::string _id;
+    };
+
+    static void addMeshRigidBodyData(std::string id, Mesh* mesh, unsigned char* vertexData, unsigned int vertexByteCount);
+    static void addMeshRigidBodyData(std::string id, unsigned char* indexData, unsigned int indexByteCount);
+    static void addSceneAnimation(const char* animationID, const char* targetID, const char* url);
+    static void addSceneNodeProperty(SceneNodeProperty::Type type, const char* nodeID, const char* url = NULL);
+    static void applyNodeProperties(const Scene* scene, const Properties* sceneProperties);
+    static void applyNodeUrls(Scene* scene);
+    static void buildReferenceTables(Properties* sceneProperties);
+    static void calculateNodesWithMeshRigidBodies(const Properties* sceneProperties);
+    static void createAnimations(const Scene* scene);
+    static const MeshRigidBodyData* getMeshRigidBodyData(std::string id);
+    static PhysicsConstraint* loadGenericConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
+    static PhysicsConstraint* loadHingeConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
+    static Scene* loadMainSceneData(const Properties* sceneProperties);
+    static void loadPhysics(Properties* physics, Scene* scene);
+    static void loadReferencedFiles();
+    static PhysicsConstraint* loadSocketConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
+    static PhysicsConstraint* loadSpringConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
+    static void splitURL(const char* url, std::string* file, std::string* id);
+    
+    // ------------------------------------------------------------------------
+
+    // Holds the properties object for a given file path.
+    static std::map<std::string, Properties*> _propertiesFromFile;
+
+    // Holds the animations declared in the .scene file.
+    static std::vector<SceneAnimation> _animations;
+
+    // Holds all the node properties declared in the .scene file.
+    static std::vector<SceneNodeProperty> _nodeProperties;
+
+    // Holds the node IDs that need to be loaded with mesh rigid body support.
+    static std::vector<std::string> _nodesWithMeshRB;
+
+    // Stores the mesh data needed for triangle mesh rigid body support.
+    static std::map<std::string, MeshRigidBodyData>* _meshRigidBodyData;
+};
+
+}
+
+#endif

+ 5 - 3
gameplay/src/gameplay.h

@@ -58,12 +58,14 @@
 #include "AnimationClip.h"
 
 // Physics
-#include "PhysicsConstraint.h"
 #include "PhysicsController.h"
+#include "PhysicsConstraint.h"
 #include "PhysicsFixedConstraint.h"
 #include "PhysicsGenericConstraint.h"
 #include "PhysicsHingeConstraint.h"
-#include "PhysicsMotionState.h"
-#include "PhysicsRigidBody.h"
 #include "PhysicsSocketConstraint.h"
 #include "PhysicsSpringConstraint.h"
+#include "PhysicsRigidBody.h"
+#include "PhysicsMotionState.h"
+
+