Explorar o código

Merge pull request #52 from blackberry-gaming/next

Next
Sean Paul Taylor %!s(int64=13) %!d(string=hai) anos
pai
achega
d691ec5f34
Modificáronse 100 ficheiros con 4633 adicións e 1743 borrados
  1. 15 12
      .gitignore
  2. 2 4
      gameplay-encoder/gameplay-encoder.xcodeproj/project.pbxproj
  3. 1 0
      gameplay-encoder/src/Camera.h
  4. 1 0
      gameplay-encoder/src/DAEChannelTarget.h
  5. 1 0
      gameplay-encoder/src/DAESceneEncoder.h
  6. 1 0
      gameplay-encoder/src/Effect.h
  7. 55 4
      gameplay-encoder/src/FBXSceneEncoder.cpp
  8. 14 12
      gameplay-encoder/src/MeshSkin.cpp
  9. 2 0
      gameplay-template/template.bar-descriptor.xml
  10. 1 1
      gameplay/android/jni/Android.mk
  11. 6 2
      gameplay/gameplay.vcxproj
  12. 18 6
      gameplay/gameplay.vcxproj.filters
  13. 12 12
      gameplay/gameplay.xcodeproj/project.pbxproj
  14. BIN=BIN
      gameplay/res/logo_black.png
  15. BIN=BIN
      gameplay/res/logo_powered_black.png
  16. BIN=BIN
      gameplay/res/logo_powered_white.png
  17. BIN=BIN
      gameplay/res/logo_white.png
  18. BIN=BIN
      gameplay/res/textures/particle-default.png
  19. 50 2
      gameplay/src/Animation.cpp
  20. 44 9
      gameplay/src/Animation.h
  21. 2 2
      gameplay/src/AnimationClip.cpp
  22. 1 1
      gameplay/src/AnimationController.cpp
  23. 29 0
      gameplay/src/AnimationTarget.cpp
  24. 22 2
      gameplay/src/AnimationTarget.h
  25. 12 12
      gameplay/src/AudioBuffer.cpp
  26. 2 2
      gameplay/src/AudioController.cpp
  27. 1 1
      gameplay/src/AudioController.h
  28. 14 8
      gameplay/src/AudioListener.cpp
  29. 9 0
      gameplay/src/AudioListener.h
  30. 47 8
      gameplay/src/AudioSource.cpp
  31. 16 6
      gameplay/src/AudioSource.h
  32. 24 6
      gameplay/src/Base.h
  33. 7 0
      gameplay/src/BoundingBox.cpp
  34. 10 1
      gameplay/src/BoundingBox.h
  35. 31 33
      gameplay/src/Camera.cpp
  36. 17 7
      gameplay/src/Camera.h
  37. 67 40
      gameplay/src/CheckBox.cpp
  38. 7 5
      gameplay/src/CheckBox.h
  39. 5 15
      gameplay/src/Container.cpp
  40. 1 1
      gameplay/src/Container.h
  41. 609 72
      gameplay/src/Control.cpp
  42. 408 14
      gameplay/src/Control.h
  43. 148 1
      gameplay/src/DebugNew.cpp
  44. 14 0
      gameplay/src/DebugNew.h
  45. 1 1
      gameplay/src/FileSystem.cpp
  46. 15 8
      gameplay/src/Font.h
  47. 15 15
      gameplay/src/Form.cpp
  48. 6 3
      gameplay/src/Form.h
  49. 26 6
      gameplay/src/Game.cpp
  50. 75 1
      gameplay/src/Game.h
  51. 19 0
      gameplay/src/Game.inl
  52. 10 0
      gameplay/src/Joint.cpp
  53. 24 0
      gameplay/src/Joint.h
  54. 4 1
      gameplay/src/Label.cpp
  55. 26 0
      gameplay/src/Light.cpp
  56. 15 5
      gameplay/src/Light.h
  57. 19 0
      gameplay/src/Material.cpp
  58. 11 0
      gameplay/src/Material.h
  59. 80 2
      gameplay/src/MaterialParameter.cpp
  60. 11 2
      gameplay/src/MaterialParameter.h
  61. 1 1
      gameplay/src/MeshBatch.h
  62. 60 1
      gameplay/src/MeshSkin.cpp
  63. 29 0
      gameplay/src/MeshSkin.h
  64. 17 2
      gameplay/src/Model.cpp
  65. 12 2
      gameplay/src/Model.h
  66. 233 32
      gameplay/src/Node.cpp
  67. 259 62
      gameplay/src/Node.h
  68. 3 4
      gameplay/src/ParticleEmitter.cpp
  69. 3 3
      gameplay/src/ParticleEmitter.h
  70. 10 4
      gameplay/src/Pass.cpp
  71. 16 0
      gameplay/src/Pass.h
  72. 84 135
      gameplay/src/PhysicsCharacter.cpp
  73. 16 33
      gameplay/src/PhysicsCharacter.h
  74. 49 9
      gameplay/src/PhysicsCollisionObject.cpp
  75. 60 27
      gameplay/src/PhysicsCollisionObject.h
  76. 408 0
      gameplay/src/PhysicsCollisionShape.cpp
  77. 235 0
      gameplay/src/PhysicsCollisionShape.h
  78. 1 1
      gameplay/src/PhysicsConstraint.cpp
  79. 428 126
      gameplay/src/PhysicsController.cpp
  80. 24 59
      gameplay/src/PhysicsController.h
  81. 38 46
      gameplay/src/PhysicsGhostObject.cpp
  82. 16 20
      gameplay/src/PhysicsGhostObject.h
  83. 2 1
      gameplay/src/PhysicsMotionState.cpp
  84. 7 5
      gameplay/src/PhysicsMotionState.h
  85. 112 414
      gameplay/src/PhysicsRigidBody.cpp
  86. 153 193
      gameplay/src/PhysicsRigidBody.h
  87. 32 67
      gameplay/src/PhysicsRigidBody.inl
  88. 7 0
      gameplay/src/Platform.h
  89. 5 0
      gameplay/src/PlatformAndroid.cpp
  90. 101 94
      gameplay/src/PlatformMacOS.mm
  91. 8 1
      gameplay/src/PlatformQNX.cpp
  92. 6 0
      gameplay/src/PlatformWin32.cpp
  93. 7 0
      gameplay/src/PlatformiOS.mm
  94. 56 37
      gameplay/src/RadioButton.cpp
  95. 4 4
      gameplay/src/RadioButton.h
  96. 4 4
      gameplay/src/Rectangle.cpp
  97. 7 7
      gameplay/src/Rectangle.h
  98. 8 0
      gameplay/src/Ref.cpp
  99. 2 0
      gameplay/src/Ref.h
  100. 27 4
      gameplay/src/RenderState.cpp

+ 15 - 12
.gitignore

@@ -43,7 +43,8 @@ Thumbs.db
 /gameplay-samples/sample00-mesh/Device-Coverage
 /gameplay-samples/sample00-mesh/Device-Profile
 /gameplay-samples/sample00-mesh/Device-Release
-/gameplay-samples/sample00-mesh/res/shaders
+/gameplay-samples/sample00-mesh/res/shaders
+/gameplay-samples/sample00-mesh/res/logo_powered_white.png
 /gameplay-samples/sample00-mesh/sample00-mesh.xcodeproj/xcuserdata
 /gameplay-samples/sample01-longboard/Debug
 /gameplay-samples/sample01-longboard/DebugMem
@@ -55,7 +56,8 @@ Thumbs.db
 /gameplay-samples/sample01-longboard/Device-Coverage
 /gameplay-samples/sample01-longboard/Device-Profile
 /gameplay-samples/sample01-longboard/Device-Release
-/gameplay-samples/sample01-longboard/res/shaders
+/gameplay-samples/sample01-longboard/res/shaders
+/gameplay-samples/sample01-longboard/res/logo_powered_white.png
 /gameplay-samples/sample01-longboard/sample01-longboard.xcodeproj/xcuserdata
 /gameplay-samples/sample02-spaceship/Debug
 /gameplay-samples/sample02-spaceship/DebugMem
@@ -67,7 +69,8 @@ Thumbs.db
 /gameplay-samples/sample02-spaceship/Device-Coverage
 /gameplay-samples/sample02-spaceship/Device-Profile
 /gameplay-samples/sample02-spaceship/Device-Release
-/gameplay-samples/sample02-spaceship/res/shaders
+/gameplay-samples/sample02-spaceship/res/shaders
+/gameplay-samples/sample02-spaceship/res/logo_powered_white.png
 /gameplay-samples/sample02-spaceship/sample02-spaceship.xcodeproj/xcuserdata
 /gameplay-samples/sample03-character/Debug
 /gameplay-samples/sample03-character/DebugMem
@@ -79,11 +82,17 @@ Thumbs.db
 /gameplay-samples/sample03-character/Device-Coverage
 /gameplay-samples/sample03-character/Device-Profile
 /gameplay-samples/sample03-character/Device-Release
-/gameplay-samples/sample03-character/res/shaders
+/gameplay-samples/sample03-character/res/shaders
+/gameplay-samples/sample03-character/res/logo_powered_white.png
 /gameplay-samples/sample03-character/sample03-character.xcodeproj/xcuserdata
 
 /gameplay-android/obj
-/gameplay-android/NUL
+/gameplay-android/NUL
+/gameplay/android/NUL
+/gameplay/android/proguard.cfg
+/gameplay/android/local.properties
+/gameplay/android/project.properties
+/gameplay/android/obj
 /gameplay-samples/sample00-mesh/android/src
 /gameplay-samples/sample00-mesh/android/assets
 /gameplay-samples/sample00-mesh/android/bin
@@ -128,11 +137,5 @@ Thumbs.db
 /gameplay-samples/sample03-character/android/libs
 /gameplay-samples/sample03-character/android/obj
 /gameplay-samples/sample03-character/android/NUL
-/gameplay-samples/sample01-longboard/NUL
-
-/gameplay/android/NUL
-/gameplay/android/proguard.cfg
-/gameplay/android/local.properties
-/gameplay/android/project.properties
-/gameplay/android/obj
+/gameplay-samples/sample01-longboard/NUL
 

+ 2 - 4
gameplay-encoder/gameplay-encoder.xcodeproj/project.pbxproj

@@ -503,7 +503,7 @@
 		42475CF1147208A100610A6A /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+				ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
 				CLANG_CXX_LANGUAGE_STANDARD = "compiler-default";
 				CLANG_CXX_LIBRARY = "compiler-default";
 				GCC_C_LANGUAGE_STANDARD = "compiler-default";
@@ -524,7 +524,6 @@
 					"\"$(SRCROOT)/../external-deps/collada-dom/lib/macos\"",
 					"\"$(SRCROOT)/../external-deps/pcre/lib/macos\"",
 					"\"$(SRCROOT)/../../../Library/Developer/Xcode/DerivedData/gameplay-exiunaubxxjndaapmcqkaoeboiob/Build/Products/Debug\"",
-					"\"$(SYSTEM_APPS_DIR)/Autodesk/FBXSDK20122/lib/gcc4/ub\"",
 					"\"$(SRCROOT)/../external-deps/libpng/lib/macos\"",
 				);
 				MACH_O_TYPE = mh_execute;
@@ -536,7 +535,7 @@
 		42475CF2147208A100610A6A /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+				ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
 				CLANG_CXX_LANGUAGE_STANDARD = "compiler-default";
 				CLANG_CXX_LIBRARY = "compiler-default";
 				GCC_C_LANGUAGE_STANDARD = "compiler-default";
@@ -557,7 +556,6 @@
 					"\"$(SRCROOT)/../external-deps/collada-dom/lib/macos\"",
 					"\"$(SRCROOT)/../external-deps/pcre/lib/macos\"",
 					"\"$(SRCROOT)/../../../Library/Developer/Xcode/DerivedData/gameplay-exiunaubxxjndaapmcqkaoeboiob/Build/Products/Debug\"",
-					"\"$(SYSTEM_APPS_DIR)/Autodesk/FBXSDK20122/lib/gcc4/ub\"",
 					"\"$(SRCROOT)/../external-deps/libpng/lib/macos\"",
 				);
 				MACH_O_TYPE = mh_execute;

+ 1 - 0
gameplay-encoder/src/Camera.h

@@ -44,6 +44,7 @@ public:
     };
 
 private:
+    
     unsigned char _cameraType;
     float _fieldOfView;
     float _aspectRatio;

+ 1 - 0
gameplay-encoder/src/DAEChannelTarget.h

@@ -55,6 +55,7 @@ public:
     void getPropertyName(size_t index, std::string* str);
 
 private:
+    
     /**
      * The channel element.
      */

+ 1 - 0
gameplay-encoder/src/DAESceneEncoder.h

@@ -190,6 +190,7 @@ private:
     static int getVertexUsageType(const std::string& semantic);
     
 private:
+    
     DAE* _collada;        // Collada datastore in memory to read from.
     domCOLLADA* _dom;
     FILE* file;        // Output file to write to.

+ 1 - 0
gameplay-encoder/src/Effect.h

@@ -29,6 +29,7 @@ public:
     virtual void writeText(FILE* file);
 
 private:
+    
     std::string _vertexShader;
     std::string _fragmentShader;
 };

+ 55 - 4
gameplay-encoder/src/FBXSceneEncoder.cpp

@@ -249,7 +249,13 @@ void FBXSceneEncoder::loadScene(KFbxScene* fbxScene)
     // Find the ambient light of the scene
     KFbxColor ambientColor = fbxScene->GetGlobalSettings().GetAmbientColor();
     scene->setAmbientColor((float)ambientColor.mRed, (float)ambientColor.mGreen, (float)ambientColor.mBlue);
-
+    
+    // Assign the first camera node (if there is one) in the scene as the active camera
+    // This ensures that if there's a camera in the scene that it is assigned as the 
+    // active camera.
+    // TODO: add logic to find the "active" camera node in the fbxScene
+    scene->setActiveCameraNode(scene->getFirstCameraNode());
+    
     _gamePlayFile.addScene(scene);
 }
 
@@ -646,20 +652,65 @@ void FBXSceneEncoder::loadLight(KFbxNode* fbxNode, Node* node)
     switch (fbxLight->LightType.Get())
     {
     case KFbxLight::ePOINT:
-        light->setPointLight();
-        // TODO: range
+    {
+        KFbxLight::EDecayType decayType = fbxLight->DecayType.Get();
+        switch (decayType)
+        {
+        case KFbxLight::eNONE:
+            // No decay. Can assume we have an ambient light, because ambient lights in the scene are 
+            // converted to point lights with no decay when exporting to FBX.
+            light->setAmbientLight();
+            break;
+        case KFbxLight::eLINEAR:
+            light->setPointLight();
+            light->setLinearAttenuation((float)fbxLight->DecayStart.Get());
+            break;
+        case KFbxLight::eQUADRATIC:
+            light->setPointLight();
+            light->setQuadraticAttenuation((float)fbxLight->DecayStart.Get());
+            break;
+        case KFbxLight::eCUBIC:
+        default:
+            // Not supported..
+            break;
+        }
         break;
+    }
     case KFbxLight::eDIRECTIONAL:
+    {
         light->setDirectionalLight();
         break;
+    }
     case KFbxLight::eSPOT:
+    {
         light->setSpotLight();
-        // TODO: range and angles
+
+        KFbxLight::EDecayType decayType = fbxLight->DecayType.Get();
+        switch (decayType)
+        {
+        case KFbxLight::eNONE:
+            // No decay.
+            break;
+        case KFbxLight::eLINEAR:
+            light->setLinearAttenuation((float)fbxLight->DecayStart.Get());
+            break;  
+        case KFbxLight::eQUADRATIC:
+            light->setQuadraticAttenuation((float)fbxLight->DecayStart.Get());
+            break;
+        case KFbxLight::eCUBIC:
+            // Not supported..
+            break;
+        }
+
+        light->setFalloffAngle(MATH_DEG_TO_RAD((float)fbxLight->ConeAngle.Get())); // fall off angle
         break;
+    }
     default:
+    {
         warning("Unknown light type in node.");
         return;
     }
+    }
 
     _gamePlayFile.addLight(light);
     node->setLight(light);

+ 14 - 12
gameplay-encoder/src/MeshSkin.cpp

@@ -313,26 +313,28 @@ void MeshSkin::computeBounds()
         if (duration > maxDuration)
             maxDuration = duration;
 
-        // Set curve points
-        float* keyValuesPtr = keyValues;
-        for (unsigned int j = 0; j < keyCount; ++j)
+        if (duration > 0.0f)
         {
-            // Store time normalized, between 0-1
-            float t = (keyTimes[j] - startTime) / duration;
+            // Set curve points
+            float* keyValuesPtr = keyValues;
+            for (unsigned int j = 0; j < keyCount; ++j)
+            {
+                // Store time normalized, between 0-1
+                float t = (keyTimes[j] - startTime) / duration;
 
-            // Set the curve point
-            // TODO: Handle other interpolation types
-            curve->setPoint(j, t, keyValuesPtr, gameplay::Curve::LINEAR);
+                // Set the curve point
+                // TODO: Handle other interpolation types
+                curve->setPoint(j, t, keyValuesPtr, gameplay::Curve::LINEAR);
 
-            // Move to the next point on the curve
-            keyValuesPtr += curve->getComponentCount();
+                // Move to the next point on the curve
+                keyValuesPtr += curve->getComponentCount();
+            }
+            curves.push_back(curve);
         }
 
         delete[] keyValues;
         keyValues = NULL;
 
-        curves.push_back(curve);
-
         DEBUGPRINT_VARG("> %d%%\r", (int)((float)(i+1) / (float)channelCount * 100.0f));
     }
     DEBUGPRINT("\n");

+ 2 - 0
gameplay-template/template.bar-descriptor.xml

@@ -39,6 +39,8 @@
 
     <!--  Unique author ID assigned by signing authority. Required if using debug tokens. -->
     <!-- <authorId>gYAAgPkLP1tZlyYP1wiMaRFFNMw</authorId> -->
+    
+    <platformVersion>2.0.0.7971</platformVersion>
 
     <initialWindow>
         <aspectRatio>landscape</aspectRatio>

+ 1 - 1
gameplay/android/jni/Android.mk

@@ -16,7 +16,7 @@ LOCAL_PATH := $(call my-dir)/../../src
 
 include $(CLEAR_VARS)
 LOCAL_MODULE    := libgameplay
-LOCAL_SRC_FILES := Animation.cpp DepthStencilTarget.cpp MeshBatch.cpp PhysicsRigidBody.cpp SceneLoader.cpp AnimationClip.cpp Effect.cpp MeshPart.cpp PhysicsSocketConstraint.cpp SpriteBatch.cpp AnimationController.cpp FileSystem.cpp MeshSkin.cpp PhysicsSpringConstraint.cpp Technique.cpp AnimationTarget.cpp Font.cpp Model.cpp Plane.cpp Texture.cpp AnimationValue.cpp FrameBuffer.cpp Node.cpp PlatformAndroid.cpp PlatformQNX.cpp AudioBuffer.cpp Frustum.cpp Package.cpp PlatformWin32.cpp Transform.cpp AudioController.cpp Game.cpp ParticleEmitter.cpp Properties.cpp Vector2.cpp AudioListener.cpp Image.cpp Pass.cpp Quaternion.cpp Vector3.cpp AudioSource.cpp Joint.cpp PhysicsConstraint.cpp Ray.cpp Vector4.cpp BoundingBox.cpp Light.cpp PhysicsController.cpp Rectangle.cpp VertexAttributeBinding.cpp BoundingSphere.cpp Material.cpp PhysicsFixedConstraint.cpp Ref.cpp VertexFormat.cpp Camera.cpp MaterialParameter.cpp PhysicsGenericConstraint.cpp RenderState.cpp Viewport.cpp Curve.cpp Matrix.cpp PhysicsHingeConstraint.cpp RenderTarget.cpp DebugNew.cpp Mesh.cpp PhysicsMotionState.cpp Scene.cpp
+LOCAL_SRC_FILES := AbsoluteLayout.cpp Animation.cpp AnimationClip.cpp AnimationController.cpp AnimationTarget.cpp AnimationValue.cpp AudioBuffer.cpp AudioController.cpp AudioListener.cpp AudioSource.cpp BoundingBox.cpp BoundingSphere.cpp Button.cpp Camera.cpp CheckBox.cpp Container.cpp Control.cpp Curve.cpp DebugNew.cpp DepthStencilTarget.cpp Effect.cpp FileSystem.cpp Font.cpp Form.cpp FrameBuffer.cpp Frustum.cpp Game.cpp gameplay-main-android.cpp gameplay-main-qnx.cpp gameplay-main-win32.cpp Image.cpp Joint.cpp Label.cpp Light.cpp Material.cpp MaterialParameter.cpp Matrix.cpp Mesh.cpp MeshBatch.cpp MeshPart.cpp MeshSkin.cpp Model.cpp Node.cpp Package.cpp ParticleEmitter.cpp Pass.cpp PhysicsCharacter.cpp PhysicsCollisionObject.cpp PhysicsCollisionShape.cpp PhysicsConstraint.cpp PhysicsController.cpp PhysicsFixedConstraint.cpp PhysicsGenericConstraint.cpp PhysicsGhostObject.cpp PhysicsHingeConstraint.cpp PhysicsMotionState.cpp PhysicsRigidBody.cpp PhysicsSocketConstraint.cpp PhysicsSpringConstraint.cpp Plane.cpp PlatformAndroid.cpp PlatformQNX.cpp PlatformWin32.cpp Properties.cpp Quaternion.cpp RadioButton.cpp Ray.cpp Rectangle.cpp Ref.cpp RenderState.cpp RenderTarget.cpp Scene.cpp SceneLoader.cpp Slider.cpp SpriteBatch.cpp Technique.cpp TextBox.cpp Texture.cpp Theme.cpp Transform.cpp Vector2.cpp Vector3.cpp Vector4.cpp VertexAttributeBinding.cpp VertexFormat.cpp VerticalLayout.cpp
 LOCAL_CFLAGS := -D__ANDROID__ -I"../../external-deps/bullet/include" -I"../../external-deps/libpng/include"
 LOCAL_STATIC_LIBRARIES := android_native_app_glue
 

+ 6 - 2
gameplay/gameplay.vcxproj

@@ -63,6 +63,7 @@
     <ClCompile Include="src\ParticleEmitter.cpp" />
     <ClCompile Include="src\PhysicsCharacter.cpp" />
     <ClCompile Include="src\PhysicsCollisionObject.cpp" />
+    <ClCompile Include="src\PhysicsCollisionShape.cpp" />
     <ClCompile Include="src\PhysicsConstraint.cpp" />
     <ClCompile Include="src\PhysicsController.cpp" />
     <ClCompile Include="src\PhysicsFixedConstraint.cpp" />
@@ -100,7 +101,6 @@
     <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" />
@@ -153,6 +153,7 @@
     <ClInclude Include="src\ParticleEmitter.h" />
     <ClInclude Include="src\PhysicsCharacter.h" />
     <ClInclude Include="src\PhysicsCollisionObject.h" />
+    <ClInclude Include="src\PhysicsCollisionShape.h" />
     <ClInclude Include="src\PhysicsConstraint.h" />
     <ClInclude Include="src\PhysicsController.h" />
     <ClInclude Include="src\PhysicsFixedConstraint.h" />
@@ -190,9 +191,12 @@
     <ClInclude Include="src\VertexAttributeBinding.h" />
     <ClInclude Include="src\VertexFormat.h" />
     <ClInclude Include="src\VerticalLayout.h" />
-    <ClInclude Include="src\Viewport.h" />
   </ItemGroup>
   <ItemGroup>
+    <None Include="res\logo_black.png" />
+    <None Include="res\logo_powered_black.png" />
+    <None Include="res\logo_powered_white.png" />
+    <None Include="res\logo_white.png" />
     <None Include="res\shaders\bumped-specular.fsh" />
     <None Include="res\shaders\bumped-specular.vsh" />
     <None Include="res\shaders\bumped.fsh" />

+ 18 - 6
gameplay/gameplay.vcxproj.filters

@@ -126,9 +126,6 @@
     <ClCompile Include="src\VertexFormat.cpp">
       <Filter>src</Filter>
     </ClCompile>
-    <ClCompile Include="src\Viewport.cpp">
-      <Filter>src</Filter>
-    </ClCompile>
     <ClCompile Include="src\AudioController.cpp">
       <Filter>src</Filter>
     </ClCompile>
@@ -273,6 +270,9 @@
     <ClCompile Include="src\PhysicsGhostObject.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\PhysicsCollisionShape.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -392,9 +392,6 @@
     <ClInclude Include="src\VertexFormat.h">
       <Filter>src</Filter>
     </ClInclude>
-    <ClInclude Include="src\Viewport.h">
-      <Filter>src</Filter>
-    </ClInclude>
     <ClInclude Include="src\AudioController.h">
       <Filter>src</Filter>
     </ClInclude>
@@ -539,6 +536,9 @@
     <ClInclude Include="src\PhysicsGhostObject.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\PhysicsCollisionShape.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\shaders\bumped-specular.vsh">
@@ -628,6 +628,18 @@
     <None Include="src\PlatformiOS.mm">
       <Filter>src</Filter>
     </None>
+    <None Include="res\logo_black.png">
+      <Filter>res</Filter>
+    </None>
+    <None Include="res\logo_powered_black.png">
+      <Filter>res</Filter>
+    </None>
+    <None Include="res\logo_powered_white.png">
+      <Filter>res</Filter>
+    </None>
+    <None Include="res\logo_white.png">
+      <Filter>res</Filter>
+    </None>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\PhysicsFixedConstraint.inl">

+ 12 - 12
gameplay/gameplay.xcodeproj/project.pbxproj

@@ -14,6 +14,10 @@
 		4208DEEC14A407B900D3C511 /* Keyboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 4208DEEB14A407B900D3C511 /* Keyboard.h */; };
 		4208DEEE14A407D500D3C511 /* Touch.h in Headers */ = {isa = PBXBuildFile; fileRef = 4208DEED14A407D500D3C511 /* Touch.h */; };
 		4234D99E14686C52003031B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4234D99D14686C52003031B3 /* Cocoa.framework */; };
+		42554EA1152BC35C000ED910 /* PhysicsCollisionShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42554E9F152BC35C000ED910 /* PhysicsCollisionShape.cpp */; };
+		42554EA2152BC35C000ED910 /* PhysicsCollisionShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42554E9F152BC35C000ED910 /* PhysicsCollisionShape.cpp */; };
+		42554EA3152BC35C000ED910 /* PhysicsCollisionShape.h in Headers */ = {isa = PBXBuildFile; fileRef = 42554EA0152BC35C000ED910 /* PhysicsCollisionShape.h */; };
+		42554EA4152BC35C000ED910 /* PhysicsCollisionShape.h in Headers */ = {isa = PBXBuildFile; fileRef = 42554EA0152BC35C000ED910 /* PhysicsCollisionShape.h */; };
 		428390991489D6E800E2B2F5 /* SceneLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 428390971489D6E800E2B2F5 /* SceneLoader.cpp */; };
 		4283909A1489D6E800E2B2F5 /* SceneLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 428390981489D6E800E2B2F5 /* SceneLoader.h */; };
 		42CCD556146EC1EB00353661 /* libpng.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 42CCD555146EC1EB00353661 /* libpng.a */; };
@@ -149,8 +153,6 @@
 		42CD0EC8147D8FF60000361E /* VertexAttributeBinding.h in Headers */ = {isa = PBXBuildFile; fileRef = 42CD0E41147D8FF50000361E /* VertexAttributeBinding.h */; };
 		42CD0EC9147D8FF60000361E /* VertexFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0E42147D8FF50000361E /* VertexFormat.cpp */; };
 		42CD0ECA147D8FF60000361E /* VertexFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 42CD0E43147D8FF50000361E /* VertexFormat.h */; };
-		42CD0ECB147D8FF60000361E /* Viewport.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0E44147D8FF50000361E /* Viewport.cpp */; };
-		42CD0ECC147D8FF60000361E /* Viewport.h in Headers */ = {isa = PBXBuildFile; fileRef = 42CD0E45147D8FF50000361E /* Viewport.h */; };
 		5B04C52D14BFCFE100EB0071 /* Animation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0DB1147D8FF50000361E /* Animation.cpp */; };
 		5B04C52E14BFCFE100EB0071 /* AnimationClip.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0DB3147D8FF50000361E /* AnimationClip.cpp */; };
 		5B04C52F14BFCFE100EB0071 /* AnimationController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0DB5147D8FF50000361E /* AnimationController.cpp */; };
@@ -212,7 +214,6 @@
 		5B04C56D14BFCFE100EB0071 /* Vector4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0E3D147D8FF50000361E /* Vector4.cpp */; };
 		5B04C56E14BFCFE100EB0071 /* VertexAttributeBinding.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0E40147D8FF50000361E /* VertexAttributeBinding.cpp */; };
 		5B04C56F14BFCFE100EB0071 /* VertexFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0E42147D8FF50000361E /* VertexFormat.cpp */; };
-		5B04C57014BFCFE100EB0071 /* Viewport.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42CD0E44147D8FF50000361E /* Viewport.cpp */; };
 		5B04C57114BFCFE100EB0071 /* SceneLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 428390971489D6E800E2B2F5 /* SceneLoader.cpp */; };
 		5B04C57214BFCFE100EB0071 /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4208DEE614A4079F00D3C511 /* Image.cpp */; };
 		5B04C57314BFCFE100EB0071 /* MeshBatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4201818D14A41B18008C3F56 /* MeshBatch.cpp */; };
@@ -286,7 +287,6 @@
 		5B04C5BE14BFCFE100EB0071 /* Vector4.h in Headers */ = {isa = PBXBuildFile; fileRef = 42CD0E3E147D8FF50000361E /* Vector4.h */; };
 		5B04C5BF14BFCFE100EB0071 /* VertexAttributeBinding.h in Headers */ = {isa = PBXBuildFile; fileRef = 42CD0E41147D8FF50000361E /* VertexAttributeBinding.h */; };
 		5B04C5C014BFCFE100EB0071 /* VertexFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 42CD0E43147D8FF50000361E /* VertexFormat.h */; };
-		5B04C5C114BFCFE100EB0071 /* Viewport.h in Headers */ = {isa = PBXBuildFile; fileRef = 42CD0E45147D8FF50000361E /* Viewport.h */; };
 		5B04C5C214BFCFE100EB0071 /* SceneLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 428390981489D6E800E2B2F5 /* SceneLoader.h */; };
 		5B04C5C314BFCFE100EB0071 /* Image.h in Headers */ = {isa = PBXBuildFile; fileRef = 4208DEE714A4079F00D3C511 /* Image.h */; };
 		5B04C5C414BFCFE100EB0071 /* Keyboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 4208DEEB14A407B900D3C511 /* Keyboard.h */; };
@@ -387,6 +387,8 @@
 		4208DEED14A407D500D3C511 /* Touch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Touch.h; path = src/Touch.h; sourceTree = SOURCE_ROOT; };
 		4234D99A14686C52003031B3 /* libgameplay.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libgameplay.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		4234D99D14686C52003031B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
+		42554E9F152BC35C000ED910 /* PhysicsCollisionShape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PhysicsCollisionShape.cpp; path = src/PhysicsCollisionShape.cpp; sourceTree = SOURCE_ROOT; };
+		42554EA0152BC35C000ED910 /* PhysicsCollisionShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PhysicsCollisionShape.h; path = src/PhysicsCollisionShape.h; sourceTree = SOURCE_ROOT; };
 		428390971489D6E800E2B2F5 /* SceneLoader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SceneLoader.cpp; path = src/SceneLoader.cpp; sourceTree = SOURCE_ROOT; };
 		428390981489D6E800E2B2F5 /* SceneLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SceneLoader.h; path = src/SceneLoader.h; sourceTree = SOURCE_ROOT; };
 		42C932AF14919FD10098216A /* Game.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Game.inl; path = src/Game.inl; sourceTree = SOURCE_ROOT; };
@@ -541,8 +543,6 @@
 		42CD0E41147D8FF50000361E /* VertexAttributeBinding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VertexAttributeBinding.h; path = src/VertexAttributeBinding.h; sourceTree = SOURCE_ROOT; };
 		42CD0E42147D8FF50000361E /* VertexFormat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VertexFormat.cpp; path = src/VertexFormat.cpp; sourceTree = SOURCE_ROOT; };
 		42CD0E43147D8FF50000361E /* VertexFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VertexFormat.h; path = src/VertexFormat.h; sourceTree = SOURCE_ROOT; };
-		42CD0E44147D8FF50000361E /* Viewport.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Viewport.cpp; path = src/Viewport.cpp; sourceTree = SOURCE_ROOT; };
-		42CD0E45147D8FF50000361E /* Viewport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Viewport.h; path = src/Viewport.h; sourceTree = SOURCE_ROOT; };
 		5B04C5CA14BFCFE100EB0071 /* libgameplay.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libgameplay.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		5B04C5CB14BFD48500EB0071 /* gameplay-main-ios.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "gameplay-main-ios.mm"; path = "src/gameplay-main-ios.mm"; sourceTree = SOURCE_ROOT; };
 		5B04C5CC14BFD48500EB0071 /* PlatformiOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = PlatformiOS.mm; path = src/PlatformiOS.mm; sourceTree = SOURCE_ROOT; };
@@ -784,6 +784,8 @@
 				5BD5266C150F8257004C9099 /* PhysicsCharacter.h */,
 				5BD5266D150F8257004C9099 /* PhysicsCollisionObject.cpp */,
 				5BD5266E150F8258004C9099 /* PhysicsCollisionObject.h */,
+				42554E9F152BC35C000ED910 /* PhysicsCollisionShape.cpp */,
+				42554EA0152BC35C000ED910 /* PhysicsCollisionShape.h */,
 				42CD0DFF147D8FF50000361E /* PhysicsConstraint.cpp */,
 				42CD0E00147D8FF50000361E /* PhysicsConstraint.h */,
 				42CD0E01147D8FF50000361E /* PhysicsConstraint.inl */,
@@ -868,8 +870,6 @@
 				42CD0E43147D8FF50000361E /* VertexFormat.h */,
 				5BD5264D150F822A004C9099 /* VerticalLayout.cpp */,
 				5BD5264E150F822A004C9099 /* VerticalLayout.h */,
-				42CD0E44147D8FF50000361E /* Viewport.cpp */,
-				42CD0E45147D8FF50000361E /* Viewport.h */,
 			);
 			name = src;
 			path = gameplay;
@@ -1019,7 +1019,6 @@
 				42CD0EC6147D8FF60000361E /* Vector4.h in Headers */,
 				42CD0EC8147D8FF60000361E /* VertexAttributeBinding.h in Headers */,
 				42CD0ECA147D8FF60000361E /* VertexFormat.h in Headers */,
-				42CD0ECC147D8FF60000361E /* Viewport.h in Headers */,
 				4283909A1489D6E800E2B2F5 /* SceneLoader.h in Headers */,
 				4208DEEA14A4079F00D3C511 /* Image.h in Headers */,
 				4208DEEC14A407B900D3C511 /* Keyboard.h in Headers */,
@@ -1044,6 +1043,7 @@
 				5BD52671150F8258004C9099 /* PhysicsCharacter.h in Headers */,
 				5BD52675150F8258004C9099 /* PhysicsCollisionObject.h in Headers */,
 				5BBE14401513E400003FB362 /* PhysicsGhostObject.h in Headers */,
+				42554EA3152BC35C000ED910 /* PhysicsCollisionShape.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1115,7 +1115,6 @@
 				5B04C5BE14BFCFE100EB0071 /* Vector4.h in Headers */,
 				5B04C5BF14BFCFE100EB0071 /* VertexAttributeBinding.h in Headers */,
 				5B04C5C014BFCFE100EB0071 /* VertexFormat.h in Headers */,
-				5B04C5C114BFCFE100EB0071 /* Viewport.h in Headers */,
 				5B04C5C214BFCFE100EB0071 /* SceneLoader.h in Headers */,
 				5B04C5C314BFCFE100EB0071 /* Image.h in Headers */,
 				5B04C5C414BFCFE100EB0071 /* Keyboard.h in Headers */,
@@ -1139,6 +1138,7 @@
 				5BC4E756150F843D00CBE1C0 /* Theme.h in Headers */,
 				5BC4E758150F843D00CBE1C0 /* VerticalLayout.h in Headers */,
 				5BBE14411513E400003FB362 /* PhysicsGhostObject.h in Headers */,
+				42554EA4152BC35C000ED910 /* PhysicsCollisionShape.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1273,7 +1273,6 @@
 				42CD0EC5147D8FF60000361E /* Vector4.cpp in Sources */,
 				42CD0EC7147D8FF60000361E /* VertexAttributeBinding.cpp in Sources */,
 				42CD0EC9147D8FF60000361E /* VertexFormat.cpp in Sources */,
-				42CD0ECB147D8FF60000361E /* Viewport.cpp in Sources */,
 				428390991489D6E800E2B2F5 /* SceneLoader.cpp in Sources */,
 				4208DEE914A4079F00D3C511 /* Image.cpp in Sources */,
 				4201819014A41B18008C3F56 /* MeshBatch.cpp in Sources */,
@@ -1292,6 +1291,7 @@
 				5BD5266F150F8258004C9099 /* PhysicsCharacter.cpp in Sources */,
 				5BD52673150F8258004C9099 /* PhysicsCollisionObject.cpp in Sources */,
 				5BBE143E1513E400003FB362 /* PhysicsGhostObject.cpp in Sources */,
+				42554EA1152BC35C000ED910 /* PhysicsCollisionShape.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1360,7 +1360,6 @@
 				5B04C56D14BFCFE100EB0071 /* Vector4.cpp in Sources */,
 				5B04C56E14BFCFE100EB0071 /* VertexAttributeBinding.cpp in Sources */,
 				5B04C56F14BFCFE100EB0071 /* VertexFormat.cpp in Sources */,
-				5B04C57014BFCFE100EB0071 /* Viewport.cpp in Sources */,
 				5B04C57114BFCFE100EB0071 /* SceneLoader.cpp in Sources */,
 				5B04C57214BFCFE100EB0071 /* Image.cpp in Sources */,
 				5B04C57314BFCFE100EB0071 /* MeshBatch.cpp in Sources */,
@@ -1381,6 +1380,7 @@
 				5BC4E755150F843D00CBE1C0 /* Theme.cpp in Sources */,
 				5BC4E757150F843D00CBE1C0 /* VerticalLayout.cpp in Sources */,
 				5BBE143F1513E400003FB362 /* PhysicsGhostObject.cpp in Sources */,
+				42554EA2152BC35C000ED910 /* PhysicsCollisionShape.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

BIN=BIN
gameplay/res/logo_black.png


BIN=BIN
gameplay/res/logo_powered_black.png


BIN=BIN
gameplay/res/logo_powered_white.png


BIN=BIN
gameplay/res/logo_white.png


BIN=BIN
gameplay/res/textures/particle-default.png


+ 50 - 2
gameplay/src/Animation.cpp

@@ -27,6 +27,11 @@ Animation::Animation(const char* id, AnimationTarget* target, int propertyId, un
     createChannel(target, propertyId, keyCount, keyTimes, keyValues, keyInValue, keyOutValue, type);
 }
 
+Animation::Animation(const char* id)
+    : _controller(Game::getInstance()->getAnimationController()), _id(id), _duration(0), _defaultClip(NULL), _clips(NULL)
+{
+}
+
 Animation::~Animation()
 {
     if (_defaultClip)
@@ -54,8 +59,9 @@ Animation::~Animation()
 }
 
 Animation::Channel::Channel(Animation* animation, AnimationTarget* target, int propertyId, Curve* curve, unsigned long duration)
-    : _animation(animation), _target(target), _propertyId(propertyId), _curve(curve), _duration(duration)
+    : _animation(animation), _target(target), _propertyId(propertyId), _duration(duration)
 {
+    _curveRef = Animation::CurveRef::create(curve);
     // get property component count, and ensure the property exists on the AnimationTarget by getting the property component count.
     assert(_target->getAnimationPropertyComponentCount(propertyId));
 
@@ -64,12 +70,47 @@ Animation::Channel::Channel(Animation* animation, AnimationTarget* target, int p
     _target->addChannel(this);
 }
 
+Animation::Channel::Channel(const Channel& copy, Animation* animation, AnimationTarget* target)
+    : _animation(animation), _target(target), _propertyId(copy._propertyId), _duration(copy._duration)
+{
+    _curveRef = copy._curveRef;
+    _curveRef->addRef();
+
+    _animation->addRef();
+    _target->addChannel(this);
+}
+
 Animation::Channel::~Channel()
 {
-    SAFE_DELETE(_curve);
+    SAFE_RELEASE(_curveRef);
     SAFE_RELEASE(_animation);
 }
 
+Curve* Animation::Channel::getCurve() const
+{
+    return _curveRef->getCurve();
+}
+
+Animation::CurveRef* Animation::CurveRef::create(Curve* curve)
+{
+    return new CurveRef(curve);
+}
+
+Curve* Animation::CurveRef::getCurve() const
+{
+    return _curve;
+}
+
+Animation::CurveRef::CurveRef(Curve* curve)
+    : _curve(curve)
+{
+}
+
+Animation::CurveRef::~CurveRef()
+{
+    SAFE_DELETE(_curve);
+}
+
 const char* Animation::getId() const
 {
     return _id.c_str();
@@ -372,4 +413,11 @@ void Animation::setTransformRotationOffset(Curve* curve, unsigned int propertyId
     return;
 }
 
+Animation* Animation::clone()
+{
+    Animation* animation = new Animation(getId());
+    _controller->addAnimation(animation);
+    return animation;
+}
+
 }

+ 44 - 9
gameplay/src/Animation.h

@@ -3,6 +3,7 @@
 
 #include "Ref.h"
 #include "Properties.h"
+#include "Curve.h"
 
 namespace gameplay
 {
@@ -10,7 +11,6 @@ namespace gameplay
 class AnimationTarget;
 class AnimationController;
 class AnimationClip;
-class Curve;
 
 /**
  * Defines a generic property animation.
@@ -96,6 +96,26 @@ public:
 
 private:
 
+    /**
+     * Defines a reference counted Curve wrapper.
+     * 
+     * Multiple channels can share the same Curve.
+     */
+    class CurveRef : public Ref
+    {
+    public:
+        static CurveRef* create(Curve* curve);
+        Curve* getCurve() const;
+
+    private:
+        CurveRef(Curve* curve);
+        CurveRef(const CurveRef&); // Hidden copy constructor.
+        ~CurveRef();
+        CurveRef& operator=(const CurveRef&); // Hidden copy assignment operator.
+
+        Curve* _curve;
+    };
+
     /**
      * Defines a channel which holds the target, target property, curve values, and duration.
      *
@@ -112,23 +132,21 @@ private:
     private:
 
         Channel(Animation* animation, AnimationTarget* target, int propertyId, Curve* curve, unsigned long duration);
-        Channel(const Channel& copy);
+        Channel(const Channel& copy, Animation* animation, AnimationTarget* target);
+        Channel(const Channel&); // Hidden copy constructor.
         ~Channel();
+        Channel& operator=(const Channel&); // Hidden copy assignment operator.
+        Curve* getCurve() const;
 
         Animation* _animation;                // Reference to the animation this channel belongs to.
         AnimationTarget* _target;             // The target of this channel.
         int _propertyId;                      // The target property this channel targets.
-        Curve* _curve;                        // The curve used to represent the animation data.
+        CurveRef* _curveRef;                  // The curve used to represent the animation data.
         unsigned long _duration;              // The length of the animation (in milliseconds).
     };
 
     /**
-     * Constructor.
-     */
-    Animation();
-
-    /**
-     * Constructor.
+     * Hidden copy constructor.
      */
     Animation(const Animation& copy);
 
@@ -142,11 +160,21 @@ private:
      */
     Animation(const char* id, AnimationTarget* target, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, unsigned int type);
 
+    /**
+     * Constructor.
+     */
+    Animation(const char* id);
+
     /**
      * Destructor.
      */
     ~Animation();
 
+    /**
+     * Hidden copy assignment operator.
+     */
+    Animation& operator=(const Animation&);
+
     /**
      * Creates the default clip.
      */
@@ -191,6 +219,13 @@ private:
      * Sets the rotation offset in a Curve representing a Transform's animation data.
      */
     void setTransformRotationOffset(Curve* curve, unsigned int propertyId);
+
+    /**
+     * Clones this animation.
+     * 
+     * @return The newly created animation.
+     */
+    Animation* clone();
     
     AnimationController* _controller;       // The AnimationController that this Animation will run on.
     std::string _id;                        // The Animation's ID.

+ 2 - 2
gameplay/src/AnimationClip.cpp

@@ -19,7 +19,7 @@ AnimationClip::AnimationClip(const char* id, Animation* animation, unsigned long
     unsigned int channelCount = _animation->_channels.size();    
     for (unsigned int i = 0; i < channelCount; i++)
     {
-        _values.push_back(new AnimationValue(_animation->_channels[i]->_curve->getComponentCount()));
+        _values.push_back(new AnimationValue(_animation->_channels[i]->getCurve()->getComponentCount()));
     }
 }
 
@@ -446,7 +446,7 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
             activeTargets->push_front(target);
 
         // Evaluate the point on Curve
-        channel->_curve->evaluate(percentComplete, value->_value);
+        channel->getCurve()->evaluate(percentComplete, value->_value);
         // Set the animation value on the target property.
         target->setAnimationPropertyValue(channel->_propertyId, value, _blendWeight);
     }

+ 1 - 1
gameplay/src/AnimationController.cpp

@@ -229,7 +229,7 @@ Animation* AnimationController::createAnimation(const char* id, AnimationTarget*
     
     const char* keyOutStr = animationProperties->getString("keyOut");
     float* keyOut = NULL;
-    if(keyOutStr)
+    if (keyOutStr)
     {   
         keyOut = new float[components];
         startOffset = 0;

+ 29 - 0
gameplay/src/AnimationTarget.cpp

@@ -2,6 +2,7 @@
 #include "AnimationTarget.h"
 #include "Animation.h"
 #include "Game.h"
+#include "Node.h"
 
 namespace gameplay
 {
@@ -118,6 +119,34 @@ void AnimationTarget::deleteChannel(Animation::Channel* channel)
     }
 }
 
+void AnimationTarget::cloneInto(AnimationTarget* target, NodeCloneContext &context) const
+{
+    if (_animationChannels)
+    {
+        for (std::vector<Animation::Channel*>::const_iterator it = _animationChannels->begin(); it != _animationChannels->end(); ++it)
+        {
+            Animation::Channel* channel = *it;
+            assert(channel->_animation);
+
+            bool animationCloned = false;
+
+            // Don't clone the Animaton if it is already in the clone context.
+            Animation* animation = context.findClonedAnimation(channel->_animation);
+            if (animation == NULL)
+            {
+                animation = channel->_animation->clone();
+                animationCloned = true;
+            }
+            assert(animation);
+
+            context.registerClonedAnimation(channel->_animation, animation);
+            
+            Animation::Channel* channelCopy = new Animation::Channel(*channel, animation, target);
+            animation->addChannel(channelCopy);
+        }
+    }
+}
+
 }
 
 

+ 22 - 2
gameplay/src/AnimationTarget.h

@@ -9,6 +9,7 @@ namespace gameplay
 
 class Animation;
 class AnimationValue;
+class NodeCloneContext;
 
 /**
  * Defines an interface allowing animation to target
@@ -44,6 +45,7 @@ public:
      * 
      * @param propertyId The ID of the property on the AnimationTarget to set the animation property value on.
      * @param value The container to set the animation property value in.
+     * @param blendWeight The blend weight.
      */
     virtual void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f) = 0;
 
@@ -65,13 +67,31 @@ protected:
      */
     virtual ~AnimationTarget();
 
-    void addChannel(Animation::Channel* animation);
+    /**
+     * Adds the given animation channel to this animation target.
+     * 
+     * @param channel The animation channel to add.
+     */
+    void addChannel(Animation::Channel* channel);
 
+    /**
+     * Deletes the given animation channel from this animation target.
+     * 
+     * @param channel The animation channel to delete.
+     */
     void deleteChannel(Animation::Channel* channel);
 
+    /**
+     * Copies data from this animation target into the given target for the purpose of cloning.
+     * 
+     * @param target The target to copy into.
+     * @param context The clone context.
+     */
+    void cloneInto(AnimationTarget* target, NodeCloneContext &context) const;
+
     TargetType _targetType;             // The type of target this is.
 
-    char _animationPropertyBitFlag;     // Bit flag used to indicate which properties on the AnimationTarget are currently animating.
+    unsigned char _animationPropertyBitFlag;     // Bit flag used to indicate which properties on the AnimationTarget are currently animating.
 
 private:
 

+ 12 - 12
gameplay/src/AudioBuffer.cpp

@@ -132,23 +132,23 @@ cleanup:
     return NULL;
 #else
     // Get the file header in order to determine the type.
-    AAsset* asset = AAssetManager_open(__assetManager, path, AASSET_MODE_RANDOM);
+    AAsset* asset = AAssetManager_open(__assetManager, path, AASSET_MODE_RANDOM);
     char header[12];
     if (AAsset_read(asset, header, 12) != 12)
     {
         LOG_ERROR_VARG("Invalid audio buffer file: %s", path);
         return NULL;
-    }
-
-    // Get the file descriptor for the audio file.
-    off_t start, length;
-    int fd = AAsset_openFileDescriptor(asset, &start, &length);
-    if (fd < 0)
-    {
-        LOG_ERROR_VARG("Failed to open file descriptor for asset: %s", path);
-        return NULL;
-    }
-    AAsset_close(asset);
+    }
+
+    // Get the file descriptor for the audio file.
+    off_t start, length;
+    int fd = AAsset_openFileDescriptor(asset, &start, &length);
+    if (fd < 0)
+    {
+        LOG_ERROR_VARG("Failed to open file descriptor for asset: %s", path);
+        return NULL;
+    }
+    AAsset_close(asset);
     SLDataLocator_AndroidFD data = {SL_DATALOCATOR_ANDROIDFD, fd, start, length};
 
     // Set the appropriate mime type information.

+ 2 - 2
gameplay/src/AudioController.cpp

@@ -170,7 +170,7 @@ void AudioController::update(long elapsedTime)
     {
 #ifndef __ANDROID__
         alListenerf(AL_GAIN, listener->getGain());
-        alListenerfv(AL_ORIENTATION, (ALfloat*)&listener->getOrientationForward());
+        alListenerfv(AL_ORIENTATION, (ALfloat*)listener->getOrientation());
         alListenerfv(AL_VELOCITY, (ALfloat*)&listener->getVelocity());
         alListenerfv(AL_POSITION, (ALfloat*)&listener->getPosition());
 #else
@@ -181,7 +181,7 @@ void AudioController::update(long elapsedTime)
             SLresult result = (*_engineEngine)->CreateListener(_engineEngine, &_listenerObject, 2, interfaces, required);
             if (result != SL_RESULT_SUCCESS)
             {
-                WARN("AudioController: failed to create listener.");
+                WARN_VARG("AudioController: failed to create listener (%u).", result);
                 return;
             }
 

+ 1 - 1
gameplay/src/AudioController.h

@@ -58,7 +58,7 @@ private:
     ALCdevice* _alcDevice;
     ALCcontext* _alcContext;
 #else
-    SLObjectItf _engineObject;
+    SLObjectItf _engineObject;
     SLEngineItf _engineEngine;
     SLObjectItf _outputMixObject;
     SLObjectItf _listenerObject;

+ 14 - 8
gameplay/src/AudioListener.cpp

@@ -1,27 +1,24 @@
 #include "Base.h"
 #include "Node.h"
 #include "AudioListener.h"
+#include "Game.h"
 
 namespace gameplay
 {
 
-static AudioListener* __audioListenerInstance = NULL;
-
 AudioListener::AudioListener()
     : _gain(1.0f), _camera(NULL)
-
 {
-    assert(__audioListenerInstance == NULL);
-    __audioListenerInstance = this;
 }
 
 AudioListener::~AudioListener()
 {
+    SAFE_RELEASE(_camera);
 }
 
 AudioListener* AudioListener::getInstance()
 {
-    return __audioListenerInstance;
+    return Game::getInstance()->getAudioListener();
 }
 
 float AudioListener::getGain() const 
@@ -54,6 +51,11 @@ void AudioListener::setVelocity(const Vector3& velocity)
     _velocity = velocity;
 }
 
+const float* AudioListener::getOrientation() const
+{
+    return (const float*)&_orientation[0];
+}
+
 const Vector3& AudioListener::getOrientationForward() const 
 { 
     return _orientation[0]; 
@@ -106,8 +108,12 @@ void AudioListener::transformChanged(Transform* transform, long cookie)
 {
     if (transform)
     {
-        setPosition(transform->getTranslation());
-        setOrientation(transform->getForwardVector(), transform->getUpVector());
+        Node* node = static_cast<Node*>(transform);
+        setPosition(node->getTranslationWorld());
+        
+        Vector3 up;
+        node->getWorldMatrix().getUpVector(&up);
+        setOrientation(node->getForwardVectorWorld(), up);
     }
 }
 

+ 9 - 0
gameplay/src/AudioListener.h

@@ -15,6 +15,7 @@ class Camera;
 class AudioListener : public Transform::Listener
 {
     friend class AudioController;
+    friend class Game;
 
 public:
 
@@ -67,6 +68,14 @@ public:
      */
     void setVelocity(const Vector3& velocity);
 
+    /**
+     * Gets the float pointer to the orientation of the audio listener.
+     * Orientation is represented as 6 floats. (forward.x, forward.y, forward.z, up.x, up.y, up.z).
+     * 
+     * @return Pointer to the 6 orientation float values.
+     */
+    const float* getOrientation() const;
+
     /**
      * Gets the forward orientation vector of the audio listener.
      *

+ 47 - 8
gameplay/src/AudioSource.cpp

@@ -4,6 +4,7 @@
 #include "AudioController.h"
 #include "AudioSource.h"
 #include "Game.h"
+#include "Node.h"
 
 namespace gameplay
 {
@@ -26,37 +27,37 @@ AudioSource::AudioSource(AudioBuffer* buffer, const SLObjectItf& player)
 {
     // Get the different interfaces for the OpenSL audio player that we need.
     SLresult result = (*_playerObject)->GetInterface(_playerObject, SL_IID_3DDOPPLER, &_playerDoppler);
-    if(result != SL_RESULT_SUCCESS)
+    if (result != SL_RESULT_SUCCESS)
     {
         WARN("AudioSource::AudioSource() - Failed to get 3D doppler interface for OpenSL audio player.");
     }
     
     result = (*_playerObject)->GetInterface(_playerObject, SL_IID_3DLOCATION, &_playerLocation);
-    if(result != SL_RESULT_SUCCESS)
+    if (result != SL_RESULT_SUCCESS)
     {
         WARN("AudioSource::AudioSource() - Failed to get 3D location interface for OpenSL audio player.");
     }
 
     result = (*_playerObject)->GetInterface(_playerObject, SL_IID_PLAY, &_playerPlay);
-    if(result != SL_RESULT_SUCCESS)
+    if (result != SL_RESULT_SUCCESS)
     {
         WARN("AudioSource::AudioSource() - Failed to get play interface for OpenSL audio player.");
     }
 
     result = (*_playerObject)->GetInterface(_playerObject, SL_IID_PITCH, &_playerPitch);
-    if(result != SL_RESULT_SUCCESS)
+    if (result != SL_RESULT_SUCCESS)
     {
         WARN("AudioSource::AudioSource() - Failed to get rate pitch interface for OpenSL audio player.");
     }
 
     result = (*_playerObject)->GetInterface(_playerObject, SL_IID_SEEK, &_playerSeek);
-    if(result != SL_RESULT_SUCCESS)
+    if (result != SL_RESULT_SUCCESS)
     {
         WARN("AudioSource::AudioSource() - Failed to get seek interface for OpenSL audio player.");
     }
 
     result = (*_playerObject)->GetInterface(_playerObject, SL_IID_VOLUME, &_playerVolume);
-    if(result != SL_RESULT_SUCCESS)
+    if (result != SL_RESULT_SUCCESS)
     {
         WARN("AudioSource::AudioSource() - Failed to get volume interface for OpenSL audio player.");
     }
@@ -159,7 +160,7 @@ AudioSource* AudioSource::create(const char* path)
     }
 
     result = (*player)->Realize(player, SL_BOOLEAN_FALSE);
-    if(result != SL_RESULT_SUCCESS)
+    if (result != SL_RESULT_SUCCESS)
     {
         WARN("AudioSource::create - Failed to realize OpenSL audio player.");
     }
@@ -454,6 +455,8 @@ void AudioSource::setNode(Node* node)
         if (_node)
         {
             _node->addListener(this);
+            // Update the audio source position.
+            transformChanged(_node, 0);
         }
     }
 }
@@ -461,7 +464,11 @@ void AudioSource::setNode(Node* node)
 void AudioSource::transformChanged(Transform* transform, long cookie)
 {
 #ifndef __ANDROID__
-    alSourcefv(_alSource, AL_POSITION, (const ALfloat*)&transform->getTranslation());
+    if (_node)
+    {
+    	Vector3 translation = _node->getTranslationWorld();
+        alSourcefv(_alSource, AL_POSITION, (const ALfloat*)&translation.x);
+    }
 #else
     if (_playerLocation)
     {
@@ -478,4 +485,36 @@ void AudioSource::transformChanged(Transform* transform, long cookie)
 #endif
 }
 
+AudioSource* AudioSource::clone(NodeCloneContext &context) const
+{
+#ifndef __ANDROID__
+    ALuint alSource = 0;
+    alGenSources(1, &alSource);
+    if (alGetError() != AL_NO_ERROR)
+    {
+        LOG_ERROR("AudioSource::createAudioSource - Error generating audio source.");
+        return NULL;
+    }
+    AudioSource* audioClone = new AudioSource(_buffer, alSource);
+#else
+    // TODO: Implement cloning audio source for Android
+    AudioSource* audioClone = new AudioSource(_buffer, _playerObject);
+
+#endif
+    _buffer->addRef();
+    audioClone->setLooped(isLooped());
+    audioClone->setGain(getGain());
+    audioClone->setPitch(getPitch());
+    audioClone->setVelocity(getVelocity());
+    if (Node* node = getNode())
+    {
+        Node* clonedNode = context.findClonedNode(node);
+        if (clonedNode)
+        {
+            audioClone->setNode(clonedNode);
+        }
+    }
+    return audioClone;
+}
+
 }

+ 16 - 6
gameplay/src/AudioSource.h

@@ -10,6 +10,7 @@ namespace gameplay
 
 class AudioBuffer;
 class Node;
+class NodeCloneContext;
 
 /**
  *  Declares an audio source in 3D space.
@@ -176,15 +177,24 @@ private:
      */
     void transformChanged(Transform* transform, long cookie);
 
+    /**
+     * Clones the audio source and returns a new audio source.
+     * 
+     * @param context The clone context.
+     * 
+     * @return The newly created audio source.
+     */
+    AudioSource* clone(NodeCloneContext &context) const;
+
 #ifndef __ANDROID__
     ALuint _alSource;
 #else
-    SLObjectItf _playerObject;
-    SL3DDopplerItf _playerDoppler;
-    SL3DLocationItf _playerLocation;
-    SLPlayItf _playerPlay;
-    SLPitchItf _playerPitch;
-    SLSeekItf _playerSeek;
+    SLObjectItf _playerObject;
+    SL3DDopplerItf _playerDoppler;
+    SL3DLocationItf _playerLocation;
+    SLPlayItf _playerPlay;
+    SLPitchItf _playerPitch;
+    SLSeekItf _playerSeek;
     SLVolumeItf _playerVolume;
     SLmillibel _maxVolume;
 #endif

+ 24 - 6
gameplay/src/Base.h

@@ -98,7 +98,6 @@ extern void printError(const char* format, ...);
 #endif
 #define WARN(x) printError(x)
 #define WARN_VARG(x, ...) printError(x, __VA_ARGS__)
-
 #endif
 
 // Bullet Physics
@@ -144,7 +143,7 @@ extern void printError(const char* format, ...);
 #define MATH_LOG2E                  1.442695040888963387f
 #define MATH_PI                     3.14159265358979323846f
 #define MATH_PIOVER2                1.57079632679489661923f
-#define MATH_PIOVER4                M_PI_4
+#define MATH_PIOVER4                0.785398163397448309616f
 #define MATH_PIX2                   6.28318530717958647693f
 #define MATH_EPSILON                0.000001f
 #define MATH_CLAMP(x, lo, hi)       ((x < lo) ? lo : ((x > hi) ? hi : x))
@@ -204,10 +203,10 @@ extern void printError(const char* format, ...);
     #define OPENGL_ES_PVR    
 #elif WIN32
     #define WIN32_LEAN_AND_MEAN
-	#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG                      0x8C00
-	#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG                      0x8C01
-	#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG                     0x8C02
-	#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG                     0x8C03
+    #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG                      0x8C00
+    #define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG                      0x8C01
+    #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG                     0x8C02
+    #define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG                     0x8C03
     #include <GL/glew.h>
 #elif __APPLE__
     #include "TargetConditionals.h"
@@ -322,4 +321,23 @@ extern GLenum __gl_error_code;
 extern void amain(struct android_app* state);
 #endif
 
+
+// Assert has special behavior on Windows (for Visual Studio).
+#ifdef WIN32
+#ifdef assert
+#undef assert
+#endif
+#ifdef _DEBUG
+#define assert(expression) do { \
+    if (!(expression)) \
+    { \
+        printError("Assertion \'" #expression "\' failed."); \
+        __debugbreak(); \
+    } } while (0)
+
+#else
+#define assert(expression) do { (void)sizeof(expression); } while (0)
+#endif
+#endif
+
 #endif

+ 7 - 0
gameplay/src/BoundingBox.cpp

@@ -55,6 +55,13 @@ void BoundingBox::getCorners(Vector3* dst) const
     dst[7].set(min.x, max.y, min.z);
 }
 
+Vector3 BoundingBox::getCenter() const
+{
+	Vector3 center;
+	getCenter(&center);
+	return center;
+}
+
 void BoundingBox::getCenter(Vector3* dst) const
 {
     dst->set(min, max);

+ 10 - 1
gameplay/src/BoundingBox.h

@@ -53,6 +53,15 @@ public:
      */
     static const BoundingBox& empty();
 
+	/**
+     * Gets the center point of the bounding box.
+     *
+     * This method computes the center point of the box from its min and max.
+     *
+     * @return The center point of the bounding box.
+     */
+    Vector3 getCenter() const;
+
     /**
      * Gets the center point of the bounding box.
      *
@@ -71,7 +80,7 @@ public:
      * specify the far face starting at the upper left point when looking towards the origin from the negative
      * z-axis in a counter-clockwise fashion.
      *
-     * @param dst The array to store the corners in. Must be size 6.
+     * @param dst The array to store the corners in. Must be size 8.
      */
     void getCorners(Vector3* dst) const;
 

+ 31 - 33
gameplay/src/Camera.cpp

@@ -243,24 +243,13 @@ const Frustum& Camera::getFrustum() const
     return _bounds;
 }
 
-void Camera::project(const Viewport* viewport, const Vector3& position, float* x, float* y, float* depth)
+void Camera::project(const Rectangle& viewport, const Vector3& position, float* x, float* y, float* depth)
 {
     // Determine viewport coords to use.
-    float vpx, vpy, vpw, vph;
-    if (viewport)
-    {
-        vpx = viewport->getX();
-        vpy = viewport->getY();
-        vpw = viewport->getWidth();
-        vph = viewport->getHeight();
-    }
-    else
-    {
-        vpx = 0;
-        vpy = 0;
-        vpw = Game::getInstance()->getWidth();
-        vph = Game::getInstance()->getHeight();
-    }
+    float vpx = viewport.x;
+    float vpy = viewport.y;
+    float vpw = viewport.width;
+    float vph = viewport.height;
 
     // Transform the point to clip-space.
     Vector4 clipPos;
@@ -280,24 +269,13 @@ void Camera::project(const Viewport* viewport, const Vector3& position, float* x
     }
 }
 
-void Camera::unproject(const Viewport* viewport, float x, float y, float depth, Vector3* dst)
+void Camera::unproject(const Rectangle& viewport, float x, float y, float depth, Vector3* dst)
 {
     // Determine viewport coords to use.
-    float vpx, vpy, vpw, vph;
-    if (viewport)
-    {
-        vpx = viewport->getX();
-        vpy = viewport->getY();
-        vpw = viewport->getWidth();
-        vph = viewport->getHeight();
-    }
-    else
-    {
-        vpx = 0;
-        vpy = 0;
-        vpw = Game::getInstance()->getWidth();
-        vph = Game::getInstance()->getHeight();
-    }
+    float vpx = viewport.x;
+    float vpy = viewport.y;
+    float vpw = viewport.width;
+    float vph = viewport.height;
     
     // Create our screen space position in NDC.
     Vector4 screen(
@@ -325,7 +303,7 @@ void Camera::unproject(const Viewport* viewport, float x, float y, float depth,
     dst->set(screen.x, screen.y, screen.z);
 }
 
-void Camera::pickRay(const Viewport* viewport, float x, float y, Ray* dst)
+void Camera::pickRay(const Rectangle& viewport, float x, float y, Ray* dst)
 {
     // Get the world-space position at the near clip plane.
     Vector3 nearPoint;
@@ -343,6 +321,26 @@ void Camera::pickRay(const Viewport* viewport, float x, float y, Ray* dst)
     dst->set(nearPoint, direction);
 }
 
+Camera* Camera::clone(NodeCloneContext &context) const
+{
+    Camera* cameraClone = NULL;
+    if (getCameraType() == PERSPECTIVE)
+    {
+        cameraClone = createPerspective(_fieldOfView, _aspectRatio, _nearPlane, _farPlane);
+    }
+    else if (getCameraType() == ORTHOGRAPHIC)
+    {
+        cameraClone = createOrthographic(getZoomX(), getZoomY(), getAspectRatio(), _nearPlane, _farPlane);
+    }
+    assert(cameraClone);
+
+    if (Node* node = context.findClonedNode(getNode()))
+    {
+        cameraClone->setNode(node);
+    }
+    return cameraClone;
+}
+
 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;

+ 17 - 7
gameplay/src/Camera.h

@@ -4,12 +4,13 @@
 #include "Ref.h"
 #include "Transform.h"
 #include "Frustum.h"
-#include "Viewport.h"
+#include "Rectangle.h"
 
 namespace gameplay
 {
 
 class Node;
+class NodeCloneContext;
 
 /**
  * Defines a camera which acts as a view of a scene to be rendered.
@@ -196,13 +197,13 @@ public:
     /**
      * Projects the specified world position into the viewport coordinates.
      *
-     * @param viewport The viewport to use, or NULL to use a viewport the size of the window.
+     * @param viewport The viewport rectangle to use.
      * @param position The world space position.
      * @param x The returned viewport x coordinate.
      * @param y The returned viewport y coordinate.
      * @param depth The returned pixel depth (can be NULL).
      */
-    void project(const Viewport* viewport, const Vector3& position, float* x, float* y, float* depth = NULL);
+    void project(const Rectangle& viewport, const Vector3& position, float* x, float* y, float* depth = NULL);
 
     /**
      * Converts a viewport-space coordinate to a world-space position for the given depth value.
@@ -210,23 +211,23 @@ public:
      * The depth parameter is a value ranging between 0 and 1, where 0 returns a point on the
      * near clipping plane and 1 returns a point on the far clipping plane.
      *
-     * @param viewport The viewport to use, or NULL to use a viewport the size of the window.
+     * @param viewport The viewport rectangle to use.
      * @param x The viewport-space x coordinate.
      * @param y The viewport-space y coordinate.
      * @param depth The depth range.
      * @param dst The world space position.
      */
-    void unproject(const Viewport* viewport, float x, float y, float depth, Vector3* dst);
+    void unproject(const Rectangle& viewport, float x, float y, float depth, Vector3* dst);
 
     /**
      * Picks a ray that can be used for picking given the specified viewport-space coordinates.
      *
-     * @param viewport The viewport to use, or NULL to use a viewport the size of the window.
+     * @param viewport The viewport rectangle to use.
      * @param x The viewport x-coordinate.
      * @param y The viewport y-coordinate.
      * @param dst The computed pick ray.
      */
-    void pickRay(const Viewport* viewport, float x, float y, Ray* dst);
+    void pickRay(const Rectangle& viewport, float x, float y, Ray* dst);
 
 private:
 
@@ -245,6 +246,15 @@ private:
      */
     virtual ~Camera();
 
+    /**
+     * Clones the camera and returns a new camera.
+     * 
+     * @param context The clone context.
+     * 
+     * @return The newly created camera.
+     */
+    Camera* clone(NodeCloneContext &context) const;
+
     /**
      * @see Transform::Listener::transformChanged
      */

+ 67 - 40
gameplay/src/CheckBox.cpp

@@ -1,5 +1,6 @@
 #include "Base.h"
 #include "CheckBox.h"
+#include "Game.h"
 
 namespace gameplay
 {
@@ -22,7 +23,7 @@ CheckBox* CheckBox::create(Theme::Style* style, Properties* properties)
 {
     CheckBox* checkBox = new CheckBox();
     checkBox->init(style, properties);
-    properties->getVector2("iconSize", &checkBox->_iconSize);
+    properties->getVector2("iconSize", &checkBox->_imageSize);
     checkBox->_checked = properties->getBool("checked");
 
     return checkBox;
@@ -33,29 +34,22 @@ bool CheckBox::isChecked()
     return _checked;
 }
 
-void CheckBox::setIconSize(float width, float height)
+void CheckBox::setImageSize(float width, float height)
 {
-    _iconSize.set(width, height);
+    _imageSize.set(width, height);
 }
 
-const Vector2& CheckBox::getIconSize() const
+const Vector2& CheckBox::getImageSize() const
 {
-    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
-    Theme::Icon* icon = overlay->getCheckBoxIcon();
-    if (_iconSize.isZero() && icon)
-    {
-        return icon->getSize();
-    }
-
-    return _iconSize;
+    return _imageSize;
 }
 
 void CheckBox::addListener(Control::Listener* listener, int eventFlags)
 {
-    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    if ((eventFlags & Control::Listener::TEXT_CHANGED) == Control::Listener::TEXT_CHANGED)
     {
         assert("TEXT_CHANGED event is not applicable to CheckBox.");
-        eventFlags &= ~Listener::TEXT_CHANGED;
+        eventFlags &= ~Control::Listener::TEXT_CHANGED;
     }
 
     Control::addListener(listener, eventFlags);
@@ -78,7 +72,15 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
                     y > 0 && y <= _bounds.height)
                 {
                     _checked = !_checked;
-                    notifyListeners(Listener::VALUE_CHANGED);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
+
+                    // Animate between icons.  Old fades out, then the new fades in.
+                    /*
+                    AnimationController* animationController = Game::getInstance()->getAnimationController();
+                    float from[1] = { 1.0f };
+                    float to[1] = { 0.0f };
+                    animationController->createAnimationFromTo("CheckBox::toggle", this, CheckBox::ANIMATE_SPRITE_ALPHA, from, to, Curve::QUADRATIC_IN_OUT, 200L);
+                    */
                 }
             }
         }
@@ -92,12 +94,23 @@ void CheckBox::update(const Rectangle& clip)
 {
     Control::update(clip);
 
-    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
-    Theme::Icon* icon = overlay->getCheckBoxIcon();
-    Vector2& size = _iconSize;
-    if (_iconSize.isZero() && icon)
+    Vector2 size;
+    if (_imageSize.isZero())
+    {
+        if (_checked)
+        {
+            const Rectangle& selectedRegion = getImageRegion("checked", _state);
+            size.set(selectedRegion.width, selectedRegion.height);
+        }
+        else
+        {
+            const Rectangle& unselectedRegion = getImageRegion("unchecked", _state);
+            size.set(unselectedRegion.width, unselectedRegion.height);
+        }
+    }
+    else
     {
-        size = icon->getSize();
+        size.set(_imageSize);
     }
     float iconWidth = size.x;
 
@@ -105,43 +118,57 @@ void CheckBox::update(const Rectangle& clip)
     _textBounds.width -= iconWidth;
 }
 
-void CheckBox::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
+void CheckBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     // 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::Border border = getBorder(_state);
+    const Theme::Padding padding = _style->getPadding();
+    float opacity = getOpacity(_state);
+
+    if (_checked)
     {
-        const Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
-        Theme::Border border;
-        if (containerRegion)
+        const Rectangle& selectedRegion = getImageRegion("checked", _state);
+        const Theme::UVs& selected = getImageUVs("checked", _state);
+        Vector4 selectedColor = getImageColor("checked", _state);
+        selectedColor.w *= opacity;
+
+        Vector2 size;
+        if (_imageSize.isZero())
         {
-                border = containerRegion->getBorder();
+            size.set(selectedRegion.width, selectedRegion.height);
         }
-        const Theme::Padding padding = _style->getPadding();
-
-        Vector2& size = _iconSize;
-        if (_iconSize.isZero())
+        else
         {
-            size = icon->getSize();
+            size.set(_imageSize);
         }
 
-        const Vector4 color = icon->getColor();
-
         Vector2 pos(clip.x + _position.x + border.left + padding.left,
             clip.y + _position.y + (_bounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
 
-        if (_checked)
+        spriteBatch->draw(pos.x, pos.y, size.x, size.y, selected.u1, selected.v1, selected.u2, selected.v2, selectedColor, _clip);
+    }
+    else
+    {
+        const Rectangle& unselectedRegion = getImageRegion("unchecked", _state);
+        const Theme::UVs& unselected = getImageUVs("unchecked", _state);
+        Vector4 unselectedColor = getImageColor("unchecked", _state);
+        unselectedColor.w *= opacity;
+
+        Vector2 size;
+        if (_imageSize.isZero())
         {
-            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, _clip);
+            size.set(unselectedRegion.width, unselectedRegion.height);
         }
         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, _clip);
+            size.set(_imageSize);
         }
+
+        Vector2 pos(clip.x + _position.x + border.left + padding.left,
+            clip.y + _position.y + (_bounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+
+        spriteBatch->draw(pos.x, pos.y, size.x, size.y, unselected.u1, unselected.v1, unselected.u2, unselected.v2, unselectedColor, _clip);
     }
 }
 

+ 7 - 5
gameplay/src/CheckBox.h

@@ -24,7 +24,7 @@ namespace gameplay
  *      iconSize    = <width, height>   // The size to draw the checkbox icon, if different from its size in the texture.
  * }
  */
-class CheckBox : public Button
+class CheckBox : public Button //, public AnimationClip::Listener
 {
     friend class Container;
 
@@ -42,14 +42,14 @@ public:
      * @param width The width to draw the checkbox icon.
      * @param height The height to draw the checkbox icon.
      */
-    void setIconSize(float width, float height);
+    void setImageSize(float width, float height);
 
     /**
      * Get the size at which the checkbox icon will be drawn.
      *
      * @return The size of the checkbox icon.
      */
-    const Vector2& getIconSize() const;
+    const Vector2& getImageSize() const;
 
     /**
      * Add a listener to be notified of specific events affecting
@@ -63,6 +63,8 @@ public:
      */
     virtual void addListener(Control::Listener* listener, int eventFlags);
 
+  //  virtual void animationEvent(AnimationClip* clip, EventType type);
+
 protected:
     CheckBox();
     ~CheckBox();
@@ -105,10 +107,10 @@ protected:
      * @param spriteBatch The sprite batch containing this control's icons.
      * @param position The container position this control is relative to.
      */
-    void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
+    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
     bool _checked;      // Whether this checkbox is currently checked.
-    Vector2 _iconSize;  // The size to draw the checkbox icon, if different from its size in the texture.
+    Vector2 _imageSize;  // The size to draw the checkbox icon, if different from its size in the texture.
 
 private:
     CheckBox(const CheckBox& copy);

+ 5 - 15
gameplay/src/Container.cpp

@@ -34,7 +34,7 @@ namespace gameplay
     Container* Container::create(Layout::Type type)
     {
         Layout* layout = NULL;
-        switch(type)
+        switch (type)
         {
         case Layout::LAYOUT_ABSOLUTE:
             layout = AbsoluteLayout::create();
@@ -219,8 +219,6 @@ namespace gameplay
         Control::drawBorder(spriteBatch, clip);
 
         // Now call drawBorder on all controls within this container.
-        //Vector2 pos(clip.x + _position.x, clip.y + _position.y);
-        //const Rectangle newClip(clip.x + _position.x, clip.y + _position.y, _size.x, _size.y);
         std::vector<Control*>::const_iterator it;
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
@@ -229,14 +227,13 @@ namespace gameplay
         }
     }
 
-    void Container::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
+    void Container::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     {
-        //const Rectangle newClip(clip.x + _position.x, clip.y + _position.y, _size.x, _size.y);
         std::vector<Control*>::const_iterator it;
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
             Control* control = *it;
-            control->drawSprites(spriteBatch, _clip);
+            control->drawImages(spriteBatch, _clip);
         }
 
         _dirty = false;
@@ -244,7 +241,6 @@ namespace gameplay
 
     void Container::drawText(const Rectangle& clip)
     {
-        //const Rectangle newClip(clip.x + _position.x, clip.y + _position.y, _size.x, _size.y);
         std::vector<Control*>::const_iterator it;
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
@@ -285,14 +281,8 @@ namespace gameplay
 
         bool eventConsumed = false;
 
-        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();
+        const Theme::Border& border = getBorder(_state);
+        const Theme::Padding& padding = getPadding();
         float xPos = border.left + padding.left;
         float yPos = border.top + padding.top;
 

+ 1 - 1
gameplay/src/Container.h

@@ -146,7 +146,7 @@ protected:
      * @param spriteBatch The sprite batch containing this control's icons.
      * @param clip The clipping rectangle of this container's parent container.
      */
-    virtual void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
+    virtual void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
     /**
      * Draws the text of all controls within this container.

+ 609 - 72
gameplay/src/Control.cpp

@@ -1,11 +1,12 @@
 #include "Base.h"
+#include "Game.h"
 #include "Control.h"
 
 namespace gameplay
 {
     Control::Control()
         : _id(""), _state(Control::NORMAL), _position(Vector2::zero()), _size(Vector2::zero()), _bounds(Rectangle::empty()), _clip(Rectangle::empty()),
-          _autoWidth(true), _autoHeight(true), _dirty(true), _consumeTouchEvents(true), _listeners(NULL)
+            _autoWidth(true), _autoHeight(true), _dirty(true), _consumeTouchEvents(true), _listeners(NULL), _styleOverridden(false)
     {
     }
 
@@ -24,6 +25,11 @@ namespace gameplay
             }
             SAFE_DELETE(_listeners);
         }
+
+        if (_styleOverridden)
+        {
+            SAFE_DELETE(_style);
+        }
     }
 
     void Control::init(Theme::Style* style, Properties* properties)
@@ -37,9 +43,7 @@ namespace gameplay
 
         const char* id = properties->getId();
         if (id)
-        {
             _id = id;
-        }
     }
 
     const char* Control::getID() const
@@ -47,9 +51,24 @@ namespace gameplay
         return _id.c_str();
     }
 
-    void Control::setPosition(float x, float y)
+    void Control::setPosition(float x, float y, unsigned long duration)
     {
-        _position.set(x, y);
+        if (duration > 0L)
+        {
+            AnimationController* animationController = Game::getInstance()->getAnimationController();
+            float from[2] = { _position.x, _position.y };
+            float to[2] = { x, y };
+            Animation* moveAnimation = animationController->createAnimationFromTo("Control::setPosition", this, Control::ANIMATE_POSITION,
+                from, to, gameplay::Curve::QUADRATIC_IN_OUT, duration);
+            AnimationClip* clip = moveAnimation->getClip();
+            clip->play();
+        }
+        else
+        {
+            _position.set(x, y);
+        }
+
+        _dirty = true;
     }
 
     const Vector2& Control::getPosition() const
@@ -57,9 +76,24 @@ namespace gameplay
         return _position;
     }
 
-    void Control::setSize(float width, float height)
+    void Control::setSize(float width, float height, unsigned long duration)
     {
-        _size.set(width, height);
+        if (duration > 0L)
+        {
+            AnimationController* animationController = Game::getInstance()->getAnimationController();
+            float from[2] = { _size.x, _size.y };
+            float to[2] = { width, height };
+            Animation* resizeAnimation = animationController->createAnimationFromTo("Control::setSize", this, Control::ANIMATE_SIZE,
+                from, to, gameplay::Curve::QUADRATIC_IN_OUT, duration);
+            AnimationClip* clip = resizeAnimation->getClip();
+            clip->play();
+        }
+        else
+        {
+            _size.set(width, height);
+        }
+
+        _dirty = true;
     }
 
     const Vector2& Control::getSize() const
@@ -67,6 +101,312 @@ namespace gameplay
         return _size;
     }
 
+    void Control::setOpacity(float opacity, unsigned char states, unsigned long duration)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            if (duration > 0L)
+            {
+                float from[1] = { overlays[i]->getOpacity() };
+                float to[1] = { opacity };
+
+                // Fun with chaining.
+                Game::getInstance()->getAnimationController()->createAnimationFromTo("Overlay::setOpacity", overlays[i], Theme::Style::Overlay::ANIMATE_OPACITY,
+                    from, to, gameplay::Curve::QUADRATIC_IN_OUT, duration)->getClip()->play();
+            }
+            else
+            {
+                overlays[i]->setOpacity(opacity);
+            }
+        }
+        
+        if (duration > 0L)
+        {
+            // All this animation does is make sure this control sets its dirty flag during the animation.
+            float from[1] = { 0.0f };
+            float to[1] = { 1.0f };
+
+            Game::getInstance()->getAnimationController()->createAnimationFromTo("Control::setOpacity", this, Control::ANIMATE_OPACITY,
+                from, to, gameplay::Curve::QUADRATIC_IN_OUT, duration)->getClip()->play();
+        }
+        
+        _dirty = true;
+    }
+
+    float Control::getOpacity(State state) const
+    {
+        return getOverlay(state)->getOpacity();
+    }
+
+    void Control::setBorder(float top, float bottom, float left, float right, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setBorder(top, bottom, left, right);
+        }
+
+        _dirty = true;
+    }
+
+    const Theme::Border& Control::getBorder(State state) const
+    {
+        return getOverlay(state)->getBorder();
+    }
+
+    void Control::setSkinRegion(const Rectangle& region, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setSkinRegion(region, _style->_tw, _style->_th);
+        }
+
+        _dirty = true;
+    }
+
+    const Rectangle& Control::getSkinRegion(State state) const
+    {
+        return getOverlay(state)->getSkinRegion();
+    }
+
+    const Theme::UVs& Control::getSkinUVs(Theme::Skin::SkinArea area, State state) const
+    {
+        return getOverlay(state)->getSkinUVs(area);
+    }
+
+    void Control::setSkinColor(const Vector4& color, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setSkinColor(color);
+        }
+
+        _dirty = true;
+    }
+
+    const Vector4& Control::getSkinColor(State state) const
+    {
+        return getOverlay(state)->getSkinColor();
+    }
+
+    void Control::setMargin(float top, float bottom, float left, float right)
+    {
+        _style->setMargin(top, bottom, left, right);
+        _dirty = true;
+    }
+
+    const Theme::Margin& Control::getMargin() const
+    {
+        return _style->getMargin();
+    }
+
+    void Control::setPadding(float top, float bottom, float left, float right)
+    {
+        _style->setPadding(top, bottom, left, right);
+        _dirty = true;
+    }
+    
+    const Theme::Padding& Control::getPadding() const
+    {
+        return _style->getPadding();
+    }
+
+    void Control::setImageRegion(const char* id, const Rectangle& region, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setImageRegion(id, region, _style->_tw, _style->_th);
+        }
+
+        _dirty = true;
+    }
+
+    const Rectangle& Control::getImageRegion(const char* id, State state) const
+    {
+        return getOverlay(state)->getImageRegion(id);
+    }
+
+    void Control::setImageColor(const char* id, const Vector4& color, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setImageColor(id, color);
+        }
+
+        _dirty = true;
+    }
+
+    const Vector4& Control::getImageColor(const char* id, State state) const
+    {
+        return getOverlay(state)->getImageColor(id);
+    }
+
+    const Theme::UVs& Control::getImageUVs(const char* id, State state) const
+    {
+        return getOverlay(state)->getImageUVs(id);
+    }
+
+    void Control::setCursorRegion(const Rectangle& region, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setCursorRegion(region, _style->_tw, _style->_th);
+        }
+
+        _dirty = true;
+    }
+
+    const Rectangle& Control::getCursorRegion(State state) const
+    {
+        return getOverlay(state)->getCursorRegion();
+    }
+
+    void Control::setCursorColor(const Vector4& color, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setCursorColor(color);
+        }
+
+        _dirty = true;
+    }
+
+    const Vector4& Control::getCursorColor(State state)
+    {
+        return getOverlay(state)->getCursorColor();
+    }
+    
+    const Theme::UVs& Control::getCursorUVs(State state)
+    {
+        return getOverlay(state)->getCursorUVs();
+    }
+
+    void Control::setFont(Font* font, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setFont(font);
+        }
+
+        _dirty = true;
+    }
+
+    Font* Control::getFont(State state) const
+    {
+        return getOverlay(state)->getFont();
+    }
+
+    void Control::setFontSize(unsigned int fontSize, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setFontSize(fontSize);
+        }
+
+        _dirty = true;
+    }
+
+    unsigned int Control::getFontSize(State state) const
+    {
+        return getOverlay(state)->getFontSize();
+    }
+
+    void Control::setTextColor(const Vector4& color, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setTextColor(color);
+        }
+
+        _dirty = true;
+    }
+
+    const Vector4& Control::getTextColor(State state) const
+    {
+        return getOverlay(state)->getTextColor();
+    }
+
+    void Control::setTextAlignment(Font::Justify alignment, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setTextAlignment(alignment);
+        }
+
+        _dirty = true;
+    }
+
+    Font::Justify Control::getTextAlignment(State state) const
+    {
+        return getOverlay(state)->getTextAlignment();
+    }
+
+    void Control::setTextRightToLeft(bool rightToLeft, unsigned char states)
+    {
+        overrideStyle();
+        Theme::Style::Overlay* overlays[MAX_OVERLAYS] = { 0 };
+        getOverlays(states, overlays);
+
+        for (int i = 0; i < MAX_OVERLAYS - 1 && overlays[i]; ++i)
+        {
+            overlays[i]->setTextRightToLeft(rightToLeft);
+        }
+
+        _dirty = true;
+    }
+
+    bool Control::getTextRightToLeft(State state) const
+    {
+        return getOverlay(state)->getTextRightToLeft();
+    }
+
     const Rectangle& Control::getBounds() const
     {
         return _bounds;
@@ -81,6 +421,7 @@ namespace gameplay
     {
         _autoWidth = width;
         _autoHeight = height;
+        _dirty = true;
     }
 
     void Control::setStyle(Theme::Style* style)
@@ -101,9 +442,10 @@ namespace gameplay
     void Control::setState(State state)
     {
         _state = state;
+        _dirty = true;
     }
 
-    Control::State Control::getState()
+    Control::State Control::getState() const
     {
         return _state;
     }
@@ -111,11 +453,13 @@ namespace gameplay
     void Control::disable()
     {
         _state = DISABLED;
+        _dirty = true;
     }
 
     void Control::enable()
     {
         _state = NORMAL;
+        _dirty = true;
     }
 
     bool Control::isEnabled()
@@ -270,14 +614,8 @@ namespace gameplay
         _bounds.set(_position.x, _position.y, width, height);
 
         // Calculate the clipping viewport.
-        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();
+        const Theme::Border& border = getBorder(_state);
+        const Theme::Padding& padding = getPadding();
 
         x +=  border.left + padding.left;
         y +=  border.top + padding.top;
@@ -318,65 +656,61 @@ namespace gameplay
         Vector2 pos(clip.x + _position.x, clip.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.right && !border.top && !border.bottom)
-            {
-                // No border, just draw the image.
-                spriteBatch->draw(pos.x, pos.y, _size.x, _size.y, center.u1, center.v1, center.u2, center.v2, borderColor, clip);
-            }
-            else
-            {
-                if (border.left && border.top)
-                    spriteBatch->draw(pos.x, pos.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, borderColor, clip);
-                if (border.top)
-                    spriteBatch->draw(pos.x + border.left, pos.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, borderColor, clip);
-                if (border.right && border.top)
-                    spriteBatch->draw(rightX, pos.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, borderColor, clip);
-                if (border.left)
-                    spriteBatch->draw(pos.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, borderColor, clip);
-                if (border.left && border.right && border.top && border.bottom)
-                    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, clip);
-                if (border.right)
-                    spriteBatch->draw(rightX, midY, border.right, midHeight, right.u1, right.v1, right.u2, right.v2, borderColor, clip);
-                if (border.bottom && border.left)
-                    spriteBatch->draw(pos.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, borderColor, clip);
-                if (border.bottom)
-                    spriteBatch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, borderColor, clip);
-                if (border.bottom && border.right)
-                    spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, borderColor, clip);
-            }
+        //Theme::UVs topLeft, top, topRight, left, center, right, bottomLeft, bottom, bottomRight;
+        const Theme::UVs& topLeft = getSkinUVs(Theme::Skin::TOP_LEFT, _state);
+        const Theme::UVs& top = getSkinUVs(Theme::Skin::TOP, _state);
+        const Theme::UVs& topRight = getSkinUVs(Theme::Skin::TOP_RIGHT, _state);
+        const Theme::UVs& left = getSkinUVs(Theme::Skin::LEFT, _state);
+        const Theme::UVs& center = getSkinUVs(Theme::Skin::CENTER, _state);
+        const Theme::UVs& right = getSkinUVs(Theme::Skin::RIGHT, _state);
+        const Theme::UVs& bottomLeft = getSkinUVs(Theme::Skin::BOTTOM_LEFT, _state);
+        const Theme::UVs& bottom = getSkinUVs(Theme::Skin::BOTTOM, _state);
+        const Theme::UVs& bottomRight = getSkinUVs(Theme::Skin::BOTTOM_RIGHT, _state);
+
+        // Calculate screen-space positions.
+        const Theme::Border& border = getBorder(_state);
+        const Theme::Padding& padding = getPadding();
+        Vector4 skinColor = getSkinColor(_state);
+        skinColor.w *= getOpacity(_state);
+
+        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.right && !border.top && !border.bottom)
+        {
+            // No border, just draw the image.
+            spriteBatch->draw(pos.x, pos.y, _size.x, _size.y, center.u1, center.v1, center.u2, center.v2, skinColor, clip);
+        }
+        else
+        {
+            if (border.left && border.top)
+                spriteBatch->draw(pos.x, pos.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, skinColor, clip);
+            if (border.top)
+                spriteBatch->draw(pos.x + border.left, pos.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, skinColor, clip);
+            if (border.right && border.top)
+                spriteBatch->draw(rightX, pos.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
+            if (border.left)
+                spriteBatch->draw(pos.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, skinColor, clip);
+            if (border.left && border.right && border.top && border.bottom)
+                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, skinColor, clip);
+            if (border.right)
+                spriteBatch->draw(rightX, midY, border.right, midHeight, right.u1, right.v1, right.u2, right.v2, skinColor, clip);
+            if (border.bottom && border.left)
+                spriteBatch->draw(pos.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, skinColor, clip);
+            if (border.bottom)
+                spriteBatch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, skinColor, clip);
+            if (border.bottom && border.right)
+                spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, skinColor, clip);
         }
     }
 
-    void Control::drawSprites(SpriteBatch* spriteBatch, const Rectangle& position)
+    void Control::drawImages(SpriteBatch* spriteBatch, const Rectangle& position)
     {
     }
 
@@ -420,4 +754,207 @@ namespace gameplay
 
         return NORMAL;
     }
+
+    // Implementation of AnimationHandler
+    unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
+    {
+        switch(propertyId)
+        {
+        case ANIMATE_POSITION:
+        case ANIMATE_SIZE:
+            return 2;
+
+        case ANIMATE_POSITION_X:
+        case ANIMATE_POSITION_Y:
+        case ANIMATE_SIZE_WIDTH:
+        case ANIMATE_SIZE_HEIGHT:
+        case ANIMATE_OPACITY:
+            return 1;
+
+        default:
+            return -1;
+        }
+    }
+
+    void Control::getAnimationPropertyValue(int propertyId, AnimationValue* value)
+    {
+        switch(propertyId)
+        {
+        case ANIMATE_POSITION:
+            value->setFloat(0, _position.x);
+            value->setFloat(1, _position.y);
+            break;
+        case ANIMATE_SIZE:
+            value->setFloat(0, _size.x);
+            value->setFloat(1, _size.y);
+            break;
+        case ANIMATE_POSITION_X:
+            value->setFloat(0, _position.x);
+            break;
+        case ANIMATE_POSITION_Y:
+            value->setFloat(0, _position.y);
+            break;
+        case ANIMATE_SIZE_WIDTH:
+            value->setFloat(0, _size.x);
+            break;
+        case ANIMATE_SIZE_HEIGHT:
+            value->setFloat(0, _size.y);
+            break;
+        case ANIMATE_OPACITY:
+        default:
+            break;
+        }
+    }
+
+    void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
+    {
+        switch(propertyId)
+        {
+        case ANIMATE_POSITION:
+            applyAnimationValuePositionX(value->getFloat(0), blendWeight);
+            applyAnimationValuePositionY(value->getFloat(1), blendWeight);
+            break;
+        case ANIMATE_POSITION_X:
+            applyAnimationValuePositionX(value->getFloat(0), blendWeight);
+            break;
+        case ANIMATE_POSITION_Y:
+            applyAnimationValuePositionY(value->getFloat(0), blendWeight);
+            break;
+        case ANIMATE_SIZE:
+            applyAnimationValueSizeWidth(value->getFloat(0), blendWeight);
+            applyAnimationValueSizeHeight(value->getFloat(1), blendWeight);
+            break;
+        case ANIMATE_SIZE_WIDTH:
+            applyAnimationValueSizeWidth(value->getFloat(0), blendWeight);
+            break;
+        case ANIMATE_SIZE_HEIGHT:
+            applyAnimationValueSizeHeight(value->getFloat(0), blendWeight);
+            break;
+        case ANIMATE_OPACITY:
+            applyAnimationValueOpacity();
+        default:
+            break;
+        }
+    }
+
+    void Control::applyAnimationValuePositionX(float x, float blendWeight)
+    {
+        if ((_animationPropertyBitFlag & ANIMATION_POSITION_X_BIT) != ANIMATION_POSITION_X_BIT)
+        {
+            _animationPropertyBitFlag |= ANIMATION_POSITION_X_BIT;
+        }
+        else
+        {
+            x = Curve::lerp(blendWeight, _position.x, x);
+        }
+        _position.x = x;
+        _dirty = true;
+    }
+    
+    void Control::applyAnimationValuePositionY(float y, float blendWeight)
+    {
+        if ((_animationPropertyBitFlag & ANIMATION_POSITION_Y_BIT) != ANIMATION_POSITION_Y_BIT)
+        {
+            _animationPropertyBitFlag |= ANIMATION_POSITION_Y_BIT;
+        }
+        else
+        {
+            y = Curve::lerp(blendWeight, _position.y, y);
+        }
+        _position.y = y;
+        _dirty = true;
+    }
+    
+    void Control::applyAnimationValueSizeWidth(float width, float blendWeight)
+    {
+        if ((_animationPropertyBitFlag & ANIMATION_SIZE_WIDTH_BIT) != ANIMATION_SIZE_WIDTH_BIT)
+        {
+            _animationPropertyBitFlag |= ANIMATION_SIZE_WIDTH_BIT;
+        }
+        else
+        {
+            width = Curve::lerp(blendWeight, _size.x, width);
+        }
+        _size.x = width;
+        _dirty = true;
+    }
+
+    void Control::applyAnimationValueSizeHeight(float height, float blendWeight)
+    {
+        if ((_animationPropertyBitFlag & ANIMATION_SIZE_HEIGHT_BIT) != ANIMATION_SIZE_HEIGHT_BIT)
+        {
+            _animationPropertyBitFlag |= ANIMATION_SIZE_HEIGHT_BIT;
+        }
+        else
+        {
+            height = Curve::lerp(blendWeight, _size.y, height);
+        }
+        _size.y = height;
+        _dirty = true;
+    }
+
+    void Control::applyAnimationValueOpacity()
+    {
+        if ((_animationPropertyBitFlag & ANIMATION_OPACITY_BIT) != ANIMATION_OPACITY_BIT)
+        {
+            _animationPropertyBitFlag |= ANIMATION_OPACITY_BIT;
+        }
+        _dirty = true;
+    }
+    
+    Theme::Style::Overlay** Control::getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays)
+    {
+        unsigned int index = 0;
+        if ((overlayTypes & NORMAL) == NORMAL)
+        {
+            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
+        }
+
+        if ((overlayTypes & FOCUS) == FOCUS)
+        {
+            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+        }
+
+        if ((overlayTypes & ACTIVE) == ACTIVE)
+        {
+            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
+        }
+
+        if ((overlayTypes & DISABLED) == DISABLED)
+        {
+            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
+        }
+
+        return overlays;
+    }
+
+    Theme::Style::Overlay* Control::getOverlay(State state) const
+    {
+        switch(state)
+        {
+        case Control::NORMAL:
+            return _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
+        case Control::FOCUS:
+            return _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+        case Control::ACTIVE:
+            return _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
+        case Control::DISABLED:
+            return _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
+        default:
+            return NULL;
+        }
+    }
+
+    void Control::overrideStyle()
+    {
+        if (_styleOverridden)
+        {
+            return;
+        }
+
+        // Copy the style.
+        WARN_VARG("%d", sizeof(Theme::Style::Overlay));
+        _style = new Theme::Style(*_style);
+        _styleOverridden = true;
+    }
 }

+ 408 - 14
gameplay/src/Control.h

@@ -9,13 +9,18 @@
 #include "Touch.h"
 #include "Keyboard.h"
 
+/**
+ * Default duration of UI animations.
+ */
+#define DEFAULT_UI_ANIMATION_DURATION 200L
+
 namespace gameplay
 {
 
 /**
  * Base class for UI controls.
  */
-class Control : public Ref
+class Control : public Ref, public AnimationTarget
 {
     friend class Form;
     friend class Container;
@@ -29,12 +34,15 @@ public:
      */
     enum State
     {
-        NORMAL,
-        FOCUS,
-        ACTIVE,
-        DISABLED
+        NORMAL = 0x01,
+        FOCUS = 0x02,
+        ACTIVE = 0x04,
+        DISABLED = 0x08,
     };
 
+    // Only for setting control states
+    static const unsigned char STATE_ALL = NORMAL | FOCUS | ACTIVE | DISABLED;
+
     class Listener
     {
     public:
@@ -50,6 +58,41 @@ public:
         virtual void controlEvent(Control* control, EventType evt) = 0;
     };
 
+    /**
+     * Position animation property. Data = x, y
+     */
+    static const int ANIMATE_POSITION = 1;
+
+    /**
+     * Position x animation property. Data = x
+     */
+    static const int ANIMATE_POSITION_X = 2;
+
+    /**
+     * Position y animation property. Data = y
+     */
+    static const int ANIMATE_POSITION_Y = 3;
+
+    /**
+     * Size animation property.  Data = width, height
+     */
+    static const int ANIMATE_SIZE = 4;
+
+    /**
+     * Size width animation property.  Data = width
+     */
+    static const int ANIMATE_SIZE_WIDTH = 5;
+
+    /**
+     * Size height animation property.  Data = height
+     */
+    static const int ANIMATE_SIZE_HEIGHT = 6;
+
+    /**
+     * Opacity property.  Data = opacity
+     */
+    static const int ANIMATE_OPACITY = 7;
+
     /**
      * Get this control's ID string.
      *
@@ -63,7 +106,7 @@ public:
      * @param x The x coordinate.
      * @param y The y coordinate.
      */
-    void setPosition(float x, float y);
+    void setPosition(float x, float y, unsigned long duration = DEFAULT_UI_ANIMATION_DURATION);
 
     /**
      * Get the position of this control relative to its parent container.
@@ -78,7 +121,7 @@ public:
      * @param width The width.
      * @param height The height.
      */
-    void setSize(float width, float height);
+    void setSize(float width, float height, unsigned long duration = DEFAULT_UI_ANIMATION_DURATION);
 
     /**
      * Get the desired size of this control, including its border and padding, before clipping.
@@ -87,6 +130,314 @@ public:
      */
     const Vector2& getSize() const;
 
+    // Themed properties.
+    
+    //void setBorder(const Theme::Border& border, unsigned char states = STATE_ALL);
+    /**
+     * Set the size of this control's border.
+     *
+     * @param top The height of the border's top side.
+     * @param bottom The height of the border's bottom side.
+     * @param left The width of the border's left side.
+     * @param right The width of the border's right side.
+     */
+    void setBorder(float top, float bottom, float left, float right, unsigned char states = STATE_ALL);
+
+    /**
+     * Get the measurements of this control's border for a given state. 
+     *
+     * @return This control's border.
+     */
+    const Theme::Border& getBorder(State state = NORMAL) const;
+
+    /**
+     * Set the texture region of this control's skin.
+     *
+     * @param region The texture region, in pixels.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setSkinRegion(const Rectangle& region, unsigned char states = STATE_ALL);
+
+    /**
+     * Get the texture region of this control's skin for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return The texture region of this control's skin.
+     */
+    const Rectangle& getSkinRegion(State state = NORMAL) const;
+
+    /**
+     * Get the texture coordinates of an area of this control's skin for a given state.
+     *
+     * @param area The area of the skin to get the coordinates of.
+     * @param state The state to get this property from.
+     *
+     * @return The texture coordinates of an area of this control's skin.
+     */
+    const Theme::UVs& getSkinUVs(Theme::Skin::SkinArea area, State state = NORMAL) const;
+
+    /**
+     * Set the blend color of this control's skin.
+     *
+     * @param color The new blend color.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setSkinColor(const Vector4& color, unsigned char states = STATE_ALL);
+
+    /**
+     * Get the blend color of this control's skin for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return The blend color of this control's skin.
+     */
+    const Vector4& getSkinColor(State state = NORMAL) const;
+
+    /**
+     * Set this control's margin.
+     *
+     * @param top Height of top margin.
+     * @param bottom Height of bottom margin.
+     * @param left Width of left margin.
+     * @param right Width of right margin.
+     */
+    void setMargin(float top, float bottom, float left, float right);
+
+    /**
+     * Get this control's margin.
+     *
+     * @return This control's margin.
+     */
+    const Theme::Margin& getMargin() const;
+
+    /**
+     * Set this control's padding.
+     *
+     * @param top Height of top padding.
+     * @param bottom Height of bottom padding.
+     * @param left Width of left padding.
+     * @param right Width of right padding.
+     */
+    void setPadding(float top, float bottom, float left, float right);
+
+    /**
+     * Get this control's padding.
+     *
+     * @return This control's padding.
+     */
+    const Theme::Padding& getPadding() const;
+
+    /**
+     * Set the texture region of an image used by this control.
+     *
+     * @param id The ID of the image to modify.
+     * @param region The new texture region of the image.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setImageRegion(const char* id, const Rectangle& region, unsigned char states = STATE_ALL);
+
+    /**
+     * Get the texture region of an image used by this control for a given state.
+     *
+     * @param id The ID of the image.
+     * @param state The state to get this property from.
+     *
+     * @return The texture region of the specified image.
+     */
+    const Rectangle& getImageRegion(const char* id, State state) const;
+
+    /**
+     * Set the blend color of an image used by this control.
+     *
+     * @param id The ID of the image to modify.
+     * @param color The new blend color of the image.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setImageColor(const char* id, const Vector4& color, unsigned char states = STATE_ALL);
+
+    /**
+     * Get the blend color of an image used by this control for a given state.
+     *
+     * @param id The ID of the image.
+     * @param state The state to get this property from.
+     *
+     * @return The blend color of the specified image.
+     */
+    const Vector4& getImageColor(const char* id, State state) const;
+
+    /**
+     * Get the texture coordinates of an image used by this control for a given state.
+     *
+     * @param id The ID of the image.
+     * @param state The state to get this property from.
+     *
+     * @return The texture coordinates of the specified image.
+     */
+    const Theme::UVs& getImageUVs(const char* id, State state) const;
+
+    /**
+     * Set the texture region of this control's cursor.
+     *
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setCursorRegion(const Rectangle& region, unsigned char states);
+
+    /**
+     * Get the texture region of this control's cursor for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return The texture region of this control's cursor.
+     */
+    const Rectangle& getCursorRegion(State state) const;
+
+    /**
+     * Set the blend color of this control's cursor.
+     *
+     * @param color The new blend color.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setCursorColor(const Vector4& color, unsigned char states);
+
+    /**
+     * Get the blend color of this control's cursor for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return The blend color of this control's cursor.
+     */
+    const Vector4& getCursorColor(State state);
+    
+    /**
+     * Get the texture coordinates of this control's cursor for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return The texture coordinates of this control's cursor.
+     */
+    const Theme::UVs& getCursorUVs(State state);
+
+    /**
+     * Set the font used by this control.
+     *
+     * @param font The new font to use.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setFont(Font* font, unsigned char states = STATE_ALL);
+
+    /**
+     * Get the font used by this control for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return the font used by this control.
+     */
+    Font* getFont(State state = NORMAL) const;
+
+    /**
+     * Set this control's font size.
+     *
+     * @param size The new font size.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setFontSize(unsigned int size, unsigned char states = STATE_ALL);
+
+    /**
+     * Get this control's font size for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return This control's font size.
+     */
+    unsigned int getFontSize(State state = NORMAL) const;
+
+    /**
+     * Set this control's text color.
+     *
+     * @param color The new text color.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setTextColor(const Vector4& color, unsigned char states = STATE_ALL);
+
+    /**
+     * Get this control's text color for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return This control's text color.
+     */
+    const Vector4& getTextColor(State state = NORMAL) const;
+
+    /**
+     * Set this control's text alignment.
+     *
+     * @param alignment The new text alignment.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setTextAlignment(Font::Justify alignment, unsigned char states = STATE_ALL);
+
+    /**
+     * Get this control's text alignment for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return This control's text alignment for the given state.
+     */
+    Font::Justify getTextAlignment(State state = NORMAL) const;
+
+    /**
+     * Set whether text is drawn from right to left within this control.
+     *
+     * @param rightToLeft Whether text is drawn from right to left within this control.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     */
+    void setTextRightToLeft(bool rightToLeft, unsigned char states = STATE_ALL);
+
+    /**
+     * Get whether text is drawn from right to left within this control, for a given state.
+     *
+     * @param state The state to get this property from.
+     *
+     * @return Whether text is drawn from right to left within this control, for the given state.
+     */
+    bool getTextRightToLeft(State state = NORMAL) const;
+
+    /**
+     * Set the opacity of this control.
+     *
+     * @param opacity The new opacity.
+     * @param states The states to set this property on.
+     *               One or more members of the Control::State enum, ORed together.
+     * @param duration The duration to animate opacity by.
+     */
+    void setOpacity(float opacity, unsigned char states = STATE_ALL, unsigned long duration = DEFAULT_UI_ANIMATION_DURATION);
+
+    /**
+     * Get the opacity of this control for a given state. 
+     *
+     * @param state The state to get this property from.
+     *
+     * @return The opacity of this control for a given state.
+     */
+    float getOpacity(State state = NORMAL) const;
+
+    // TODO
+    // Controls must state the names of the images they use, for the purposes of a future UI editor.
+    //virtual std::vector<std::string> getImageNames() = 0;
+
+    // Control state.
     /**
      * Get the bounds of this control, relative to its parent container, after clipping.
      *
@@ -106,7 +457,7 @@ public:
      * 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.
+     * the Container around STATE_ALL its children.
      *
      * @param width Whether to automatically determine this Control's width.
      * @param height Whether to automatically determine this Control's height.
@@ -125,7 +476,7 @@ public:
      *
      * @return This control's current state.
      */
-    State getState();
+    State getState() const;
 
     /**
      * Disable this control.
@@ -185,6 +536,21 @@ public:
      */
     virtual void addListener(Control::Listener* listener, int eventFlags);
 
+    /**
+     * @see AnimationTarget#getAnimationPropertyComponentCount
+     */
+    unsigned int getAnimationPropertyComponentCount(int propertyId) const;
+
+    /**
+     * @see AnimationTarget#getAnimationProperty
+     */
+    void getAnimationPropertyValue(int propertyId, AnimationValue* value);
+
+    /**
+     * @see AnimationTarget#setAnimationProperty
+     */
+    void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
+
 protected:
     Control();
     virtual ~Control();
@@ -230,6 +596,7 @@ protected:
      */
     virtual void update(const Rectangle& clip);
 
+private:
     /**
      * Draws the themed border and background of a control.
      *
@@ -238,13 +605,14 @@ protected:
      */
     virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
 
+protected:
     /**
-     * Draw the icons associated with this control.
+     * Draw the images associated with this control.
      *
      * @param spriteBatch The sprite batch containing this control's icons.
      * @param clip The clipping rectangle of this control's parent container.
      */
-    virtual void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
+    virtual void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
     /**
      * Draw this control's text.
@@ -254,7 +622,7 @@ protected:
     virtual void drawText(const Rectangle& clip);
 
     /**
-     * Initialize properties common to all Controls.
+     * Initialize properties common to STATE_ALL Controls.
      */
     virtual void init(Theme::Style* style, Properties* properties);
 
@@ -274,7 +642,7 @@ protected:
     static State getStateFromString(const char* state);
 
     /**
-     * Notify all listeners of a specific event.
+     * Notify STATE_ALL listeners of a specific event.
      */
     void notifyListeners(Listener::EventType eventType);
 
@@ -285,7 +653,7 @@ protected:
     Vector2 _position;      // Position, relative to parent container's clipping window.
     Vector2 _size;          // Desired size.  Will be clipped.
     Rectangle _bounds;      // The position and size of this control, relative to parent container's bounds, including border and padding, after clipping.
-    Rectangle _textBounds;  // The position and size of this control's content, before clipping.  Used for text alignment.
+    Rectangle _textBounds;  // The position and size of this control's text area, before clipping.  Used for text alignment.
     Rectangle _clip;        // Clipping window of this control's content, after clipping.
     bool _autoWidth;
     bool _autoHeight;
@@ -296,6 +664,32 @@ protected:
     ListenerMap* _listeners;
 
 private:
+    // Animation blending bits.
+    static const char ANIMATION_POSITION_X_BIT = 0x01;
+    static const char ANIMATION_POSITION_Y_BIT = 0x02;
+    static const char ANIMATION_SIZE_WIDTH_BIT = 0x04;
+    static const char ANIMATION_SIZE_HEIGHT_BIT = 0x08;
+    static const char ANIMATION_OPACITY_BIT = 0x10;
+
+    bool _styleOverridden;
+
+    void applyAnimationValuePositionX(float x, float blendWeight);
+    void applyAnimationValuePositionY(float y, float blendWeight);
+    void applyAnimationValueSizeWidth(float width, float blendWeight);
+    void applyAnimationValueSizeHeight(float height, float blendWeight);
+    void applyAnimationValueOpacity();
+
+    // Gets the overlays requested in the overlayTypes bitflag.
+    Theme::Style::Overlay** getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays);
+
+    /**
+     * Gets an overlay from a control state.
+     */
+    Theme::Style::Overlay* getOverlay(Control::State state) const;
+
+    // Ensures that this control has a copy of its style so that it can override it without affecting other controls.
+    void overrideStyle();
+
     Control(const Control& copy);
 };
 

+ 148 - 1
gameplay/src/DebugNew.cpp

@@ -5,6 +5,15 @@
 #include <cstdio>
 #include <cstdarg>
 
+#ifdef WIN32
+#include <windows.h>
+#include <dbghelp.h>
+#pragma comment(lib,"dbghelp.lib")
+
+#define MAX_STACK_FRAMES 16
+bool __trackStackTrace = false;
+#endif
+
 struct MemoryAllocationRecord
 {
     unsigned long address;          // address returned to the caller after allocation
@@ -13,6 +22,10 @@ struct MemoryAllocationRecord
     int line;                       // source line of the allocation request
     MemoryAllocationRecord* next;
     MemoryAllocationRecord* prev;
+#ifdef WIN32
+    bool trackStackTrace;
+    unsigned int pc[MAX_STACK_FRAMES];
+#endif
 };
 
 MemoryAllocationRecord* __memoryAllocations = 0;
@@ -99,6 +112,54 @@ void* debugAlloc(std::size_t size, const char* file, int line)
     rec->next = __memoryAllocations;
     rec->prev = 0;
 
+    // Capture the stack frame (up to MAX_STACK_FRAMES) if we 
+    // are running on Windows and the user has enabled it.
+#if defined(WIN32)
+    rec->trackStackTrace = __trackStackTrace;
+    if (rec->trackStackTrace)
+    {
+        static bool initialized = false;
+        if (!initialized)
+        {
+            if (!SymInitialize(GetCurrentProcess(), NULL, true))
+                gameplay::printError("Stack trace tracking will not work.");
+            initialized = true;
+        }
+    
+        // Get the current context (state of EBP, EIP, ESP registers).
+        static CONTEXT context;
+        RtlCaptureContext(&context);
+    
+        static STACKFRAME64 stackFrame;
+        memset(&stackFrame, 0, sizeof(STACKFRAME64));
+
+        // Initialize the stack frame based on the machine architecture.
+#ifdef _M_IX86
+        static const DWORD machineType = IMAGE_FILE_MACHINE_I386;
+        stackFrame.AddrPC.Offset = context.Eip;
+        stackFrame.AddrPC.Mode = AddrModeFlat;
+        stackFrame.AddrFrame.Offset = context.Ebp;
+        stackFrame.AddrFrame.Mode = AddrModeFlat;
+        stackFrame.AddrStack.Offset = context.Esp;
+        stackFrame.AddrStack.Mode = AddrModeFlat;
+#else
+#error "Machine architecture not supported!"
+#endif
+
+        // Walk up the stack and store the program counters.
+        memset(rec->pc, 0, sizeof(rec->pc));
+        for (int i = 0; i < MAX_STACK_FRAMES; i++)
+        {
+            rec->pc[i] = stackFrame.AddrPC.Offset;
+            if (!StackWalk64(machineType, GetCurrentProcess(), GetCurrentThread(), &stackFrame,
+                &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
+            {
+                break;
+            }
+        }
+    }
+#endif
+
     if (__memoryAllocations)
         __memoryAllocations->prev = rec;
     __memoryAllocations = rec;
@@ -137,6 +198,70 @@ void debugFree(void* p)
     free(mem);
 }
 
+#ifdef WIN32
+void printStackTrace(MemoryAllocationRecord* rec)
+{
+    const unsigned int bufferSize = 512;
+
+    // Resolve the program counter to the corresponding function names.
+    unsigned int pc;
+    for (int i = 0; i < MAX_STACK_FRAMES; i++)
+    {
+        // Check to see if we are at the end of the stack trace.
+        pc = rec->pc[i];
+        if (pc == 0)
+            break;
+
+        // Get the function name.
+        unsigned char buffer[sizeof(IMAGEHLP_SYMBOL64) + bufferSize];
+        IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*)buffer;
+        DWORD64 displacement;
+        memset(symbol, 0, sizeof(IMAGEHLP_SYMBOL64) + bufferSize);
+        symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
+        symbol->MaxNameLength = bufferSize;
+        if (!SymGetSymFromAddr64(GetCurrentProcess(), pc, &displacement, symbol))
+        {
+            gameplay::printError("[memory] STACK TRACE: <unknown location>");
+        }
+        else
+        {
+            symbol->Name[bufferSize - 1] = '\0';
+
+            // Check if we need to go further up the stack.
+            if (strncmp(symbol->Name, "operator new", 12) == 0)
+            {
+                // In operator new or new[], keep going...
+            }
+            else
+            {
+                // Get the file and line number.
+                if (pc != 0)
+                {
+                    IMAGEHLP_LINE64 line;
+                    DWORD displacement;
+                    memset(&line, 0, sizeof(line));
+                    line.SizeOfStruct = sizeof(line);
+                    if (!SymGetLineFromAddr64(GetCurrentProcess(), pc, &displacement, &line))
+                    {
+                        gameplay::printError("[memory] STACK TRACE: %s - <unknown file>:<unknown line number>", symbol->Name);
+                    }
+                    else
+                    {
+                        const char* file = strrchr(line.FileName, '\\');
+                        if(!file) 
+                            file = line.FileName;
+                        else
+                            file++;
+                        
+                        gameplay::printError("[memory] STACK TRACE: %s - %s:%d", symbol->Name, file, line.LineNumber);
+                    }
+                }
+            }
+        }
+    }
+}
+#endif
+
 extern void printMemoryLeaks()
 {
     // Dump general heap memory leaks
@@ -150,10 +275,32 @@ extern void printMemoryLeaks()
         MemoryAllocationRecord* rec = __memoryAllocations;
         while (rec)
         {
-            gameplay::printError("[memory] LEAK: HEAP allocation leak of size %d leak from line %d in file '%s'.", rec->size, rec->line, rec->file);
+#ifdef WIN32
+            if (rec->trackStackTrace)
+            {
+                gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d:", rec->address, rec->size);
+                printStackTrace(rec);
+            }
+            else
+                gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d from line %d in file '%s'.", rec->address, rec->size, rec->line, rec->file);
+#else
+            gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d from line %d in file '%s'.", rec->address, rec->size, rec->line, rec->file);
+#endif
             rec = rec->next;
         }
     }
 }
 
+#if defined(WIN32)
+void setTrackStackTrace(bool trackStackTrace)
+{
+    __trackStackTrace = trackStackTrace;
+}
+
+void toggleTrackStackTrace()
+{
+    __trackStackTrace = !__trackStackTrace;
+}
+#endif
+
 #endif

+ 14 - 0
gameplay/src/DebugNew.h

@@ -89,4 +89,18 @@ T* bullet_new(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9)
 #endif
 }
 
+#if defined(WIN32)
+/**
+ * Sets whether stack traces are tracked on memory allocations or not.
+ * 
+ * @param trackStackTrace Whether to track the stack trace on memory allocations.
+ */
+void setTrackStackTrace(bool trackStackTrace);
+
+/**
+ * Toggles stack trace tracking on memory allocations.
+ */
+void toggleTrackStackTrace();
+#endif
+
 #endif

+ 1 - 1
gameplay/src/FileSystem.cpp

@@ -110,7 +110,7 @@ bool FileSystem::listFiles(const char* dirPath, std::vector<std::string>& files)
             filename.assign(wfilename.begin(), wfilename.end());
             files.push_back(filename);
         }
-    } while(FindNextFile(hFind, &FindFileData) != 0);
+    } while (FindNextFile(hFind, &FindFileData) != 0);
 
     FindClose(hFind);
     return true;

+ 15 - 8
gameplay/src/Font.h

@@ -149,16 +149,16 @@ public:
      * @param rightToLeft Whether to draw text from right to left.
      * @param clip A region to clip text within after applying justification to the viewport area.
      */
-    void drawText(const char* text, const Rectangle& area, const Vector4& color,
-        unsigned int size = 0, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false, const Rectangle* clip = NULL);
+    void drawText(const char* text, const Rectangle& area, const Vector4& color, unsigned int size = 0, 
+				  Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false, const Rectangle* clip = NULL);
 
     /**
      * Measures a string's width and height without alignment, wrapping or clipping.
      *
      * @param text The text to measure.
      * @param size
-     * @param width Destination for the text's width.
-     * @param height Destination for the text's height.
+     * @param widthOut Destination for the text's width.
+     * @param heightOut Destination for the text's height.
      */
     void measureText(const char* text, unsigned int size, unsigned int* widthOut, unsigned int* heightOut);
 
@@ -177,11 +177,15 @@ 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.
+    /**
+     * 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.
+    /**
+     * 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);
 
@@ -213,12 +217,14 @@ private:
 
     // 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 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);
+                         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);
 
@@ -231,6 +237,7 @@ private:
     unsigned int _glyphCount;
     Texture* _texture;
     SpriteBatch* _batch;
+	Rectangle _viewport;
 };
 
 }

+ 15 - 15
gameplay/src/Form.cpp

@@ -13,7 +13,7 @@ namespace gameplay
 {
     static std::vector<Form*> __forms;
 
-    Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL), _viewport(NULL)
+    Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL)
     {
     }
 
@@ -27,7 +27,6 @@ namespace gameplay
         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);
@@ -45,9 +44,7 @@ namespace gameplay
         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();
@@ -78,7 +75,7 @@ namespace gameplay
     Form* Form::create(const char* themeFile, Layout::Type type)
     {
         Layout* layout;
-        switch(type)
+        switch (type)
         {
         case Layout::LAYOUT_ABSOLUTE:
             layout = AbsoluteLayout::create();
@@ -121,14 +118,14 @@ namespace gameplay
     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);
+        initializeQuad(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);
+        initializeQuad(mesh);
         SAFE_RELEASE(mesh);
     }
 
@@ -143,7 +140,6 @@ namespace gameplay
 
             Matrix::createOrthographicOffCenter(0, _size.x, _size.y, 0, 0, 1, &_projectionMatrix);
             _theme->setProjectionMatrix(_projectionMatrix);
-            _viewport = new Viewport(0, 0, _size.x, _size.y);
             
             _node->setModel(_quad);
         }
@@ -171,14 +167,19 @@ namespace gameplay
             if (isDirty())
             {
                 _frameBuffer->bind();
-                _viewport->bind();
+
+				Game* game = Game::getInstance();
+				Rectangle prevViewport = game->getViewport();
+                
+				game->setViewport(Rectangle(_position.x, _position.y, _size.x, _size.y));
 
                 draw(_theme->getSpriteBatch(), _clip);
 
                 // Rebind the default framebuffer and game viewport.
                 FrameBuffer::bindDefault();
-                Game* game = Game::getInstance();
-                GL_ASSERT( glViewport(0, 0, game->getWidth(), game->getHeight()) );
+
+				// restore the previous game viewport
+				game->setViewport(prevViewport);
             }
 
             _quad->draw();
@@ -210,7 +211,7 @@ namespace gameplay
                 control->drawBorder(spriteBatch, clip);
 
                 // Add all themed foreground sprites (checkboxes etc.) to the same batch.
-                control->drawSprites(spriteBatch, clip);
+                control->drawImages(spriteBatch, clip);
             }
         }
         spriteBatch->end();
@@ -229,7 +230,7 @@ namespace gameplay
         _dirty = false;
     }
 
-    void Form::initQuad(Mesh* mesh)
+    void Form::initializeQuad(Mesh* mesh)
     {
         // Release current model.
         SAFE_RELEASE(_quad);
@@ -266,7 +267,6 @@ namespace gameplay
         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());
 
@@ -299,7 +299,7 @@ namespace gameplay
 
                         // Unproject point into world space.
                         Ray ray;
-                        camera->pickRay(NULL, x, y, &ray);
+						camera->pickRay(Game::getInstance()->getViewport(), x, y, &ray);
 
                         // Find the quad's plane.
                         // We know its normal is the quad's forward vector.

+ 6 - 3
gameplay/src/Form.h

@@ -22,6 +22,7 @@ class Form : public Container
     friend class Platform;
 
 public:
+
     /**
      * Create from properties file.
      * The top-most namespace in the file must be named 'form'.  The following properties are available for forms:
@@ -105,7 +106,9 @@ public:
     void draw();
 
 protected:
-    Form();
+    
+	Form();
+
     virtual ~Form();
 
     static Form* create(const char* textureFile, Layout::Type type);
@@ -115,7 +118,7 @@ protected:
      *
      * @param mesh The mesh to create a model from.
      */
-    void initQuad(Mesh* mesh);
+    void initializeQuad(Mesh* mesh);
 
     /**
      * Draw this form into the current framebuffer.
@@ -142,9 +145,9 @@ protected:
     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.
 
 private:
+
     Form(const Form& copy);
 };
 

+ 26 - 6
gameplay/src/Game.cpp

@@ -17,10 +17,11 @@ Game::Game()
     : _initialized(false), _state(UNINITIALIZED), 
       _frameLastFPS(0), _frameCount(0), _frameRate(0), 
       _clearDepth(1.0f), _clearStencil(0),
-      _animationController(NULL), _audioController(NULL)
+      _animationController(NULL), _audioController(NULL), _physicsController(NULL), _audioListener(NULL)
 {
     assert(__gameInstance == NULL);
     __gameInstance = this;
+    _timeEvents = new std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> >();
 }
 
 Game::Game(const Game& copy)
@@ -31,7 +32,7 @@ Game::~Game()
 {
     // Do not call any virtual functions from the destructor.
     // Finalization is done from outside this class.
-
+    delete _timeEvents;
 #ifdef GAMEPLAY_MEM_LEAK_DETECTION
     Ref::printLeaks();
     printMemoryLeaks();
@@ -92,6 +93,8 @@ bool Game::startup()
     if (_state != UNINITIALIZED)
         return false;
 
+	setViewport(Rectangle(0.0f, 0.0f, (float)_width, (float)_height));
+
     RenderState::initialize();
 
     _animationController = new AnimationController();
@@ -124,6 +127,8 @@ void Game::shutdown()
         _physicsController->finalize();
         SAFE_DELETE(_physicsController);
 
+        SAFE_DELETE(_audioListener);
+
         RenderState::finalize();
     }
 
@@ -210,6 +215,12 @@ void Game::frame()
     }
 }
 
+void Game::setViewport(const Rectangle& viewport)
+{
+	_viewport = viewport;
+	glViewport((GLuint)viewport.x, (GLuint)viewport.y, (GLuint)viewport.width, (GLuint)viewport.height); 
+}
+
 void Game::clear(ClearFlags flags, const Vector4& clearColor, float clearDepth, int clearStencil)
 {
     GLbitfield bits = 0;
@@ -253,6 +264,15 @@ void Game::clear(ClearFlags flags, const Vector4& clearColor, float clearDepth,
     glClear(bits);
 }
 
+AudioListener* Game::getAudioListener()
+{
+    if (_audioListener == NULL)
+    {
+        _audioListener = new AudioListener();
+    }
+    return _audioListener;
+}
+
 void Game::menu()
 {
 }
@@ -269,7 +289,7 @@ void Game::schedule(long timeOffset, TimeListener* timeListener, void* cookie)
 {
     assert(timeListener);
     TimeEvent timeEvent(getGameTime() + timeOffset, timeListener, cookie);
-    _timeEvents.push(timeEvent);
+    _timeEvents->push(timeEvent);
 }
 
 bool Game::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
@@ -293,15 +313,15 @@ void Game::updateOnce()
 
 void Game::fireTimeEvents(long frameTime)
 {
-    while (_timeEvents.size() > 0)
+    while (_timeEvents->size() > 0)
     {
-        const TimeEvent* timeEvent = &_timeEvents.top();
+        const TimeEvent* timeEvent = &_timeEvents->top();
         if (timeEvent->time > frameTime)
         {
             break;
         }
         timeEvent->listener->timeEvent(frameTime - timeEvent->time, timeEvent->cookie);
-        _timeEvents.pop();
+        _timeEvents->pop();
     }
 }
 

+ 75 - 1
gameplay/src/Game.h

@@ -9,6 +9,8 @@
 #include "AudioController.h"
 #include "AnimationController.h"
 #include "PhysicsController.h"
+#include "AudioListener.h"
+#include "Rectangle.h"
 #include "Vector4.h"
 #include "TimeListener.h"
 
@@ -150,6 +152,22 @@ public:
      */
     inline unsigned int getHeight() const;
 
+	/**
+	 * Gets the game current viewport.
+	 *
+	 * The default viewport is Rectangle(0, 0, Game::getWidth(), Game::getHeight()).
+	 */
+	inline const Rectangle& getViewport() const;
+
+	/**
+	 * Set the game current viewport.
+	 *
+	 * The x, y, width and height of the viewport must all be positive.
+	 *
+	 * viewport The custom viewport to be set on the game.
+	 */
+	void setViewport(const Rectangle& viewport);
+
     /**
      * Clears the specified resource buffers to the specified clear values. 
      *
@@ -184,6 +202,13 @@ public:
      */
     inline PhysicsController* getPhysicsController() const;
 
+    /**
+     * Gets the audio listener for 3D audio.
+     * 
+     * @return The audio listener for this game.
+     */
+    AudioListener* getAudioListener();
+
     /**
      * Menu callback on menu events.
      */
@@ -377,15 +402,64 @@ private:
     unsigned int _frameRate;                    // The current frame rate.
     unsigned int _width;                        // The game's display width.
     unsigned int _height;                       // The game's display height.
+	Rectangle _viewport;						// the games's current viewport.
     Vector4 _clearColor;                        // The clear color value last used for clearing the color buffer.
     float _clearDepth;                          // The clear depth value last used for clearing the depth buffer.
     int _clearStencil;                          // The clear stencil value last used for clearing the stencil buffer.
     AnimationController* _animationController;  // Controls the scheduling and running of animations.
     AudioController* _audioController;          // Controls audio sources that are playing in the game.
     PhysicsController* _physicsController;      // Controls the simulation of a physics scene and entities.
-    std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> > _timeEvents; // Contains the scheduled time events.
+    AudioListener* _audioListener;              // The audio listener in 3D space.
+    std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> >* _timeEvents; // Contains the scheduled time events.
+
+    // Note: Do not add STL object member variables on the stack; this will cause false memory leaks to be reported.
+
+    friend class SplashDisplayer;
+};
+
+/**
+ * Used for displaying splash screens.
+ */
+class SplashDisplayer
+{
+public:
+
+    /**
+     * Displays a splash screen using the {@link Game#renderOnce} mechanism for at least the given amount of time.
+     * 
+     * @param instance See {@link Game#renderOnce}.
+     * @param method See {@link Game#renderOnce}.
+     * @param cookie See {@link Game#renderOnce}.
+     * @param time The minimum amount of time to display the splash screen (in milliseconds).
+     */
+    template <typename T> void run(T* instance, void (T::*method) (void*), void* cookie, long time);
+
+    /**
+     * Destructor.
+     */
+    ~SplashDisplayer();
+
+private:
+
+    long _time;
+    long _startTime;
 };
 
+/**
+ * Displays a splash screen using the {@link Game#renderOnce} mechanism for at least the given amount
+ * of time. This function is intended to be called at the beginning of a block of code that is be 
+ * executed while the splash screen is displayed (i.e. Game#initialize). This function will block 
+ * at the end of the block of code in which it is called for the amount of time that has not yet elapsed.
+ * 
+ * @param instance See {@link Game#renderOnce}.
+ * @param method See {@link Game#renderOnce}.
+ * @param cookie See {@link Game#renderOnce}.
+ * @param time The minimum amount of time to display the splash screen (in milliseconds).
+ */
+#define displaySplash(instance, method, cookie, time) \
+    SplashDisplayer __##instance##SplashDisplayer; \
+    __##instance##SplashDisplayer.run(instance, method, cookie, time)
+
 }
 
 #include "Game.inl"

+ 19 - 0
gameplay/src/Game.inl

@@ -24,6 +24,11 @@ inline unsigned int Game::getHeight() const
     return _height;
 }
 
+inline const Rectangle& Game::getViewport() const
+{
+	return _viewport;
+}
+
 inline AnimationController* Game::getAnimationController() const
 {
     return _animationController;
@@ -66,4 +71,18 @@ inline void Game::displayKeyboard(bool display)
     Platform::displayKeyboard(display);
 }
 
+template <typename T> void SplashDisplayer::run(T* instance, void (T::*method) (void*), void* cookie, long time)
+{
+    _time = time;
+    Game::getInstance()->renderOnce(instance, method, cookie);
+    _startTime = Game::getInstance()->getGameTime();
+}
+
+inline SplashDisplayer::~SplashDisplayer()
+{
+    long elapsedTime = Game::getInstance()->getGameTime() - _startTime;
+    if (elapsedTime < _time)
+        Platform::sleep(_time - (Game::getInstance()->getGameTime() - _startTime));
+}
+
 }

+ 10 - 0
gameplay/src/Joint.cpp

@@ -19,6 +19,16 @@ Joint* Joint::create(const char* id)
     return new Joint(id);
 }
 
+Node* Joint::cloneSingleNode(NodeCloneContext &context) const
+{
+    Joint* copy = Joint::create(getId());
+    context.registerClonedNode(this, copy);
+    copy->_bindPose = _bindPose;
+    copy->_skinCount = _skinCount;
+    Node::cloneInto(copy, context);
+    return copy;
+}
+
 Node::Type Joint::getType() const
 {
     return Node::JOINT;

+ 24 - 0
gameplay/src/Joint.h

@@ -53,6 +53,16 @@ protected:
      */
     static Joint* create(const char* id);
 
+    /**
+     * Clones a single node and its data but not its children.
+     * This method returns a node pointer but actually creates a Joint.
+     * 
+     * @param context The clone context.
+     * 
+     * @return Pointer to the newly created joint.
+     */
+    virtual Node* cloneSingleNode(NodeCloneContext &context) const;
+
     /**
      * Sets the inverse bind pose matrix.
      * 
@@ -64,6 +74,20 @@ protected:
 
     void transformChanged();
 
+private:
+
+    /**
+     * Hidden copy constructor.
+     */
+    Joint(const Joint& copy);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    Joint& operator=(const Joint&);
+
+protected:
+
     Matrix _bindPose;
     bool _jointMatrixDirty;
     unsigned int _skinCount;

+ 4 - 1
gameplay/src/Label.cpp

@@ -73,9 +73,12 @@ namespace gameplay
         Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
         Font* font = overlay->getFont();
 
+        Vector4 textColor = overlay->getTextColor();
+        textColor.w *= overlay->getOpacity();
+
         // Draw the text.
         font->begin();
-        font->drawText(_text.c_str(), _textBounds, overlay->getTextColor(), overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft(), &_clip);
+        font->drawText(_text.c_str(), _textBounds, textColor, overlay->getFontSize(), overlay->getTextAlignment(), true, overlay->getTextRightToLeft(), &_clip);
         font->end();
 
         _dirty = false;

+ 26 - 0
gameplay/src/Light.cpp

@@ -196,6 +196,32 @@ float Light::getOuterAngleCos()  const
     return _spot->outerAngleCos;
 }
 
+Light* Light::clone(NodeCloneContext &context) const
+{
+    Light* lightClone = NULL;
+    switch (_type)
+    {
+    case DIRECTIONAL:
+        lightClone = createDirectional(getColor());
+        break;
+    case POINT:
+        lightClone = createPoint(getColor(), getRange());
+        break;
+    case SPOT:
+        lightClone = createSpot(getColor(), getRange(), getInnerAngle(), getOuterAngle());
+        break;
+    default:
+        assert(false);
+    }
+    assert(lightClone);
+
+    if (Node* node = context.findClonedNode(getNode()))
+    {
+        lightClone->setNode(node);
+    }
+    return lightClone;
+}
+
 Light::Directional::Directional(const Vector3& color)
     : color(color)
 {

+ 15 - 5
gameplay/src/Light.h

@@ -8,6 +8,7 @@ namespace gameplay
 {
 
 class Node;
+class NodeCloneContext;
 
 /**
  * Defines a light.
@@ -54,8 +55,8 @@ public:
      * 
      * @param color The light's color.
      * @param range The light's range.
-     * @param innerCosAngle The light's inner angle (in radians).
-     * @param outerCosAngle The light's outer angle (in radians).
+     * @param innerAngle The light's inner angle (in radians).
+     * @param outerAngle The light's outer angle (in radians).
      * 
      * @return The new spot light.
      */
@@ -104,7 +105,7 @@ public:
     /**
      * Sets the range of point or spot light.
      *
-     * @param range of point or spot light.
+     * @param range The range of point or spot light.
      */
     void setRange(float range);
 
@@ -125,7 +126,7 @@ public:
     /**
      * Sets the inner angle of a spot light (in radians).
      *
-     * @param inner angle of spot light (in radians).
+     * @param innerAngle The angle of spot light (in radians).
      */
     void setInnerAngle(float innerAngle);
 
@@ -139,7 +140,7 @@ public:
     /**
      * Sets the outer angle of a spot light (in radians).
      *
-     * @param outer angle of spot light (in radians).
+     * @param outerAngle The angle of spot light (in radians).
      */
     void setOuterAngle(float outerAngle);
 
@@ -223,6 +224,15 @@ private:
      */
     void setNode(Node* node);
 
+    /**
+     * Clones the light and returns a new light.
+     * 
+     * @param context The clone context.
+     * 
+     * @return The newly created light.
+     */
+    Light* clone(NodeCloneContext &context) const;
+
     Light::Type _type;
     union
     {

+ 19 - 0
gameplay/src/Material.cpp

@@ -5,6 +5,7 @@
 #include "Technique.h"
 #include "Pass.h"
 #include "Properties.h"
+#include "Node.h"
 
 namespace gameplay
 {
@@ -125,6 +126,24 @@ Material* Material::create(const char* vshPath, const char* fshPath, const char*
     return material;
 }
 
+Material* Material::clone(NodeCloneContext &context) const
+{
+    Material* material = new Material();
+    RenderState::cloneInto(material, context);
+
+    for (std::vector<Technique*>::const_iterator it = _techniques.begin(); it != _techniques.end(); ++it)
+    {
+        const Technique* technique = *it;
+        Technique* techniqueClone = technique->clone(material, context);
+        material->_techniques.push_back(techniqueClone);
+        if (_currentTechnique == technique)
+        {
+            material->_currentTechnique = techniqueClone;
+        }
+    }
+    return material;
+}
+
 unsigned int Material::getTechniqueCount() const
 {
     return _techniques.size();

+ 11 - 0
gameplay/src/Material.h

@@ -8,6 +8,8 @@
 namespace gameplay
 {
 
+class NodeCloneContext;
+
 /**
  * Defines a material for an object to be rendered.
  *
@@ -68,6 +70,15 @@ public:
      */
     static Material* create(const char* vshPath, const char* fshPath, const char* defines = NULL);
 
+    /**
+     * Clones this material.
+     * 
+     * @param context The clone context.
+     * 
+     * @return The newly created material.
+     */
+    Material* clone(NodeCloneContext &context) const;
+
     /**
      * Returns the number of techniques in the material.
      *

+ 80 - 2
gameplay/src/MaterialParameter.cpp

@@ -32,7 +32,7 @@ void MaterialParameter::clearValue()
             SAFE_DELETE_ARRAY(_value.intPtrValue);
             break;
         case MaterialParameter::METHOD:
-            SAFE_DELETE(_value.method);
+            SAFE_RELEASE(_value.method);
             break;
         }
 
@@ -294,7 +294,7 @@ unsigned int MaterialParameter::getAnimationPropertyComponentCount(int propertyI
     {
         case ANIMATE_UNIFORM:
         {
-            switch(_type)
+            switch (_type)
             {
                 // These types don't support animation.
                 case NONE:
@@ -478,4 +478,82 @@ void MaterialParameter::applyAnimationValue(AnimationValue* value, float blendWe
     }
 }
 
+void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
+{
+    materialParameter->_type = _type;
+    materialParameter->_count = _count;
+    materialParameter->_dynamic = _dynamic;
+    materialParameter->_uniform = _uniform;
+    switch (_type)
+    {
+    case NONE:
+        break;
+    case FLOAT:
+        materialParameter->setValue(_value.floatValue);
+        break;
+    case INT:
+        materialParameter->setValue(_value.intValue);
+        break;
+    case VECTOR2:
+    {
+        Vector2* value = reinterpret_cast<Vector2*>(_value.floatPtrValue);
+        if (_count == 1)
+        {
+            materialParameter->setValue(*value);
+        }
+        else
+        {
+            materialParameter->setValue(value, _count);
+        }
+        break;
+    }   
+    case VECTOR3:
+    {
+        Vector3* value = reinterpret_cast<Vector3*>(_value.floatPtrValue);
+        if (_count == 1)
+        {
+            materialParameter->setValue(*value);
+        }
+        else
+        {
+            materialParameter->setValue(value, _count);
+        }
+        break;
+    }
+    case VECTOR4:
+    {
+        Vector4* value = reinterpret_cast<Vector4*>(_value.floatPtrValue);
+        if (_count == 1)
+        {
+            materialParameter->setValue(*value);
+        }
+        else
+        {
+            materialParameter->setValue(value, _count);
+        }
+        break;
+    }
+    case MATRIX:
+    {
+        Matrix* value = reinterpret_cast<Matrix*>(_value.floatPtrValue);
+        if (_count == 1)
+        {
+            materialParameter->setValue(*value);
+        }
+        else
+        {
+            materialParameter->setValue(value, _count);
+        }
+        break;
+    }
+    case SAMPLER:
+        materialParameter->setValue(_value.samplerValue);
+        break;
+    case METHOD:
+        materialParameter->_value.method = _value.method;
+        materialParameter->_value.method->addRef();
+        break;
+    }
+}
+
 }

+ 11 - 2
gameplay/src/MaterialParameter.h

@@ -171,14 +171,16 @@ private:
     /**
      * Interface implemented by templated method bindings for simple storage and iteration.
      */
-    class MethodBinding
+    class MethodBinding : public Ref
     {
     public:
+        virtual void setValue(Effect* effect) = 0;
+
+    protected:
         /**
          * Destructor.
          */
         virtual ~MethodBinding() { }
-        virtual void setValue(Effect* effect) = 0;
     };
 
     /**
@@ -257,6 +259,13 @@ private:
 
     void applyAnimationValue(AnimationValue* value, float blendWeight, int components);
 
+    /**
+     * Copies the data from this MaterialParameter into the given MaterialParameter.
+     * 
+     * @param materialParameter The MaterialParameter to copy the data to.
+     */
+    void cloneInto(MaterialParameter* materialParameter) const;
+
     unsigned int _count;
     bool _dynamic;
     std::string _name;

+ 1 - 1
gameplay/src/MeshBatch.h

@@ -54,7 +54,7 @@ public:
     /**
      * Explicitly sets a new capacity for the batch.
      *
-     * @param The new batch capacity.
+     * @param capacity The new batch capacity.
      */
     void setCapacity(unsigned int capacity);
 

+ 60 - 1
gameplay/src/MeshSkin.cpp

@@ -9,7 +9,7 @@ namespace gameplay
 {
 
 MeshSkin::MeshSkin()
-    : _rootJoint(NULL), _matrixPalette(NULL), _model(NULL)
+    : _rootJoint(NULL), _rootNode(NULL), _matrixPalette(NULL), _model(NULL)
 {
 }
 
@@ -57,6 +57,37 @@ Joint* MeshSkin::getJoint(const char* id) const
     return NULL;
 }
 
+MeshSkin* MeshSkin::clone() const
+{
+    MeshSkin* skin = new MeshSkin();
+    skin->_bindShape = _bindShape;
+    if (_rootNode && _rootJoint)
+    {
+        const unsigned int jointCount = getJointCount();
+        skin->setJointCount(jointCount);
+
+        assert(skin->_rootNode == NULL);
+        skin->_rootNode = _rootNode->clone();
+        Node* node = skin->_rootNode->findNode(_rootJoint->getId());
+        assert(node);
+        skin->_rootJoint = static_cast<Joint*>(node);
+        for (unsigned int i = 0; i < jointCount; ++i)
+        {
+            Joint* oldJoint = getJoint(i);
+            
+            Joint* newJoint = static_cast<Joint*>(skin->_rootJoint->findNode(oldJoint->getId()));
+            if (!newJoint)
+            {
+                if (strcmp(skin->_rootJoint->getId(), oldJoint->getId()) == 0)
+                    newJoint = static_cast<Joint*>(skin->_rootJoint);
+            }
+            assert(newJoint);
+            skin->setJoint(newJoint, i);
+        }
+    }
+    return skin;
+}
+
 void MeshSkin::setJointCount(unsigned int jointCount)
 {
     // Erase the joints vector and release all joints
@@ -145,6 +176,21 @@ void MeshSkin::setRootJoint(Joint* joint)
     {
         _rootJoint->getParent()->addListener(this, 1);
     }
+
+    Node* newRootNode = _rootJoint;
+    if (newRootNode)
+    {
+        // Find the top level parent node of the root joint
+        for (Node* node = newRootNode->getParent(); node != NULL; node = node->getParent())
+        {
+            if (node->getParent() == NULL)
+            {
+                newRootNode = node;
+                break;
+            }
+        }
+    }
+    setRootNode(newRootNode);
 }
 
 void MeshSkin::transformChanged(Transform* transform, long cookie)
@@ -179,6 +225,19 @@ int MeshSkin::getJointIndex(Joint* joint) const
     return -1;
 }
 
+void MeshSkin::setRootNode(Node* node)
+{
+    if (_rootNode != node)
+    {
+        SAFE_RELEASE(_rootNode);
+        _rootNode = node;
+        if (_rootNode)
+        {
+            _rootNode->addRef();
+        }
+    }
+}
+
 void MeshSkin::clearJoints()
 {
     setRootJoint(NULL);

+ 29 - 0
gameplay/src/MeshSkin.h

@@ -10,6 +10,7 @@ namespace gameplay
 class Package;
 class Model;
 class Joint;
+class Node;
 
 /**
  * Represents the skin for a mesh.
@@ -115,10 +116,27 @@ private:
      */
     MeshSkin();
 
+    /**
+     * Hidden copy constructor.
+     */
+    MeshSkin(const MeshSkin&);
+
     /**
      * Destructor.
      */
     ~MeshSkin();
+    
+    /**
+     * Hidden copy assignment operator.
+     */
+    MeshSkin& operator=(const MeshSkin&);
+
+    /**
+     * Clones the MeshSkin and the joints that it references.
+     * 
+     * @return The newly created MeshSkin.
+     */
+    MeshSkin* clone() const;
 
     /**
      * Sets the number of joints that can be stored in this skin.
@@ -136,6 +154,13 @@ private:
      */
     void setJoint(Joint* joint, unsigned int index);
 
+    /**
+     * Sets the root node of this mesh skin.
+     * 
+     * @param node The node to set as the root node, may be NULL.
+     */
+    void setRootNode(Node* node);
+
     /**
      * Clears the list of joints and releases each joint.
      */
@@ -144,6 +169,10 @@ private:
     Matrix _bindShape;
     std::vector<Joint*> _joints;
     Joint* _rootJoint;
+    // Pointer to the root node of the mesh skin.
+    // The purpose is so that the joint hierarchy doesn't need to be in the scene.
+    // If the joints are not in the scene then something has to hold a reference to it.
+    Node* _rootNode;
 
     // Pointer to the array of palette matrices.
     // This array is passed to the vertex shader as a uniform.

+ 17 - 2
gameplay/src/Model.cpp

@@ -4,6 +4,7 @@
 #include "Scene.h"
 #include "Technique.h"
 #include "Pass.h"
+#include "Node.h"
 
 namespace gameplay
 {
@@ -195,7 +196,7 @@ bool Model::hasMaterial(unsigned int partIndex) const
     return (partIndex < _partCount && _partMaterials && _partMaterials[partIndex]);
 }
 
-MeshSkin* Model::getSkin()
+MeshSkin* Model::getSkin() const
 {
     return _skin;
 }
@@ -209,7 +210,8 @@ void Model::setSkin(MeshSkin* skin)
 
         // Assign the new skin
         _skin = skin;
-        _skin->_model = this;
+        if (_skin)
+            _skin->_model = this;
     }
 }
 
@@ -357,6 +359,19 @@ void Model::validatePartCount()
     }
 }
 
+Model* Model::clone(NodeCloneContext &context)
+{
+    Model* model = Model::create(getMesh());
+    if (getSkin())
+    {
+        model->setSkin(getSkin()->clone());
+    }
+    Material* materialClone = getMaterial()->clone(context);
+    model->setMaterial(materialClone); // TODO: Don't forget material parts
+    materialClone->release();
+    return model;
+}
+
 void Model::setMaterialNodeBinding(Material *material)
 {
     if (_node)

+ 12 - 2
gameplay/src/Model.h

@@ -4,13 +4,14 @@
 #include "Mesh.h"
 #include "MeshSkin.h"
 #include "Material.h"
-#include "Node.h"
 
 namespace gameplay
 {
 
 class Package;
 class MeshSkin;
+class Node;
+class NodeCloneContext;
 
 /**
  * Defines a Model which is an instance of a Mesh that can be drawn
@@ -123,7 +124,7 @@ public:
      * 
      * @return The MeshSkin, or NULL if one is not set.
      */
-    MeshSkin* getSkin();
+    MeshSkin* getSkin() const;
 
     /**
      * Returns the node that is associated with this model.
@@ -177,6 +178,15 @@ private:
 
     void validatePartCount();
 
+    /**
+     * Clones the model and returns a new model.
+     * 
+     * @param context The clone context.
+     * 
+     * @return The new cloned model.
+     */
+    Model* clone(NodeCloneContext &context);
+
     Mesh* _mesh;
     Material* _material;
     unsigned int _partCount;

+ 233 - 32
gameplay/src/Node.cpp

@@ -2,18 +2,27 @@
 #include "Node.h"
 #include "Scene.h"
 #include "Joint.h"
+#include "PhysicsRigidBody.h"
+#include "PhysicsGhostObject.h"
+#include "PhysicsCharacter.h"
+#include "Game.h"
 
+// Node dirty flags
 #define NODE_DIRTY_WORLD 1
 #define NODE_DIRTY_BOUNDS 2
 #define NODE_DIRTY_ALL (NODE_DIRTY_WORLD | NODE_DIRTY_BOUNDS)
 
+// Node property flags
+#define NODE_FLAG_VISIBLE 1
+#define NODE_FLAG_TRANSPARENT 2
+
 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), _form(NULL), _audioSource(NULL), _particleEmitter(NULL), _physicsRigidBody(NULL), 
-    _ghostObject(NULL), _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true)
+    _nodeFlags(NODE_FLAG_VISIBLE), _camera(NULL), _light(NULL), _model(NULL), _form(NULL), _audioSource(NULL), _particleEmitter(NULL),
+	_collisionObject(NULL), _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true), _userData(NULL)
 {
     if (id)
     {
@@ -21,11 +30,6 @@ Node::Node(const char* id)
     }
 }
 
-Node::Node(const Node& node)
-{
-    // hidden
-}
-
 Node::~Node()
 {
     removeAllChildren();
@@ -45,8 +49,16 @@ Node::~Node()
     SAFE_RELEASE(_audioSource);
     SAFE_RELEASE(_particleEmitter);
     SAFE_RELEASE(_form);
-    SAFE_DELETE(_physicsRigidBody);
-    SAFE_DELETE(_ghostObject);
+    SAFE_DELETE(_collisionObject);
+
+    // Cleanup user data
+    if (_userData)
+    {
+        // Call custom cleanup callback if specified
+        if (_userData->cleanupCallback)
+            _userData->cleanupCallback(_userData->pointer);
+        SAFE_DELETE(_userData);
+    }
 }
 
 Node* Node::create(const char* id)
@@ -197,12 +209,67 @@ Node* Node::getParent() const
     return _parent;
 }
 
+bool Node::isVisible() const
+{
+    return ((_nodeFlags & NODE_FLAG_VISIBLE) == NODE_FLAG_VISIBLE);
+}
+
+void Node::setVisible(bool visible)
+{
+    if (visible)
+        _nodeFlags |= NODE_FLAG_VISIBLE;
+    else
+        _nodeFlags &= ~NODE_FLAG_VISIBLE;
+}
+
+bool Node::isTransparent() const
+{
+    return ((_nodeFlags & NODE_FLAG_TRANSPARENT) == NODE_FLAG_TRANSPARENT);
+}
+
+void Node::setTransparent(bool transparent)
+{
+    if (transparent)
+        _nodeFlags |= NODE_FLAG_TRANSPARENT;
+    else
+        _nodeFlags &= ~NODE_FLAG_TRANSPARENT;
+}
+
+void* Node::getUserPointer() const
+{
+    return (_userData ? _userData->pointer : NULL);
+}
+
+void Node::setUserPointer(void* pointer, void (*cleanupCallback)(void*))
+{
+    // If existing user pointer is being changed, call cleanup function to free previous pointer
+    if (_userData && _userData->pointer && _userData->cleanupCallback && pointer != _userData->pointer)
+    {
+        _userData->cleanupCallback(_userData->pointer);
+    }
+
+    if (pointer)
+    {
+        // Assign user pointer
+        if (_userData == NULL)
+            _userData = new UserData();
+
+        _userData->pointer = pointer;
+        _userData->cleanupCallback = cleanupCallback;
+    }
+    else
+    {
+        // Clear user pointer
+        SAFE_DELETE(_userData);
+    }
+}
+
 unsigned int Node::getChildCount() const
 {
     return _childCount;
 }
 
-Node* Node::findNode(const char* id, bool recursive, bool exactMatch)
+Node* Node::findNode(const char* id, bool recursive, bool exactMatch) const
 {
     assert(id);
     
@@ -232,7 +299,7 @@ Node* Node::findNode(const char* id, bool recursive, bool exactMatch)
     return NULL;
 }   
 
-unsigned int Node::findNodes(const char* id, std::vector<Node*>& nodes, bool recursive, bool exactMatch)
+unsigned int Node::findNodes(const char* id, std::vector<Node*>& nodes, bool recursive, bool exactMatch) const
 {
     assert(id);
     
@@ -296,7 +363,7 @@ const Matrix& Node::getWorldMatrix() const
         // If we have a parent, multiply our parent world transform by our local
         // transform to obtain our final resolved world transform.
         Node* parent = getParent();
-        if (parent && (!_physicsRigidBody || _physicsRigidBody->isKinematic()) )
+		if (parent && (!_collisionObject || _collisionObject->isKinematic()))
         {
             Matrix::multiply(parent->getWorldMatrix(), getMatrix(), &_world);
         }
@@ -703,6 +770,70 @@ const BoundingSphere& Node::getBoundingSphere() const
     return _bounds;
 }
 
+
+Node* Node::clone() const
+{
+    NodeCloneContext context;
+    return cloneRecursive(context);
+}
+
+Node* Node::cloneSingleNode(NodeCloneContext &context) const
+{
+    Node* copy = Node::create(getId());
+    context.registerClonedNode(this, copy);
+    cloneInto(copy, context);
+    return copy;
+}
+
+Node* Node::cloneRecursive(NodeCloneContext &context) const
+{
+    Node* copy = cloneSingleNode(context);
+
+    for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
+    {
+        Node* childCopy = child->cloneRecursive(context);
+        copy->addChild(childCopy); // TODO: Does child order matter?
+        childCopy->release();
+    }
+    return copy;
+}
+
+void Node::cloneInto(Node* node, NodeCloneContext &context) const
+{
+    Transform::cloneInto(node, context);
+
+    // TODO: Clone the rest of the node data.
+    //node->setCamera(getCamera());
+    //node->setLight(getLight());
+
+    if (Camera* camera = getCamera())
+    {
+        Camera* cameraClone = camera->clone(context);
+        node->setCamera(cameraClone);
+        cameraClone->release();
+    }
+    if (Light* light = getLight())
+    {
+        Light* lightClone = light->clone(context);
+        node->setLight(lightClone);
+        lightClone->release();
+    }
+    if (AudioSource* audio = getAudioSource())
+    {
+        AudioSource* audioClone = audio->clone(context);
+        node->setAudioSource(audioClone);
+        audioClone->release();
+    }
+    if (Model* model = getModel())
+    {
+        Model* modelClone = model->clone(context);
+        node->setModel(modelClone);
+        modelClone->release();
+    }
+    node->_world = _world;
+    node->_bounds = _bounds;
+}
+
 AudioSource* Node::getAudioSource() const
 {
     return _audioSource;
@@ -753,45 +884,115 @@ void Node::setParticleEmitter(ParticleEmitter* emitter)
     }
 }
 
-PhysicsRigidBody* Node::getRigidBody() const
+PhysicsCollisionObject* Node::getCollisionObject() const
+{
+    return _collisionObject;
+}
+
+PhysicsCollisionObject* Node::setCollisionObject(PhysicsCollisionObject::Type type, const PhysicsCollisionShape::Definition& shape, PhysicsRigidBody::Parameters* rigidBodyParameters)
+{
+	SAFE_DELETE(_collisionObject);
+
+	switch (type)
+	{
+	case PhysicsCollisionObject::RIGID_BODY:
+		{
+			_collisionObject = new PhysicsRigidBody(this, shape, rigidBodyParameters ? *rigidBodyParameters : PhysicsRigidBody::Parameters());
+		}
+		break;
+
+	case PhysicsCollisionObject::GHOST_OBJECT:
+		{
+			_collisionObject = new PhysicsGhostObject(this, shape);
+		}
+		break;
+
+	case PhysicsCollisionObject::CHARACTER:
+		{
+			_collisionObject = new PhysicsCharacter(this, shape);
+		}
+		break;
+	}
+
+	return _collisionObject;
+}
+
+PhysicsCollisionObject* Node::setCollisionObject(const char* filePath)
+{
+    // Load the collision object properties from file.
+    Properties* properties = Properties::create(filePath);
+    assert(properties);
+    if (properties == NULL)
+    {
+        WARN_VARG("Failed to load collision object file: %s", filePath);
+        return NULL;
+    }
+
+    PhysicsCollisionObject* collisionObject = setCollisionObject(properties->getNextNamespace());
+    SAFE_DELETE(properties);
+
+    return collisionObject;
+}
+
+PhysicsCollisionObject* Node::setCollisionObject(Properties* properties)
 {
-    return _physicsRigidBody;
+    SAFE_DELETE(_collisionObject);
+
+    // Check if the properties is valid.
+    if (!properties || 
+        !(strcmp(properties->getNamespace(), "character") == 0 || 
+        strcmp(properties->getNamespace(), "ghost") == 0 || 
+        strcmp(properties->getNamespace(), "rigidbody") == 0))
+    {
+        WARN("Failed to load collision object from properties object: must be non-null object and have namespace equal to \'character\', \'ghost\', or \'rigidbody\'.");
+        return NULL;
+    }
+
+    if (strcmp(properties->getNamespace(), "character") == 0)
+    {
+        _collisionObject = PhysicsCharacter::create(this, properties);
+    }
+    else if (strcmp(properties->getNamespace(), "ghost") == 0)
+    {
+        _collisionObject = PhysicsGhostObject::create(this, properties);
+    }
+    else if (strcmp(properties->getNamespace(), "rigidbody") == 0)
+    {
+        _collisionObject = PhysicsRigidBody::create(this, properties);
+    }
+	return _collisionObject;
 }
 
-void Node::setRigidBody(PhysicsRigidBody::ShapeType type, float mass, float friction,
-        float restitution, float linearDamping, float angularDamping)
+NodeCloneContext::NodeCloneContext()
 {
-    SAFE_DELETE(_physicsRigidBody);
     
-    if (type != PhysicsRigidBody::SHAPE_NONE)
-        _physicsRigidBody = new PhysicsRigidBody(this, type, mass, friction, restitution, linearDamping, angularDamping);
 }
 
-void Node::setRigidBody(const char* filePath)
+NodeCloneContext::~NodeCloneContext()
 {
-    SAFE_DELETE(_physicsRigidBody);
 
-    _physicsRigidBody = PhysicsRigidBody::create(this, filePath);
 }
 
-void Node::setRigidBody(Properties* properties)
+Animation* NodeCloneContext::findClonedAnimation(const Animation* animation)
 {
-    SAFE_DELETE(_physicsRigidBody);
+    AnimationMap::iterator it = _clonedAnimations.find(animation);
+    return it != _clonedAnimations.end() ? it->second : NULL;
+}
 
-    _physicsRigidBody = PhysicsRigidBody::create(this, properties);
+void NodeCloneContext::registerClonedAnimation(const Animation* original, Animation* clone)
+{
+    _clonedAnimations[original] = clone;
 }
 
-PhysicsGhostObject* Node::getGhostObject()
+Node* NodeCloneContext::findClonedNode(const Node* node)
 {
-    return _ghostObject;
+    NodeMap::iterator it = _clonedNodes.find(node);
+    return it != _clonedNodes.end() ? it->second : NULL;
 }
 
-void Node::setGhostObject(PhysicsRigidBody::ShapeType type)
+void NodeCloneContext::registerClonedNode(const Node* original, Node* clone)
 {
-    SAFE_DELETE(_ghostObject);
-    
-    if (type != PhysicsRigidBody::SHAPE_NONE)
-        _ghostObject = new PhysicsGhostObject(this, type);
+    _clonedNodes[original] = clone;
 }
 
 }

+ 259 - 62
gameplay/src/Node.h

@@ -8,8 +8,9 @@
 #include "Form.h"
 #include "AudioSource.h"
 #include "ParticleEmitter.h"
-#include "PhysicsGhostObject.h"
 #include "PhysicsRigidBody.h"
+#include "PhysicsCollisionObject.h"
+#include "PhysicsCollisionShape.h"
 #include "BoundingBox.h"
 
 namespace gameplay
@@ -112,6 +113,70 @@ public:
      */
     Node* getParent() const;
 
+    /**
+     * Returns whether this node is visible (true by default).
+     *
+     * @return Whether the node is visible.
+     */
+    bool isVisible() const;
+
+    /**
+     * Sets whether this node is visible.
+     *
+     * @return Whether this node is visible.
+     */
+    void setVisible(bool visible);
+
+    /**
+     * Returns whether this node is transparent (false by default).
+     *
+     * All nodes are opaque by default, unless otherwise set as
+     * transparent using the setTransparent method. These methods
+     * can be used to flag nodes as transparent and then query the
+     * property during game execution, for example to render all
+     * opaque objects first, followed by transparent objects with
+     * alpha blending enabled.
+     *
+     * @return Whether the node is transparent.
+     */
+    bool isTransparent() const;
+
+    /**
+     * Sets whether this node is transparent.
+     *
+     * @param transparent Whether the node is transparent.
+     */
+    void setTransparent(bool transparent);
+
+    /**
+     * Returns the user pointer for this node.
+     *
+     * @return The user pointer for this node.
+     * @see setUserPointer(void*)
+     */
+    void* getUserPointer() const;
+
+    /**
+     * Sets the user pointer for this node.
+     *
+     * The user pointer is initially NULL and can be set to anything.
+     * This is normally used to store game-specific data, such as 
+     * game state for a particular node.  For example, attributes
+     * for a game character, such as hit points, stamina, etc can
+     * be defined in a game structure and stored in this field.
+     *
+     * When a node is deleted, the (optional) cleanup callback
+     * function passed to this function is called to allow the 
+     * user to free any memory associated with the user pointer.
+     *
+     * @param pointer User pointer.
+     * @param cleanupCallback Optional callback that is called when the
+     *      Node is being destroyed (or when the user pointer changes),
+     *      to allow the user to cleanup any memory associated with the
+     *      user pointer.
+     */
+    void setUserPointer(void* pointer, void (*cleanupCallback)(void*) = NULL);
+
     /**
      * Returns the number of direct children of this item.
      *
@@ -122,18 +187,18 @@ public:
     /**
      * Returns the first child node that matches the given ID.
      *
-     * This method checks the specified ID against its own ID, as well as its 
-     * immediate children nodes. If recursive is true, it also traverses the
-     * Node's hierarchy.
+     * This method checks the specified ID against its immediate child nodes 
+     * but does not check the ID against itself.
+     * If recursive is true, it also traverses the Node's hierarchy with a breadth first search.
      *
      * @param id The ID of the child to find.
-     * @param recursive true to search recursively all the node's children, false for only direct children.
+     * @param recursive True to search recursively all the node's children, false for only direct children.
      * @param exactMatch true if only nodes whose ID exactly matches the specified ID are returned,
      *        or false if nodes that start with the given ID are returned.
      * 
      * @return The Node found or NULL if not found.
      */
-    Node* findNode(const char* id, bool recursive = true, bool exactMatch = true);
+    Node* findNode(const char* id, bool recursive = true, bool exactMatch = true) const;
 
     /**
      * Returns all child nodes that match the given ID.
@@ -146,7 +211,7 @@ public:
      * 
      * @return The number of matches found.
      */
-    unsigned int findNodes(const char* id, std::vector<Node*>& nodes, bool recursive = true, bool exactMatch = true);
+    unsigned int findNodes(const char* id, std::vector<Node*>& nodes, bool recursive = true, bool exactMatch = true) const;
 
     /**
      * Gets the scene.
@@ -366,61 +431,68 @@ public:
      */
     void setParticleEmitter(ParticleEmitter* emitter);
 
-    /**
-     * Returns the pointer to this node's physics rigid body or NULL.
-     *
-     * @return The pointer to this node's physics rigid body or NULL.
-     */
-    PhysicsRigidBody* getRigidBody() const;
-
-    /**
-     * Sets (or disables) the physics rigid body for this node.
-     * 
-     * Note: This is only allowed for nodes that have a model attached to them.
-     *
-     * @param type The type of rigid body to set; to disable the physics rigid
-     *      body, pass PhysicsRigidBody#SHAPE_NONE.
-     * @param mass The mass of the rigid body, in kilograms.
-     * @param friction The friction of the rigid body (between 0.0 and 1.0, where 0.0 is
-     *      minimal friction and 1.0 is maximal friction).
-     * @param restitution The restitution of the rigid body (this controls the bounciness of
-     *      the rigid body; between 0.0 and 1.0, where 0.0 is minimal bounciness and 1.0 is maximal bounciness).
-     * @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 setRigidBody(PhysicsRigidBody::ShapeType type, float mass = 0.0f, float friction = 0.5f,
-        float restitution = 0.0f, float linearDamping = 0.0f, float angularDamping = 0.0f);
-
-    /**
-     * Returns the pointer to this node's physics ghost object or NULL.
-     * 
-     * @return The pointer to this node's physics ghost object or NULL.
-     */
-    PhysicsGhostObject* getGhostObject();
-
-    /**
-     * Sets (or disables) the physics ghost object for this node.
-     * 
-     * Note: This is only allowed for nodes that have a model attached to them.
-     *
-     * @param type The type of physics ghost object to set; to disable the physics ghost
-     *      object, pass PhysicsRigidBody#SHAPE_NONE.
-     */
-    void setGhostObject(PhysicsRigidBody::ShapeType type);
-
-    /**
-     * Sets the physics rigid body for this node using the rigid body definition in the given file.
+	/**
+	 * Returns the pointer to this node's physics collision object.
+	 *
+	 * The type of the returned collision object can be queried using
+	 * the PhysicsCollisionObject::getType() method.
+	 *
+	 * @return The pointer to this node's physics collision object.
+	 */
+	PhysicsCollisionObject* getCollisionObject() const;
+
+	/**
+	 * Sets (or disables) the physics collision object for this node.
+	 *
+	 * The supported collision object types include rigid bodies, ghost objects and 
+	 * characters.
+	 *
+	 * Rigid bodies are used to represent most physical objects in a game. The important
+	 * feature of rigid bodies is that they can be simulated by the physics system as other
+	 * rigid bodies or collision objects collide with them. To support this physics simulation,
+	 * rigid bodies require additional parameters, such as mass, friction and restitution to
+	 * define their physical features. These parameters can be passed into the
+	 * 'rigidBodyParameters' parameter.
+	 *
+	 * Ghost objects are a simple type of collision object that are not simulated. By default
+	 * they pass through other objects in the scene without affecting them. Ghost objects do
+	 * receive collision events however, which makes them useful for representing non-simulated
+	 * entities in a game that still require collision events, such as volumetric triggers, 
+	 * power-ups, etc.
+	 *
+	 * Characters are an extention of ghost objects which provide a number of additional features
+	 * for animating and moving characters within a game. Characters are represented as ghost
+	 * objects instead of rigid bodies to allow more direct control over character movement,
+	 * since attempting to model a physics character with a simulated rigid body usually results
+	 * in unresponse and unpredictable character movement. Unlike normal ghost objects,
+	 * characters to react to other characters and rigid bodies in the world. Characters react
+	 * to gravity and collide (and respond) with rigid bodies to allow them to walk on the ground,
+	 * slide along walls and walk up/down slopes and stairs.
+	 *
+	 * @param type The type of the collision object to set; to disable the physics
+	 *		collision object, pass PhysicsCollisionObject::NONE.
+	 * @param shape Definition of a physics collision shape to be used for this collision object.
+	 *		Use the static shape methods on the PhysicsCollisionShape class to specificy a shape
+	 *		definition, such as PhysicsCollisionShape::box().
+	 * @param rigidBodyParameters If type is PhysicsCollisionObject::RIGID_BODY, this
+	 *		must point to a valid rigid body parameters object containing information
+	 *		about the rigid body; otherwise, this parmater may be NULL.
+	 */
+	PhysicsCollisionObject* setCollisionObject(PhysicsCollisionObject::Type type, const PhysicsCollisionShape::Definition& shape, PhysicsRigidBody::Parameters* rigidBodyParameters = NULL);
+
+    /**
+     * Sets the physics collision object for this node using the definition in the given file.
      * 
-     * @param filePath The path to the file that contains the rigid body definition.
+     * @param filePath The path to the file that contains the collision object definition.
      */
-    void setRigidBody(const char* filePath);
+    PhysicsCollisionObject* setCollisionObject(const char* filePath);
 
     /**
-     * Sets the physics rigid body for this node from the given properties object.
+     * Sets the physics collision object for this node from the given properties object.
      * 
-     * @param properties The properties object defining the rigid body (must have namespace equal to 'rigidbody').
+     * @param properties The properties object defining the collision ojbect.
      */
-    void setRigidBody(Properties* properties);
+    PhysicsCollisionObject* setCollisionObject(Properties* properties);
 
     /**
      * Returns the bounding sphere for the Node, in world space.
@@ -443,6 +515,13 @@ public:
      */
     const BoundingSphere& getBoundingSphere() const;
 
+    /**
+     * Clones the node and all of its child nodes.
+     * 
+     * @return A new node.
+     */
+    Node* clone() const;
+
 protected:
 
     /**
@@ -451,14 +530,35 @@ protected:
     Node(const char* id);
 
     /**
-     * Copy constructor.
+     * Destructor.
      */
-    Node(const Node& copy);
+    virtual ~Node();
 
     /**
-     * Destructor.
+     * Clones a single node and its data but not its children.
+     * 
+     * @param context The clone context.
+     * 
+     * @return Pointer to the newly created node.
      */
-    virtual ~Node();
+    virtual Node* cloneSingleNode(NodeCloneContext &context) const;
+
+    /**
+     * Recursively clones this node and its children.
+     * 
+     * @param context The clone context.
+     * 
+     * @return The newly created node.
+     */
+    Node* cloneRecursive(NodeCloneContext &context) const;
+
+    /**
+     * Copies the data from this node into the given node.
+     * 
+     * @param node The node to copy the data to.
+     * @param context The clone context.
+     */
+    void cloneInto(Node* node, NodeCloneContext &context) const;
 
     /**
      * Removes this node from its parent.
@@ -470,6 +570,9 @@ protected:
      */
     void transformChanged();
 
+    /**
+     * Called when this Node's hierarchy changes.
+     */
     void hierarchyChanged();
 
     /**
@@ -477,6 +580,26 @@ protected:
      */
     void setBoundsDirty();
 
+private:
+
+    /**
+     * Hidden copy constructor.
+     */
+    Node(const Node& copy);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    Node& operator=(const Node&);
+
+protected:
+
+    struct UserData
+    {
+        void* pointer;
+        void (*cleanupCallback)(void*);
+    };
+
     Scene* _scene;
     std::string _id;
     Node* _firstChild;
@@ -484,18 +607,92 @@ protected:
     Node* _prevSibling;
     Node* _parent;
     unsigned int _childCount;
+    unsigned int _nodeFlags;
     Camera* _camera;
     Light* _light;
     Model* _model;
     Form* _form;
     AudioSource* _audioSource;
     ParticleEmitter* _particleEmitter;
-    PhysicsRigidBody* _physicsRigidBody;
-    PhysicsGhostObject* _ghostObject;
+    PhysicsCollisionObject* _collisionObject;
     mutable Matrix _world;
     mutable int _dirtyBits;
     bool _notifyHierarchyChanged;
     mutable BoundingSphere _bounds;
+    UserData* _userData;
+};
+
+/**
+ * NodeCloneContext represents the context data that is kept when cloning a node.
+ * 
+ * The NodeCloneContext is used to make sure objects don't get cloned twice.
+ */
+class NodeCloneContext
+{
+public:
+
+    /**
+     * Constructor.
+     */
+    NodeCloneContext();
+
+    /**
+     * Destructor.
+     */
+    ~NodeCloneContext();
+
+    /**
+     * Finds the cloned animation of the given animation or NULL if this animation was not registered with this context.
+     * 
+     * @param animation The animation to search for the cloned copy of.
+     * 
+     * @return The cloned animation or NULL if not found.
+     */
+    Animation* findClonedAnimation(const Animation* animation);
+
+    /**
+     * Registers the cloned animation with this context so that it doesn't get cloned twice.
+     * 
+     * @param original The pointer to the original animation.
+     * @param clone The pointer to the cloned animation.
+     */
+    void registerClonedAnimation(const Animation* original, Animation* clone);
+
+    /**
+     * Finds the cloned node of the given node or NULL if this node was not registered with this context.
+     * 
+     * @param node The node to search for the cloned copy of.
+     * 
+     * @return The cloned node or NULL if not found.
+     */
+    Node* findClonedNode(const Node* node);
+
+    /**
+     * Registers the cloned node with this context so that it doens't get cloned twice.
+     * 
+     * @param original The pointer to the original node.
+     * @param clone The pointer to the cloned node.
+     */
+    void registerClonedNode(const Node* original, Node* clone);
+
+private:
+    
+    /**
+     * Hidden copy constructor.
+     */
+    NodeCloneContext(const NodeCloneContext&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    NodeCloneContext& operator=(const NodeCloneContext&);
+
+private:
+    typedef std::map<const Animation*, Animation*> AnimationMap;
+    typedef std::map<const Node*, Node*> NodeMap;
+
+    AnimationMap _clonedAnimations;
+    NodeMap _clonedNodes;
 };
 
 }

+ 3 - 4
gameplay/src/ParticleEmitter.cpp

@@ -50,12 +50,11 @@ ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlendin
     Texture* texture = NULL;
     texture = Texture::create(textureFile, true);    
 
-    if (!texture)
+	if (!texture)
     {
-        // Use default texture.
-        texture = Texture::create("../gameplay/res/textures/particle-default.png", true);
+        LOG_ERROR_VARG("Error creating ParticleEmitter: Could not read texture file: %s", textureFile);
+        return NULL;
     }
-    assert(texture);
 
     // Use default SpriteBatch material.
     SpriteBatch* batch =  SpriteBatch::create(texture, NULL, particleCountMax);

+ 3 - 3
gameplay/src/ParticleEmitter.h

@@ -432,8 +432,8 @@ public:
      * Gets the maximum rotation speed of each emitted particle.
      * This determines the speed of rotation of each particle's screen-facing billboard.
      *
-     * @param min The minimum rotation speed (per particle).
-     * @param max The maximum rotation speed (per particle).
+     * @param speedMin The minimum rotation speed (per particle).
+     * @param speedMax The maximum rotation speed (per particle).
      */
     void setRotationPerParticle(float speedMin, float speedMax);
 
@@ -494,7 +494,7 @@ public:
     /**
      * Sets whether particles cycle through the sprite frames.
      *
-     * @param animating Whether to animate particles through the sprite frames.
+     * @param animated Whether to animate particles through the sprite frames.
      */
     void setSpriteAnimated(bool animated);
 

+ 10 - 4
gameplay/src/Pass.cpp

@@ -2,6 +2,7 @@
 #include "Pass.h"
 #include "Technique.h"
 #include "Material.h"
+#include "Node.h"
 
 namespace gameplay
 {
@@ -14,10 +15,6 @@ Pass::Pass(const char* id, Technique* technique, Effect* effect) :
     RenderState::_parent = _technique;
 }
 
-Pass::Pass(const Pass& copy)
-{
-}
-
 Pass::~Pass()
 {
     SAFE_RELEASE(_effect);
@@ -83,4 +80,13 @@ void Pass::unbind()
     }
 }
 
+Pass* Pass::clone(Technique* technique, NodeCloneContext &context) const
+{
+    Effect* effect = getEffect();
+    effect->addRef();
+    Pass* pass = new Pass(getId(), technique, effect);
+    RenderState::cloneInto(pass, context);
+    return pass;
+}
+
 }

+ 16 - 0
gameplay/src/Pass.h

@@ -8,6 +8,7 @@ namespace gameplay
 {
 
 class Technique;
+class NodeCloneContext;
 
 /**
  * Defines a pass for an object to be rendered.
@@ -87,6 +88,21 @@ private:
      */
     static Pass* create(const char* id, Technique* technique, const char* vshPath, const char* fshPath, const char* defines);
 
+    /**
+     * Hidden copy assignment operator.
+     */
+    Pass& operator=(const Pass&);
+
+    /**
+     * Clones the Pass and assigns it the given Technique.
+     * 
+     * @param technique The technique to assign to the new Pass.
+     * @param context The clone context.
+     * 
+     * @return The newly created Pass.
+     */
+    Pass* clone(Technique* technique, NodeCloneContext &context) const;
+
     std::string _id;
     Technique* _technique;
     Effect* _effect;

+ 84 - 135
gameplay/src/PhysicsCharacter.cpp

@@ -10,16 +10,6 @@
 #include "Game.h"
 #include "PhysicsController.h"
 
-// Amount to walk collision normal when attempting to repair a collision.
-// To small a value will result in inefficient collision repairs (several iterations
-// to fix a collision and slow resolution), whereas larger values will result
-// in less accurate collision resolution.
-//#define COLLISION_REPAIR_INCREMENT 0.2f
-#define COLLISION_REPAIR_MARGIN 1.0f
-
-// Maximum number of iterations used to perform perform collision repair each update.
-#define COLLISION_REPAIR_MAX_ITERATIONS 4
-
 namespace gameplay
 {
 
@@ -32,11 +22,12 @@ public:
 	{
 	}
 
-	virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
+	btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
 	{
 		if (convexResult.m_hitCollisionObject == _me)
 			return btScalar(1.0);
 
+        /*
 		btVector3 hitNormalWorld;
 		if (normalInWorldSpace)
 		{
@@ -52,8 +43,9 @@ public:
         {
 			return btScalar(1.0);
 		}
+        */
 
-		return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace);
+		return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
 	}
 
 protected:
@@ -63,60 +55,50 @@ protected:
 	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),
+PhysicsCharacter::PhysicsCharacter(Node* node, const PhysicsCollisionShape::Definition& shape)
+    : PhysicsGhostObject(node, shape), _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), _physicsEnabled(true)
+    _stepHeight(0.1f), _slopeAngle(0.0f), _cosSlopeAngle(0.0f), _physicsEnabled(true)
 {
-    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());
+	setMaxSlopeAngle(45.0f);
 
-    PhysicsController* pc = Game::getInstance()->getPhysicsController();
-
-    // Create a capsule collision shape (this is automatically deleted by PhysicsController when our collision object is removed)
-    _collisionShape = static_cast<btConvexShape*>(pc->createCapsule(radius, height - radius*2.0f));
-
-    // 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);
-
-    // Add the collision object to the physics system
-    pc->addCollisionObject(this);
+    // Set the collision flags on the ghost object to indicate it's a character
+    _ghostObject->setCollisionFlags(_ghostObject->getCollisionFlags() | btCollisionObject::CF_CHARACTER_OBJECT);
 
     // Register ourselves as an action on the physics world so we are called back during physics ticks
-    pc->_world->addAction(this);
+    Game::getInstance()->getPhysicsController()->_world->addAction(this);
 }
 
 PhysicsCharacter::~PhysicsCharacter()
 {
-    // Remove ourself from physics system
-    PhysicsController* pc = Game::getInstance()->getPhysicsController();
+    // Unregister ourselves as action from world
+    Game::getInstance()->getPhysicsController()->_world->removeAction(this);
+}
 
-    pc->removeCollisionObject(this);
+PhysicsCharacter* PhysicsCharacter::create(Node* node, Properties* properties)
+{
+    // Check if the properties is valid and has a valid namespace.
+    assert(properties);
+    if (!properties || !(strcmp(properties->getNamespace(), "character") == 0))
+    {
+        WARN("Failed to load physics character from properties object: must be non-null object and have namespace equal to \'character\'.");
+        return NULL;
+    }
 
-    // Unregister ourselves as action from world
-    pc->_world->removeAction(this);
+    // Load the physics collision shape definition.
+    PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
+    if (shape == NULL)
+    {
+        WARN("Failed to create collision shape during physics character creation.");
+        return NULL;
+    }
 
-    SAFE_DELETE(_ghostObject);
+    // Create the physics character.
+    PhysicsCharacter* character = new PhysicsCharacter(node, *shape);
+    SAFE_DELETE(shape);
 
-    _node->removeListener(this);
-    SAFE_RELEASE(_node);
-    SAFE_DELETE(_motionState);
+    return character;
 }
 
 PhysicsCollisionObject::Type PhysicsCharacter::getType() const
@@ -139,16 +121,6 @@ void PhysicsCharacter::setPhysicsEnabled(bool enabled)
     _physicsEnabled = enabled;
 }
 
-btCollisionShape* PhysicsCharacter::getCollisionShape() const
-{
-    return _collisionShape;
-}
-
-Node* PhysicsCharacter::getNode() const
-{
-    return _node;
-}
-
 float PhysicsCharacter::getMaxStepHeight() const
 {
     return _stepHeight;
@@ -308,6 +280,7 @@ void PhysicsCharacter::setRotation(const Quaternion& rotation)
 {
     _node->setRotation(rotation);
 }
+
 void PhysicsCharacter::setForwardVelocity(float velocity)
 {
     _forwardVelocity = velocity;
@@ -370,18 +343,6 @@ void PhysicsCharacter::updateCurrentVelocity()
     }
 }
 
-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.
@@ -392,27 +353,24 @@ void PhysicsCharacter::updateAction(btCollisionWorld* collisionWorld, btScalar d
     // dynamic objects (i.e. objects that moved and now intersect the character).
     if (_physicsEnabled)
     {
-        _colliding = fixCollision(collisionWorld);
-        /*_colliding = false;
+        //_colliding = fixCollision(collisionWorld);
+        _colliding = false;
         int stepCount = 0;
 	    while (fixCollision(collisionWorld))
 	    {
             _colliding = true;
 
-            // After a small number of attempts to fix a collision/penetration, give up.
-            // This hanldes the case where we are deeply penetrating some object and attempting
-            // to step out of it (by COLLISION_REPAIR_INCREMENT units) does not fix the collision.
-            if (++stepCount > COLLISION_REPAIR_MAX_ITERATIONS)
+            if (++stepCount > 4)
 		    {
-                WARN_VARG("Character '%s' could not recover from collision.", _node->getId());
+                // Most likely we are wedged between a number of different collision objects
 			    break;
 		    }
-	    }*/
+	    }
     }
 
     // Update current and target world positions
-    btTransform transform = _ghostObject->getWorldTransform();
-    _currentPosition = transform.getOrigin();
+	btVector3 startPosition = _ghostObject->getWorldTransform().getOrigin();
+	_currentPosition = startPosition;
 
     // Process movement in the up direction
     if (_physicsEnabled)
@@ -422,19 +380,12 @@ void PhysicsCharacter::updateAction(btCollisionWorld* collisionWorld, btScalar d
     stepForwardAndStrafe(collisionWorld, deltaTimeStep);
 
     // Process movement in the down direction
-    if (_physicsEnabled)
-        stepDown(collisionWorld, deltaTimeStep);
+	if (_physicsEnabled)
+		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());
+	btVector3 translation = _currentPosition - startPosition;
+	_node->translate(translation.x(), translation.y(), translation.z());
 }
 
 void PhysicsCharacter::stepUp(btCollisionWorld* collisionWorld, btScalar time)
@@ -447,6 +398,7 @@ void PhysicsCharacter::stepUp(btCollisionWorld* collisionWorld, btScalar time)
     // 
     // 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.
+	_currentPosition += btVector3(0, _stepHeight, 0);
 }
 
 void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, float time)
@@ -496,7 +448,7 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
     {
         // No velocity, so we aren't moving
         return;
-    }
+	}
 
     // Translate the target position by the velocity vector (already scaled by t)
     btVector3 targetPosition = _currentPosition + velocity;
@@ -509,12 +461,13 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
     }
 
     // Check for collisions by performing a bullet convex sweep test
-    btTransform start, end;
+    btTransform start;
+	btTransform end;
 	start.setIdentity();
 	end.setIdentity();
 
 	btScalar fraction = 1.0;
-	btScalar distance2 = (_currentPosition-targetPosition).length2();
+	btScalar distance2;
 
 	if (_colliding && (_normalizedVelocity.dot(_collisionNormal) > btScalar(0.0)))
 	{
@@ -527,30 +480,30 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
 	{
 		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);
+		_ghostObject->convexSweepTest(static_cast<btConvexShape*>(_collisionShape->getShape()), start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
 
 		fraction -= callback.m_closestHitFraction;
 
 		if (callback.hasHit())
         {
-			// We hit something so can move only a fraction
-			//btScalar hitDistance = (callback.m_hitPointWorld - _currentPosition).length();
+			/*Vector3 normal(callback.m_hitNormalWorld.x(), callback.m_hitNormalWorld.y(), callback.m_hitNormalWorld.z());
+			PhysicsCollisionObject* o = Game::getInstance()->getPhysicsController()->getCollisionObject(callback.m_hitCollisionObject);
+			if (o->getType() == PhysicsCollisionObject::RIGID_BODY && o->isDynamic())
+			{
+				PhysicsRigidBody* rb = static_cast<PhysicsRigidBody*>(o);
+				normal.normalize();
+				rb->applyImpulse(-normal);
+			}*/
 
-            //_currentPosition.setInterpolate3(_currentPosition, targetPosition, callback.m_closestHitFraction);
+            updateTargetPositionFromCollision(targetPosition, callback.m_hitNormalWorld);
 
-			updateTargetPositionFromCollision(targetPosition, callback.m_hitNormalWorld);
 			btVector3 currentDir = targetPosition - _currentPosition;
 			distance2 = currentDir.length2();
 			if (distance2 > FLT_EPSILON)
@@ -567,7 +520,6 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
         else
         {
             // Nothing in our way
-            //_currentPosition = targetPosition;
             break;
         }
     }
@@ -577,30 +529,27 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
 
 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();
+    // Contribute basic gravity to fall velocity.
+	btVector3 gravity = Game::getInstance()->getPhysicsController()->_world->getGravity();
     _fallVelocity += (gravity * time);
 
-    btVector3 targetPosition = _currentPosition + _fallVelocity;
+    btVector3 targetPosition = _currentPosition + (_fallVelocity * time);
+	targetPosition -= btVector3(0, _stepHeight, 0);
 
     // 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, 
+	btTransform start;
+	btTransform end;
+	start.setIdentity();
+	end.setIdentity();
+	start.setOrigin(_currentPosition);
+	end.setOrigin(targetPosition);
+
     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);
+
+	_ghostObject->convexSweepTest(static_cast<btConvexShape*>(_collisionShape->getShape()), start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
+
 	if (callback.hasHit())
 	{
         // Collision detected, fix it
@@ -684,7 +633,9 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
 	world->getDispatcher()->dispatchAllCollisionPairs(pairCache, world->getDispatchInfo(), world->getDispatcher());
 
     // Store our current world position
-    btVector3 currentPosition = _ghostObject->getWorldTransform().getOrigin();
+    Vector3 startPosition;
+	_node->getWorldMatrix().getTranslation(&startPosition);
+	btVector3 currentPosition = BV(startPosition);
 
     // Handle all collisions/overlappign pairs
 	btScalar maxPenetration = btScalar(0.0);
@@ -723,10 +674,10 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
                         _collisionNormal = pt.m_normalWorldOnB * directionSign;
 					}
 
+					//Node* node = Game::getInstance()->getPhysicsController()->getCollisionObject((btCollisionObject*)(manifold->getBody0() == _ghostObject ? manifold->getBody1() : manifold->getBody0()))->getNode();
+
                     // Calculate new position for object, which is translated back along the collision normal
-                    btVector3 n(pt.m_normalWorldOnB);
-                    n.normalize();
-					currentPosition += /*pt.m_normalWorldOnB*/n * directionSign * dist * COLLISION_REPAIR_MARGIN;// + _collisionShape->getMargin());// * COLLISION_REPAIR_INCREMENT;
+					currentPosition += pt.m_normalWorldOnB * directionSign * dist * 0.2f;
 					collision = true;
 				}
 			}
@@ -734,10 +685,8 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
 		}
 	}
 
-    // Set the new world transformation to apply to fix the collision
-	btTransform newTransform = _ghostObject->getWorldTransform();
-	newTransform.setOrigin(currentPosition);
-	_ghostObject->setWorldTransform(newTransform);
+	// Set the new world transformation to apply to fix the collision
+	_node->translate(Vector3(currentPosition.x(), currentPosition.y(), currentPosition.z()) - startPosition);
 
 	return collision;
 }

+ 16 - 33
gameplay/src/PhysicsCharacter.h

@@ -2,9 +2,8 @@
 #define PHYSICSCHARACTER_H_
 
 #include "Node.h"
-#include "PhysicsRigidBody.h"
-#include "PhysicsMotionState.h"
-#include "Vector3.h"
+#include "PhysicsGhostObject.h"
+#include "Properties.h"
 
 namespace gameplay
 {
@@ -24,9 +23,9 @@ namespace gameplay
  * 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.
  */
-class PhysicsCharacter : public PhysicsCollisionObject, public Transform::Listener, public btActionInterface
+class PhysicsCharacter : public PhysicsGhostObject, public btActionInterface
 {
-    friend class PhysicsController;
+    friend class Node;
 
 public:
 
@@ -56,15 +55,6 @@ public:
      */
     PhysicsCollisionObject::Type getType() const;
 
-    /**
-     * Returns the character node for this PhysicsCharacter.
-     *
-     * @return The character Node.
-     *
-     * @see PhysicsCollisionObject::getNode.
-     */
-    Node* getNode() const;
-
     /**
      * Returns whether physics simulation is enabled for the physics character.
      *
@@ -260,11 +250,6 @@ public:
      */
     void jump(float height);
 
-    /**
-     * @see Transform::Listener::transformChanged
-     */
-    void transformChanged(Transform* transform, long cookie);
-
     /**
      * @see btActionInterface::updateAction
      */
@@ -282,11 +267,6 @@ protected:
      */
     btCollisionObject* getCollisionObject() const;
 
-    /**
-     * @see PhysicsCollisionObject::getCollisionShape
-     */
-    btCollisionShape* getCollisionShape() const;
-
 private:
 
     struct CharacterAnimation
@@ -307,11 +287,9 @@ private:
      * Use PhysicsController::createCharacter to create physics characters.
      *
      * @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.
+	 * @param shape Physis collision shape definition.
      */
-    PhysicsCharacter(Node* node, float radius, float height, const Vector3 center = Vector3::zero());
+	PhysicsCharacter(Node* node, const PhysicsCollisionShape::Definition& shape);
 
     /**
      * Destructor.
@@ -320,6 +298,16 @@ private:
      */
     virtual ~PhysicsCharacter();
 
+    /**
+     * Creates a physics character from the specified properties object.
+     * 
+     * @param node The node to create a physics character for; note that the node must have
+     *      a model attached to it prior to creating a physics character for it.
+     * @param properties The properties object defining the physics character (must have namespace equal to 'character').
+     * @return The newly created physics character, or <code>NULL</code> if the physics character failed to load.
+     */
+    static PhysicsCharacter* create(Node* node, Properties* properties);
+
     void updateCurrentVelocity();
 
     void play(CharacterAnimation* animation, unsigned int layer);
@@ -334,8 +322,6 @@ private:
 
     bool fixCollision(btCollisionWorld* world);
 
-    Node* _node;
-    PhysicsMotionState* _motionState;
     btVector3 _moveVelocity;
     float _forwardVelocity;
     float _rightVelocity;
@@ -347,10 +333,7 @@ private:
     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;

+ 49 - 9
gameplay/src/PhysicsCollisionObject.cpp

@@ -6,12 +6,60 @@
 namespace gameplay
 {
 
-PhysicsCollisionObject::PhysicsCollisionObject()
+// Internal class used to implement the collidesWith(PhysicsCollisionObject*) function.
+struct CollidesWithCallback : public btCollisionWorld::ContactResultCallback
+{
+    btScalar addSingleResult(btManifoldPoint& cp, 
+		const btCollisionObject* a, int partIdA, int indexA, 
+		const btCollisionObject* b, int partIdB, int indexB)
+	{
+		result = true;
+		return 0.0f;
+	}
+
+    bool result;
+};
+
+PhysicsCollisionObject::PhysicsCollisionObject(Node* node)
+	: _node(node), _motionState(NULL), _collisionShape(NULL)
 {
 }
 
 PhysicsCollisionObject::~PhysicsCollisionObject()
 {
+	SAFE_DELETE(_motionState);
+
+	Game::getInstance()->getPhysicsController()->destroyShape(_collisionShape);
+}
+
+PhysicsCollisionShape::Type PhysicsCollisionObject::getShapeType() const
+{
+	return getCollisionShape()->getType();
+}
+
+Node* PhysicsCollisionObject::getNode() const
+{
+	return _node;
+}
+
+PhysicsCollisionShape* PhysicsCollisionObject::getCollisionShape() const
+{
+	return _collisionShape;
+}
+
+PhysicsMotionState* PhysicsCollisionObject::getMotionState() const
+{
+	return _motionState;
+}
+
+bool PhysicsCollisionObject::isKinematic() const
+{
+	return getCollisionObject()->isKinematicObject();
+}
+
+bool PhysicsCollisionObject::isDynamic() const
+{
+	return !getCollisionObject()->isStaticOrKinematicObject();
 }
 
 void PhysicsCollisionObject::addCollisionListener(CollisionListener* listener, PhysicsCollisionObject* object)
@@ -55,12 +103,4 @@ bool PhysicsCollisionObject::CollisionPair::operator < (const CollisionPair& col
     return false;
 }
 
-btScalar PhysicsCollisionObject::CollidesWithCallback::addSingleResult(btManifoldPoint& cp,
-    const btCollisionObject* a, int partIdA, int indexA, 
-    const btCollisionObject* b, int partIdB, int indexB)
-{
-    result = true;
-    return 0.0f;
-}
-
 }

+ 60 - 27
gameplay/src/PhysicsCollisionObject.h

@@ -2,6 +2,8 @@
 #define PHYSICSCOLLISIONOBJECT_H_
 
 #include "Vector3.h"
+#include "PhysicsCollisionShape.h"
+#include "PhysicsMotionState.h"
 
 namespace gameplay
 {
@@ -14,11 +16,12 @@ class Node;
 class PhysicsCollisionObject
 {
     friend class PhysicsController;
+	friend class PhysicsConstraint;
 
 public:
 
     /**
-     * Enumeration of all possible collision object types.
+     * Represents the different types of collision objects.
      */
     enum Type
     {
@@ -35,7 +38,12 @@ public:
         /** 
          * PhysicsGhostObject type.
          */
-        GHOST_OBJECT
+        GHOST_OBJECT,
+
+		/**
+		 * No collision object.
+		 */
+		NONE
     };
 
     /** 
@@ -114,15 +122,52 @@ public:
                                     const Vector3& contactPointB = Vector3::zero()) = 0;
     };
 
+	/**
+     * Virtual destructor.
+     */
+    virtual ~PhysicsCollisionObject();
+
     /**
      * Returns the type of the collision object.
      */
     virtual PhysicsCollisionObject::Type getType() const = 0;
 
+	/**
+	 * Returns the type of the shape for this collision object.
+	 */
+	PhysicsCollisionShape::Type getShapeType() const;
+
     /**
      * Returns the node associated with this collision object.
      */
-    virtual Node* getNode() const = 0;
+    Node* getNode() const;
+
+	/**
+     * Returns the collision shape.
+     *
+     * @return The collision shape.
+     */
+    PhysicsCollisionShape* getCollisionShape() const;
+
+	/**
+	 * Returns whether this collision object is kinematic.
+	 *
+	 * A kinematic collision object is an object that is not simulated by
+	 * the physics system and instead has its transform driven manually.
+	 *
+	 * @return Whether the collision object is kinematic.
+	 */
+	bool isKinematic() const;
+
+    /**
+     * Returns whether this collision object is dynamic.
+	 *
+	 * A dynamic collision object is simulated entirely by the physics system,
+	 * such as with dynamic rigid bodies. 
+     *
+     * @return Whether the collision object is dynamic.
+     */
+    bool isDynamic() const;
 
     /**
      * Adds a collision listener for this collision object.
@@ -153,12 +198,7 @@ protected:
     /**
      * Constructor.
      */
-    PhysicsCollisionObject();
-
-    /**
-     * Virtual destructor.
-     */
-    virtual ~PhysicsCollisionObject();
+    PhysicsCollisionObject(Node* node);
 
     /**
      * Returns the Bullet Physics collision object.
@@ -167,24 +207,17 @@ protected:
      */
     virtual btCollisionObject* getCollisionObject() const = 0;
 
-    /**
-     * Returns the Bullet Physics collision shape.
-     *
-     * @return The Bullet collision shape.
-     */
-    virtual btCollisionShape* getCollisionShape() const = 0;
-
-private:
-
-    // Internal class used to implement the collidesWith(PhysicsRigidBody*) function.
-    struct CollidesWithCallback : public btCollisionWorld::ContactResultCallback
-    {
-        btScalar addSingleResult(btManifoldPoint& cp, 
-                                 const btCollisionObject* a, int partIdA, int indexA, 
-                                 const btCollisionObject* b, int partIdB, int indexB);
-
-        bool result;
-    };
+	/**
+	 * Returns the physics motion state.
+	 *
+	 * @return The motion state object.
+	 */
+	PhysicsMotionState* getMotionState() const;
+
+	// Common member variables
+	Node* _node;
+    PhysicsMotionState* _motionState;
+    PhysicsCollisionShape* _collisionShape;
 
 };
 

+ 408 - 0
gameplay/src/PhysicsCollisionShape.cpp

@@ -0,0 +1,408 @@
+#include "Base.h"
+#include "PhysicsCollisionShape.h"
+#include "Node.h"
+#include "Properties.h"
+
+namespace gameplay
+{
+
+PhysicsCollisionShape::PhysicsCollisionShape(Type type, btCollisionShape* shape)
+    : _type(type), _shape(shape)
+{
+    memset(&_shapeData, 0, sizeof(_shapeData));
+}
+
+PhysicsCollisionShape::PhysicsCollisionShape(const PhysicsCollisionShape& copy)
+{
+	// hidden
+}
+
+PhysicsCollisionShape::~PhysicsCollisionShape()
+{
+    if (_shape)
+    {
+        // Cleanup shape-specific cached data
+        switch (_type)
+        {
+        case SHAPE_MESH:
+            if (_shapeData.meshData)
+            {
+                SAFE_DELETE_ARRAY(_shapeData.meshData->vertexData);
+                for (unsigned int i = 0; i < _shapeData.meshData->indexData.size(); i++)
+                {
+                    SAFE_DELETE_ARRAY(_shapeData.meshData->indexData[i]);
+                }
+                SAFE_DELETE(_shapeData.meshData);
+            }
+            break;
+        case SHAPE_HEIGHTFIELD:
+            if (_shapeData.heightfieldData)
+            {
+                SAFE_DELETE_ARRAY(_shapeData.heightfieldData->heightData);
+                SAFE_DELETE(_shapeData.heightfieldData);
+            }
+            break;
+        }
+
+        // Free the bullet shape
+        SAFE_DELETE(_shape);
+    }
+}
+
+PhysicsCollisionShape::Type PhysicsCollisionShape::getType() const
+{
+    return _type;
+}
+
+PhysicsCollisionShape::Definition::Definition()
+    : isExplicit(false), centerAbsolute(false)
+{
+    memset(&data, 0, sizeof(data));
+}
+
+PhysicsCollisionShape::Definition::Definition(const Definition& definition)
+{
+    // Bitwise-copy the definition object (equivalent to default copy constructor).
+    memcpy(this, &definition, sizeof(PhysicsCollisionShape::Definition));
+
+    // Handle the types that have reference-counted members.
+    switch (type)
+    {
+    case PhysicsCollisionShape::SHAPE_HEIGHTFIELD:
+        data.heightfield->addRef();
+        break;
+
+    case PhysicsCollisionShape::SHAPE_MESH:
+        data.mesh->addRef();
+        break;
+    }
+}
+
+PhysicsCollisionShape::Definition::~Definition()
+{
+    switch (type)
+    {
+    case PhysicsCollisionShape::SHAPE_HEIGHTFIELD:
+        SAFE_RELEASE(data.heightfield);
+        break;
+
+    case PhysicsCollisionShape::SHAPE_MESH:
+        SAFE_RELEASE(data.mesh);
+        break;
+    }
+}
+
+PhysicsCollisionShape::Definition& PhysicsCollisionShape::Definition::operator=(const Definition& definition)
+{
+    if (this != &definition)
+    {
+        // Bitwise-copy the definition object (equivalent to default copy constructor).
+        memcpy(this, &definition, sizeof(PhysicsCollisionShape::Definition));
+
+        // Handle the types that have reference-counted members.
+        switch (type)
+        {
+        case PhysicsCollisionShape::SHAPE_HEIGHTFIELD:
+            data.heightfield->addRef();
+            break;
+
+        case PhysicsCollisionShape::SHAPE_MESH:
+            data.mesh->addRef();
+            break;
+        }
+    }
+
+    return *this;
+}
+
+PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Node* node, Properties* properties)
+{
+    // Check if the properties is valid and has a valid namespace.
+    assert(properties);
+    if (!properties || 
+        !(strcmp(properties->getNamespace(), "character") == 0 || 
+        strcmp(properties->getNamespace(), "ghost") == 0 || 
+        strcmp(properties->getNamespace(), "rigidbody") == 0))
+    {
+        WARN("Failed to load physics collision shape from properties object: must be non-null object and have namespace equal to \'character\', \'ghost\', or \'rigidbody\'.");
+        return NULL;
+    }
+
+    // Set values to their defaults.
+    PhysicsCollisionShape::Type type = PhysicsCollisionShape::SHAPE_BOX;
+    Vector3* extents = NULL;
+    Vector3* center = NULL;
+    float radius = -1.0f;
+    float height = -1.0f;
+    bool centerIsAbsolute = false;
+    const char* imagePath = NULL;
+    bool typeSpecified = false;
+
+    // Load the defined properties.
+    properties->rewind();
+    const char* name;
+    while (name = properties->getNextProperty())
+    {
+        if (strcmp(name, "type") == 0)
+        {
+            std::string typeStr = properties->getString();
+            if (typeStr == "BOX")
+                type = SHAPE_BOX;
+            else if (typeStr == "SPHERE")
+                type = SHAPE_SPHERE;
+            else if (typeStr == "MESH")
+                type = SHAPE_MESH;
+            else if (typeStr == "HEIGHTFIELD")
+                type = SHAPE_HEIGHTFIELD;
+            else if (typeStr == "CAPSULE")
+                type = SHAPE_CAPSULE;
+            else
+            {
+                WARN_VARG("Could not create physics collision shape; unsupported value for collision shape type: '%s'.", typeStr.c_str());
+                return NULL;
+            }
+
+            typeSpecified = true;
+        }
+        else if (strcmp(name, "image") == 0)
+        {
+            imagePath = properties->getString();
+        }
+        else if (strcmp(name, "radius") == 0)
+        {
+            radius = properties->getFloat();
+        }
+        else if (strcmp(name, "height") == 0)
+        {
+            height = properties->getFloat();
+        }
+        else if (strcmp(name, "extents") == 0)
+        {
+            extents = new Vector3();
+            properties->getVector3("extents", extents);
+        }
+        else if (strcmp(name, "center") == 0)
+        {
+            center = new Vector3();
+            properties->getVector3("center", center);
+        }
+        else if (strcmp(name, "center-absolute") == 0)
+        {
+            centerIsAbsolute = properties->getBool();
+        }
+    }
+
+    if (!typeSpecified)
+    {
+        WARN("Missing 'type' specifier for collision shape definition.");
+        return NULL;
+    }
+
+    // Create the collision shape.
+    PhysicsCollisionShape::Definition* shape = new PhysicsCollisionShape::Definition();
+    switch (type)
+    {
+        case SHAPE_BOX:
+            if (extents)
+            {
+                if (center)
+                {
+                    *shape = box(*extents, *center, centerIsAbsolute);
+                }
+                else
+                {
+                    *shape = box(*extents);
+                }
+            }
+            else
+            {
+                *shape = box();
+            }
+            break;
+        case SHAPE_SPHERE:
+            if (radius != -1.0f)
+            {
+                if (center)
+                {
+                    *shape = sphere(radius, *center, centerIsAbsolute);
+                }
+                else
+                {
+                    *shape = sphere(radius);
+                }
+            }
+            else
+            {
+                *shape = sphere();
+            }
+            break;
+        case SHAPE_CAPSULE:
+            if (radius != -1.0f && height != -1.0f)
+            {
+                if (center)
+                {
+                    *shape = capsule(radius, height, *center, centerIsAbsolute);
+                }
+                else
+                {
+                    *shape = capsule(radius, height);
+                }
+            }
+            else
+            {
+                *shape = capsule();
+            }
+            break;
+        case SHAPE_MESH:
+        {
+            // Mesh is required on node
+            Mesh* nodeMesh = node->getModel() ? node->getModel()->getMesh() : NULL;
+            if (nodeMesh == NULL)
+            {
+                WARN("Cannot create mesh rigid body for node without mode/mesh.");
+                return NULL;
+            }
+
+            // Check that the node's mesh's primitive type is supported.
+            switch (nodeMesh->getPrimitiveType())
+            {
+                case Mesh::TRIANGLES:
+                {
+                    *shape = mesh(nodeMesh);
+                    break;
+                }
+                case Mesh::LINES:
+                case Mesh::LINE_STRIP:
+                case Mesh::POINTS:
+                case Mesh::TRIANGLE_STRIP:
+                    WARN("Mesh rigid bodies are currently only supported on meshes with primitive type equal to TRIANGLES.");
+                    SAFE_DELETE(shape);
+                    break;
+            }
+
+            break;
+        }
+        case SHAPE_HEIGHTFIELD:
+            if (imagePath == NULL)
+            {
+                WARN("Heightfield rigid body requires an image path.");
+            }
+            else
+            {
+                // Load the image data from the given file path.
+                Image* image = Image::create(imagePath);
+                if (!image)
+                    return NULL;
+
+                // Ensure that the image's pixel format is supported.
+                switch (image->getFormat())
+                {
+                    case Image::RGB:
+                    case Image::RGBA:
+                        break;
+                    default:
+                        WARN_VARG("Heightmap: pixel format is not supported: %d", image->getFormat());
+                        return NULL;
+                }
+
+                *shape = PhysicsCollisionShape::heightfield(image);
+                SAFE_RELEASE(image);
+            }
+            break;
+        default:
+            WARN("Unsupported value for physics collision shape type.");
+            break;
+    }
+
+    SAFE_DELETE(extents);
+    SAFE_DELETE(center);
+
+    return shape;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::box()
+{
+    Definition d;
+    d.type = SHAPE_BOX;
+    d.isExplicit = false;
+    d.centerAbsolute = false;
+    return d;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::box(const Vector3& extents, const Vector3& center, bool absolute)
+{
+	Definition d;
+	d.type = SHAPE_BOX;
+	memcpy(d.data.box.extents, &extents.x, sizeof(float) * 3);
+	memcpy(d.data.box.center, &center.x, sizeof(float) * 3);
+	d.isExplicit = true;
+	d.centerAbsolute = absolute;
+	return d;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::sphere()
+{
+    Definition d;
+    d.type = SHAPE_SPHERE;
+    d.isExplicit = false;
+    d.centerAbsolute = false;
+    return d;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::sphere(float radius, const Vector3& center, bool absolute)
+{
+	Definition d;
+	d.type = SHAPE_SPHERE;
+	d.data.sphere.radius = radius;
+	memcpy(d.data.sphere.center, &center.x, sizeof(float) * 3);
+	d.isExplicit  = true;
+	d.centerAbsolute = absolute;
+	return d;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::capsule()
+{
+    Definition d;
+    d.type = SHAPE_CAPSULE;
+    d.isExplicit = false;
+    d.centerAbsolute = false;
+    return d;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::capsule(float radius, float height, const Vector3& center, bool absolute)
+{
+	Definition d;
+	d.type = SHAPE_CAPSULE;
+	d.data.capsule.radius = radius;
+	d.data.capsule.height = height;
+	memcpy(d.data.capsule.center, &center.x, sizeof(float) * 3);
+	d.isExplicit = true;
+	d.centerAbsolute = absolute;
+	return d;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::heightfield(Image* image)
+{
+    image->addRef();
+
+    Definition d;
+    d.type = SHAPE_HEIGHTFIELD;
+    d.data.heightfield = image;
+    d.isExplicit = true;
+    d.centerAbsolute = false;
+    return d;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::mesh(Mesh* mesh)
+{
+    mesh->addRef();
+
+    Definition d;
+    d.type = SHAPE_MESH;
+    d.data.mesh = mesh;
+    d.isExplicit = true;
+    d.centerAbsolute = false;
+    return d;
+}
+
+}

+ 235 - 0
gameplay/src/PhysicsCollisionShape.h

@@ -0,0 +1,235 @@
+#ifndef PHYSICSCOLLISIONSHAPE_H_
+#define PHYSICSCOLLISIONSHAPE_H_
+
+#include "Vector3.h"
+#include "Image.h"
+#include "Mesh.h"
+
+namespace gameplay
+{
+    class Node;
+    class Properties;
+
+/**
+ * Base physics collision shape class that all supported shapes derive from.
+ */
+class PhysicsCollisionShape : public Ref
+{
+    friend class PhysicsController;
+    friend class PhysicsRigidBody;
+
+public:
+
+    /**
+     * Defines the supported collision shape types.
+     */
+    enum Type
+    {
+        SHAPE_BOX,
+        SHAPE_SPHERE,
+        SHAPE_CAPSULE,
+        SHAPE_MESH,
+        SHAPE_HEIGHTFIELD
+    };
+
+    /**
+     * Structure representing the definition of a collision shape, which is used
+     * during collision shape construction time.
+     *
+     * Use the static methods on the PhysicsCollisionShape class to return
+     * 
+     */
+    struct Definition
+    {
+        friend class PhysicsCollisionShape;
+        friend class PhysicsController;
+        friend class PhysicsRigidBody;
+        friend class PhysicsCharacter;
+        friend class PhysicsGhostObject;
+
+    public:
+
+        ~Definition();
+
+    private:
+
+        Definition();
+        Definition(const Definition& definition);
+        Definition& operator=(const Definition& definition);
+
+        /**
+         * Creates a PhysicsCollisionShape#Definition object from the given properties object (for the given node).
+         * 
+         * @param node The node to create the PhysicsCollisionShape::Definition object for.
+         * @param properties The properties object to create the PhysicsCollisionShape#Definition object from.
+         * @return A PhysicsCollisionShape#Definition object.
+         */
+        static Definition* create(Node* node, Properties* properties);
+
+        // Shape type.
+        PhysicsCollisionShape::Type type;
+
+		// Shape data.
+		struct BoxData { float center[3], extents[3]; };
+		struct SphereData { float center[3]; float radius; };
+		struct CapsuleData { float center[3]; float radius, height; };
+
+        union
+        {
+			BoxData box;
+			SphereData sphere;
+			CapsuleData capsule;
+			Image* heightfield;
+			Mesh* mesh;
+		} data;
+
+        // Whether the shape definition is explicit, or if it is inherited from node bounds.
+        bool isExplicit;
+
+        // Whether the center position is absolute or relative to the node position.
+        bool centerAbsolute;
+    };
+
+    /**
+     * Returns the type of this collision shape.
+     *
+     * @return The collision shape type.
+     */
+    PhysicsCollisionShape::Type getType() const;
+
+	/**
+	 * Returns the internal bullet physics shape object.
+	 *
+	 * @return The bullet shape object.
+	 */
+	btCollisionShape* getShape() const
+	{
+		return _shape;
+	}
+
+    /**
+     * Defines a box shape, using the bounding volume of the node it is attached to.
+     *
+     * @return Definition of a box shape.
+     */
+    static PhysicsCollisionShape::Definition box();
+
+    /**
+     * Defines a box shape, using the specified shape information and center.
+     *
+     * @param extents Extents of the box shape along the x, y and z axes.
+     * @param center Center point of the box.
+     * @param absolute True to specifiy that the given center point is an absolute position.
+     *		By default the center point is treated as relative to the location of the node
+     *		that the shape is attached to.
+     *
+     * @return Definition of a box shape.
+     */
+    static PhysicsCollisionShape::Definition box(const Vector3& extents, const Vector3& center = Vector3::zero(), bool absolute = false);
+
+    /**
+     * Defines a sphere shape, using the bounding volume of the node it is attached to.
+     *
+     * @return Definition of a sphere shape.
+     */
+    static PhysicsCollisionShape::Definition sphere();
+
+    /**
+     * Defines a sphere shape, using the specified shape information and center.
+     *
+     * @param radius Radius of the sphere.
+     * @param center Center point of the sphere.
+     * @param absolute True to specifiy that the given center point is an absolute position.
+     *		By default the center point is treated as relative to the location of the node
+     *		that the shape is attached to.
+     *
+     * @return Definition of a sphere shape.
+     */
+    static PhysicsCollisionShape::Definition sphere(float radius, const Vector3& center = Vector3::zero(), bool absolute = false);
+
+    /**
+     * Defines a capsule shape, using the bounding volume of the node it is attached to.
+     *
+     * @return Definition of a capsule shape.
+     */
+    static PhysicsCollisionShape::Definition capsule();
+
+    /**
+     * Defines a capsule shape, using the specified shape information and center.
+     *
+     * @param radius Radius of the capsule.
+     * @param height Height of the capsule.
+     * @param center Center point of the capsule.
+     * @param absolute True to specifiy that the given center point is an absolute position.
+     *		By default the center point is treated as relative to the location of the node
+     *		that the shape is attached to.
+     *
+     * @return Definition of a capsule shape.
+     */
+    static PhysicsCollisionShape::Definition capsule(float radius, float height, const Vector3& center = Vector3::zero(), bool absolute = false);
+
+    /**
+     * Defines a heightfield shape using the specified heightfield image.
+     *
+     * @return Definition of a heightfield shape.
+     */
+    static PhysicsCollisionShape::Definition heightfield(Image* image);
+
+    /**
+     * Defines a mesh shape using the specified mehs.
+     *
+     * @return Definition of a mesh shape.
+     */
+    static PhysicsCollisionShape::Definition mesh(Mesh* mesh);
+
+private:
+
+    struct MeshData
+    {
+        float* vertexData;
+        std::vector<unsigned char*> indexData;
+    };
+
+    struct HeightfieldData
+    {
+        float* heightData;
+        unsigned int width;
+        unsigned int height;
+        mutable Matrix inverse;
+        mutable bool inverseIsDirty;
+    };
+
+    /**
+     * Constructor.
+     */
+    PhysicsCollisionShape(Type type, btCollisionShape* shape);
+
+	/** 
+	 * Hidden copy constructor.
+	 */
+	PhysicsCollisionShape(const PhysicsCollisionShape& copy);
+
+	/**
+	 * Destructor.
+	 */
+	~PhysicsCollisionShape();
+
+
+    // Shape type
+    Type _type;
+
+    // Bullet shape object
+    btCollisionShape* _shape;
+
+    // Shape specific cached data
+    union
+    {
+        MeshData* meshData;
+        HeightfieldData* heightfieldData;
+    } _shapeData;
+
+};
+
+}
+
+#endif

+ 1 - 1
gameplay/src/PhysicsConstraint.cpp

@@ -155,7 +155,7 @@ Vector3 PhysicsConstraint::getWorldCenterOfMass(const Model* model)
 
 Vector3 PhysicsConstraint::offsetByCenterOfMass(const Node* node, const Vector3& v)
 {
-    btVector3 centerOfMassOffset = ((PhysicsMotionState*)node->getRigidBody()->_body->getMotionState())->_centerOfMassOffset.getOrigin();
+	btVector3 centerOfMassOffset = (node->getCollisionObject()->getMotionState())->_centerOfMassOffset.getOrigin();
     return Vector3(v.x + centerOfMassOffset.x(), v.y + centerOfMassOffset.y(), v.z + centerOfMassOffset.z());
 }
 

+ 428 - 126
gameplay/src/PhysicsController.cpp

@@ -1,8 +1,10 @@
 #include "Base.h"
-#include "Game.h"
-#include "MeshPart.h"
 #include "PhysicsController.h"
+#include "PhysicsRigidBody.h"
+#include "PhysicsCharacter.h"
 #include "PhysicsMotionState.h"
+#include "Game.h"
+#include "MeshPart.h"
 #include "Package.h"
 
 #include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
@@ -42,16 +44,6 @@ 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);
@@ -163,7 +155,7 @@ PhysicsCollisionObject* PhysicsController::rayTest(const Ray& ray, float distanc
 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.
+    // Get pointers to the PhysicsCollisionObject objects.
     PhysicsCollisionObject* rbA = Game::getInstance()->getPhysicsController()->getCollisionObject(a);
     PhysicsCollisionObject* rbB = Game::getInstance()->getPhysicsController()->getCollisionObject(b);
 
@@ -171,7 +163,7 @@ btScalar PhysicsController::addSingleResult(btManifoldPoint& cp, const btCollisi
     // 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);
+    PhysicsCollisionObject::CollisionPair pair(rbA, rbB);
 
     CollisionInfo* collisionInfo;
     if (_collisionStatus.count(pair) > 0)
@@ -184,7 +176,7 @@ btScalar PhysicsController::addSingleResult(btManifoldPoint& cp, const btCollisi
         collisionInfo = &_collisionStatus[pair];
 
         // Add the appropriate listeners.
-        PhysicsRigidBody::CollisionPair p1(pair.objectA, NULL);
+        PhysicsCollisionObject::CollisionPair p1(pair.objectA, NULL);
         if (_collisionStatus.count(p1) > 0)
         {
             const CollisionInfo& ci = _collisionStatus[p1];
@@ -194,7 +186,7 @@ btScalar PhysicsController::addSingleResult(btManifoldPoint& cp, const btCollisi
                 collisionInfo->_listeners.push_back(*iter);
             }
         }
-        PhysicsRigidBody::CollisionPair p2(pair.objectB, NULL);
+        PhysicsCollisionObject::CollisionPair p2(pair.objectB, NULL);
         if (_collisionStatus.count(p2) > 0)
         {
             const CollisionInfo& ci = _collisionStatus[p2];
@@ -243,6 +235,8 @@ void PhysicsController::initialize()
     _ghostPairCallback = bullet_new<btGhostPairCallback>();
     _world->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback);
 
+	_world->getDispatchInfo().m_allowedCcdPenetration = 0.0001f;
+
     // Set up debug drawing.
     _debugDrawer = new DebugDrawer();
     _world->setDebugDrawer(_debugDrawer);
@@ -283,7 +277,7 @@ void PhysicsController::update(long elapsedTime)
     {
         Listener::EventType oldStatus = _status;
 
-        if (_status = Listener::DEACTIVATED)
+        if (_status == Listener::DEACTIVATED)
         {
             for (int i = 0; i < _world->getNumCollisionObjects(); i++)
             {
@@ -328,12 +322,12 @@ void PhysicsController::update(long elapsedTime)
     // If an entry was marked for removal in the last frame, remove it now.
 
     // Dirty the collision status cache entries.
-    std::map<PhysicsRigidBody::CollisionPair, CollisionInfo>::iterator iter = _collisionStatus.begin();
+    std::map<PhysicsCollisionObject::CollisionPair, CollisionInfo>::iterator iter = _collisionStatus.begin();
     for (; iter != _collisionStatus.end();)
     {
         if ((iter->second._status & REMOVE) != 0)
         {
-            std::map<PhysicsRigidBody::CollisionPair, CollisionInfo>::iterator eraseIter = iter;
+            std::map<PhysicsCollisionObject::CollisionPair, CollisionInfo>::iterator eraseIter = iter;
             iter++;
             _collisionStatus.erase(eraseIter);
         }
@@ -410,15 +404,15 @@ void PhysicsController::addCollisionObject(PhysicsCollisionObject* object)
     switch (object->getType())
     {
     case PhysicsCollisionObject::RIGID_BODY:
-        _world->addRigidBody(static_cast<btRigidBody*>(object->getCollisionObject()));
+		_world->addRigidBody(static_cast<btRigidBody*>(object->getCollisionObject()), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::DefaultFilter | btBroadphaseProxy::StaticFilter | btBroadphaseProxy::CharacterFilter | btBroadphaseProxy::AllFilter);
         break;
 
     case PhysicsCollisionObject::CHARACTER:
-        _world->addCollisionObject(object->getCollisionObject(), btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::StaticFilter | btBroadphaseProxy::CharacterFilter | btBroadphaseProxy::DefaultFilter);
+		_world->addCollisionObject(object->getCollisionObject(), btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::DefaultFilter | btBroadphaseProxy::StaticFilter | btBroadphaseProxy::CharacterFilter | btBroadphaseProxy::AllFilter);
         break;
 
     case PhysicsCollisionObject::GHOST_OBJECT:
-        _world->addCollisionObject(object->getCollisionObject(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::AllFilter);
+		_world->addCollisionObject(object->getCollisionObject(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::DefaultFilter | btBroadphaseProxy::StaticFilter | btBroadphaseProxy::CharacterFilter | btBroadphaseProxy::AllFilter);
         break;
 
     default:
@@ -449,28 +443,8 @@ void PhysicsController::removeCollisionObject(PhysicsCollisionObject* object)
         }
     }
 
-    // Release collision shape
-    if (object->getCollisionShape())
-    {
-        PhysicsCollisionShape* shape = reinterpret_cast<PhysicsCollisionShape*>(object->getCollisionShape()->getUserPointer());
-        if (shape)
-        {
-            if (shape->getRefCount() == 1)
-            {
-                std::vector<PhysicsCollisionShape*>::iterator shapeItr = std::find(_shapes.begin(), _shapes.end(), shape);
-                shape->release();
-                if (shapeItr != _shapes.end())
-                    _shapes.erase(shapeItr);
-            }
-            else
-            {
-                shape->release();
-            }
-        }
-    }
-
     // Find all references to the object in the collision status cache and mark them for removal.
-    std::map<PhysicsRigidBody::CollisionPair, CollisionInfo>::iterator iter = _collisionStatus.begin();
+    std::map<PhysicsCollisionObject::CollisionPair, CollisionInfo>::iterator iter = _collisionStatus.begin();
     for (; iter != _collisionStatus.end(); iter++)
     {
         if (iter->first.objectA == object || iter->first.objectB == object)
@@ -484,55 +458,207 @@ PhysicsCollisionObject* PhysicsController::getCollisionObject(const btCollisionO
     return reinterpret_cast<PhysicsCollisionObject*>(collisionObject->getUserPointer());
 }
 
-btCollisionShape* PhysicsController::createBox(const Vector3& min, const Vector3& max, const Vector3& scale)
+void getBoundingBox(Node* node, BoundingBox* out, bool merge = false)
 {
-    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));
+	if (node->getModel())
+	{
+		if (merge)
+			out->merge(node->getModel()->getMesh()->getBoundingBox());
+		else
+		{
+			out->set(node->getModel()->getMesh()->getBoundingBox());
+			merge = true;
+		}
+	}
+
+	Node* child = node->getFirstChild();
+	while (child)
+	{
+		getBoundingBox(child, out, merge);
+		child = child->getNextSibling();
+	}
+}
 
-    // Return the box shape from the cache if it already exists.
-    for (unsigned int i = 0; i < _shapes.size(); i++)
+void getBoundingSphere(Node* node, BoundingSphere* out, bool merge = false)
+{
+	if (node->getModel())
+	{
+		if (merge)
+			out->merge(node->getModel()->getMesh()->getBoundingSphere());
+		else
+		{
+			out->set(node->getModel()->getMesh()->getBoundingSphere());
+			merge = true;
+		}
+	}
+
+	Node* child = node->getFirstChild();
+	while (child)
+	{
+		getBoundingSphere(child, out, merge);
+		child = child->getNextSibling();
+	}
+}
+
+void computeCenterOfMass(const Vector3& center, const Vector3& scale, Vector3* centerOfMassOffset)
+{
+	// Update center of mass offset
+	*centerOfMassOffset = center;
+	centerOfMassOffset->x *= scale.x;
+	centerOfMassOffset->y *= scale.y;
+	centerOfMassOffset->z *= scale.z;
+	centerOfMassOffset->negate();
+}
+
+PhysicsCollisionShape* PhysicsController::createShape(Node* node, const PhysicsCollisionShape::Definition& shape, Vector3* centerOfMassOffset)
+{
+	PhysicsCollisionShape* collisionShape = NULL;
+
+    // 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 (shape.type)
     {
-        if (_shapes[i]->_shape->getShapeType() == BOX_SHAPE_PROXYTYPE)
+	case PhysicsCollisionShape::SHAPE_BOX:
         {
-            btBoxShape* box = static_cast<btBoxShape*>(_shapes[i]->_shape);
-            if (box->getHalfExtentsWithMargin() == halfExtents)
-            {
-                _shapes[i]->addRef();
-                return box;
-            }
+			if (shape.isExplicit)
+			{
+				// Use the passed in box information
+				collisionShape = createBox(Vector3(shape.data.box.extents), Vector3::one());
+
+				if (shape.centerAbsolute)
+				{
+					computeCenterOfMass(Vector3(shape.data.box.center), Vector3::one(), centerOfMassOffset);
+				}
+				else
+				{
+					BoundingBox box;
+					getBoundingBox(node, &box);
+					computeCenterOfMass(box.getCenter() + Vector3(shape.data.box.center), scale, centerOfMassOffset);
+				}
+			}
+			else
+			{
+				// Automatically compute bounding box from mesh's bounding box
+				BoundingBox box;
+				getBoundingBox(node, &box);
+				collisionShape = createBox(Vector3(std::abs(box.max.x - box.min.x), std::abs(box.max.y - box.min.y), std::abs(box.max.z - box.min.z)), scale);
+
+				computeCenterOfMass(box.getCenter(), scale, centerOfMassOffset);
+			}
+        }
+		break;
+
+	case PhysicsCollisionShape::SHAPE_SPHERE:
+        {
+			if (shape.isExplicit)
+			{
+				// Use the passed in sphere information
+				collisionShape = createSphere(shape.data.sphere.radius, Vector3::one());
+
+				if (shape.centerAbsolute)
+				{
+					computeCenterOfMass(Vector3(shape.data.sphere.center), Vector3::one(), centerOfMassOffset);
+				}
+				else
+				{
+					BoundingSphere sphere;
+					getBoundingSphere(node, &sphere);
+					computeCenterOfMass(sphere.center + Vector3(shape.data.sphere.center), scale, centerOfMassOffset);
+				}
+			}
+			else
+			{
+				// Automatically compute bounding sphere from mesh's bounding sphere
+				BoundingSphere sphere;
+				getBoundingSphere(node, &sphere);
+				collisionShape = createSphere(sphere.radius, scale);
+
+				computeCenterOfMass(sphere.center, scale, centerOfMassOffset);
+			}
+        }
+		break;
+
+	case PhysicsCollisionShape::SHAPE_CAPSULE:
+		{
+			if (shape.isExplicit)
+			{
+				// Use the passed in capsule information
+				collisionShape = createCapsule(shape.data.capsule.radius, shape.data.capsule.height, Vector3::one());
+
+				if (shape.centerAbsolute)
+				{
+					computeCenterOfMass(Vector3(shape.data.capsule.center), Vector3::one(), centerOfMassOffset);
+				}
+				else
+				{
+					BoundingBox box;
+					getBoundingBox(node, &box);
+					computeCenterOfMass(box.getCenter() + Vector3(shape.data.capsule.center), scale, centerOfMassOffset);
+				}
+			}
+			else
+			{
+				// Compute a capsule shape that roughly matches the bounding box of the mesh
+				BoundingBox box;
+				getBoundingBox(node, &box);
+				float radius = std::max((box.max.x - box.min.x) * 0.5f, (box.max.z - box.min.z) * 0.5f);
+				float height = box.max.y - box.min.y;
+				collisionShape = createCapsule(radius, height, scale);
+
+				computeCenterOfMass(box.getCenter(), scale, centerOfMassOffset);
+			}
+		}
+		break;
+
+	case PhysicsCollisionShape::SHAPE_HEIGHTFIELD:
+		{
+			// Build heightfield rigid body from the passed in shape
+			collisionShape = createHeightfield(node, shape.data.heightfield, centerOfMassOffset);
+		}
+		break;
+
+	case PhysicsCollisionShape::SHAPE_MESH:
+        {
+			// Build mesh from passed in shape
+			collisionShape = createMesh(shape.data.mesh, scale);
         }
+		break;
     }
-    
-    // Create the box shape and add it to the cache.
-    btBoxShape* box = bullet_new<btBoxShape>(halfExtents);
-    _shapes.push_back(new PhysicsCollisionShape(box));
 
-    return box;
+	return collisionShape;
 }
 
-btCollisionShape* PhysicsController::createCapsule(float radius, float height)
+PhysicsCollisionShape* PhysicsController::createBox(const Vector3& extents, const Vector3& scale)
 {
-    // Return the capsule shape from the cache if it already exists.
-    for (unsigned int i = 0; i < _shapes.size(); i++)
+    btVector3 halfExtents(scale.x * 0.5 * extents.x, scale.y * 0.5 * extents.y, scale.z * 0.5 * extents.z);
+
+	PhysicsCollisionShape* shape;
+
+    // Return the box shape from the cache if it already exists.
+    for (unsigned int i = 0; i < _shapes.size(); ++i)
     {
-        if (_shapes[i]->_shape->getShapeType() == CAPSULE_SHAPE_PROXYTYPE)
+		shape = _shapes[i];
+		if (shape->getType() == PhysicsCollisionShape::SHAPE_BOX)
         {
-            btCapsuleShape* capsule = static_cast<btCapsuleShape*>(_shapes[i]->_shape);
-            if (capsule->getRadius() == radius && capsule->getHalfHeight() == 0.5f * height)
+			btBoxShape* box = static_cast<btBoxShape*>(shape->_shape);
+            if (box->getHalfExtentsWithMargin() == halfExtents)
             {
-                _shapes[i]->addRef();
-                return capsule;
+				shape->addRef();
+                return shape;
             }
         }
     }
-    
-    // Create the capsule shape and add it to the cache.
-    btCapsuleShape* capsule = bullet_new<btCapsuleShape>(radius, height);
-    _shapes.push_back(new PhysicsCollisionShape(capsule));
 
-    return capsule;
+    // Create the box shape and add it to the cache.
+	shape = new PhysicsCollisionShape(PhysicsCollisionShape::SHAPE_BOX, bullet_new<btBoxShape>(halfExtents));
+    _shapes.push_back(shape);
+
+    return shape;
 }
 
-btCollisionShape* PhysicsController::createSphere(float radius, const Vector3& scale)
+PhysicsCollisionShape* 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.
@@ -541,48 +667,188 @@ btCollisionShape* PhysicsController::createSphere(float radius, const Vector3& s
         uniformScale = scale.y;
     if (uniformScale < scale.z)
         uniformScale = scale.z;
-    
+
+	float scaledRadius = radius * uniformScale;
+
+	PhysicsCollisionShape* shape;
+
     // Return the sphere shape from the cache if it already exists.
-    for (unsigned int i = 0; i < _shapes.size(); i++)
+    for (unsigned int i = 0; i < _shapes.size(); ++i)
     {
-        if (_shapes[i]->_shape->getShapeType() == SPHERE_SHAPE_PROXYTYPE)
+		shape = _shapes[i];
+		if (shape->getType() == PhysicsCollisionShape::SHAPE_SPHERE)
         {
-            btSphereShape* sphere = static_cast<btSphereShape*>(_shapes[i]->_shape);
-            if (sphere->getRadius() == uniformScale * radius)
+            btSphereShape* sphere = static_cast<btSphereShape*>(shape->_shape);
+            if (sphere->getRadius() == scaledRadius)
             {
-                _shapes[i]->addRef();
-                return sphere;
+                shape->addRef();
+                return shape;
             }
         }
     }
 
     // Create the sphere shape and add it to the cache.
-    btSphereShape* sphere = bullet_new<btSphereShape>(uniformScale * radius);
-    _shapes.push_back(new PhysicsCollisionShape(sphere));
+	shape = new PhysicsCollisionShape(PhysicsCollisionShape::SHAPE_SPHERE, bullet_new<btSphereShape>(scaledRadius));
+    _shapes.push_back(shape);
 
-    return sphere;
+    return shape;
 }
 
-btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Vector3& scale)
+PhysicsCollisionShape* PhysicsController::createCapsule(float radius, float height, const Vector3& scale)
 {
-    assert(body);
+	float girthScale = scale.x;
+	if (girthScale < scale.z)
+		girthScale = scale.z;
+	float scaledRadius = radius * girthScale;
+	float scaledHeight = height * scale.y - radius * 2;
+
+	PhysicsCollisionShape* shape;
 
-    // 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)
+    // Return the capsule shape from the cache if it already exists.
+    for (unsigned int i = 0; i < _shapes.size(); i++)
     {
-        LOG_ERROR("Cannot create mesh rigid body for node without model/mesh.");
-        return NULL;
+		shape = _shapes[i];
+		if (shape->getType() == PhysicsCollisionShape::SHAPE_CAPSULE)
+        {
+            btCapsuleShape* capsule = static_cast<btCapsuleShape*>(shape->_shape);
+            if (capsule->getRadius() == scaledRadius && capsule->getHalfHeight() == 0.5f * scaledHeight)
+            {
+                shape->addRef();
+                return shape;
+            }
+        }
     }
 
-    // Only support meshes with triangle list primitive types
-    if (mesh->getPrimitiveType() != Mesh::TRIANGLES)
+    // Create the capsule shape and add it to the cache.
+	shape = new PhysicsCollisionShape(PhysicsCollisionShape::SHAPE_CAPSULE, bullet_new<btCapsuleShape>(scaledRadius, scaledHeight));
+    _shapes.push_back(shape);
+
+    return shape;
+}
+
+PhysicsCollisionShape* PhysicsController::createHeightfield(Node* node, Image* image, Vector3* centerOfMassOffset)
+{
+	// Get the dimensions of the heightfield.
+	// If the node has a mesh defined, use the dimensions of the bounding box for the mesh.
+	// Otherwise simply use the image dimensions (with a max height of 255).
+	float width, length, minHeight, maxHeight;
+	if (node->getModel() && node->getModel()->getMesh())
+	{
+		const BoundingBox& box = node->getModel()->getMesh()->getBoundingBox();
+		width = box.max.x - box.min.x;
+		length = box.max.z - box.min.z;
+		minHeight = box.min.y;
+		maxHeight = box.max.y;
+	}
+	else
+	{
+		width = image->getWidth();
+		length = image->getHeight();
+		minHeight = 0.0f;
+		maxHeight = 255.0f;
+	}
+
+    // Get the size in bytes of a pixel (we ensure that the image's
+    // pixel format is actually supported before calling this constructor).
+    unsigned int pixelSize = 0;
+    switch (image->getFormat())
     {
-        LOG_ERROR("Cannot create mesh rigid body for mesh without TRIANGLES primitive type.");
-        return NULL;
+        case Image::RGB:
+            pixelSize = 3;
+            break;
+        case Image::RGBA:
+            pixelSize = 4;
+            break;
+		default:
+			LOG_ERROR("Unsupported pixel format for heightmap image.");
+			return NULL;
+    }
+
+    // Calculate the heights for each pixel.
+    float* heights = new float[image->getWidth() * image->getHeight()];
+	unsigned char* data = image->getData();
+    for (unsigned int x = 0, w = image->getWidth(); x < w; ++x)
+    {
+        for (unsigned int y = 0, h = image->getHeight(); y < h; ++y)
+        {
+            heights[x + y * w] = ((((float)data[(x + y * h) * pixelSize + 0]) +
+                ((float)data[(x + y * h) * pixelSize + 1]) +
+                ((float)data[(x + y * h) * pixelSize + 2])) / 768.0f) * (maxHeight - minHeight) + minHeight;
+        }
     }
 
+	PhysicsCollisionShape::HeightfieldData* heightfieldData = new PhysicsCollisionShape::HeightfieldData();
+	heightfieldData->heightData = NULL;
+	heightfieldData->inverseIsDirty = true;
+
+    // Generate the heightmap data needed for physics (one height per world unit).
+    unsigned int sizeWidth = width;
+    unsigned int sizeHeight = length;
+	heightfieldData->width = sizeWidth + 1;
+    heightfieldData->height = sizeHeight + 1;
+	heightfieldData->heightData = new float[heightfieldData->width * heightfieldData->height];
+    unsigned int heightIndex = 0;
+    float widthImageFactor = (float)(image->getWidth() - 1) / sizeWidth;
+    float heightImageFactor = (float)(image->getHeight() - 1) / sizeHeight;
+    float x = 0.0f;
+    float z = 0.0f;
+    for (unsigned int row = 0, z = 0.0f; row <= sizeHeight; row++, z += 1.0f)
+    {
+        for (unsigned int col = 0, x = 0.0f; col <= sizeWidth; col++, x += 1.0f)
+        {
+			heightIndex = row * heightfieldData->width + col;
+			heightfieldData->heightData[heightIndex] = calculateHeight(heights, image->getWidth(), image->getHeight(), x * widthImageFactor, (sizeHeight - z) * heightImageFactor);
+        }
+    }
+    SAFE_DELETE_ARRAY(heights);
+
+    // Offset the heightmap's center of mass according to the way that Bullet calculates the origin 
+    // of its heightfield collision shape; see documentation for the btHeightfieldTerrainShape for more info.
+    Vector3 s;
+    node->getWorldMatrix().getScale(&s);
+	centerOfMassOffset->set(0.0f, -(maxHeight - (0.5f * (maxHeight - minHeight))) / s.y, 0.0f);
+
+	// Create the bullet terrain shape
+	btHeightfieldTerrainShape* terrainShape = bullet_new<btHeightfieldTerrainShape>(
+		heightfieldData->width, heightfieldData->height, heightfieldData->heightData, 1.0f, minHeight, maxHeight, 1, PHY_FLOAT, false);
+
+	// Create our collision shape object and store heightfieldData in it
+	PhysicsCollisionShape* shape = new PhysicsCollisionShape(PhysicsCollisionShape::SHAPE_HEIGHTFIELD, terrainShape);
+	shape->_shapeData.heightfieldData = heightfieldData;
+
+    _shapes.push_back(shape);
+
+    return shape;
+}
+
+PhysicsCollisionShape* PhysicsController::createMesh(Mesh* mesh, const Vector3& scale)
+{
+    assert(mesh);
+
+	// Only support meshes with triangle list primitive types
+	bool triMesh = true;
+	if (mesh->getPartCount() > 0)
+	{
+		for (unsigned int i = 0; i < mesh->getPartCount(); ++i)
+		{
+			if (mesh->getPart(i)->getPrimitiveType() != Mesh::TRIANGLES)
+			{
+				triMesh = false;
+				break;
+			}
+		}
+	}
+	else
+	{
+		triMesh = mesh->getPrimitiveType() == Mesh::TRIANGLES;
+	}
+
+	if (!triMesh)
+	{
+		LOG_ERROR("Mesh rigid bodies are currently only supported on meshes with 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)
@@ -597,11 +863,15 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
         return NULL;
     }
 
+	// Create mesh data to be populated and store in returned collision shape
+	PhysicsCollisionShape::MeshData* shapeMeshData = new PhysicsCollisionShape::MeshData();
+	shapeMeshData->vertexData = NULL;
+
     // Copy the scaled vertex position data to the rigid body's local buffer.
     Matrix m;
     Matrix::createScale(scale, &m);
     unsigned int vertexCount = data->vertexCount;
-    body->_vertexData = new float[vertexCount * 3];
+	shapeMeshData->vertexData = new float[vertexCount * 3];
     Vector3 v;
     int vertexStride = data->vertexFormat.getVertexSize();
     for (unsigned int i = 0; i < data->vertexCount; i++)
@@ -610,7 +880,7 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
               *((float*)&data->vertexData[i * vertexStride + 1 * sizeof(float)]),
               *((float*)&data->vertexData[i * vertexStride + 2 * sizeof(float)]));
         v *= m;
-        memcpy(&(body->_vertexData[i * 3]), &v, sizeof(float) * 3);
+		memcpy(&(shapeMeshData->vertexData[i * 3]), &v, sizeof(float) * 3);
     }
 
     btTriangleIndexVertexArray* meshInterface = bullet_new<btTriangleIndexVertexArray>();
@@ -643,7 +913,7 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
 
             // 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);
+			shapeMeshData->indexData.push_back(meshPart->indexData);
             meshPart->indexData = NULL;
 
             // Create a btIndexedMesh object for the current mesh part.
@@ -651,9 +921,9 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
             indexedMesh.m_indexType = indexType;
             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_triangleIndexBase = (const unsigned char*)shapeMeshData->indexData[i];
             indexedMesh.m_triangleIndexStride = indexStride*3;
-            indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
+			indexedMesh.m_vertexBase = (const unsigned char*)shapeMeshData->vertexData;
             indexedMesh.m_vertexStride = sizeof(float)*3;
             indexedMesh.m_vertexType = PHY_FLOAT;
 
@@ -669,16 +939,16 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
         {
             indexData[i] = i;
         }
-        body->_indexData.push_back((unsigned char*)indexData);
+        shapeMeshData->indexData.push_back((unsigned char*)indexData);
 
         // Create a single btIndexedMesh object for the mesh interface.
         btIndexedMesh indexedMesh;
         indexedMesh.m_indexType = PHY_INTEGER;
         indexedMesh.m_numTriangles = data->vertexCount / 3; // assume TRIANGLES primitive type
         indexedMesh.m_numVertices = data->vertexCount;
-        indexedMesh.m_triangleIndexBase = body->_indexData[0];
+        indexedMesh.m_triangleIndexBase = shapeMeshData->indexData[0];
         indexedMesh.m_triangleIndexStride = sizeof(unsigned int);
-        indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
+        indexedMesh.m_vertexBase = (const unsigned char*)shapeMeshData->vertexData;
         indexedMesh.m_vertexStride = sizeof(float)*3;
         indexedMesh.m_vertexType = PHY_FLOAT;
 
@@ -686,8 +956,11 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
         meshInterface->addIndexedMesh(indexedMesh, indexedMesh.m_indexType);
     }
 
-    btBvhTriangleMeshShape* shape = bullet_new<btBvhTriangleMeshShape>(meshInterface, true);
-    _shapes.push_back(new PhysicsCollisionShape(shape));
+	// Create our collision shape object and store shapeMeshData in it
+	PhysicsCollisionShape* shape = new PhysicsCollisionShape(PhysicsCollisionShape::SHAPE_MESH, bullet_new<btBvhTriangleMeshShape>(meshInterface, true));
+	shape->_shapeData.meshData = shapeMeshData;
+
+    _shapes.push_back(shape);
 
     // Free the temporary mesh data now that it's stored in physics system
     SAFE_DELETE(data);
@@ -695,11 +968,52 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body, const Ve
     return shape;
 }
 
-btCollisionShape* PhysicsController::createHeightfield(int width, int height, void* heightfieldData, float minHeight, float maxHeight)
+void PhysicsController::destroyShape(PhysicsCollisionShape* shape)
 {
-    btCollisionShape* shape = bullet_new<btHeightfieldTerrainShape>(width, height, heightfieldData, 1.0f, minHeight, maxHeight, 1, PHY_FLOAT, false);
-    _shapes.push_back(new PhysicsCollisionShape(shape));
-    return shape;
+    if (shape)
+    {
+        if (shape->getRefCount() == 1)
+        {
+			// Remove shape from shape cache
+            std::vector<PhysicsCollisionShape*>::iterator shapeItr = std::find(_shapes.begin(), _shapes.end(), shape);
+            if (shapeItr != _shapes.end())
+                _shapes.erase(shapeItr);
+        }
+
+		// Release the shape
+		shape->release();
+    }
+}
+
+float PhysicsController::calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y)
+{
+    unsigned int x1 = x;
+    unsigned int y1 = y;
+    unsigned int x2 = x1 + 1;
+    unsigned int y2 = y1 + 1;
+    float tmp;
+    float xFactor = modf(x, &tmp);
+    float yFactor = modf(y, &tmp);
+    float xFactorI = 1.0f - xFactor;
+    float yFactorI = 1.0f - yFactor;
+
+    if (x2 >= width && y2 >= height)
+    {
+        return data[x1 + y1 * width];
+    }
+    else if (x2 >= width)
+    {
+        return data[x1 + y1 * width] * yFactorI + data[x1 + y2 * width] * yFactor;
+    }
+    else if (y2 >= height)
+    {
+        return data[x1 + y1 * width] * xFactorI + data[x2 + y1 * width] * xFactor;
+    }
+    else
+    {
+        return data[x1 + y1 * width] * xFactorI * yFactorI + data[x1 + y2 * width] * xFactorI * yFactor + 
+            data[x2 + y2 * width] * xFactor * yFactor + data[x2 + y1 * width] * xFactor * yFactorI;
+    }
 }
 
 void PhysicsController::addConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint)
@@ -743,7 +1057,7 @@ void PhysicsController::removeConstraint(PhysicsConstraint* constraint)
         }
     }
 }
-    
+
 PhysicsController::DebugDrawer::DebugDrawer()
     : _mode(btIDebugDraw::DBG_DrawAabb | btIDebugDraw::DBG_DrawConstraintLimits | btIDebugDraw::DBG_DrawConstraints | 
        btIDebugDraw::DBG_DrawContactPoints | btIDebugDraw::DBG_DrawWireframe), _viewProjection(NULL), _meshBatch(NULL)
@@ -760,7 +1074,7 @@ PhysicsController::DebugDrawer::DebugDrawer()
         "    gl_Position = u_viewProjectionMatrix * a_position;\n"
         "}"
     };
-        
+
     // Fragment shader for drawing colored lines.
     const char* fs_str = 
     {
@@ -772,7 +1086,7 @@ PhysicsController::DebugDrawer::DebugDrawer()
         "   gl_FragColor = v_color;\n"
         "}"
     };
-        
+
     Effect* effect = Effect::createFromSource(vs_str, fs_str);
     Material* material = Material::create(effect);
     material->getStateBlock()->setDepthTest(true);
@@ -783,7 +1097,7 @@ PhysicsController::DebugDrawer::DebugDrawer()
         VertexFormat::Element(VertexFormat::COLOR, 4),
     };
     _meshBatch = MeshBatch::create(VertexFormat(elements, 2), Mesh::LINES, material, false);
-    
+
     SAFE_RELEASE(material);
     SAFE_RELEASE(effect);
 }
@@ -809,7 +1123,7 @@ void PhysicsController::DebugDrawer::end()
 void PhysicsController::DebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& fromColor, const btVector3& toColor)
 {
     static DebugDrawer::DebugVertex fromVertex, toVertex;
-    
+
     fromVertex.x = from.getX();
     fromVertex.y = from.getY();
     fromVertex.z = from.getZ();
@@ -860,16 +1174,4 @@ int	PhysicsController::DebugDrawer::getDebugMode() const
     return _mode;
 }
 
-PhysicsController::PhysicsCollisionShape::PhysicsCollisionShape(btCollisionShape* shape)
-    : _shape(shape)
-{
-    // Assign user pointer to allow efficient lookup of PhysicsCollisionShape from bullet object
-    shape->setUserPointer(this);
-}
-
-PhysicsController::PhysicsCollisionShape::~PhysicsCollisionShape()
-{
-    SAFE_DELETE(_shape);
-}
-
 }

+ 24 - 59
gameplay/src/PhysicsController.h

@@ -7,12 +7,12 @@
 #include "PhysicsHingeConstraint.h"
 #include "PhysicsSocketConstraint.h"
 #include "PhysicsSpringConstraint.h"
-#include "PhysicsRigidBody.h"
-#include "PhysicsCharacter.h"
+#include "PhysicsCollisionObject.h"
+#include "MeshBatch.h"
 
 namespace gameplay
 {
-    
+
 /**
  * Defines a class for controlling game physics.
  */
@@ -63,42 +63,6 @@ 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.
      * 
@@ -265,16 +229,7 @@ private:
         int _status;
     };
 
-    // Wraps Bullet collision shapes (used for implementing shape caching).
-    struct PhysicsCollisionShape : public Ref
-    {
-        PhysicsCollisionShape(btCollisionShape* shape);
-        ~PhysicsCollisionShape();
-
-        btCollisionShape* _shape;
-    };
-
-    /**
+	/**
      * Constructor.
      */
     PhysicsController();
@@ -323,21 +278,31 @@ private:
     
     // Gets the corresponding GamePlay object for the given Bullet object.
     PhysicsCollisionObject* getCollisionObject(const btCollisionObject* collisionObject) const;
+
+	// Creates a collision shape for the given node and gameplay shape definition.
+	// Populates 'centerOfMassOffset' with the correct calculated center of mass offset.
+	PhysicsCollisionShape* createShape(Node* node, const PhysicsCollisionShape::Definition& shape, Vector3* centerOfMassOffset);
     
-    // Creates a box collision shape to be used in the creation of a rigid body.
-    btCollisionShape* createBox(const Vector3& min, const Vector3& max, const Vector3& scale);
+    // Creates a box collision shape.
+    PhysicsCollisionShape* createBox(const Vector3& extents, const Vector3& scale);
+
+	// Creates a sphere collision shape.
+    PhysicsCollisionShape* createSphere(float radius, const Vector3& scale);
+
+    // Creates a capsule collision shape.
+    PhysicsCollisionShape* createCapsule(float radius, float height, 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 heightfield collision shape.
+    PhysicsCollisionShape* createHeightfield(Node* node, Image* image, Vector3* centerOfMassOffset);
 
-    // Creates a sphere collision shape to be used in the creation of a rigid body.
-    btCollisionShape* createSphere(float radius, const Vector3& scale);
+    // Creates a triangle mesh collision shape.
+    PhysicsCollisionShape* createMesh(Mesh* mesh, const Vector3& scale);
 
-    // Creates a triangle mesh collision shape to be used in the creation of a rigid body.
-    btCollisionShape* createMesh(PhysicsRigidBody* body, const Vector3& scale);
+	// Destroys a collision shape created through PhysicsController
+	void destroyShape(PhysicsCollisionShape* shape);
 
-    // Creates a heightfield collision shape to be used in the creation of a rigid body.
-    btCollisionShape* createHeightfield(int width, int height, void* heightfieldData, float minHeight, float maxHeight);
+	// Helper function for calculating heights from heightmap (image) or heightfield data.
+	static float calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y);
 
     // Sets up the given constraint for the given two rigid bodies.
     void addConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint);

+ 38 - 46
gameplay/src/PhysicsGhostObject.cpp

@@ -1,74 +1,71 @@
 #include "Base.h"
-#include "Game.h"
 #include "PhysicsGhostObject.h"
-#include "Vector3.h"
+#include "Node.h"
+#include "Game.h"
 
 namespace gameplay
 {
 
-PhysicsGhostObject::PhysicsGhostObject(Node* node, PhysicsRigidBody::ShapeType type)
-    : _node(node), _motionState(NULL), _shape(NULL), _ghostObject(NULL)
+PhysicsGhostObject::PhysicsGhostObject(Node* node, const PhysicsCollisionShape::Definition& shape)
+    : PhysicsCollisionObject(node), _ghostObject(NULL)
 {
-    _node->addListener(this);
+    Vector3 centerOfMassOffset;
+    PhysicsController* physicsController = Game::getInstance()->getPhysicsController();
+
+    // Create and set the collision shape for the ghost object.
+    _collisionShape = physicsController->createShape(node, shape, &centerOfMassOffset);
 
     // Create the ghost object.
     _ghostObject = bullet_new<btPairCachingGhostObject>();
+	_ghostObject->setCollisionShape(_collisionShape->getShape());
 
-    // Get the node's world scale (we need to apply this during creation since ghost objects don't scale dynamically).
-    Vector3 scale;
-    _node->getWorldMatrix().getScale(&scale);
-
-    // 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;
-        
     // Initialize a physics motion state object for syncing the transform.
-    Vector3 centerOfMassOffset(-c);
     _motionState = new PhysicsMotionState(_node, &centerOfMassOffset);
     _motionState->getWorldTransform(_ghostObject->getWorldTransform());
 
-    // Create and set the collision shape for the ghost object.
-    PhysicsController* physicsController = Game::getInstance()->getPhysicsController();
-    switch (type)
-    {
-        case PhysicsRigidBody::SHAPE_BOX:
-        {
-            const BoundingBox& box = node->getModel()->getMesh()->getBoundingBox();
-            _shape = physicsController->createBox(box.min, box.max, scale);
-            break;
-        }
-        case PhysicsRigidBody::SHAPE_SPHERE:
-        {
-            const BoundingSphere& sphere = node->getModel()->getMesh()->getBoundingSphere();
-            _shape = physicsController->createSphere(sphere.radius, scale);
-            break;
-        }
-    }
-    _ghostObject->setCollisionShape(_shape);
-
     // Add the ghost object to the physics world.
     physicsController->addCollisionObject(this);
+
+    _node->addListener(this);
 }
 
 PhysicsGhostObject::~PhysicsGhostObject()
 {
+    _node->removeListener(this);
+
     Game::getInstance()->getPhysicsController()->removeCollisionObject(this);
 
-    _shape = NULL;
     SAFE_DELETE(_ghostObject);
-    SAFE_DELETE(_motionState);
 }
 
-PhysicsCollisionObject::Type PhysicsGhostObject::getType() const
+PhysicsGhostObject* PhysicsGhostObject::create(Node* node, Properties* properties)
 {
-    return GHOST_OBJECT;
+    // Check if the properties is valid and has a valid namespace.
+    assert(properties);
+    if (!properties || !(strcmp(properties->getNamespace(), "ghost") == 0))
+    {
+        WARN("Failed to load ghost object from properties object: must be non-null object and have namespace equal to \'ghost\'.");
+        return NULL;
+    }
+
+    // Load the physics collision shape definition.
+    PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
+    if (shape == NULL)
+    {
+        WARN("Failed to create collision shape during ghost object creation.");
+        return NULL;
+    }
+
+    // Create the ghost object.
+    PhysicsGhostObject* ghost = new PhysicsGhostObject(node, *shape);
+    SAFE_DELETE(shape);
+
+    return ghost;
 }
 
-Node* PhysicsGhostObject::getNode() const
+PhysicsCollisionObject::Type PhysicsGhostObject::getType() const
 {
-    return _node;
+    return GHOST_OBJECT;
 }
 
 btCollisionObject* PhysicsGhostObject::getCollisionObject() const
@@ -76,11 +73,6 @@ btCollisionObject* PhysicsGhostObject::getCollisionObject() const
     return _ghostObject;
 }
 
-btCollisionShape* PhysicsGhostObject::getCollisionShape() const
-{
-    return _shape;
-}
-
 void PhysicsGhostObject::transformChanged(Transform* transform, long cookie)
 {
     // Update the motion state with the transform from the node.

+ 16 - 20
gameplay/src/PhysicsGhostObject.h

@@ -16,7 +16,6 @@ class PhysicsMotionState;
 class PhysicsGhostObject : public PhysicsCollisionObject, public Transform::Listener
 {
     friend class Node;
-    friend class PhysicsController;
 
 public:
 
@@ -26,9 +25,9 @@ public:
     PhysicsCollisionObject::Type getType() const;
 
     /**
-     * @see PhysicsCollisionObject#getNode
+     * Used to synchronize the transform between GamePlay and Bullet.
      */
-    Node* getNode() const;
+    void transformChanged(Transform* transform, long cookie);
 
 protected:
 
@@ -37,37 +36,34 @@ protected:
      */
     btCollisionObject* getCollisionObject() const;
 
-    /**
-     * @see PhysicsCollisionObject::getCollisionShape
-     */
-    btCollisionShape* getCollisionShape() const;
-
-private:
+protected:
 
     /**
      * Constructor.
      * 
      * @param node The node to attach the ghost object to.
-     * @param type The type of ghost object (collision shape type).
+     * @param shape The collision shape definition for the ghost object.
      */
-    PhysicsGhostObject(Node* node, PhysicsRigidBody::ShapeType type);
-    
+    PhysicsGhostObject(Node* node, const PhysicsCollisionShape::Definition& shape);
+
     /**
      * Destructor.
      */
-    ~PhysicsGhostObject();
+    virtual ~PhysicsGhostObject();
 
     /**
-     * Used to synchronize the transform between GamePlay and Bullet.
+     * Creates a ghost object from the specified properties object.
+     * 
+     * @param node The node to create a ghost object for; note that the node must have
+     *      a model attached to it prior to creating a ghost object for it.
+     * @param properties The properties object defining the ghost object (must have namespace equal to 'ghost').
+     * @return The newly created ghost object, or <code>NULL</code> if the ghost object failed to load.
      */
-    void transformChanged(Transform* transform, long cookie);
+    static PhysicsGhostObject* create(Node* node, Properties* properties);
 
-    Node* _node;
-    PhysicsMotionState* _motionState;
-    btCollisionShape* _shape;
-    btGhostObject* _ghostObject;
+    btPairCachingGhostObject* _ghostObject;
 };
 
 }
 
-#endif
+#endif

+ 2 - 1
gameplay/src/PhysicsMotionState.cpp

@@ -1,5 +1,6 @@
 #include "Base.h"
 #include "PhysicsMotionState.h"
+#include "Node.h"
 
 namespace gameplay
 {
@@ -22,7 +23,7 @@ PhysicsMotionState::~PhysicsMotionState()
 
 void PhysicsMotionState::getWorldTransform(btTransform &transform) const
 {
-    if (_node->getRigidBody() && _node->getRigidBody()->isKinematic())
+	if (_node->getCollisionObject() && _node->getCollisionObject()->isKinematic())
         updateTransformFromNode();
 
     transform = _centerOfMassOffset.inverse() * _worldTransform;

+ 7 - 5
gameplay/src/PhysicsMotionState.h

@@ -1,12 +1,13 @@
 #ifndef PHYSICSMOTIONSTATE_H_
 #define PHYSICSMOTIONSTATE_H_
 
-#include "Node.h"
-#include "PhysicsRigidBody.h"
+#include "Vector3.h"
 
 namespace gameplay
 {
 
+class Node;
+
 /**
  * Interface between GamePlay and Bullet to keep object transforms synchronized properly.
  * 
@@ -14,10 +15,11 @@ namespace gameplay
  */
 class PhysicsMotionState : public btMotionState
 {
-    friend class PhysicsRigidBody;
-    friend class PhysicsCharacter;
+	friend class PhysicsCollisionObject;
+	friend class PhysicsRigidBody;
+	friend class PhysicsGhostObject;
+	friend class PhysicsCharacter;
     friend class PhysicsConstraint;
-    friend class PhysicsGhostObject;
 
 protected:
 

+ 112 - 414
gameplay/src/PhysicsRigidBody.cpp

@@ -1,217 +1,81 @@
 #include "Base.h"
+#include "PhysicsRigidBody.h"
+#include "PhysicsMotionState.h"
+#include "PhysicsController.h"
 #include "Game.h"
 #include "Image.h"
-#include "PhysicsController.h"
-#include "PhysicsMotionState.h"
-#include "PhysicsRigidBody.h"
+#include "MeshPart.h"
+#include "Node.h"
 
 namespace gameplay
 {
 
-// Internal values used for creating mesh, heightfield, and capsule rigid bodies.
-#define SHAPE_MESH ((PhysicsRigidBody::ShapeType)(PhysicsRigidBody::SHAPE_NONE + 1))
-#define SHAPE_HEIGHTFIELD ((PhysicsRigidBody::ShapeType)(PhysicsRigidBody::SHAPE_NONE + 2))
-#define SHAPE_CAPSULE ((PhysicsRigidBody::ShapeType)(PhysicsRigidBody::SHAPE_NONE + 3))
-
-// Helper function for calculating heights from heightmap (image) or heightfield data.
-static float calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y);
-
-PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::ShapeType type, float mass, 
-    float friction, float restitution, float linearDamping, float angularDamping)
-        : _shape(NULL), _body(NULL), _node(node), _angularVelocity(NULL),
-        _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
-        _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
+PhysicsRigidBody::PhysicsRigidBody(Node* node, const PhysicsCollisionShape::Definition& shape, const Parameters& parameters)
+        : PhysicsCollisionObject(node), _body(NULL), _mass(parameters.mass), _constraints(NULL)
 {
-    // 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 our collision shape
+    Vector3 centerOfMassOffset;
+    _collisionShape = Game::getInstance()->getPhysicsController()->createShape(node, shape, &centerOfMassOffset);
 
-    switch (type)
-    {
-        case SHAPE_BOX:
-        {
-            const BoundingBox& box = node->getModel()->getMesh()->getBoundingBox();
-            _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, scale);
-            break;
-        }
-        case SHAPE_MESH:
-        {
-            _shape = Game::getInstance()->getPhysicsController()->createMesh(this, scale);
-            break;
-        }
-    }
+    // Create motion state object
+    _motionState = new PhysicsMotionState(node, (centerOfMassOffset.lengthSquared() > MATH_EPSILON) ? &centerOfMassOffset : NULL);
 
-    // 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();
+    // If the mass is non-zero, then the object is dynamic so we calculate the local 
+    // inertia. However, if the collision shape is a triangle mesh, we don't calculate 
+    // inertia since Bullet doesn't currently support this.
+    btVector3 localInertia(0.0, 0.0, 0.0);
+	if (parameters.mass != 0.0 && _collisionShape->getType() != PhysicsCollisionShape::SHAPE_MESH)
+		_collisionShape->getShape()->calculateLocalInertia(parameters.mass, localInertia);
 
-    // 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);
+    // Create the Bullet physics rigid body object.
+    btRigidBody::btRigidBodyConstructionInfo rbInfo(parameters.mass, _motionState, _collisionShape->getShape(), localInertia);
+    rbInfo.m_friction = parameters.friction;
+    rbInfo.m_restitution = parameters.restitution;
+    rbInfo.m_linearDamping = parameters.linearDamping;
+    rbInfo.m_angularDamping = parameters.angularDamping;
 
-    // Add the rigid body to the physics world.
-    Game::getInstance()->getPhysicsController()->addCollisionObject(this);
-}
+    // Create + assign the new bullet rigid body object.
+    _body = bullet_new<btRigidBody>(rbInfo);
 
-PhysicsRigidBody::PhysicsRigidBody(Node* node, Image* image, float mass,
-    float friction, float restitution, float linearDamping, float angularDamping)
-        : _shape(NULL), _body(NULL), _node(node), _angularVelocity(NULL),
-        _anisotropicFriction(NULL), _gravity(NULL), _linearVelocity(NULL), _vertexData(NULL),
-        _indexData(NULL), _heightfieldData(NULL), _inverse(NULL), _inverseIsDirty(true)
-{
-    // Get the width, length and minimum and maximum height of the heightfield.
-    const BoundingBox& box = node->getModel()->getMesh()->getBoundingBox();
-    float width = box.max.x - box.min.x;
-    float minHeight = box.min.y;
-    float maxHeight = box.max.y;
-    float length = box.max.z - box.min.z;
-
-    // Get the size in bytes of a pixel (we ensure that the image's
-    // pixel format is actually supported before calling this constructor).
-    unsigned int pixelSize = 0;
-    switch (image->getFormat())
-    {
-        case Image::RGB:
-            pixelSize = 3;
-            break;
-        case Image::RGBA:
-            pixelSize = 4;
-            break;
-    }
+    // Set other initially defined properties.
+    setKinematic(parameters.kinematic);
+    setAnisotropicFriction(parameters.anisotropicFriction);
+    setGravity(parameters.gravity);
 
-    // Calculate the heights for each pixel.
-    float* data = new float[image->getWidth() * image->getHeight()];
-    for (unsigned int x = 0; x < image->getWidth(); x++)
-    {
-        for (unsigned int y = 0; y < image->getHeight(); y++)
-        {
-            data[x + y * image->getWidth()] = ((((float)image->getData()[(x + y * image->getHeight()) * pixelSize + 0]) +
-                ((float)image->getData()[(x + y * image->getHeight()) * pixelSize + 1]) +
-                ((float)image->getData()[(x + y * image->getHeight()) * pixelSize + 2])) / 768.0f) * (maxHeight - minHeight) + minHeight;
-        }
-    }
+    // Add ourself to the physics world.
+    Game::getInstance()->getPhysicsController()->addCollisionObject(this);
 
-    // Generate the heightmap data needed for physics (one height per world unit).
-    unsigned int sizeWidth = width;
-    unsigned int sizeHeight = length;
-    _width = sizeWidth + 1;
-    _height = sizeHeight + 1;
-    _heightfieldData = new float[_width * _height];
-    unsigned int heightIndex = 0;
-    float widthImageFactor = (float)(image->getWidth() - 1) / sizeWidth;
-    float heightImageFactor = (float)(image->getHeight() - 1) / sizeHeight;
-    float x = 0.0f;
-    float z = 0.0f;
-    for (unsigned int row = 0, z = 0.0f; row <= sizeHeight; row++, z += 1.0f)
+    if (_collisionShape->getType() == PhysicsCollisionShape::SHAPE_HEIGHTFIELD)
     {
-        for (unsigned int col = 0, x = 0.0f; col <= sizeWidth; col++, x += 1.0f)
-        {
-            heightIndex = row * _width + col;
-            _heightfieldData[heightIndex] = calculateHeight(data, image->getWidth(), image->getHeight(), x * widthImageFactor, (sizeHeight - z) * heightImageFactor);
-        }
+        // Add a listener on the node's transform so we can track dirty changes to calculate
+        // an inverse matrix for transforming heightfield points between world and local space.
+        _node->addListener(this);
     }
-    SAFE_DELETE_ARRAY(data);
-
-    // Create the heightfield collision shape.
-    _shape = Game::getInstance()->getPhysicsController()->createHeightfield(_width, _height, _heightfieldData, minHeight, maxHeight);
-
-    // Offset the heightmap's center of mass according to the way that Bullet calculates the origin 
-    // of its heightfield collision shape; see documentation for the btHeightfieldTerrainShape for more info.
-    Vector3 s;
-    node->getWorldMatrix().getScale(&s);
-    Vector3 c (0.0f, -(maxHeight - (0.5f * (maxHeight - minHeight))) / s.y, 0.0f);
-
-    // Create the Bullet rigid body.
-    if (c.lengthSquared() > MATH_EPSILON)
-        _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping, &c);
-    else
-        _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
-
-    // Add the rigid body to the physics world.
-    Game::getInstance()->getPhysicsController()->addCollisionObject(this);
-
-    // Add the rigid body as a listener on the node's transform.
-    _node->addListener(this);
-}
-
-PhysicsRigidBody::PhysicsRigidBody(Node* node, float radius, float height, float mass, float friction,
-    float restitution, float linearDamping, float angularDamping)
-        : _shape(NULL), _body(NULL), _node(node), _angularVelocity(NULL),
-        _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.
-    if (c.lengthSquared() > MATH_EPSILON)
-        _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping, &c);
-    else
-        _body = createRigidBodyInternal(_shape, mass, node, friction, restitution, linearDamping, angularDamping);
-
-    // Add the rigid body to the physics world.
-    Game::getInstance()->getPhysicsController()->addCollisionObject(this);
 }
 
 PhysicsRigidBody::~PhysicsRigidBody()
 {
     // Clean up all constraints linked to this rigid body.
-    PhysicsConstraint* ptr = NULL;
-    while (_constraints.size() > 0)
+    if (_constraints)
     {
-        ptr = _constraints.back();
-        _constraints.pop_back();
-        SAFE_DELETE(ptr);
+        for (unsigned int i = 0; i < _constraints->size(); ++i)
+        {
+            SAFE_DELETE((*_constraints)[i]);
+        }
+        SAFE_DELETE(_constraints);
     }
 
+    // Remove collision object from physics controller
     Game::getInstance()->getPhysicsController()->removeCollisionObject(this);
 
     // Clean up the rigid body and its related objects.
-    if (_body)
-    {
-        if (_body->getMotionState())
-            delete _body->getMotionState();
-        SAFE_DELETE(_body);
-    }
+    SAFE_DELETE(_body);
 
-    SAFE_DELETE(_angularVelocity);
-    SAFE_DELETE(_anisotropicFriction);
-    SAFE_DELETE(_gravity);
-    SAFE_DELETE(_linearVelocity);
-    SAFE_DELETE_ARRAY(_vertexData);
-    for (unsigned int i = 0; i < _indexData.size(); i++)
+    // Unregister node listener (only registered for heihgtfield collision shape types)
+    if (_collisionShape->getType() == PhysicsCollisionShape::SHAPE_HEIGHTFIELD)
     {
-        SAFE_DELETE_ARRAY(_indexData[i]);
+        _node->removeListener(this);
     }
-    SAFE_DELETE_ARRAY(_heightfieldData);
-    SAFE_DELETE(_inverse);
-}
-
-Node* PhysicsRigidBody::getNode() const
-{
-    return _node;
 }
 
 PhysicsCollisionObject::Type PhysicsRigidBody::getType() const
@@ -224,11 +88,6 @@ btCollisionObject* PhysicsRigidBody::getCollisionObject() const
     return _body;
 }
 
-btCollisionShape* PhysicsRigidBody::getCollisionShape() const
-{
-    return _shape;
-}
-
 void PhysicsRigidBody::applyForce(const Vector3& force, const Vector3* relativePosition)
 {
     // If the force is significant enough, activate the rigid body 
@@ -282,25 +141,6 @@ void PhysicsRigidBody::applyTorqueImpulse(const Vector3& torque)
     }
 }
 
-PhysicsRigidBody* PhysicsRigidBody::create(Node* node, const char* filePath)
-{
-    assert(filePath);
-
-    // Load the rigid body properties from file.
-    Properties* properties = Properties::create(filePath);
-    assert(properties);
-    if (properties == NULL)
-    {
-        WARN_VARG("Failed to load rigid body file: %s", filePath);
-        return NULL;
-    }
-
-    PhysicsRigidBody* body = create(node, properties->getNextNamespace());
-    SAFE_DELETE(properties);
-
-    return body;
-}
-
 PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
@@ -311,285 +151,143 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
         return NULL;
     }
 
-    // Set values to their defaults.
-    PhysicsRigidBody::ShapeType type = PhysicsRigidBody::SHAPE_NONE;
-    float mass = 0.0;
-    float friction = 0.5;
-    float restitution = 0.0;
-    float linearDamping = 0.0;
-    float angularDamping = 0.0;
-    bool kinematic = false;
-    Vector3* gravity = NULL;
-    Vector3* anisotropicFriction = NULL;
-    const char* imagePath = NULL;
-    float radius = -1.0f;
-    float height = -1.0f;
-
-    // Load the defined properties.
+    // Load the physics collision shape definition.
+    PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
+    if (shape == NULL)
+    {
+        WARN("Failed to create collision shape during rigid body creation.");
+        return NULL;
+    }
+
+    // Set the rigid body parameters to their defaults.
+    Parameters parameters;
+
+    // Load the defined rigid body parameters.
     properties->rewind();
     const char* name;
-    while (name = properties->getNextProperty())
+    while ((name = properties->getNextProperty()) != NULL)
     {
-        if (strcmp(name, "type") == 0)
+        if (strcmp(name, "mass") == 0)
         {
-            std::string typeStr = properties->getString();
-            if (typeStr == "BOX")
-                type = SHAPE_BOX;
-            else if (typeStr == "SPHERE")
-                type = SHAPE_SPHERE;
-            else if (typeStr == "MESH")
-                type = SHAPE_MESH;
-            else if (typeStr == "HEIGHTFIELD")
-                type = SHAPE_HEIGHTFIELD;
-            else if (typeStr == "CAPSULE")
-                type = SHAPE_CAPSULE;
-            else
-            {
-                WARN_VARG("Could not create rigid body; unsupported value for rigid body type: '%s'.", typeStr.c_str());
-                return NULL;
-            }
-        }
-        else if (strcmp(name, "mass") == 0)
-        {
-            mass = properties->getFloat();
+            parameters.mass = properties->getFloat();
         }
         else if (strcmp(name, "friction") == 0)
         {
-            friction = properties->getFloat();
+            parameters.friction = properties->getFloat();
         }
         else if (strcmp(name, "restitution") == 0)
         {
-            restitution = properties->getFloat();
+            parameters.restitution = properties->getFloat();
         }
         else if (strcmp(name, "linearDamping") == 0)
         {
-            linearDamping = properties->getFloat();
+            parameters.linearDamping = properties->getFloat();
         }
         else if (strcmp(name, "angularDamping") == 0)
         {
-            angularDamping = properties->getFloat();
+            parameters.angularDamping = properties->getFloat();
         }
         else if (strcmp(name, "kinematic") == 0)
         {
-            kinematic = properties->getBool();
-        }
-        else if (strcmp(name, "gravity") == 0)
-        {
-            gravity = new Vector3();
-            properties->getVector3(NULL, gravity);
+            parameters.kinematic = properties->getBool();
         }
         else if (strcmp(name, "anisotropicFriction") == 0)
         {
-            anisotropicFriction = new Vector3();
-            properties->getVector3(NULL, anisotropicFriction);
-        }
-        else if (strcmp(name, "image") == 0)
-        {
-            imagePath = properties->getString();
+            properties->getVector3(NULL, &parameters.anisotropicFriction);
         }
-        else if (strcmp(name, "radius") == 0)
-        {
-            radius = properties->getFloat();
-        }
-        else if (strcmp(name, "height") == 0)
+        else if (strcmp(name, "gravity") == 0)
         {
-            height = properties->getFloat();
+            properties->getVector3(NULL, &parameters.gravity);
         }
     }
 
-    // If the rigid body type is equal to mesh, check that the node's mesh's primitive type is supported.
-    if (type == SHAPE_MESH)
-    {
-        Mesh* mesh = node->getModel()->getMesh();
+    // Create the rigid body.
+    PhysicsRigidBody* body = new PhysicsRigidBody(node, *shape, parameters);
+    SAFE_DELETE(shape);
 
-        switch (mesh->getPrimitiveType())
-        {
-        case Mesh::TRIANGLES:
-            break;
-        case Mesh::LINES:
-        case Mesh::LINE_STRIP:
-        case Mesh::POINTS:
-        case Mesh::TRIANGLE_STRIP:
-            WARN("Mesh rigid bodies are currently only supported on meshes with primitive type equal to TRIANGLES.");
-
-            SAFE_DELETE(gravity);
-            SAFE_DELETE(anisotropicFriction);
-            return NULL;
-        }
-    }
+    return body;
+}
 
-    // Create the rigid body.
-    PhysicsRigidBody* body = NULL;
-    switch (type)
+void PhysicsRigidBody::setKinematic(bool kinematic)
+{
+    if (kinematic)
     {
-        case SHAPE_HEIGHTFIELD:
-            if (imagePath == NULL)
-            {
-                WARN("Heightfield rigid body requires an image path.");
-            }
-            else
-            {
-                // Load the image data from the given file path.
-                Image* image = Image::create(imagePath);
-                if (!image)
-                    return NULL;
-
-                // Ensure that the image's pixel format is supported.
-                switch (image->getFormat())
-                {
-                    case Image::RGB:
-                    case Image::RGBA:
-                        break;
-                    default:
-                        WARN_VARG("Heightmap: pixel format is not supported: %d", image->getFormat());
-                        return NULL;
-                }
-
-                body = new PhysicsRigidBody(node, image, mass, friction, restitution, linearDamping, angularDamping);
-                SAFE_RELEASE(image);
-            }
-            break;
-        case SHAPE_CAPSULE:
-            if (radius == -1.0f || height == -1.0f)
-            {
-                WARN("Both 'radius' and 'height' must be specified for a capsule rigid body.");
-            }
-            else
-            {
-                body = new PhysicsRigidBody(node, radius, height, mass, friction, restitution, linearDamping, angularDamping);
-            }
-            break;
-        default:
-            body = new PhysicsRigidBody(node, type, mass, friction, restitution, linearDamping, angularDamping);
-            break;
+        _body->setCollisionFlags(_body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
+        _body->setActivationState(DISABLE_DEACTIVATION);
+    }
+    else
+    {
+        _body->setCollisionFlags(_body->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT);
+        _body->setActivationState(ACTIVE_TAG);
     }
-
-    // Set any initially defined properties.
-    if (kinematic)
-        body->setKinematic(kinematic);
-    if (gravity)
-        body->setGravity(*gravity);
-    if (anisotropicFriction)
-        body->setAnisotropicFriction(*anisotropicFriction);
-
-    // Clean up any loaded properties that are on the heap.
-    SAFE_DELETE(gravity);
-    SAFE_DELETE(anisotropicFriction);
-
-    return body;
 }
 
 float PhysicsRigidBody::getHeight(float x, float y) const
 {
     // This function is only supported for heightfield rigid bodies.
-    if (_shape->getShapeType() != TERRAIN_SHAPE_PROXYTYPE)
+    if (_collisionShape->getType() != PhysicsCollisionShape::SHAPE_HEIGHTFIELD)
     {
         WARN("Attempting to get the height of a non-heightfield rigid body.");
         return 0.0f;
     }
 
     // Calculate the correct x, y position relative to the heightfield data.
-    if (_inverseIsDirty)
+    if (_collisionShape->_shapeData.heightfieldData->inverseIsDirty)
     {
-        if (_inverse == NULL)
-            _inverse = new Matrix();
-
-        _node->getWorldMatrix().invert(_inverse);
-        _inverseIsDirty = false;
+        _node->getWorldMatrix().invert(&_collisionShape->_shapeData.heightfieldData->inverse);
+        _collisionShape->_shapeData.heightfieldData->inverseIsDirty = false;
     }
 
-    Vector3 v = (*_inverse) * Vector3(x, 0.0f, y);
-    x = (v.x + (0.5f * (_width - 1))) * _width / (_width - 1);
-    y = (v.z + (0.5f * (_height - 1))) * _height / (_height - 1);
+    float w = _collisionShape->_shapeData.heightfieldData->width;
+    float h = _collisionShape->_shapeData.heightfieldData->height;
+
+    Vector3 v = _collisionShape->_shapeData.heightfieldData->inverse * Vector3(x, 0.0f, y);
+    x = (v.x + (0.5f * (w - 1))) * w / (w - 1);
+    y = (v.z + (0.5f * (h - 1))) * h / (h - 1);
 
     // Check that the x, y position is within the bounds.
-    if (x < 0.0f || x > _width || y < 0.0f || y > _height)
+    if (x < 0.0f || x > w || y < 0.0f || y > h)
     {
-        WARN_VARG("Attempting to get height at point '%f, %f', which is outside the range of the heightfield with width %d and height %d.", x, y, _width, _height);
+        WARN_VARG("Attempting to get height at point '%f, %f', which is outside the range of the heightfield with width %d and height %d.", x, y, w, h);
         return 0.0f;
     }
 
-    return calculateHeight(_heightfieldData, _width, _height, x, y);
-}
-
-btRigidBody* PhysicsRigidBody::createRigidBodyInternal(btCollisionShape* shape, float mass, Node* node,
-                                                       float friction, float restitution, float linearDamping, float angularDamping, 
-                                                       const Vector3* centerOfMassOffset)
-{
-    // If the mass is non-zero, then the object is dynamic so we calculate the local 
-    // inertia. However, if the collision shape is a triangle mesh, we don't calculate 
-    // inertia since Bullet doesn't currently support this.
-    btVector3 localInertia(0.0, 0.0, 0.0);
-    if (mass != 0.0 && shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE)
-        shape->calculateLocalInertia(mass, localInertia);
-
-    // Create the Bullet physics rigid body object.
-    PhysicsMotionState* motionState = new PhysicsMotionState(node, centerOfMassOffset);
-    btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, motionState, shape, localInertia);
-    rbInfo.m_friction = friction;
-    rbInfo.m_restitution = restitution;
-    rbInfo.m_linearDamping = linearDamping;
-    rbInfo.m_angularDamping = angularDamping;
-    btRigidBody* body = bullet_new<btRigidBody>(rbInfo);
-
-    return body;
+    return PhysicsController::calculateHeight(_collisionShape->_shapeData.heightfieldData->heightData, w, h, x, y);
 }
 
 void PhysicsRigidBody::addConstraint(PhysicsConstraint* constraint)
 {
-    _constraints.push_back(constraint);
+    if (_constraints == NULL)
+        _constraints = new std::vector<PhysicsConstraint*>();
+
+    _constraints->push_back(constraint);
 }
 
 void PhysicsRigidBody::removeConstraint(PhysicsConstraint* constraint)
 {
-    for (unsigned int i = 0; i < _constraints.size(); i++)
+    if (_constraints)
     {
-        if (_constraints[i] == constraint)
+        for (unsigned int i = 0; i < _constraints->size(); ++i)
         {
-            _constraints.erase(_constraints.begin() + i);
-            break;
+            if ((*_constraints)[i] == constraint)
+            {
+                _constraints->erase(_constraints->begin() + i);
+                break;
+            }
         }
     }
 }
 
 bool PhysicsRigidBody::supportsConstraints()
 {
-    return _shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE && _shape->getShapeType() != TERRAIN_SHAPE_PROXYTYPE;
+    return (getShapeType() != PhysicsCollisionShape::SHAPE_HEIGHTFIELD && getShapeType() != PhysicsCollisionShape::SHAPE_MESH);
 }
 
 void PhysicsRigidBody::transformChanged(Transform* transform, long cookie)
 {
-    _inverseIsDirty = true;
-}
-
-float calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y)
-{
-    unsigned int x1 = x;
-    unsigned int y1 = y;
-    unsigned int x2 = x1 + 1;
-    unsigned int y2 = y1 + 1;
-    float tmp;
-    float xFactor = modf(x, &tmp);
-    float yFactor = modf(y, &tmp);
-    float xFactorI = 1.0f - xFactor;
-    float yFactorI = 1.0f - yFactor;
-
-    if (x2 >= width && y2 >= height)
-    {
-        return data[x1 + y1 * width];
-    }
-    else if (x2 >= width)
-    {
-        return data[x1 + y1 * width] * yFactorI + data[x1 + y2 * width] * yFactor;
-    }
-    else if (y2 >= height)
-    {
-        return data[x1 + y1 * width] * xFactorI + data[x2 + y1 * width] * xFactor;
-    }
-    else
+    if (getShapeType() == PhysicsCollisionShape::SHAPE_HEIGHTFIELD)
     {
-        return data[x1 + y1 * width] * xFactorI * yFactorI + data[x1 + y2 * width] * xFactorI * yFactor + 
-            data[x2 + y2 * width] * xFactor * yFactor + data[x2 + y1 * width] * xFactor * yFactorI;
+        _collisionShape->_shapeData.heightfieldData->inverseIsDirty = true;
     }
 }
 

+ 153 - 193
gameplay/src/PhysicsRigidBody.h

@@ -30,204 +30,235 @@ class PhysicsRigidBody : public PhysicsCollisionObject, public Transform::Listen
 
 public:
 
-    /**
-     * Represents the different types of rigid bodies.
-     */
-    enum ShapeType
-    {
-        SHAPE_BOX,
-        SHAPE_SPHERE,
-        SHAPE_NONE,
-        SHAPE_MAX = 10
-    };
+	/**
+	 * Rigid body construction parameters.
+	 */
+	struct Parameters
+	{
+		/**
+		 * The mass of the rigid body, in kilograms.
+		 */
+		float mass;
+
+		/**
+		 * The friction of the rigid body (non-zero values give best simulation results).
+		 */
+		float friction;
+
+		/**
+		 * The restitution of the rigid body (this controls the bounciness of
+		 * the rigid body; use zero for best simulation results).
+		 */
+        float restitution;
+
+		/**
+		 * The percentage of linear velocity lost per second (between 0.0 and 1.0).
+		 */
+		float linearDamping;
+
+		/**
+		 * The percentage of angular velocity lost per second (between 0.0 and 1.0).
+		 */
+		float angularDamping;
+
+		/**
+		 * Whether the rigid body is kinematic.
+		 */
+		bool kinematic;
+
+		/**
+		 * The ansitropic friction term for the rigid body.
+		 */
+		Vector3 anisotropicFriction;
+
+		/**
+		 * The gravity acceleration factor for the rigid body.
+		 */
+		Vector3 gravity;
+
+		/**
+		 * Constructor.
+		 */
+		Parameters(float mass = 0.0f, float friction = 0.5f, float resititution = 0.0f,
+			float linearDamping = 0.0f, float angularDamping = 0.0f, bool kinematic = false,
+			const Vector3& anisotropicFriction = Vector3::one(), const Vector3& gravity = Vector3::zero())
+			: mass(mass), friction(friction), restitution(restitution), linearDamping(linearDamping), angularDamping(angularDamping),
+			  kinematic(kinematic), anisotropicFriction(anisotropicFriction), gravity(gravity)
+		{
+		}
+	};
 
     /**
      * @see PhysicsCollisionObject#getType
      */
     PhysicsCollisionObject::Type getType() const;
 
-    /**
-     * Applies the given force to the rigid body (optionally, from the given relative position).
-     * 
-     * @param force The force to be applied.
-     * @param relativePosition The relative position from which to apply the force.
-     */
-    void applyForce(const Vector3& force, const Vector3* relativePosition = NULL);
+	/**
+	 * Gets the rigid body's mass.
+	 *
+	 * @return The mass.
+	 */
+	inline float getMass() const;
 
     /**
-     * Applies the given force impulse to the rigid body (optionally, from the given relative position).
+     * Gets the rigid body's friction.
      * 
-     * @param impulse The force impulse to be applied.
-     * @param relativePosition The relative position from which to apply the force.
+     * @return The friction.
      */
-    void applyImpulse(const Vector3& impulse, const Vector3* relativePosition = NULL);
+    inline float getFriction() const;
 
     /**
-     * Applies the given torque to the rigid body.
+     * Sets the rigid body's friction.
      * 
-     * @param torque The torque to be applied.
+     * @param friction The friction.
      */
-    void applyTorque(const Vector3& torque);
+    inline void setFriction(float friction);
 
-    /**
-     * Applies the given torque impulse to the rigid body.
-     * 
-     * @param torque The torque impulse to be applied.
-     */
-    void applyTorqueImpulse(const Vector3& torque);
+	/**
+	 * Gets the rigid body's restitution.
+	 *
+	 * @return The restitution.
+	 */
+	inline float getRestitution() const;
 
     /**
-     * Gets the rigid body's angular damping.
+     * Sets the rigid body's restitution (or bounciness).
      * 
-     * @return The angular damping.
+     * @param restitution The restitution.
      */
-    inline float getAngularDamping() const;
+    inline void setRestitution(float restitution);
 
-    /**
-     * Gets the rigid body's angular velocity.
-     * 
-     * @return The angular velocity.
-     */
-    inline const Vector3& getAngularVelocity() const;
+	/**
+	 * Gets the rigid body's linear damping.
+	 *
+	 * @return The linear damping.
+	 */
+	inline float getLinearDamping() const;
 
-    /**
-     * Gets the rigid body's anisotropic friction.
-     * 
-     * @return The anisotropic friction.
-     */
-    inline const Vector3& getAnisotropicFriction() const;
+	/**
+	 * Gets the rigid body's angular damping.
+	 *
+	 * @return The angular damping.
+	 */
+	inline float getAngularDamping() const;
 
     /**
-     * Gets the rigid body's friction.
+     * Sets the rigid body's linear and angular damping.
      * 
-     * @return The friction.
+     * @param linearDamping The linear damping; between 0.0 (minimum) and 1.0 (maximum).
+     * @param angularDamping The angular damping; between 0.0 (minimum) and 1.0 (maximum).
      */
-    inline float getFriction() const;
+    inline void setDamping(float linearDamping, float angularDamping);
 
-    /**
-     * Gets the gravity that affects the rigid body (this can
-     * be different from the global gravity; @see #setGravity).
+	/**
+     * Gets the rigid body's linear velocity.
      * 
-     * @return The gravity.
+     * @return The linear velocity.
      */
-    inline const Vector3& getGravity() const;
+    inline Vector3 getLinearVelocity() const;
 
     /**
-     * Gets the height at the given point (only for rigid bodies of type HEIGHTFIELD).
+     * Sets the rigid body's linear velocity.
      * 
-     * @param x The x position.
-     * @param y The y position.
-     * @return The height at the given point.
+     * @param velocity The linear velocity.
      */
-    float getHeight(float x, float y) const;
+    inline void setLinearVelocity(const Vector3& velocity);
 
     /**
-     * Gets the rigid body's linear damping.
+     * Gets the rigid body's angular velocity.
      * 
-     * @return The linear damping.
+     * @return The angular velocity.
      */
-    inline float getLinearDamping() const;
+    inline Vector3 getAngularVelocity() const;
 
     /**
-     * Gets the rigid body's linear velocity.
+     * Sets the rigid body's angular velocity.
      * 
-     * @return The linear velocity.
+     * @param velocity The angular velocity.
      */
-    inline const Vector3& getLinearVelocity() const;
+    inline void setAngularVelocity(const Vector3& velocity);
 
     /**
-     * Gets the node that the rigid body is attached to.
+     * Gets the rigid body's anisotropic friction.
      * 
-     * @return The node.
-     *
-     * @see PhysicsCollisionObject::getNode.
+     * @return The anisotropic friction.
      */
-    Node* getNode() const;
+    inline Vector3 getAnisotropicFriction() const;
 
     /**
-     * Gets the rigid body's restitution.
+     * Sets the rigid body's anisotropic friction.
      * 
-     * @return The restitution.
+     * @param friction The anisotropic friction.
      */
-    inline float getRestitution() const;
+    inline void setAnisotropicFriction(const Vector3& friction);
 
     /**
-     * Gets whether the rigid body is a kinematic rigid body or not.
+     * Gets the gravity that affects the rigid body (this can
+     * be different from the global gravity; @see #setGravity).
      * 
-     * @return Whether the rigid body is kinematic or not.
-     */
-    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.
+     * @return The gravity.
      */
-    inline bool isDynamic() const;
+    inline Vector3 getGravity() const;
 
     /**
-     * Sets the rigid body's angular velocity.
+     * Sets the rigid body's gravity (this overrides the global gravity for this rigid body).
      * 
-     * @param velocity The angular velocity.
+     * @param gravity The gravity.
      */
-    inline void setAngularVelocity(const Vector3& velocity);
+    inline void setGravity(const Vector3& gravity);
 
     /**
-     * Sets the rigid body's anisotropic friction.
+     * Sets whether the rigid body is a kinematic rigid body or not.
      * 
-     * @param friction The anisotropic friction.
+     * @param kinematic Whether the rigid body is kinematic or not.
      */
-    inline void setAnisotropicFriction(const Vector3& friction);
+    void setKinematic(bool kinematic);
 
     /**
-     * Sets the rigid body's linear and angular damping.
+     * Gets the height at the given point (only for rigid bodies of type HEIGHTFIELD).
      * 
-     * @param linearDamping The linear damping; between 0.0 (minimum) and 1.0 (maximum).
-     * @param angularDamping The angular damping; between 0.0 (minimum) and 1.0 (maximum).
+     * @param x The x position.
+     * @param y The y position.
+     * @return The height at the given point, or zero if this is not a heightfield rigid body.
      */
-    inline void setDamping(float linearDamping, float angularDamping);
+    float getHeight(float x, float y) const;
 
     /**
-     * Sets the rigid body's friction.
-     * 
-     * @param friction The friction.
+     * Gets whether the rigid body is a static rigid body or not.
+     *
+     * @return Whether the rigid body is static.
      */
-    inline void setFriction(float friction);
+    bool isStatic() const;
 
     /**
-     * Sets the rigid body's gravity (this overrides the global gravity for this rigid body).
+     * Applies the given force to the rigid body (optionally, from the given relative position).
      * 
-     * @param gravity The gravity.
+     * @param force The force to be applied.
+     * @param relativePosition The relative position from which to apply the force.
      */
-    inline void setGravity(const Vector3& gravity);
+    void applyForce(const Vector3& force, const Vector3* relativePosition = NULL);
 
     /**
-     * Sets whether the rigid body is a kinematic rigid body or not.
+     * Applies the given force impulse to the rigid body (optionally, from the given relative position).
      * 
-     * @param kinematic Whether the rigid body is kinematic or not.
+     * @param impulse The force impulse to be applied.
+     * @param relativePosition The relative position from which to apply the force.
      */
-    inline void setKinematic(bool kinematic);
+    void applyImpulse(const Vector3& impulse, const Vector3* relativePosition = NULL);
 
     /**
-     * Sets the rigid body's linear velocity.
+     * Applies the given torque to the rigid body.
      * 
-     * @param velocity The linear velocity.
+     * @param torque The torque to be applied.
      */
-    inline void setLinearVelocity(const Vector3& velocity);
+    void applyTorque(const Vector3& torque);
 
     /**
-     * Sets the rigid body's restitution (or bounciness).
+     * Applies the given torque impulse to the rigid body.
      * 
-     * @param restitution The restitution.
+     * @param torque The torque impulse to be applied.
      */
-    inline void setRestitution(float restitution);
+    void applyTorqueImpulse(const Vector3& torque);
 
 protected:
 
@@ -236,11 +267,6 @@ protected:
      */
     btCollisionObject* getCollisionObject() const;
 
-    /**
-     * @see PhysicsCollisionObject::getCollisionShape
-     */
-    btCollisionShape* getCollisionShape() const;
-
 private:
 
     /**
@@ -248,49 +274,10 @@ private:
      * 
      * @param node The node to create a rigid body for; note that the node must have
      *      a model attached to it prior to creating a rigid body for it.
-     * @param type The type of rigid body to set.
-     * @param mass The mass of the rigid body, in kilograms.
-     * @param friction The friction of the rigid body (non-zero values give best simulation results).
-     * @param restitution The restitution of the rigid body (this controls the bounciness of
-     *      the rigid body; use zero for best simulation results).
-     * @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).
-     */
-    PhysicsRigidBody(Node* node, PhysicsRigidBody::ShapeType type, float mass, float friction = 0.5,
-        float restitution = 0.0, float linearDamping = 0.0, float angularDamping = 0.0);
-
-    /**
-     * Creates a heightfield rigid body.
-     * 
-     * @param node The node to create the heightfield rigid body for; note that the node must have
-     *      a model attached to it prior to creating a rigid body for it.
-     * @param image The heightfield image.
-     * @param mass The mass of the rigid body, in kilograms.
-     * @param friction The friction of the rigid body (non-zero values give best simulation results).
-     * @param restitution The restitution of the rigid body (this controls the bounciness of
-     *      the rigid body; use zero for best simulation results).
-     * @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).
-     */
-    PhysicsRigidBody(Node* node, Image* image, float mass, float friction = 0.5,
-        float restitution = 0.0, float linearDamping = 0.0, float angularDamping = 0.0);
-
-    /**
-     * Creates a capsule rigid body.
-     * 
-     * @param node The node to create the heightfield rigid body for; note that the node must have
-     *      a model attached to it prior to creating a rigid body for it.
-     * @param radius The radius of the capsule.
-     * @param height The height of the cylindrical part of the capsule (not including the ends).
-     * @param mass The mass of the rigid body, in kilograms.
-     * @param friction The friction of the rigid body (non-zero values give best simulation results).
-     * @param restitution The restitution of the rigid body (this controls the bounciness of
-     *      the rigid body; use zero for best simulation results).
-     * @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).
+     * @param shape The rigid body shape construction information.
+     * @param parameters The rigid body construction parameters.
      */
-    PhysicsRigidBody(Node* node, float radius, float height, float mass, float friction = 0.5,
-        float restitution = 0.0, float linearDamping = 0.0, float angularDamping = 0.0);
+	PhysicsRigidBody(Node* node, const PhysicsCollisionShape::Definition& shape, const Parameters& parameters);
 
     /**
      * Destructor.
@@ -302,16 +289,6 @@ private:
      */
     PhysicsRigidBody(const PhysicsRigidBody& body);
 
-    /**
-     * Creates a rigid body from the rigid body file at the given path.
-     * 
-     * @param node The node to create a rigid body for; note that the node must have
-     *      a model attached to it prior to creating a rigid body for it.
-     * @param filePath The path to the rigid body file.
-     * @return The rigid body or <code>NULL</code> if the rigid body could not be loaded.
-     */
-    static PhysicsRigidBody* create(Node* node, const char* filePath);
-
     /**
      * Creates a rigid body from the specified properties object.
      * 
@@ -322,39 +299,22 @@ private:
      */
     static PhysicsRigidBody* create(Node* node, Properties* properties);
 
-    // Creates the underlying Bullet Physics rigid body object
-    // for a PhysicsRigidBody object using the given parameters.
-    static btRigidBody* createRigidBodyInternal(btCollisionShape* shape, float mass, Node* node,
-                                                float friction, float restitution, float linearDamping, float angularDamping,
-                                                const Vector3* centerOfMassOffset = NULL);
-
     // Adds a constraint to this rigid body.
     void addConstraint(PhysicsConstraint* constraint);
 
     // Removes a constraint from this rigid body (used by the constraint destructor).
     void removeConstraint(PhysicsConstraint* constraint);
-    
+
     // Whether or not the rigid body supports constraints fully.
     bool supportsConstraints();
 
     // Used for implementing getHeight() when the heightfield has a transform that can change.
     void transformChanged(Transform* transform, long cookie);
 
-    btCollisionShape* _shape;
     btRigidBody* _body;
-    Node* _node;
-    std::vector<PhysicsConstraint*> _constraints;
-    mutable Vector3* _angularVelocity;
-    mutable Vector3* _anisotropicFriction;
-    mutable Vector3* _gravity;
-    mutable Vector3* _linearVelocity;
-    float* _vertexData;
-    std::vector<unsigned char*> _indexData;
-    float* _heightfieldData;
-    unsigned int _width;
-    unsigned int _height;
-    mutable Matrix* _inverse;
-    mutable bool _inverseIsDirty;
+	float _mass;
+	std::vector<PhysicsConstraint*>* _constraints;
+
 };
 
 }

+ 32 - 67
gameplay/src/PhysicsRigidBody.inl

@@ -4,44 +4,29 @@
 namespace gameplay
 {
 
-inline float PhysicsRigidBody::getAngularDamping() const
+inline float PhysicsRigidBody::getMass() const
 {
-    return _body->getAngularDamping();
+	return _mass;
 }
 
-inline const Vector3& PhysicsRigidBody::getAngularVelocity() const
+inline float PhysicsRigidBody::getFriction() const
 {
-    if (!_angularVelocity)
-        _angularVelocity = new Vector3();
-
-    const btVector3& v = _body->getAngularVelocity();
-    _angularVelocity->set(v.x(), v.y(), v.z());
-    return *_angularVelocity;
+    return _body->getFriction();
 }
 
-inline const Vector3& PhysicsRigidBody::getAnisotropicFriction() const
+inline void PhysicsRigidBody::setFriction(float friction)
 {
-    if (!_anisotropicFriction)
-        _anisotropicFriction = new Vector3();
-
-    const btVector3& af = _body->getAnisotropicFriction();
-    _anisotropicFriction->set(af.x(), af.y(), af.z());
-    return *_anisotropicFriction;
+    _body->setFriction(friction);
 }
 
-inline float PhysicsRigidBody::getFriction() const
+inline float PhysicsRigidBody::getRestitution() const
 {
-    return _body->getFriction();
+    return _body->getRestitution();
 }
 
-inline const Vector3& PhysicsRigidBody::getGravity() const
+inline void PhysicsRigidBody::setRestitution(float restitution)
 {
-    if (!_gravity)
-        _gravity = new Vector3();
-
-    const btVector3& g = _body->getGravity();
-    _gravity->set(g.x(), g.y(), g.z());
-    return *_gravity;
+    _body->setRestitution(restitution);
 }
 
 inline float PhysicsRigidBody::getLinearDamping() const
@@ -49,34 +34,31 @@ inline float PhysicsRigidBody::getLinearDamping() const
     return _body->getLinearDamping();
 }
 
-inline const Vector3& PhysicsRigidBody::getLinearVelocity() const
+inline float PhysicsRigidBody::getAngularDamping() const
 {
-    if (!_linearVelocity)
-        _linearVelocity = new Vector3();
-
-    const btVector3& v = _body->getLinearVelocity();
-    _linearVelocity->set(v.x(), v.y(), v.z());
-    return *_linearVelocity;
+    return _body->getAngularDamping();
 }
 
-inline float PhysicsRigidBody::getRestitution() const
+inline void PhysicsRigidBody::setDamping(float linearDamping, float angularDamping)
 {
-    return _body->getRestitution();
+    _body->setDamping(linearDamping, angularDamping);
 }
 
-inline bool PhysicsRigidBody::isKinematic() const
+inline Vector3 PhysicsRigidBody::getLinearVelocity() const
 {
-    return _body->isKinematicObject();
+    const btVector3& v = _body->getLinearVelocity();
+	return Vector3(v.x(), v.y(), v.z());
 }
 
-inline bool PhysicsRigidBody::isStatic() const
+inline void PhysicsRigidBody::setLinearVelocity(const Vector3& velocity)
 {
-    return _body->isStaticObject();
+    _body->setLinearVelocity(BV(velocity));
 }
 
-inline bool PhysicsRigidBody::isDynamic() const
+inline Vector3 PhysicsRigidBody::getAngularVelocity() const
 {
-    return !_body->isStaticOrKinematicObject();
+    const btVector3& v = _body->getAngularVelocity();
+    return Vector3(v.x(), v.y(), v.z());
 }
 
 inline void PhysicsRigidBody::setAngularVelocity(const Vector3& velocity)
@@ -84,19 +66,21 @@ inline void PhysicsRigidBody::setAngularVelocity(const Vector3& velocity)
     _body->setAngularVelocity(BV(velocity));
 }
 
-inline void PhysicsRigidBody::setAnisotropicFriction(const Vector3& friction)
+inline Vector3 PhysicsRigidBody::getAnisotropicFriction() const
 {
-    _body->setAnisotropicFriction(BV(friction));
+    const btVector3& af = _body->getAnisotropicFriction();
+    return Vector3(af.x(), af.y(), af.z());
 }
 
-inline void PhysicsRigidBody::setDamping(float linearDamping, float angularDamping)
+inline void PhysicsRigidBody::setAnisotropicFriction(const Vector3& friction)
 {
-    _body->setDamping(linearDamping, angularDamping);
+    _body->setAnisotropicFriction(BV(friction));
 }
 
-inline void PhysicsRigidBody::setFriction(float friction)
+inline Vector3 PhysicsRigidBody::getGravity() const
 {
-    _body->setFriction(friction);
+    const btVector3& g = _body->getGravity();
+    return Vector3(g.x(), g.y(), g.z());
 }
 
 inline void PhysicsRigidBody::setGravity(const Vector3& gravity)
@@ -104,28 +88,9 @@ inline void PhysicsRigidBody::setGravity(const Vector3& gravity)
     _body->setGravity(BV(gravity));
 }
 
-inline void PhysicsRigidBody::setKinematic(bool kinematic)
-{
-    if (kinematic)
-    {
-        _body->setCollisionFlags(_body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
-        _body->setActivationState(DISABLE_DEACTIVATION);
-    }
-    else
-    {
-        _body->setCollisionFlags(_body->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT);
-        _body->setActivationState(ACTIVE_TAG);
-    }
-}
-
-inline void PhysicsRigidBody::setLinearVelocity(const Vector3& velocity)
-{
-    _body->setLinearVelocity(BV(velocity));
-}
-
-inline void PhysicsRigidBody::setRestitution(float restitution)
+inline bool PhysicsRigidBody::isStatic() const
 {
-    _body->setRestitution(restitution);
+    return _body->isStaticObject();
 }
 
 }

+ 7 - 0
gameplay/src/Platform.h

@@ -123,6 +123,13 @@ public:
 
     static void keyEventInternal(Keyboard::KeyEvent evt, int key);
 
+    /**
+     * Sleeps synchronously for the given amount of time (in milliseconds).
+     *
+     * @param ms How long to sleep (in milliseconds).
+     */
+    static void sleep(long ms);
+
 private:
 
     /**

+ 5 - 0
gameplay/src/PlatformAndroid.cpp

@@ -848,6 +848,11 @@ void Platform::displayKeyboard(bool display)
         __displayKeyboard = false;
 }
 
+void Platform::sleep(long ms)
+{
+    usleep(ms * 1000);
+}
+
 }
 
 #endif

+ 101 - 94
gameplay/src/PlatformMacOS.mm

@@ -4,6 +4,8 @@
 #include "Platform.h"
 #include "FileSystem.h"
 #include "Game.h"
+#include "Form.h"
+#include <unistd.h>
 
 #import <Cocoa/Cocoa.h>
 #import <QuartzCore/CVDisplayLink.h>
@@ -13,10 +15,10 @@
 using namespace std;
 using namespace gameplay;
 
-// Default to 720p
-#define WINDOW_WIDTH    1280
-#define WINDOW_HEIGHT   720
-
+// Default to 720p
+#define WINDOW_WIDTH    1280
+#define WINDOW_HEIGHT   720
+
 static const float ACCELEROMETER_X_FACTOR = 90.0f / WINDOW_WIDTH;
 static const float ACCELEROMETER_Y_FACTOR = 90.0f / WINDOW_HEIGHT;
 
@@ -30,7 +32,7 @@ static int __ly;
 static bool __hasMouse = false;
 static bool __leftMouseDown = false;
 static bool __rightMouseDown = false;
-static bool __otherMouseDown = false;
+static bool __otherMouseDown = false;
 static bool __shiftDown = false;
 
 long getMachTimeInMilliseconds()
@@ -107,7 +109,7 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
 
 - (id) initWithFrame: (NSRect) frame
 {    
-
+
     NSOpenGLPixelFormatAttribute attrs[] = 
     {
         NSOpenGLPFAAccelerated,
@@ -123,12 +125,12 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     if (!pf)
         NSLog(@"OpenGL pixel format not supported.");
     
-    if((self = [super initWithFrame:frame pixelFormat:[pf autorelease]])) 
-    {
-        lock = [[NSRecursiveLock alloc] init];
-        _game = Game::getInstance();
-        __timeStart = getMachTimeInMilliseconds();
-    }
+    if((self = [super initWithFrame:frame pixelFormat:[pf autorelease]])) 
+    {
+        lock = [[NSRecursiveLock alloc] init];
+        _game = Game::getInstance();
+        __timeStart = getMachTimeInMilliseconds();
+    }
     
     return self;
 }
@@ -181,16 +183,16 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     [super dealloc];
 }
 
-
-- (void) mouse: (Mouse::MouseEvent) mouseEvent orTouchEvent: (Touch::TouchEvent) touchEvent atX: (int) x y: (int) y s: (int) s 
-{
-    if (!Game::getInstance()->mouseEvent(mouseEvent, x, y, s))
-    {
-        Game::getInstance()->touchEvent(touchEvent, x, y, 0);
-    }
-        
-}
-
+
+- (void) mouse: (Mouse::MouseEvent) mouseEvent orTouchEvent: (Touch::TouchEvent) touchEvent atX: (int) x y: (int) y s: (int) s 
+{
+    if (!Game::getInstance()->mouseEvent(mouseEvent, x, y, s))
+    {
+        Game::getInstance()->touchEvent(touchEvent, x, y, 0);
+    }
+        
+}
+
 - (void) mouseDown: (NSEvent*) event
 {
     NSPoint point = [event locationInWindow];
@@ -218,12 +220,12 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     [self mouse: Mouse::MOUSE_RELEASE_LEFT_BUTTON orTouchEvent: Touch::TOUCH_RELEASE atX: point.x y: WINDOW_HEIGHT - point.y s: 0];
 }
 
-- (void)mouseMoved:(NSEvent *) event 
-{
-    NSPoint point = [event locationInWindow];
-    Game::getInstance()->mouseEvent(Mouse::MOUSE_MOVE, point.x, WINDOW_HEIGHT - point.y, 0);
-}
-
+- (void)mouseMoved:(NSEvent *) event 
+{
+    NSPoint point = [event locationInWindow];
+    Game::getInstance()->mouseEvent(Mouse::MOUSE_MOVE, point.x, WINDOW_HEIGHT - point.y, 0);
+}
+
 - (void) mouseDragged: (NSEvent*) event
 {
     NSPoint point = [event locationInWindow];
@@ -238,15 +240,15 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     __rightMouseDown = true;
      NSPoint point = [event locationInWindow];
     __lx = point.x;
-    __ly = WINDOW_HEIGHT - point.y;    
-    _game->mouseEvent(Mouse::MOUSE_PRESS_RIGHT_BUTTON, point.x, WINDOW_HEIGHT - point.y, 0);
+    __ly = WINDOW_HEIGHT - point.y;    
+    _game->mouseEvent(Mouse::MOUSE_PRESS_RIGHT_BUTTON, point.x, WINDOW_HEIGHT - point.y, 0);
 }
 
 - (void) rightMouseUp: (NSEvent*) event
 {
    __rightMouseDown = false;
-    NSPoint point = [event locationInWindow];
-    _game->mouseEvent(Mouse::MOUSE_RELEASE_RIGHT_BUTTON, point.x, WINDOW_HEIGHT - point.y, 0);
+    NSPoint point = [event locationInWindow];
+    _game->mouseEvent(Mouse::MOUSE_RELEASE_RIGHT_BUTTON, point.x, WINDOW_HEIGHT - point.y, 0);
 }
 
 - (void) rightMouseDragged: (NSEvent*) event
@@ -266,48 +268,48 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
         __lx = point.x;
         __ly = (WINDOW_HEIGHT - point.y);
     }
-    
-    // In right-mouse case, whether __rightMouseDown is true or false
-    // this should not matter, mouse move is still occuring
-    _game->mouseEvent(Mouse::MOUSE_MOVE, point.x, WINDOW_HEIGHT - point.y, 0);
-}
-
-- (void)otherMouseDown: (NSEvent *) event 
-{
-    __otherMouseDown = true;
-    NSPoint point = [event locationInWindow];
-    _game->mouseEvent(Mouse::MOUSE_PRESS_MIDDLE_BUTTON, point.x, WINDOW_HEIGHT - point.y, 0);
-}
-
-- (void)otherMouseUp: (NSEvent *) event 
-{
-    __otherMouseDown = false;
-    NSPoint point = [event locationInWindow];
-    _game->mouseEvent(Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, point.x, WINDOW_HEIGHT - point.y, 0);
-}
-
-- (void)otherMouseDragged: (NSEvent *) event 
-{
-    NSPoint point = [event locationInWindow];
-    _game->mouseEvent(Mouse::MOUSE_MOVE, point.x, WINDOW_HEIGHT - point.y, 0);
-}
-
+    
+    // In right-mouse case, whether __rightMouseDown is true or false
+    // this should not matter, mouse move is still occuring
+    _game->mouseEvent(Mouse::MOUSE_MOVE, point.x, WINDOW_HEIGHT - point.y, 0);
+}
+
+- (void)otherMouseDown: (NSEvent *) event 
+{
+    __otherMouseDown = true;
+    NSPoint point = [event locationInWindow];
+    _game->mouseEvent(Mouse::MOUSE_PRESS_MIDDLE_BUTTON, point.x, WINDOW_HEIGHT - point.y, 0);
+}
+
+- (void)otherMouseUp: (NSEvent *) event 
+{
+    __otherMouseDown = false;
+    NSPoint point = [event locationInWindow];
+    _game->mouseEvent(Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, point.x, WINDOW_HEIGHT - point.y, 0);
+}
+
+- (void)otherMouseDragged: (NSEvent *) event 
+{
+    NSPoint point = [event locationInWindow];
+    _game->mouseEvent(Mouse::MOUSE_MOVE, point.x, WINDOW_HEIGHT - point.y, 0);
+}
+
 - (void) mouseEntered: (NSEvent*)event
 {
     __hasMouse = true;
 }
 
-- (void)scrollWheel: (NSEvent *) event 
-{
-    NSPoint point = [event locationInWindow];
-    Game::getInstance()->mouseEvent(Mouse::MOUSE_WHEEL, point.x, WINDOW_HEIGHT - point.y, (int)([event deltaY] * 10.0f));
-}
-
+- (void)scrollWheel: (NSEvent *) event 
+{
+    NSPoint point = [event locationInWindow];
+    Game::getInstance()->mouseEvent(Mouse::MOUSE_WHEEL, point.x, WINDOW_HEIGHT - point.y, (int)([event deltaY] * 10.0f));
+}
+
 - (void) mouseExited: (NSEvent*)event
 {
     __leftMouseDown = false;
     __rightMouseDown = false;
-    __otherMouseDown = false;
+    __otherMouseDown = false;
     __hasMouse = false;
 }
 
@@ -523,28 +525,28 @@ int getKey(unsigned short keyCode, unsigned int modifierFlags)
             _game->keyEvent((flags & NSAlphaShiftKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_CAPS_LOCK);
             break;
         case 0x38:
-            _game->keyEvent((flags & NSShiftKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_SHIFT);
+            _game->keyEvent((flags & NSShiftKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_SHIFT);
             break;
         case 0x3C:
-            _game->keyEvent((flags & NSShiftKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_SHIFT);
+            _game->keyEvent((flags & NSShiftKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_SHIFT);
             break;
         case 0x3A:
-            _game->keyEvent((flags & NSAlternateKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_ALT);
+            _game->keyEvent((flags & NSAlternateKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_ALT);
             break;
         case 0x3D:
-            _game->keyEvent((flags & NSAlternateKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_ALT);
+            _game->keyEvent((flags & NSAlternateKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_ALT);
             break;
         case 0x3B:
-            _game->keyEvent((flags & NSControlKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_CTRL);
+            _game->keyEvent((flags & NSControlKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_CTRL);
             break;
         case 0x3E:
-            _game->keyEvent((flags & NSControlKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_CTRL);
+            _game->keyEvent((flags & NSControlKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_CTRL);
             break;
         case 0x37:
-            _game->keyEvent((flags & NSCommandKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_HYPER);
+            _game->keyEvent((flags & NSCommandKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_HYPER);
             break;
         case 0x36:
-            _game->keyEvent((flags & NSCommandKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_HYPER);
+            _game->keyEvent((flags & NSCommandKeyMask) ? Keyboard::KEY_PRESS : Keyboard::KEY_RELEASE, Keyboard::KEY_HYPER);
             break;
     }
 }
@@ -598,7 +600,7 @@ Platform* Platform::create(Game* game)
 int Platform::enterMessagePump()
 {
     NSAutoreleasePool* pool = [NSAutoreleasePool new];
-    NSApplication* app = [NSApplication sharedApplication];
+    NSApplication* app = [NSApplication sharedApplication];
     NSRect screenBounds = [[NSScreen mainScreen] frame];
     NSRect viewBounds = NSMakeRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
     
@@ -615,27 +617,27 @@ int Platform::enterMessagePump()
                         backing:NSBackingStoreBuffered
                         defer:NO];
     
-    [window setAcceptsMouseMovedEvents:YES];
+    [window setAcceptsMouseMovedEvents:YES];
     [window setContentView:__view];
     [window setDelegate:__view];
     [__view release];
     
-    [app run];
+    [app run];
     
     [pool release];
     return EXIT_SUCCESS;
 }
-
-unsigned int Platform::getDisplayWidth()
-{
-    return WINDOW_WIDTH;
-}
-
-unsigned int Platform::getDisplayHeight()
-{
-    return WINDOW_HEIGHT;
-}
-
+
+unsigned int Platform::getDisplayWidth()
+{
+    return WINDOW_WIDTH;
+}
+
+unsigned int Platform::getDisplayHeight()
+{
+    return WINDOW_HEIGHT;
+}
+
 long Platform::getAbsoluteTime()
 {
     __timeAbsolute = getMachTimeInMilliseconds();
@@ -678,13 +680,18 @@ void Platform::displayKeyboard(bool display)
 {
     // Do nothing.
 }
-
-void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-{
-    if (!Form::touchEventInternal(evt, x, y, contactIndex))
-    {
-        Game::getInstance()->touchEvent(evt, x, y, contactIndex);
-    }
+
+void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    if (!Form::touchEventInternal(evt, x, y, contactIndex))
+    {
+        Game::getInstance()->touchEvent(evt, x, y, contactIndex);
+    }
+}
+
+void Platform::sleep(long ms)
+{
+    usleep(ms * 1000);
 }
 
 }

+ 8 - 1
gameplay/src/PlatformQNX.cpp

@@ -4,6 +4,8 @@
 #include "Platform.h"
 #include "FileSystem.h"
 #include "Game.h"
+#include "Form.h"
+#include <unistd.h>
 #include <sys/keycodes.h>
 #include <screen/screen.h>
 #include <input/screen_helpers.h>
@@ -941,7 +943,7 @@ int Platform::enterMessagePump()
                         if ((flags & KEY_REPEAT) == 0)
                         {
                             keyEventInternal(evt, getKey(value));
-                            if (evt == gameplay::Keyboard::KEY_PRESS && flags & KEY_SYM_VALID)
+                            if (evt == gameplay::Keyboard::KEY_PRESS && (flags & KEY_SYM_VALID))
                             {
                                 int unicode = getUnicode(value);
                                 if (unicode)
@@ -1098,6 +1100,11 @@ void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
     Form::keyEventInternal(evt, key);
 }
 
+void Platform::sleep(long ms)
+{
+    usleep(ms * 1000);
+}
+
 }
 
 #endif

+ 6 - 0
gameplay/src/PlatformWin32.cpp

@@ -4,6 +4,7 @@
 #include "Platform.h"
 #include "FileSystem.h"
 #include "Game.h"
+#include "Form.h"
 #include <GL/wglew.h>
 
 // Default to 720p
@@ -681,6 +682,11 @@ void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
     Form::keyEventInternal(evt, key);
 }
 
+void Platform::sleep(long ms)
+{
+    Sleep(ms);
+}
+
 }
 
 #endif

+ 7 - 0
gameplay/src/PlatformiOS.mm

@@ -5,6 +5,8 @@
 #include "FileSystem.h"
 #include "Game.h"
 
+#include <unistd.h>
+
 #import <UIKit/UIKit.h>
 #import <QuartzCore/QuartzCore.h>
 #import <CoreMotion/CoreMotion.h>
@@ -853,6 +855,11 @@ void Platform::displayKeyboard(bool display)
         else [__view dismissKeyboard];
     }
 }
+
+void Platform::sleep(long ms)
+{
+    usleep(ms * 1000);
+}
     
 }
 

+ 56 - 37
gameplay/src/RadioButton.cpp

@@ -29,7 +29,7 @@ RadioButton* RadioButton::create(Theme::Style* style, Properties* properties)
     RadioButton* radioButton = new RadioButton();
     radioButton->init(style, properties);
 
-    properties->getVector2("iconSize", &radioButton->_iconSize);
+    properties->getVector2("imageSize", &radioButton->_imageSize);
 
     if (properties->getBool("selected"))
     {
@@ -48,21 +48,14 @@ RadioButton* RadioButton::create(Theme::Style* style, Properties* properties)
     return radioButton;
 }
 
-void RadioButton::setIconSize(float width, float height)
+void RadioButton::setImageSize(float width, float height)
 {
-    _iconSize.set(width, height);
+    _imageSize.set(width, height);
 }
 
-const Vector2& RadioButton::getIconSize() const
+const Vector2& RadioButton::getImageSize() const
 {
-    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
-    Theme::Icon* icon = overlay->getCheckBoxIcon();
-    if (_iconSize.isZero() && icon)
-    {
-        return icon->getSize();
-    }
-
-    return _iconSize;
+    return _imageSize;
 }
 
 void RadioButton::addListener(Control::Listener* listener, int eventFlags)
@@ -121,42 +114,57 @@ void RadioButton::clearSelected(const std::string& groupId)
     }
 }
 
-void RadioButton::drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip)
+void RadioButton::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
     // Left, v-center.
-    // TODO: Set an alignment for icons.
-    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
-    Theme::Icon* icon = overlay->getRadioButtonIcon();
-    if (icon)
+    // TODO: Set an alignment for radio button images.
+    const Theme::Border border = getBorder(_state);
+    const Theme::Padding padding = _style->getPadding();
+    float opacity = getOpacity(_state);
+
+    if (_selected)
     {
-        Theme::Border border;
-        Theme::ContainerRegion* containerRegion = overlay->getContainerRegion();
-        if (containerRegion)
+        const Rectangle& selectedRegion = getImageRegion("selected", _state);
+        const Theme::UVs& selected = getImageUVs("selected", _state);
+        Vector4 selectedColor = getImageColor("selected", _state);
+        selectedColor.w *= opacity;
+
+        Vector2 size;
+        if (_imageSize.isZero())
         {
-            border = containerRegion->getBorder();
+            size.set(selectedRegion.width, selectedRegion.height);
         }
-        const Theme::Padding padding = _style->getPadding();
-
-        Vector2& size = _iconSize;
-        if (_iconSize.isZero())
+        else
         {
-            size = icon->getSize();
+            size.set(_imageSize);
         }
-        const Vector4 color = icon->getColor();
 
         Vector2 pos(clip.x + _position.x + border.left + padding.left,
             clip.y + _position.y + (_bounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
 
-        if (_selected)
+        spriteBatch->draw(pos.x, pos.y, size.x, size.y, selected.u1, selected.v1, selected.u2, selected.v2, selectedColor, _clip);
+    }
+    else
+    {
+        const Rectangle& unselectedRegion = getImageRegion("unselected", _state);
+        const Theme::UVs& unselected = getImageUVs("unselected", _state);
+        Vector4 unselectedColor = getImageColor("unselected", _state);
+        unselectedColor.w *= opacity;
+
+        Vector2 size;
+        if (_imageSize.isZero())
         {
-            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);
+            size.set(unselectedRegion.width, unselectedRegion.height);
         }
         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);
+            size.set(_imageSize);
         }
+
+        Vector2 pos(clip.x + _position.x + border.left + padding.left,
+            clip.y + _position.y + (_bounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+
+        spriteBatch->draw(pos.x, pos.y, size.x, size.y, unselected.u1, unselected.v1, unselected.u2, unselected.v2, unselectedColor, _clip);
     }
 }
 
@@ -164,12 +172,23 @@ void RadioButton::update(const Rectangle& clip)
 {
     Control::update(clip);
 
-    Theme::Style::Overlay* overlay = _style->getOverlay(getOverlayType());
-    Theme::Icon* icon = overlay->getCheckBoxIcon();
-    Vector2& size = _iconSize;
-    if (_iconSize.isZero() && icon)
+    Vector2 size;
+    if (_imageSize.isZero())
+    {
+        if (_selected)
+        {
+            const Rectangle& selectedRegion = getImageRegion("selected", _state);
+            size.set(selectedRegion.width, selectedRegion.height);
+        }
+        else
+        {
+            const Rectangle& unselectedRegion = getImageRegion("unselected", _state);
+            size.set(unselectedRegion.width, unselectedRegion.height);
+        }
+    }
+    else
     {
-        size = icon->getSize();
+        size.set(_imageSize);
     }
     float iconWidth = size.x;
 

+ 4 - 4
gameplay/src/RadioButton.h

@@ -44,14 +44,14 @@ public:
      * @param width The width to draw the radio button icon.
      * @param height The height to draw the radio button icon.
      */
-    void setIconSize(float width, float height);
+    void setImageSize(float width, float height);
 
     /**
      * Get the size at which the radio button icon will be drawn.
      *
      * @return The size of the radio button icon.
      */
-    const Vector2& getIconSize() const;
+    const Vector2& getImageSize() const;
 
     /**
      * Add a listener to be notified of specific events affecting
@@ -83,14 +83,14 @@ protected:
 
     void update(const Rectangle& clip);
 
-    void drawSprites(SpriteBatch* spriteBatch, const Rectangle& clip);
+    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
     // Clear the _selected flag of all radio buttons in the given group.
     static void clearSelected(const std::string& groupId);
 
     std::string _groupId;
     bool _selected;
-    Vector2 _iconSize;
+    Vector2 _imageSize;
 
 private:
     RadioButton(const RadioButton& copy);

+ 4 - 4
gameplay/src/Rectangle.cpp

@@ -44,18 +44,18 @@ void Rectangle::set(const Rectangle& r)
     set(r.x, r.y, r.width, r.height);
 }
 
-void Rectangle::set(float x, float y)
+void Rectangle::set(float x, float y, float width, float height)
 {
     this->x = x;
     this->y = y;
+    this->width = width;
+    this->height = height;
 }
 
-void Rectangle::set(float x, float y, float width, float height)
+void Rectangle::setPosition(float x, float y)
 {
     this->x = x;
     this->y = y;
-    this->width = width;
-    this->height = height;
 }
 
 float Rectangle::left() const

+ 7 - 7
gameplay/src/Rectangle.h

@@ -92,19 +92,19 @@ public:
     void set(float x, float y, float width, float height);
 
     /**
-     * Sets the x-coordinate and y-coordinate values of this rectangle to the specified values.
+     * Sets the values of this rectangle to those in the specified rectangle.
      *
-     * @param x The x-coordinate of the rectangle.
-     * @param y The y-coordinate of the rectangle.
+     * @param r The rectangle to copy.
      */
-    void set(float x, float y);
+    void set(const Rectangle& r);
 
     /**
-     * Sets the values of this rectangle to those in the specified rectangle.
+     * Sets the x-coordinate and y-coordinate values of this rectangle to the specified values.
      *
-     * @param r The rectangle to copy.
+     * @param x The x-coordinate of the rectangle.
+     * @param y The y-coordinate of the rectangle.
      */
-    void set(const Rectangle& r);
+    void setPosition(float x, float y);
 
     /**
      * Returns the x-coordinate of the left side of the rectangle.

+ 8 - 0
gameplay/src/Ref.cpp

@@ -18,6 +18,14 @@ Ref::Ref() :
 #endif
 }
 
+Ref::Ref(const Ref& copy) :
+    _refCount(1)
+{
+#ifdef GAMEPLAY_MEM_LEAK_DETECTION
+    __record = trackRef(this);
+#endif
+}
+
 Ref::~Ref()
 {
 }

+ 2 - 0
gameplay/src/Ref.h

@@ -50,6 +50,8 @@ protected:
      */
     Ref();
 
+    Ref(const Ref& copy);
+
     /**
      * Destructor.
      */

+ 27 - 4
gameplay/src/RenderState.cpp

@@ -3,6 +3,7 @@
 #include "Node.h"
 #include "Pass.h"
 #include "Technique.h"
+#include "Node.h"
 
 // Render state override bits
 #define RS_BLEND 1
@@ -21,10 +22,6 @@ RenderState::RenderState()
 {
 }
 
-RenderState::RenderState(const RenderState& copy)
-{
-}
-
 RenderState::~RenderState()
 {
     SAFE_RELEASE(_state);
@@ -311,6 +308,32 @@ RenderState* RenderState::getTopmost(RenderState* below)
     return NULL;
 }
 
+void RenderState::cloneInto(RenderState* renderState, NodeCloneContext& context) const
+{
+    for (std::map<std::string, AutoBinding>::const_iterator it = _autoBindings.begin(); it != _autoBindings.end(); ++it)
+    {
+        renderState->setParameterAutoBinding(it->first.c_str(), it->second);
+    }
+    for (std::vector<MaterialParameter*>::const_iterator it = _parameters.begin(); it != _parameters.end(); ++it)
+    {
+        const MaterialParameter* param = *it;
+
+        MaterialParameter* paramCopy = new MaterialParameter(param->getName());
+        param->cloneInto(paramCopy);
+
+        renderState->_parameters.push_back(paramCopy);
+    }
+    renderState->_parent = _parent;
+    if (Node* node = context.findClonedNode(_nodeBinding))
+    {
+        renderState->setNodeBinding(node);
+    }
+    if (_state)
+    {
+        renderState->setStateBlock(_state);
+    }
+}
+
 RenderState::StateBlock::StateBlock()
     : _blendEnabled(false), _cullFaceEnabled(false), _depthTestEnabled(false), _depthWriteEnabled(false),
       _srcBlend(RenderState::BLEND_ONE), _dstBlend(RenderState::BLEND_ONE), _bits(0L)

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio