Prechádzať zdrojové kódy

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

Conflicts:
	gameplay/src/Container.cpp
	gameplay/src/Form.cpp
Adam Blake 14 rokov pred
rodič
commit
e7662b7951
60 zmenil súbory, kde vykonal 1985 pridanie a 659 odobranie
  1. 1 1
      gameplay-encoder/gameplay-binary.txt
  2. 1 1
      gameplay-encoder/src/FBXSceneEncoder.cpp
  3. 2 2
      gameplay-encoder/src/Mesh.cpp
  4. 2 2
      gameplay-encoder/src/MeshSkin.cpp
  5. 2 0
      gameplay/gameplay.vcxproj
  6. 7 1
      gameplay/gameplay.vcxproj.filters
  7. 17 5
      gameplay/res/shaders/colored-specular.fsh
  8. 11 2
      gameplay/res/shaders/colored-specular.vsh
  9. 2 1
      gameplay/res/shaders/colored.fsh
  10. 5 2
      gameplay/res/shaders/diffuse-specular.fsh
  11. 9 11
      gameplay/res/shaders/diffuse-specular.vsh
  12. 2 1
      gameplay/res/shaders/diffuse.fsh
  13. 12 0
      gameplay/src/Animation.cpp
  14. 5 0
      gameplay/src/Animation.h
  15. 16 0
      gameplay/src/AnimationController.cpp
  16. 5 0
      gameplay/src/AnimationController.h
  17. 1 0
      gameplay/src/Base.h
  18. 2 1
      gameplay/src/Camera.cpp
  19. 5 0
      gameplay/src/Container.cpp
  20. 4 0
      gameplay/src/Font.cpp
  21. 5 5
      gameplay/src/Font.h
  22. 36 32
      gameplay/src/Game.cpp
  23. 3 9
      gameplay/src/Game.h
  24. 0 7
      gameplay/src/Game.inl
  25. 3 0
      gameplay/src/Label.cpp
  26. 7 7
      gameplay/src/Material.cpp
  27. 5 0
      gameplay/src/Mesh.cpp
  28. 10 0
      gameplay/src/Mesh.h
  29. 8 3
      gameplay/src/Model.cpp
  30. 11 2
      gameplay/src/Model.h
  31. 31 7
      gameplay/src/Node.cpp
  32. 17 1
      gameplay/src/Node.h
  33. 151 104
      gameplay/src/Package.cpp
  34. 49 29
      gameplay/src/Package.h
  35. 683 0
      gameplay/src/PhysicsCharacter.cpp
  36. 317 0
      gameplay/src/PhysicsCharacter.h
  37. 1 1
      gameplay/src/PhysicsConstraint.cpp
  38. 86 29
      gameplay/src/PhysicsController.cpp
  39. 46 4
      gameplay/src/PhysicsController.h
  40. 1 0
      gameplay/src/PhysicsFixedConstraint.cpp
  41. 1 1
      gameplay/src/PhysicsGenericConstraint.cpp
  42. 1 1
      gameplay/src/PhysicsHingeConstraint.cpp
  43. 1 0
      gameplay/src/PhysicsMotionState.cpp
  44. 1 0
      gameplay/src/PhysicsMotionState.h
  45. 0 1
      gameplay/src/PhysicsRigidBody.cpp
  46. 15 0
      gameplay/src/PhysicsRigidBody.h
  47. 11 1
      gameplay/src/PhysicsRigidBody.inl
  48. 1 1
      gameplay/src/PhysicsSocketConstraint.cpp
  49. 1 1
      gameplay/src/PhysicsSpringConstraint.cpp
  50. 20 25
      gameplay/src/PlatformQNX.cpp
  51. 7 1
      gameplay/src/PlatformWin32.cpp
  52. 3 5
      gameplay/src/Properties.cpp
  53. 16 0
      gameplay/src/RenderState.cpp
  54. 10 0
      gameplay/src/RenderState.h
  55. 1 1
      gameplay/src/Scene.cpp
  56. 1 1
      gameplay/src/Scene.h
  57. 284 323
      gameplay/src/SceneLoader.cpp
  58. 28 26
      gameplay/src/SceneLoader.h
  59. 1 1
      gameplay/src/Theme.cpp
  60. 1 0
      gameplay/src/gameplay.h

+ 1 - 1
gameplay-encoder/gameplay-binary.txt

@@ -198,9 +198,9 @@ Reference
 34->Mesh
                 vertexFormat            VertexElement[] { enum VertexUsage usage, unint size }
                 vertices                byte[]
-                parts                   MeshPart[]
                 boundingBox             BoundingBox { float[3] min, float[3] max }
                 boundingSphere          BoundingSphere { float[3] center, float radius }
+                parts                   MeshPart[]
 ------------------------------------------------------------------------------------------------------
 35->MeshPart
                 primitiveType           enum PrimitiveType

+ 1 - 1
gameplay-encoder/src/FBXSceneEncoder.cpp

@@ -269,7 +269,7 @@ void FBXSceneEncoder::loadAnimationChannels(KFbxAnimLayer* animLayer, KFbxNode*
     // TODO: Ignore properties that are not animated (scale, rotation, translation)
     // This should result in only one animation channel per animated node.
 
-    float startTime = FLT_MAX, stopTime = -1.0f, frameRate = FLT_MIN;
+    float startTime = FLT_MAX, stopTime = -1.0f, frameRate = -FLT_MAX;
     bool tx = false, ty = false, tz = false, rx = false, ry = false, rz = false, sx = false, sy = false, sz = false;
     KFbxAnimCurve* animCurve = NULL;
     animCurve = fbxNode->LclTranslation.GetCurve<KFbxAnimCurve>(animLayer, KFCURVENODE_T_X);

+ 2 - 2
gameplay-encoder/src/Mesh.cpp

@@ -156,7 +156,7 @@ void Mesh::generateHeightmap(const char* filename)
     float* heights = new float[width * height];
     int index = 0;
     float minHeight = FLT_MAX;
-    float maxHeight = FLT_MIN;
+    float maxHeight = -FLT_MAX;
     for (int z = minZ; z <= maxZ; z++)
     {
         rayOrigin.z = (float)z;
@@ -390,7 +390,7 @@ void Mesh::computeBounds()
     }
 
     bounds.min.x = bounds.min.y = bounds.min.z = FLT_MAX;
-    bounds.max.x = bounds.max.y = bounds.max.z = FLT_MIN;
+    bounds.max.x = bounds.max.y = bounds.max.z = -FLT_MAX;
     bounds.center.x = bounds.center.y = bounds.center.z = 0.0f;
     bounds.radius = 0.0f;
 

+ 2 - 2
gameplay-encoder/src/MeshSkin.cpp

@@ -225,7 +225,7 @@ void MeshSkin::computeBounds()
         vertices.clear();
         BoundingVolume jointBounds;
         jointBounds.min.set(FLT_MAX, FLT_MAX, FLT_MAX);
-        jointBounds.max.set(FLT_MIN, FLT_MIN, FLT_MIN);
+        jointBounds.max.set(-FLT_MAX, -FLT_MAX, -FLT_MAX);
         for (unsigned int j = 0; j < vertexCount; ++j)
         {
             const Vertex& v = _mesh->getVertex(j);
@@ -354,7 +354,7 @@ void MeshSkin::computeBounds()
     Matrix temp;
     Matrix* jointTransforms = new Matrix[jointCount];
     _mesh->bounds.min.set(FLT_MAX, FLT_MAX, FLT_MAX);
-    _mesh->bounds.max.set(FLT_MIN, FLT_MIN, FLT_MIN);
+    _mesh->bounds.max.set(-FLT_MAX, -FLT_MAX, -FLT_MAX);
     _mesh->bounds.center.set(0, 0, 0);
     _mesh->bounds.radius = 0;
     Vector3 skinnedPos;

+ 2 - 0
gameplay/gameplay.vcxproj

@@ -61,6 +61,7 @@
     <ClCompile Include="src\Node.cpp" />
     <ClCompile Include="src\Package.cpp" />
     <ClCompile Include="src\ParticleEmitter.cpp" />
+    <ClCompile Include="src\PhysicsCharacter.cpp" />
     <ClCompile Include="src\PhysicsConstraint.cpp" />
     <ClCompile Include="src\PhysicsController.cpp" />
     <ClCompile Include="src\PhysicsFixedConstraint.cpp" />
@@ -148,6 +149,7 @@
     <ClInclude Include="src\Node.h" />
     <ClInclude Include="src\Package.h" />
     <ClInclude Include="src\ParticleEmitter.h" />
+    <ClInclude Include="src\PhysicsCharacter.h" />
     <ClInclude Include="src\PhysicsConstraint.h" />
     <ClInclude Include="src\PhysicsController.h" />
     <ClInclude Include="src\PhysicsFixedConstraint.h" />

+ 7 - 1
gameplay/gameplay.vcxproj.filters

@@ -264,6 +264,9 @@
     <ClCompile Include="src\TextBox.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\PhysicsCharacter.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -518,6 +521,9 @@
     <ClInclude Include="src\TextBox.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\PhysicsCharacter.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\shaders\bumped-specular.vsh">
@@ -652,4 +658,4 @@
       <Filter>src</Filter>
     </None>
   </ItemGroup>
-</Project>
+</Project>

+ 17 - 5
gameplay/res/shaders/colored-specular.fsh

@@ -5,12 +5,17 @@ precision highp float;
 // Uniforms
 uniform vec3 u_lightColor;                      // Light color
 uniform vec3 u_ambientColor;                    // Ambient color
-uniform float u_specularExponent;               // Specular exponent or shininess property.
+uniform float u_specularExponent;               // Specular exponent or shininess property
+#if !defined(VERTEX_COLOR)
 uniform vec4 u_diffuseColor;                    // Diffuse color
+#endif
 
 // Inputs
-varying vec3 v_normalVector;                    // NormalVector in view space.
+varying vec3 v_normalVector;                    // NormalVector in view space
 varying vec3 v_cameraDirection;                 // Camera direction
+#if defined(VERTEX_COLOR)
+varying vec4 v_color;							// Vertex color
+#endif
 
 // Global variables
 vec4 _baseColor;                                // Base color
@@ -24,7 +29,10 @@ void lighting(vec3 normalVector, vec3 cameraDirection, vec3 lightDirection, floa
     _ambientColor = _baseColor.rgb * u_ambientColor;
 
     // Diffuse
-    float diffuseIntensity = attenuation * max(0.0, dot(normalVector, lightDirection));
+	float ddot = abs(dot(normalVector, lightDirection));
+	if (ddot < 0)
+		ddot = abs(ddot) * 0.25f; // simulate light bounce at partial intensity
+    float diffuseIntensity = attenuation * ddot;
     diffuseIntensity = max(0.0, diffuseIntensity);
     _diffuseColor = u_lightColor * _baseColor.rgb * diffuseIntensity;
 
@@ -103,8 +111,12 @@ void applyLight()
 
 void main()
 {
-    // Fetch diffuse color from texture.
-    _baseColor = u_diffuseColor;
+    // Set base diffuse color
+#if defined(VERTEX_COLOR)
+	_baseColor = v_color;
+#else
+	_baseColor = u_diffuseColor;
+#endif
 
     // Apply light
     applyLight();

+ 11 - 2
gameplay/res/shaders/colored-specular.vsh

@@ -7,11 +7,16 @@ uniform vec3 u_cameraPosition;                      // Position of the camera.
 // Inputs
 attribute vec4 a_position;                          // Vertex Position (x, y, z, w)
 attribute vec3 a_normal;                            // Vertex Normal (x, y, z)
-attribute vec2 a_texCoord;                          // Vertex Texture Coordinate (u, v)
+#if defined(VERTEX_COLOR)
+attribute vec4 a_color;
+#endif
 
 // Outputs
-varying vec3 v_normalVector;                        // NormalVector in view space.
+varying vec3 v_normalVector;                        // NormalVector in view space
 varying vec3 v_cameraDirection;                     // Camera direction
+#if defined(VERTEX_COLOR)
+varying vec4 v_color;								// Vertex color
+#endif
 
 #if defined(SKINNING)
 
@@ -189,6 +194,10 @@ void main()
     vec4 positionWorldSpace = u_worldMatrix * position;
     v_cameraDirection = u_cameraPosition - positionWorldSpace.xyz;
 
+#if defined(VERTEX_COLOR)
+	v_color = a_color;
+#endif
+
     // Apply light.
     applyLight(position);
 }

+ 2 - 1
gameplay/res/shaders/colored.fsh

@@ -21,7 +21,8 @@ void lighting(vec3 normalVector, vec3 lightDirection, float attenuation)
     _ambientColor = _baseColor.rgb * u_ambientColor;
 
     // Diffuse
-    float diffuseIntensity = attenuation * max(0.0, dot(normalVector, lightDirection));
+	float ddot = dot(normalVector, lightDirection);
+    float diffuseIntensity = attenuation * ddot;
     diffuseIntensity = max(0.0, diffuseIntensity);
     _diffuseColor = u_lightColor * _baseColor.rgb * diffuseIntensity;
 }

+ 5 - 2
gameplay/res/shaders/diffuse-specular.fsh

@@ -25,12 +25,15 @@ void lighting(vec3 normalVector, vec3 cameraDirection, vec3 lightDirection, floa
     _ambientColor = _baseColor.rgb * u_ambientColor;
 
     // Diffuse
-    float diffuseIntensity = attenuation * max(0.0, dot(normalVector, lightDirection));
+	float ddot = dot(normalVector, lightDirection);
+	if (ddot < 0)
+		ddot = abs(ddot) * 0.25f; // simulate light bounce at partial intensity
+    float diffuseIntensity = attenuation * ddot;
     diffuseIntensity = max(0.0, diffuseIntensity);
     _diffuseColor = u_lightColor * _baseColor.rgb * diffuseIntensity;
 
     // Specular
-    vec3 halfVector = normalize(cameraDirection + lightDirection);
+    vec3 halfVector = normalize(lightDirection + cameraDirection);
     float specularIntensity = attenuation * max(0.0, pow(dot(normalVector, halfVector), u_specularExponent));
     specularIntensity = max(0.0, specularIntensity);
     _specularColor = u_lightColor * _baseColor.rgb * specularIntensity;

+ 9 - 11
gameplay/res/shaders/diffuse-specular.vsh

@@ -1,8 +1,8 @@
 // Uniforms
 uniform mat4 u_worldViewProjectionMatrix;           // Matrix to transform a position to clip space.
 uniform mat4 u_inverseTransposeWorldViewMatrix;     // Matrix to transform a normal to view space.
-uniform mat4 u_worldMatrix;                         // Matrix to tranform a position to world space.
-uniform vec3 u_cameraPosition;                      // Position of the camera.
+uniform mat4 u_worldViewMatrix;                     // Matrix to tranform a position to view space.
+uniform vec3 u_cameraPosition;                      // Position of the camera in view space.
 
 // Inputs
 attribute vec4 a_position;                          // Vertex Position (x, y, z, w)
@@ -117,7 +117,6 @@ vec3 getNormal()
 
 #if defined(POINT_LIGHT)
 
-uniform mat4 u_worldViewMatrix;                         // Matrix to tranform a position to view space.
 uniform vec3 u_pointLightPosition;                      // Position
 uniform float u_pointLightRangeInverse;                 // Inverse of light range.
 varying vec4 v_vertexToPointLightDirection;             // Light direction w.r.t current vertex.
@@ -141,7 +140,6 @@ void applyLight(vec4 position)
 
 #elif defined(SPOT_LIGHT)
 
-uniform mat4 u_worldViewMatrix;                         // Matrix to tranform a position to view space.
 uniform vec3 u_spotLightPosition;                       // Position
 uniform float u_spotLightRangeInverse;                  // Inverse of light range.
 varying vec3 v_vertexToSpotLightDirection;              // Light direction w.r.t current vertex.
@@ -179,18 +177,18 @@ void main()
     gl_Position = u_worldViewProjectionMatrix * position;
 
     // Transform normal to view space.
-    mat3 inverseTransposeWorldViewMatrix = mat3(u_inverseTransposeWorldViewMatrix[0].xyz,
-                                                u_inverseTransposeWorldViewMatrix[1].xyz,
-                                                u_inverseTransposeWorldViewMatrix[2].xyz);
-    v_normalVector = inverseTransposeWorldViewMatrix * normal;
+    mat3 normalMatrix = mat3(u_inverseTransposeWorldViewMatrix[0].xyz,
+                             u_inverseTransposeWorldViewMatrix[1].xyz,
+                             u_inverseTransposeWorldViewMatrix[2].xyz);
+    v_normalVector = normalMatrix * normal;
 
     // Compute the camera direction.
-    vec4 positionWorldSpace = u_worldMatrix * position;
+    vec4 positionWorldSpace = u_worldViewMatrix * position;
     v_cameraDirection = u_cameraPosition - positionWorldSpace.xyz;
 
     // Apply light.
     applyLight(position);
 
-    // Pass on the texture coordinates to Fragment shader.
+	// Pass on the texture coordinates to Fragment shader.
     v_texCoord = a_texCoord;
-}
+}

+ 2 - 1
gameplay/res/shaders/diffuse.fsh

@@ -22,7 +22,8 @@ void lighting(vec3 normalVector, vec3 lightDirection, float attenuation)
     _ambientColor = _baseColor.rgb * u_ambientColor;
 
     // Diffuse
-    float diffuseIntensity = attenuation * max(0.0, dot(normalVector, lightDirection));
+	float ddot = dot(normalVector, lightDirection);
+    float diffuseIntensity = attenuation * ddot;
     diffuseIntensity = max(0.0, diffuseIntensity);
     _diffuseColor = u_lightColor * _baseColor.rgb * diffuseIntensity;
 }

