Ver Fonte

Merge pull request #37 from blackberry-gaming/next

Next
Sean Paul Taylor há 14 anos atrás
pai
commit
7fb6d0dea2
100 ficheiros alterados com 6163 adições e 1348 exclusões
  1. 1 1
      gameplay-encoder/gameplay-binary.txt
  2. 1 0
      gameplay-encoder/src/DAESceneEncoder.cpp
  3. 17 16
      gameplay-encoder/src/FBXSceneEncoder.cpp
  4. 26 1
      gameplay-encoder/src/GPBFile.cpp
  5. 4 0
      gameplay-encoder/src/GPBFile.h
  6. 2 4
      gameplay-encoder/src/Mesh.cpp
  7. 2 4
      gameplay-encoder/src/Mesh.h
  8. 2 2
      gameplay-encoder/src/MeshSkin.cpp
  9. 1 1
      gameplay.doxyfile
  10. 27 0
      gameplay/gameplay.vcxproj
  11. 82 1
      gameplay/gameplay.vcxproj.filters
  12. 17 5
      gameplay/res/shaders/colored-specular.fsh
  13. 11 2
      gameplay/res/shaders/colored-specular.vsh
  14. 2 1
      gameplay/res/shaders/colored.fsh
  15. 5 2
      gameplay/res/shaders/diffuse-specular.fsh
  16. 9 11
      gameplay/res/shaders/diffuse-specular.vsh
  17. 2 1
      gameplay/res/shaders/diffuse.fsh
  18. 44 0
      gameplay/src/AbsoluteLayout.cpp
  19. 26 0
      gameplay/src/AbsoluteLayout.h
  20. 38 16
      gameplay/src/Animation.cpp
  21. 13 0
      gameplay/src/Animation.h
  22. 135 64
      gameplay/src/AnimationClip.cpp
  23. 69 33
      gameplay/src/AnimationClip.h
  24. 67 18
      gameplay/src/AnimationController.cpp
  25. 15 10
      gameplay/src/AnimationController.h
  26. 23 0
      gameplay/src/AnimationTarget.cpp
  27. 2 0
      gameplay/src/AnimationTarget.h
  28. 71 23
      gameplay/src/AudioBuffer.cpp
  29. 12 2
      gameplay/src/Base.h
  30. 1 1
      gameplay/src/BoundingBox.h
  31. 1 1
      gameplay/src/BoundingBox.inl
  32. 1 1
      gameplay/src/BoundingSphere.h
  33. 1 1
      gameplay/src/BoundingSphere.inl
  34. 92 0
      gameplay/src/Button.cpp
  35. 75 0
      gameplay/src/Button.h
  36. 2 1
      gameplay/src/Camera.cpp
  37. 155 0
      gameplay/src/CheckBox.cpp
  38. 34 0
      gameplay/src/CheckBox.h
  39. 352 0
      gameplay/src/Container.cpp
  40. 92 0
      gameplay/src/Container.h
  41. 183 0
      gameplay/src/Control.cpp
  42. 134 0
      gameplay/src/Control.h
  43. 2 1
      gameplay/src/DebugNew.cpp
  44. 24 0
      gameplay/src/FlowLayout.h
  45. 536 28
      gameplay/src/Font.cpp
  46. 26 8
      gameplay/src/Font.h
  47. 383 0
      gameplay/src/Form.cpp
  48. 70 0
      gameplay/src/Form.h
  49. 50 32
      gameplay/src/Game.cpp
  50. 9 0
      gameplay/src/Game.h
  51. 1 1
      gameplay/src/Game.inl
  52. 109 0
      gameplay/src/Label.cpp
  53. 35 0
      gameplay/src/Label.h
  54. 33 0
      gameplay/src/Layout.h
  55. 7 7
      gameplay/src/Material.cpp
  56. 1 1
      gameplay/src/Matrix.cpp
  57. 9 9
      gameplay/src/Matrix.h
  58. 6 6
      gameplay/src/Matrix.inl
  59. 5 0
      gameplay/src/Mesh.cpp
  60. 10 0
      gameplay/src/Mesh.h
  61. 8 3
      gameplay/src/Model.cpp
  62. 11 2
      gameplay/src/Model.h
  63. 68 12
      gameplay/src/Node.cpp
  64. 27 5
      gameplay/src/Node.h
  65. 151 104
      gameplay/src/Package.cpp
  66. 49 29
      gameplay/src/Package.h
  67. 35 2
      gameplay/src/ParticleEmitter.cpp
  68. 7 0
      gameplay/src/ParticleEmitter.h
  69. 683 0
      gameplay/src/PhysicsCharacter.cpp
  70. 317 0
      gameplay/src/PhysicsCharacter.h
  71. 3 3
      gameplay/src/PhysicsConstraint.cpp
  72. 257 84
      gameplay/src/PhysicsController.cpp
  73. 82 6
      gameplay/src/PhysicsController.h
  74. 1 0
      gameplay/src/PhysicsFixedConstraint.cpp
  75. 4 4
      gameplay/src/PhysicsGenericConstraint.cpp
  76. 8 8
      gameplay/src/PhysicsGenericConstraint.inl
  77. 4 4
      gameplay/src/PhysicsHingeConstraint.cpp
  78. 6 6
      gameplay/src/PhysicsMotionState.cpp
  79. 1 0
      gameplay/src/PhysicsMotionState.h
  80. 28 55
      gameplay/src/PhysicsRigidBody.cpp
  81. 43 30
      gameplay/src/PhysicsRigidBody.h
  82. 23 12
      gameplay/src/PhysicsRigidBody.inl
  83. 3 3
      gameplay/src/PhysicsSocketConstraint.cpp
  84. 3 3
      gameplay/src/PhysicsSpringConstraint.cpp
  85. 2 2
      gameplay/src/Plane.h
  86. 1 1
      gameplay/src/Plane.inl
  87. 7 0
      gameplay/src/Platform.h
  88. 554 554
      gameplay/src/PlatformMacOS.mm
  89. 103 53
      gameplay/src/PlatformQNX.cpp
  90. 44 10
      gameplay/src/PlatformWin32.cpp
  91. 179 27
      gameplay/src/Properties.cpp
  92. 13 4
      gameplay/src/Properties.h
  93. 1 1
      gameplay/src/Quaternion.h
  94. 1 1
      gameplay/src/Quaternion.inl
  95. 189 0
      gameplay/src/RadioButton.cpp
  96. 37 0
      gameplay/src/RadioButton.h
  97. 25 1
      gameplay/src/Ray.cpp
  98. 3 2
      gameplay/src/Ray.h
  99. 1 1
      gameplay/src/Ray.inl
  100. 16 0
      gameplay/src/RenderState.cpp

+ 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 - 0
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -627,6 +627,7 @@ bool DAESceneEncoder::loadTarget(const domChannelRef& channelRef, AnimationChann
                     Quaternion rotation;
                     Vector3 translation;
                     matrix.decompose(&scale, &rotation, &translation);
+                    rotation.normalize();
 
                     size_t k = i * 10;
                     floats[k+0] = scale.x;

+ 17 - 16
gameplay-encoder/src/FBXSceneEncoder.cpp

@@ -15,7 +15,7 @@ using namespace gameplay;
  * 
  * @return The aspect ratio from the camera.
  */
-float getAspectRatio(KFbxCamera* fbxCamera);
+static float getAspectRatio(KFbxCamera* fbxCamera);
 
 /**
  * Returns the field of view Y from the given camera.
@@ -24,7 +24,7 @@ float getAspectRatio(KFbxCamera* fbxCamera);
  * 
  * @return The field of view Y.
  */
-float getFieldOfView(KFbxCamera* fbxCamera);
+static float getFieldOfView(KFbxCamera* fbxCamera);
 
 /**
  * Loads the texture coordinates from given mesh's polygon part into the vertex.
@@ -34,7 +34,7 @@ float getFieldOfView(KFbxCamera* fbxCamera);
  * @param posInPoly The position in the polygon.
  * @param vertex The vertex to copy the texture coordinates to.
  */
-void loadTextureCoords(KFbxMesh* fbxMesh, int polyIndex, int posInPoly, Vertex* vertex);
+static void loadTextureCoords(KFbxMesh* fbxMesh, int polyIndex, int posInPoly, Vertex* vertex);
 
 /**
  * Loads the normal from the mesh and adds it to the given vertex.
@@ -43,7 +43,7 @@ void loadTextureCoords(KFbxMesh* fbxMesh, int polyIndex, int posInPoly, Vertex*
  * @param vertexIndex The vertex index in the mesh.
  * @param vertex The vertex to copy to.
  */
-void loadNormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
+static void loadNormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
 
 /**
  * Loads the tangent from the mesh and adds it to the given vertex.
@@ -52,7 +52,7 @@ void loadNormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
  * @param vertexIndex The index of the vertex within fbxMesh.
  * @param vertex The vertex to copy to.
  */
-void loadTangent(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
+static void loadTangent(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
 
 /**
  * Loads the binormal from the mesh and adds it to the given vertex.
@@ -61,7 +61,7 @@ void loadTangent(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
  * @param vertexIndex The index of the vertex within fbxMesh.
  * @param vertex The vertex to copy to.
  */
-void loadBinormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
+static void loadBinormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
 
 /**
  * Loads the vertex diffuse color from the mesh and adds it to the given vertex.
@@ -70,7 +70,7 @@ void loadBinormal(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
  * @param vertexIndex The index of the vertex within fbxMesh.
  * @param vertex The vertex to copy to.
  */
-void loadVertexColor(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
+static void loadVertexColor(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
 
 /**
  * Loads the blend weight and blend indices data into the vertex.
@@ -78,7 +78,7 @@ void loadVertexColor(KFbxMesh* fbxMesh, int vertexIndex, Vertex* vertex);
  * @param vertexWeights List of vertex weights. The x member contains the blendIndices. The y member contains the blendWeights.
  * @param vertex The vertex to copy the blend data to.
  */
-void loadBlendData(const std::vector<Vector2>& vertexWeights, Vertex* vertex);
+static void loadBlendData(const std::vector<Vector2>& vertexWeights, Vertex* vertex);
 
 /**
  * Loads the blend weights and blend indices from the given mesh.
@@ -90,17 +90,17 @@ void loadBlendData(const std::vector<Vector2>& vertexWeights, Vertex* vertex);
  * 
  * @return True if this mesh has a mesh skin, false otherwise.
  */
-bool loadBlendWeights(KFbxMesh* fbxMesh, std::vector<std::vector<Vector2> >& weights);
+static bool loadBlendWeights(KFbxMesh* fbxMesh, std::vector<std::vector<Vector2> >& weights);
 
 /**
  * Copies from an FBX matrix to a float[16] array.
  */
-void copyMatrix(const KFbxMatrix& fbxMatrix, float* matrix);
+static void copyMatrix(const KFbxMatrix& fbxMatrix, float* matrix);
 
 /**
  * Copies from an FBX matrix to a gameplay matrix.
  */
-void copyMatrix(const KFbxMatrix& fbxMatrix, Matrix& matrix);
+static void copyMatrix(const KFbxMatrix& fbxMatrix, Matrix& matrix);
 
 /**
  * Finds the min and max start time and stop time of the given animation curve.
@@ -114,7 +114,7 @@ void copyMatrix(const KFbxMatrix& fbxMatrix, Matrix& matrix);
  * @param stopTime The max stop time. (in/out)
  * @param frameRate The frame rate. (in/out)
  */
-void findMinMaxTime(KFbxAnimCurve* animCurve, float* startTime, float* stopTime, float* frameRate);
+static void findMinMaxTime(KFbxAnimCurve* animCurve, float* startTime, float* stopTime, float* frameRate);
 
 /**
  * Appends a key frame of the given node's transform at the given time.
@@ -124,7 +124,7 @@ void findMinMaxTime(KFbxAnimCurve* animCurve, float* startTime, float* stopTime,
  * @param keyTimes The list of key times to append to.
  * @param keyValues The list of key values to append to.
  */
-void appendKeyFrame(KFbxNode* fbxNode, float time, std::vector<float>* keyTimes, std::vector<float>* keyValues);
+static void appendKeyFrame(KFbxNode* fbxNode, float time, std::vector<float>* keyTimes, std::vector<float>* keyValues);
 
 /**
  * Decomposes the given node's matrix transform at the given time and copies to scale, rotation and translation.
@@ -135,7 +135,7 @@ void appendKeyFrame(KFbxNode* fbxNode, float time, std::vector<float>* keyTimes,
  * @param rotation The rotation to copy to.
  * @param translation The translation to copy to.
  */
-void decompose(KFbxNode* fbxNode, float time, Vector3* scale, Quaternion* rotation, Vector3* translation);
+static void decompose(KFbxNode* fbxNode, float time, Vector3* scale, Quaternion* rotation, Vector3* translation);
 
 /**
  * Creates an animation channel that targets the given node and target attribute using the given key times and key values.
@@ -147,7 +147,7 @@ void decompose(KFbxNode* fbxNode, float time, Vector3* scale, Quaternion* rotati
  * 
  * @return The newly created animation channel.
  */
-AnimationChannel* createAnimationChannel(KFbxNode* fbxNode, unsigned int targetAttrib, const std::vector<float>& keyTimes, const std::vector<float>& keyValues);
+static AnimationChannel* createAnimationChannel(KFbxNode* fbxNode, unsigned int targetAttrib, const std::vector<float>& keyTimes, const std::vector<float>& keyValues);
 
 void addScaleChannel(Animation* animation, KFbxNode* fbxNode, float startTime, float stopTime);
 
@@ -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);
@@ -1274,6 +1274,7 @@ void appendKeyFrame(KFbxNode* fbxNode, float time, std::vector<float>* keyTimes,
     Quaternion rotation;
     Vector3 translation;
     matrix.decompose(&scale, &rotation, &translation);
+    rotation.normalize();
 
     keyTimes->push_back(time);
     keyValues->push_back(scale.x);

+ 26 - 1
gameplay-encoder/src/GPBFile.cpp

@@ -253,8 +253,13 @@ void GPBFile::adjust()
         }
     }
 
+    for (std::list<Node*>::const_iterator i = _nodes.begin(); i != _nodes.end(); ++i)
+    {
+        computeBounds(*i);
+    }
+
     // try to convert joint transform animations into rotation animations
-    //optimizeTransformAnimations(); // TODO: Fix bounding sphere before re-enabling this
+    //optimizeTransformAnimations();
 
     // TODO:
     // remove ambient _lights
@@ -270,6 +275,26 @@ void GPBFile::adjust()
     //   This can be merged into one animation. Same for scale animations.
 }
 
+void GPBFile::computeBounds(Node* node)
+{
+    assert(node);
+    if (Model* model = node->getModel())
+    {
+        if (Mesh* mesh = model->getMesh())
+        {
+            mesh->computeBounds();
+        }
+        if (MeshSkin* skin = model->getSkin())
+        {
+            skin->computeBounds();
+        }
+    }
+    for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
+    {
+        computeBounds(child);
+    }
+}
+
 void GPBFile::optimizeTransformAnimations()
 {
     const unsigned int animationCount = _animations.getAnimationCount();

+ 4 - 0
gameplay-encoder/src/GPBFile.h

@@ -99,6 +99,10 @@ public:
     void adjust();
 
 private:
+    /**
+     * Computes the bounds of all meshes in the node hierarchy.
+     */
+    void computeBounds(Node* node);
     void optimizeTransformAnimations();
 
     /**

+ 2 - 4
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;
@@ -273,7 +273,6 @@ void Mesh::writeBinaryVertices(FILE* file)
     }
 
     // Write bounds
-    computeBounds();
     write(&bounds.min.x, 3, file);
     write(&bounds.max.x, 3, file);
     write(&bounds.center.x, 3, file);
@@ -302,7 +301,6 @@ void Mesh::writeText(FILE* file)
     fprintf(file, "</vertices>\n");
 
     // write bounds
-    computeBounds();
     fprintf(file, "<bounds>\n");
     fprintf(file, "<min>\n");
     writeVectorText(bounds.min, file);
@@ -392,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 - 4
gameplay-encoder/src/Mesh.h

@@ -65,16 +65,14 @@ public:
      */
     void generateHeightmap(const char* filename);
 
+    void computeBounds();
+
     Model* model;
     std::vector<Vertex> vertices;
     std::vector<MeshPart*> parts;
     BoundingVolume bounds;
     std::map<Vertex, unsigned int> vertexLookupTable;
 
-private:
-
-    void computeBounds();
-
 private:
     std::vector<VertexElement> _vertexFormat;
 

+ 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;

+ 1 - 1
gameplay.doxyfile

@@ -548,7 +548,7 @@ SHOW_FILES             = YES
 # Namespaces page.  This will remove the Namespaces entry from the Quick Index 
 # and from the Folder Tree View (if specified). The default is YES.
 
-SHOW_NAMESPACES        = YES
+SHOW_NAMESPACES        = NO
 
 # The FILE_VERSION_FILTER tag can be used to specify a program or script that 
 # doxygen should invoke to get the current version for each file (typically from 

+ 27 - 0
gameplay/gameplay.vcxproj

@@ -15,6 +15,7 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
+    <ClCompile Include="src\AbsoluteLayout.cpp" />
     <ClCompile Include="src\Animation.cpp" />
     <ClCompile Include="src\AnimationClip.cpp" />
     <ClCompile Include="src\AnimationController.cpp" />
@@ -26,13 +27,18 @@
     <ClCompile Include="src\AudioSource.cpp" />
     <ClCompile Include="src\BoundingBox.cpp" />
     <ClCompile Include="src\BoundingSphere.cpp" />
+    <ClCompile Include="src\Button.cpp" />
     <ClCompile Include="src\Camera.cpp" />
+    <ClCompile Include="src\CheckBox.cpp" />
+    <ClCompile Include="src\Container.cpp" />
+    <ClCompile Include="src\Control.cpp" />
     <ClCompile Include="src\Curve.cpp" />
     <ClCompile Include="src\DebugNew.cpp" />
     <ClCompile Include="src\DepthStencilTarget.cpp" />
     <ClCompile Include="src\Effect.cpp" />
     <ClCompile Include="src\FileSystem.cpp" />
     <ClCompile Include="src\Font.cpp" />
+    <ClCompile Include="src\Form.cpp" />
     <ClCompile Include="src\FrameBuffer.cpp" />
     <ClCompile Include="src\Frustum.cpp" />
     <ClCompile Include="src\Game.cpp" />
@@ -41,6 +47,7 @@
     <ClCompile Include="src\gameplay-main-win32.cpp" />
     <ClCompile Include="src\Image.cpp" />
     <ClCompile Include="src\Joint.cpp" />
+    <ClCompile Include="src\Label.cpp" />
     <ClCompile Include="src\Light.cpp" />
     <ClCompile Include="src\Material.cpp" />
     <ClCompile Include="src\MeshBatch.cpp" />
@@ -54,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" />
@@ -69,6 +77,7 @@
     <ClCompile Include="src\PlatformWin32.cpp" />
     <ClCompile Include="src\Properties.cpp" />
     <ClCompile Include="src\Quaternion.cpp" />
+    <ClCompile Include="src\RadioButton.cpp" />
     <ClCompile Include="src\Ray.cpp" />
     <ClCompile Include="src\Rectangle.cpp" />
     <ClCompile Include="src\Ref.cpp" />
@@ -76,18 +85,23 @@
     <ClCompile Include="src\RenderTarget.cpp" />
     <ClCompile Include="src\Scene.cpp" />
     <ClCompile Include="src\SceneLoader.cpp" />
+    <ClCompile Include="src\Slider.cpp" />
     <ClCompile Include="src\SpriteBatch.cpp" />
     <ClCompile Include="src\Technique.cpp" />
+    <ClCompile Include="src\TextBox.cpp" />
     <ClCompile Include="src\Texture.cpp" />
+    <ClCompile Include="src\Theme.cpp" />
     <ClCompile Include="src\Transform.cpp" />
     <ClCompile Include="src\Vector2.cpp" />
     <ClCompile Include="src\Vector3.cpp" />
     <ClCompile Include="src\Vector4.cpp" />
     <ClCompile Include="src\VertexAttributeBinding.cpp" />
     <ClCompile Include="src\VertexFormat.cpp" />
+    <ClCompile Include="src\VerticalLayout.cpp" />
     <ClCompile Include="src\Viewport.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="src\AbsoluteLayout.h" />
     <ClInclude Include="src\Animation.h" />
     <ClInclude Include="src\AnimationClip.h" />
     <ClInclude Include="src\AnimationController.h" />
@@ -100,13 +114,18 @@
     <ClInclude Include="src\Base.h" />
     <ClInclude Include="src\BoundingBox.h" />
     <ClInclude Include="src\BoundingSphere.h" />
+    <ClInclude Include="src\Button.h" />
     <ClInclude Include="src\Camera.h" />
+    <ClInclude Include="src\CheckBox.h" />
+    <ClInclude Include="src\Container.h" />
+    <ClInclude Include="src\Control.h" />
     <ClInclude Include="src\Curve.h" />
     <ClInclude Include="src\DebugNew.h" />
     <ClInclude Include="src\DepthStencilTarget.h" />
     <ClInclude Include="src\Effect.h" />
     <ClInclude Include="src\FileSystem.h" />
     <ClInclude Include="src\Font.h" />
+    <ClInclude Include="src\Form.h" />
     <ClInclude Include="src\FrameBuffer.h" />
     <ClInclude Include="src\Frustum.h" />
     <ClInclude Include="src\Game.h" />
@@ -114,6 +133,8 @@
     <ClInclude Include="src\Image.h" />
     <ClInclude Include="src\Joint.h" />
     <ClInclude Include="src\Keyboard.h" />
+    <ClInclude Include="src\Label.h" />
+    <ClInclude Include="src\Layout.h" />
     <ClInclude Include="src\Light.h" />
     <ClInclude Include="src\Material.h" />
     <ClInclude Include="src\MeshBatch.h" />
@@ -128,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" />
@@ -141,6 +163,7 @@
     <ClInclude Include="src\Platform.h" />
     <ClInclude Include="src\Properties.h" />
     <ClInclude Include="src\Quaternion.h" />
+    <ClInclude Include="src\RadioButton.h" />
     <ClInclude Include="src\Ray.h" />
     <ClInclude Include="src\Rectangle.h" />
     <ClInclude Include="src\Ref.h" />
@@ -148,9 +171,12 @@
     <ClInclude Include="src\RenderTarget.h" />
     <ClInclude Include="src\Scene.h" />
     <ClInclude Include="src\SceneLoader.h" />
+    <ClInclude Include="src\Slider.h" />
     <ClInclude Include="src\SpriteBatch.h" />
     <ClInclude Include="src\Technique.h" />
+    <ClInclude Include="src\TextBox.h" />
     <ClInclude Include="src\Texture.h" />
+    <ClInclude Include="src\Theme.h" />
     <ClInclude Include="src\Touch.h" />
     <ClInclude Include="src\Transform.h" />
     <ClInclude Include="src\Vector2.h" />
@@ -158,6 +184,7 @@
     <ClInclude Include="src\Vector4.h" />
     <ClInclude Include="src\VertexAttributeBinding.h" />
     <ClInclude Include="src\VertexFormat.h" />
+    <ClInclude Include="src\VerticalLayout.h" />
     <ClInclude Include="src\Viewport.h" />
   </ItemGroup>
   <ItemGroup>

+ 82 - 1
gameplay/gameplay.vcxproj.filters

@@ -228,6 +228,45 @@
     <ClCompile Include="src\PlatformAndroid.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\AbsoluteLayout.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Button.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\CheckBox.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Container.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Control.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Label.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\RadioButton.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Slider.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\VerticalLayout.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Form.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\Theme.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <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">
@@ -443,6 +482,48 @@
     <ClInclude Include="src\Mouse.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\AbsoluteLayout.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Button.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\CheckBox.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Container.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Control.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Label.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Layout.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\RadioButton.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Slider.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\VerticalLayout.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Form.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\Theme.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <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">
@@ -577,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;
 }

+ 44 - 0
gameplay/src/AbsoluteLayout.cpp

@@ -0,0 +1,44 @@
+#include "Base.h"
+#include "Control.h"
+#include "AbsoluteLayout.h"
+#include "Container.h"
+
+namespace gameplay
+{
+    AbsoluteLayout::AbsoluteLayout()
+    {
+    }
+
+    AbsoluteLayout::AbsoluteLayout(const AbsoluteLayout& copy)
+    {
+    }
+
+    AbsoluteLayout::~AbsoluteLayout()
+    {
+    }
+
+    AbsoluteLayout* AbsoluteLayout::create()
+    {
+        AbsoluteLayout* al = new AbsoluteLayout();
+        return al;
+    }
+
+    Layout::Type AbsoluteLayout::getType()
+    {
+        return Layout::LAYOUT_ABSOLUTE;
+    }
+
+    void AbsoluteLayout::update(const Container* container)
+    {
+        // An AbsoluteLayout does nothing to modify the layout of Controls.
+        std::vector<Control*> controls = container->getControls();
+        unsigned int controlsCount = controls.size();
+        for (unsigned int i = 0; i < controlsCount; i++)
+        {
+            if (controls[i]->isDirty())
+            {
+                controls[i]->update(container->getPosition());
+            }
+        }
+    }
+}

+ 26 - 0
gameplay/src/AbsoluteLayout.h

@@ -0,0 +1,26 @@
+#ifndef ABSOLUTELAYOUT_H_
+#define ABSOLUTELAYOUT_H_
+
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class AbsoluteLayout : public Layout
+{
+public:
+    static AbsoluteLayout* create();
+
+    Layout::Type getType();
+
+    void update(const Container* container);
+
+private:
+    AbsoluteLayout();
+    AbsoluteLayout(const AbsoluteLayout& copy);
+    virtual ~AbsoluteLayout();
+};
+
+}
+
+#endif

+ 38 - 16
gameplay/src/Animation.cpp

@@ -31,7 +31,8 @@ Animation::~Animation()
 {
     if (_defaultClip)
     {
-        _defaultClip->stop();
+        if (_defaultClip->isClipStateBitSet(AnimationClip::CLIP_IS_PLAYING_BIT))
+            _controller->unschedule(_defaultClip);
         SAFE_RELEASE(_defaultClip);
     }
 
@@ -42,7 +43,8 @@ Animation::~Animation()
         while (clipIter != _clips->end())
         {   
             AnimationClip* clip = *clipIter;
-            clip->stop();
+            if (clip->isClipStateBitSet(AnimationClip::CLIP_IS_PLAYING_BIT))
+                _controller->unschedule(clip);
             SAFE_RELEASE(clip);
             clipIter++;
         }
@@ -65,7 +67,6 @@ Animation::Channel::Channel(Animation* animation, AnimationTarget* target, int p
 Animation::Channel::~Channel()
 {
     SAFE_DELETE(_curve);
-    _animation->removeChannel(this);
     SAFE_RELEASE(_animation);
 }
 
@@ -120,10 +121,10 @@ AnimationClip* Animation::getClip(const char* id)
     }
 }
 
-void Animation::play(const char* id)
+void Animation::play(const char* clipId)
 {
     // If id is NULL, play the default clip.
-    if (id == NULL)
+    if (clipId == NULL)
     {
         if (_defaultClip == NULL)
             createDefaultClip();
@@ -133,33 +134,54 @@ void Animation::play(const char* id)
     else
     {
         // Find animation clip.. and play.
-        AnimationClip* clip = findClip(id);
+        AnimationClip* clip = findClip(clipId);
         if (clip != NULL)
-        {
             clip->play();
-        }
     }
 }
 
-void Animation::stop(const char* id)
+void Animation::stop(const char* clipId)
 {
     // If id is NULL, play the default clip.
-    if (id == NULL)
+    if (clipId == NULL)
     {
-        if (_defaultClip == NULL)
-            createDefaultClip();
-
-        _defaultClip->stop();
+        if (_defaultClip)
+            _defaultClip->stop();
     }
     else
     {
         // Find animation clip.. and play.
-        AnimationClip* clip = findClip(id);
+        AnimationClip* clip = findClip(clipId);
         if (clip != NULL)
-        {
             clip->stop();
+    }
+}
+
+void Animation::pause(const char * clipId)
+{
+    if (clipId == NULL)
+    {
+        if (_defaultClip)
+            _defaultClip->pause();
+    }
+    else
+    {
+        AnimationClip* clip = findClip(clipId);
+        if (clip != NULL)
+            clip->pause();
+    }
+}
+
+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()

+ 13 - 0
gameplay/src/Animation.h

@@ -82,6 +82,18 @@ public:
      */
     void stop(const char* clipId = NULL);
 
+    /** 
+     * Pauses the AnimationClip with the specified name.
+     *
+     * @param clipId The ID of the AnimationClip to pause. If NULL, pauses the default clip.
+     */
+    void pause(const char* clipId = NULL);
+
+    /**
+     * Returns true if this animation targets the given AnimationTarget.
+     */
+    bool targets(AnimationTarget* target) const;
+
 private:
 
     /**
@@ -92,6 +104,7 @@ private:
      */
     class Channel
     {
+        friend class AnimationController;
         friend class AnimationClip;
         friend class Animation;
         friend class AnimationTarget;

+ 135 - 64
gameplay/src/AnimationClip.cpp

@@ -36,10 +36,31 @@ AnimationClip::~AnimationClip()
     SAFE_RELEASE(_crossFadeToClip);
     SAFE_DELETE(_beginListeners);
     SAFE_DELETE(_endListeners);
-    SAFE_DELETE(_listeners);
+
+    if (_listeners)
+    {
+        *_listenerItr = _listeners->begin();
+        while (*_listenerItr != _listeners->end())
+        {
+            ListenerEvent* lEvt = **_listenerItr;
+            SAFE_DELETE(lEvt);
+            ++*_listenerItr;
+        }
+        SAFE_DELETE(_listeners);
+    }
     SAFE_DELETE(_listenerItr);
 }
 
+AnimationClip::ListenerEvent::ListenerEvent(Listener* listener, unsigned long eventTime)
+{
+    _listener = listener;
+    _eventTime = eventTime;
+}
+
+AnimationClip::ListenerEvent::~ListenerEvent()
+{
+}
+
 const char* AnimationClip::getID() const
 {
     return _id.c_str();
@@ -108,6 +129,11 @@ unsigned long AnimationClip::getActiveDuration() const
     return _activeDuration;
 }
 
+unsigned long AnimationClip::getDuration() const
+{
+    return _duration;
+}
+
 void AnimationClip::setSpeed(float speed)
 {
     _speed = speed;
@@ -137,14 +163,26 @@ void AnimationClip::play()
 {
     if (isClipStateBitSet(CLIP_IS_PLAYING_BIT))
     {
-        _animation->_controller->unschedule(this);
-        resetClipStateBit(CLIP_IS_STARTED_BIT);
-        onEnd();
+        // If paused, reset the bit and return.
+        if (isClipStateBitSet(CLIP_IS_PAUSED_BIT))
+        {
+            resetClipStateBit(CLIP_IS_PAUSED_BIT);
+            return;
+        }
+
+        // If the clip is set to be removed, reset the flag.
+        if (isClipStateBitSet(CLIP_IS_MARKED_FOR_REMOVAL_BIT))
+            resetClipStateBit(CLIP_IS_MARKED_FOR_REMOVAL_BIT);
+
+        // Set the state bit to restart.
+        setClipStateBit(CLIP_IS_RESTARTED_BIT);
     }
     else
+    {
         setClipStateBit(CLIP_IS_PLAYING_BIT);
+        _animation->_controller->schedule(this);
+    }
     
-    _animation->_controller->schedule(this);
     _timeStarted = Game::getGameTime();
 }
 
@@ -152,9 +190,20 @@ void AnimationClip::stop()
 {
     if (isClipStateBitSet(CLIP_IS_PLAYING_BIT))
     {
-        _animation->_controller->unschedule(this);
-        resetClipStateBit(CLIP_IS_PLAYING_BIT | CLIP_IS_STARTED_BIT);
-        onEnd();
+        // Reset the restarted and paused bits. 
+        resetClipStateBit(CLIP_IS_RESTARTED_BIT);
+        resetClipStateBit(CLIP_IS_PAUSED_BIT);
+
+        // Mark the clip to removed from the AnimationController.
+        setClipStateBit(CLIP_IS_MARKED_FOR_REMOVAL_BIT);
+    }
+}
+
+void AnimationClip::pause()
+{
+    if (isClipStateBitSet(CLIP_IS_PLAYING_BIT) && !isClipStateBitSet(CLIP_IS_MARKED_FOR_REMOVAL_BIT))
+    {
+        setClipStateBit(CLIP_IS_PAUSED_BIT);
     }
 }
 
@@ -162,26 +211,33 @@ void AnimationClip::crossFade(AnimationClip* clip, unsigned long duration)
 {
     assert(clip);
 
+    // Check if the given clip is fading into this clip.
+    // We should reset the clip from fading out, and this one from fading in 
+    // in order to start the crossfade back the other way.
+    if (clip->isClipStateBitSet(CLIP_IS_FADING_OUT_BIT) && clip->_crossFadeToClip == this)
+    {
+        clip->resetClipStateBit(CLIP_IS_FADING_OUT_BIT);
+        clip->_crossFadeToClip->resetClipStateBit(CLIP_IS_FADING_IN_BIT);
+        SAFE_RELEASE(clip->_crossFadeToClip);
+    }
+
     // If I already have a clip I'm fading to and it's not the same as the given clip release it.
     // Assign the new clip and increase it's ref count.
     if (_crossFadeToClip)
     {
-        _crossFadeToClip->stop();
-        _crossFadeToClip->resetClipStateBit(CLIP_IS_FADING_IN_BIT);
         SAFE_RELEASE(_crossFadeToClip);
     }
-        
+
+    // Set and initialize the crossfade clip
     _crossFadeToClip = clip;
     _crossFadeToClip->addRef();
-        
-    // Set the crossfade clip to fading in, and initialize it's blend weight to zero.
     _crossFadeToClip->setClipStateBit(CLIP_IS_FADING_IN_BIT);
     _crossFadeToClip->_blendWeight = 0.0f;
     
-    // Set this clip to fade out, set the fade duration and reset the fade elapsed time.
+    // Set and intiliaze this clip to fade out
     setClipStateBit(CLIP_IS_FADING_OUT_STARTED_BIT);
     setClipStateBit(CLIP_IS_FADING_OUT_BIT);
-    _crossFadeOutElapsed = 0;
+    _crossFadeOutElapsed = 0L;
     _crossFadeOutDuration = duration;
     
     // If this clip is currently not playing, we should start playing it.
@@ -195,39 +251,40 @@ void AnimationClip::crossFade(AnimationClip* clip, unsigned long duration)
 void AnimationClip::addListener(AnimationClip::Listener* listener, unsigned long eventTime)
 {
     assert(listener);
-    assert(eventTime < _duration);
+    assert(eventTime < _activeDuration);
 
-    listener->_listenerTime = eventTime;
+    ListenerEvent* listenerEvent = new ListenerEvent(listener, eventTime);
 
     if (!_listeners)
     {
-        _listeners = new std::list<Listener*>;
-        _listeners->push_front(listener);
+        _listeners = new std::list<ListenerEvent*>;
+        _listeners->push_front(listenerEvent);
 
-        _listenerItr = new std::list<Listener*>::iterator;
+        _listenerItr = new std::list<ListenerEvent*>::iterator;
         if (isClipStateBitSet(CLIP_IS_PLAYING_BIT))
             *_listenerItr = _listeners->begin();
     }
     else
     {
-        for (std::list<Listener*>::iterator itr = _listeners->begin(); itr != _listeners->begin(); itr++)
+        for (std::list<ListenerEvent*>::iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
         {
-            if (eventTime < (*itr)->_listenerTime)
+            if (eventTime < (*itr)->_eventTime)
             {
-                itr = _listeners->insert(itr, listener);
+                itr = _listeners->insert(itr, listenerEvent);
 
                 // If playing, update the iterator if we need to.
                 // otherwise, it will just be set the next time the clip gets played.
                 if (isClipStateBitSet(CLIP_IS_PLAYING_BIT))
                 {
                     unsigned long currentTime = _elapsedTime % _duration;
-                    if ((_speed >= 0.0f && currentTime < eventTime && (*_listenerItr == _listeners->end() || eventTime < (**_listenerItr)->_listenerTime)) || 
-                        (_speed <= 0 && currentTime > eventTime && (*_listenerItr == _listeners->begin() || eventTime > (**_listenerItr)->_listenerTime)))
+                    if ((_speed >= 0.0f && currentTime < eventTime && (*_listenerItr == _listeners->end() || eventTime < (**_listenerItr)->_eventTime)) || 
+                        (_speed <= 0 && currentTime > eventTime && (*_listenerItr == _listeners->begin() || eventTime > (**_listenerItr)->_eventTime)))
                         *_listenerItr = itr;
                 }
                 return;
             }
         }
+        _listeners->push_back(listenerEvent);
     }
 }
 
@@ -249,8 +306,21 @@ void AnimationClip::addEndListener(AnimationClip::Listener* listener)
 
 bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*>* activeTargets)
 {
-    if (!isClipStateBitSet(CLIP_IS_STARTED_BIT))
+    if (isClipStateBitSet(CLIP_IS_PAUSED_BIT))
+    {
+        return false;
+    }
+    else if (isClipStateBitSet(CLIP_IS_MARKED_FOR_REMOVAL_BIT))
+    {   // If the marked for removal bit is set, it means stop() was called on the AnimationClip at some point
+        // after the last update call. Reset the flag, and return true so the AnimationClip is removed from the 
+        // running clips on the AnimationController.
+        onEnd();
+        return true;
+    }
+    else if (!isClipStateBitSet(CLIP_IS_STARTED_BIT))
+    {
         onBegin();
+    }
     else
     {
         _elapsedTime += elapsedTime * _speed;
@@ -266,9 +336,17 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
         resetClipStateBit(CLIP_IS_STARTED_BIT);
         if (_speed >= 0.0f)
         {
-            currentTime = _activeDuration % _duration; // Get's the fractional part of the final repeat.
-            if (currentTime == 0L)
-                currentTime = _duration;
+            // If _duration == 0, we have a "pose". Just set currentTime to 0.
+            if (_duration == 0)
+            {
+                currentTime = 0L;
+            }
+            else
+            {
+                currentTime = _activeDuration % _duration; // Get's the fractional part of the final repeat.
+                if (currentTime == 0L)
+                    currentTime = _duration;
+            }
         }
         else
         {
@@ -277,8 +355,11 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
     }
     else
     {
-        // Gets portion/fraction of the repeat.
-        currentTime = _elapsedTime % _duration;
+        // If _duration == 0, we have a "pose". Just set currentTime to 0.
+        if (_duration == 0)
+            currentTime = 0L;
+        else // Gets portion/fraction of the repeat.
+            currentTime = _elapsedTime % _duration;
     }
 
     // Notify any listeners of Animation events.
@@ -286,17 +367,17 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
     {
         if (_speed >= 0.0f)
         {
-            while (*_listenerItr != _listeners->end() && currentTime >= (**_listenerItr)->_listenerTime)
+            while (*_listenerItr != _listeners->end() && _elapsedTime >= (long) (**_listenerItr)->_eventTime)
             {
-                (**_listenerItr)->animationEvent(this, Listener::DEFAULT);
+                (**_listenerItr)->_listener->animationEvent(this, Listener::DEFAULT);
                 ++*_listenerItr;
             }
         }
         else
         {
-            while (*_listenerItr != _listeners->begin() && currentTime <= (**_listenerItr)->_listenerTime)
+            while (*_listenerItr != _listeners->begin() && _elapsedTime <= (long) (**_listenerItr)->_eventTime)
             {
-                (**_listenerItr)->animationEvent(this, Listener::DEFAULT);
+                (**_listenerItr)->_listener->animationEvent(this, Listener::DEFAULT);
                 --*_listenerItr;
             }
         }
@@ -338,20 +419,11 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
         }
         else
         {   // Fade is done.
-            // Set blendweights
             _crossFadeToClip->_blendWeight = 1.0f;
-
-            // Adjust the crossfade clip's blend weight if this clip is also fading in.
-            if (isClipStateBitSet(CLIP_IS_FADING_IN_BIT))
-                _crossFadeToClip->_blendWeight *= _blendWeight;
-
-            _blendWeight = 0.0f; 
-
-            // reset the states of our clips to turn off the cross fade.
+            _blendWeight = 0.0f;
+            resetClipStateBit(CLIP_IS_STARTED_BIT);            
+            resetClipStateBit(CLIP_IS_FADING_OUT_BIT);
             _crossFadeToClip->resetClipStateBit(CLIP_IS_FADING_IN_BIT);
-            resetClipStateBit(CLIP_IS_FADING_OUT_BIT | CLIP_IS_STARTED_BIT);
-
-            // Release the crossfade clip, mark ourselves as done and set our blend weight to 0.
             SAFE_RELEASE(_crossFadeToClip);
         }
     }
@@ -380,20 +452,9 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
     }
 
     // When ended. Probably should move to it's own method so we can call it when the clip is ended early.
-    if (!isClipStateBitSet(CLIP_IS_STARTED_BIT))
+    if (isClipStateBitSet(CLIP_IS_MARKED_FOR_REMOVAL_BIT) || !isClipStateBitSet(CLIP_IS_STARTED_BIT))
     {
         onEnd();
-        // Notify end listeners if any.
-        if (_endListeners)
-        {
-            std::vector<Listener*>::iterator listener = _endListeners->begin();
-            while (listener != _endListeners->end())
-            {
-                (*listener)->animationEvent(this, Listener::END);
-                listener++;
-            }
-        }
-
         return true;
     }
 
@@ -434,21 +495,31 @@ void AnimationClip::onBegin()
 void AnimationClip::onEnd()
 {
     _blendWeight = 1.0f;
-    _timeStarted = 0;
-    resetClipStateBit(CLIP_IS_PLAYING_BIT);
+    resetClipStateBit(CLIP_ALL_BITS);
+
+    // Notify end listeners if any.
+    if (_endListeners)
+    {
+        std::vector<Listener*>::iterator listener = _endListeners->begin();
+        while (listener != _endListeners->end())
+        {
+            (*listener)->animationEvent(this, Listener::END);
+            listener++;
+        }
+    }
 }
 
-bool AnimationClip::isClipStateBitSet(char bit) const
+bool AnimationClip::isClipStateBitSet(unsigned char bit) const
 {
     return (_stateBits & bit) == bit;
 }
 
-void AnimationClip::setClipStateBit(char bit)
+void AnimationClip::setClipStateBit(unsigned char bit)
 {
     _stateBits |= bit;
 }
 
-void AnimationClip::resetClipStateBit(char bit)
+void AnimationClip::resetClipStateBit(unsigned char bit)
 {
     _stateBits &= ~bit;
 }

+ 69 - 33
gameplay/src/AnimationClip.h

@@ -37,7 +37,6 @@ public:
     public:
 
         Listener() 
-            : _listenerTime(0L)
         {
         }
 
@@ -66,9 +65,6 @@ public:
          * Handles when animation event occurs.
          */
         virtual void animationEvent(AnimationClip* clip, EventType type) = 0;
-
-    private:
-        unsigned long _listenerTime;
     };
 
     /**
@@ -138,6 +134,13 @@ public:
      */
     unsigned long getActiveDuration() const;
 
+    /**
+     * Gets the AnimationClip's duration.
+     *
+     * @return the AnimationClip's duration.
+     */
+    unsigned long getDuration() const;
+
     /**
      * Set the AnimationClip's running speed. 
      *
@@ -183,6 +186,11 @@ public:
      */
     void stop();
 
+    /**
+     * Pauses the AnimationClip.
+     */
+    void pause();
+
     /**
      * Fades this clip out, and the specified clip in over the given duration.
      *
@@ -217,11 +225,39 @@ public:
     void addListener(AnimationClip::Listener* listener, unsigned long eventTime);
 
 private:
-    static const char CLIP_IS_PLAYING_BIT = 0x01;             // Bit representing whether AnimationClip is a running clip in AnimationController
-    static const char CLIP_IS_STARTED_BIT = 0x02;             // Bit representing whether the AnimationClip has actually been started (ie: received first call to update())
-    static const char CLIP_IS_FADING_OUT_STARTED_BIT = 0x04;  // Bit representing that a cross fade has started.
-    static const char CLIP_IS_FADING_OUT_BIT = 0x08;          // Bit representing whether the clip is fading out.
-    static const char CLIP_IS_FADING_IN_BIT = 0x10;           // Bit representing whether the clip is fading out.
+    /**
+     * State bits.
+     */
+    static const unsigned char CLIP_IS_PLAYING_BIT = 0x01;             // Bit representing whether AnimationClip is a running clip in AnimationController
+    static const unsigned char CLIP_IS_STARTED_BIT = 0x02;             // Bit representing whether the AnimationClip has actually been started (ie: received first call to update())
+    static const unsigned char CLIP_IS_FADING_OUT_STARTED_BIT = 0x04;  // Bit representing that a cross fade has started.
+    static const unsigned char CLIP_IS_FADING_OUT_BIT = 0x08;          // Bit representing whether the clip is fading out.
+    static const unsigned char CLIP_IS_FADING_IN_BIT = 0x10;           // Bit representing whether the clip is fading out.
+    static const unsigned char CLIP_IS_MARKED_FOR_REMOVAL_BIT = 0x20;  // Bit representing whether the clip has ended and should be removed from the AnimationController.
+    static const unsigned char CLIP_IS_RESTARTED_BIT = 0x40;           // Bit representing if the clip should be restarted by the AnimationController.
+    static const unsigned char CLIP_IS_PAUSED_BIT = 0x80;              // Bit representing if the clip is currently paused.
+    static const unsigned char CLIP_ALL_BITS = 0xFF;                   // Bit mask for all the state bits.
+
+    /**
+     * ListenerEvent.
+     *
+     * Internal structure used for storing the event time at which an AnimationClip::Listener should be called back.
+     */
+    struct ListenerEvent
+    {
+        /** 
+         * Constructor.
+         */
+        ListenerEvent(Listener* listener, unsigned long eventTime);
+
+        /**
+         * Destructor.
+         */
+        ~ListenerEvent();
+
+        Listener* _listener;        // This listener to call back when this event is triggered.
+        unsigned long _eventTime;   // The time at which the listener will be called back at during the playback of the AnimationClip.
+    };
 
     /**
      * Constructor.
@@ -261,38 +297,38 @@ private:
     /**
      * Determines whether the given bit is set in the AnimationClip's state.
      */
-    bool isClipStateBitSet(char bit) const;
+    bool isClipStateBitSet(unsigned char bit) const;
 
     /**
      * Sets the given bit in the AnimationClip's state.
      */
-    void setClipStateBit(char bit);
+    void setClipStateBit(unsigned char bit);
 
     /**
      * Resets the given bit in the AnimationClip's state.
      */
-    void resetClipStateBit(char bit);
-
-    std::string _id;                                // AnimationClip ID.
-    Animation* _animation;                          // The Animation this clip is created from.
-    unsigned long _startTime;                       // Start time of the clip.
-    unsigned long _endTime;                         // End time of the clip.
-    unsigned long _duration;                        // The total duration.
-    char _stateBits;                                // Bit flag used to keep track of the clip's current state.
-    float _repeatCount;                             // The clip's repeat count.
-    unsigned long _activeDuration;                  // The active duration of the clip.
-    float _speed;                                   // The speed that the clip is playing. Default is 1.0. Negative goes in reverse.
-    unsigned long _timeStarted;                     // The game time when this clip was actually started.
-    long _elapsedTime;                              // Time elapsed while the clip is running.
-    AnimationClip* _crossFadeToClip;                // The clip to cross fade to.
-    unsigned long _crossFadeOutElapsed;             // The amount of time that has elapsed for the crossfade.
-    unsigned long _crossFadeOutDuration;            // The duration of the cross fade.
-    float _blendWeight;                             // The clip's blendweight.
-    std::vector<AnimationValue*> _values;           // AnimationValue holder.
-    std::vector<Listener*>* _beginListeners;        // Collection of begin listeners on the clip.
-    std::vector<Listener*>* _endListeners;          // Collection of end listeners on the clip.
-    std::list<Listener*>* _listeners;               // Ordered collection of listeners on the clip.
-    std::list<Listener*>::iterator* _listenerItr;   // Iterator that points to the next listener event to be triggered.
+    void resetClipStateBit(unsigned char bit);
+
+    std::string _id;                                    // AnimationClip ID.
+    Animation* _animation;                              // The Animation this clip is created from.
+    unsigned long _startTime;                           // Start time of the clip.
+    unsigned long _endTime;                             // End time of the clip.
+    unsigned long _duration;                            // The total duration.
+    unsigned char _stateBits;                           // Bit flag used to keep track of the clip's current state.
+    float _repeatCount;                                 // The clip's repeat count.
+    unsigned long _activeDuration;                      // The active duration of the clip.
+    float _speed;                                       // The speed that the clip is playing. Default is 1.0. Negative goes in reverse.
+    unsigned long _timeStarted;                         // The game time when this clip was actually started.
+    long _elapsedTime;                                  // Time elapsed while the clip is running.
+    AnimationClip* _crossFadeToClip;                    // The clip to cross fade to.
+    unsigned long _crossFadeOutElapsed;                 // The amount of time that has elapsed for the crossfade.
+    unsigned long _crossFadeOutDuration;                // The duration of the cross fade.
+    float _blendWeight;                                 // The clip's blendweight.
+    std::vector<AnimationValue*> _values;               // AnimationValue holder.
+    std::vector<Listener*>* _beginListeners;            // Collection of begin listeners on the clip.
+    std::vector<Listener*>* _endListeners;              // Collection of end listeners on the clip.
+    std::list<ListenerEvent*>* _listeners;              // Ordered collection of listeners on the clip.
+    std::list<ListenerEvent*>::iterator* _listenerItr;  // Iterator that points to the next listener event to be triggered.
 };
 
 }

+ 67 - 18
gameplay/src/AnimationController.cpp

@@ -13,7 +13,14 @@ AnimationController::AnimationController()
 
 AnimationController::~AnimationController()
 {
-    destroyAllAnimations();
+    std::vector<Animation*>::iterator itr = _animations.begin();
+    for ( ; itr != _animations.end(); itr++)
+    {
+        Animation* temp = *itr;
+        SAFE_RELEASE(temp);
+    }
+
+    _animations.clear();
 }
 
 Animation* AnimationController::createAnimation(const char* id, AnimationTarget* target, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, Curve::InterpolationType type)
@@ -105,18 +112,31 @@ 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();
     while (clipIter != _runningClips.end())
     {
         AnimationClip* clip = *clipIter;
-        clipIter++;
         clip->stop();
+        clipIter++;
     }
-    _runningClips.clear();
-
-    _state = IDLE;
 }
 
 Animation* AnimationController::createAnimation(const char* id, AnimationTarget* target, Properties* animationProperties)
@@ -269,7 +289,13 @@ void AnimationController::initialize()
 
 void AnimationController::finalize()
 {
-    stopAllAnimations();
+    std::list<AnimationClip*>::iterator itr = _runningClips.begin();
+    for ( ; itr != _runningClips.end(); itr++)
+    {
+        AnimationClip* clip = *itr;
+        SAFE_RELEASE(clip);
+    }
+    _runningClips.clear();
     _state = STOPPED;
 }
 
@@ -326,7 +352,15 @@ void AnimationController::update(long elapsedTime)
     while (clipIter != _runningClips.end())
     {
         AnimationClip* clip = (*clipIter);
-        if (clip->update(elapsedTime, &_activeTargets))
+        if (clip->isClipStateBitSet(AnimationClip::CLIP_IS_RESTARTED_BIT))
+        {   // If the CLIP_IS_RESTARTED_BIT is set, we should end the clip and 
+            // move it from where it is in the running clips list to the back.
+            clip->onEnd();
+            clip->setClipStateBit(AnimationClip::CLIP_IS_PLAYING_BIT);
+            _runningClips.push_back(clip);
+            clipIter = _runningClips.erase(clipIter);
+        }
+        else if (clip->update(elapsedTime, &_activeTargets))
         {
             SAFE_RELEASE(clip);
             clipIter = _runningClips.erase(clipIter);
@@ -358,30 +392,45 @@ void AnimationController::addAnimation(Animation* animation)
 
 void AnimationController::destroyAnimation(Animation* animation)
 {
-    std::vector<Animation*>::iterator itr = _animations.begin();
+    assert(animation);
 
-    while (itr != _animations.end())
+    std::vector<Animation::Channel*>::iterator cItr = animation->_channels.begin();
+    for (; cItr != animation->_channels.end(); cItr++)
     {
-        if (animation == *itr)
+        Animation::Channel* channel = *cItr;
+        channel->_target->deleteChannel(channel);
+    }
+
+    std::vector<Animation*>::iterator aItr = _animations.begin();
+    while (aItr != _animations.end())
+    {
+        if (animation == *aItr)
         {
-            Animation* animation = *itr;
-            _animations.erase(itr);
-            SAFE_RELEASE(animation);
+            Animation* temp = *aItr;
+            SAFE_RELEASE(temp);
+            _animations.erase(aItr);
             return;
         }
-        itr++;
+        aItr++;
     }
 }
 
 void AnimationController::destroyAllAnimations()
 {
-    std::vector<Animation*>::iterator itr = _animations.begin();
+    std::vector<Animation*>::iterator aItr = _animations.begin();
     
-    while (itr != _animations.end())
+    while (aItr != _animations.end())
     {
-        Animation* animation = *itr;
+        Animation* animation = *aItr;
+        std::vector<Animation::Channel*>::iterator cItr = animation->_channels.begin();
+        for (; cItr != animation->_channels.end(); cItr++)
+        {
+            Animation::Channel* channel = *cItr;
+            channel->_target->deleteChannel(channel);
+        }
+
         SAFE_RELEASE(animation);
-        itr++;
+        aItr++;
     }
 
     _animations.clear();

+ 15 - 10
gameplay/src/AnimationController.h

@@ -106,10 +106,25 @@ 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.
      */
     void stopAllAnimations();
+
+    /**
+     * Removes the given animation from this AnimationTarget.
+     */
+    void destroyAnimation(Animation* animation);
+
+    /**
+     * Removes all animations from the AnimationTarget.
+     */ 
+    void destroyAllAnimations();
        
 private:
 
@@ -193,16 +208,6 @@ private:
      * Adds an animation on this AnimationTarget.
      */ 
     void addAnimation(Animation* animation);
-
-    /**
-     * Removes the given animation from this AnimationTarget.
-     */
-    void destroyAnimation(Animation* animation);
-
-    /**
-     * Removes all animations from the AnimationTarget.
-     */ 
-    void destroyAllAnimations();
     
     State _state;                               // The current state of the AnimationController.
     std::list<AnimationClip*> _runningClips;    // A list of running AnimationClips.

+ 23 - 0
gameplay/src/AnimationTarget.cpp

@@ -19,6 +19,7 @@ AnimationTarget::~AnimationTarget()
         while (itr != _animationChannels->end())
         {
             Animation::Channel* channel = (*itr);
+            channel->_animation->removeChannel(channel);
             SAFE_DELETE(channel);
             itr++;
         }
@@ -95,6 +96,28 @@ int AnimationTarget::getPropertyId(TargetType type, const char* propertyIdStr)
     return -1;
 }
 
+void AnimationTarget::deleteChannel(Animation::Channel* channel)
+{
+    if (_animationChannels)
+    {
+        std::vector<Animation::Channel*>::iterator itr = _animationChannels->begin();
+        for ( ; itr != _animationChannels->end(); itr++)
+        {
+            Animation::Channel* temp = *itr;
+            if (channel == temp)
+            {
+                SAFE_DELETE(channel);
+                _animationChannels->erase(itr);
+
+                if (_animationChannels->empty())
+                    SAFE_DELETE(_animationChannels);
+
+                return;
+            }
+        }
+    }
+}
+
 }
 
 

+ 2 - 0
gameplay/src/AnimationTarget.h

@@ -67,6 +67,8 @@ protected:
 
     void addChannel(Animation::Channel* animation);
 
+    void deleteChannel(Animation::Channel* channel);
+
     TargetType _targetType;             // The type of target this is.
 
     char _animationPropertyBitFlag;     // Bit flag used to indicate which properties on the AnimationTarget are currently animating.

+ 71 - 23
gameplay/src/AudioBuffer.cpp

@@ -25,6 +25,17 @@ AudioBuffer::AudioBuffer(const char* path) : _filePath(path)
 
 AudioBuffer::~AudioBuffer()
 {
+    // Remove the buffer from the cache.
+    unsigned int bufferCount = (unsigned int)__buffers.size();
+    for (unsigned int i = 0; i < bufferCount; i++)
+    {
+        if (this == __buffers[i])
+        {
+            __buffers.erase(__buffers.begin() + i);
+            break;
+        }
+    }
+
 #ifndef __ANDROID__
     if (_alBuffer)
     {
@@ -179,6 +190,12 @@ bool AudioBuffer::loadWav(FILE* file, ALuint buffer)
     if (fread(stream, 1, 8, file) != 8 || memcmp(stream, "fmt ", 4) != 0 )
         return false;
     
+    unsigned int section_size;
+    section_size  = stream[7]<<24;
+    section_size |= stream[6]<<16;
+    section_size |= stream[5]<<8;
+    section_size |= stream[4];
+
     // Check for a valid pcm format.
     if (fread(stream, 1, 2, file) != 2 || stream[1] != 0 || stream[0] != 1)
     {
@@ -216,7 +233,6 @@ bool AudioBuffer::loadWav(FILE* file, ALuint buffer)
     bits  = stream[1]<<8;
     bits |= stream[0];
     
-
     // Now convert the given channel count and bit depth into an OpenAL format. 
     ALuint format = 0;
     if (bits == 8)
@@ -239,13 +255,45 @@ bool AudioBuffer::loadWav(FILE* file, ALuint buffer)
         return false;
     }
     
-    // Read the data chunk, which will hold the decoded sample data 
-    if (fread(stream, 1, 4, file) != 4 || memcmp(stream, "data", 4) != 0)
+    // Check against the size of the format header as there may be more data that we need to read
+    if (section_size > 16)
+    {
+        unsigned int length = section_size - 16;
+
+        // extension size is 2 bytes
+        if (fread(stream, 1, length, file) != length)
+            return false;
+    }
+
+    if (fread(stream, 1, 4, file) != 4)
+        return false;
+
+    // read the next chunk, could be fact section or the data section
+    if (memcmp(stream, "fact", 4) == 0)
+    {
+        if (fread(stream, 1, 4, file) != 4)
+            return false;
+
+        section_size  = stream[3]<<24;
+        section_size |= stream[2]<<16;
+        section_size |= stream[1]<<8;
+        section_size |= stream[0];
+
+        // read in the rest of the fact section
+        if (fread(stream, 1, section_size, file) != section_size)
+            return false;
+
+        if (fread(stream, 1, 4, file) != 4)
+            return false;
+    }
+
+    // should now be the data section which holds the decoded sample data
+    if (memcmp(stream, "data", 4) != 0)
     {
         LOG_ERROR("WAV file has no data.");
         return false;
     }
-    
+
     // Read how much data is remaining and buffer it up.
     unsigned int dataSize;
     fread(&dataSize, sizeof(int), 1, file);
@@ -288,34 +336,34 @@ bool AudioBuffer::loadOgg(FILE* file, ALuint buffer)
     else
         format = AL_FORMAT_STEREO16;
 
-	// size = #samples * #channels * 2 (for 16 bit)
+    // size = #samples * #channels * 2 (for 16 bit)
     unsigned int data_size = ov_pcm_total(&ogg_file, -1) * info->channels * 2;
     char* data = new char[data_size];
 
     while (size < data_size)
     {
-    	result = ov_read(&ogg_file, data + size, data_size - size, 0, 2, 1, &section);
-    	if (result > 0)
-    	{
-    		size += result;
-    	}
-    	else if (result < 0)
-    	{
-    		SAFE_DELETE_ARRAY(data);
-    		LOG_ERROR("OGG file missing data.");
-    		return false;
-    	}
-    	else
-    	{
-    		break;
-    	}
+        result = ov_read(&ogg_file, data + size, data_size - size, 0, 2, 1, &section);
+        if (result > 0)
+        {
+            size += result;
+        }
+        else if (result < 0)
+        {
+            SAFE_DELETE_ARRAY(data);
+            LOG_ERROR("OGG file missing data.");
+            return false;
+        }
+        else
+        {
+            break;
+        }
     }
     
     if (size == 0)
     {
-    	SAFE_DELETE_ARRAY(data);
-    	LOG_ERROR("Unable to read OGG data.");
-    	return false;
+        SAFE_DELETE_ARRAY(data);
+        LOG_ERROR("Unable to read OGG data.");
+        return false;
     }
 
     alBufferData(buffer, format, data, data_size, info->rate);

+ 12 - 2
gameplay/src/Base.h

@@ -17,6 +17,7 @@
 #include <string>
 #include <vector>
 #include <list>
+#include <set>
 #include <stack>
 #include <map>
 #include <algorithm>
@@ -102,13 +103,15 @@ 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))
 
 // Debug new for memory leak detection
 #include "DebugNew.h"
 
 // Object deletion macro
 #define SAFE_DELETE(x) \
-    if (x) \
     { \
         delete x; \
         x = NULL; \
@@ -116,7 +119,6 @@ extern void printError(const char* format, ...);
 
 // Array deletion macro
 #define SAFE_DELETE_ARRAY(x) \
-    if (x) \
     { \
         delete[] x; \
         x = NULL; \
@@ -145,10 +147,18 @@ extern void printError(const char* format, ...);
 #define MATH_PIOVER4                M_PI_4
 #define MATH_PIX2                   6.28318530717958647693f
 #define MATH_EPSILON                0.000001f
+#define MATH_CLAMP(x, lo, hi)       ((x < lo) ? lo : ((x > hi) ? hi : x))
 #ifndef M_1_PI
 #define M_1_PI                      0.31830988618379067154
 #endif
 
+#ifdef WIN32
+    inline float round(float r)
+    {
+        return (r > 0.0f) ? floor(r + 0.5f) : ceil(r - 0.5f);
+    }
+#endif
+
 // NOMINMAX makes sure that windef.h doesn't add macros min and max
 #ifdef WIN32
     #define NOMINMAX

+ 1 - 1
gameplay/src/BoundingBox.h

@@ -191,7 +191,7 @@ public:
  * @param box The bounding box to transform.
  * @return The resulting transformed bounding box.
  */
-inline BoundingBox operator*(const Matrix& matrix, const BoundingBox& box);
+inline const BoundingBox operator*(const Matrix& matrix, const BoundingBox& box);
 
 }
 

+ 1 - 1
gameplay/src/BoundingBox.inl

@@ -9,7 +9,7 @@ inline BoundingBox& BoundingBox::operator*=(const Matrix& matrix)
     return *this;
 }
 
-inline BoundingBox operator*(const Matrix& matrix, const BoundingBox& box)
+inline const BoundingBox operator*(const Matrix& matrix, const BoundingBox& box)
 {
     BoundingBox b(box);
     b.transform(matrix);

+ 1 - 1
gameplay/src/BoundingSphere.h

@@ -183,7 +183,7 @@ private:
  * @param sphere The bounding sphere to transform.
  * @return The resulting transformed bounding sphere.
  */
-inline BoundingSphere operator*(const Matrix& matrix, const BoundingSphere& sphere);
+inline const BoundingSphere operator*(const Matrix& matrix, const BoundingSphere& sphere);
 
 }
 

+ 1 - 1
gameplay/src/BoundingSphere.inl

@@ -9,7 +9,7 @@ inline BoundingSphere& BoundingSphere::operator*=(const Matrix& matrix)
     return *this;
 }
 
-inline BoundingSphere operator*(const Matrix& matrix, const BoundingSphere& sphere)
+inline const BoundingSphere operator*(const Matrix& matrix, const BoundingSphere& sphere)
 {
     BoundingSphere s(sphere);
     s.transform(matrix);

+ 92 - 0
gameplay/src/Button.cpp

@@ -0,0 +1,92 @@
+#include "Base.h"
+#include "Button.h"
+
+namespace gameplay
+{
+    static std::vector<Button*> __buttons;
+
+    Button::Button() : _callback(NULL)
+    {
+    }
+
+    Button::~Button()
+    {
+        SAFE_DELETE(_callback);
+    }
+
+    Button* Button::create(Theme::Style* style, Properties* properties)
+    {
+        Button* button = new Button();
+        button->_style = style;
+        properties->getVector2("position", &button->_position);
+        properties->getVector2("size", &button->_size);
+
+        const char* id = properties->getId();
+        if (id)
+        {
+            button->_id = id;
+        }
+
+        const char* text = properties->getString("text");
+        if (text)
+        {
+            button->_text = text;
+        }
+
+        __buttons.push_back(button);
+
+        return button;
+    }
+
+    Button* Button::create(const char* id, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
+    {
+        Button* button = new Button();
+        button->_id = id;
+        button->_position.set(x, y);
+        button->_size.set(width, height);
+
+        __buttons.push_back(button);
+
+        return button;
+    }
+
+    Button* Button::getButton(const char* id)
+    {
+        std::vector<Button*>::const_iterator it;
+        for (it = __buttons.begin(); it < __buttons.end(); it++)
+        {
+            Button* b = *it;
+            if (strcmp(id, b->getID()) == 0)
+            {
+                return b;
+            }
+        }
+
+        return NULL;
+    }
+
+    void Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        if (_state != STATE_DISABLED)
+        {
+            switch (evt)
+            {
+            case Touch::TOUCH_PRESS:
+                // TODO: button-down callback.
+
+                _state = Control::STATE_ACTIVE;
+                break;
+            case Touch::TOUCH_RELEASE:
+                if (_callback &&
+                    x > 0 && x <= _size.x &&
+                    y > 0 && y <= _size.y)
+                {
+                    // Button-clicked callback.
+                    _callback->trigger(this);
+                }
+                setState(Control::STATE_NORMAL);
+                break;
+            }
+        }
+    }
+}

+ 75 - 0
gameplay/src/Button.h

@@ -0,0 +1,75 @@
+#ifndef BUTTON_H_
+#define BUTTON_H_
+
+#include "Label.h"
+#include "Touch.h"
+#include "Theme.h"
+#include "Properties.h"
+
+namespace gameplay
+{
+
+class Button : public Label
+{
+    class Callback;
+
+public:
+    Button();
+    virtual ~Button();
+
+    static Button* create(Theme::Style* style, Properties* properties);
+    static Button* create(const char* id, unsigned int x, unsigned int y, unsigned int width, unsigned int height);
+    static Button* getButton(const char* id);
+
+    template <class ClassType>
+    void setCallback(ClassType* instance, void (ClassType::*callbackMethod)(Button*));
+
+    void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+protected:
+    Callback* _callback;
+
+private:
+    Button(const Button& copy);
+
+    class Callback
+    {
+    public:
+        virtual ~Callback() { }
+        virtual void trigger(Button* button) = 0;
+    };
+
+    template <class ClassType>
+    class CallbackImpl : public Callback
+    {
+        typedef void (ClassType::*CallbackMethod)(Button*);
+    public:
+        CallbackImpl(ClassType* instance, CallbackMethod method);
+        void trigger(Button* button);
+    private:
+        ClassType* _instance;
+        CallbackMethod _method;
+    };
+};
+
+template <class ClassType>
+Button::CallbackImpl<ClassType>::CallbackImpl(ClassType* instance, CallbackMethod method)
+    : _instance(instance), _method(method)
+{
+}
+
+template <class ClassType>
+void Button::setCallback(ClassType* instance, void (ClassType::*callbackMethod)(Button*))
+{
+    _callback = new CallbackImpl<ClassType>(instance, callbackMethod);
+}
+
+template <class ClassType>
+void Button::CallbackImpl<ClassType>::trigger(Button* button)
+{
+    (_instance->*_method)(button);
+}
+
+}
+
+#endif

+ 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;

+ 155 - 0
gameplay/src/CheckBox.cpp

@@ -0,0 +1,155 @@
+#include "Base.h"
+#include "CheckBox.h"
+
+namespace gameplay
+{
+
+static std::vector<CheckBox*> __checkBoxes;
+
+CheckBox::CheckBox() : _checked(false)
+{
+}
+
+CheckBox::CheckBox(const CheckBox& copy)
+{
+    // Hidden.
+}
+
+CheckBox::~CheckBox()
+{
+
+}
+
+CheckBox* CheckBox::create(Theme::Style* style, Properties* properties)
+{
+    CheckBox* checkbox = new CheckBox();
+    checkbox->_style = style;
+    properties->getVector2("position", &checkbox->_position);
+    properties->getVector2("size", &checkbox->_size);
+    checkbox->_checked = properties->getBool("checked");
+
+    const char* id = properties->getId();
+    if (id)
+    {
+        checkbox->_id = id;
+    }
+
+    const char* text = properties->getString("text");
+    if (text)
+    {
+        checkbox->_text = text;
+    }
+
+    __checkBoxes.push_back(checkbox);
+
+    return checkbox;
+}
+
+CheckBox* CheckBox::getCheckBox(const char* id)
+{
+    std::vector<CheckBox*>::const_iterator it;
+    for (it = __checkBoxes.begin(); it < __checkBoxes.end(); it++)
+    {
+        CheckBox* checkbox = *it;
+        if (strcmp(id, checkbox->getID()) == 0)
+        {
+            return checkbox;
+        }
+    }
+
+    return NULL;
+}
+
+void CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    switch (evt)
+    {
+    case Touch::TOUCH_RELEASE:
+        {
+            if (_state == Control::STATE_ACTIVE)
+            {
+                if (x > 0 && x <= _size.x &&
+                    y > 0 && y <= _size.y)
+                {
+                    _checked = !_checked;
+                }
+            }
+        }
+        break;
+    }
+
+    Button::touchEvent(evt, x, y, contactIndex);
+}
+
+void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+{
+    // Left, v-center.
+    // TODO: Set an alignment for icons.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getCheckBoxIcon();
+    if (icon)
+    {
+        const Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+        Theme::Border border;
+        if (containerRegion)
+        {
+                border = containerRegion->getBorder();
+        }
+        const Theme::Padding padding = _style->getPadding();
+
+        const Vector2 size = icon->getSize();
+        const Vector4 color = icon->getColor();
+
+        Vector2 pos(position.x + _position.x + border.left + padding.left,
+            position.y + _position.y + (_size.y - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+
+        if (_checked)
+        {
+            const Theme::UVs on = icon->getOnUVs();
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, on.u1, on.v1, on.u2, on.v2, color);
+        }
+        else
+        {
+            const Theme::UVs off = icon->getOffUVs();
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, off.u1, off.v1, off.u2, off.v2, color);
+        }
+    }
+}
+
+void CheckBox::drawText(const Vector2& position)
+{
+    // TODO: Batch all labels that use the same font.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getCheckBoxIcon();
+    Theme::Border border;
+    Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+    if (containerRegion)
+    {
+        border = overlay->getContainerRegion()->getBorder();
+    }
+    Theme::Padding padding = _style->getPadding();
+
+    // Set up the text viewport.
+    float iconWidth = 0.0f;
+    if (icon)
+    {
+        iconWidth = icon->getSize().x;
+    }
+
+    Font* font = overlay->getFont();
+    Vector2 pos(position.x + _position.x + border.left + padding.left + iconWidth,
+            position.y + _position.y + border.top + padding.top);
+
+    Rectangle viewport(pos.x, pos.y,
+        _size.x - border.left - padding.left - border.right - padding.right - iconWidth,
+        _size.y - border.top - padding.top - border.bottom - padding.bottom - font->getSize());
+    
+    // Draw the text.
+    font->begin();
+    font->drawText(_text.c_str(), viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+    font->end();
+
+    _dirty = false;
+}
+
+}

+ 34 - 0
gameplay/src/CheckBox.h

@@ -0,0 +1,34 @@
+#ifndef CHECKBOX_H_
+#define CHECKBOX_H_
+
+#include "Theme.h"
+#include "Properties.h"
+#include "Touch.h"
+#include "Button.h"
+
+namespace gameplay
+{
+
+class CheckBox : public Button
+{
+public:
+    CheckBox();
+    ~CheckBox();
+
+    static CheckBox* create(Theme::Style* style, Properties* properties);
+    static CheckBox* getCheckBox(const char* id);
+
+    void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    void drawText(const Vector2& position);
+
+private:
+    CheckBox(const CheckBox& copy);
+
+    bool _checked;
+};
+
+}
+
+#endif

+ 352 - 0
gameplay/src/Container.cpp

@@ -0,0 +1,352 @@
+#include "Base.h"
+#include "Container.h"
+#include "Layout.h"
+#include "AbsoluteLayout.h"
+#include "VerticalLayout.h"
+#include "Label.h"
+#include "Button.h"
+#include "CheckBox.h"
+#include "RadioButton.h"
+#include "Slider.h"
+#include "TextBox.h"
+
+namespace gameplay
+{
+    static std::vector<Container*> __containers;
+
+    Container::Container() : _layout(NULL)
+    {
+    }
+
+    Container::Container(const Container& copy)
+    {
+    }
+
+    Container::~Container()
+    {
+        std::vector<Control*>::iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            SAFE_RELEASE((*it));
+        }
+
+        SAFE_RELEASE(_layout);
+    }
+
+    Container* Container::create(const char* id, Layout::Type type)
+    {
+        Layout* layout = NULL;
+        switch(type)
+        {
+        case Layout::LAYOUT_ABSOLUTE:
+            layout = AbsoluteLayout::create();
+            break;
+        case Layout::LAYOUT_FLOW:
+            break;
+        case Layout::LAYOUT_VERTICAL:
+            layout = VerticalLayout::create();
+            break;
+        }
+
+        Container* container = new Container();
+        container->_id = id;
+        container->_layout = layout;
+
+        __containers.push_back(container);
+
+        return container;
+    }
+
+    Container* Container::create(Theme::Style* style, Properties* properties, Theme* theme)
+    {
+        const char* id = properties->getId();
+        const char* layoutString = properties->getString("layout");
+        Container* container = Container::create(id, getLayoutType(layoutString));
+
+        container->_style = style;
+        properties->getVector2("position", &container->_position);
+        properties->getVector2("size", &container->_size);
+
+        container->addControls(theme, properties);
+
+        return container;
+    }
+
+    void Container::addControls(Theme* theme, Properties* properties)
+    {
+        // Add all the controls to this container.
+        Properties* controlSpace = properties->getNextNamespace();
+        while (controlSpace != NULL)
+        {
+            Control* control = NULL;
+
+            const char* controlStyleName = controlSpace->getString("style");
+            Theme::Style* controlStyle = NULL;
+            if (controlStyleName)
+            {
+                 controlStyle = theme->getStyle(controlStyleName);
+            }
+
+            std::string controlName(controlSpace->getNamespace());
+            std::transform(controlName.begin(), controlName.end(), controlName.begin(), (int(*)(int))toupper);
+            if (controlName == "LABEL")
+            {
+                control = Label::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "BUTTON")
+            {
+                control = Button::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "CHECKBOX")
+            {
+                control = CheckBox::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "RADIOBUTTON")
+            {
+                control = RadioButton::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "CONTAINER")
+            {
+                control = Container::create(controlStyle, controlSpace, theme);
+            }
+            else if (controlName == "SLIDER")
+            {
+                control = Slider::create(controlStyle, controlSpace);
+            }
+            else if (controlName == "TEXTBOX")
+            {
+                control = TextBox::create(controlStyle, controlSpace);
+            }
+
+            // Add the new control to the form.
+            if (control)
+            {
+                addControl(control);
+            }
+
+            // Get the next control.
+            controlSpace = properties->getNextNamespace();
+        }
+    }
+
+    Container* Container::getContainer(const char* id)
+    {
+        std::vector<Container*>::const_iterator it;
+        for (it = __containers.begin(); it < __containers.end(); it++)
+        {
+            Container* c = *it;
+            if (strcmp(id, c->getID()) == 0)
+            {
+                return c;
+            }
+        }
+
+        return NULL;
+    }
+
+    Layout* Container::getLayout()
+    {
+        return _layout;
+    }
+
+    unsigned int Container::addControl(Control* control)
+    {
+        _controls.push_back(control);
+
+        return _controls.size() - 1;
+    }
+
+    void Container::insertControl(Control* control, unsigned int index)
+    {
+        std::vector<Control*>::iterator it = _controls.begin() + index;
+        _controls.insert(it, control);
+    }
+
+    void Container::removeControl(unsigned int index)
+    {
+        std::vector<Control*>::iterator it = _controls.begin() + index;
+        _controls.erase(it);
+    }
+
+    void Container::removeControl(const char* id)
+    {
+        std::vector<Control*>::iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* c = *it;
+            if (strcmp(id, c->getID()) == 0)
+            {
+                _controls.erase(it);
+            }
+        }
+    }
+
+    void Container::removeControl(Control* control)
+    {
+        std::vector<Control*>::iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            if (*it == control)
+            {
+                _controls.erase(it);
+            }
+        }
+    }
+
+    Control* Container::getControl(unsigned int index) const
+    {
+        std::vector<Control*>::const_iterator it = _controls.begin() + index;
+        return *it;
+    }
+
+    Control* Container::getControl(const char* id) const
+    {
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* c = *it;
+            if (strcmp(id, c->getID()) == 0)
+            {
+                return c;
+            }
+        }
+
+        return NULL;
+    }
+
+    std::vector<Control*> Container::getControls() const
+    {
+        return _controls;
+    }
+
+    void Container::update(const Vector2& position)
+    {
+        // Should probably have sizeChanged() for this.
+        if (isDirty())
+        {
+            _layout->update(this);
+        }
+
+        _dirty = false;
+    }
+
+    void Container::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->drawSprites(spriteBatch, pos);
+        }
+
+        _dirty = false;
+    }
+
+    void Container::drawText(const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->drawText(pos);
+        }
+
+        _dirty = false;
+    }
+
+    bool Container::isDirty()
+    {
+        if (_dirty)
+        {
+            return true;
+        }
+        else
+        {
+            std::vector<Control*>::const_iterator it;
+            for (it = _controls.begin(); it < _controls.end(); it++)
+            {
+                if ((*it)->isDirty())
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    void Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        if (getState() == Control::STATE_DISABLED)
+            return;
+
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            const Vector2& size = control->getSize();
+            const Vector2& position = control->getPosition();
+            
+            if (control->getState() != Control::STATE_NORMAL ||
+                (x >= position.x &&
+                 x <= position.x + size.x &&
+                 y >= position.y &&
+                 y <= position.y + size.y))
+            {
+                // Pass on the event's position relative to the control.
+                control->touchEvent(evt, x - position.x, y - position.y, contactIndex);
+            }
+        }
+
+        if (getState() == Control::STATE_DISABLED)
+            return;
+
+        switch (evt)
+        {
+        case Touch::TOUCH_PRESS:
+            setState(Control::STATE_ACTIVE);
+            break;
+        case Touch::TOUCH_RELEASE:
+            setState(Control::STATE_NORMAL);
+            break;
+        }
+    }
+
+    void Container::keyEvent(Keyboard::KeyEvent evt, int key)
+    {
+        if (getState() == Control::STATE_DISABLED)
+            return;
+
+        std::vector<Control*>::const_iterator it;
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+            control->keyEvent(evt, key);
+        }
+    }
+
+    Layout::Type Container::getLayoutType(const char* layoutString)
+    {
+        std::string layoutName(layoutString);
+        std::transform(layoutName.begin(), layoutName.end(), layoutName.begin(), (int(*)(int))toupper);
+        if (layoutName == "LAYOUT_ABSOLUTE")
+        {
+            return Layout::LAYOUT_ABSOLUTE;
+        }
+        else if (layoutName == "LAYOUT_VERTICAL")
+        {
+            return Layout::LAYOUT_VERTICAL;
+        }
+        else if (layoutName == "LAYOUT_FLOW")
+        {
+            return Layout::LAYOUT_FLOW;
+        }
+        else
+        {
+            // Default.
+            return Layout::LAYOUT_ABSOLUTE;
+        }
+    }
+}

+ 92 - 0
gameplay/src/Container.h

@@ -0,0 +1,92 @@
+#ifndef CONTAINER_H_
+#define CONTAINER_H_
+
+#include "Control.h"
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class Container : public Control
+{
+public:
+    /**
+     * A Container's layout type must be specified at creation time.
+     */
+    static Container* create(const char* id, Layout::Type type);
+    static Container* create(Theme::Style* style, Properties* properties, Theme* theme);
+    static Container* getContainer(const char* id);
+
+    Layout* getLayout();
+
+    /**
+     * Add a control to this layout.
+     * The control will be assigned the next available index.
+     *
+     * @param control The Control to add.
+     *
+     * @return The index assigned to the added Control.
+     */
+    unsigned int addControl(Control* control);
+
+    /**
+     * Insert a Control at a specific index.
+     *
+     * @param control The Control to add.
+     * @param index The index at which to insert the Control.
+     */
+    void insertControl(Control* control, unsigned int index);
+
+    void removeControl(unsigned int index);
+    void removeControl(const char* id);
+    void removeControl(Control* control);
+
+    /**
+     * Get the Control at a specific index.
+     *
+     * @param index The index at which to retrieve the Control.
+     *
+     * @return The Control at the given index.
+     */
+    Control* getControl(unsigned int index) const;
+
+    /**
+     * Get a Control with a specific ID that belongs to this Layout.
+     *
+     * @param id The ID of the Control to search for.
+     */
+    Control* getControl(const char* id) const;
+
+    std::vector<Control*> getControls() const;
+
+    /**
+     * Updates the position of each Control within this Container
+     * according to the Container's Layout.
+     */
+    virtual void update(const Vector2& position);
+
+    //void draw(Theme* theme, const Vector2& position);
+    virtual void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    virtual void drawText(const Vector2& position);
+
+    virtual void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    virtual void keyEvent(Keyboard::KeyEvent evt, int key);
+
+protected:
+    Container();
+    Container(const Container& copy);
+    virtual ~Container();
+
+    static Layout::Type getLayoutType(const char* layoutString);
+
+    bool isDirty();
+    void addControls(Theme* theme, Properties* properties);
+
+    Layout* _layout;
+    std::vector<Control*> _controls;
+};
+
+}
+
+#endif

+ 183 - 0
gameplay/src/Control.cpp