+ 12 - 0
gameplay/src/Animation.cpp

@@ -172,6 +172,18 @@ void Animation::pause(const char * clipId)
     }
 }
 
+bool Animation::targets(AnimationTarget* target) const
+{
+    for (std::vector<Animation::Channel*>::const_iterator itr = _channels.begin(); itr != _channels.end(); ++itr)
+    {
+        if ((*itr)->_target == target)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
 void Animation::createDefaultClip()
 {
     _defaultClip = new AnimationClip("default_clip", this, 0.0f, _duration);

+ 5 - 0
gameplay/src/Animation.h

@@ -89,6 +89,11 @@ public:
      */
     void pause(const char* clipId = NULL);
 
+    /**
+     * Returns true if this animation targets the given AnimationTarget.
+     */
+    bool targets(AnimationTarget* target) const;
+
 private:
 
     /**

+ 16 - 0
gameplay/src/AnimationController.cpp

@@ -112,6 +112,22 @@ Animation* AnimationController::getAnimation(const char* id) const
     return NULL;
 }
 
+Animation* AnimationController::getAnimation(AnimationTarget* target) const
+{
+    if (!target)
+        return NULL;
+    const unsigned int animationCount = _animations.size();
+    for (unsigned int i = 0; i < animationCount; ++i)
+    {
+        Animation* animation = _animations[i];
+        if (animation->targets(target))
+        {
+            return animation;
+        }
+    }
+    return NULL;
+}
+
 void AnimationController::stopAllAnimations() 
 {
     std::list<AnimationClip*>::iterator clipIter = _runningClips.begin();

+ 5 - 0
gameplay/src/AnimationController.h

@@ -106,6 +106,11 @@ public:
      */
     Animation* getAnimation(const char* id) const;
 
+    /**
+     * Returns the first animation that targets the given AnimationTarget.
+     */
+    Animation* getAnimation(AnimationTarget* target) const;
+
     /** 
      * Stops all AnimationClips currently playing on the AnimationController.
      */

+ 1 - 0
gameplay/src/Base.h

@@ -103,6 +103,7 @@ extern void printError(const char* format, ...);
 
 // Bullet Physics
 #include <btBulletDynamicsCommon.h>
+#include <BulletCollision/CollisionDispatch/btGhostObject.h>
 #define BV(v) (btVector3((v).x, (v).y, (v).z))
 #define BQ(q) (btQuaternion((q).x, (q).y, (q).z, (q).w))
 

+ 2 - 1
gameplay/src/Camera.cpp

@@ -2,6 +2,8 @@
 #include "Camera.h"
 #include "Game.h"
 #include "Node.h"
+#include "Game.h"
+#include "PhysicsController.h"
 
 // Camera dirty bits
 #define CAMERA_DIRTY_VIEW 1
@@ -341,7 +343,6 @@ void Camera::pickRay(const Viewport* viewport, float x, float y, Ray* dst)
     dst->set(nearPoint, direction);
 }
 
-
 void Camera::transformChanged(Transform* transform, long cookie)
 {
     _dirtyBits |= CAMERA_DIRTY_VIEW | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;

+ 5 - 0
gameplay/src/Container.cpp

@@ -271,6 +271,11 @@ namespace gameplay
 
     bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
     {
+        if (!isEnabled())
+        {
+            return false;
+        }
+
         bool eventConsumed = false;
 
         std::vector<Control*>::const_iterator it;

+ 4 - 0
gameplay/src/Font.cpp

@@ -176,6 +176,8 @@ void Font::begin()
 
 void Font::drawText(const char* text, int x, int y, const Vector4& color, unsigned int size, bool rightToLeft)
 {
+    if (size == 0)
+        size = _size;
     float scale = (float)size / _size;
     const char* cursor = NULL;
 
@@ -289,6 +291,8 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
 
 void Font::drawText(const char* text, const Rectangle& area, const Vector4& color, unsigned int size, Justify justify, bool wrap, bool rightToLeft)
 {
+    if (size == 0)
+        size = _size;
     float scale = (float)size / _size;
     const char* token = text;
     const int length = strlen(text);

+ 5 - 5
gameplay/src/Font.h

@@ -132,9 +132,9 @@ public:
      * @param x The viewport x position to draw text at.
      * @param y The viewport y position to draw text at.
      * @param color The color of text.
-     * @param size The size to draw text.
+     * @param size The size to draw text (0 for default size).
      */
-    void drawText(const char* text, int x, int y, const Vector4& color, unsigned int size, bool rightToLeft = false);
+    void drawText(const char* text, int x, int y, const Vector4& color, unsigned int size = 0, bool rightToLeft = false);
 
     /**
      * Draws the specified text within a rectangular area, with a specified alignment and scale.
@@ -143,13 +143,13 @@ public:
      * @param text The text to draw.
      * @param clip The viewport area to draw within.  Text will be clipped outside this rectangle.
      * @param color The color of text.
-     * @param size The size to draw text.
+     * @param size The size to draw text (0 for default size).
      * @param justify Justification of text within the viewport.
      * @param wrap Wraps text to fit within the width of the viewport if true.
      * @param rightToLeft
      */
-    void drawText(const char* text, const Rectangle& clip, const Vector4& color, unsigned int size,
-                  Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+    void drawText(const char* text, const Rectangle& clip, const Vector4& color,
+        unsigned int size = 0, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
 
     /**
      * Measures a string's width and height without alignment, wrapping or clipping.

+ 36 - 32
gameplay/src/Game.cpp

@@ -161,45 +161,49 @@ void Game::exit()
 
 void Game::frame()
 {
-    if (_state != RUNNING)
+    if (!_initialized)
     {
-        return;
+        initialize();
+        _initialized = true;
     }
-    else
+
+    if (_state == Game::RUNNING)
     {
-        if (!_initialized)
+        // Update Time.
+        static long lastFrameTime = Game::getGameTime();
+        long frameTime = Game::getGameTime();
+        long elapsedTime = (frameTime - lastFrameTime);
+        lastFrameTime = frameTime;
+
+        // Update the scheduled and running animations.
+        _animationController->update(elapsedTime);
+    
+        // Update the physics.
+        _physicsController->update(elapsedTime);
+        // Application Update.
+        update(elapsedTime);
+
+        // Audio Rendering.
+        _audioController->update(elapsedTime);
+        // Graphics Rendering.
+        render(elapsedTime);
+
+        // Update FPS.
+        ++_frameCount;
+        if ((Game::getGameTime() - _frameLastFPS) >= 1000)
         {
-            initialize();
-            _initialized = true;
+            _frameRate = _frameCount;
+            _frameCount = 0;
+            _frameLastFPS = Game::getGameTime();
         }
     }
-
-    // Update Time.
-    static long lastFrameTime = Game::getGameTime();
-    long frameTime = Game::getGameTime();
-    long elapsedTime = (frameTime - lastFrameTime);
-    lastFrameTime = frameTime;
-
-    // Update the scheduled and running animations.
-    _animationController->update(elapsedTime);
-    
-    // Update the physics.
-    _physicsController->update(elapsedTime);
-    // Application Update.
-    update(elapsedTime);
-
-    // Audio Rendering.
-    _audioController->update(elapsedTime);
-    // Graphics Rendering.
-    render(elapsedTime);
-
-    // Update FPS.
-    ++_frameCount;
-    if ((Game::getGameTime() - _frameLastFPS) >= 1000)
+    else
     {
-        _frameRate = _frameCount;
-        _frameCount = 0;
-        _frameLastFPS = Game::getGameTime();
+        // Application Update.
+        update(0);
+
+        // Graphics Rendering.
+        render(0);
     }
 }
 

+ 3 - 9
gameplay/src/Game.h

@@ -300,15 +300,9 @@ protected:
     void renderOnce(T* instance, void (T::*method)(void*), void* cookie);
 
     /**
-     * Updates the game once.
-     *
-     * This is useful for rendering animated splash screens.
-     */
-    template <class T>
-    void updateOnce(T* instance, void (T::*method)(void*), void* cookie);
-
-    /**
-     * Updates the game once.
+     * Updates the game's internal systems (audio, animation, physics) once.
+     * 
+     * Note: This does not call the user-defined Game::update() function.
      *
      * This is useful for rendering animated splash screens.
      */

+ 0 - 7
gameplay/src/Game.inl

@@ -46,13 +46,6 @@ void Game::renderOnce(T* instance, void (T::*method)(void*), void* cookie)
     Platform::swapBuffers();
 }
 
-template <class T>
-void Game::updateOnce(T* instance, void (T::*method)(void*), void* cookie)
-{
-    updateOnce();
-    (instance->*method)(cookie);
-}
-
 inline void Game::setMultiTouch(bool enabled)
 {
     Platform::setMultiTouch(enabled);

+ 3 - 0
gameplay/src/Label.cpp

@@ -103,6 +103,9 @@ namespace gameplay
 
     void Label::drawText(const Vector2& position)
     {
+        if (_text.size() <= 0)
+            return;
+
         // TODO: Batch all labels that use the same font.
         Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
         Font* font = overlay->getFont();

+ 7 - 7
gameplay/src/Material.cpp

@@ -212,17 +212,17 @@ bool Material::loadPass(Technique* technique, Properties* passProperties)
     const char* fragmentShaderPath = passProperties->getString("fragmentShader");
     assert(fragmentShaderPath);
     const char* defines = passProperties->getString("defines");
-    std::string define = "";
+    std::string define;
     if (defines != NULL)
     {
-        char* token = strtok((char*)defines, ";");
-        while (token)
+        define = defines;
+        define.insert(0, "#define ");
+        unsigned int pos;
+        while ((pos = define.find(';')) != std::string::npos)
         {
-            define += "#define ";
-            define += token;
-            define += "\n";
-            token = strtok(NULL, ";");
+            define.replace(pos, 1, "\n#define ");
         }
+        define += "\n";
     }
 
     // Create the pass

+ 5 - 0
gameplay/src/Mesh.cpp

@@ -235,6 +235,11 @@ Mesh* Mesh::createBoundingBox(const BoundingBox& box)
     return mesh;
 }
 
+const char* Mesh::getUrl() const
+{
+    return _url.c_str();
+}
+
 const VertexFormat& Mesh::getVertexFormat() const
 {
     return _vertexFormat;

+ 10 - 0
gameplay/src/Mesh.h

@@ -123,6 +123,15 @@ public:
      */
     static Mesh* createBoundingBox(const BoundingBox& box);
 
+    /**
+     * Returns a URL from which the mesh was loaded from.
+     *
+     * For meshes loaded from a Package, this URL will point
+     * to the file and ID of the mesh within the package. For
+     * all other meshes, an empty string will be returned.
+     */
+    const char* getUrl() const;
+
     /**
      * Gets the vertex format for the mesh.
      *
@@ -295,6 +304,7 @@ private:
      */
     Mesh(const Mesh& copy);
 
+    std::string _url;
     const VertexFormat _vertexFormat;
     unsigned int _vertexCount;
     VertexBufferHandle _vertexBuffer;

+ 8 - 3
gameplay/src/Model.cpp

@@ -190,6 +190,11 @@ Material* Model::setMaterial(const char* materialPath, int partIndex)
     return material;
 }
 
+bool Model::hasPartMaterial(unsigned int partIndex) const
+{
+    return (partIndex >= 0 && partIndex < _partCount && _partMaterials && _partMaterials[partIndex]);
+}
+
 MeshSkin* Model::getSkin()
 {
     return _skin;
@@ -276,15 +281,15 @@ void Model::draw(bool wireframe)
             MeshPart* part = _mesh->getPart(i);
 
             // Get the material for this mesh part.
-            Material* material;
-            if (_partMaterials && i < _partCount && _partMaterials[i])
+            Material* material = getMaterial(i);
+            /*if (_partMaterials && i < _partCount && _partMaterials[i])
             {
                 material = _partMaterials[i]; // Use part material
             }
             else
             {
                 material = _material; // Use shared material
-            }
+            }*/
 
             if (material)
             {

+ 11 - 2
gameplay/src/Model.h

@@ -109,6 +109,15 @@ public:
      */
     Material* setMaterial(const char* materialPath, int partIndex = -1);
 
+    /**
+     * Determines if a custom (non-shared) material is set for the specified part index.
+     *
+     * @param partIndex MeshPart index.
+     *
+     * @return True if a custom MeshPart material is set for the specified index, false otherwise.
+     */
+    bool hasPartMaterial(unsigned int partIndex) const;
+
     /**
      * Returns the MeshSkin.
      * 
@@ -146,13 +155,13 @@ private:
      * Destructor. Hidden use release() instead.
      */
     ~Model();
-
+
     /**
      * Sets the MeshSkin for this model.
      * 
      * @param skin The MeshSkin for this model.
      */
-    void setSkin(MeshSkin* skin);
+    void setSkin(MeshSkin* skin);
 
     /**
      * Sets the node that is associated with this model.

+ 31 - 7
gameplay/src/Node.cpp

@@ -317,7 +317,7 @@ const Matrix& Node::getWorldMatrix() const
 const Matrix& Node::getWorldViewMatrix() const
 {
     static Matrix worldView;
-    
+
     Matrix::multiply(getViewMatrix(), getWorldMatrix(), &worldView);
 
     return worldView;
@@ -326,18 +326,21 @@ const Matrix& Node::getWorldViewMatrix() const
 const Matrix& Node::getInverseTransposeWorldViewMatrix() const
 {
     static Matrix invTransWorldView;
-
-    // Assume the matrix is always dirty since the camera is moving
-    // almost every frame in most games.
-    //    
-    // TODO: Optimize here to NOT calculate the inverse transpose if the matrix is orthogonal.
     Matrix::multiply(getViewMatrix(), getWorldMatrix(), &invTransWorldView);
     invTransWorldView.invert();
     invTransWorldView.transpose();
-
     return invTransWorldView;
 }
 
+const Matrix& Node::getInverseTransposeWorldMatrix() const
+{
+    static Matrix invTransWorld;
+    invTransWorld = getWorldMatrix();
+    invTransWorld.invert();
+    invTransWorld.transpose();
+    return invTransWorld;
+}
+
 const Matrix& Node::getViewMatrix() const
 {
     Scene* scene = getScene();
@@ -444,6 +447,8 @@ Vector3 Node::getForwardVectorView() const
     Vector3 vector;
     getWorldMatrix().getForwardVector(&vector);
     getViewMatrix().transformVector(&vector);
+    //getForwardVector(&vector);
+    //getWorldViewMatrix().transformVector(&vector);
     return vector;
 }
 
@@ -466,6 +471,25 @@ Vector3 Node::getActiveCameraTranslationWorld() const
     return Vector3::zero();
 }
 
+Vector3 Node::getActiveCameraTranslationView() const
+{
+    Scene* scene = getScene();
+    if (scene)
+    {
+        Camera* camera = scene->getActiveCamera();
+        if (camera)
+        {
+            Node* cameraNode = camera->getNode();
+            if (cameraNode)
+            {
+                return cameraNode->getTranslationView();
+            }
+        }
+    }
+
+    return Vector3::zero();
+}
+
 void Node::hierarchyChanged()
 {
     // When our hierarchy changes our world transform is affected, so we must dirty it.

+ 17 - 1
gameplay/src/Node.h

@@ -173,10 +173,19 @@ public:
      */
     const Matrix& getWorldViewMatrix() const;
 
+    /**
+     * Gets the inverse transpose world matrix corresponding to this node.
+     *
+     * This matrix is typically used to transform normal vectors into world space.
+     *
+     * @return The inverse world matrix of this node.
+     */
+    const Matrix& getInverseTransposeWorldMatrix() const;
+
     /**
      * Gets the inverse transpose world view matrix corresponding to this node.
      *
-     * This matrix is typically used to transform normal vectors.
+     * This matrix is typically used to transform normal vectors into view space.
      *
      * @return The inverse world view matrix of this node.
      */
@@ -261,6 +270,13 @@ public:
      */
     Vector3 getActiveCameraTranslationWorld() const;
 
+    /**
+     * Returns the view-space translation vector of the currently active camera for this node's scene.
+     *
+     * @return The translation vector of the scene's active camera, in view-space.
+     */
+    Vector3 getActiveCameraTranslationView() const;
+
     /**
      * Returns the pointer to this node's camera.
      *

+ 151 - 104
gameplay/src/Package.cpp

@@ -3,7 +3,6 @@
 #include "FileSystem.h"
 #include "MeshPart.h"
 #include "Scene.h"
-#include "SceneLoader.h"
 #include "Joint.h"
 
 #define GPB_PACKAGE_VERSION_MAJOR 1
@@ -319,11 +318,6 @@ 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();
 
@@ -355,7 +349,7 @@ Scene* Package::loadScene(const char* id, const std::vector<std::string>* nodesW
         // Read each child directly into the scene
         for (unsigned int i = 0; i < childrenCount; i++)
         {
-            Node* node = readNode(scene, NULL, nodesWithMeshRB);
+            Node* node = readNode(scene, NULL);
             if (node)
             {
                 scene->addNode(node);
@@ -417,17 +411,12 @@ Scene* Package::loadScene(const char* id, const std::vector<std::string>* nodesW
 }
 
 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, loadWithMeshRBSupport);
+    Node* node = loadNode(id, NULL, NULL);
    
     if (node)
     {
@@ -437,7 +426,7 @@ Node* Package::loadNode(const char* id, bool loadWithMeshRBSupport)
     return node;
 }
 
-Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport)
+Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext)
 {
     assert(id);
 
@@ -463,22 +452,13 @@ Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext,
             return NULL;
         }
 
-        if (loadWithMeshRBSupport)
-        {
-            std::vector<std::string> nodesWithMeshRBSupport;
-            nodesWithMeshRBSupport.push_back(id);
-            node = readNode(sceneContext, nodeContext, &nodesWithMeshRBSupport);
-        }
-        else
-        {
-            node = readNode(sceneContext, nodeContext, NULL);
-        }
+        node = readNode(sceneContext, nodeContext);
     }
 
     return node;
 }
 
-Node* Package::readNode(Scene* sceneContext, Node* nodeContext, const std::vector<std::string>* nodesWithMeshRB)
+Node* Package::readNode(Scene* sceneContext, Node* nodeContext)
 {
     const char* id = getIdFromOffset();
 
@@ -529,7 +509,7 @@ Node* Package::readNode(Scene* sceneContext, Node* nodeContext, const std::vecto
         // Read each child
         for (unsigned int i = 0; i < childrenCount; i++)
         {
-            Node* child = readNode(sceneContext, nodeContext, nodesWithMeshRB);
+            Node* child = readNode(sceneContext, nodeContext);
             if (child)
             {
                 node->addChild(child);
@@ -554,23 +534,8 @@ Node* Package::readNode(Scene* sceneContext, Node* nodeContext, const std::vecto
         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, loadWithMeshRBSupport, node->getId());
+    Model* model = readModel(sceneContext, nodeContext, node->getId());
     if (model)
     {
         node->setModel(model);
@@ -701,14 +666,14 @@ Light* Package::readLight()
     return light;
 }
 
-Model* Package::readModel(Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport, const char* nodeId)
+Model* Package::readModel(Scene* sceneContext, Node* nodeContext, 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, loadWithMeshRBSupport, nodeId);
+        mesh = loadMesh(xref.c_str() + 1, nodeId);
         if (mesh)
         {
             Model* model = Model::create(mesh);
@@ -836,7 +801,7 @@ void Package::resolveJointReferences(Scene* sceneContext, Node* nodeContext)
             {
                 jointId = jointId.substr(1, jointId.length() - 1);
 
-                Node* n = loadNode(jointId.c_str(), sceneContext, nodeContext, false);
+                Node* n = loadNode(jointId.c_str(), sceneContext, nodeContext);
                 if (n && n->getType() == Node::JOINT)
                 {
                     Joint* joint = static_cast<Joint*>(n);
@@ -1012,12 +977,12 @@ Animation* Package::readAnimationChannel(Scene* scene, Animation* animation, con
 
 Mesh* Package::loadMesh(const char* id)
 {
-    return loadMesh(id, false, NULL);
+    return loadMesh(id, false);
 }
 
-Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char* nodeId)
+Mesh* Package::loadMesh(const char* id, const char* nodeId)
 {
-    // save the file position
+    // Save the file position
     long position = ftell(_file);
 
     // Seek to the specified Mesh
@@ -1027,6 +992,56 @@ Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char*
         return NULL;
     }
 
+    // Read mesh data
+    MeshData* meshData = readMeshData();
+    if (meshData == NULL)
+    {
+        return NULL;
+    }
+
+    // Create Mesh
+    Mesh* mesh = Mesh::createMesh(meshData->vertexFormat, meshData->vertexCount, false);
+    if (mesh == NULL)
+    {
+        LOG_ERROR_VARG("Failed to create mesh: %s", id);
+        SAFE_DELETE_ARRAY(meshData);
+        return NULL;
+    }
+
+    mesh->_url = _path;
+    mesh->_url += "#";
+    mesh->_url += id;
+
+    mesh->setVertexData(meshData->vertexData, 0, meshData->vertexCount);
+
+    mesh->_boundingBox.set(meshData->boundingBox);
+    mesh->_boundingSphere.set(meshData->boundingSphere);
+
+    // Create mesh parts
+    for (unsigned int i = 0; i < meshData->parts.size(); ++i)
+    {
+        MeshPartData* partData = meshData->parts[i];
+
+        MeshPart* part = mesh->addPart(partData->primitiveType, partData->indexFormat, partData->indexCount, false);
+        if (part == NULL)
+        {
+            LOG_ERROR_VARG("Failed to create mesh part (i=%d): %s", i, id);
+            SAFE_DELETE(meshData);
+            return NULL;
+        }
+        part->setIndexData(partData->indexData, 0, partData->indexCount);
+    }
+
+    SAFE_DELETE(meshData);
+
+    // Restore file pointer
+    fseek(_file, position, SEEK_SET);
+
+    return mesh;
+}
+
+Package::MeshData* Package::readMeshData()
+{
     // Read vertex format/elements
     unsigned int vertexElementCount;
     if (fread(&vertexElementCount, 4, 1, _file) != 1 || vertexElementCount < 1)
@@ -1047,60 +1062,42 @@ Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char*
         vertexElements[i].size = vSize;
     }
 
-    // Create VertexFormat
-    VertexFormat vertexFormat(vertexElements, vertexElementCount);
+    MeshData* meshData = new MeshData(VertexFormat(vertexElements, vertexElementCount));
+
     SAFE_DELETE_ARRAY(vertexElements);
 
     // Read vertex data
     unsigned int vertexByteCount;
     if (fread(&vertexByteCount, 4, 1, _file) != 1 || vertexByteCount == 0)
     {
+        SAFE_DELETE(meshData);
         return NULL;
     }
-    unsigned char* vertexData = new unsigned char[vertexByteCount];
-    if (fread(vertexData, 1, vertexByteCount, _file) != vertexByteCount)
+    meshData->vertexCount = vertexByteCount / meshData->vertexFormat.getVertexSize();
+    meshData->vertexData = new unsigned char[vertexByteCount];
+    if (fread(meshData->vertexData, 1, vertexByteCount, _file) != vertexByteCount)
     {
-        LOG_ERROR_VARG("Failed to read %d vertex data bytes for mesh: %s", vertexByteCount, id);
+        SAFE_DELETE(meshData);
         return NULL;
     }
 
     // Read mesh bounds (bounding box and bounding sphere)
-    Vector3 boundsMin, boundsMax, boundsCenter;
-    float boundsRadius = 0.0f;
-    if (fread(&boundsMin.x, 4, 3, _file) != 3 || fread(&boundsMax.x, 4, 3, _file) != 3)
-    {
-        LOG_ERROR_VARG("Failed to read bounding box for mesh: %s", id);
-        return NULL;
-    }
-    if (fread(&boundsCenter.x, 4, 3, _file) != 3 || fread(&boundsRadius, 4, 1, _file) != 1)
+    if (fread(&meshData->boundingBox.min.x, 4, 3, _file) != 3 || fread(&meshData->boundingBox.max.x, 4, 3, _file) != 3)
     {
-        LOG_ERROR_VARG("Failed to read bounding sphere for mesh: %s", id);
+        SAFE_DELETE(meshData);
         return NULL;
     }
-
-    // Create Mesh
-    int vertexCount = vertexByteCount / vertexFormat.getVertexSize();
-    Mesh* mesh = Mesh::createMesh(vertexFormat, vertexCount, false);
-    if (mesh == NULL)
+    if (fread(&meshData->boundingSphere.center.x, 4, 3, _file) != 3 || fread(&meshData->boundingSphere.radius, 4, 1, _file) != 1)
     {
-        LOG_ERROR_VARG("Failed to create mesh: %s", id);
-        SAFE_DELETE_ARRAY(vertexData);
+        SAFE_DELETE(meshData);
         return NULL;
     }
-    mesh->setVertexData(vertexData, 0, vertexCount);
-    if (loadWithMeshRBSupport)
-        SceneLoader::addMeshRigidBodyData(_path, nodeId, mesh, vertexData, vertexByteCount);
-    SAFE_DELETE_ARRAY(vertexData);
-
-    // Set mesh bounding volumes
-    mesh->_boundingBox.set(boundsMin, boundsMax);
-    mesh->_boundingSphere.set(boundsCenter, boundsRadius);
 
     // Read mesh parts
     unsigned int meshPartCount;
     if (fread(&meshPartCount, 4, 1, _file) != 1)
     {
-        SAFE_RELEASE(mesh);
+        SAFE_DELETE(meshData);
         return NULL;
     }
     for (unsigned int i = 0; i < meshPartCount; ++i)
@@ -1111,23 +1108,18 @@ Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char*
             fread(&iFormat, 4, 1, _file) != 1 ||
             fread(&iByteCount, 4, 1, _file) != 1)
         {
-            LOG_ERROR_VARG("Failed to read mesh part (i=%d): %s", i, id);
-            SAFE_RELEASE(mesh);
+            SAFE_DELETE(meshData);
             return NULL;
         }
 
-        unsigned char* indexData = new unsigned char[iByteCount];
-        if (fread(indexData, 1, iByteCount, _file) != iByteCount)
-        {
-            LOG_ERROR_VARG("Failed to read %d index data bytes for mesh part (i=%d): %s", iByteCount, i, id);
-            SAFE_DELETE_ARRAY(indexData);
-            SAFE_RELEASE(mesh);
-            return NULL;
-        }
+        MeshPartData* partData = new MeshPartData();
+        meshData->parts.push_back(partData);
 
-        Mesh::IndexFormat indexFormat = (Mesh::IndexFormat)iFormat;
+        partData->primitiveType = (Mesh::PrimitiveType)pType;
+        partData->indexFormat = (Mesh::IndexFormat)iFormat;
+        
         unsigned int indexSize = 0;
-        switch (indexFormat)
+        switch (partData->indexFormat)
         {
         case Mesh::INDEX8:
             indexSize = 1;
@@ -1139,23 +1131,53 @@ Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char*
             indexSize = 4;
             break;
         }
-        unsigned int indexCount = iByteCount / indexSize;
-        MeshPart* part = mesh->addPart((Mesh::PrimitiveType)pType, indexFormat, indexCount, false);
-        if (part == NULL)
+
+        partData->indexCount = iByteCount / indexSize;
+
+        partData->indexData = new unsigned char[iByteCount];
+        if (fread(partData->indexData, 1, iByteCount, _file) != iByteCount)
         {
-            LOG_ERROR_VARG("Failed to create mesh part (i=%d): %s", i, id);
-            SAFE_DELETE_ARRAY(indexData);
-            SAFE_RELEASE(mesh);
+            SAFE_DELETE(meshData);
             return NULL;
         }
-        part->setIndexData(indexData, 0, indexCount);
-        if (loadWithMeshRBSupport)
-            SceneLoader::addMeshRigidBodyData(_path, nodeId, indexData, iByteCount);
-        SAFE_DELETE_ARRAY(indexData);
     }
 
-    fseek(_file, position, SEEK_SET);
-    return mesh;
+    return meshData;
+}
+
+Package::MeshData* Package::readMeshData(const char* url)
+{
+    assert(url);
+
+    unsigned int len = strlen(url);
+    if (len == 0)
+        return NULL;
+
+    // Parse URL (formatted as 'package#id')
+    std::string urlstring(url);
+    unsigned int pos = urlstring.find('#');
+    if (pos == std::string::npos)
+        return NULL;
+
+    std::string file = urlstring.substr(0, pos);
+    std::string id = urlstring.substr(pos + 1);
+
+    // Load package
+    Package* pkg = Package::create(file.c_str());
+    if (pkg == NULL)
+        return NULL;
+
+    // Seek to mesh with specified ID in package
+    Reference* ref = pkg->seekTo(id.c_str(), PACKAGE_TYPE_MESH);
+    if (ref == NULL)
+        return NULL;
+
+    // Read mesh data from current file position
+    MeshData* meshData = pkg->readMeshData();
+
+    SAFE_RELEASE(pkg);
+
+    return meshData;
 }
 
 Font* Package::loadFont(const char* id)
@@ -1285,8 +1307,8 @@ const char* Package::getObjectID(unsigned int index) const
     return (index >= _referenceCount ? NULL : _references[index].id.c_str());
 }
 
-Package::Reference::Reference() :
-    type(0), offset(0)
+Package::Reference::Reference()
+    : type(0), offset(0)
 {
 }
 
@@ -1294,4 +1316,29 @@ Package::Reference::~Reference()
 {
 }
 
+Package::MeshPartData::MeshPartData() :
+    indexCount(0), indexData(NULL)
+{
+}
+
+Package::MeshPartData::~MeshPartData()
+{
+    SAFE_DELETE_ARRAY(indexData);
+}
+
+Package::MeshData::MeshData(const VertexFormat& vertexFormat)
+    : vertexFormat(vertexFormat), vertexCount(0), vertexData(NULL)
+{
+}
+
+Package::MeshData::~MeshData()
+{
+    SAFE_DELETE_ARRAY(vertexData);
+
+    for (unsigned int i = 0; i < parts.size(); ++i)
+    {
+        SAFE_DELETE(parts[i]);
+    }
+}
+
 }

+ 49 - 29
gameplay/src/Package.h

@@ -15,7 +15,7 @@ namespace gameplay
  */
 class Package : public Ref
 {
-    friend class SceneLoader;
+    friend class PhysicsController;
 
 public:
 
@@ -118,6 +118,31 @@ private:
         std::vector<Matrix> inverseBindPoseMatrices;
     };
 
+    struct MeshPartData
+    {
+        MeshPartData();
+        ~MeshPartData();
+
+        Mesh::PrimitiveType primitiveType;
+        Mesh::IndexFormat indexFormat;
+        unsigned int indexCount;
+        unsigned char* indexData;
+    };
+
+    struct MeshData
+    {
+        MeshData(const VertexFormat& vertexFormat);
+        ~MeshData();
+
+        VertexFormat vertexFormat;
+        unsigned int vertexCount;
+        unsigned char* vertexData;
+        BoundingBox boundingBox;
+        BoundingSphere boundingSphere;
+        Mesh::PrimitiveType primitiveType;
+        std::vector<MeshPartData*> parts;
+    };
+
     Package(const char* path);
 
     /**
@@ -173,46 +198,22 @@ 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, bool loadWithMeshRBSupport);
+    Node* loadNode(const char* id, Scene* sceneContext, Node* nodeContext);
 
     /**
      * 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);
+    Mesh* loadMesh(const char* id, const char* nodeId);
 
     /**
      * Reads an unsigned int from the current file position.
@@ -299,7 +300,7 @@ private:
      * 
      * @return A pointer to new node or NULL if there was an error.
      */
-    Node* readNode(Scene* sceneContext, Node* nodeContext, const std::vector<std::string>* nodesWithMeshRB);
+    Node* readNode(Scene* sceneContext, Node* nodeContext);
 
     /**
      * Reads a camera from the current file position.
@@ -320,7 +321,25 @@ private:
      * 
      * @return A pointer to a new model or NULL if there was an error.
      */
-    Model* readModel(Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport, const char* nodeId);
+    Model* readModel(Scene* sceneContext, Node* nodeContext, const char* nodeId);
+
+    /**
+     * Reads mesh data from the current file position.
+     */
+    MeshData* readMeshData();
+
+    /**
+     * Reads mesh data for the specified URL.
+     *
+     * The specified URL should be formatted as 'package#id', where
+     * 'package' is the package file containing the mesh and 'id' is the ID
+     * of the mesh to read data for.
+     *
+     * @param url The URL to read mesh data from.
+     *
+     * @return The mesh rigid body data.
+     */
+    static MeshData* readMeshData(const char* url);
 
     /**
      * Reads a mesh skin from the current file position.
@@ -368,6 +387,7 @@ private:
     void resolveJointReferences(Scene* sceneContext, Node* nodeContext);
 
 private:
+
     std::string _path;
     unsigned int _referenceCount;
     Reference* _references;

+ 683 - 0
gameplay/src/PhysicsCharacter.cpp

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

+ 317 - 0
gameplay/src/PhysicsCharacter.h

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

+ 1 - 1
gameplay/src/PhysicsConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsConstraint.h"
-
 #include "Game.h"
 #include "Node.h"
 #include "PhysicsMotionState.h"

+ 86 - 29
gameplay/src/PhysicsController.cpp

@@ -3,7 +3,7 @@
 #include "MeshPart.h"
 #include "PhysicsController.h"
 #include "PhysicsMotionState.h"
-#include "SceneLoader.h"
+#include "Package.h"
 
 // The initial capacity of the Bullet debug drawer's vertex batch.
 #define INITIAL_CAPACITY 280
@@ -18,8 +18,8 @@ const int PhysicsController::REMOVE        = 0x08;
 
 PhysicsController::PhysicsController()
   : _collisionConfiguration(NULL), _dispatcher(NULL),
-    _overlappingPairCache(NULL), _solver(NULL), _world(NULL), _debugDrawer(NULL), 
-    _status(PhysicsController::Listener::DEACTIVATED), _listeners(NULL),
+    _overlappingPairCache(NULL), _solver(NULL), _world(NULL), _ghostPairCallback(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.
@@ -27,6 +27,7 @@ PhysicsController::PhysicsController()
 
 PhysicsController::~PhysicsController()
 {
+    SAFE_DELETE(_ghostPairCallback);
     SAFE_DELETE(_debugDrawer);
     SAFE_DELETE(_listeners);
 }
@@ -39,6 +40,16 @@ void PhysicsController::addStatusListener(Listener* listener)
     _listeners->push_back(listener);
 }
 
+PhysicsCharacter* PhysicsController::createCharacter(Node* node, float radius, float height, const Vector3& center)
+{
+    return new PhysicsCharacter(node, radius, height, center);
+}
+
+void PhysicsController::destroyCharacter(PhysicsCharacter* character)
+{
+    SAFE_DELETE(character);
+}
+
 PhysicsFixedConstraint* PhysicsController::createFixedConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b)
 {
     checkConstraintRigidBodies(a, b);
@@ -129,12 +140,20 @@ void PhysicsController::drawDebug(const Matrix& viewProjection)
     _debugDrawer->end();
 }
 
-PhysicsRigidBody* PhysicsController::rayTest(const Ray& ray, float distance)
+PhysicsRigidBody* PhysicsController::rayTest(const Ray& ray, float distance, Vector3* hitPoint, float* hitFraction)
 {
     btCollisionWorld::ClosestRayResultCallback callback(BV(ray.getOrigin()), BV(distance * ray.getDirection()));
     _world->rayTest(BV(ray.getOrigin()), BV(distance * ray.getDirection()), callback);
     if (callback.hasHit())
+    {
+        if (hitPoint)
+            hitPoint->set(callback.m_hitPointWorld.x(), callback.m_hitPointWorld.y(), callback.m_hitPointWorld.z());
+
+        if (hitFraction)
+            *hitFraction = callback.m_closestHitFraction;
+
         return getRigidBody(callback.m_collisionObject);
+    }
 
     return NULL;
 }
@@ -226,6 +245,10 @@ void PhysicsController::initialize()
     _world = new btDiscreteDynamicsWorld(_dispatcher, _overlappingPairCache, _solver, _collisionConfiguration);
     _world->setGravity(BV(_gravity));
 
+    // Register ghost pair callback so bullet detects collisions with ghost objects (used for character collisions).
+    _ghostPairCallback = bullet_new<btGhostPairCallback>();
+    _world->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback);
+
     // Set up debug drawing.
     _debugDrawer = new DebugDrawer();
     _world->setDebugDrawer(_debugDrawer);
@@ -235,6 +258,7 @@ void PhysicsController::finalize()
 {
     // Clean up the world and its various components.
     SAFE_DELETE(_world);
+    SAFE_DELETE(_ghostPairCallback);
     SAFE_DELETE(_solver);
     SAFE_DELETE(_overlappingPairCache);
     SAFE_DELETE(_dispatcher);
@@ -519,23 +543,52 @@ btCollisionShape* PhysicsController::createSphere(float radius, const Vector3& s
     // Create the sphere shape and add it to the cache.
     btSphereShape* sphere = bullet_new<btSphereShape>(uniformScale * radius);
     _shapes.push_back(new PhysicsCollisionShape(sphere));
-    
+
     return sphere;
 }
 
 btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Vector3& scale)
 {
-    // Retrieve the mesh rigid body data from the loaded scene.
-    const SceneLoader::MeshRigidBodyData* data = SceneLoader::getMeshRigidBodyData(body->_node->getId());
+    assert(body);
+
+    // Retrieve the mesh rigid body data from the node's mesh.
+    Model* model = body->_node ? body->_node->getModel() : NULL;
+    Mesh* mesh = model ? model->getMesh() : NULL;
+    if (mesh == NULL)
+    {
+        LOG_ERROR("Cannot create mesh rigid body for node without model/mesh.");
+        return NULL;
+    }
+
+    // Only support meshes with triangle list primitive types
+    if (mesh->getPrimitiveType() != Mesh::TRIANGLES)
+    {
+        LOG_ERROR("Cannot create mesh rigid body for mesh without TRIANGLES primitive type.");
+        return NULL;
+    }
+
+    // The mesh must have a valid URL (i.e. it must have been loaded from a Package)
+    // in order to fetch mesh data for computing mesh rigid body.
+    if (strlen(mesh->getUrl()) == 0)
+    {
+        LOG_ERROR("Cannot create mesh rigid body for mesh without valid URL.");
+        return NULL;
+    }
+
+    Package::MeshData* data = Package::readMeshData(mesh->getUrl());
+    if (data == NULL)
+    {
+        return NULL;
+    }
 
     // Copy the scaled vertex position data to the rigid body's local buffer.
     Matrix m;
     Matrix::createScale(scale, &m);
-    unsigned int vertexCount = data->mesh->getVertexCount();
+    unsigned int vertexCount = data->vertexCount;
     body->_vertexData = new float[vertexCount * 3];
     Vector3 v;
-    int vertexStride = data->mesh->getVertexFormat().getVertexSize();
-    for (unsigned int i = 0; i < vertexCount; i++)
+    int vertexStride = data->vertexFormat.getVertexSize();
+    for (unsigned int i = 0; i < data->vertexCount; i++)
     {
         v.set(*((float*)&data->vertexData[i * vertexStride + 0 * sizeof(float)]),
               *((float*)&data->vertexData[i * vertexStride + 1 * sizeof(float)]),
@@ -543,19 +596,20 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
         v *= m;
         memcpy(&(body->_vertexData[i * 3]), &v, sizeof(float) * 3);
     }
-    
+
     btTriangleIndexVertexArray* meshInterface = bullet_new<btTriangleIndexVertexArray>();
 
-    if (data->mesh->getPartCount() > 0)
+    unsigned int partCount = data->parts.size();
+    if (partCount > 0)
     {
         PHY_ScalarType indexType = PHY_UCHAR;
         int indexStride = 0;
-        MeshPart* meshPart = NULL;
-        for (unsigned int i = 0; i < data->mesh->getPartCount(); i++)
+        Package::MeshPartData* meshPart = NULL;
+        for (unsigned int i = 0; i < partCount; i++)
         {
-            meshPart = data->mesh->getPart(i);
+            meshPart = data->parts[i];
 
-            switch (meshPart->getIndexFormat())
+            switch (meshPart->indexFormat)
             {
             case Mesh::INDEX8:
                 indexType = PHY_UCHAR;
@@ -571,17 +625,16 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
                 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);
+            // Move the index data into the rigid body's local buffer.
+            // Set it to NULL in the MeshPartData so it is not released when the data is freed.
+            body->_indexData.push_back(meshPart->indexData);
+            meshPart->indexData = NULL;
 
             // 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_numTriangles = meshPart->indexCount / 3; // assume TRIANGLES primitive type
+            indexedMesh.m_numVertices = meshPart->indexCount;
             indexedMesh.m_triangleIndexBase = (const unsigned char*)body->_indexData[i];
             indexedMesh.m_triangleIndexStride = indexStride*3;
             indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
@@ -595,8 +648,8 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
     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++)
+        unsigned int* indexData = new unsigned int[data->vertexCount];
+        for (unsigned int i = 0; i < data->vertexCount; i++)
         {
             indexData[i] = i;
         }
@@ -605,8 +658,8 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
         // 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_numTriangles = data->vertexCount / 3; // assume TRIANGLES primitive type
+        indexedMesh.m_numVertices = data->vertexCount;
         indexedMesh.m_triangleIndexBase = body->_indexData[0];
         indexedMesh.m_triangleIndexStride = sizeof(unsigned int);
         indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
@@ -620,6 +673,9 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
     btBvhTriangleMeshShape* shape = bullet_new<btBvhTriangleMeshShape>(meshInterface, true);
     _shapes.push_back(new PhysicsCollisionShape(shape));
 
+    // Free the temporary mesh data now that it's stored in physics system
+    SAFE_DELETE(data);
+
     return shape;
 }
 
@@ -696,6 +752,7 @@ PhysicsController::DebugDrawer::DebugDrawer()
         
     Effect* effect = Effect::createFromSource(vs_str, fs_str);
     Material* material = Material::create(effect);
+    material->getStateBlock()->setDepthTest(true);
 
     VertexFormat::Element elements[] =
     {
@@ -721,9 +778,9 @@ void PhysicsController::DebugDrawer::begin(const Matrix& viewProjection)
 
 void PhysicsController::DebugDrawer::end()
 {
+    _meshBatch->end();
     _meshBatch->getMaterial()->getParameter("u_viewProjectionMatrix")->setValue(_viewProjection);
     _meshBatch->draw();
-    _meshBatch->end();
 }
 
 void PhysicsController::DebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& fromColor, const btVector3& toColor)
@@ -780,4 +837,4 @@ int	PhysicsController::DebugDrawer::getDebugMode() const
     return _mode;
 }
 
-}
+}

+ 46 - 4
gameplay/src/PhysicsController.h

@@ -8,6 +8,7 @@
 #include "PhysicsSocketConstraint.h"
 #include "PhysicsSpringConstraint.h"
 #include "PhysicsRigidBody.h"
+#include "PhysicsCharacter.h"
 
 namespace gameplay
 {
@@ -20,6 +21,7 @@ class PhysicsController : public btCollisionWorld::ContactResultCallback
     friend class Game;
     friend class PhysicsConstraint;
     friend class PhysicsRigidBody;
+    friend class PhysicsCharacter;
 
 public:
 
@@ -59,6 +61,42 @@ public:
      */
     void addStatusListener(PhysicsController::Listener* listener);
 
+    /**
+     * Creates a new PhysicsCharacter.
+     *
+     * The created character is added to the physics world and automatically receives
+     * physics updates to handle interactions and collisions between the character
+     * and other physics objects in the world. The character will continue to receive
+     * updates until it is destroyed via the destroyCharacter(PhysicsCharacter*) method.
+     *
+     * The node may point to any node in the scene that you wish to control as a character.
+     * When a PhysicsCharacter is created for a particular node, the game will normally
+     * perform all movement directly through the PhysicsCharacter interface and not through
+     * the node itself.
+     *
+     * The radius, height and center parameters define a capsule volume that is used
+     * to represent the character in the physics world. All collision handling is 
+     * performed using this capsule.
+     *
+     * Note that PhysicsCharacter should not be mixed with rigid bodies. Therefore, you 
+     * should ensure that the node (and any of its children) used to create the
+     * PhysicsCharacter does not have any rigid bodies assigned. Doing so will cause
+     * unexpected results.
+     *
+     * @param node Scene node that represents the character.
+     * @param radius Radius of capsule volume used for character collisions.
+     * @param height Height of the capsule volume used for character collisions.
+     * @param center Center point of the capsule volume for the character.
+     */
+    PhysicsCharacter* createCharacter(Node* node, float radius, float height, const Vector3& center = Vector3::zero());
+
+    /**
+     * Destroys a PhysicsCharacter and removes it from the physics world.
+     *
+     * @param character PhysicsCharacter to destroy.
+     */
+    void destroyCharacter(PhysicsCharacter* character);
+
     /**
      * Creates a fixed constraint.
      * 
@@ -181,7 +219,7 @@ public:
      * @param gravity The gravity vector.
      */
     void setGravity(const Vector3& gravity);
-    
+   
     /**
      * Draws debugging information (rigid body outlines, etc.) using the given view projection matrix.
      * 
@@ -194,9 +232,12 @@ public:
      * 
      * @param ray The ray to test intersection with.
      * @param distance How far along the given ray to test for intersections.
-     * @return The first rigid body that the ray intersects.
+     * @param hitPoint Optional Vector3 point that is populated with the world-space point of intersection.
+     * @param hitFraction Optional float pointer that is populated with the distance along the ray
+     *      (as a fraction between 0-1) where the intersection occurred.
+     * @return The first rigid body that the ray intersects, or NULL if no intersection was found.
      */
-    PhysicsRigidBody* rayTest(const Ray& ray, float distance);
+    PhysicsRigidBody* rayTest(const Ray& ray, float distance, Vector3* hitPoint = NULL, float* hitFraction = NULL);
 
 protected:
 
@@ -335,6 +376,7 @@ private:
     btBroadphaseInterface* _overlappingPairCache;
     btSequentialImpulseConstraintSolver* _solver;
     btDynamicsWorld* _world;
+    btGhostPairCallback* _ghostPairCallback;
     std::vector<PhysicsCollisionShape*> _shapes;
     DebugDrawer* _debugDrawer;
     Listener::EventType _status;
@@ -346,4 +388,4 @@ private:
 
 }
 
-#endif
+#endif

+ 1 - 0
gameplay/src/PhysicsFixedConstraint.cpp

@@ -1,3 +1,4 @@
+#include "Base.h"
 #include "PhysicsFixedConstraint.h"
 
 namespace gameplay

+ 1 - 1
gameplay/src/PhysicsGenericConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsGenericConstraint.h"
-
 #include "Node.h"
 #include "PhysicsMotionState.h"
 #include "PhysicsRigidBody.h"

+ 1 - 1
gameplay/src/PhysicsHingeConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsHingeConstraint.h"
-
 #include "Node.h"
 
 namespace gameplay

+ 1 - 0
gameplay/src/PhysicsMotionState.cpp

@@ -1,3 +1,4 @@
+#include "Base.h"
 #include "PhysicsMotionState.h"
 
 namespace gameplay

+ 1 - 0
gameplay/src/PhysicsMotionState.h

@@ -15,6 +15,7 @@ namespace gameplay
 class PhysicsMotionState : public btMotionState
 {
     friend class PhysicsRigidBody;
+    friend class PhysicsCharacter;
     friend class PhysicsConstraint;
 
 protected:

+ 0 - 1
gameplay/src/PhysicsRigidBody.cpp

@@ -477,7 +477,6 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
     return body;
 }
 
-
 float PhysicsRigidBody::getHeight(float x, float y) const
 {
     // This function is only supported for heightfield rigid bodies.

+ 15 - 0
gameplay/src/PhysicsRigidBody.h

@@ -18,6 +18,7 @@ class PhysicsConstraint;
 class PhysicsRigidBody : public Transform::Listener
 {
     friend class Node;
+    friend class PhysicsCharacter;
     friend class PhysicsConstraint;
     friend class PhysicsController;
     friend class PhysicsFixedConstraint;
@@ -233,6 +234,20 @@ public:
      */
     inline bool isKinematic() const;
 
+    /**
+     * Gets whether the rigid body is a static rigid body or not.
+     *
+     * @return Whether the rigid body is static.
+     */
+    inline bool isStatic() const;
+
+    /**
+     * Gets whether the rigid body is a dynamic rigid body or not.
+     *
+     * @return Whether the rigid body is dynamic.
+     */
+    inline bool isDynamic() const;
+
     /**
      * Sets the rigid body's angular velocity.
      * 

+ 11 - 1
gameplay/src/PhysicsRigidBody.inl

@@ -71,7 +71,17 @@ inline float PhysicsRigidBody::getRestitution() const
 
 inline bool PhysicsRigidBody::isKinematic() const
 {
-    return (_body->getCollisionFlags() & btCollisionObject::CF_KINEMATIC_OBJECT) != 0;
+    return _body->isKinematicObject();
+}
+
+inline bool PhysicsRigidBody::isStatic() const
+{
+    return _body->isStaticObject();
+}
+
+inline bool PhysicsRigidBody::isDynamic() const
+{
+    return !_body->isStaticOrKinematicObject();
 }
 
 inline void PhysicsRigidBody::setAngularVelocity(const Vector3& velocity)

+ 1 - 1
gameplay/src/PhysicsSocketConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsSocketConstraint.h"
-
 #include "Node.h"
 
 namespace gameplay

+ 1 - 1
gameplay/src/PhysicsSpringConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsSpringConstraint.h"
-
 #include "Node.h"
 #include "PhysicsRigidBody.h"
 

+ 20 - 25
gameplay/src/PlatformQNX.cpp

@@ -970,32 +970,27 @@ int Platform::enterMessagePump()
         if (_game->getState() == Game::UNINITIALIZED)
             break;
 
-        // Idle time (no events left to process) is spent rendering.
-        // We skip rendering when the app is paused.
-        if (_game->getState() != Game::PAUSED)
+        _game->frame();
+
+        // Post the new frame to the display.
+        // Note that there are a couple cases where eglSwapBuffers could fail
+        // with an error code that requires a certain level of re-initialization:
+        //
+        // 1) EGL_BAD_NATIVE_WINDOW - Called when the surface we're currently using
+        //    is invalidated. This would require us to destroy our EGL surface,
+        //    close our OpenKODE window, and start again.
+        //
+        // 2) EGL_CONTEXT_LOST - Power management event that led to our EGL context
+        //    being lost. Requires us to re-create and re-initalize our EGL context
+        //    and all OpenGL ES state.
+        //
+        // For now, if we get these, we'll simply exit.
+        rc = eglSwapBuffers(__eglDisplay, __eglSurface);
+        if (rc != EGL_TRUE)
         {
-            _game->frame();
-
-            // Post the new frame to the display.
-            // Note that there are a couple cases where eglSwapBuffers could fail
-            // with an error code that requires a certain level of re-initialization:
-            //
-            // 1) EGL_BAD_NATIVE_WINDOW - Called when the surface we're currently using
-            //    is invalidated. This would require us to destroy our EGL surface,
-            //    close our OpenKODE window, and start again.
-            //
-            // 2) EGL_CONTEXT_LOST - Power management event that led to our EGL context
-            //    being lost. Requires us to re-create and re-initalize our EGL context
-            //    and all OpenGL ES state.
-            //
-            // For now, if we get these, we'll simply exit.
-            rc = eglSwapBuffers(__eglDisplay, __eglSurface);
-            if (rc != EGL_TRUE)
-            {
-                _game->exit();
-                perror("eglSwapBuffers");
-                break;
-            }
+            _game->exit();
+            perror("eglSwapBuffers");
+            break;
         }
     }
 

+ 7 - 1
gameplay/src/PlatformWin32.cpp

@@ -253,6 +253,12 @@ static gameplay::Keyboard::Key getKey(WPARAM win32KeyCode, bool shiftDown)
 
 LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
+    if (hwnd != __hwnd)
+    {
+        // Ignore messages that are not for our game window
+        return DefWindowProc(hwnd, msg, wParam, lParam); 
+    }
+
     // Scale factors for the mouse movement used to simulate the accelerometer.
     static const float ACCELEROMETER_X_FACTOR = 90.0f / WINDOW_WIDTH;
     static const float ACCELEROMETER_Y_FACTOR = 90.0f / WINDOW_HEIGHT;
@@ -401,7 +407,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     case WM_KILLFOCUS:
         break;
     }
-
+    
     return DefWindowProc(hwnd, msg, wParam, lParam); 
 }
 

+ 3 - 5
gameplay/src/Properties.cpp

@@ -567,12 +567,10 @@ const char* Properties::getString(const char* name) const
 
 bool Properties::getBool(const char* name) const
 {
-    if (exists(name))
+    const char* valueString = getString(name);
+    if (valueString)
     {
-        if (_properties.find(name)->second == "true")
-        {
-            return true;
-        }
+        return (strcmp(valueString, "true") == 0);
     }
 
     return false;

+ 16 - 0
gameplay/src/RenderState.cpp

@@ -130,6 +130,10 @@ void RenderState::setParameterAutoBinding(const char* name, const char* autoBind
     {
         value = WORLD_VIEW_PROJECTION_MATRIX;
     }
+    else if (strcmp(autoBinding, "INVERSE_TRANSPOSE_WORLD_MATRIX") == 0)
+    {
+        value = INVERSE_TRANSPOSE_WORLD_MATRIX;
+    }
     else if (strcmp(autoBinding, "INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX") == 0)
     {
         value = INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX;
@@ -138,6 +142,10 @@ void RenderState::setParameterAutoBinding(const char* name, const char* autoBind
     {
         value = CAMERA_WORLD_POSITION;
     }
+    else if (strcmp(autoBinding, "CAMERA_VIEW_POSITION") == 0)
+    {
+        value = CAMERA_VIEW_POSITION;
+    }
     else if (strcmp(autoBinding, "MATRIX_PALETTE") == 0)
     {
         value = MATRIX_PALETTE;
@@ -218,6 +226,10 @@ void RenderState::applyAutoBinding(const char* uniformName, AutoBinding autoBind
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getWorldViewProjectionMatrix);
         break;
 
+    case INVERSE_TRANSPOSE_WORLD_MATRIX:
+        getParameter(uniformName)->bindValue(_nodeBinding, &Node::getInverseTransposeWorldMatrix);
+        break;
+
     case INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX:
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getInverseTransposeWorldViewMatrix);
         break;
@@ -226,6 +238,10 @@ void RenderState::applyAutoBinding(const char* uniformName, AutoBinding autoBind
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getActiveCameraTranslationWorld);
         break;
 
+    case CAMERA_VIEW_POSITION:
+        getParameter(uniformName)->bindValue(_nodeBinding, &Node::getActiveCameraTranslationView);
+        break;
+
     case MATRIX_PALETTE:
         {
             Model* model = _nodeBinding->getModel();

+ 10 - 0
gameplay/src/RenderState.h

@@ -57,6 +57,11 @@ public:
          */
         WORLD_VIEW_PROJECTION_MATRIX,
 
+        /**
+         * Binds a node's InverseTransposeWorl matrix.
+         */
+        INVERSE_TRANSPOSE_WORLD_MATRIX,
+
         /**
          * Binds a node's InverseTransposeWorldView matrix.
          */
@@ -67,6 +72,11 @@ public:
          */
         CAMERA_WORLD_POSITION,
 
+        /**
+         * Binds the view-space position (Vector3) of the active camera for the node's scene.
+         */
+        CAMERA_VIEW_POSITION,
+
         /**
          * Binds the matrix palette of MeshSkin attached to a node's model.
          */

+ 1 - 1
gameplay/src/Scene.cpp

@@ -278,7 +278,7 @@ void Scene::setViewport(const Viewport& viewport)
     _viewport = viewport;
 }
 
-const Vector3& Scene::getAmbientColor()
+const Vector3& Scene::getAmbientColor() const
 {
     return _ambientColor;
 }

+ 1 - 1
gameplay/src/Scene.h

@@ -151,7 +151,7 @@ public:
      * 
      * @return The ambient color of the scene.
      */
-    const Vector3& getAmbientColor();
+    const Vector3& getAmbientColor() const;
 
     /**
      * Sets the ambient color of the scene.

+ 284 - 323
gameplay/src/SceneLoader.cpp

@@ -9,9 +9,7 @@ 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;
+std::vector<SceneLoader::SceneNode> SceneLoader::_sceneNodes;
 std::string SceneLoader::_path;
 
 Scene* SceneLoader::load(const char* filePath)
@@ -39,26 +37,10 @@ Scene* SceneLoader::load(const char* filePath)
 
     // Get the path to the main GPB.
     _path = sceneProperties->getString("path");
-    
     // 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)
@@ -69,8 +51,18 @@ Scene* SceneLoader::load(const char* filePath)
 
     // First apply the node url properties. Following that,
     // apply the normal node properties and create the animations.
+    // We apply rigid body properties after all other node properties
+    // so that the transform (SRT) properties get applied before
+    // processing rigid bodies.
     applyNodeUrls(scene);
-    applyNodeProperties(scene, sceneProperties);
+    applyNodeProperties(scene, sceneProperties, 
+        SceneNodeProperty::AUDIO | 
+        SceneNodeProperty::MATERIAL | 
+        SceneNodeProperty::PARTICLE |
+        SceneNodeProperty::ROTATE |
+        SceneNodeProperty::SCALE |
+        SceneNodeProperty::TRANSLATE);
+    applyNodeProperties(scene, sceneProperties, SceneNodeProperty::RIGIDBODY);
     createAnimations(scene);
 
     // Find the physics properties object.
@@ -101,95 +93,21 @@ Scene* SceneLoader::load(const char* filePath)
     // 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();
+    _sceneNodes.clear();
 
     return scene;
 }
 
-void SceneLoader::addMeshRigidBodyData(std::string package, 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;
-    }
-
-    // If the node with the mesh rigid body is renamed, we need to find the new id.
-    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
-    {
-        if (_nodeProperties[i]._type == SceneNodeProperty::URL &&
-            _nodeProperties[i]._id == id)
-        {
-            if ((package == _path && _nodeProperties[i]._file.size() == 0) ||
-                (package == _nodeProperties[i]._file))
-            {
-                id = _nodeProperties[i]._nodeID;
-                break;
-            }
-        }
-    }
-
-    (*_meshRigidBodyData)[id].mesh = mesh;
-    (*_meshRigidBodyData)[id].vertexData = new unsigned char[vertexByteCount];
-    memcpy((*_meshRigidBodyData)[id].vertexData, vertexData, vertexByteCount);
-}
-
-void SceneLoader::addMeshRigidBodyData(std::string package, 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;
-    }
-
-    // If the node with the mesh rigid body is renamed, we need to find the new id.
-    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
-    {
-        if (_nodeProperties[i]._type == SceneNodeProperty::URL &&
-            _nodeProperties[i]._id == id)
-        {
-            if ((package == _path && _nodeProperties[i]._file.size() == 0) ||
-                (package == _nodeProperties[i]._file))
-            {
-                id = _nodeProperties[i]._nodeID;
-                break;
-            }
-        }
-    }
-
-    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)
@@ -199,166 +117,154 @@ void SceneLoader::addSceneAnimation(const char* animationID, const char* targetI
     _animations.push_back(SceneAnimation(animationID, targetID, file, id));
 }
 
-void SceneLoader::addSceneNodeProperty(SceneNodeProperty::Type type, const char* nodeID, const char* url)
+void SceneLoader::addSceneNodeProperty(SceneNode& sceneNode, SceneNodeProperty::Type type, const char* url, int index)
 {
     // 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;
 
+    SceneNodeProperty prop(type, file, id, index);
+
+    // Parse for wildcharacter character (only supported on the URL attribute)
+    if (type == SceneNodeProperty::URL)
+    {
+        if (id.length() > 1 && id.at(id.length()-1) == '*')
+        {
+            prop._id = id.substr(0, id.length()-1);
+            sceneNode._exactMatch = false;
+        }
+    }
+
     // Add the node property to the list of node properties to be resolved later.
-    _nodeProperties.push_back(SceneNodeProperty(type, nodeID, file, id));
+    sceneNode._properties.push_back(prop);
 }
 
-void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* sceneProperties)
+void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* sceneProperties, unsigned int typeFlags)
 {
-    // Apply all of the remaining scene node properties except rigid body (we apply that last).
-    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
+    for (unsigned int i = 0, ncount = _sceneNodes.size(); i < ncount; ++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;
-        }
+        SceneNode& sceneNode = _sceneNodes[i];
 
-        if (_nodeProperties[i]._type == SceneNodeProperty::AUDIO ||
-            _nodeProperties[i]._type == SceneNodeProperty::MATERIAL ||
-            _nodeProperties[i]._type == SceneNodeProperty::PARTICLE ||
-            _nodeProperties[i]._type == SceneNodeProperty::RIGIDBODY)
+        if (sceneNode._exactMatch)
         {
-            // Check to make sure the referenced properties object was loaded properly.
-            Properties* p = _propertiesFromFile[_nodeProperties[i]._file];
-            if (!p)
+            // Find the node matching the specified ID exactly
+            Node* node = scene->findNode(sceneNode._nodeID);
+            if (!node)
             {
-                WARN_VARG("The referenced node data in file '%s' failed to load.", _nodeProperties[i]._file.c_str());
+                WARN_VARG("Attempting to set a property for node '%s', which does not exist in the scene.", sceneNode._nodeID);
                 continue;
             }
 
-            // If a specific namespace within the file was specified, load that namespace.
-            if (_nodeProperties[i]._id.size() > 0)
+            for (unsigned int j = 0, pcount = sceneNode._properties.size(); j < pcount; ++j)
             {
-                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;
-                }
+                SceneNodeProperty& snp = sceneNode._properties[j];
+                if ((typeFlags & snp._type) == snp._type)
+                    applyNodeProperty(sceneNode, node, sceneProperties, snp);
             }
-            else
+        }
+        else
+        {
+            // Find all nodes matching the specified ID
+            std::vector<Node*> nodes;
+            unsigned int nodeCount = scene->findNodes(sceneNode._nodeID, nodes, true, false);
+            if (nodeCount == 0)
+                continue;
+            
+            for (unsigned int j = 0, pcount = sceneNode._properties.size(); j < pcount; ++j)
             {
-                // Otherwise, use the first namespace.
-                p->rewind();
-                p = p->getNextNamespace();
-            }
+                SceneNodeProperty& snp = sceneNode._properties[j];
+                if ((typeFlags & snp._type) == 0)
+                    continue;
 
-            switch (_nodeProperties[i]._type)
-            {
-            case SceneNodeProperty::AUDIO:
-            {
-                AudioSource* audioSource = AudioSource::create(p);
-                node->setAudioSource(audioSource);
-                SAFE_RELEASE(audioSource);
-                break;
+                for (unsigned int k = 0; k < nodeCount; ++k)
+                    applyNodeProperty(sceneNode, nodes[k], sceneProperties, snp);
             }
-            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);
-                }
+        }
+    }
+}
+
+void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Properties* sceneProperties, const SceneNodeProperty& snp)
+{
+    if (snp._type == SceneNodeProperty::AUDIO ||
+        snp._type == SceneNodeProperty::MATERIAL ||
+        snp._type == SceneNodeProperty::PARTICLE ||
+        snp._type == SceneNodeProperty::RIGIDBODY)
+    {
+        // Check to make sure the referenced properties object was loaded properly.
+        Properties* p = _propertiesFromFile[snp._file];
+        if (!p)
+        {
+            WARN_VARG("The referenced node data in file '%s' failed to load.", snp._file.c_str());
+            return;
+        }
 
-                break;
-            case SceneNodeProperty::PARTICLE:
+        // If a specific namespace within the file was specified, load that namespace.
+        if (snp._id.size() > 0)
+        {
+            p = p->getNamespace(snp._id.c_str());
+            if (!p)
             {
-                ParticleEmitter* particleEmitter = ParticleEmitter::create(p);
-                node->setParticleEmitter(particleEmitter);
-                SAFE_RELEASE(particleEmitter);
-                break;
-            }
-            case SceneNodeProperty::RIGIDBODY:
-                // Process this last in a separate loop to allow scale, translate, rotate to be applied first.
-                break;
-            default:
-                // This cannot happen.
-                break;
+                WARN_VARG("The referenced node data at '%s#%s' failed to load.", snp._file.c_str(), snp._id.c_str());
+                return;
             }
         }
         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;
-            }
+            // Otherwise, use the first namespace.
+            p->rewind();
+            p = p->getNextNamespace();
         }
-    }
 
-    // Process rigid body properties.
-    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
-    {
-        if (_nodeProperties[i]._type == SceneNodeProperty::RIGIDBODY)
+        switch (snp._type)
         {
-            // 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)
+        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.", sceneNode._nodeID);
+            else
             {
-                WARN_VARG("Attempting to set a property for node '%s', which does not exist in the scene.", _nodeProperties[i]._nodeID);
-                continue;
+                Material* material = Material::create(p);
+                node->getModel()->setMaterial(material, snp._index);
+                SAFE_RELEASE(material);
             }
-
+            break;
+        case SceneNodeProperty::PARTICLE:
+        {
+            ParticleEmitter* particleEmitter = ParticleEmitter::create(p);
+            node->setParticleEmitter(particleEmitter);
+            SAFE_RELEASE(particleEmitter);
+            break;
+        }
+        case SceneNodeProperty::RIGIDBODY:
+        {
             // Check to make sure the referenced properties object was loaded properly.
-            Properties* p = _propertiesFromFile[_nodeProperties[i]._file];
+            Properties* p = _propertiesFromFile[snp._file];
             if (!p)
             {
-                WARN_VARG("The referenced node data in file '%s' failed to load.", _nodeProperties[i]._file.c_str());
-                continue;
+                WARN_VARG("The referenced node data in file '%s' failed to load.", snp._file.c_str());
+                return;
             }
 
             // If a specific namespace within the file was specified, load that namespace.
-            if (_nodeProperties[i]._id.size() > 0)
+            if (snp._id.size() > 0)
             {
-                p = p->getNamespace(_nodeProperties[i]._id.c_str());
+                p = p->getNamespace(snp._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;
+                    WARN_VARG("The referenced node data at '%s#%s' failed to load.", snp._file.c_str(), snp._id.c_str());
+                    return;
                 }
             }
             else
@@ -369,11 +275,11 @@ void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* scen
             }
 
             // If the scene file specifies a rigid body model, use it for creating the rigid body.
-            Properties* np = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
+            Properties* np = sceneProperties->getNamespace(sceneNode._nodeID);
             const char* name = NULL;
             if (np && (name = np->getString("rigidbodymodel")))
             {
-                Node* modelNode = scene->findNode(name);
+                Node* modelNode = node->getScene()->findNode(name);
                 if (!modelNode)
                     WARN_VARG("Node '%s' does not exist; attempting to use its model for rigid body creation.", name);
                 else
@@ -391,9 +297,48 @@ void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* scen
                 }
             }
             else if (!node->getModel())
-                WARN_VARG("Attempting to set a rigid body on node '%s', which has no model.", _nodeProperties[i]._nodeID);
+                WARN_VARG("Attempting to set a rigid body on node '%s', which has no model.", sceneNode._nodeID);
             else
                 node->setRigidBody(p);
+            break;
+        }
+        default:
+            // This cannot happen.
+            break;
+        }
+    }
+    else
+    {
+        // Handle Scale, Rotate and Translate
+        Properties* np = sceneProperties->getNamespace(sceneNode._nodeID);
+        const char* name = NULL;
+
+        switch (snp._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.", snp._type);
+            break;
         }
     }
 }
@@ -402,65 +347,126 @@ 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(); )
+    for (unsigned int i = 0, ncount = _sceneNodes.size(); i < ncount; ++i)
     {
-        if (_nodeProperties[i]._type == SceneNodeProperty::URL)
+        SceneNode& sceneNode = _sceneNodes[i];
+
+        // Iterate backwards over the properties list so we can remove properties as we go
+        // without danger of indexing out of bounds.
+        for (int j = sceneNode._properties.size() - 1; j >= 0; --j)
         {
-            // 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
+            SceneNodeProperty& snp = sceneNode._properties[j];
+            if (snp._type != SceneNodeProperty::URL)
+                continue;
+
+            if (snp._file.empty())
             {
-                // 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)
+                // The node is from the main GPB and should just be renamed.
+
+                // 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?
+                if (sceneNode._exactMatch)
                 {
-                    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());
+                    Node* node = scene->findNode(snp._id.c_str());
+                    if (node)
+                    {
+                        node->setId(sceneNode._nodeID);
+                    }
                     else
                     {
-                        bool loadWithMeshRBSupport = false;
-                        for (unsigned int j = 0; j < _nodesWithMeshRB.size(); j++)
+                        WARN_VARG("Could not find node '%s' in main scene GPB file.", snp._id.c_str());
+                    }
+                }
+                else
+                {
+                    // Search for nodes using a partial match
+                    std::string partialMatch = snp._id;
+                    std::vector<Node*> nodes;
+                    unsigned int nodeCount = scene->findNodes(snp._id.c_str(), nodes, true, false);
+                    if (nodeCount > 0)
+                    {
+                        for (unsigned int k = 0; k < nodeCount; ++k)
                         {
-                            if (_nodeProperties[i]._id == _nodesWithMeshRB[j])
-                            {
-                                loadWithMeshRBSupport = true;
-                                break;
-                            }
+                            // Construct a new node ID using _nodeID plus the remainder of the partial match.
+                            Node* node = nodes[k];
+                            std::string newID(sceneNode._nodeID);
+                            newID += (node->getId() + snp._id.length());
+                            node->setId(newID.c_str());
                         }
+                    }
+                    else
+                    {
+                        WARN_VARG("Could not find any nodes matching '%s' in main scene GPB file.", snp._id.c_str());
+                    }
+                }
+            }
+            else
+            {
+                // An external file was referenced, so load the node from file and then insert it into the scene with the new ID.
 
-                        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
+                // TODO: Revisit this to determine if we should cache Package objects for the duration of the scene
+                // load to prevent constantly creating/destroying the same externally referenced packages each time
+                // a url with a file is encountered.
+                Package* tmpPackage = Package::create(snp._file.c_str());
+                if (tmpPackage)
+                {
+                    if (sceneNode._exactMatch)
+                    {
+                        Node* node = tmpPackage->loadNode(snp._id.c_str());
+                        if (node)
                         {
-                            node->setId(_nodeProperties[i]._nodeID);
+                            node->setId(sceneNode._nodeID);
                             scene->addNode(node);
                             SAFE_RELEASE(node);
                         }
-                        
-                        SAFE_RELEASE(tmpPackage);
+                        else
+                        {
+                            WARN_VARG("Could not load node '%s' in GPB file '%s'.", snp._id.c_str(), snp._file.c_str());
+                        }
+                    }
+                    else
+                    {
+                        // Search for nodes in the package using a partial match
+                        std::string partialMatch = snp._id;
+                        unsigned int objectCount = tmpPackage->getObjectCount();
+                        unsigned int matchCount = 0;
+                        for (unsigned int k = 0; k < objectCount; ++k)
+                        {
+                            const char* objid = tmpPackage->getObjectID(k);
+                            if (strstr(objid, snp._id.c_str()) == objid)
+                            {
+                                // This object ID matches (starts with).
+                                // Try to load this object as a Node.
+                                Node* node = tmpPackage->loadNode(objid);
+                                if (node)
+                                {
+                                    // Construct a new node ID using _nodeID plus the remainder of the partial match.
+                                    std::string newID(sceneNode._nodeID);
+                                    newID += (node->getId() + snp._id.length());
+                                    node->setId(newID.c_str());
+                                    scene->addNode(node);
+                                    SAFE_RELEASE(node);
+                                    matchCount++;
+                                }
+                            }
+                        }
+                        if (matchCount == 0)
+                        {
+                            WARN_VARG("Could not find any nodes matching '%s' in GPB file '%s'.", snp._id.c_str(), snp._file.c_str());
+                        }
                     }
+
+                    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);
+                    WARN_VARG("Failed to load GPB file '%s' for node stitching.", snp._file.c_str());
                 }
             }
 
             // Remove the node property since we are done applying it.
-            _nodeProperties.erase(_nodeProperties.begin() + i);
+            sceneNode._properties.erase(sceneNode._properties.begin() + j);
         }
-        else
-            i++;
     }
 }
 
@@ -479,27 +485,40 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
                 continue;
             }
 
+            // Add a SceneNode to the end of the list
+            _sceneNodes.resize(_sceneNodes.size() + 1);
+            SceneNode& sceneNode = _sceneNodes[_sceneNodes.size()-1];
+            sceneNode._nodeID = ns->getId();
+
             while (name = ns->getNextProperty())
             {
                 if (strcmp(name, "url") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::URL, ns->getId(), ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::URL, ns->getString());
                 }
                 else if (strcmp(name, "audio") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::AUDIO, ns->getId(), ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::AUDIO, ns->getString());
                 }
-                else if (strcmp(name, "material") == 0)
+                else if (strncmp(name, "material", 8) == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::MATERIAL, ns->getId(), ns->getString());
+                    int materialIndex = -1;
+                    name = strchr(name, '[');
+                    if (name && strlen(name) >= 3)
+                    {
+                        std::string indexString(name);
+                        indexString = indexString.substr(1, indexString.size()-2);
+                        materialIndex = (unsigned int)atoi(indexString.c_str());
+                    }
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::MATERIAL, ns->getString(), materialIndex);
                 }
                 else if (strcmp(name, "particle") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::PARTICLE, ns->getId(), ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, ns->getString());
                 }
                 else if (strcmp(name, "rigidbody") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::RIGIDBODY, ns->getId(), ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::RIGIDBODY, ns->getString());
                 }
                 else if (strcmp(name, "rigidbodymodel") == 0)
                 {
@@ -507,15 +526,15 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
                 }
                 else if (strcmp(name, "translate") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::TRANSLATE, ns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::TRANSLATE);
                 }
                 else if (strcmp(name, "rotate") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::ROTATE, ns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::ROTATE);
                 }
                 else if (strcmp(name, "scale") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::SCALE, ns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::SCALE);
                 }
                 else
                 {
@@ -566,64 +585,16 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
         }
         else
         {
+            // TODO: Should we ignore these items? They could be used for generic properties file inheritence.
             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
-                    {
-                        const char* id = _nodeProperties[i]._nodeID;
-                        for (unsigned int j = 0; j < _nodeProperties.size(); j++)
-                        {
-                            if (_nodeProperties[j]._type == SceneNodeProperty::URL &&
-                                _nodeProperties[j]._nodeID == _nodeProperties[i]._nodeID)
-                            {
-                                id = _nodeProperties[j]._id.c_str();
-                                break;
-                            }
-                        }
-                        _nodesWithMeshRB.push_back(id);
-                    }
-                }
-            }
-        }
-    }
-}
-
 void SceneLoader::createAnimations(const Scene* scene)
 {
     // Create the scene animations.
-    for (unsigned int i = 0; i < _animations.size(); i++)
+    for (unsigned int i = 0, count = _animations.size(); i < count; i++)
     {
         // If the target node doesn't exist in the scene, then we
         // can't do anything so we skip to the next animation.
@@ -655,17 +626,6 @@ void SceneLoader::createAnimations(const Scene* scene)
     }
 }
 
-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;
@@ -755,8 +715,9 @@ Scene* SceneLoader::loadMainSceneData(const Properties* sceneProperties)
         WARN_VARG("Failed to load scene GPB file '%s'.", _path.c_str());
         return NULL;
     }
-    
-    Scene* scene = package->loadScene(NULL, &_nodesWithMeshRB);
+
+    const char* sceneID = strlen(sceneProperties->getId()) == 0 ? NULL : sceneProperties->getId();
+    Scene* scene = package->loadScene(sceneID);
     if (!scene)
     {
         WARN_VARG("Failed to load scene from '%s'.", _path.c_str());
@@ -862,7 +823,7 @@ void SceneLoader::loadPhysics(Properties* physics, Scene* scene)
             
             // If the constraint failed to load, continue on to the next one.
             if (!physicsConstraint)
-                    continue;
+                continue;
 
             // If the breaking impulse was specified, apply it to the constraint.
             if (constraint->getString("breakingImpulse"))

+ 28 - 26
gameplay/src/SceneLoader.h

@@ -15,8 +15,6 @@ namespace gameplay
  */
 class SceneLoader
 {
-    friend class Package;
-    friend class PhysicsController;
     friend class Scene;
 
 private:
@@ -27,13 +25,6 @@ private:
     // ------------------------------------------------------------------------
     // 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)
@@ -47,27 +38,43 @@ private:
 
     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) {}
+        enum Type
+        {
+            AUDIO = 1,
+            MATERIAL = 2,
+            PARTICLE = 4,
+            RIGIDBODY = 8,
+            TRANSLATE = 16,
+            ROTATE = 32,
+            SCALE = 64,
+            URL = 128
+        };
+
+        SceneNodeProperty(Type type, std::string file, std::string id, int index) : _type(type), _file(file), _id(id), _index(index) { }
 
         Type _type;
-        const char* _nodeID;
         std::string _file;
         std::string _id;
+        int _index;
+    };
+
+    struct SceneNode
+    {
+        SceneNode() : _nodeID(""), _exactMatch(true) { }
+
+        const char* _nodeID;
+        bool _exactMatch;
+        std::vector<SceneNodeProperty> _properties;
     };
 
-    static void addMeshRigidBodyData(std::string package, std::string id, Mesh* mesh, unsigned char* vertexData, unsigned int vertexByteCount);
-    static void addMeshRigidBodyData(std::string package, 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 addSceneNodeProperty(SceneNode& sceneNode, SceneNodeProperty::Type type, const char* url = NULL, int index = 0);
+    static void applyNodeProperties(const Scene* scene, const Properties* sceneProperties, unsigned int typeFlags);
+    static void applyNodeProperty(SceneNode& sceneNode, Node* node, const Properties* sceneProperties, const SceneNodeProperty& snp);
     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);
@@ -85,14 +92,9 @@ private:
     // 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;
+    // Holds all the nodes+properties declared in the .scene file.
+    static std::vector<SceneNode> _sceneNodes;
 
-    // Stores the mesh data needed for triangle mesh rigid body support.
-    static std::map<std::string, MeshRigidBodyData>* _meshRigidBodyData;
 
     // The path of the main GPB for the scene being loaded.
     static std::string _path;

+ 1 - 1
gameplay/src/Theme.cpp

@@ -101,7 +101,7 @@ namespace gameplay
         
         // Parse the Properties object and set up the theme.
         const char* textureFile = themeProperties->getString("texture");
-        theme->_texture = Texture::create(textureFile, true);
+        theme->_texture = Texture::create(textureFile, false);
         theme->_spriteBatch = SpriteBatch::create(theme->_texture);
 
         Properties* space = themeProperties->getNextNamespace();

+ 1 - 0
gameplay/src/gameplay.h

@@ -67,6 +67,7 @@
 #include "PhysicsSocketConstraint.h"
 #include "PhysicsSpringConstraint.h"
 #include "PhysicsRigidBody.h"
+#include "PhysicsCharacter.h"
 
 
 // UI