@@ -0,0 +1,183 @@
+#include "Base.h"
+#include "Control.h"
+
+namespace gameplay
+{
+    Control::Control()
+        : _id(""), _state(Control::STATE_NORMAL), _size(Vector2::zero()), _position(Vector2::zero()), _border(Vector2::zero()), _padding(Vector2::zero()),
+          _autoWidth(true), _autoHeight(true), _dirty(true)
+    {
+    }
+
+    Control::Control(const Control& copy)
+    {
+    }
+
+    Control::~Control()
+    {
+    }
+
+    const char* Control::getID()
+    {
+        return _id.c_str();
+    }
+
+    const Rectangle Control::getBounds(bool includePadding) const
+    {
+        // TODO
+        return Rectangle();
+    }
+
+    void Control::setPosition(float x, float y)
+    {
+        _position.set(x, y);
+    }
+
+    const Vector2& Control::getPosition() const
+    {
+        return _position;
+    }
+
+    void Control::setSize(float width, float height)
+    {
+        _size.set(width, height);
+    }
+
+    const Vector2& Control::getSize() const
+    {
+        return _size;
+    }
+
+    void Control::setAutoSize(bool width, bool height)
+    {
+        _autoWidth = width;
+        _autoHeight = height;
+    }
+
+    void Control::setStyle(Theme::Style* style)
+    {
+        _style = style;
+    }
+
+    Theme::Style* Control::getStyle() const
+    {
+        return _style;
+    }
+
+    void Control::setState(State state)
+    {
+        _state = state;
+    }
+
+    Control::State Control::getState()
+    {
+        return _state;
+    }
+
+    void Control::disable()
+    {
+        _state = STATE_DISABLED;
+    }
+
+    void Control::enable()
+    {
+        _state = STATE_NORMAL;
+    }
+
+    Theme::Style::OverlayType Control::getOverlayType() const
+    {
+        switch (_state)
+        {
+        case Control::STATE_NORMAL:
+            return Theme::Style::OVERLAY_NORMAL;
+        case Control::STATE_FOCUS:
+            return Theme::Style::OVERLAY_FOCUS;
+        case Control::STATE_ACTIVE:
+            return Theme::Style::OVERLAY_ACTIVE;
+        default:
+            return Theme::Style::OVERLAY_NORMAL;
+        }
+    }
+
+    void Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        // Empty stub to be implemented by Button and its descendents.
+    }
+
+    void Control::keyEvent(Keyboard::KeyEvent evt, int key)
+    {
+    }
+
+    void Control::update(const Vector2& position)
+    {
+    }
+
+    void Control::drawBorder(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+
+        // Get the border and background images for this control's current state.
+        Theme::ContainerRegion* containerRegion = _style->getOverlay(getOverlayType())->getContainerRegion();
+        if (containerRegion)
+        {
+            // Get the UVs.
+            Theme::UVs topLeft, top, topRight, left, center, right, bottomLeft, bottom, bottomRight;
+            topLeft = containerRegion->getUVs(Theme::ContainerRegion::TOP_LEFT);
+            top = containerRegion->getUVs(Theme::ContainerRegion::TOP);
+            topRight = containerRegion->getUVs(Theme::ContainerRegion::TOP_RIGHT);
+            left = containerRegion->getUVs(Theme::ContainerRegion::LEFT);
+            center = containerRegion->getUVs(Theme::ContainerRegion::CENTER);
+            right = containerRegion->getUVs(Theme::ContainerRegion::RIGHT);
+            bottomLeft = containerRegion->getUVs(Theme::ContainerRegion::BOTTOM_LEFT);
+            bottom = containerRegion->getUVs(Theme::ContainerRegion::BOTTOM);
+            bottomRight = containerRegion->getUVs(Theme::ContainerRegion::BOTTOM_RIGHT);
+
+            // Calculate screen-space positions.
+            Theme::Border border = containerRegion->getBorder();
+            Theme::Padding padding = _style->getPadding();
+            Vector4 borderColor = containerRegion->getColor();
+
+            float midWidth = _size.x - border.left - border.right;
+            float midHeight = _size.y - border.top - border.bottom;
+            float midX = pos.x + border.left;
+            float midY = pos.y + border.top;
+            float rightX = pos.x + _size.x - border.right;
+            float bottomY = pos.y + _size.y - border.bottom;
+
+            // Draw themed border sprites.
+            if (border.left && border.top)
+                spriteBatch->draw(pos.x, pos.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, borderColor);
+            if (border.top)
+                spriteBatch->draw(pos.x + border.left, pos.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, borderColor);
+            if (border.right && border.top)
+                spriteBatch->draw(rightX, pos.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, borderColor);
+            if (border.left)
+                spriteBatch->draw(pos.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, borderColor);
+            
+            spriteBatch->draw(pos.x + border.left, pos.y + border.top, _size.x - border.left - border.right, _size.y - border.top - border.bottom,
+                center.u1, center.v1, center.u2, center.v2, borderColor);
+
+            if (border.right)
+                spriteBatch->draw(rightX, midY, border.right, midHeight, right.u1, right.v1, right.u2, right.v2, borderColor);
+            if (border.bottom && border.left)
+                spriteBatch->draw(pos.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, borderColor);
+            if (border.bottom)
+                spriteBatch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, borderColor);
+            if (border.bottom && border.right)
+                spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, borderColor);
+        }
+    }
+
+    void Control::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+    }
+
+    void Control::drawText(const Vector2& position)
+    {
+    }
+
+    bool Control::isDirty()
+    {
+        return _dirty;
+    }
+}

+ 134 - 0
gameplay/src/Control.h

@@ -0,0 +1,134 @@
+#ifndef CONTROL_H_
+#define CONTROL_H_
+
+#include "Ref.h"
+#include "Rectangle.h"
+#include "Vector2.h"
+#include "SpriteBatch.h"
+#include "Theme.h"
+#include "Touch.h"
+#include "Keyboard.h"
+
+namespace gameplay
+{
+
+class Control : public Ref
+{
+public:
+
+    enum State
+    {
+        STATE_NORMAL,
+        STATE_FOCUS,
+        STATE_ACTIVE,
+        STATE_DISABLED
+    };
+
+    const char* getID();
+
+    /**
+     * Get the actual bounding box of this Control, local to its Container,
+     * after any calculations performed due to the Container's Layout or settings of auto-size.
+     * Always includes the Control's border.
+     * Can optionally include the Control's padding.
+     * Query getPosition() and getSize() to learn the bounds without border or padding.
+     */
+    const Rectangle getBounds(bool includePadding) const;
+
+    /**
+     * Set the position of this Control relative to its parent Container.
+     *
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     */
+    void setPosition(float x, float y);
+
+    /**
+     * Get the position of this Control relative to its parent Container.
+     *
+     * @return The position vector.
+     */
+    const Vector2& getPosition() const;
+
+    /**
+     * Set the size of this Control, including its border and padding.
+     *
+     * @param width The width.
+     * @param height The height.
+     */
+    void setSize(float width, float height);
+
+    /**
+     * Get the size of this Control, including its border and padding.
+     *
+     * @return The size vector.
+     */
+    const Vector2& getSize() const;
+
+    /**
+     * Set width and/or height to auto-size to size a Control to tightly fit
+     * its text and themed visual elements (CheckBox / RadioButton toggle etc.).
+     *
+     * Similarly set this on the width and/or height of a Container to tightly fit
+     * the Container around all its children.
+     *
+     * @param width Whether to automatically determine this Control's width.
+     * @param height Whether to automatically determine this Control's height.
+     */
+    void setAutoSize(bool width, bool height);
+
+    void setState(State state);
+    State getState();
+
+    void disable();
+    void enable();
+
+    Theme::Style::OverlayType getOverlayType() const;
+
+    /**
+     * Defaults to empty stub.
+     */
+    virtual void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    virtual void keyEvent(Keyboard::KeyEvent evt, int key);
+
+    virtual void update(const Vector2& position);
+
+    /**
+     * Draws the themed border and background of a control.
+     */
+    void drawBorder(SpriteBatch* spriteBatch, const Vector2& position);
+    virtual void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    virtual void drawText(const Vector2& position);
+
+    /**
+     * Returns whether this Control has been modified since the last time
+     * isDirty() was called, and resets its dirty flag.
+     */
+    virtual bool isDirty();
+
+    void setStyle(Theme::Style* Style);
+    Theme::Style* getStyle() const;
+
+    void themeChanged();
+
+protected:
+    Control();
+    Control(const Control& copy);
+    virtual ~Control();
+
+    std::string _id;
+    State _state;           // Determines overlay used during draw().
+    Vector2 _size;
+    Vector2 _position;
+    Vector2 _border;
+    Vector2 _padding;
+    bool _autoWidth;
+    bool _autoHeight;
+    bool _dirty;
+    Theme::Style* _style;
+};
+
+}
+
+#endif

+ 2 - 1
gameplay/src/DebugNew.cpp

@@ -109,7 +109,8 @@ void* debugAlloc(std::size_t size, const char* file, int line)
 
 void debugFree(void* p)
 {
-    assert(p);
+    if (p == 0)
+        return;
 
     // Backup passed in pointer to access memory allocation record
     void* mem = ((unsigned char*)p) - sizeof(MemoryAllocationRecord);

+ 24 - 0
gameplay/src/FlowLayout.h

@@ -0,0 +1,24 @@
+#ifndef FLOWLAYOUT_H_
+#define FLOWLAYOUT_H_
+
+#include "Layout.h"
+
+namespace gameplay
+{
+
+class FlowLayout : public Layout
+{
+public:
+    static FlowLayout* create();
+
+    void setRightToLeft(bool rightToLeft);
+
+private:
+    FlowLayout();
+    FlowLayout(const FlowLayout& copy);
+    virtual ~FlowLayout();
+};
+
+}
+
+#endif

+ 536 - 28
gameplay/src/Font.cpp

@@ -176,11 +176,14 @@ 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;
-    char* cursor = NULL;
+    const char* cursor = NULL;
+
     if (rightToLeft)
     {
-        cursor = const_cast<char*>(text);
+        cursor = text;
     }
 
     int xPos = x, yPos = y;
@@ -204,7 +207,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 switch (delimiter)
                 {
                 case ' ':
-                    xPos += size>>1;
+                    xPos += (float)size*0.5f;
                     break;
                 case '\r':
                 case '\n':
@@ -212,7 +215,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                     xPos = x;
                     break;
                 case '\t':
-                    xPos += (size>>1)*4;
+                    xPos += ((float)size*0.5f)*4;
                     break;
                 case 0:
                     done = true;
@@ -253,7 +256,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
             switch (c)
             {
             case ' ':
-                xPos += size>>1;
+                xPos += (float)size*0.5f;
                 break;
             case '\r':
             case '\n':
@@ -261,7 +264,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 xPos = x;
                 break;
             case '\t':
-                xPos += (size>>1)*4;
+                xPos += ((float)size*0.5f)*4;
                 break;
             default:
                 int index = c - 32; // HACK for ASCII
@@ -269,7 +272,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 {
                     Glyph& g = _glyphs[index];
                     _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
-                    xPos += g.width * scale + (size>>3);
+                    xPos += g.width * scale + ((float)size*0.125f);
                     break;
                 }
             }
@@ -288,8 +291,10 @@ 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;
-    char* token = const_cast<char*>(text);
+    const char* token = text;
     const int length = strlen(text);
     int yPos = area.y;
 
@@ -305,7 +310,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
         hAlign = ALIGN_LEFT;
     }
 
-    token = const_cast<char*>(text);
+    token = text;
 
     // For alignments other than top-left, need to calculate the y position to begin drawing from
     // and the starting x position of each line.  For right-to-left text, need to determine
@@ -337,7 +342,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                     switch (delimiter)
                     {
                         case ' ':
-                            delimWidth += size>>1;
+                            delimWidth += (float)size*0.5f;
                             lineLength++;
                             break;
                         case '\r':
@@ -354,7 +359,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                             delimWidth = 0;
                             break;
                         case '\t':
-                            delimWidth += (size>>1)*4;
+                            delimWidth += ((float)size*0.5f)*4;
                             lineLength++;
                             break;
                         case 0:
@@ -476,12 +481,12 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
         xPos = *xPositionsIt++;
     }
 
-    token = const_cast<char*>(text);
+    token = text;
     
     int iteration = 1;
     unsigned int lineLength;
     unsigned int currentLineLength = 0;
-    char* lineStart;
+    const char* lineStart;
     std::vector<unsigned int>::const_iterator lineLengthsIt;
     if (rightToLeft)
     {
@@ -574,7 +579,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                         _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
                     }
                 }
-                xPos += g.width*scale + (size>>3);
+                xPos += g.width*scale + ((float)size*0.125f);
             }
         }
 
@@ -651,7 +656,7 @@ void Font::measureText(const char* text, unsigned int size, unsigned int* width,
 {
     float scale = (float)size / _size;
     const int length = strlen(text);
-    char* token = const_cast<char*>(text);
+    const char* token = text;
 
     *width = 0;
     *height = 0;
@@ -691,7 +696,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
         hAlign = ALIGN_LEFT;
     }
 
-    char* token = const_cast<char*>(text);
+    const char* token = text;
     std::vector<bool> emptyLines;
     std::vector<Vector2> lines;
 
@@ -715,7 +720,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
                 switch (delimiter)
                 {
                     case ' ':
-                        delimWidth += size>>1;
+                        delimWidth += (float)size*0.5f;
                         break;
                     case '\r':
                     case '\n':
@@ -751,7 +756,7 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
                         delimWidth = 0;
                         break;
                     case '\t':
-                        delimWidth += (size>>1)*4;
+                        delimWidth += ((float)size*0.5f)*4;
                         break;
                     case 0:
                         reachedEOF = true;
@@ -996,6 +1001,413 @@ void Font::measureText(const char* text, const Rectangle& viewport, unsigned int
     }
 }
 
+unsigned int Font::getIndexAtLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                      Justify justify, bool wrap, bool rightToLeft)
+{
+    return getIndexOrLocation(text, area, size, inLocation, outLocation, -1, justify, wrap, rightToLeft);
+}
+
+void Font::getLocationAtIndex(const char* text, const Rectangle& clip, unsigned int size, Vector2* outLocation, const unsigned int destIndex,
+                              Justify justify, bool wrap, bool rightToLeft)
+{
+    getIndexOrLocation(text, clip, size, *outLocation, outLocation, (const int)destIndex, justify, wrap, rightToLeft);
+}
+
+unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                      const int destIndex, Justify justify, bool wrap, bool rightToLeft)
+{
+    unsigned int charIndex = 0;
+
+    // Essentially need to measure text until we reach inLocation.
+    float scale = (float)size / _size;
+    const char* token = text;
+    const int length = strlen(text);
+    int yPos = area.y;
+
+    Justify vAlign = static_cast<Justify>(justify & 0xF0);
+    if (vAlign == 0)
+    {
+        vAlign = ALIGN_TOP;
+    }
+
+    Justify hAlign = static_cast<Justify>(justify & 0x0F);
+    if (hAlign == 0)
+    {
+        hAlign = ALIGN_LEFT;
+    }
+
+    token = text;
+
+    // For alignments other than top-left, need to calculate the y position to begin drawing from
+    // and the starting x position of each line.  For right-to-left text, need to determine
+    // the number of characters on each line.
+    std::vector<int> xPositions;
+    std::vector<unsigned int> lineLengths;
+    if (vAlign != ALIGN_TOP || hAlign != ALIGN_LEFT || rightToLeft)
+    {
+        int lineWidth = 0;
+        int delimWidth = 0;
+
+        if (wrap)
+        {
+            // Go a word at a time.
+            bool reachedEOF = false;
+            unsigned int lineLength = 0;
+            while (token[0] != 0)
+            {
+                unsigned int tokenWidth = 0;
+
+                // Handle delimiters until next token.
+                char delimiter = token[0];
+                while (delimiter == ' ' ||
+                       delimiter == '\t' ||
+                       delimiter == '\r' ||
+                       delimiter == '\n' ||
+                       delimiter == 0)
+                {
+                    switch (delimiter)
+                    {
+                        case ' ':
+                            delimWidth += (float)size*0.5f;
+                            lineLength++;
+                            break;
+                        case '\r':
+                        case '\n':
+                            yPos += size;
+
+                            if (lineWidth > 0)
+                            {
+                                addLineInfo(area, lineWidth, lineLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+                            }
+
+                            lineWidth = 0;
+                            lineLength = 0;
+                            delimWidth = 0;
+                            break;
+                        case '\t':
+                            delimWidth += ((float)size*0.5f)*4;
+                            lineLength++;
+                            break;
+                        case 0:
+                            reachedEOF = true;
+                            break;
+                    }
+
+                    if (reachedEOF)
+                    {
+                        break;
+                    }
+
+                    token++;
+                    delimiter = token[0];
+                }
+
+                if (reachedEOF || token == NULL)
+                {
+                    break;
+                }
+
+                unsigned int tokenLength = strcspn(token, " \r\n\t");
+                tokenWidth += getTokenWidth(token, tokenLength, size, scale);
+
+                // Wrap if necessary.
+                if (lineWidth + tokenWidth + delimWidth > area.width)
+                {
+                    yPos += size;
+
+                    // Push position of current line.
+                    if (lineLength)
+                    {
+                        addLineInfo(area, lineWidth, lineLength-1, hAlign, &xPositions, &lineLengths, rightToLeft);
+                    }
+                    else
+                    {
+                        addLineInfo(area, lineWidth, tokenLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+                    }
+
+                    // Move token to the next line.
+                    lineWidth = 0;
+                    lineLength = 0;
+                    delimWidth = 0;
+                }
+                else
+                {
+                    lineWidth += delimWidth;
+                    delimWidth = 0;
+                }
+
+                lineWidth += tokenWidth;
+                lineLength += tokenLength;
+                token += tokenLength;
+            }
+
+            // Final calculation of vertical position.
+            int textHeight = yPos - area.y;
+            int vWhiteSpace = area.height - textHeight;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                yPos = area.y + vWhiteSpace / 2;
+            }
+            else if (vAlign == ALIGN_BOTTOM)
+            {
+                yPos = area.y + vWhiteSpace;
+            }
+
+            // Calculation of final horizontal position.
+            addLineInfo(area, lineWidth, lineLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+        }
+        else
+        {
+            // Go a line at a time.
+            while (token[0] != 0)
+            {
+                char delimiter = token[0];
+                while (delimiter == '\n')
+                {
+                    yPos += size;
+                    ++token;
+                    delimiter = token[0];
+                }
+
+                unsigned int tokenLength = strcspn(token, "\n");
+                if (tokenLength == 0)
+                {
+                    tokenLength = strlen(token);
+                }
+
+                int lineWidth = getTokenWidth(token, tokenLength, size, scale);
+                addLineInfo(area, lineWidth, tokenLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+
+                token += tokenLength;
+            }
+
+            int textHeight = yPos - area.y;
+            int vWhiteSpace = area.height - textHeight;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                yPos = area.y + vWhiteSpace / 2;
+            }
+            else if (vAlign == ALIGN_BOTTOM)
+            {
+                yPos = area.y + vWhiteSpace;
+            }
+        }
+
+        if (vAlign == ALIGN_TOP)
+        {
+            yPos = area.y;
+        }
+    }
+
+    // Now we have the info we need in order to render.
+    int xPos = area.x;
+    std::vector<int>::const_iterator xPositionsIt = xPositions.begin();
+    if (xPositionsIt != xPositions.end())
+    {
+        xPos = *xPositionsIt++;
+    }
+
+    token = text;
+    
+    int iteration = 1;
+    unsigned int lineLength;
+    unsigned int currentLineLength = 0;
+    const char* lineStart;
+    std::vector<unsigned int>::const_iterator lineLengthsIt;
+    if (rightToLeft)
+    {
+        lineStart = token;
+        lineLengthsIt = lineLengths.begin();
+        lineLength = *lineLengthsIt++;
+        token += lineLength - 1;
+        iteration = -1;
+    }
+
+    while (token[0] != 0)
+    {
+        // Handle delimiters until next token.
+        unsigned int delimLength = 0;
+        int result;
+        if (destIndex == -1)
+        {
+            result = handleDelimiters(&token, size, iteration, area.x, &xPos, &yPos, &delimLength, &xPositionsIt, xPositions.end(), &charIndex, &inLocation);
+        }
+        else
+        {
+            result = handleDelimiters(&token, size, iteration, area.x, &xPos, &yPos, &delimLength, &xPositionsIt, xPositions.end(), &charIndex, NULL, charIndex, destIndex);
+        }
+
+        currentLineLength += delimLength;
+        if (result == 0)
+        {
+            break;
+        }
+        else if (result == 2)
+        {
+            outLocation->x = xPos;
+            outLocation->y = yPos;
+            return charIndex;
+        }
+
+        if (destIndex == (int)charIndex ||
+            (destIndex == -1 &&
+             inLocation.x >= xPos && inLocation.x < floor(xPos + ((float)size*0.125f)) &&
+             inLocation.y >= yPos && inLocation.y < yPos + size))
+        {
+            outLocation->x = xPos;
+            outLocation->y = yPos;
+            return charIndex;
+        }
+
+        bool truncated = false;
+        unsigned int tokenLength;
+        unsigned int tokenWidth;
+        unsigned int startIndex;
+        if (rightToLeft)
+        {
+            tokenLength = getReversedTokenLength(token, text);
+            currentLineLength += tokenLength;
+            charIndex += tokenLength;
+            token -= (tokenLength - 1);
+            tokenWidth = getTokenWidth(token, tokenLength, size, scale);
+            iteration = -1;
+            startIndex = tokenLength - 1;
+        }
+        else
+        {
+            tokenLength = strcspn(token, " \r\n\t");
+            tokenWidth = getTokenWidth(token, tokenLength, size, scale);
+            iteration = 1;
+            startIndex = 0;
+        }
+
+        // Wrap if necessary.
+        if (wrap &&
+            xPos + tokenWidth > area.x + area.width ||
+            (rightToLeft && currentLineLength > lineLength))
+        {
+            yPos += size;
+            currentLineLength = tokenLength;
+
+            if (xPositionsIt != xPositions.end())
+            {
+                xPos = *xPositionsIt++;
+            }
+            else
+            {
+                xPos = area.x;
+            }
+        }
+
+        if (yPos > area.y + area.height)
+        {
+            // Truncate below area's vertical limit.
+            break;
+        }
+
+        for (int i = startIndex; i < (int)tokenLength && i >= 0; i += iteration)
+        {
+            char c = token[i];
+            int glyphIndex = c - 32; // HACK for ASCII
+        
+            if (glyphIndex >= 0 && glyphIndex < (int)_glyphCount)
+            {
+                Glyph& g = _glyphs[glyphIndex];
+
+                if (xPos + (int)(g.width*scale) > area.x + area.width)
+                {
+                    // Truncate this line and go on to the next one.
+                    truncated = true;
+                    break;
+                }
+
+                // Check against inLocation.
+                if (destIndex == (int)charIndex ||
+                    (destIndex == -1 &&
+                    inLocation.x >= xPos && inLocation.x < floor(xPos + g.width*scale + ((float)size*0.125f)) &&
+                    inLocation.y >= yPos && inLocation.y < yPos + size))
+                {
+                    outLocation->x = xPos;
+                    outLocation->y = yPos;
+                    return charIndex;
+                }
+
+                xPos += g.width*scale + ((float)size*0.125f);
+                charIndex++;
+            }
+        }
+
+        if (!truncated)
+        {
+            if (rightToLeft)
+            {
+                if (token == lineStart)
+                {
+                    token += lineLength;
+                    
+                    // Now handle delimiters going forwards.
+                    if (!handleDelimiters(&token, size, 1, area.x, &xPos, &yPos, &currentLineLength, &xPositionsIt, xPositions.end()))
+                    {
+                        break;
+                    }
+                    charIndex += currentLineLength;
+
+                    if (lineLengthsIt != lineLengths.end())
+                    {
+                        lineLength = *lineLengthsIt++;
+                    }
+                    lineStart = token;
+                    token += lineLength-1;
+                    charIndex += tokenLength;
+                }
+                else
+                {
+                    token--;
+                }
+            }
+            else
+            {
+                token += tokenLength;
+            }
+        }
+        else
+        {
+            if (rightToLeft)
+            {
+                token = lineStart + lineLength;
+                
+                if (!handleDelimiters(&token, size, 1, area.x, &xPos, &yPos, &currentLineLength, &xPositionsIt, xPositions.end()))
+                {
+                    break;
+                }
+
+                if (lineLengthsIt != lineLengths.end())
+                {
+                    lineLength = *lineLengthsIt++;
+                }
+                lineStart = token;
+                token += lineLength-1;
+            }
+            else
+            {
+                // Skip the rest of this line.
+                unsigned int tokenLength = strcspn(token, "\n");
+
+                if (tokenLength > 0)
+                {                
+                    // Get first token of next line.
+                    token += tokenLength;
+                    charIndex += tokenLength;
+                }
+            }
+        }
+    }
+
+    outLocation->x = xPos;
+    outLocation->y = yPos;
+    return charIndex;
+}
+
 unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigned int size, float scale)
 {
     // Calculate width of word or line.
@@ -1006,17 +1418,17 @@ unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigne
         switch (c)
         {
         case ' ':
-            tokenWidth += size>>1;
+            tokenWidth += (float)size*0.5f;
             break;
         case '\t':
-            tokenWidth += (size>>1)*4;
+            tokenWidth += ((float)size*0.5f)*4;
             break;
         default:
             int glyphIndex = c - 32;
             if (glyphIndex >= 0 && glyphIndex < (int)_glyphCount)
             {
                 Glyph& g = _glyphs[glyphIndex];
-                tokenWidth += g.width * scale + (size>>3);
+                tokenWidth += g.width * scale + ((float)size*0.125f);
             }
             break;
         }
@@ -1027,7 +1439,7 @@ unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigne
 
 unsigned int Font::getReversedTokenLength(const char* token, const char* bufStart)
 {
-    char* cursor = const_cast<char*>(token);
+    const char* cursor = token;
     char c = cursor[0];
     unsigned int length = 0;
 
@@ -1046,8 +1458,9 @@ unsigned int Font::getReversedTokenLength(const char* token, const char* bufStar
     return length;
 }
 
-bool Font::handleDelimiters(char** token, const unsigned int size, const int iteration, const int areaX, int* xPos, int* yPos, unsigned int* lineLength,
-                      std::vector<int>::const_iterator* xPositionsIt, std::vector<int>::const_iterator xPositionsEnd)
+int Font::handleDelimiters(const char** token, const unsigned int size, const int iteration, const int areaX, int* xPos, int* yPos, unsigned int* lineLength,
+                          std::vector<int>::const_iterator* xPositionsIt, std::vector<int>::const_iterator xPositionsEnd, unsigned int* charIndex,
+                          const Vector2* stopAtPosition, const int currentIndex, const int destIndex)
 {
     char delimiter = *token[0];
     bool nextLine = true;
@@ -1057,11 +1470,24 @@ bool Font::handleDelimiters(char** token, const unsigned int size, const int ite
             delimiter == '\n' ||
             delimiter == 0)
     {
+        if ((stopAtPosition &&
+            stopAtPosition->x >= *xPos && stopAtPosition->x < floor(*xPos + ((float)size*0.5f)) &&
+            stopAtPosition->y >= *yPos && stopAtPosition->y < *yPos + size) ||
+            (currentIndex >= 0 && destIndex >= 0 && currentIndex + (int)*lineLength == destIndex))
+        {
+            // Success + stopAtPosition was reached.
+            return 2;
+        }
+
         switch (delimiter)
         {
             case ' ':
-                *xPos += size>>1;
+                *xPos += (float)size*0.5f;
                 (*lineLength)++;
+                if (charIndex)
+                {
+                    (*charIndex)++;
+                }
                 break;
             case '\r':
             case '\n':
@@ -1081,21 +1507,31 @@ bool Font::handleDelimiters(char** token, const unsigned int size, const int ite
                     }
                     nextLine = false;
                     *lineLength = 0;
+                    if (charIndex)
+                    {
+                        (*charIndex)++;
+                    }
                 }
                 break;
             case '\t':
-                *xPos += (size>>1)*4;
+                *xPos += ((float)size*0.5f)*4;
                 (*lineLength)++;
+                if (charIndex)
+                {
+                    (*charIndex)++;
+                }
                 break;
             case 0:
-                return false;
+                // EOF reached.
+                return 0;
         }
 
         *token += iteration;
         delimiter = *token[0];
     }
 
-    return true;
+    // Success.
+    return 1;
 }
 
 void Font::addLineInfo(const Rectangle& area, int lineWidth, int lineLength, Justify hAlign,
@@ -1117,4 +1553,76 @@ void Font::addLineInfo(const Rectangle& area, int lineWidth, int lineLength, Jus
     }
 }
 
+SpriteBatch* Font::getSpriteBatch()
+{
+    return _batch;
+}
+
+Font::Justify Font::getJustifyFromString(const char* justify)
+{
+    if (strcmp(justify, "ALIGN_LEFT") == 0)
+    {
+        return Font::ALIGN_LEFT;
+    }
+    else if (strcmp(justify, "ALIGN_HCENTER") == 0)
+    {
+        return Font::ALIGN_HCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_RIGHT") == 0)
+    {
+        return Font::ALIGN_RIGHT;
+    }
+    else if (strcmp(justify, "ALIGN_TOP") == 0)
+    {
+        return Font::ALIGN_TOP;
+    }
+    else if (strcmp(justify, "ALIGN_VCENTER") == 0)
+    {
+        return Font::ALIGN_VCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_BOTTOM") == 0)
+    {
+        return Font::ALIGN_BOTTOM;
+    }
+    else if (strcmp(justify, "ALIGN_TOP_LEFT") == 0)
+    {
+        return Font::ALIGN_TOP_LEFT;
+    }
+    else if (strcmp(justify, "ALIGN_VCENTER_LEFT") == 0)
+    {
+        return Font::ALIGN_VCENTER_LEFT;
+    }
+    else if (strcmp(justify, "ALIGN_BOTTOM_LEFT") == 0)
+    {
+        return Font::ALIGN_BOTTOM_LEFT;
+    }
+    else if (strcmp(justify, "ALIGN_TOP_HCENTER") == 0)
+    {
+        return Font::ALIGN_TOP_HCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_VCENTER_HCENTER") == 0)
+    {
+        return Font::ALIGN_VCENTER_HCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_BOTTOM_HCENTER") == 0)
+    {
+        return Font::ALIGN_BOTTOM_HCENTER;
+    }
+    else if (strcmp(justify, "ALIGN_TOP_RIGHT") == 0)
+    {
+        return Font::ALIGN_TOP_RIGHT;
+    }
+    else if (strcmp(justify, "ALIGN_VCENTER_RIGHT") == 0)
+    {
+        return Font::ALIGN_VCENTER_RIGHT;
+    }
+    else if (strcmp(justify, "ALIGN_BOTTOM_RIGHT") == 0)
+    {
+        return Font::ALIGN_BOTTOM_RIGHT;
+    }
+
+    // Default.
+    return Font::ALIGN_TOP_LEFT;
+}
+
 }

+ 26 - 8
gameplay/src/Font.h

@@ -12,6 +12,7 @@ namespace gameplay
 class Font : public Ref
 {
     friend class Package;
+    friend class TextBox;
 
 public:
 
@@ -131,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.
@@ -142,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.
@@ -175,6 +176,18 @@ public:
     void measureText(const char* text, const Rectangle& clip, unsigned int size, Rectangle* out,
                      Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool ignoreClip = false);
 
+    // Get an index into a string corresponding to the character nearest the given location within the clip region.
+    unsigned int getIndexAtLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                    Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+
+    // Get the location of the character at the given index.
+    void getLocationAtIndex(const char* text, const Rectangle& clip, unsigned int size, Vector2* outLocation, const unsigned int destIndex,
+                            Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+
+    SpriteBatch* getSpriteBatch();
+
+    static Justify getJustifyFromString(const char* justify);
+
 
 private:
 
@@ -193,13 +206,18 @@ private:
      */
     ~Font();
 
+    // Used by both getIndexAtLocation and getLocationAtIndex.
+    unsigned int getIndexOrLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                    const int destIndex = -1, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+
     // Utilities
     unsigned int getTokenWidth(const char* token, unsigned int length, unsigned int size, float scale);
     unsigned int getReversedTokenLength(const char* token, const char* bufStart);
 
-    // Returns false if EOF was reached, true otherwise.
-    bool handleDelimiters(char** token, const unsigned int size, const int iteration, const int areaX, int* xPos, int* yPos, unsigned int* lineLength,
-                          std::vector<int>::const_iterator* xPositionsIt, std::vector<int>::const_iterator xPositionsEnd);
+    // Returns 0 if EOF was reached, 1 if delimiters were handles correctly, and 2 if the stopAtPosition was reached while handling delimiters.
+    int handleDelimiters(const char** token, const unsigned int size, const int iteration, const int areaX, int* xPos, int* yPos, unsigned int* lineLength,
+                          std::vector<int>::const_iterator* xPositionsIt, std::vector<int>::const_iterator xPositionsEnd, unsigned int* charIndex = NULL,
+                          const Vector2* stopAtPosition = NULL, const int currentIndex = -1, const int destIndex = -1);
     void addLineInfo(const Rectangle& area, int lineWidth, int lineLength, Justify hAlign,
                      std::vector<int>* xPositions, std::vector<unsigned int>* lineLengths, bool rightToLeft);
 

+ 383 - 0
gameplay/src/Form.cpp

@@ -0,0 +1,383 @@
+#include "Base.h"
+#include "Form.h"
+#include "AbsoluteLayout.h"
+#include "VerticalLayout.h"
+#include "Game.h"
+#include "Theme.h"
+#include "Label.h"
+#include "Button.h"
+#include "CheckBox.h"
+#include "Scene.h"
+
+namespace gameplay
+{
+    static std::vector<Form*> __forms;
+
+    Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL), _viewport(NULL)
+    {
+    }
+
+    Form::Form(const Form& copy)
+    {
+    }
+
+    Form::~Form()
+    {
+        SAFE_RELEASE(_quad);
+        SAFE_RELEASE(_node);
+        SAFE_RELEASE(_frameBuffer);
+        SAFE_RELEASE(_theme);
+        SAFE_DELETE(_viewport);
+
+        // Remove this Form from the global list.
+        std::vector<Form*>::iterator it = std::find(__forms.begin(), __forms.end(), this);
+        if (it != __forms.end())
+        {
+            __forms.erase(it);
+        }
+    }
+
+    Form* Form::create(const char* path)
+    {
+        // Load Form from .form file.
+        assert(path);
+
+        Properties* properties = Properties::create(path);
+        assert(properties);
+        if (properties == NULL)
+        {
+            return NULL;
+        }
+
+        // Check if the Properties is valid and has a valid namespace.
+        Properties* formProperties = properties->getNextNamespace();
+        assert(formProperties);
+        if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
+        {
+            SAFE_DELETE(properties);
+            return NULL;
+        }
+
+        // Create new form with given ID, theme and layout.
+        const char* id = formProperties->getId();
+        const char* themeFile = formProperties->getString("theme");
+        const char* layoutString = formProperties->getString("layout");
+        Form* form = Form::create(id, themeFile, getLayoutType(layoutString));
+
+        // Set form's position and dimensions.
+        formProperties->getVector2("position", &form->_position);
+        formProperties->getVector2("size", &form->_size);
+
+        // Set style from theme.
+        const char* styleName = formProperties->getString("style");
+        form->setStyle(form->getTheme()->getStyle(styleName));
+
+        // Add all the controls to the form.
+        form->addControls(form->getTheme(), formProperties);
+
+        SAFE_DELETE(properties);
+
+        return form;
+    }
+
+    Form* Form::create(const char* id, const char* themeFile, Layout::Type type)
+    {
+        Layout* layout;
+        switch(type)
+        {
+        case Layout::LAYOUT_ABSOLUTE:
+            layout = AbsoluteLayout::create();
+            break;
+        case Layout::LAYOUT_FLOW:
+            break;
+        case Layout::LAYOUT_VERTICAL:
+            layout = VerticalLayout::create();
+            break;
+        }
+
+        assert(themeFile);
+        Theme* theme = Theme::create(themeFile);
+        assert(theme);
+
+        Form* form = new Form();
+        form->_id = id;
+        form->_layout = layout;
+        form->_frameBuffer = FrameBuffer::create(id);
+        form->_theme = theme;
+
+        __forms.push_back(form);
+
+        return form;
+    }
+
+    Form* Form::getForm(const char* id)
+    {
+        std::vector<Form*>::const_iterator it;
+        for (it = __forms.begin(); it < __forms.end(); it++)
+        {
+            Form* f = *it;
+            if (strcmp(id, f->getID()) == 0)
+            {
+                return f;
+            }
+        }
+        
+        return NULL;
+    }
+
+    void Form::setTheme(Theme* theme)
+    {
+        _theme = theme;
+    }
+
+    Theme* Form::getTheme() const
+    {
+        return _theme;
+    }
+
+    void Form::setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4)
+    {
+        Mesh* mesh = Mesh::createQuad(p1, p2, p3, p4);
+        initQuad(mesh);
+        SAFE_RELEASE(mesh);
+    }
+
+    void Form::setQuad(float x, float y, float width, float height)
+    {
+        Mesh* mesh = Mesh::createQuad(x, y, width, height);
+        initQuad(mesh);
+        SAFE_RELEASE(mesh);
+    }
+
+    void Form::setNode(Node* node)
+    {
+        // Set this Form up to be 3D by initializing a quad, projection matrix and viewport.
+        setQuad(0.0f, 0.0f, _size.x, _size.y);
+        Matrix::createOrthographicOffCenter(0, _size.x, _size.y, 0, 0, 1, &_projectionMatrix);
+        _theme->setProjectionMatrix(_projectionMatrix);
+        _viewport = new Viewport(0, 0, _size.x, _size.y);
+
+        // Connect the new node.
+        _node = node;
+        if (_node)
+        {
+            _node->setModel(_quad);
+        }
+    }
+
+    void Form::update()
+    {
+        Container::update(Vector2::zero());
+    }
+
+    void Form::draw()
+    {
+        // If this Form has a Node then it's a 3D Form.  The contents will be rendered
+        // into a FrameBuffer which will be used to texture a quad.  The quad will be
+        // given the same dimensions as the form and must be transformed appropriately
+        // by the user (or they can call setQuad() themselves).
+        // "Billboard mode" should also be allowed for 3D Forms.
+
+        // On the other hand, if this Form has not been set on a Node it will render
+        // directly to the display.
+
+
+        // Check whether this Form has changed since the last call to draw()
+        // and if so, render into the FrameBuffer.
+        if (_node)
+        {
+            if (isDirty())
+            {
+                _frameBuffer->bind();
+                _viewport->bind();
+
+                Game* game = Game::getInstance();
+
+                // Clear form background color.
+                //game->clear(Game::CLEAR_COLOR, _style->getOverlay(getOverlayType())->getBorderColor(), 1.0f, 0);
+
+                draw(_theme->getSpriteBatch(), _position);
+                FrameBuffer::bindDefault();
+
+                // Rebind the game viewport.
+                GL_ASSERT( glViewport(0, 0, game->getWidth(), game->getHeight()) );
+            }
+
+            _quad->draw();
+        }
+        else
+        {
+            draw(_theme->getSpriteBatch(), _position);
+        }
+    }
+
+    void Form::draw(SpriteBatch* spriteBatch, const Vector2& position)
+    {
+        std::vector<Control*>::const_iterator it;
+
+        // Batch all themed border and background sprites.
+        spriteBatch->begin();
+
+        // Draw the form's border and background.
+        // We don't pass the form's position to itself or it will be applied twice!
+        drawBorder(spriteBatch, Vector2::zero());
+
+        // Draw each control's border and background.
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+
+            //if (!_node || (*it)->isDirty())
+            {
+                control->drawBorder(spriteBatch, position);
+
+                // Add all themed foreground sprites (checkboxes etc.) to the same batch.
+                control->drawSprites(spriteBatch, position);
+            }
+        }
+        spriteBatch->end();
+
+        // Draw all control foregrounds / text.
+        for (it = _controls.begin(); it < _controls.end(); it++)
+        {
+            Control* control = *it;
+
+            //if (!_node || (*it)->isDirty())
+            {
+                control->drawText(position);
+            }
+        }
+
+        _dirty = false;
+    }
+
+    void Form::initQuad(Mesh* mesh)
+    {
+        // Release current model.
+        SAFE_RELEASE(_quad);
+
+        // Create the model
+        _quad = Model::create(mesh);
+
+        // Create the material
+        Material* material = _quad->setMaterial("res/shaders/textured.vsh", "res/shaders/textured.fsh");
+
+        // Set the common render state block for the material
+        RenderState::StateBlock* stateBlock = _theme->getSpriteBatch()->getStateBlock();
+        stateBlock->setDepthWrite(true);
+        //material->setStateBlock(_theme->getSpriteBatch()->getStateBlock());
+        material->setStateBlock(stateBlock);
+
+        // Bind the WorldViewProjection matrix
+        material->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
+
+        // Use the FrameBuffer to texture the quad.
+        if (_frameBuffer->getRenderTarget() == NULL)
+        {
+            RenderTarget* rt = RenderTarget::create(_id.c_str(), _size.x, _size.y);
+            _frameBuffer->setRenderTarget(rt);
+            SAFE_RELEASE(rt);
+        }
+
+        Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
+        sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
+        material->getParameter("u_texture")->setValue(sampler);
+
+        material->getParameter("u_textureRepeat")->setValue(Vector2::one());
+        material->getParameter("u_textureTransform")->setValue(Vector2::zero());
+
+        SAFE_RELEASE(sampler);
+    }
+
+    void Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    {
+        // Check for a collision with each Form in __forms.
+        // Pass the event on.
+        std::vector<Form*>::const_iterator it;
+        for (it = __forms.begin(); it < __forms.end(); it++)
+        {
+            Form* form = *it;
+
+            if (form->getState() == Control::STATE_DISABLED)
+                continue;
+
+            Node* node = form->_node;
+            if (node)
+            {
+                Scene* scene = node->getScene();
+                Camera* camera = scene->getActiveCamera();
+
+                if (camera)
+                {
+                    // Get info about the form's position.
+                    Matrix m = node->getMatrix();
+                    const Vector2& size = form->getSize();
+                    Vector3 min(0, 0, 0);
+                    m.transformPoint(&min);
+
+                    // Unproject point into world space.
+                    Ray ray;
+                    camera->pickRay(NULL, x, y, &ray);
+
+                    // Find the quad's plane.
+                    // We know its normal is the quad's forward vector.
+                    Vector3 normal = node->getForwardVectorWorld();
+
+                    // To get the plane's distance from the origin,
+                    // we'll find the distance from the plane defined
+                    // by the quad's forward vector and one of its points
+                    // to the plane defined by the same vector and the origin.
+                    const float& a = normal.x; const float& b = normal.y; const float& c = normal.z;
+                    const float d = -(a*min.x) - (b*min.y) - (c*min.z);
+                    const float distance = abs(d) /  sqrt(a*a + b*b + c*c);
+                    Plane plane(normal, -distance);
+
+                    // Check for collision with plane.
+                    float collisionDistance = ray.intersects(plane);
+                    if (collisionDistance != Ray::INTERSECTS_NONE)
+                    {
+                        // Multiply the ray's direction vector by collision distance
+                        // and add that to the ray's origin.
+                        Vector3 point = ray.getOrigin() + collisionDistance*ray.getDirection();
+
+                        // Project this point into the plane.
+                        m.invert();
+                        m.transformPoint(&point);
+
+                        // Pass the touch event on.
+                        form->touchEvent(evt, point.x, size.y - point.y, contactIndex);
+                    }
+                }
+            }
+            else
+            {
+                // Simply compare with the form's bounds.
+                const Vector2& size = form->getSize();
+                const Vector2& position = form->getPosition();
+
+                if (form->getState() == Control::STATE_ACTIVE ||
+                    (x >= position.x &&
+                     x <= position.x + size.x &&
+                     y >= position.y &&
+                     y <= position.y + size.y))
+                {
+                    // Pass on the event's position relative to the form.
+                    form->touchEvent(evt, x - position.x, y - position.y, contactIndex);
+                }
+            }
+        }
+    }
+
+    void Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+    {
+        std::vector<Form*>::const_iterator it;
+        for (it = __forms.begin(); it < __forms.end(); it++)
+        {
+            Form* form = *it;
+            if (form->getState() == Control::STATE_DISABLED)
+                continue;
+
+            form->keyEvent(evt, key);
+        }
+    }
+}

+ 70 - 0
gameplay/src/Form.h

@@ -0,0 +1,70 @@
+#ifndef FORM_H_
+#define FORM_H_
+
+#include "Ref.h"
+#include "Container.h"
+#include "Mesh.h"
+#include "Node.h"
+#include "FrameBuffer.h"
+#include "Touch.h"
+#include "Keyboard.h"
+
+namespace gameplay
+{
+
+class Theme;
+
+class Form : public Container
+{
+    friend class Platform;
+
+public:
+    /**
+     * Create from .form file.
+     */
+    static Form* create(const char* path);
+    static Form* create(const char* id, const char* textureFile, Layout::Type type);
+    static Form* getForm(const char* id);
+
+    void setTheme(Theme* theme);
+    Theme* getTheme() const;
+
+    /**
+     * Create a 3D quad to texture with this Form.
+     */
+    void setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4);
+
+    /**
+     * Create a 2D quad to texture with this Form.
+     */
+    void setQuad(float x, float y, float width, float height);
+
+    void setNode(Node* node);
+
+    void update();
+    void draw();
+
+private:
+    Form();
+    Form(const Form& copy);
+    virtual ~Form();
+
+    void initQuad(Mesh* mesh);
+    void draw(SpriteBatch* spriteBatch);
+    void draw(SpriteBatch* spriteBatch, const Vector2& position);
+
+    static void touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    static void keyEventInternal(Keyboard::KeyEvent evt, int key);
+
+    Theme* _theme;              // The Theme applied to this Form.
+    Model* _quad;               // Quad for rendering this Form in world-space.
+    Node* _node;                // Node for transforming this Form in world-space.
+    FrameBuffer* _frameBuffer;  // FBO the Form is rendered into for texturing the quad.
+    Matrix _projectionMatrix;   // Orthographic projection matrix to be set on SpriteBatch objects when rendering into the FBO.
+    Viewport* _viewport;        // Viewport for setting before rendering into the FBO.
+};
+
+}
+
+#endif

+ 50 - 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);
     }
 }
 
@@ -263,4 +267,18 @@ bool Game::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
     return false;
 }
 
+void Game::updateOnce()
+{
+    // Update Time.
+    static long lastFrameTime = Game::getGameTime();
+    long frameTime = Game::getGameTime();
+    long elapsedTime = (frameTime - lastFrameTime);
+    lastFrameTime = frameTime;
+
+    // Update the internal controllers.
+    _animationController->update(elapsedTime);
+    _physicsController->update(elapsedTime);
+    _audioController->update(elapsedTime);
+}
+
 }

+ 9 - 0
gameplay/src/Game.h

@@ -299,6 +299,15 @@ protected:
     template <class T>
     void renderOnce(T* instance, void (T::*method)(void*), void* cookie);
 
+    /**
+     * 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.
+     */
+    void updateOnce();
+
 private:
 
     /**

+ 1 - 1
gameplay/src/Game.inl

@@ -40,7 +40,7 @@ inline PhysicsController* Game::getPhysicsController() const
 }
 
 template <class T>
-void  Game::renderOnce(T* instance, void (T::*method)(void*), void* cookie)
+void Game::renderOnce(T* instance, void (T::*method)(void*), void* cookie)
 {
     (instance->*method)(cookie);
     Platform::swapBuffers();

+ 109 - 0
gameplay/src/Label.cpp

@@ -0,0 +1,109 @@
+#include "Base.h"
+#include "Label.h"
+
+namespace gameplay
+{
+    static std::vector<Label*> __labels;
+
+    Label::Label() : _text("")
+    {
+    }
+
+    Label::Label(const Label& copy)
+    {
+    }
+
+    Label::~Label()
+    {
+    }
+
+    Label* Label::create(Theme::Style* style, Properties* properties)
+    {
+        Label* label = new Label();
+        label->_style = style;
+        properties->getVector2("position", &label->_position);
+        properties->getVector2("size", &label->_size);
+
+        const char* id = properties->getId();
+        if (id)
+        {
+            label->_id = id;
+        }
+
+        const char* text = properties->getString("text");
+        if (text)
+        {
+            label->_text = text;
+        }
+
+        __labels.push_back(label);
+
+        return label;
+    }
+
+    Label* Label::getLabel(const char* id)
+    {
+        std::vector<Label*>::const_iterator it;
+        for (it = __labels.begin(); it < __labels.end(); it++)
+        {
+            Label* l = *it;
+            if (strcmp(id, l->getID()) == 0)
+            {
+                return l;
+            }
+        }
+
+        return NULL;
+    }
+    
+    void Label::setText(const char* text)
+    {
+        if (text)
+        {
+            _text = text;
+        }
+    }
+
+    const char* Label::getText()
+    {
+        return _text.c_str();
+    }
+
+    void Label::update(const Vector2& position)
+    {
+        Vector2 pos(position.x + _position.x, position.y + _position.y);
+        Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+        
+        Theme::Border border;
+        Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+        if (containerRegion)
+        {
+            border = overlay->getContainerRegion()->getBorder();
+        }
+        Theme::Padding padding = _style->getPadding();
+
+        // Set up the text viewport.
+        Font* font = overlay->getFont();
+        _viewport.set(pos.x + border.left + padding.left,
+                      pos.y + border.top + padding.top,
+                      _size.x - border.left - padding.left - border.right - padding.right,
+                      _size.y - border.top - padding.top - border.bottom - padding.bottom - overlay->getFontSize());
+    }
+
+    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();
+
+        // Draw the text.
+        font->begin();
+        font->drawText(_text.c_str(), _viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+        font->end();
+
+        _dirty = false;
+    }
+}

+ 35 - 0
gameplay/src/Label.h

@@ -0,0 +1,35 @@
+#ifndef LABEL_H_
+#define LABEL_H_
+
+#include "Control.h"
+#include "Theme.h"
+
+namespace gameplay
+{
+
+class Label : public Control
+{
+public:
+    static Label* create(Theme::Style* style, Properties* properties);
+    static Label* getLabel(const char* id);
+    
+    void setText(const char* text);
+    const char* getText();
+
+    void update(const Vector2& position);
+
+    void drawText(const Vector2& position);
+
+protected:
+
+    Label();
+    Label(const Label& copy);
+    virtual ~Label();
+
+    std::string _text;
+    Rectangle _viewport;
+};
+
+}
+
+#endif

+ 33 - 0
gameplay/src/Layout.h

@@ -0,0 +1,33 @@
+#ifndef LAYOUT_H_
+#define LAYOUT_H_
+
+#include "Ref.h"
+
+namespace gameplay
+{
+
+class Container;
+
+class Layout : public Ref
+{
+public:
+    enum Type
+    {
+        LAYOUT_FLOW,
+        LAYOUT_VERTICAL,
+        LAYOUT_ABSOLUTE
+    };
+
+    virtual Type getType() = 0;
+
+    virtual void update(const Container* container) = 0;
+
+protected:
+    //Layout();
+    //Layout(const Layout& copy);
+    //virtual ~Layout();
+};
+
+}
+
+#endif

+ 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

+ 1 - 1
gameplay/src/Matrix.cpp

@@ -460,7 +460,7 @@ bool Matrix::decompose(Vector3* scale, Quaternion* rotation, Vector3* translatio
     // Now calculate the rotation from the resulting matrix (axes).
     float trace = xaxis.x + yaxis.y + zaxis.z + 1.0f;
 
-    if (trace > MATH_TOLERANCE)
+    if (trace > MATH_EPSILON)
     {
         float s = 0.5f / sqrt(trace);
         rotation->w = 0.25f / s;

+ 9 - 9
gameplay/src/Matrix.h

@@ -831,7 +831,7 @@ public:
      * @param m The matrix to add.
      * @return The matrix sum.
      */
-    inline Matrix operator+(const Matrix& m) const;
+    inline const Matrix operator+(const Matrix& m) const;
     
     /**
      * Adds the given matrix to this matrix.
@@ -842,14 +842,14 @@ public:
     inline Matrix& operator+=(const Matrix& m);
 
     /**
-     * Calculates the sum of this matrix with the given matrix.
+     * Calculates the difference of this matrix with the given matrix.
      * 
      * Note: this does not modify this matrix.
      * 
-     * @param m The matrix to add.
-     * @return The matrix sum.
+     * @param m The matrix to subtract.
+     * @return The matrix difference.
      */
-    inline Matrix operator-(const Matrix& m) const;
+    inline const Matrix operator-(const Matrix& m) const;
 
     /**
      * Subtracts the given matrix from this matrix.
@@ -866,7 +866,7 @@ public:
      * 
      * @return The negation of this matrix.
      */
-    inline Matrix operator-() const;
+    inline const Matrix operator-() const;
 
     /**
      * Calculates the matrix product of this matrix with the given matrix.
@@ -876,7 +876,7 @@ public:
      * @param m The matrix to multiply by.
      * @return The matrix product.
      */
-    inline Matrix operator*(const Matrix& m) const;
+    inline const Matrix operator*(const Matrix& m) const;
 
     /**
      * Right-multiplies this matrix by the given matrix.
@@ -907,7 +907,7 @@ inline Vector3& operator*=(Vector3& v, const Matrix& m);
  * @param v The vector to transform.
  * @return The resulting transformed vector.
  */
-inline Vector3 operator*(const Matrix& m, const Vector3& v);
+inline const Vector3 operator*(const Matrix& m, const Vector3& v);
 
 /**
  * Transforms the given vector by the given matrix.
@@ -929,7 +929,7 @@ inline Vector4& operator*=(Vector4& v, const Matrix& m);
  * @param v The vector to transform.
  * @return The resulting transformed vector.
  */
-inline Vector4 operator*(const Matrix& m, const Vector4& v);
+inline const Vector4 operator*(const Matrix& m, const Vector4& v);
 
 }
 

+ 6 - 6
gameplay/src/Matrix.inl

@@ -3,7 +3,7 @@
 namespace gameplay
 {
 
-inline Matrix Matrix::operator+(const Matrix& m) const
+inline const Matrix Matrix::operator+(const Matrix& m) const
 {
     Matrix result(*this);
     result.add(m);
@@ -16,7 +16,7 @@ inline Matrix& Matrix::operator+=(const Matrix& m)
     return *this;
 }
 
-inline Matrix Matrix::operator-(const Matrix& m) const
+inline const Matrix Matrix::operator-(const Matrix& m) const
 {
     Matrix result(*this);
     result.subtract(m);
@@ -29,14 +29,14 @@ inline Matrix& Matrix::operator-=(const Matrix& m)
     return *this;
 }
 
-inline Matrix Matrix::operator-() const
+inline const Matrix Matrix::operator-() const
 {
     Matrix m(*this);
     m.negate();
     return m;
 }
 
-inline Matrix Matrix::operator*(const Matrix& m) const
+inline const Matrix Matrix::operator*(const Matrix& m) const
 {
     Matrix result(*this);
     result.multiply(m);
@@ -55,7 +55,7 @@ inline Vector3& operator*=(Vector3& v, const Matrix& m)
     return v;
 }
 
-inline Vector3 operator*(const Matrix& m, const Vector3& v)
+inline const Vector3 operator*(const Matrix& m, const Vector3& v)
 {
     Vector3 x;
     m.transformVector(v, &x);
@@ -68,7 +68,7 @@ inline Vector4& operator*=(Vector4& v, const Matrix& m)
     return v;
 }
 
-inline Vector4 operator*(const Matrix& m, const Vector4& v)
+inline const Vector4 operator*(const Matrix& m, const Vector4& v)
 {
     Vector4 x;
     m.transformVector(v, &x);

+ 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.

+ 68 - 12
gameplay/src/Node.cpp

@@ -12,7 +12,7 @@ namespace gameplay
 
 Node::Node(const char* id)
     : _scene(NULL), _firstChild(NULL), _nextSibling(NULL), _prevSibling(NULL), _parent(NULL), _childCount(NULL),
-    _camera(NULL), _light(NULL), _model(NULL), _audioSource(NULL), _particleEmitter(NULL), _physicsRigidBody(NULL), 
+    _camera(NULL), _light(NULL), _model(NULL), _form(NULL), _audioSource(NULL), _particleEmitter(NULL), _physicsRigidBody(NULL), 
     _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true)
 {
     if (id)
@@ -30,6 +30,13 @@ Node::~Node()
 {
     removeAllChildren();
 
+    if (_model)
+        _model->setNode(NULL);
+    if (_audioSource)
+        _audioSource->setNode(NULL);
+    if (_particleEmitter)
+        _particleEmitter->setNode(NULL);
+
     SAFE_RELEASE(_camera);
     SAFE_RELEASE(_light);
     SAFE_RELEASE(_model);
@@ -310,7 +317,7 @@ const Matrix& Node::getWorldMatrix() const
 const Matrix& Node::getWorldViewMatrix() const
 {
     static Matrix worldView;
-    
+
     Matrix::multiply(getViewMatrix(), getWorldMatrix(), &worldView);
 
     return worldView;
@@ -319,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();
@@ -437,6 +447,8 @@ Vector3 Node::getForwardVectorView() const
     Vector3 vector;
     getWorldMatrix().getForwardVector(&vector);
     getViewMatrix().transformVector(&vector);
+    //getForwardVector(&vector);
+    //getWorldViewMatrix().transformVector(&vector);
     return vector;
 }
 
@@ -459,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.
@@ -567,6 +598,31 @@ Model* Node::getModel() const
     return _model;
 }
 
+void Node::setForm(Form* form)
+{
+    if (_form != form)
+    {
+        if (_form)
+        {
+            _form->setNode(NULL);
+            SAFE_RELEASE(_form);
+        }
+
+        _form = form;
+
+        if (_form)
+        {
+            _form->addRef();
+            _form->setNode(this);
+        }
+    }
+}
+
+Form* Node::getForm() const
+{
+    return _form;
+}
+
 const BoundingSphere& Node::getBoundingSphere() const
 {
     if (_dirtyBits & NODE_DIRTY_BOUNDS)
@@ -693,12 +749,12 @@ void Node::setParticleEmitter(ParticleEmitter* emitter)
     }
 }
 
-PhysicsRigidBody* Node::getPhysicsRigidBody() const
+PhysicsRigidBody* Node::getRigidBody() const
 {
     return _physicsRigidBody;
 }
 
-void Node::setPhysicsRigidBody(PhysicsRigidBody::Type type, float mass, float friction,
+void Node::setRigidBody(PhysicsRigidBody::Type type, float mass, float friction,
         float restitution, float linearDamping, float angularDamping)
 {
     SAFE_DELETE(_physicsRigidBody);
@@ -707,14 +763,14 @@ void Node::setPhysicsRigidBody(PhysicsRigidBody::Type type, float mass, float fr
         _physicsRigidBody = new PhysicsRigidBody(this, type, mass, friction, restitution, linearDamping, angularDamping);
 }
 
-void Node::setPhysicsRigidBody(const char* filePath)
+void Node::setRigidBody(const char* filePath)
 {
     SAFE_DELETE(_physicsRigidBody);
 
     _physicsRigidBody = PhysicsRigidBody::create(this, filePath);
 }
 
-void Node::setPhysicsRigidBody(Properties* properties)
+void Node::setRigidBody(Properties* properties)
 {
     SAFE_DELETE(_physicsRigidBody);
 

+ 27 - 5
gameplay/src/Node.h

@@ -5,6 +5,7 @@
 #include "Camera.h"
 #include "Light.h"
 #include "Model.h"
+#include "Form.h"
 #include "AudioSource.h"
 #include "ParticleEmitter.h"
 #include "PhysicsRigidBody.h"
@@ -15,6 +16,7 @@ namespace gameplay
 
 class Package;
 class Scene;
+class Form;
 
 /**
  * Defines a basic hierachial structure of transformation spaces.
@@ -171,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.
      */
@@ -259,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.
      *
@@ -310,6 +328,9 @@ public:
      */
     void setModel(Model* model);
 
+    Form* getForm() const;
+    void setForm(Form* form);
+
     /**
      * Returns the pointer to this node's audio source.
      *
@@ -349,7 +370,7 @@ public:
      *
      * @return The pointer to this node's physics rigid body or NULL.
      */
-    PhysicsRigidBody* getPhysicsRigidBody() const;
+    PhysicsRigidBody* getRigidBody() const;
 
     /**
      * Sets (or disables) the physics rigid body for this node.
@@ -366,7 +387,7 @@ public:
      * @param linearDamping The percentage of linear velocity lost per second (between 0.0 and 1.0).
      * @param angularDamping The percentage of angular velocity lost per second (between 0.0 and 1.0).
      */
-    void setPhysicsRigidBody(PhysicsRigidBody::Type type, float mass = 0.0f, float friction = 0.5f,
+    void setRigidBody(PhysicsRigidBody::Type type, float mass = 0.0f, float friction = 0.5f,
         float restitution = 0.0f, float linearDamping = 0.0f, float angularDamping = 0.0f);
 
     /**
@@ -374,14 +395,14 @@ public:
      * 
      * @param filePath The path to the file that contains the rigid body definition.
      */
-    void setPhysicsRigidBody(const char* filePath);
+    void setRigidBody(const char* filePath);
 
     /**
      * Sets the physics rigid body for this node from the given properties object.
      * 
      * @param properties The properties object defining the rigid body (must have namespace equal to 'rigidbody').
      */
-    void setPhysicsRigidBody(Properties* properties);
+    void setRigidBody(Properties* properties);
 
     /**
      * Returns the bounding sphere for the Node, in world space.
@@ -448,6 +469,7 @@ protected:
     Camera* _camera;
     Light* _light;
     Model* _model;
+    Form* _form;
     AudioSource* _audioSource;
     ParticleEmitter* _particleEmitter;
     PhysicsRigidBody* _physicsRigidBody;

+ 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(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(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;

+ 35 - 2
gameplay/src/ParticleEmitter.cpp

@@ -235,6 +235,27 @@ bool ParticleEmitter::isStarted() const
     return _started;
 }
 
+bool ParticleEmitter::isActive() const
+{
+    if (_started)
+        return true;
+
+    if (!_node)
+        return false;
+
+    bool active = false;
+    for (unsigned int i = 0; i < _particleCount; i++)
+    {
+        if (_particles[i]._energy > 0)
+        {
+            active = true;
+            break;
+        }
+    }
+
+    return active;
+}
+
 void ParticleEmitter::emit(unsigned int particleCount)
 {
     // Limit particleCount so as not to go over _particleCountMax.
@@ -742,6 +763,11 @@ ParticleEmitter::TextureBlending ParticleEmitter::getTextureBlendingFromString(c
 
 void ParticleEmitter::update(long elapsedTime)
 {
+    if (!isActive())
+    {
+        return;
+    }
+
     // Calculate the time passed since last update.
     float elapsedSecs = (float)elapsedTime / 1000.0f;
 
@@ -848,6 +874,11 @@ void ParticleEmitter::update(long elapsedTime)
 
 void ParticleEmitter::draw()
 {
+    if (!isActive())
+    {
+        return;
+    }
+
     if (_particleCount > 0)
     {
         // Set our node's view projection matrix to this emitter's effect.
@@ -867,7 +898,8 @@ void ParticleEmitter::draw()
             {
                 Particle* p = &_particles[i];
                 _spriteBatch->draw(p->_position.x, p->_position.y, p->_position.z, p->_size, p->_size,
-                                   _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3], p->_color);
+                                   _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3], p->_color,
+                                   true);
             }
         }
         else
@@ -879,7 +911,8 @@ void ParticleEmitter::draw()
             {
                 Particle* p = &_particles[i];
                 _spriteBatch->draw(p->_position, p->_size, p->_size,
-                                   _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3], p->_color, pivot, p->_angle);
+                                   _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3], p->_color, pivot, p->_angle,
+                                   true);
             }
         }
 

+ 7 - 0
gameplay/src/ParticleEmitter.h

@@ -212,6 +212,13 @@ public:
      */
     bool isStarted() const;
 
+    /**
+     * Gets whether this ParticleEmitter is currently active (i.e. if any of its particles are alive).
+     * 
+     * @return Whether this ParticleEmitter is currently active.
+     */
+    bool isActive() const;
+
     /**
      * Generates an arbitrary number of particles all at once.  Each newly emitted
      * particle has its properties assigned within the ranges defined by its ParticleEmitter.

+ 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

+ 3 - 3
gameplay/src/PhysicsConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsConstraint.h"
-
 #include "Game.h"
 #include "Node.h"
 #include "PhysicsMotionState.h"
@@ -118,7 +118,7 @@ btTransform PhysicsConstraint::getTransformOffset(const Node* node, const Vector
     
     t = offsetByCenterOfMass(node, t);
 
-    return btTransform(btQuaternion(r.x, r.y, r.z, r.w), btVector3(t.x, t.y, t.z));
+    return btTransform(BQ(r), BV(t));
 }
 
 Vector3 PhysicsConstraint::getWorldCenterOfMass(const Model* model)
@@ -155,7 +155,7 @@ Vector3 PhysicsConstraint::getWorldCenterOfMass(const Model* model)
 
 Vector3 PhysicsConstraint::offsetByCenterOfMass(const Node* node, const Vector3& v)
 {
-    btVector3 centerOfMassOffset = ((PhysicsMotionState*)node->getPhysicsRigidBody()->_body->getMotionState())->_centerOfMassOffset.getOrigin();
+    btVector3 centerOfMassOffset = ((PhysicsMotionState*)node->getRigidBody()->_body->getMotionState())->_centerOfMassOffset.getOrigin();
     return Vector3(v.x + centerOfMassOffset.x(), v.y + centerOfMassOffset.y(), v.z + centerOfMassOffset.z());
 }
 

+ 257 - 84
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
@@ -11,10 +11,15 @@
 namespace gameplay
 {
 
+const int PhysicsController::DIRTY         = 0x01;
+const int PhysicsController::COLLISION     = 0x02;
+const int PhysicsController::REGISTERED    = 0x04;
+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.
@@ -22,6 +27,7 @@ PhysicsController::PhysicsController()
 
 PhysicsController::~PhysicsController()
 {
+    SAFE_DELETE(_ghostPairCallback);
     SAFE_DELETE(_debugDrawer);
     SAFE_DELETE(_listeners);
 }
@@ -34,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);
@@ -104,7 +120,7 @@ PhysicsSpringConstraint* PhysicsController::createSpringConstraint(PhysicsRigidB
     return constraint;
 }
 
-const Vector3& PhysicsController::getGravity(const Vector3& gravity) const
+const Vector3& PhysicsController::getGravity() const
 {
     return _gravity;
 }
@@ -114,7 +130,7 @@ void PhysicsController::setGravity(const Vector3& gravity)
     _gravity = gravity;
 
     if (_world)
-        _world->setGravity(btVector3(_gravity.x, _gravity.y, _gravity.z));
+        _world->setGravity(BV(_gravity));
 }
 
 void PhysicsController::drawDebug(const Matrix& viewProjection)
@@ -124,6 +140,100 @@ void PhysicsController::drawDebug(const Matrix& viewProjection)
     _debugDrawer->end();
 }
 
+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;
+}
+
+btScalar PhysicsController::addSingleResult(btManifoldPoint& cp, const btCollisionObject* a, int partIdA, int indexA, 
+    const btCollisionObject* b, int partIdB, int indexB)
+{
+    // Get pointers to the PhysicsRigidBody objects.
+    PhysicsRigidBody* rbA = Game::getInstance()->getPhysicsController()->getRigidBody(a);
+    PhysicsRigidBody* rbB = Game::getInstance()->getPhysicsController()->getRigidBody(b);
+    
+    // If the given rigid body pair has collided in the past, then
+    // we notify the listeners only if the pair was not colliding
+    // during the previous frame. Otherwise, it's a new pair, so add a
+    // new entry to the cache with the appropriate listeners and notify them.
+    PhysicsRigidBody::CollisionPair pair(rbA, rbB);
+    if (_collisionStatus.count(pair) > 0)
+    {
+        const CollisionInfo& collisionInfo = _collisionStatus[pair];
+        if ((collisionInfo._status & COLLISION) == 0)
+        {
+            std::vector<PhysicsRigidBody::Listener*>::const_iterator iter = collisionInfo._listeners.begin();
+            for (; iter != collisionInfo._listeners.end(); iter++)
+            {
+                if ((collisionInfo._status & REMOVE) == 0)
+                {
+                    (*iter)->collisionEvent(PhysicsRigidBody::Listener::COLLIDING, pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()),
+                        Vector3(cp.getPositionWorldOnB().x(), cp.getPositionWorldOnB().y(), cp.getPositionWorldOnB().z()));
+                }
+            }
+        }
+    }
+    else
+    {
+        CollisionInfo& collisionInfo = _collisionStatus[pair];
+
+        // Initialized the status for the new entry.
+        collisionInfo._status = 0;
+
+        // Add the appropriate listeners.
+        PhysicsRigidBody::CollisionPair p1(pair.rigidBodyA, NULL);
+        if (_collisionStatus.count(p1) > 0)
+        {
+            const CollisionInfo& ci = _collisionStatus[p1];
+            std::vector<PhysicsRigidBody::Listener*>::const_iterator iter = ci._listeners.begin();
+            for (; iter != ci._listeners.end(); iter++)
+            {
+                collisionInfo._listeners.push_back(*iter);
+            }
+        }
+        PhysicsRigidBody::CollisionPair p2(pair.rigidBodyB, NULL);
+        if (_collisionStatus.count(p2) > 0)
+        {
+            const CollisionInfo& ci = _collisionStatus[p2];
+            std::vector<PhysicsRigidBody::Listener*>::const_iterator iter = ci._listeners.begin();
+            for (; iter != ci._listeners.end(); iter++)
+            {
+                collisionInfo._listeners.push_back(*iter);
+            }
+        }
+
+        std::vector<PhysicsRigidBody::Listener*>::iterator iter = collisionInfo._listeners.begin();
+        for (; iter != collisionInfo._listeners.end(); iter++)
+        {
+            if ((collisionInfo._status & REMOVE) == 0)
+            {
+                (*iter)->collisionEvent(PhysicsRigidBody::Listener::COLLIDING, pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()),
+                    Vector3(cp.getPositionWorldOnB().x(), cp.getPositionWorldOnB().y(), cp.getPositionWorldOnB().z()));
+            }
+        }
+    }
+
+    // Update the collision status cache (we remove the dirty bit
+    // set in the controller's update so that this particular collision pair's
+    // status is not reset to 'no collision' when the controller's update completes).
+    _collisionStatus[pair]._status &= ~DIRTY;
+    _collisionStatus[pair]._status |= COLLISION;
+    return 0.0f;
+}
+
 void PhysicsController::initialize()
 {
     _collisionConfiguration = new btDefaultCollisionConfiguration();
@@ -133,7 +243,11 @@ void PhysicsController::initialize()
 
     // Create the world.
     _world = new btDiscreteDynamicsWorld(_dispatcher, _overlappingPairCache, _solver, _collisionConfiguration);
-    _world->setGravity(btVector3(_gravity.x, _gravity.y, _gravity.z));
+    _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();
@@ -144,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);
@@ -216,68 +331,75 @@ void PhysicsController::update(long elapsedTime)
     // During collision processing, if a collision occurs, the status is 
     // set to COLLISION and the DIRTY bit is cleared. Then, after collision processing 
     // is finished, if a given status is still dirty, the COLLISION bit is cleared.
+    //
+    // If an entry was marked for removal in the last frame, remove it now.
 
-    // Dirty all the collision listeners' collision status caches.
-    for (unsigned int i = 0; i < _bodies.size(); i++)
+    // Dirty the collision status cache entries.
+    std::map<PhysicsRigidBody::CollisionPair, CollisionInfo>::iterator iter = _collisionStatus.begin();
+    for (; iter != _collisionStatus.end();)
     {
-        if (_bodies[i]->_listeners)
+        if ((iter->second._status & REMOVE) != 0)
         {
-            for (unsigned int k = 0; k < _bodies[i]->_listeners->size(); k++)
-            {
-                std::map<PhysicsRigidBody::CollisionPair, int>::iterator iter = (*_bodies[i]->_listeners)[k]->_collisionStatus.begin();
-                for (; iter != (*_bodies[i]->_listeners)[k]->_collisionStatus.end(); iter++)
-                {
-                    iter->second |= PhysicsRigidBody::Listener::DIRTY;
-                }
-            }
+            std::map<PhysicsRigidBody::CollisionPair, CollisionInfo>::iterator eraseIter = iter;
+            iter++;
+            _collisionStatus.erase(eraseIter);
+        }
+        else
+        {
+            iter->second._status |= DIRTY;
+            iter++;
         }
     }
 
-    // Go through the physics rigid bodies and update the collision listeners.
-    for (unsigned int i = 0; i < _bodies.size(); i++)
+    // Go through the collision status cache and perform all registered collision tests.
+    iter = _collisionStatus.begin();
+    for (; iter != _collisionStatus.end(); iter++)
     {
-        if (_bodies[i]->_listeners)
+        // If this collision pair was one that was registered for listening, then perform the collision test.
+        // (In the case where we register for all collisions with a rigid body, there will be a lot
+        // of collision pairs in the status cache that we did not explicitly register for.)
+        if ((iter->second._status & REGISTERED) != 0 && (iter->second._status & REMOVE) == 0)
         {
-            for (unsigned int k = 0; k < _bodies[i]->_listeners->size(); k++)
-            {
-                std::map<PhysicsRigidBody::CollisionPair, int>::iterator iter = (*_bodies[i]->_listeners)[k]->_collisionStatus.begin();
-                for (; iter != (*_bodies[i]->_listeners)[k]->_collisionStatus.end(); iter++)
-                {
-                    // If this collision pair was one that was registered for listening, then perform the collision test.
-                    // (In the case where we register for all collisions with a rigid body, there will be a lot
-                    // of collision pairs in the status cache that we did not explicitly register for.)
-                    if ((iter->second & PhysicsRigidBody::Listener::REGISTERED) != 0)
-                    {
-                        if (iter->first._rbB)
-                            Game::getInstance()->getPhysicsController()->_world->contactPairTest(iter->first._rbA->_body, iter->first._rbB->_body, *(*_bodies[i]->_listeners)[k]);
-                        else
-                            Game::getInstance()->getPhysicsController()->_world->contactTest(iter->first._rbA->_body, *(*_bodies[i]->_listeners)[k]);
-                    }
-                }   
-            }
+            if (iter->first.rigidBodyB)
+                _world->contactPairTest(iter->first.rigidBodyA->_body, iter->first.rigidBodyB->_body, *this);
+            else
+                _world->contactTest(iter->first.rigidBodyA->_body, *this);
         }
     }
 
-    // Go through all the collision listeners and update their collision status caches.
-    for (unsigned int i = 0; i < _bodies.size(); i++)
+    // Update all the collision status cache entries.
+    iter = _collisionStatus.begin();
+    for (; iter != _collisionStatus.end(); iter++)
     {
-        if (_bodies[i]->_listeners)
+        if ((iter->second._status & DIRTY) != 0)
         {
-            for (unsigned int k = 0; k < _bodies[i]->_listeners->size(); k++)
+            if ((iter->second._status & COLLISION) != 0 && iter->first.rigidBodyB)
             {
-                std::map<PhysicsRigidBody::CollisionPair, int>::iterator iter = (*_bodies[i]->_listeners)[k]->_collisionStatus.begin();
-                for (; iter != (*_bodies[i]->_listeners)[k]->_collisionStatus.end(); iter++)
+                unsigned int size = iter->second._listeners.size();
+                for (unsigned int i = 0; i < size; i++)
                 {
-                    if ((iter->second & PhysicsRigidBody::Listener::DIRTY) != 0)
-                    {
-                        iter->second &= ~PhysicsRigidBody::Listener::COLLISION;
-                    }
+                    iter->second._listeners[i]->collisionEvent(PhysicsRigidBody::Listener::NOT_COLLIDING, iter->first);
                 }
             }
+
+            iter->second._status &= ~COLLISION;
         }
     }
 }
-    
+
+void PhysicsController::addCollisionListener(PhysicsRigidBody::Listener* listener, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
+{
+    PhysicsRigidBody::CollisionPair pair(rbA, rbB);
+
+    // Make sure the status of the entry is initialized properly.
+    if (_collisionStatus.count(pair) == 0)
+        _collisionStatus[pair]._status = 0;
+
+    // Add the listener and ensure the status includes that this collision pair is registered.
+    _collisionStatus[pair]._listeners.push_back(listener);
+    if ((_collisionStatus[pair]._status & PhysicsController::REGISTERED) == 0)
+        _collisionStatus[pair]._status |= PhysicsController::REGISTERED;
+}
 
 void PhysicsController::addRigidBody(PhysicsRigidBody* body)
 {
@@ -311,9 +433,27 @@ void PhysicsController::removeRigidBody(PhysicsRigidBody* rigidBody)
             else
                 _shapes[i]->release();
 
-            return;
+            break;
+        }
+    }
+
+    // Remove the rigid body from the controller's list.
+    for (unsigned int i = 0; i < _bodies.size(); i++)
+    {
+        if (_bodies[i] == rigidBody)
+        {
+            _bodies.erase(_bodies.begin() + i);
+            break;
         }
     }
+
+    // Find all references to the rigid body in the collision status cache and mark them for removal.
+    std::map<PhysicsRigidBody::CollisionPair, CollisionInfo>::iterator iter = _collisionStatus.begin();
+    for (; iter != _collisionStatus.end(); iter++)
+    {
+        if (iter->first.rigidBodyA == rigidBody || iter->first.rigidBodyB == rigidBody)
+            iter->second._status |= REMOVE;
+    }
 }
 
 PhysicsRigidBody* PhysicsController::getRigidBody(const btCollisionObject* collisionObject)
@@ -328,9 +468,9 @@ PhysicsRigidBody* PhysicsController::getRigidBody(const btCollisionObject* colli
     return NULL;
 }
 
-btCollisionShape* PhysicsController::createBox(const Vector3& min, const Vector3& max, const btVector3& scale)
+btCollisionShape* PhysicsController::createBox(const Vector3& min, const Vector3& max, const Vector3& scale)
 {
-    btVector3 halfExtents(scale.x() * 0.5 * abs(max.x - min.x), scale.y() * 0.5 * abs(max.y - min.y), scale.z() * 0.5 * abs(max.z - min.z));
+    btVector3 halfExtents(scale.x * 0.5 * abs(max.x - min.x), scale.y * 0.5 * abs(max.y - min.y), scale.z * 0.5 * abs(max.z - min.z));
 
     // Return the box shape from the cache if it already exists.
     for (unsigned int i = 0; i < _shapes.size(); i++)
@@ -376,15 +516,15 @@ btCollisionShape* PhysicsController::createCapsule(float radius, float height)
     return capsule;
 }
 
-btCollisionShape* PhysicsController::createSphere(float radius, const btVector3& scale)
+btCollisionShape* PhysicsController::createSphere(float radius, const Vector3& scale)
 {
     // Since sphere shapes depend only on the radius, the best we can do is take
     // the largest dimension and apply that as the uniform scale to the rigid body.
-    float uniformScale = scale.x();
-    if (uniformScale < scale.y())
-        uniformScale = scale.y();
-    if (uniformScale < scale.z())
-        uniformScale = scale.z();
+    float uniformScale = scale.x;
+    if (uniformScale < scale.y)
+        uniformScale = scale.y;
+    if (uniformScale < scale.z)
+        uniformScale = scale.z;
     
     // Return the sphere shape from the cache if it already exists.
     for (unsigned int i = 0; i < _shapes.size(); i++)
@@ -403,23 +543,52 @@ btCollisionShape* PhysicsController::createSphere(float radius, const btVector3&
     // 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)
+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(body->_node->getScaleX(), body->_node->getScaleY(), body->_node->getScaleZ(), &m);
-    unsigned int vertexCount = data->mesh->getVertexCount();
+    Matrix::createScale(scale, &m);
+    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)]),
@@ -427,19 +596,20 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
         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;
@@ -455,19 +625,18 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
                 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;
+            indexedMesh.m_triangleIndexStride = indexStride*3;
             indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
             indexedMesh.m_vertexStride = sizeof(float)*3;
             indexedMesh.m_vertexType = PHY_FLOAT;
@@ -479,8 +648,8 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
     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;
         }
@@ -489,8 +658,8 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
         // 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;
@@ -504,6 +673,9 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
     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;
 }
 
@@ -580,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[] =
     {
@@ -605,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)

+ 82 - 6
gameplay/src/PhysicsController.h

@@ -8,6 +8,7 @@
 #include "PhysicsSocketConstraint.h"
 #include "PhysicsSpringConstraint.h"
 #include "PhysicsRigidBody.h"
+#include "PhysicsCharacter.h"
 
 namespace gameplay
 {
@@ -15,11 +16,12 @@ namespace gameplay
 /**
  * Defines a class for controlling game physics.
  */
-class PhysicsController
+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.
      * 
@@ -173,7 +211,7 @@ public:
      * 
      * @return The gravity vector.
      */
-    const Vector3& getGravity(const Vector3& gravity) const;
+    const Vector3& getGravity() const;
 
     /**
      * Sets the gravity vector for the simulated physics world.
@@ -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.
      * 
@@ -189,8 +227,41 @@ public:
      */
     void drawDebug(const Matrix& viewProjection);
 
+    /**
+     * Gets the first rigid body that the given ray intersects.
+     * 
+     * @param ray The ray to test intersection with.
+     * @param distance How far along the given ray to test for intersections.
+     * @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, Vector3* hitPoint = NULL, float* hitFraction = NULL);
+
+protected:
+
+    /**
+     * Internal function used for Bullet integration (do not use or override).
+     */
+    btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* a, int partIdA, int indexA, const btCollisionObject* b, int partIdB, int indexB);    
+
 private:
 
+    // Internal constants for the collision status cache.
+    static const int DIRTY;
+    static const int COLLISION;
+    static const int REGISTERED;
+    static const int REMOVE;
+
+    // Represents the collision listeners and status for a given collision pair (used by the collision status cache).
+    struct CollisionInfo
+    {
+        std::vector<PhysicsRigidBody::Listener*> _listeners;
+        int _status;
+    };
+
+    // Wraps Bullet collision shapes (used for implementing shape caching).
     struct PhysicsCollisionShape : public Ref
     {
         PhysicsCollisionShape(btCollisionShape* shape) : _shape(shape) {}
@@ -234,6 +305,9 @@ private:
      */
     void update(long elapsedTime);
 
+    // Adds the given collision listener for the two given rigid bodies.
+    void addCollisionListener(PhysicsRigidBody::Listener* listener, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
+
     // Adds the given rigid body to the world.
     void addRigidBody(PhysicsRigidBody* body);
     
@@ -244,16 +318,16 @@ private:
     PhysicsRigidBody* getRigidBody(const btCollisionObject* collisionObject);
     
     // Creates a box collision shape to be used in the creation of a rigid body.
-    btCollisionShape* createBox(const Vector3& min, const Vector3& max, const btVector3& scale);
+    btCollisionShape* createBox(const Vector3& min, const Vector3& max, const Vector3& scale);
 
     // Creates a capsule collision shape to be used in the creation of a rigid body.
     btCollisionShape* createCapsule(float radius, float height);
 
     // Creates a sphere collision shape to be used in the creation of a rigid body.
-    btCollisionShape* createSphere(float radius, const btVector3& scale);
+    btCollisionShape* createSphere(float radius, const Vector3& scale);
 
     // Creates a triangle mesh collision shape to be used in the creation of a rigid body.
-    btCollisionShape* createMesh(PhysicsRigidBody* body);
+    btCollisionShape* createMesh(PhysicsRigidBody* body, const Vector3& scale);
 
     // Sets up the given constraint for the given two rigid bodies.
     void addConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint);
@@ -302,12 +376,14 @@ private:
     btBroadphaseInterface* _overlappingPairCache;
     btSequentialImpulseConstraintSolver* _solver;
     btDynamicsWorld* _world;
+    btGhostPairCallback* _ghostPairCallback;
     std::vector<PhysicsCollisionShape*> _shapes;
     DebugDrawer* _debugDrawer;
     Listener::EventType _status;
     std::vector<Listener*>* _listeners;
     std::vector<PhysicsRigidBody*> _bodies;
     Vector3 _gravity;
+    std::map<PhysicsRigidBody::CollisionPair, CollisionInfo> _collisionStatus;  
 };
 
 }

+ 1 - 0
gameplay/src/PhysicsFixedConstraint.cpp

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

+ 4 - 4
gameplay/src/PhysicsGenericConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsGenericConstraint.h"
-
 #include "Node.h"
 #include "PhysicsMotionState.h"
 #include "PhysicsRigidBody.h"
@@ -45,13 +45,13 @@ PhysicsGenericConstraint::PhysicsGenericConstraint(PhysicsRigidBody* a, const Qu
         b->getNode()->getWorldMatrix().getScale(&sB);
         Vector3 tB(translationOffsetB.x * sB.x, translationOffsetB.y * sB.y, translationOffsetB.z * sB.z);
 
-        btTransform frameInA(btQuaternion(rotationOffsetA.x, rotationOffsetA.y, rotationOffsetA.z, rotationOffsetA.w), btVector3(tA.x, tA.y, tA.z));
-        btTransform frameInB(btQuaternion(rotationOffsetB.x, rotationOffsetB.y, rotationOffsetB.z, rotationOffsetB.w), btVector3(tB.x, tB.y, tB.z));
+        btTransform frameInA(BQ(rotationOffsetA), BV(tA));
+        btTransform frameInB(BQ(rotationOffsetB), BV(tB));
         _constraint = new btGeneric6DofConstraint(*a->_body, *b->_body, frameInA, frameInB, true);
     }
     else
     {
-        btTransform frameInA(btQuaternion(rotationOffsetA.x, rotationOffsetA.y, rotationOffsetA.z, rotationOffsetA.w), btVector3(tA.x, tA.y, tA.z));
+        btTransform frameInA(BQ(rotationOffsetA), BV(tA));
         _constraint = new btGeneric6DofConstraint(*a->_body, frameInA, true);
     }
 }

+ 8 - 8
gameplay/src/PhysicsGenericConstraint.inl

@@ -45,42 +45,42 @@ inline const Vector3& PhysicsGenericConstraint::getTranslationOffsetB() const
 
 inline void PhysicsGenericConstraint::setAngularLowerLimit(const Vector3& limits)
 {
-    ((btGeneric6DofConstraint*)_constraint)->setAngularLowerLimit(btVector3(limits.x, limits.y, limits.z));
+    ((btGeneric6DofConstraint*)_constraint)->setAngularLowerLimit(BV(limits));
 }
 
 inline void PhysicsGenericConstraint::setAngularUpperLimit(const Vector3& limits)
 {
-    ((btGeneric6DofConstraint*)_constraint)->setAngularUpperLimit(btVector3(limits.x, limits.y, limits.z));
+    ((btGeneric6DofConstraint*)_constraint)->setAngularUpperLimit(BV(limits));
 }
 
 inline void PhysicsGenericConstraint::setLinearLowerLimit(const Vector3& limits)
 {
-    ((btGeneric6DofConstraint*)_constraint)->setLinearLowerLimit(btVector3(limits.x, limits.y, limits.z));
+    ((btGeneric6DofConstraint*)_constraint)->setLinearLowerLimit(BV(limits));
 }
     
 inline void PhysicsGenericConstraint::setLinearUpperLimit(const Vector3& limits)
 {
-    ((btGeneric6DofConstraint*)_constraint)->setLinearUpperLimit(btVector3(limits.x, limits.y, limits.z));
+    ((btGeneric6DofConstraint*)_constraint)->setLinearUpperLimit(BV(limits));
 }
 
 inline void PhysicsGenericConstraint::setRotationOffsetA(const Quaternion& rotationOffset)
 {
-    static_cast<btGeneric6DofConstraint*>(_constraint)->getFrameOffsetA().setRotation(btQuaternion(rotationOffset.x, rotationOffset.y, rotationOffset.z, rotationOffset.w));
+    static_cast<btGeneric6DofConstraint*>(_constraint)->getFrameOffsetA().setRotation(BQ(rotationOffset));
 }
 
 inline void PhysicsGenericConstraint::setRotationOffsetB(const Quaternion& rotationOffset)
 {
-    static_cast<btGeneric6DofConstraint*>(_constraint)->getFrameOffsetB().setRotation(btQuaternion(rotationOffset.x, rotationOffset.y, rotationOffset.z, rotationOffset.w));
+    static_cast<btGeneric6DofConstraint*>(_constraint)->getFrameOffsetB().setRotation(BQ(rotationOffset));
 }
 
 inline void PhysicsGenericConstraint::setTranslationOffsetA(const Vector3& translationOffset)
 {
-    static_cast<btGeneric6DofConstraint*>(_constraint)->getFrameOffsetA().setOrigin(btVector3(translationOffset.x, translationOffset.y, translationOffset.z));
+    static_cast<btGeneric6DofConstraint*>(_constraint)->getFrameOffsetA().setOrigin(BV(translationOffset));
 }
 
 inline void PhysicsGenericConstraint::setTranslationOffsetB(const Vector3& translationOffset)
 {
-    static_cast<btGeneric6DofConstraint*>(_constraint)->getFrameOffsetB().setOrigin(btVector3(translationOffset.x, translationOffset.y, translationOffset.z));
+    static_cast<btGeneric6DofConstraint*>(_constraint)->getFrameOffsetB().setOrigin(BV(translationOffset));
 }
 
 }

+ 4 - 4
gameplay/src/PhysicsHingeConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsHingeConstraint.h"
-
 #include "Node.h"
 
 namespace gameplay
@@ -27,13 +27,13 @@ PhysicsHingeConstraint::PhysicsHingeConstraint(PhysicsRigidBody* a, const Quater
         b->getNode()->getWorldMatrix().getScale(&sB);
         Vector3 tB(translationOffsetB.x * sB.x, translationOffsetB.y * sB.y, translationOffsetB.z * sB.z);
 
-        btTransform frameInA(btQuaternion(rotationOffsetA.x, rotationOffsetA.y, rotationOffsetA.z, rotationOffsetA.w), btVector3(tA.x, tA.y, tA.z));
-        btTransform frameInB(btQuaternion(rotationOffsetB.x, rotationOffsetB.y, rotationOffsetB.z, rotationOffsetB.w), btVector3(tB.x, tB.y, tB.z));
+        btTransform frameInA(BQ(rotationOffsetA), BV(tA));
+        btTransform frameInB(BQ(rotationOffsetB), BV(tB));
         _constraint = new btHingeConstraint(*a->_body, *b->_body, frameInA, frameInB);
     }
     else
     {
-        btTransform frameInA(btQuaternion(rotationOffsetA.x, rotationOffsetA.y, rotationOffsetA.z, rotationOffsetA.w), btVector3(tA.x, tA.y, tA.z));
+        btTransform frameInA(BQ(rotationOffsetA), BV(tA));
         _constraint = new btHingeConstraint(*a->_body, frameInA);
     }
 }

+ 6 - 6
gameplay/src/PhysicsMotionState.cpp

@@ -1,3 +1,4 @@
+#include "Base.h"
 #include "PhysicsMotionState.h"
 
 namespace gameplay
@@ -9,7 +10,7 @@ PhysicsMotionState::PhysicsMotionState(Node* node, const Vector3* centerOfMassOf
     if (centerOfMassOffset)
     {
         // Store the center of mass offset.
-        _centerOfMassOffset.setOrigin(btVector3(centerOfMassOffset->x, centerOfMassOffset->y, centerOfMassOffset->z));
+        _centerOfMassOffset.setOrigin(BV(*centerOfMassOffset));
     }
 
     updateTransformFromNode();
@@ -21,7 +22,7 @@ PhysicsMotionState::~PhysicsMotionState()
 
 void PhysicsMotionState::getWorldTransform(btTransform &transform) const
 {
-    if (_node->getPhysicsRigidBody() && _node->getPhysicsRigidBody()->isKinematic())
+    if (_node->getRigidBody() && _node->getRigidBody()->isKinematic())
         updateTransformFromNode();
 
     transform = _centerOfMassOffset.inverse() * _worldTransform;
@@ -49,17 +50,16 @@ void PhysicsMotionState::updateTransformFromNode() const
     {
         // When there is a center of mass offset, we modify the initial world transformation
         // so that when physics is initially applied, the object is in the correct location.
-        btQuaternion orientation(rotation.x, rotation.y, rotation.z, rotation.w);
-        btTransform offset = btTransform(orientation, btVector3(0.0f, 0.0f, 0.0f)) * _centerOfMassOffset.inverse();
+        btTransform offset = btTransform(BQ(rotation), btVector3(0.0f, 0.0f, 0.0f)) * _centerOfMassOffset.inverse();
 
         btVector3 origin(m.m[12] + _centerOfMassOffset.getOrigin().getX() + offset.getOrigin().getX(), 
                          m.m[13] + _centerOfMassOffset.getOrigin().getY() + offset.getOrigin().getY(), 
                          m.m[14] + _centerOfMassOffset.getOrigin().getZ() + offset.getOrigin().getZ());
-        _worldTransform = btTransform(orientation, origin);
+        _worldTransform = btTransform(BQ(rotation), origin);
     }
     else
     {
-        _worldTransform = btTransform(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w), btVector3(m.m[12], m.m[13], m.m[14]));
+        _worldTransform = btTransform(BQ(rotation), btVector3(m.m[12], m.m[13], m.m[14]));
     }
 }
 

+ 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:

+ 28 - 55
gameplay/src/PhysicsRigidBody.cpp

@@ -10,10 +10,6 @@
 namespace gameplay
 {
 
-const int PhysicsRigidBody::Listener::DIRTY         = 0x01;
-const int PhysicsRigidBody::Listener::COLLISION     = 0x02;
-const int PhysicsRigidBody::Listener::REGISTERED    = 0x04;
-
 // Internal values used for creating mesh, heightfield, and capsule rigid bodies.
 #define SHAPE_MESH ((PhysicsRigidBody::Type)(PhysicsRigidBody::SHAPE_NONE + 1))
 #define SHAPE_HEIGHTFIELD ((PhysicsRigidBody::Type)(PhysicsRigidBody::SHAPE_NONE + 2))
@@ -28,33 +24,40 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, floa
         _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
         _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
 {
+    // Get the node's world scale (we need to apply this during creation since rigid bodies don't scale dynamically).
+    Vector3 scale;
+    node->getWorldMatrix().getScale(&scale);
+
     switch (type)
     {
         case SHAPE_BOX:
         {
             const BoundingBox& box = node->getModel()->getMesh()->getBoundingBox();
-            _shape = Game::getInstance()->getPhysicsController()->createBox(box.min, box.max, btVector3(node->getScaleX(), node->getScaleY(), node->getScaleZ()));
+            _shape = Game::getInstance()->getPhysicsController()->createBox(box.min, box.max, scale);
             break;
         }
         case SHAPE_SPHERE:
         {
             const BoundingSphere& sphere = node->getModel()->getMesh()->getBoundingSphere();
-            _shape = Game::getInstance()->getPhysicsController()->createSphere(sphere.radius, btVector3(node->getScaleX(), node->getScaleY(), node->getScaleZ()));
+            _shape = Game::getInstance()->getPhysicsController()->createSphere(sphere.radius, scale);
             break;
         }
         case SHAPE_MESH:
         {
-            _shape = Game::getInstance()->getPhysicsController()->createMesh(this);
+            _shape = Game::getInstance()->getPhysicsController()->createMesh(this, scale);
             break;
         }
     }
 
     // Use the center of the bounding sphere as the center of mass offset.
     Vector3 c(node->getModel()->getMesh()->getBoundingSphere().center);
+    c.x *= scale.x;
+    c.y *= scale.y;
+    c.z *= scale.z;
     c.negate();
 
-    // Create the Bullet rigid body.
-    if (c.lengthSquared() > MATH_EPSILON)
+    // Create the Bullet rigid body (we don't apply center of mass offsets on mesh rigid bodies).
+    if (c.lengthSquared() > MATH_EPSILON && type != SHAPE_MESH)
         _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping, &c);
     else
         _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
@@ -151,11 +154,18 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, float radius, float height, float
         _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
         _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
 {
+    // Get the node's world scale (we need to apply this during creation since rigid bodies don't scale dynamically).
+    Vector3 scale;
+    node->getWorldMatrix().getScale(&scale);
+
     // Create the capsule collision shape.
     _shape = Game::getInstance()->getPhysicsController()->createCapsule(radius, height);
 
     // Use the center of the bounding sphere as the center of mass offset.
     Vector3 c(node->getModel()->getMesh()->getBoundingSphere().center);
+    c.x *= scale.x;
+    c.y *= scale.y;
+    c.z *= scale.z;
     c.negate();
 
     // Create the Bullet rigid body.
@@ -205,13 +215,7 @@ PhysicsRigidBody::~PhysicsRigidBody()
 
 void PhysicsRigidBody::addCollisionListener(Listener* listener, PhysicsRigidBody* body)
 {
-    if (!_listeners)
-        _listeners = new std::vector<Listener*>();
-
-    CollisionPair pair(this, body);
-    listener->_collisionStatus[pair] = PhysicsRigidBody::Listener::REGISTERED;
-
-    _listeners->push_back(listener);
+    Game::getInstance()->getPhysicsController()->addCollisionListener(listener, this, body);
 }
 
 void PhysicsRigidBody::applyForce(const Vector3& force, const Vector3* relativePosition)
@@ -222,9 +226,9 @@ void PhysicsRigidBody::applyForce(const Vector3& force, const Vector3* relativeP
     {
         _body->activate();
         if (relativePosition)
-            _body->applyForce(btVector3(force.x, force.y, force.z), btVector3(relativePosition->x, relativePosition->y, relativePosition->z));
+            _body->applyForce(BV(force), BV(*relativePosition));
         else
-            _body->applyCentralForce(btVector3(force.x, force.y, force.z));
+            _body->applyCentralForce(BV(force));
     }
 }
 
@@ -238,10 +242,10 @@ void PhysicsRigidBody::applyImpulse(const Vector3& impulse, const Vector3* relat
 
         if (relativePosition)
         {
-            _body->applyImpulse(btVector3(impulse.x, impulse.y, impulse.z), btVector3(relativePosition->x, relativePosition->y, relativePosition->z));
+            _body->applyImpulse(BV(impulse), BV(*relativePosition));
         }
         else
-            _body->applyCentralImpulse(btVector3(impulse.x, impulse.y, impulse.z));
+            _body->applyCentralImpulse(BV(impulse));
     }
 }
 
@@ -252,7 +256,7 @@ void PhysicsRigidBody::applyTorque(const Vector3& torque)
     if (torque.lengthSquared() > MATH_EPSILON)
     {
         _body->activate();
-        _body->applyTorque(btVector3(torque.x, torque.y, torque.z));
+        _body->applyTorque(BV(torque));
     }
 }
 
@@ -263,7 +267,7 @@ void PhysicsRigidBody::applyTorqueImpulse(const Vector3& torque)
     if (torque.lengthSquared() > MATH_EPSILON)
     {
         _body->activate();
-        _body->applyTorqueImpulse(btVector3(torque.x, torque.y, torque.z));
+        _body->applyTorqueImpulse(BV(torque));
     }
 }
 
@@ -473,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.
@@ -557,8 +560,8 @@ void PhysicsRigidBody::transformChanged(Transform* transform, long cookie)
     _inverseIsDirty = true;
 }
 
-PhysicsRigidBody::CollisionPair::CollisionPair(PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
-    : _rbA(rbA), _rbB(rbB)
+PhysicsRigidBody::CollisionPair::CollisionPair(PhysicsRigidBody* rigidBodyA, PhysicsRigidBody* rigidBodyB)
+    : rigidBodyA(rigidBodyA), rigidBodyB(rigidBodyB)
 {
     // Unused
 }
@@ -568,36 +571,6 @@ PhysicsRigidBody::Listener::~Listener()
     // Unused
 }
 
-btScalar PhysicsRigidBody::Listener::addSingleResult(btManifoldPoint& cp, 
-                                                     const btCollisionObject* a, int partIdA, int indexA, 
-                                                     const btCollisionObject* b, int partIdB, int indexB)
-{
-    // Get pointers to the PhysicsRigidBody objects.
-    PhysicsRigidBody* rbA = Game::getInstance()->getPhysicsController()->getRigidBody(a);
-    PhysicsRigidBody* rbB = Game::getInstance()->getPhysicsController()->getRigidBody(b);
-    
-    // If the given rigid body pair has collided in the past, then
-    // we notify the listener only if the pair was not colliding
-    // during the previous frame. Otherwise, it's a new pair, so notify the listener.
-    CollisionPair pair(rbA, rbB);
-    if (_collisionStatus.count(pair) > 0)
-    {
-        if ((_collisionStatus[pair] & COLLISION) == 0)
-            collisionEvent(pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()));
-    }
-    else
-    {
-        collisionEvent(pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()));
-    }
-
-    // Update the collision status cache (we remove the dirty bit
-    // set in the controller's update so that this particular collision pair's
-    // status is not reset to 'no collision' when the controller's update completes).
-    _collisionStatus[pair] &= ~DIRTY;
-    _collisionStatus[pair] |= COLLISION;
-    return 0.0f;
-}
-
 btScalar PhysicsRigidBody::CollidesWithCallback::addSingleResult(btManifoldPoint& cp, 
                                                                  const btCollisionObject* a, int partIdA, int indexA, 
                                                                  const btCollisionObject* b, int partIdB, int indexB)

+ 43 - 30
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;
@@ -49,64 +50,62 @@ public:
         /**
          * Constructor.
          */
-        CollisionPair(PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
+        CollisionPair(PhysicsRigidBody* rigidBodyA, PhysicsRigidBody* rigidBodyB);
 
         /**
          * Less than operator (needed for use as a key in map).
          * 
-         * @param cp The collision pair to compare.
+         * @param collisionPair The collision pair to compare.
          * @return True if this pair is "less than" the given pair; false otherwise.
          */
-        bool operator<(const CollisionPair& cp) const;
+        bool operator<(const CollisionPair& collisionPair) const;
 
         /** The first rigid body in the collision. */
-        PhysicsRigidBody* _rbA;
+        PhysicsRigidBody* rigidBodyA;
 
         /** The second rigid body in the collision. */
-        PhysicsRigidBody* _rbB;
+        PhysicsRigidBody* rigidBodyB;
     };
 
     /**
      * Collision listener interface.
      */
-    class Listener : public btCollisionWorld::ContactResultCallback
+    class Listener
     {
         friend class PhysicsRigidBody;
         friend class PhysicsController;
 
     public:
+        /**
+         * The type of collision event.
+         */
+        enum EventType
+        {
+            /**
+             * Event fired when the two rigid bodies start colliding.
+             */
+            COLLIDING,
+
+            /**
+             * Event fired when the two rigid bodies no longer collide.
+             */
+            NOT_COLLIDING
+        };
+
         /**
          * Destructor.
          */
         virtual ~Listener();
 
         /**
-         * Handles when a collision occurs for the rigid body where this listener is registered.
+         * Handles when a collision starts or stops occurring for the rigid body where this listener is registered.
          * 
+         * @param type The type of collision event.
          * @param collisionPair The two rigid bodies involved in the collision.
-         * @param contactPoint The point (in world space) where the collision occurred.
-         */
-        virtual void collisionEvent(const CollisionPair& collisionPair, const Vector3& contactPoint) = 0;
-        
-    protected:
-        
-        /**
-         * Internal function used for Bullet integration (do not use or override).
+         * @param contactPointA The contact point with the first rigid body (in world space).
+         * @param contactPointB The contact point with the second rigid body (in world space).
          */
-        btScalar addSingleResult(btManifoldPoint& cp, 
-                                 const btCollisionObject* a, int partIdA, int indexA, 
-                                 const btCollisionObject* b, int partIdB, int indexB);
-
-        std::map<CollisionPair, int> _collisionStatus;  // Holds the collision status for each pair of rigid bodies. 
-        
-    private:
-
-        // Internal constant.
-        static const int DIRTY;
-        // Internal constant.
-        static const int COLLISION;
-        // Internal constant.
-        static const int REGISTERED;
+        virtual void collisionEvent(EventType type, const CollisionPair& collisionPair, const Vector3& contactPointA = Vector3(), const Vector3& contactPointB = Vector3()) = 0;
     };
 
     /**
@@ -219,7 +218,7 @@ public:
      * 
      * @return The node.
      */
-    inline const Node* getNode() const;
+    inline Node* getNode();
 
     /**
      * Gets the rigid body's restitution.
@@ -235,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.
      * 

+ 23 - 12
gameplay/src/PhysicsRigidBody.inl

@@ -1,4 +1,5 @@
 #include "PhysicsRigidBody.h"
+#include "Base.h"
 
 namespace gameplay
 {
@@ -58,7 +59,7 @@ inline const Vector3& PhysicsRigidBody::getLinearVelocity() const
     return *_linearVelocity;
 }
 
-inline const Node* PhysicsRigidBody::getNode() const
+inline Node* PhysicsRigidBody::getNode()
 {
     return _node;
 }
@@ -70,17 +71,27 @@ 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)
 {
-    _body->setAngularVelocity(btVector3(velocity.x, velocity.y, velocity.z));
+    _body->setAngularVelocity(BV(velocity));
 }
 
 inline void PhysicsRigidBody::setAnisotropicFriction(const Vector3& friction)
 {
-    _body->setAnisotropicFriction(btVector3(friction.x, friction.y, friction.z));
+    _body->setAnisotropicFriction(BV(friction));
 }
 
 inline void PhysicsRigidBody::setDamping(float linearDamping, float angularDamping)
@@ -95,7 +106,7 @@ inline void PhysicsRigidBody::setFriction(float friction)
 
 inline void PhysicsRigidBody::setGravity(const Vector3& gravity)
 {
-    _body->setGravity(btVector3(gravity.x, gravity.y, gravity.z));
+    _body->setGravity(BV(gravity));
 }
 
 inline void PhysicsRigidBody::setKinematic(bool kinematic)
@@ -114,7 +125,7 @@ inline void PhysicsRigidBody::setKinematic(bool kinematic)
 
 inline void PhysicsRigidBody::setLinearVelocity(const Vector3& velocity)
 {
-    _body->setLinearVelocity(btVector3(velocity.x, velocity.y, velocity.z));
+    _body->setLinearVelocity(BV(velocity));
 }
 
 inline void PhysicsRigidBody::setRestitution(float restitution)
@@ -122,18 +133,18 @@ inline void PhysicsRigidBody::setRestitution(float restitution)
     _body->setRestitution(restitution);
 }
 
-inline bool PhysicsRigidBody::CollisionPair::operator<(const CollisionPair& cp) const
+inline bool PhysicsRigidBody::CollisionPair::operator<(const CollisionPair& collisionPair) const
 {
     // If the pairs are equal, then return false.
-    if ((_rbA == cp._rbA && _rbB == cp._rbB) || (_rbA == cp._rbB && _rbB == cp._rbA))
+    if ((rigidBodyA == collisionPair.rigidBodyA && rigidBodyB == collisionPair.rigidBodyB) || (rigidBodyA == collisionPair.rigidBodyB && rigidBodyB == collisionPair.rigidBodyA))
         return false;
     else
     {
-        // We choose to compare based on _rbA arbitrarily.
-        if (_rbA < cp._rbA)
+        // We choose to compare based on rigidBodyA arbitrarily.
+        if (rigidBodyA < collisionPair.rigidBodyA)
             return true;
-        else if (_rbA == cp._rbA)
-            return _rbB < cp._rbB;
+        else if (rigidBodyA == collisionPair.rigidBodyA)
+            return rigidBodyB < collisionPair.rigidBodyB;
         else
             return false;
     }

+ 3 - 3
gameplay/src/PhysicsSocketConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsSocketConstraint.h"
-
 #include "Node.h"
 
 namespace gameplay
@@ -38,11 +38,11 @@ PhysicsSocketConstraint::PhysicsSocketConstraint(PhysicsRigidBody* a, const Vect
         b->getNode()->getWorldMatrix().getScale(&sB);
         Vector3 tB(translationOffsetB.x * sB.x, translationOffsetB.y * sB.y, translationOffsetB.z * sB.z);
 
-        _constraint = new btPoint2PointConstraint(*a->_body, *b->_body, btVector3(tA.x, tA.y, tA.z), btVector3(tB.x, tB.y, tB.z));
+        _constraint = new btPoint2PointConstraint(*a->_body, *b->_body, BV(tA), BV(tB));
     }
     else
     {
-        _constraint = new btPoint2PointConstraint(*a->_body, btVector3(tA.x, tA.y, tA.z));
+        _constraint = new btPoint2PointConstraint(*a->_body, BV(tA));
     }
 }
 

+ 3 - 3
gameplay/src/PhysicsSpringConstraint.cpp

@@ -1,5 +1,5 @@
+#include "Base.h"
 #include "PhysicsSpringConstraint.h"
-
 #include "Node.h"
 #include "PhysicsRigidBody.h"
 
@@ -32,8 +32,8 @@ PhysicsSpringConstraint::PhysicsSpringConstraint(PhysicsRigidBody* a, const Quat
     b->getNode()->getWorldMatrix().getScale(&sB);
     Vector3 tB(translationOffsetB.x * sB.x, translationOffsetB.y * sB.y, translationOffsetB.z * sB.z);
 
-    btTransform frameInA(btQuaternion(rotationOffsetA.x, rotationOffsetA.y, rotationOffsetA.z, rotationOffsetA.w), btVector3(tA.x, tA.y, tA.z));
-    btTransform frameInB(btQuaternion(rotationOffsetB.x, rotationOffsetB.y, rotationOffsetB.z, rotationOffsetB.w), btVector3(tB.x, tB.y, tB.z));
+    btTransform frameInA(BQ(rotationOffsetA), BV(tA));
+    btTransform frameInB(BQ(rotationOffsetB), BV(tB));
     _constraint = new btGeneric6DofSpringConstraint(*a->_body, *b->_body, frameInA, frameInB, true);
 }
 

+ 2 - 2
gameplay/src/Plane.h

@@ -45,7 +45,7 @@ public:
      * Constructs a new plane from the specified values.
      *
      * @param normal The normal vector of this plane.
-     * @param distance The distance to this plane along its (unit) normal to the origin.
+     * @param distance The distance from this plane along its (unit) normal to the origin.
      */
     Plane(const Vector3& normal, float distance);
 
@@ -220,7 +220,7 @@ private:
  * @param plane The plane to transform.
  * @return The resulting transformed plane.
  */
-inline Plane operator*(const Matrix& matrix, const Plane& plane);
+inline const Plane operator*(const Matrix& matrix, const Plane& plane);
 
 }
 

+ 1 - 1
gameplay/src/Plane.inl

@@ -9,7 +9,7 @@ inline Plane& Plane::operator*=(const Matrix& matrix)
     return *this;
 }
 
-inline Plane operator*(const Matrix& matrix, const Plane& plane)
+inline const Plane operator*(const Matrix& matrix, const Plane& plane)
 {
     Plane p(plane);
     p.transform(matrix);

+ 7 - 0
gameplay/src/Platform.h

@@ -1,6 +1,9 @@
 #ifndef PLATFORM_H_
 #define PLATFORM_H_
 
+#include "Touch.h"
+#include "Keyboard.h"
+
 namespace gameplay
 {
 
@@ -116,6 +119,10 @@ public:
      */
      static void displayKeyboard(bool display);
 
+    static void touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    static void keyEventInternal(Keyboard::KeyEvent evt, int key);
+
 private:
 
     /**

Diff do ficheiro suprimidas por serem muito extensas
+ 554 - 554
gameplay/src/PlatformMacOS.mm


+ 103 - 53
gameplay/src/PlatformQNX.cpp

@@ -504,10 +504,7 @@ Platform* Platform::create(Game* game)
     int screenUsage = SCREEN_USAGE_DISPLAY|SCREEN_USAGE_OPENGL_ES2;
     int screenSwapInterval = WINDOW_VSYNC ? 1 : 0;
     int screenTransparency = SCREEN_TRANSPARENCY_NONE;
-
-    // Hard-coded to fullscreen.
-    __screenWindowSize[0] = -1;
-    __screenWindowSize[1] = -1;
+    int angle = atoi(getenv("ORIENTATION"));
 
     // Hard-coded to (0,0).
     int windowPosition[] =
@@ -574,25 +571,71 @@ Platform* Platform::create(Game* game)
         goto error;
     }
 
-    if (__screenWindowSize[0] > 0 && __screenWindowSize[1] > 0)
-    {
-        rc = screen_set_window_property_iv(__screenWindow, SCREEN_PROPERTY_SIZE, __screenWindowSize);
-        if (rc)
-        {
-            perror("screen_set_window_property_iv(SCREEN_PROPERTY_SIZE)");
-            goto error;
-        }
-    }
-    else
+	screen_display_t screen_display;
+    rc = screen_get_window_property_pv(__screenWindow, SCREEN_PROPERTY_DISPLAY, (void **)&screen_display);
+    if (rc)
     {
-        rc = screen_get_window_property_iv(__screenWindow, SCREEN_PROPERTY_SIZE, __screenWindowSize);
-        if (rc)
-        {
-            perror("screen_get_window_property_iv(SCREEN_PROPERTY_SIZE)");
-            goto error;
-        }
+    	perror("screen_get_window_property_pv(SCREEN_PROPERTY_DISPLAY)");
+    	goto error;
     }
 
+	screen_display_mode_t screen_mode;
+	rc = screen_get_display_property_pv(screen_display, SCREEN_PROPERTY_MODE, (void**)&screen_mode);
+	if (rc)
+	{
+		perror("screen_get_display_property_pv(SCREEN_PROPERTY_MODE)");
+		goto error;
+	}
+
+    int size[2];
+	rc = screen_get_window_property_iv(__screenWindow, SCREEN_PROPERTY_BUFFER_SIZE, size);
+	if (rc)
+	{
+		perror("screen_get_window_property_iv(SCREEN_PROPERTY_BUFFER_SIZE)");
+		goto error;
+	}
+
+	__screenWindowSize[0] = size[0];
+	__screenWindowSize[1] = size[1];
+
+	if ((angle == 0) || (angle == 180))
+	{
+		if (((screen_mode.width > screen_mode.height) && (size[0] < size[1])) ||
+			((screen_mode.width < screen_mode.height) && (size[0] > size[1])))
+		{
+			__screenWindowSize[1] = size[0];
+			__screenWindowSize[0] = size[1];
+		}
+	}
+	else if ((angle == 90) || (angle == 270))
+	{
+		if (((screen_mode.width > screen_mode.height) && (size[0] > size[1])) ||
+			((screen_mode.width < screen_mode.height) && (size[0] < size[1])))
+		{
+			__screenWindowSize[1] = size[0];
+			__screenWindowSize[0] = size[1];
+		}
+	}
+	else
+	{
+		perror("Navigator returned an unexpected orientation angle.");
+		goto error;
+	}
+
+	rc = screen_set_window_property_iv(__screenWindow, SCREEN_PROPERTY_BUFFER_SIZE, __screenWindowSize);
+	if (rc)
+	{
+		perror("screen_set_window_property_iv(SCREEN_PROPERTY_BUFFER_SIZE)");
+		goto error;
+	}
+
+	rc = screen_set_window_property_iv(__screenWindow, SCREEN_PROPERTY_ROTATION, &angle);
+	if (rc)
+	{
+		perror("screen_set_window_property_iv(SCREEN_PROPERTY_ROTATION)");
+		goto error;
+	}
+
     if (windowPosition[0] != 0 || windowPosition[1] != 0)
     {
         rc = screen_set_window_property_iv(__screenWindow, SCREEN_PROPERTY_POSITION, windowPosition);
@@ -762,12 +805,12 @@ int Platform::enterMessagePump()
                         if (!__multiTouch)
                         {
                             screen_get_event_property_iv(__screenEvent, SCREEN_PROPERTY_POSITION, position);
-                            Game::getInstance()->touchEvent(Touch::TOUCH_PRESS, position[0], position[1], 0);
+                           gameplay::Platform::touchEventInternal(Touch::TOUCH_PRESS, position[0], position[1], 0);
                         }
                         else
                         {
                             screen_get_mtouch_event(__screenEvent, &touchEvent, 0);
-                            Game::getInstance()->touchEvent(Touch::TOUCH_PRESS, touchEvent.x, touchEvent.y, touchEvent.contact_id);
+                            gameplay::Platform::touchEventInternal(Touch::TOUCH_PRESS, touchEvent.x, touchEvent.y, touchEvent.contact_id);
                         }
                         break;
                     }
@@ -777,12 +820,12 @@ int Platform::enterMessagePump()
                         if (!__multiTouch)
                         {
                             screen_get_event_property_iv(__screenEvent, SCREEN_PROPERTY_POSITION, position);
-                            Game::getInstance()->touchEvent(Touch::TOUCH_RELEASE, position[0], position[1], 0);
+                           gameplay::Platform::touchEventInternal(Touch::TOUCH_RELEASE, position[0], position[1], 0);
                         }
                         else
                         {
                             screen_get_mtouch_event(__screenEvent, &touchEvent, 0);
-                            Game::getInstance()->touchEvent(Touch::TOUCH_RELEASE, touchEvent.x, touchEvent.y, touchEvent.contact_id);
+                            gameplay::Platform::touchEventInternal(Touch::TOUCH_RELEASE, touchEvent.x, touchEvent.y, touchEvent.contact_id);
                         }
                         break;
                     }
@@ -792,12 +835,12 @@ int Platform::enterMessagePump()
                         if (!__multiTouch)
                         {
                             screen_get_event_property_iv(__screenEvent, SCREEN_PROPERTY_POSITION, position);
-                           Game::getInstance()->touchEvent(Touch::TOUCH_MOVE, position[0], position[1], 0);
+                           gameplay::Platform::touchEventInternal(Touch::TOUCH_MOVE, position[0], position[1], 0);
                         }
                         else
                         {
                             screen_get_mtouch_event(__screenEvent, &touchEvent, 0);
-                            Game::getInstance()->touchEvent(Touch::TOUCH_MOVE, touchEvent.x, touchEvent.y, touchEvent.contact_id);
+                            gameplay::Platform::touchEventInternal(Touch::TOUCH_MOVE, touchEvent.x, touchEvent.y, touchEvent.contact_id);
                         }
                         break;
                         break;
@@ -897,12 +940,12 @@ int Platform::enterMessagePump()
                         // Suppress key repeats
                         if ((flags & KEY_REPEAT) == 0)
                         {
-                            Game::getInstance()->keyEvent(evt, getKey(value));
+                            keyEventInternal(evt, getKey(value));
                             if (evt == gameplay::Keyboard::KEY_PRESS && flags & KEY_SYM_VALID)
                             {
                                 int unicode = getUnicode(value);
                                 if (unicode)
-                                    Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_CHAR, unicode);
+                                    keyEventInternal(gameplay::Keyboard::KEY_CHAR, unicode);
                             }
                         }
                         break;
@@ -927,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;
         }
     }
 
@@ -1046,6 +1084,18 @@ void Platform::displayKeyboard(bool display)
         virtualkeyboard_hide();
 }
 
+void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+    Form::touchEventInternal(evt, x, y, contactIndex);
+}
+
+void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    gameplay::Game::getInstance()->keyEvent(evt, key);
+    Form::keyEventInternal(evt, key);
+}
+
 }
 
 #endif

+ 44 - 10
gameplay/src/PlatformWin32.cpp

@@ -57,6 +57,18 @@ static gameplay::Keyboard::Key getKey(WPARAM win32KeyCode, bool shiftDown)
         return gameplay::Keyboard::KEY_ALT;
     case VK_APPS:
         return gameplay::Keyboard::KEY_MENU;
+    case VK_LSHIFT:
+        return gameplay::Keyboard::KEY_SHIFT;
+    case VK_RSHIFT:
+        return gameplay::Keyboard::KEY_SHIFT;
+    case VK_LCONTROL:
+        return gameplay::Keyboard::KEY_CTRL;
+    case VK_RCONTROL:
+        return gameplay::Keyboard::KEY_CTRL;
+    case VK_LMENU:
+        return gameplay::Keyboard::KEY_ALT;
+    case VK_RMENU:
+        return gameplay::Keyboard::KEY_ALT;
     case VK_LWIN:
     case VK_RWIN:
         return gameplay::Keyboard::KEY_HYPER;
@@ -241,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;
@@ -251,6 +269,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     static int lx, ly;
 
     static bool shiftDown = false;
+    static bool capsOn = false;
 
     switch (msg)
     {
@@ -270,7 +289,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     case WM_LBUTTONDOWN:
         if (!gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_PRESS_LEFT_BUTTON, LOWORD(lParam), HIWORD(lParam), 0))
         {
-            gameplay::Game::getInstance()->touchEvent(gameplay::Touch::TOUCH_PRESS, LOWORD(lParam), HIWORD(lParam), 0);
+	        gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_PRESS, LOWORD(lParam), HIWORD(lParam), 0);
         }
         lMouseDown = true;
         return 0;
@@ -279,7 +298,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         lMouseDown = false;
         if (!gameplay::Game::getInstance()->mouseEvent(gameplay::Mouse::MOUSE_RELEASE_LEFT_BUTTON, LOWORD(lParam), HIWORD(lParam), 0))
         {
-            gameplay::Game::getInstance()->touchEvent(gameplay::Touch::TOUCH_RELEASE, LOWORD(lParam), HIWORD(lParam), 0);
+	        gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_RELEASE, LOWORD(lParam), HIWORD(lParam), 0);
         }
         return 0;
 
@@ -321,7 +340,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         if (lMouseDown && !consumed)
         {
             // Mouse move events should be interpreted as touch move only if left mouse is held and the game did not consume the mouse event.
-            gameplay::Game::getInstance()->touchEvent(gameplay::Touch::TOUCH_MOVE, LOWORD(lParam), HIWORD(lParam), 0);
+            gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_MOVE, LOWORD(lParam), HIWORD(lParam), 0);
             return 0;
         }
         else if (rMouseDown)
@@ -352,31 +371,34 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         break;
 
     case WM_KEYDOWN:
-        if (wParam == VK_SHIFT)
+        if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT)
             shiftDown = true;
 
+        if (wParam == VK_CAPITAL)
+            capsOn = !capsOn;
+
         // Suppress key repeats
         if ((lParam & 0x40000000) == 0)
-            gameplay::Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown));
+	        gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown ^ capsOn));
         break;
         
     case WM_KEYUP:
-        if (wParam == VK_SHIFT)
+        if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT)
             shiftDown = false;
 
-        gameplay::Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_RELEASE, getKey(wParam, shiftDown));
+        gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_RELEASE, getKey(wParam, shiftDown ^ capsOn));
         break;
 
     case WM_CHAR:
         // Suppress key repeats
         if ((lParam & 0x40000000) == 0)
-            gameplay::Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_CHAR, wParam);
+            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
         break;
 
     case WM_UNICHAR:
         // Suppress key repeats
         if ((lParam & 0x40000000) == 0)
-            gameplay::Game::getInstance()->keyEvent(gameplay::Keyboard::KEY_CHAR, wParam);
+            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
         break;
 
     case WM_SETFOCUS:
@@ -385,7 +407,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     case WM_KILLFOCUS:
         break;
     }
-
+    
     return DefWindowProc(hwnd, msg, wParam, lParam); 
 }
 
@@ -645,6 +667,18 @@ void Platform::displayKeyboard(bool display)
     // Do nothing.
 }
 
+void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+    Form::touchEventInternal(evt, x, y, contactIndex);
+}
+
+void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    gameplay::Game::getInstance()->keyEvent(evt, key);
+    Form::keyEventInternal(evt, key);
+}
+
 }
 
 #endif

+ 179 - 27
gameplay/src/Properties.cpp

@@ -6,22 +6,44 @@
 namespace gameplay
 {
 
+Properties::Properties()
+{
+}
+
+Properties::Properties(const Properties& copy)
+{
+    _namespace = copy._namespace;
+    _id = copy._id;
+    _parentID = copy._parentID;
+    _properties = copy._properties;
+    
+    _namespaces = std::vector<Properties*>();
+    std::vector<Properties*>::const_iterator it;
+    for (it = copy._namespaces.begin(); it < copy._namespaces.end(); it++)
+    {
+        _namespaces.push_back(new Properties(**it));
+    }
+    rewind();
+}
+
 Properties::Properties(FILE* file)
 {
     readProperties(file);
-    _propertiesItr = _properties.end();
-    _namespacesItr = _namespaces.end();
+    rewind();
 }
 
-Properties::Properties(FILE* file, const char* name, const char* id) : _namespace(name)
+Properties::Properties(FILE* file, const char* name, const char* id, const char* parentID) : _namespace(name)
 {
     if (id)
     {
         _id = id;
     }
+    if (parentID)
+    {
+        _parentID = parentID;
+    }
     readProperties(file);
-    _propertiesItr = _properties.end();
-    _namespacesItr = _namespaces.end();
+    rewind();
 }
 
 Properties* Properties::create(const char* filePath)
@@ -36,6 +58,8 @@ Properties* Properties::create(const char* filePath)
 
     Properties* properties = new Properties(file);
 
+    properties->resolveInheritance();
+
     fclose(file);
 
     return properties;
@@ -47,7 +71,9 @@ void Properties::readProperties(FILE* file)
     int c;
     char* name;
     char* value;
+    char* parentID;
     char* rc;
+    char* rcc;
 
     while (true)
     {
@@ -104,11 +130,16 @@ void Properties::readProperties(FILE* file)
             }
             else
             {
+                parentID = NULL;
+
                 // This line might begin or end a namespace,
                 // or it might be a key/value pair without '='.
 
                 // Check for '{' on same line.
                 rc = strchr(line, '{');
+
+                // Check for inheritance: ':'
+                rcc = strchr(line, ':');
             
                 // Get the name of the namespace.
                 name = strtok(line, " \t\n{");
@@ -124,12 +155,18 @@ void Properties::readProperties(FILE* file)
                 }
 
                 // Get its ID if it has one.
-                value = strtok(NULL, "{");
+                value = strtok(NULL, ":{");
                 value = trimWhiteSpace(value);
+                if (rcc != NULL)
+                {
+                    parentID = strtok(NULL, "{");
+                    parentID = trimWhiteSpace(parentID);
+                }
+
                 if (value != NULL && value[0] == '{')
                 {
                     // New namespace without an ID.
-                    Properties* space = new Properties(file, name, NULL);
+                    Properties* space = new Properties(file, name, NULL, parentID);
                     _namespaces.push_back(space);
                 }
                 else
@@ -138,7 +175,7 @@ void Properties::readProperties(FILE* file)
                     if (rc != NULL)
                     {
                         // Create new namespace.
-                        Properties* space = new Properties(file, name, value);
+                        Properties* space = new Properties(file, name, value, parentID);
                         _namespaces.push_back(space);
                     }
                     else
@@ -149,7 +186,7 @@ void Properties::readProperties(FILE* file)
                         if (c == '{')
                         {
                             // Create new namespace.
-                            Properties* space = new Properties(file, name, value);
+                            Properties* space = new Properties(file, name, value, parentID);
                             _namespaces.push_back(space);
                         }
                         else
@@ -228,7 +265,123 @@ char* Properties::trimWhiteSpace(char *str)
     return str;
 }
 
-const char* Properties::getNextProperty(const char** value)
+void Properties::resolveInheritance(const char* id)
+{
+    // Namespaces can be defined like so:
+    // "name id : parentID { }"
+    // This method merges data from the parent namespace into the child.
+
+    // Get a top-level namespace.
+    Properties* derived;
+    if (id)
+    {
+        derived = getNamespace(id);
+    }
+    else
+    {
+        derived = getNextNamespace();
+    }
+    while (derived)
+    {
+        // If the namespace has a parent ID, find the parent.
+        if (!derived->_parentID.empty())
+        {
+            Properties* parent = getNamespace(derived->_parentID.c_str());
+            if (parent)
+            {
+                resolveInheritance(parent->getId());
+
+                // Copy the child.
+                Properties* overrides = new Properties(*derived);
+
+                // Delete the child's data.
+                unsigned int count = derived->_namespaces.size();
+                for (unsigned int i = 0; i < count; i++)
+                {
+                    SAFE_DELETE(derived->_namespaces[i]);
+                }
+
+                // Copy data from the parent into the child.
+                derived->_properties = parent->_properties;
+                derived->_namespaces = std::vector<Properties*>();
+                std::vector<Properties*>::const_iterator itt;
+                for (itt = parent->_namespaces.begin(); itt < parent->_namespaces.end(); itt++)
+                {
+                    derived->_namespaces.push_back(new Properties(**itt));
+                }
+                derived->rewind();
+
+                // Take the original copy of the child and override the data copied from the parent.
+                derived->mergeWith(overrides);
+
+                // Delete the child copy.
+                SAFE_DELETE(overrides);
+            }
+        }
+
+        // Resolve inheritance within this namespace.
+        derived->resolveInheritance();
+
+        // Get the next top-level namespace and check again.
+        if (!id)
+        {
+            derived = getNextNamespace();
+        }
+        else
+        {
+            derived = NULL;
+        }
+    }
+}
+
+void Properties::mergeWith(Properties* overrides)
+{
+    // Overwrite or add each property found in child.
+    char* value = new char[255];
+    overrides->rewind();
+    const char* name = overrides->getNextProperty(&value);
+    while (name)
+    {
+        this->_properties[name] = value;
+        name = overrides->getNextProperty(&value);
+    }
+    SAFE_DELETE(value);
+    this->_propertiesItr = this->_properties.end();
+
+    // Merge all common nested namespaces, add new ones.
+    Properties* overridesNamespace = overrides->getNextNamespace();
+    while (overridesNamespace)
+    {
+        bool merged = false;
+
+        rewind();
+        Properties* derivedNamespace = getNextNamespace();
+        while (derivedNamespace)
+        {
+            if (strcmp(derivedNamespace->getNamespace(), overridesNamespace->getNamespace()) == 0 &&
+                strcmp(derivedNamespace->getId(), overridesNamespace->getId()) == 0)
+            {   
+                derivedNamespace->mergeWith(overridesNamespace);
+                merged = true;
+            }
+
+            derivedNamespace = getNextNamespace();
+        }
+
+        if (!merged)
+        {
+            // Add this new namespace.
+            Properties* newNamespace = new Properties(*overridesNamespace);
+
+            this->_namespaces.push_back(newNamespace);
+            this->_namespacesItr = this->_namespaces.end();
+        }
+
+        overridesNamespace = overrides->getNextNamespace();
+    }
+}
+
+const char* Properties::getNextProperty(char** value)
 {
     if (_propertiesItr == _properties.end())
     {
@@ -248,7 +401,7 @@ const char* Properties::getNextProperty(const char** value)
         {
             if (value)
             {
-                *value = _propertiesItr->second.c_str();
+                strcpy(*value, _propertiesItr->second.c_str());
             }
             return name.c_str();
         }
@@ -324,34 +477,35 @@ bool Properties::exists(const char* name) const
     return _properties.find(name) != _properties.end();
 }
 
-bool isStringNumeric(const char* str)
+const bool isStringNumeric(const char* str)
 {
-    char* ptr = const_cast<char*>(str);
+    // The first character may be '-'
+    if (*str == '-')
+        str++;
 
-    // First character must be a digit
-    if (!isdigit(*ptr))
+    // The first character after the sign must be a digit
+    if (!isdigit(*str))
         return false;
-    ptr++;
+    str++;
 
     // All remaining characters must be digits, with a single decimal (.) permitted
     unsigned int decimalCount = 0;
-    while (*ptr)
+    while (*str)
     {
-        if (!isdigit(*ptr))
+        if (!isdigit(*str))
         {
-            if (*ptr == '.' && decimalCount == 0)
+            if (*str == '.' && decimalCount == 0)
             {
                 // Max of 1 decimal allowed
                 decimalCount++;
             }
             else
             {
-                // Not a number
+                return false;
             }
         }
-        ptr++;
+        str++;
     }
-    
     return true;
 }
 
@@ -413,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;

+ 13 - 4
gameplay/src/Properties.h

@@ -97,7 +97,7 @@ void printProperties(Properties* properties)
 {
     // Print the name and ID of the current namespace.
     const char* spacename = properties->getNamespace();
-    const char* id = properties->getID();
+    const char* id = properties->getId();
     WARN_VARG("Namespace: %s  ID: %s\n{", spacename, id);
  
     // Print all properties in this namespace.
@@ -163,7 +163,7 @@ public:
      * 
      * @return The name of the next property, or NULL if there are no properties remaining.
      */
-    const char* getNextProperty(const char** value = NULL);
+    const char* getNextProperty(char** value = NULL);
 
     /**
      * Get the next namespace.
@@ -371,14 +371,16 @@ public:
 private:
     
     /**
-     * Constructor.
+     * Constructors.
      */
+    Properties();
     Properties(FILE* file);
+    Properties(const Properties& copy);
 
     /**
      * Constructor. Read from the beginning of namespace specified
      */
-    Properties(FILE* file, const char* name, const char* id = NULL);
+    Properties(FILE* file, const char* name, const char* id = NULL, const char* parentID = NULL);
 
     void readProperties(FILE* file);
 
@@ -386,8 +388,15 @@ private:
 
     char* trimWhiteSpace(char* str);
 
+    // Called after create(); copies info from parents into derived namespaces.
+    void resolveInheritance(const char* id = NULL);
+
+    // Called by resolveInheritance().
+    void mergeWith(Properties* overrides);
+
     std::string _namespace;
     std::string _id;
+    std::string _parentID;
     std::map<std::string, std::string> _properties;
     std::map<std::string, std::string>::const_iterator _propertiesItr;
     std::vector<Properties*> _namespaces;

+ 1 - 1
gameplay/src/Quaternion.h

@@ -340,7 +340,7 @@ public:
      * @param q The quaternion to multiply.
      * @return The quaternion product.
      */
-    inline Quaternion operator*(const Quaternion& q) const;
+    inline const Quaternion operator*(const Quaternion& q) const;
 
     /**
      * Multiplies this quaternion with the given quaternion.

+ 1 - 1
gameplay/src/Quaternion.inl

@@ -3,7 +3,7 @@
 namespace gameplay
 {
 
-inline Quaternion Quaternion::operator*(const Quaternion& q) const
+inline const Quaternion Quaternion::operator*(const Quaternion& q) const
 {
     Quaternion result(*this);
     result.multiply(q);

+ 189 - 0
gameplay/src/RadioButton.cpp

@@ -0,0 +1,189 @@
+#include "Base.h"
+#include "RadioButton.h"
+
+namespace gameplay
+{
+
+static std::vector<RadioButton*> __radioButtons;
+
+RadioButton::RadioButton() : _selected(false)
+{
+}
+
+RadioButton::RadioButton(const RadioButton& copy)
+{
+    // Hidden.
+}
+
+RadioButton::~RadioButton()
+{
+
+}
+
+RadioButton* RadioButton::create(Theme::Style* style, Properties* properties)
+{
+    RadioButton* radioButton = new RadioButton();
+    radioButton->_style = style;
+    properties->getVector2("position", &radioButton->_position);
+    properties->getVector2("size", &radioButton->_size);
+    if (properties->getBool("selected"))
+    {
+        RadioButton::clearSelected(radioButton->_groupId);
+        radioButton->_selected = true;
+    }
+
+    const char* id = properties->getId();
+    if (id)
+    {
+        radioButton->_id = id;
+    }
+
+    const char* text = properties->getString("text");
+    if (text)
+    {
+        radioButton->_text = text;
+    }
+
+    const char* groupId = properties->getString("group");
+    if (groupId)
+    {
+        radioButton->_groupId = groupId;
+    }
+
+    __radioButtons.push_back(radioButton);
+
+    return radioButton;
+}
+
+RadioButton* RadioButton::getRadioButton(const char* id)
+{
+    std::vector<RadioButton*>::const_iterator it;
+    for (it = __radioButtons.begin(); it < __radioButtons.end(); it++)
+    {
+        RadioButton* radioButton = *it;
+        if (strcmp(id, radioButton->getID()) == 0)
+        {
+            return radioButton;
+        }
+    }
+
+    return NULL;
+}
+
+void RadioButton::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    switch (evt)
+    {
+    case Touch::TOUCH_PRESS:
+        {
+            _state = Control::STATE_ACTIVE;
+            _dirty = true;
+        }
+        break;
+    case Touch::TOUCH_RELEASE:
+        {
+            if (_state == Control::STATE_ACTIVE)
+            {
+                if (x > 0 && x <= _size.x &&
+                    y > 0 && y <= _size.y)
+                {
+                    if (_callback)
+                    {
+                        _callback->trigger(this);
+                    }
+                    RadioButton::clearSelected(_groupId);
+                    _selected = true;
+                }
+                setState(Control::STATE_NORMAL);
+
+            }
+        }
+        break;
+    }
+}
+
+void RadioButton::clearSelected(const std::string& groupId)
+{
+    std::vector<RadioButton*>::const_iterator it;
+    for (it = __radioButtons.begin(); it < __radioButtons.end(); it++)
+    {
+        RadioButton* radioButton = *it;
+        if (groupId == radioButton->_groupId)
+        {
+            radioButton->_selected = false;
+        }
+    }
+}
+
+void RadioButton::drawSprites(SpriteBatch* spriteBatch, const Vector2& position)
+{
+    // Left, v-center.
+    // TODO: Set an alignment for icons.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getRadioButtonIcon();
+    if (icon)
+    {
+        Theme::Border border;
+        Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+        if (containerRegion)
+        {
+            border = containerRegion->getBorder();
+        }
+        const Theme::Padding padding = _style->getPadding();
+
+        const Vector2 size = icon->getSize();
+        const Vector4 color = icon->getColor();
+
+        Vector2 pos(position.x + _position.x + border.left + padding.left,
+            position.y + _position.y + (_size.y - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+
+        if (_selected)
+        {
+            const Theme::UVs on = icon->getOnUVs();
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, on.u1, on.v1, on.u2, on.v2, color);
+        }
+        else
+        {
+            const Theme::UVs off = icon->getOffUVs();
+            spriteBatch->draw(pos.x, pos.y, size.x, size.y, off.u1, off.v1, off.u2, off.v2, color);
+        }
+    }
+}
+
+void RadioButton::drawText(const Vector2& position)
+{
+    // TODO: Batch all labels that use the same font.
+    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
+    Theme::Icon* icon = overlay->getRadioButtonIcon();
+    Theme::Border border;
+    Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
+    if (containerRegion)
+    {
+        border = containerRegion->getBorder();
+    }
+    Theme::Padding padding = _style->getPadding();
+
+    // Set up the text viewport.
+    float iconWidth = 0.0f;
+    if (icon)
+    {
+        iconWidth = icon->getSize().x;
+    }
+
+    Font* font = overlay->getFont();
+    Vector2 pos(position.x + _position.x + border.left + padding.left + iconWidth,
+            position.y + _position.y + border.top + padding.top);
+
+    Rectangle viewport(pos.x, pos.y,
+        _size.x - border.left - padding.left - border.right - padding.right - iconWidth,
+        _size.y - border.top - padding.top - border.bottom - padding.bottom - font->getSize());
+    
+    // Draw the text.
+    font->begin();
+    font->drawText(_text.c_str(), viewport, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft());
+    font->end();
+
+    _dirty = false;
+}
+
+}

+ 37 - 0
gameplay/src/RadioButton.h

@@ -0,0 +1,37 @@
+#ifndef RADIOBUTTON_H_
+#define RADIOBUTTON_H_
+
+#include "Button.h"
+#include "Theme.h"
+#include "Properties.h"
+
+namespace gameplay
+{
+
+class RadioButton : public Button
+{
+public:
+    RadioButton();
+    virtual ~RadioButton();
+
+    static RadioButton* create(Theme::Style* style, Properties* properties);
+    static RadioButton* getRadioButton(const char* id);
+
+    void touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    void drawSprites(SpriteBatch* spriteBatch, const Vector2& position);
+    void drawText(const Vector2& position);
+
+private:
+    RadioButton(const RadioButton& copy);
+
+    // Clear the _selected flag of all RadioButtons in the given group.
+    static void clearSelected(const std::string& groupId);
+
+    std::string _groupId;
+    bool _selected;
+};
+
+}
+
+#endif

+ 25 - 1
gameplay/src/Ray.cpp

@@ -105,7 +105,31 @@ float Ray::intersects(const Frustum& frustum) const
 
 float Ray::intersects(const Plane& plane) const
 {
-    return plane.intersects(*this);
+    const Vector3& normal = plane.getNormal();
+    // If the origin of the ray is on the plane then the distance is zero.
+    float alpha = (normal.dot(_origin) + plane.getDistance());
+    if (fabs(alpha) < MATH_EPSILON)
+    {
+        return 0.0f;
+    }
+
+    float dot = normal.dot(_direction);
+    
+    // If the dot product of the plane's normal and this ray's direction is zero,
+    // then the ray is parallel to the plane and does not intersect it.
+    if ( dot == 0.0f )
+    {
+        return INTERSECTS_NONE;
+    }
+    
+    // Calculate the distance along the ray's direction vector to the point where
+    // the ray intersects the plane (if it is negative the plane is behind the ray).
+    float d = -alpha / dot;
+    if (d < 0.0f)
+    {
+        return INTERSECTS_NONE;
+    }
+    return d;
 }
 
 void Ray::set(const Vector3& origin, const Vector3& direction)

+ 3 - 2
gameplay/src/Ray.h

@@ -110,7 +110,8 @@ public:
     float intersects(const Frustum& frustum) const;
 
     /**
-     * Tests whether this ray intersects the specified plane.
+     * Tests whether this ray intersects the specified plane and returns the distance
+     * from the origin of the ray to the plane.
      *
      * @param plane The plane to test intersection with.
      * 
@@ -167,7 +168,7 @@ private:
  * @param ray The ray to transform.
  * @return The resulting transformed ray.
  */
-inline Ray operator*(const Matrix& matrix, const Ray& ray);
+inline const Ray operator*(const Matrix& matrix, const Ray& ray);
 
 }
 

+ 1 - 1
gameplay/src/Ray.inl

@@ -9,7 +9,7 @@ inline Ray& Ray::operator*=(const Matrix& matrix)
     return *this;
 }
 
-inline Ray operator*(const Matrix& matrix, const Ray& ray)
+inline const Ray operator*(const Matrix& matrix, const Ray& ray)
 {
     Ray r(ray);
     r.transform(matrix);

+ 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();

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff