Ver código fonte

Merge pull request #143 from blackberry/next

gameplay v1.3.0
Sean Paul Taylor 13 anos atrás
pai
commit
d7a54c6f1a
100 arquivos alterados com 7056 adições e 3281 exclusões
  1. 62 0
      CHANGES.md
  2. 8 8
      README.md
  3. 7 7
      gameplay-encoder/README.md
  4. 3 4
      gameplay-encoder/gameplay-encoder.xcodeproj/xcshareddata/xcschemes/gameplay-encoder.xcscheme
  5. 152 97
      gameplay-encoder/src/DAESceneEncoder.cpp
  6. 3 1
      gameplay-encoder/src/DAESceneEncoder.h
  7. 80 14
      gameplay-encoder/src/DAEUtil.cpp
  8. 18 0
      gameplay-encoder/src/DAEUtil.h
  9. 165 17
      gameplay-encoder/src/EncoderArguments.cpp
  10. 21 2
      gameplay-encoder/src/EncoderArguments.h
  11. 208 169
      gameplay-encoder/src/FBXSceneEncoder.cpp
  12. 18 16
      gameplay-encoder/src/FBXSceneEncoder.h
  13. 2 2
      gameplay-encoder/src/GPBDecoder.cpp
  14. 35 6
      gameplay-encoder/src/GPBFile.cpp
  15. 6 2
      gameplay-encoder/src/GPBFile.h
  16. 1 1
      gameplay-encoder/src/Node.cpp
  17. 1 0
      gameplay-encoder/src/Scene.cpp
  18. 11 1
      gameplay-encoder/src/StringUtil.cpp
  19. 12 0
      gameplay-encoder/src/StringUtil.h
  20. 3 9
      gameplay-encoder/src/TTFFontEncoder.cpp
  21. 0 8
      gameplay-encoder/src/TTFFontEncoder.h
  22. 1 1
      gameplay-encoder/src/main.cpp
  23. 19 19
      gameplay-template/gameplay-template.xcodeproj/project.pbxproj
  24. 39 82
      gameplay.doxyfile
  25. 19 2
      gameplay/.cproject
  26. 1 1
      gameplay/android/jni/Android.mk
  27. 7 1
      gameplay/gameplay.vcxproj
  28. 19 1
      gameplay/gameplay.vcxproj.filters
  29. 24 0
      gameplay/gameplay.xcodeproj/project.pbxproj
  30. 41 39
      gameplay/src/AbsoluteLayout.cpp
  31. 2 1
      gameplay/src/AbsoluteLayout.h
  32. 47 15
      gameplay/src/Animation.cpp
  33. 41 9
      gameplay/src/AnimationClip.cpp
  34. 7 0
      gameplay/src/AnimationController.cpp
  35. 141 17
      gameplay/src/AnimationTarget.cpp
  36. 20 1
      gameplay/src/AnimationTarget.h
  37. 7 4
      gameplay/src/AnimationValue.cpp
  38. 121 64
      gameplay/src/AudioBuffer.cpp
  39. 13 11
      gameplay/src/AudioController.cpp
  40. 2 0
      gameplay/src/AudioListener.cpp
  41. 38 40
      gameplay/src/AudioSource.cpp
  42. 79 76
      gameplay/src/Base.h
  43. 7 5
      gameplay/src/BoundingBox.cpp
  44. 8 0
      gameplay/src/BoundingSphere.cpp
  45. 240 111
      gameplay/src/Bundle.cpp
  46. 30 30
      gameplay/src/Button.cpp
  47. 17 24
      gameplay/src/Camera.cpp
  48. 14 19
      gameplay/src/CheckBox.cpp
  49. 3 2
      gameplay/src/CheckBox.h
  50. 770 259
      gameplay/src/Container.cpp
  51. 223 31
      gameplay/src/Container.h
  52. 1026 830
      gameplay/src/Control.cpp
  53. 67 20
      gameplay/src/Control.h
  54. 1 5
      gameplay/src/Curve.cpp
  55. 10 10
      gameplay/src/DebugNew.cpp
  56. 10 7
      gameplay/src/DepthStencilTarget.cpp
  57. 34 13
      gameplay/src/Effect.cpp
  58. 151 28
      gameplay/src/FileSystem.cpp
  59. 57 0
      gameplay/src/FileSystem.h
  60. 7 6
      gameplay/src/FlowLayout.cpp
  61. 3 2
      gameplay/src/FlowLayout.h
  62. 377 252
      gameplay/src/Font.cpp
  63. 70 6
      gameplay/src/Font.h
  64. 533 276
      gameplay/src/Form.cpp
  65. 36 8
      gameplay/src/Form.h
  66. 29 11
      gameplay/src/FrameBuffer.cpp
  67. 4 0
      gameplay/src/FrameBuffer.h
  68. 28 23
      gameplay/src/Frustum.cpp
  69. 9 0
      gameplay/src/Frustum.h
  70. 64 17
      gameplay/src/Game.cpp
  71. 17 4
      gameplay/src/Game.h
  72. 1 0
      gameplay/src/Game.inl
  73. 32 8
      gameplay/src/Image.cpp
  74. 2 0
      gameplay/src/Joint.cpp
  75. 229 0
      gameplay/src/Joystick.cpp
  76. 149 0
      gameplay/src/Joystick.h
  77. 34 0
      gameplay/src/Joystick.inl
  78. 73 62
      gameplay/src/Label.cpp
  79. 8 2
      gameplay/src/Label.h
  80. 54 43
      gameplay/src/Layout.cpp
  81. 28 2
      gameplay/src/Layout.h
  82. 42 14
      gameplay/src/Light.cpp
  83. 69 29
      gameplay/src/Material.cpp
  84. 55 14
      gameplay/src/MaterialParameter.cpp
  85. 49 0
      gameplay/src/MathUtil.h
  86. 177 0
      gameplay/src/MathUtil.inl
  87. 229 0
      gameplay/src/MathUtilNeon.inl
  88. 83 178
      gameplay/src/Matrix.cpp
  89. 18 3
      gameplay/src/Mesh.cpp
  90. 26 10
      gameplay/src/MeshBatch.cpp
  91. 11 6
      gameplay/src/MeshBatch.inl
  92. 10 0
      gameplay/src/MeshPart.cpp
  93. 12 8
      gameplay/src/MeshSkin.cpp
  94. 50 9
      gameplay/src/Model.cpp
  95. 66 37
      gameplay/src/Node.cpp
  96. 2 1
      gameplay/src/Node.h
  97. 90 46
      gameplay/src/ParticleEmitter.cpp
  98. 15 19
      gameplay/src/ParticleEmitter.h
  99. 7 6
      gameplay/src/Pass.cpp
  100. 86 37
      gameplay/src/PhysicsCharacter.cpp

+ 62 - 0
CHANGES.md

@@ -0,0 +1,62 @@
+## v1.3.0
+
+- Portrait mode games on mobile platforms.
+- Fullscreen and configurable game resolutions on desktop platforms.
+- User Interface support for scrolling with scrollbars on Container.
+- PVRTC, ATC and DXT texture compression support.
+- Performance improvements in user interface forms and text.
+- Performance improvements in animations on transforms.
+- Performance improvements using NEON math for BlackBerry and iOS.
+- Fixes for improvements in error handling throughout all systems.
+- Fixes supporting built-in Maya COLLADA exporter via DAE_FBX export.
+- Fixes for latest FBX SDK 2013 support.
+- Fixes for loading from some WAV files that were crashing.
+- Fixes for From/By animations.
+- Fixes allowing all inline properties loaded within .scene files. (breaks compat. for .scene)
+- Fixes in .scene files for collisionObject definitions (breaks compat. for .scene)
+- Fixes for depth/z-ordering of controls.
+
+## v1.2.0
+
+- BlackBerry 10 support.
+- iOS 5.1 support.
+- Android 2.3+ support.
+- User interface system with declaritive forms and themes.
+- Bluetooth keyboard/mouse support on BlackBerry platform.
+- Developer guide.
+- Sample/turorial for sample03-character.
+- Sample for sample04-particles.
+- Fixes for loading properties from URL.
+- Fixes on Win32/MacOSX for when mouse pointer leaves the window and returns.
+- Fixes to accelerometer for Android.
+- Fixes in animation blending.
+- Fixes to GPB for loading from single node and parent node. (breaks compat. for .gpb)
+
+## v1.1.0
+
+- FBX support in gameplay-encoder.
+- MacOSX platform support using XCode.
+- Offscreen rendering functionality using FrameBuffer.
+- Loading 3D scences using declaritive .scene files.
+- Loading audio from .ogg files using vorbis.
+- Loading AudioSources from .audio files.
+- Loading Animations from .animation files.
+- AnimationClip support for cross fading.
+- Physics support using Bullet Physics.
+- Cross-platform new project generator.
+- Overloaded operators in Math classes.
+- Font improvements for justify, clip, wrap and scaling.
+- Fixes for Font::drawText to use point size and not float scalar.
+- Fixes for memory leaks in and fixes to AnimationTarget.
+- Fixes for bumped and paralax shaders.
+- Fixes to simplify folders for resources in samples.
+- Fixes to the material/shader system.
+- Fixes to the ParticleEmitter.
+
+## v1.0.1
+
+- Initial release.
+
+
+
+

+ 8 - 8
README.md

@@ -1,9 +1,9 @@
-## gameplay v1.2.0
+## gameplay v1.3.0
 An open-source, cross-platform 3D native C++ game framework making it easy to learn and write mobile and desktop games. 
 
 ## Supported Mobile Platforms
-- BlackBerry PlayBook 2.0 (using BlackBerry Native SDK 2)
-- Google Android 2.3 (using Google Android NDK r7, SDK API level 9 and up)
+- BlackBerry 10 and PlayBook 2.0 (using BlackBerry Native SDK)
+- Google Android 2.3+ (using Google Android NDK, SDK API level 9+)
 - Apple iOS 5.1 (using Apple XCode 4.3.2)
 
 ## Supported Desktop Platforms
@@ -11,12 +11,12 @@ An open-source, cross-platform 3D native C++ game framework making it easy to le
 - Apple MacOS X (using Apple XCode 4.3.2)
 
 ## Roadmap for 'next' branch
-- Shadows
-- Lua Script Bindings
+- Lua script bindings
+- Gamepad support
+- Vehicle physics
 - Terrain
-- AI
-- Editor
-- Performance/Optimizations
+- Lightmaps
+- Shadows
 
 ## Licence
 The project is open sourced under the Apache 2.0 license.

+ 7 - 7
gameplay-encoder/README.md

@@ -21,13 +21,13 @@ You must then rebuild gameplay-encoder with the follow platform/tooling instruct
 - Edit the project properties of "gameplay-encoder"
 - Add Preprocessor Definition "USE_FBX" (C++/Preprocessor)
 - Add the FBX SDK include directory to Additional Include Directories (C++/General)
-  * Example: C:/Program Files/Autodesk/FBX/FbxSdk/2012.2/include
+  * Example: C:/Program Files/Autodesk/FBX/FbxSdk/2013.1/include
 - Add the FBX lib directory to the Additional Library Directories (Linker/General)
-  * Example: C:/Program Files/Autodesk/FBX/FbxSdk/2012.2/lib/vs2010/x86
-- Add "fbxsdk-2012.2-mdd.lib" and "wininet.lib" to the Additional Dependencies (Linker/Input)
-  * Example: fbxsdk-2012.2-mdd.lib;wininet.lib
+  * Example: C:/Program Files/Autodesk/FBX/FbxSdk/2013.1/lib/vs2010/x86
+- Add "fbxsdk-2013.1-mdd.lib" and "wininet.lib" to the Additional Dependencies (Linker/Input)
+  * Example: fbxsdk-2013.1-mdd.lib;wininet.lib
 - Add a post build event to copy the DLL (Build Events/Post-Build Event)
-  * Example: copy /Y "C:\Program Files\Autodesk\FBX\FbxSdk\2012.2\lib\vs2010\x86\fbxsdk-2012.2d.dll" "$(TargetDir)"
+  * Example: copy /Y "C:\Program Files\Autodesk\FBX\FbxSdk\2013.1\lib\vs2010\x86\fbxsdk-2013.1d.dll" "$(TargetDir)"
 - Build gameplay-encoder
 
 ### Building FBX Support on Mac OS X using XCode 4.3.2+
@@ -35,9 +35,9 @@ You must then rebuild gameplay-encoder with the follow platform/tooling instruct
 - Edit the project properties of target "gameplay-encoder".
 - Add Preprocessor Macro "USE_FBX" to both Debug/Release sections. (Build Settings)
 - Add the FBX include directory to Header Search Paths: (Build Settings)
-  * Example: /Applications/Autodesk/FBXSDK20122/include
+  * Example: /Applications/Autodesk/FBXSDK20131/include
 - Add the FBX library and dependency Library/Frameworks: (Build Phases -> Link Binary with Libraries)
-  * Example: /Applications/Autodesk/FBXSDK20122/lib/gcc4/ub/libfbxsdk-2012.2-static.a  (Add Other)
+  * Example: /Applications/Autodesk/FBXSDK20131/lib/gcc4/ub/libfbxsdk-2013.1-static.a  (Add Other)
   * Example: libiconv.dylib, Cocoa.framework, SystemConfiguration.framework
 - Build gameplay-encoder
 

+ 3 - 4
gameplay-encoder/gameplay-encoder.xcodeproj/xcshareddata/xcschemes/gameplay-encoder.xcscheme

@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0430"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -23,8 +22,8 @@
       </BuildActionEntries>
    </BuildAction>
    <TestAction
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES"
       buildConfiguration = "Debug">
       <Testables>
@@ -41,7 +40,7 @@
    </TestAction>
    <LaunchAction
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       launchStyle = "0"
       useCustomWorkingDirectory = "NO"
       buildConfiguration = "Debug"

+ 152 - 97
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -54,7 +54,7 @@ void DAESceneEncoder::optimizeCOLLADA(const EncoderArguments& arguments, domCOLL
     {
         if (!_collada->writeTo(arguments.getFilePath(), arguments.getDAEOutputPath()))
         {
-            fprintf(stderr,"Error: COLLADA failed to write the dom for file:%s\n", arguments.getDAEOutputPath().c_str());
+            fprintf(stderr,"Error: COLLADA failed to write the dom for file: %s\n", arguments.getDAEOutputPath().c_str());
         }
     }
 }
@@ -227,10 +227,6 @@ void DAESceneEncoder::write(const std::string& filepath, const EncoderArguments&
 {
     _begin = clock();
     const char* nodeId = arguments.getNodeId();
-    bool text = arguments.textOutputEnabled();
-
-    std::string filenameOnly = getFilenameFromFilePath(filepath);
-    std::string dstPath = filepath.substr(0, filepath.find_last_of('/'));
     
     // Load the collada document
     _collada = new DAE();
@@ -239,7 +235,7 @@ void DAESceneEncoder::write(const std::string& filepath, const EncoderArguments&
     end("Open file");
     if (!_dom)
     {
-        fprintf(stderr,"Error: COLLADA failed to open file:%s\n", filepath.c_str());
+        fprintf(stderr,"Error: COLLADA failed to open file: %s\n", filepath.c_str());
         if (_collada)
         {
             delete _collada;
@@ -261,13 +257,7 @@ void DAESceneEncoder::write(const std::string& filepath, const EncoderArguments&
     daeElement* scene = NULL;
     if (domScene && domScene->getInstance_visual_scene())
     {
-        scene = domScene->getInstance_visual_scene()->getUrl().getElement();
-        if (scene->getElementType() != COLLADA_TYPE::VISUAL_SCENE)
-        {
-            // This occured once where Maya exported a Node and Scene element with the same ID.
-            fprintf(stderr,"Error: instance_visual_scene does not reference visual_scene for file:%s\n", filepath.c_str());
-            return;
-        }
+        scene = getVisualScene(domScene);
         if (scene)
         {
             if (nodeId == NULL)
@@ -315,24 +305,32 @@ void DAESceneEncoder::write(const std::string& filepath, const EncoderArguments&
     loadAnimations(_dom);
     end("loadAnimations");
 
-    std::string dstFilename = dstPath;
-    dstFilename.append(1, '/');
-    dstFilename.append(getFilenameNoExt(filenameOnly));
-
     _gamePlayFile.adjust();
 
-    if (text)
+    // Write the output file
+    std::string outputFilePath = arguments.getOutputFilePath();
+    if (arguments.textOutputEnabled())
     {
-        std::string outFile = dstFilename + ".xml";
-        fprintf(stderr, "Saving debug file: %s\n", outFile.c_str());
-        _gamePlayFile.saveText(outFile);
+        int pos = outputFilePath.find_last_of('.');
+        if (pos > 2)
+        {
+            std::string path = outputFilePath.substr(0, pos);
+            path.append(".xml");
+            fprintf(stderr, "Saving debug file: %s\n", path.c_str());
+            if (!_gamePlayFile.saveText(path))
+            {
+                fprintf(stderr,"Error writing text file: %s\n", path.c_str());
+            }
+        }
     }
     else
     {
-        std::string outFile = dstFilename + ".gpb";
-        fprintf(stderr, "Saving binary file: %s\n", outFile.c_str());
+        fprintf(stderr, "Saving binary file: %s\n", outputFilePath.c_str());
         begin();
-        _gamePlayFile.saveBinary(outFile);
+        if (!_gamePlayFile.saveBinary(outputFilePath))
+        {
+            fprintf(stderr,"Error writing binary file: %s\n", outputFilePath.c_str());
+        }
         end("save binary");
     }
     
@@ -366,89 +364,114 @@ void DAESceneEncoder::loadAnimations(const domCOLLADA* dom)
     }
 }
 
-void DAESceneEncoder::loadAnimation(const domAnimationRef animationRef)
+void DAESceneEncoder::loadAnimation(const domAnimationRef animationRef, const char* altId)
 {
-    // <channel> points to one <sampler>
-    // <sampler> points to multiple <input> elements
-
-    Animation* animation = new Animation();
-    const char* str = animationRef->getId();
-    if (str)
+    // Animations can contain other animations.
+    const domAnimation_Array& animationArray = animationRef->getAnimation_array();
+    unsigned int animationCount = animationArray.getCount();
+    
+    if (animationCount == 1)
+    {
+        // DAE_FBX nests 1 animation within another animation for some reason.
+        loadAnimation(animationArray.get(0), animationRef->getId());
+    }
+    else if ( animationCount > 1)
     {
-        animation->setId(str);
+        loadAnimation(animationArray.get(0));
     }
 
+    // <channel> points to one <sampler>
+    // <sampler> points to multiple <input> elements
+
     // <channel>
     const domChannel_Array& channelArray = animationRef->getChannel_array();
     size_t channelArrayCount = channelArray.getCount();
-    for (size_t i = 0; i < channelArrayCount; ++i)
+    if (channelArrayCount > 0)
     {
-        AnimationChannel* animationChannel = new AnimationChannel();
-
-        const domChannelRef& channelRef = channelArray.get(i);
-
-        // <sampler>
-        const domSamplerRef sampler = getSampler(channelRef);
-        assert(sampler);
+        Animation* animation = new Animation();
+        const char* str = animationRef->getId();
+        if (str)
+        {
+            animation->setId(str);
+        }
+        else if (altId)
+        {
+            animation->setId(altId);
+        }
 
-        // <input>
-        const domInputLocal_Array& inputArray = sampler->getInput_array();
-        size_t inputArrayCount = inputArray.getCount();
-        for (size_t j = 0; j < inputArrayCount; ++j)
+        for (size_t i = 0; i < channelArrayCount; ++i)
         {
-            const domInputLocalRef& inputLocal = inputArray.get(j);
+            AnimationChannel* animationChannel = new AnimationChannel();
 
-            // <source>
-            const domSourceRef source = getSource(inputLocal, animationRef);
+            const domChannelRef& channelRef = channelArray.get(i);
 
-            std::string semantic = inputLocal->getSemantic();
-            if (equals(semantic, "INTERPOLATION"))
-            {
-                // Interpolation source is a list of strings
-                loadInterpolation(source, animationChannel);
-            }
-            else
+            // <sampler>
+            const domSamplerRef sampler = getSampler(channelRef);
+            assert(sampler);
+
+            // <input>
+            const domInputLocal_Array& inputArray = sampler->getInput_array();
+            size_t inputArrayCount = inputArray.getCount();
+            for (size_t j = 0; j < inputArrayCount; ++j)
             {
-                // The other sources are lists of floats.
-                std::vector<float> floats;
-                copyFloats(source->getFloat_array(), &floats);
-                if (equals(semantic, "INPUT"))
-                {
-                    // TODO: Ensure param name is TIME?
-                    for (std::vector<float>::iterator k = floats.begin(); k != floats.end(); ++k)
-                    {
-                        // Convert seconds to milliseconds
-                        *k = *k * 1000.0f;
-                    }
-                    animationChannel->setKeyTimes(floats);
-                }
-                else if (equals(semantic, "OUTPUT"))
+                const domInputLocalRef& inputLocal = inputArray.get(j);
+
+                // <source>
+                const domSourceRef source = getSource(inputLocal, animationRef);
+
+                std::string semantic = inputLocal->getSemantic();
+                if (equals(semantic, "INTERPOLATION"))
                 {
-                    animationChannel->setKeyValues(floats);
+                    // Interpolation source is a list of strings
+                    loadInterpolation(source, animationChannel);
                 }
-                else if (equals(semantic, "IN_TANGENT"))
+                else
                 {
-                    animationChannel->setTangentsIn(floats);
+                    // The other sources are lists of floats.
+                    std::vector<float> floats;
+                    copyFloats(source->getFloat_array(), &floats);
+                    if (equals(semantic, "INPUT"))
+                    {
+                        // TODO: Ensure param name is TIME?
+                        for (std::vector<float>::iterator k = floats.begin(); k != floats.end(); ++k)
+                        {
+                            // Convert seconds to milliseconds
+                            *k = *k * 1000.0f;
+                        }
+                        animationChannel->setKeyTimes(floats);
+                    }
+                    else if (equals(semantic, "OUTPUT"))
+                    {
+                        animationChannel->setKeyValues(floats);
+                    }
+                    else if (equals(semantic, "IN_TANGENT"))
+                    {
+                        animationChannel->setTangentsIn(floats);
+                    }
+                    else if (equals(semantic, "OUT_TANGENT"))
+                    {
+                        animationChannel->setTangentsOut(floats);
+                    }
                 }
-                else if (equals(semantic, "OUT_TANGENT"))
+            }
+            
+            // get target attribute enum value
+            if (loadTarget(channelRef, animationChannel))
+            {
+                if (animationChannel->getKeyTimes().size() > 0)
                 {
-                    animationChannel->setTangentsOut(floats);
+                    animation->add(animationChannel);
                 }
             }
         }
-        // get target attribute enum value
-        if (loadTarget(channelRef, animationChannel))
+        if (animation->getAnimationChannelCount() > 0)
         {
-            animation->add(animationChannel);
+            _gamePlayFile.addAnimation(animation);
+        }
+        else
+        {
+            delete animation;
         }
-    }
-    if (animation->getAnimationChannelCount() > 0)
-    {
-        _gamePlayFile.addAnimation(animation);
-    }
-    else
-    {
-        delete animation;
     }
 }
 
@@ -685,6 +708,11 @@ void DAESceneEncoder::loadScene(const domVisual_scene* visualScene)
 
     const domNode_Array& nodes = visualScene->getNode_array();
     scene->setId(visualScene->getId());
+    if (scene->getId().length() == 0)
+    {
+        scene->setId("__SCENE__");
+    }
+
     size_t childCount = nodes.getCount();
     for (size_t i = 0; i < childCount; ++i)
     {
@@ -989,16 +1017,21 @@ void DAESceneEncoder::loadControllerInstance(const domNode* n, Node* node)
                     domInstance_controller::domSkeleton_Array& skeletons = instanceControllerRef->getSkeleton_array();
                     if (skeletons.getCount() == 0)
                     {
-                        warning("No skeletons found for instance controller: ");
-                        delete model;
-                        continue;
+                        domNode* rootJoint = getRootJointNode(skinElement);
+                        if (rootJoint)
+                        {
+                            loadSkeleton(rootJoint, model->getSkin());
+                            node->setModel(model);
+                        }
+                    }
+                    else
+                    {
+                        // Load the skeleton for this skin
+                        domInstance_controller::domSkeletonRef skeleton = getSkeleton(instanceControllerRef);
+                        assert(skeleton);
+                        loadSkeleton(skeleton, model->getSkin());
+                        node->setModel(model);
                     }
-                    // Load the skeleton for this skin
-                    domInstance_controller::domSkeletonRef skeleton = getSkeleton(instanceControllerRef);
-                    assert(skeleton);
-                    loadSkeleton(skeleton, model->getSkin());
-
-                    node->setModel(model);
                 }
             }
         }
@@ -1182,21 +1215,33 @@ Light* DAESceneEncoder::loadLight(const domLight* lightRef)
             {
                 light->setQuadraticAttenuation((float)quadAtt->getValue());
             }
+
+            // When Maya exports DAE_FBX, the ambient lights are converted into point lights but with not attenuation elements.
+            // If this point light has no attenuation then assume it is ambient.
+            if (!(constAtt.cast() && linearAtt.cast() && quadAtt.cast()))
+            {
+                light->setAmbientLight();
+            }
         }
     }
     _gamePlayFile.addLight(light);
     return light;
 }
 
+
 void DAESceneEncoder::loadSkeleton(domInstance_controller::domSkeleton* skeletonElement, MeshSkin* skin)
 {
     xsAnyURI skeletonUri = skeletonElement->getValue();
     daeString skeletonId = skeletonUri.getID();
     daeSIDResolver resolver(skeletonUri.getElement(), skeletonId);
     domNode* rootNode = daeSafeCast<domNode>(resolver.getElement());
-    
+    loadSkeleton(rootNode, skin);
+}
+
+void DAESceneEncoder::loadSkeleton(domNode* rootNode, MeshSkin* skin)
+{
     // Get the lookup scene id (sid) and joint index.
-    std::string id = std::string(skeletonId);
+    std::string id = std::string(rootNode->getId());
 
     // Has the skeleton (root joint) been loaded yet?
     Node* skeleton = (Node*)_gamePlayFile.getFromRefTable(id);
@@ -1674,6 +1719,7 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
             {
                 maxOffset = offset;
             }
+            int polyIndexInt = (int) polyInts.get(poly + offset);
             unsigned int polyIndex = (unsigned int) polyInts.get(poly + offset);
 
             switch (polygonInputs[k]->type)
@@ -1774,8 +1820,17 @@ Mesh* DAESceneEncoder::loadMesh(const domMesh* meshElement, const std::string& g
                 {
                     // TODO: This assumes (s, t) are first
                     unsigned int stride = (unsigned int)polygonInputs[k]->accessor->getStride();
-                    vertex.texCoord.x = (float)source.get(polyIndex * stride);
-                    vertex.texCoord.y = (float)source.get(polyIndex * stride + 1);
+                    if (polyIndexInt < 0)
+                    {
+                        unsigned int i = (unsigned int)((int)polygonInputs[k]->accessor->getCount()) + polyIndexInt;
+                        vertex.texCoord.x = (float)source.get(i * stride);
+                        vertex.texCoord.y = (float)source.get(i * stride + 1);
+                    }
+                    else
+                    {
+                        vertex.texCoord.x = (float)source.get(polyIndex * stride);
+                        vertex.texCoord.y = (float)source.get(polyIndex * stride + 1);
+                    }
                 }
                 else
                 {

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

@@ -111,14 +111,16 @@ private:
      * Loads a COLLADA animation element.
      * 
      * @param animationRef The animation dom element to load from.
+     * @param altId   The id string to use if the animation doesn't have an id.
      */
-    void loadAnimation(const domAnimationRef animationRef);
+    void loadAnimation(const domAnimationRef animationRef, const char* altId = NULL);
 
     Camera* loadCamera(const domCamera* cameraRef);
     Light* loadLight(const domLight* lightRef);
     Model* loadSkin(const domSkin* skinElement);
     Model* loadGeometry(const domGeometry* geometry, const domBind_materialRef bindMaterial);
 
+    void loadSkeleton(domNode* rootNode, MeshSkin* skin);
     void loadSkeleton(domInstance_controller::domSkeleton* skeletonElement, MeshSkin* skin);
     
     /**

+ 80 - 14
gameplay-encoder/src/DAEUtil.cpp

@@ -13,7 +13,16 @@ namespace gameplay
  * 
  * @return The index in skeletonArray or -1 if not found.
  */
-int getIndex(const domInstance_controller::domSkeleton_Array& skeletonArray, const domNodeRef& node);
+static int getIndex(const domInstance_controller::domSkeleton_Array& skeletonArray, const domNodeRef& node);
+
+/**
+ * Gets all of the animation channels that target the given node and appends them to the list.
+ * 
+ * @param animationRef The animation to search in.
+ * @param nodeIdSlash The node's id with a forward slash appended to it.
+ * @param channels The list of channels to append to.
+ */
+static void getAnimationChannels(const domAnimationRef& animationRef, const std::string& nodeIdSlash, std::list<domChannelRef>& channels);
 
 void getAnimationChannels(const domNodeRef& node, std::list<domChannelRef>& channels)
 {
@@ -33,19 +42,7 @@ void getAnimationChannels(const domNodeRef& node, std::list<domChannelRef>& chan
         for (size_t j = 0; j < animationCount; ++j)
         {
             domAnimationRef& animationRef = animationArray.get(j);
-            domChannel_Array& channelArray = animationRef->getChannel_array();
-            size_t channelArrayCount = channelArray.getCount();
-            for (size_t k = 0; k < channelArrayCount; ++k)
-            {
-                domChannelRef& channel = channelArray.get(k);
-                const char* target = channel->getTarget();
-
-                // TODO: Assumes only one target per channel?
-                if (startsWith(target, nodeIdSlash.c_str()))
-                {
-                    channels.push_back(channel);
-                }
-            }
+            getAnimationChannels(animationRef, nodeIdSlash, channels);
         }
     }
 
@@ -262,6 +259,20 @@ const domInstance_controller::domSkeletonRef getSkeleton(const domInstance_contr
     return NULL;
 }
 
+domNode* getRootJointNode(const domSkin* skin)
+{
+    std::vector<std::string> names;
+    getJointNames(skin, names);
+    daeSIDResolver resolver(const_cast<domSkin*>(skin)->getDocument()->getDomRoot(), names[0].c_str());
+    daeElement* element = resolver.getElement();
+    if (element && element->getElementType() == COLLADA_TYPE::NODE)
+    {
+        domNode* node = daeSafeCast<domNode>(resolver.getElement());
+        return node;
+    }
+    return NULL;
+}
+
 bool equalKeyTimes(const domSource* s1, const domSource* s2)
 {
     // TODO: shouldn't assume that the source has a float array.
@@ -355,4 +366,59 @@ int getIndex(const domInstance_controller::domSkeleton_Array& skeletonArray, con
     return -1;
 }
 
+void getAnimationChannels(const domAnimationRef& animationRef, const std::string& nodeIdSlash, std::list<domChannelRef>& channels)
+{
+    domChannel_Array& channelArray = animationRef->getChannel_array();
+    size_t channelArrayCount = channelArray.getCount();
+    for (size_t k = 0; k < channelArrayCount; ++k)
+    {
+        domChannelRef& channel = channelArray.get(k);
+        const char* target = channel->getTarget();
+
+        // TODO: Assumes only one target per channel?
+        if (startsWith(target, nodeIdSlash.c_str()))
+        {
+            channels.push_back(channel);
+        }
+    }
+
+    // This animation could have child animations.
+    const domAnimation_Array& animationArray = animationRef->getAnimation_array();
+    unsigned int animationCount = animationArray.getCount();
+    for (size_t i = 0; i < animationCount; ++i)
+    {
+        getAnimationChannels(animationArray[i], nodeIdSlash, channels);
+    }
+}
+
+domVisual_scene* getVisualScene(const domCOLLADA::domSceneRef& domScene)
+{
+    daeElement* scene = domScene->getInstance_visual_scene()->getUrl().getElement();
+    if (scene->getElementType() == COLLADA_TYPE::VISUAL_SCENE)
+    {
+        return static_cast<domVisual_scene*>(scene);
+    }
+
+    // DAE_FBX sometimes doesn't export an ID. In that case, see if there is only one visual scene and use that.
+    // Most of the time there is only one visual scene.
+    domCOLLADA* root = (domCOLLADA*)domScene->getDocument()->getDomRoot();
+    domLibrary_visual_scenes_Array& visualSceneLibrary = root->getLibrary_visual_scenes_array();
+    size_t visualSceneLibraryCount = visualSceneLibrary.getCount();
+    for (size_t i = 0; i < visualSceneLibraryCount; ++i)
+    {
+        domLibrary_visual_scenesRef scenesRef = visualSceneLibrary.get(i);
+        domVisual_scene_Array visualScenes = scenesRef->getVisual_scene_array();
+        size_t visualSceneCount = visualScenes.getCount();
+        for (size_t j = 0; j < visualSceneCount; ++j)
+        {
+            domVisual_sceneRef visualScene = visualScenes.get(j);
+            if (domVisual_scene* v = visualScene.cast())
+            {
+                return v;
+            }
+        }
+    }
+    return NULL;
+}
+
 }

+ 18 - 0
gameplay-encoder/src/DAEUtil.h

@@ -77,6 +77,15 @@ const domName_arrayRef getSourceNameArray(const domSourceRef& source);
  */
 const domInstance_controller::domSkeletonRef getSkeleton(const domInstance_controllerRef& instanceController);
 
+/**
+ * Returns the root joint node of the given skin.
+ * 
+ * @param skin The COLLADA skin to get the root joint for.
+ * 
+ * @return The COLLADA node or NULL if not found.
+ */
+domNode* getRootJointNode(const domSkin* skin);
+
 /**
  * Returns true if the two given animation channels have equal key time input source.
  * 
@@ -114,6 +123,15 @@ void moveChannelAndSouresToAnimation(domChannelRef& channel, domAnimationRef& an
  */
 bool isEmptyAnimation(domAnimationRef& animation);
 
+/**
+ * Gets the visual scene from the given COLLADA dom scene.
+ * 
+ * @param COLLADA dom scene.
+ * 
+ * @return The visual scene or NULL if not found.
+ */
+domVisual_scene* getVisualScene(const domCOLLADA::domSceneRef& domScene);
+
 }
 
 #endif

+ 165 - 17
gameplay-encoder/src/EncoderArguments.cpp

@@ -24,26 +24,30 @@ EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
 
     if (argc > 1)
     {
-        size_t filePathIndex = argc - 1;
-        if (argv[filePathIndex])
-        {
-            _filePath.assign(getRealPath(argv[filePathIndex]));
-        }
-        
         // read the options
-        std::vector<std::string> options;
-        for (size_t i = 1; i < filePathIndex; ++i)
+        std::vector<std::string> arguments;
+        for (size_t i = 1; i < argc; ++i)
         {
-            options.push_back(argv[i]);
+            arguments.push_back(argv[i]);
         }
-        
-        for (size_t i = 0; i < options.size(); ++i)
+        size_t index = 0;
+        for (size_t i = 0; i < arguments.size(); ++i)
         {
-            if (options[i][0] == '-')
+            if (arguments[i][0] == '-')
             {
-                readOption(options, &i);
+                readOption(arguments, &i);
+                index = i + 1;
             }
         }
+        if (arguments.size() - index == 2)
+        {
+            setInputfilePath(arguments[index]);
+            setOutputfilePath(arguments[index + 1]);
+        }
+        else if (arguments.size() - index == 1)
+        {
+            setInputfilePath(arguments[index]);
+        }
     }
     else
     {
@@ -70,10 +74,42 @@ const char* EncoderArguments::getFilePathPointer() const
     return _filePath.c_str();
 }
 
-std::string EncoderArguments::getOutputPath() const
+std::string EncoderArguments::getOutputDirPath() const
 {
-    int pos = _filePath.find_last_of('/');
-    return (pos == -1 ? _filePath : _filePath.substr(0, pos));
+    if (_fileOutputPath.size() > 0)
+    {
+        int pos = _fileOutputPath.find_last_of('/');
+        return (pos == -1 ? _fileOutputPath : _fileOutputPath.substr(0, pos));
+    }
+    else
+    {
+        int pos = _filePath.find_last_of('/');
+        return (pos == -1 ? _filePath : _filePath.substr(0, pos));
+    }
+}
+
+std::string EncoderArguments::getOutputFilePath() const
+{
+    if (_fileOutputPath.size() > 0)
+    {
+        return _fileOutputPath;
+    }
+    else
+    {
+        int pos = _filePath.find_last_of('.');
+        if (pos > 0)
+        {
+            std::string outputFilePath(_filePath.substr(0, pos));
+            outputFilePath.append(".gpb");
+            return outputFilePath;
+        }
+        else
+        {
+            std::string outputFilePath(_filePath);
+            outputFilePath.append(".gpb");
+            return outputFilePath;
+        }
+    }
 }
 
 const std::string& EncoderArguments::getDAEOutputPath() const
@@ -134,7 +170,7 @@ bool EncoderArguments::fileExists() const
 
 void EncoderArguments::printUsage() const
 {
-    fprintf(stderr,"Usage: gameplay-encoder [options] <filepath>\n\n");
+    fprintf(stderr,"Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n");
     fprintf(stderr,"Supported file extensions:\n");
     fprintf(stderr,"  .dae\t(COLLADA)\n");
     fprintf(stderr,"  .fbx\t(FBX)\n");
@@ -343,6 +379,43 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
     }
 }
 
+void EncoderArguments::setInputfilePath(const std::string& inputPath)
+{
+    _filePath.assign(getRealPath(inputPath));
+}
+
+void EncoderArguments::setOutputfilePath(const std::string& outputPath)
+{
+    if (outputPath.size() > 0 && outputPath[0] != '\0')
+    {
+        std::string realPath = getRealPath(outputPath);
+        if (endsWith(realPath.c_str(), ".gpb"))
+        {
+            _fileOutputPath.assign(realPath);
+        }
+        else if (endsWith(outputPath.c_str(), "/"))
+        {
+            std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(_filePath));
+
+            _fileOutputPath.assign(outputPath);
+            _fileOutputPath.append(filenameNoExt);
+            _fileOutputPath.append(".gpb");
+        }
+        else
+        {
+            std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(realPath));
+            int pos = realPath.find_last_of("/");
+            if (pos)
+            {
+                _fileOutputPath = realPath.substr(0, pos);
+                _fileOutputPath.append("/");
+                _fileOutputPath.append(filenameNoExt);
+                _fileOutputPath.append(".gpb");
+            }
+        }
+    }
+}
+
 std::string EncoderArguments::getRealPath(const std::string& filepath)
 {
     char path[PATH_MAX + 1]; /* not sure about the "+ 1" */
@@ -362,4 +435,79 @@ void EncoderArguments::replace_char(char* str, char oldChar, char newChar)
     }
 }
 
+std::string concat(const std::string& a, const char* b)
+{
+    std::string str(a);
+    str.append(b);
+    return str;
+}
+
+
+void unittestsEncoderArguments()
+{
+    std::string dir = EncoderArguments::getRealPath(".");
+    std::string exePath = EncoderArguments::getRealPath(".");
+    exePath.append("/gameplay-encoder.exe");
+    const char* exe = exePath.c_str();
+    {
+        const char* argv[] = {exe, "-groupAnimations", "root", "movements", "C:\\Git\\gaming\\GamePlay\\gameplay-encoder\\res\\seymour.dae"};
+        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
+        assert(equals(args.getAnimationId("root"), ("movements")));
+        assert(equals(args.getGroupAnimationNodeId()[0], ("root")));
+        assert(equals(args.getOutputFilePath(), "C:/Git/gaming/GamePlay/gameplay-encoder/res/seymour.gpb"));
+    }
+    {
+        // Test with only input file name (relative)
+        const char* argv[] = {exe, "input.dae"};
+        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
+        assert(equals(args.getFilePath(), concat(dir, "/input.dae")));
+        assert(equals(args.getOutputFilePath(), concat(dir, "/input.gpb")));
+        equals(args.getOutputDirPath(), dir);
+    }
+    {
+        // Test specifying a relative output path
+        const char* argv[] = {exe, "input.dae", "output.gpb"};
+        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
+        assert(equals(args.getFilePath(), concat(dir, "/input.dae")));
+        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
+    }
+    {
+        // Test specifying a relative output path
+        const char* argv[] = {exe, "input.fbx", "output.gpb"};
+        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
+        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
+        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
+    }
+    {
+        // Test specifying a relative output path to a directory
+        const char* argv[] = {exe, "input.dae", "stuff/output.gpb"};
+        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
+        assert(equals(args.getFilePath(), concat(dir, "/input.dae")));
+        assert(equals(args.getOutputFilePath(), concat(dir, "/stuff/output.gpb")));
+    }
+    {
+        // Test parsing some arguments
+        const char* argv[] = {exe, "-dae", "collada.dae", "-t", "input.dae", "output.gpb"};
+        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
+        assert(equals(args.getFilePath(), concat(dir, "/input.dae")));
+        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
+        assert(args.textOutputEnabled());
+        //assert(equals(args.getDAEOutputPath(), concat(dir, "/collada.dae")));
+    }
+    {
+        // Test output file with no file extension
+        const char* argv[] = {exe, "input.dae", "output"};
+        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
+        assert(equals(args.getFilePath(), concat(dir, "/input.dae")));
+        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
+    }
+    {
+        // Test output file with wrong file extension
+        const char* argv[] = {exe, "input.dae", "output.dae"};
+        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
+        assert(equals(args.getFilePath(), concat(dir, "/input.dae")));
+        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
+    }
+}
+
 }

+ 21 - 2
gameplay-encoder/src/EncoderArguments.h

@@ -57,8 +57,15 @@ public:
 
     /**
      * Returns the output path/folder.
+     * Example: "C:/dir"
      */
-    std::string getOutputPath() const;
+    std::string getOutputDirPath() const;
+
+    /**
+     * Returns the output file path.
+     * Example: "C:/dir/scene.gpb"
+     */
+    std::string getOutputFilePath() const;
 
     const std::vector<std::string>& getGroupAnimationNodeId() const;
     const std::vector<std::string>& getGroupAnimationAnimationId() const;
@@ -92,6 +99,9 @@ public:
     const char* getNodeId() const;
     unsigned int getFontSize() const;
 
+
+    static std::string getRealPath(const std::string& filepath);
+
 private:
 
     /**
@@ -103,7 +113,13 @@ private:
      */
     void readOption(const std::vector<std::string>& options, size_t *index);
 
-    static std::string getRealPath(const std::string& filepath);
+    void setInputfilePath(const std::string& inputPath);
+
+    /**
+     * Sets the output file path that the encoder will write to.
+     */
+    void setOutputfilePath(const std::string& outputPath);
+    
 
     /**
      * Replaces all instance of oldChar with newChar in str.
@@ -113,6 +129,7 @@ private:
 private:
     
     std::string _filePath;
+    std::string _fileOutputPath;
     std::string _nodeId;
     std::string _daeOutputPath;
 
@@ -129,6 +146,8 @@ private:
 
 };
 
+void unittestsEncoderArguments();
+
 }
 
 #endif

Diferenças do arquivo suprimidas por serem muito extensas
+ 208 - 169
gameplay-encoder/src/FBXSceneEncoder.cpp


+ 18 - 16
gameplay-encoder/src/FBXSceneEncoder.h

@@ -3,6 +3,8 @@
 
 #ifdef USE_FBX
 
+#define FBXSDK_NEW_API
+
 #include <iostream>
 #include <list>
 #include <vector>
@@ -65,7 +67,7 @@ private:
      * 
      * @param fbxScene The FBX scene to load.
      */
-    void loadScene(KFbxScene* fbxScene);
+    void loadScene(FbxScene* fbxScene);
 
     /**
      * Loads all of the animatiosn from the given FBX scene.
@@ -73,7 +75,7 @@ private:
      * @param fbxScene The scene to load animations from.
      * @param arguments The command line arguments passed to the encoder.
      */
-    void loadAnimations(KFbxScene* fbxScene, const EncoderArguments& arguments);
+    void loadAnimations(FbxScene* fbxScene, const EncoderArguments& arguments);
 
     /**
      * Loads the animations from the given FBX animation layer recursively starting from fbxNode.
@@ -82,7 +84,7 @@ private:
      * @param fbxNode The node to start loading animations from.
      * @param arguments The command line arguments passed to the encoder.
      */
-    void loadAnimationLayer(KFbxAnimLayer* fbxAnimLayer, KFbxNode* fbxNode, const EncoderArguments& arguments);
+    void loadAnimationLayer(FbxAnimLayer* fbxAnimLayer, FbxNode* fbxNode, const EncoderArguments& arguments);
 
     /**
      * Loads animation channels from the given node and adds the channels to the given animation.
@@ -91,14 +93,14 @@ private:
      * @param fbxNode The node to load animation channels from.
      * @param animation The animation to add the channels to.
      */
-    void loadAnimationChannels(KFbxAnimLayer* pAnimLayer, KFbxNode* fbxNode, Animation* animation);
+    void loadAnimationChannels(FbxAnimLayer* pAnimLayer, FbxNode* fbxNode, Animation* animation);
 
     /**
      * Loads the bind shape for all mesh skins that have be loaded so far.
      * 
      * @param fbxScene The FBX scene to read the bind shapes from.
      */
-    void loadBindShapes(KFbxScene* fbxScene);
+    void loadBindShapes(FbxScene* fbxScene);
 
     /**
      * Loads the camera from the given FBX node and adds to it to the given GamePlay node.
@@ -106,7 +108,7 @@ private:
      * @param fbxNode The FBX node to load from.
      * @param node The GamePlay node to add to.
      */
-    void loadCamera(KFbxNode* fbxNode, Node* node);
+    void loadCamera(FbxNode* fbxNode, Node* node);
 
     /**
      * Loads the light from the given FBX node and adds to it to the given GamePlay node.
@@ -114,7 +116,7 @@ private:
      * @param fbxNode The FBX node to load from.
      * @param node The GamePlay node to add to.
      */
-    void loadLight(KFbxNode* fbxNode, Node* node);
+    void loadLight(FbxNode* fbxNode, Node* node);
     
     /**
      * Loads the model from the given FBX node and adds to it to the given GamePlay node.
@@ -122,7 +124,7 @@ private:
      * @param fbxNode The FBX node to load from.
      * @param node The GamePlay node to add to.
      */
-    void loadModel(KFbxNode* fbxNode, Node* node);
+    void loadModel(FbxNode* fbxNode, Node* node);
 
     /**
      * Loads the mesh skin from the given FBX mesh and adds it to the given GamePlay model.
@@ -130,7 +132,7 @@ private:
      * @param fbxMesh The FBX mesh to load the skin from.
      * @param model The model to add the skin to.
      */
-    void loadSkin(KFbxMesh* fbxMesh, Model* model);
+    void loadSkin(FbxMesh* fbxMesh, Model* model);
     
     /**
      * Loads the FBX Node and creates a GamePlay Node.
@@ -139,7 +141,7 @@ private:
      * 
      * @return The newly created Node or NULL if the node could not be loaded.
      */
-    Node* loadNode(KFbxNode* fbxNode);
+    Node* loadNode(FbxNode* fbxNode);
     
     /**
      * Loads the FbxMesh and returns a GamePlay mesh.
@@ -149,7 +151,7 @@ private:
      * 
      * @return The GamePlay mesh that was loaded from the FBX Mesh.
      */
-    Mesh* loadMesh(KFbxMesh* fbxMesh);
+    Mesh* loadMesh(FbxMesh* fbxMesh);
 
     /**
      * Gets the Mesh that was saved with the given ID. Returns NULL if a match is not found.
@@ -158,7 +160,7 @@ private:
      * 
      * @return The mesh that was saved with the ID or NULL if none was found.
      */
-    Mesh* getMesh(size_t meshId);
+    Mesh* getMesh(FbxUInt64 meshId);
 
     /**
      * Saves the Mesh with the given id.
@@ -166,7 +168,7 @@ private:
      * @param meshId The ID of the FbxMesh to use as a key.
      * @param mesh The mesh to save.
      */
-    void saveMesh(size_t meshId, Mesh* mesh);
+    void saveMesh(FbxUInt64 meshId, Mesh* mesh);
     
     /**
      * Prints a message.
@@ -181,14 +183,14 @@ private:
      * @param fbxNode The FBX node to get the transfrom data from
      * @param node The GamePlay Node to copy the transform to.
      */
-    void transformNode(KFbxNode* fbxNode, Node* node);
+    void transformNode(FbxNode* fbxNode, Node* node);
 
     /**
      * Recursively triangules the meshes starting from the given node.
      * 
      * @param fbxNode The node to start triangulating from.
      */
-    static void triangulateRecursive(KFbxNode* fbxNode);
+    static void triangulateRecursive(FbxNode* fbxNode);
 
     /**
      * Prints a warning message.
@@ -210,7 +212,7 @@ private:
     /**
      * The collection of meshes for the purpose of making sure that the same model is not loaded twice. (Mesh instancing)
      */
-    std::map<size_t, Mesh*> _meshes;
+    std::map<FbxUInt64, Mesh*> _meshes;
 
     /**
      * The animation that channels should be added to it the user is using the -groupAnimation command line argument. May be NULL.

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

@@ -62,7 +62,7 @@ void GPBDecoder::readRefs()
 {
     fprintf(_outFile, "<RefTable>\n");
     // read number of refs
-    unsigned int refCount;
+    unsigned int refCount = 0;
     assert(read(&refCount));
     for (size_t i = 0; i < refCount; ++i)
     {
@@ -74,7 +74,7 @@ void GPBDecoder::readRefs()
 void GPBDecoder::readRef()
 {
     std::string xref = readString(_file);
-    unsigned int type, offset;
+    unsigned int type = 0, offset = 0;
     assert(read(&type));
     assert(read(&offset));
     

+ 35 - 6
gameplay-encoder/src/GPBFile.cpp

@@ -29,16 +29,33 @@ GPBFile* GPBFile::getInstance()
     return __instance;
 }
 
-void GPBFile::saveBinary(const std::string& filepath)
+bool GPBFile::saveBinary(const std::string& filepath)
 {
     _file = fopen(filepath.c_str(), "w+b");
+    if (!_file)
+    {
+        return false;
+    }
+    size_t n = 0;
 
     // identifier
     char identifier[] = { '«', 'G', 'P', 'B', '»', '\r', '\n', '\x1A', '\n' };
-    fwrite(identifier, 1, sizeof(identifier), _file);
+    n = fwrite(identifier, 1, sizeof(identifier), _file);
+    if (n != sizeof(identifier))
+    {
+        fclose(_file);
+        return false;
+    }
 
     // version
-    fwrite(GPB_VERSION, 1, sizeof(GPB_VERSION), _file);
+    n = fwrite(GPB_VERSION, 1, sizeof(GPB_VERSION), _file);
+    if (n != sizeof(GPB_VERSION))
+    {
+        fclose(_file);
+        return false;
+    }
+
+    // TODO: Check for errors on all file writing.
 
     // write refs
     _refTable.writeBinary(_file);
@@ -60,13 +77,24 @@ void GPBFile::saveBinary(const std::string& filepath)
     _refTable.updateOffsets(_file);
     
     fclose(_file);
+    return true;
 }
 
-void GPBFile::saveText(const std::string& filepath)
+bool GPBFile::saveText(const std::string& filepath)
 {
     _file = fopen(filepath.c_str(), "w");
+    if (!_file)
+    {
+        return false;
+    }
+
+    if (fprintf(_file, "<root>\n") <= 0)
+    {
+        fclose(_file);
+        return false;
+    }
 
-    fprintf(_file, "<root>\n");
+    // TODO: Check for errors on all file writing.
 
     // write refs
     _refTable.writeText(_file);
@@ -86,6 +114,7 @@ void GPBFile::saveText(const std::string& filepath)
     fprintf(_file, "</root>");
 
     fclose(_file);
+    return true;
 }
 
 void GPBFile::add(Object* obj)
@@ -400,7 +429,7 @@ void GPBFile::decomposeTransformAnimationChannel(Animation* animation, const Ani
 
 static bool isAlmostOne(float value)
 {
-    return std::abs(value - 1.0f) < EPSILON;
+    return std::fabs(value - 1.0f) < EPSILON;
 }
 
 }

+ 6 - 2
gameplay-encoder/src/GPBFile.h

@@ -49,15 +49,19 @@ public:
      * Saves the GPBFile as a binary file at filepath.
      *
      * @param filepath The file name and path to save to.
+     * 
+     * @return True if successful, false if error.
      */
-    void saveBinary(const std::string& filepath);
+    bool saveBinary(const std::string& filepath);
 
     /**
      * Saves the GPBFile as a text file at filepath. Useful for debugging.
      *
      * @param filepath The file name and path to save to.
+     * 
+     * @return True if successful, false if error.
      */
-    void saveText(const std::string& filepath);
+    bool saveText(const std::string& filepath);
     
     void add(Object* obj);
     void addScene(Scene* scene);

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

@@ -132,7 +132,7 @@ void Node::generateHeightmap()
         {
             DEBUGPRINT_VARG("> Generating heightmap for node: %s\n", getId().c_str());
 
-            std::string heightmapFilename(EncoderArguments::getInstance()->getOutputPath());
+            std::string heightmapFilename(EncoderArguments::getInstance()->getOutputDirPath());
             heightmapFilename += "/heightmap_";
             heightmapFilename += getId();
             heightmapFilename += ".png";

+ 1 - 0
gameplay-encoder/src/Scene.cpp

@@ -9,6 +9,7 @@ Scene::Scene(void) : _cameraNode(NULL)
     _ambientColor[0] = 0.0f;
     _ambientColor[1] = 0.0f;
     _ambientColor[2] = 0.0f;
+    setId("scene");
 }
 
 Scene::~Scene(void)

+ 11 - 1
gameplay-encoder/src/StringUtil.cpp

@@ -77,11 +77,21 @@ bool endsWith(const char* str, const char* suffix, bool ignoreCase)
     return true;
 }
 
+bool endsWith(const std::string& str, const char* suffix, bool ignoreCase)
+{
+    return endsWith(str.c_str(), suffix, ignoreCase);
+}
+
 bool equals(const std::string& a, const char* b)
 {
     return (a.compare(b) == 0);
 }
 
+bool equals(const std::string& a, const std::string& b)
+{
+    return (a.compare(b) == 0);
+}
+
 bool equalsIgnoreCase(const std::string& a, const char* b)
 {
     size_t bLength = strlen(b);
@@ -103,7 +113,7 @@ std::string getFilenameFromFilePath(const std::string& filepath)
 {
     if (filepath.find_last_of("/") != std::string::npos)
     {
-        return filepath.substr(filepath.find_last_of("/")+1);
+        return filepath.substr(filepath.find_last_of("/") + 1);
     }
     return "";
 }

+ 12 - 0
gameplay-encoder/src/StringUtil.h

@@ -4,19 +4,31 @@
 namespace gameplay
 {
 
+/**
+ * Returns true if the given string starts with the prefix.
+ */
 bool startsWith(const char* str, const char* prefix, bool ignoreCase = true);
+
+/**
+ * Returns true if the given string ends with the suffix.
+ */
 bool endsWith(const char* str, const char* suffix, bool ignoreCase = true);
+bool endsWith(const std::string& str, const char* suffix, bool ignoreCase = true);
 
 /**
  * Return true if the strings are equal. Case sensitive.
  */
 bool equals(const std::string& a, const char* b);
+bool equals(const std::string& a, const std::string& b);
 
 /**
  * Returns true if the strings are equal. Case insensitive.
  */
 bool equalsIgnoreCase(const std::string& a, const char* b);
 
+/**
+ * Returns the filename from the given real path.
+ */
 std::string getFilenameFromFilePath(const std::string& filepath);
 
 std::string getFilenameNoExt(const std::string& filename);

+ 3 - 9
gameplay-encoder/src/TTFFontEncoder.cpp

@@ -5,7 +5,7 @@
 namespace gameplay
 {
 
-void drawBitmap(unsigned char* dstBitmap, int x, int y, int dstWidth, unsigned char* srcBitmap, int srcWidth, int srcHeight)
+static void drawBitmap(unsigned char* dstBitmap, int x, int y, int dstWidth, unsigned char* srcBitmap, int srcWidth, int srcHeight)
 {
     // offset dst bitmap by x,y.
     dstBitmap +=  (x + (y * dstWidth));
@@ -18,17 +18,12 @@ void drawBitmap(unsigned char* dstBitmap, int x, int y, int dstWidth, unsigned c
     }
 }
 
-void writeUint(FILE* fp, unsigned int i)
+static void writeUint(FILE* fp, unsigned int i)
 {
     fwrite(&i, sizeof(unsigned int), 1, fp);
 }
 
-void writeFloat(FILE* fp, float f)
-{
-    fwrite(&f, sizeof(float), 1, fp);
-}
-
-void writeString(FILE* fp, const char* str)
+static void writeString(FILE* fp, const char* str)
 {
     unsigned int len = strlen(str);
     fwrite(&len, sizeof(unsigned int), 1, fp);
@@ -40,7 +35,6 @@ void writeString(FILE* fp, const char* str)
 
 int writeFont(const char* filename, unsigned int fontSize, const char* id, bool fontpreview = false)
 {
- 
     Glyph glyphArray[END_INDEX - START_INDEX];
     
     // Initialize freetype library.

+ 0 - 8
gameplay-encoder/src/TTFFontEncoder.h

@@ -18,14 +18,6 @@ public:
     float uvCoords[4];
 };
 
-void drawBitmap(unsigned char* dstBitmap, int x, int y, int dstWidth, unsigned char* srcBitmap, int srcWidth, int srcHeight);
-
-void writeUint(FILE* fp, unsigned int i);
-
-void writeFloat(FILE* fp, float f);
-
-void writeString(FILE* fp, const char* str);
-
 int writeFont(const char* filename, unsigned int fontSize, const char* id, bool fontpreview);
 
 }

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

@@ -7,7 +7,7 @@
 
 using namespace gameplay;
 
-std::string getFileName(const std::string& filepath)
+static std::string getFileName(const std::string& filepath)
 {
     size_t index1 = filepath.find_last_of('\\');
     size_t index2 = filepath.find_last_of('/');

+ 19 - 19
gameplay-template/gameplay-template.xcodeproj/project.pbxproj

@@ -43,7 +43,7 @@
 
 /* Begin PBXFileReference section */
 		42438B521491AD2000D218B8 /* libgameplay.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgameplay.a; path = "~/Library/Developer/Xcode/DerivedData/gameplay-exiunaubxxjndaapmcqkaoeboiob/Build/Products/Debug/libgameplay.a"; sourceTree = "<group>"; };
-		42C932BC1491A0DB0098216A /* TEMPLATE_PROJECT-MacOSX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TEMPLATE_PROJECT-MacOSX.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		42C932BC1491A0DB0098216A /* TEMPLATE_PROJECT-macosx.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TEMPLATE_PROJECT-macosx.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		42C932C01491A0DB0098216A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
 		42C932ED1491A4CB0098216A /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = "<group>"; };
 		42C932EF1491A5160098216A /* TemplateGame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TemplateGame.cpp; path = src/TemplateGame.cpp; sourceTree = SOURCE_ROOT; };
@@ -60,7 +60,7 @@
 		42C9332A1491A7390098216A /* libpng.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpng.a; path = "GAMEPLAY_PATH/external-deps/libpng/lib/macosx/libpng.a"; sourceTree = "<group>"; };
 		42C9332D1491A7810098216A /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
 		5B61611214CCC2200073B857 /* TEMPLATE_PROJECT-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "TEMPLATE_PROJECT-macosx.plist"; sourceTree = "<group>"; };
-		5B61612C14CCC24C0073B857 /* TEMPLATE_PROJECT-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TEMPLATE_PROJECT-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		5B61612C14CCC24C0073B857 /* TEMPLATE_PROJECT-ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TEMPLATE_PROJECT-ios.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		5B61612E14CCC24D0073B857 /* TEMPLATE_PROJECT-ios.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TEMPLATE_PROJECT-ios.plist"; sourceTree = "<group>"; };
 		5BAF2067152F2DDD003E2AC3 /* CoreMotion.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMotion.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk/System/Library/Frameworks/CoreMotion.framework; sourceTree = DEVELOPER_DIR; };
 		5BAF2068152F2DDD003E2AC3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
@@ -133,8 +133,8 @@
 		42C932BD1491A0DB0098216A /* Products */ = {
 			isa = PBXGroup;
 			children = (
-				42C932BC1491A0DB0098216A /* TEMPLATE_PROJECT-MacOSX.app */,
-				5B61612C14CCC24C0073B857 /* TEMPLATE_PROJECT-iOS.app */,
+				42C932BC1491A0DB0098216A /* TEMPLATE_PROJECT-macosx.app */,
+				5B61612C14CCC24C0073B857 /* TEMPLATE_PROJECT-ios.app */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -209,9 +209,9 @@
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
-		42C932BB1491A0DB0098216A /* TEMPLATE_PROJECT-MacOSX */ = {
+		42C932BB1491A0DB0098216A /* TEMPLATE_PROJECT-macosx */ = {
 			isa = PBXNativeTarget;
-			buildConfigurationList = 42C932DA1491A0DB0098216A /* Build configuration list for PBXNativeTarget "TEMPLATE_PROJECT-MacOSX" */;
+			buildConfigurationList = 42C932DA1491A0DB0098216A /* Build configuration list for PBXNativeTarget "TEMPLATE_PROJECT-macosx" */;
 			buildPhases = (
 				42C932B81491A0DB0098216A /* Sources */,
 				42C932B91491A0DB0098216A /* Frameworks */,
@@ -223,28 +223,28 @@
 			);
 			dependencies = (
 			);
-			name = "TEMPLATE_PROJECT-MacOSX";
+			name = "TEMPLATE_PROJECT-macosx";
 			productName = TEMPLATE_PROJECT;
-			productReference = 42C932BC1491A0DB0098216A /* TEMPLATE_PROJECT-MacOSX.app */;
+			productReference = 42C932BC1491A0DB0098216A /* TEMPLATE_PROJECT-macosx.app */;
 			productType = "com.apple.product-type.application";
 		};
-		5B61611414CCC24C0073B857 /* TEMPLATE_PROJECT-iOS */ = {
+		5B61611414CCC24C0073B857 /* TEMPLATE_PROJECT-ios */ = {
 			isa = PBXNativeTarget;
-			buildConfigurationList = 5B61612914CCC24C0073B857 /* Build configuration list for PBXNativeTarget "TEMPLATE_PROJECT-iOS" */;
+			buildConfigurationList = 5B61612914CCC24C0073B857 /* Build configuration list for PBXNativeTarget "TEMPLATE_PROJECT-ios" */;
 			buildPhases = (
 				5B61611514CCC24C0073B857 /* Sources */,
 				5B61611714CCC24C0073B857 /* Frameworks */,
 				5B61612414CCC24C0073B857 /* ShellScript */,
 				5B61612514CCC24C0073B857 /* Resources */,
-				5BAF20A3152F2FCE003E2AC3 /* Copy Gameplay Reousrces Run Script */,
+				5BAF20A3152F2FCE003E2AC3 /* Copy Gameplay Resources Run Script */,
 			);
 			buildRules = (
 			);
 			dependencies = (
 			);
-			name = "TEMPLATE_PROJECT-iOS";
+			name = "TEMPLATE_PROJECT-ios";
 			productName = TEMPLATE_PROJECT;
-			productReference = 5B61612C14CCC24C0073B857 /* TEMPLATE_PROJECT-iOS.app */;
+			productReference = 5B61612C14CCC24C0073B857 /* TEMPLATE_PROJECT-ios.app */;
 			productType = "com.apple.product-type.application";
 		};
 /* End PBXNativeTarget section */
@@ -267,8 +267,8 @@
 			projectDirPath = "";
 			projectRoot = "";
 			targets = (
-				42C932BB1491A0DB0098216A /* TEMPLATE_PROJECT-MacOSX */,
-				5B61611414CCC24C0073B857 /* TEMPLATE_PROJECT-iOS */,
+				42C932BB1491A0DB0098216A /* TEMPLATE_PROJECT-macosx */,
+				5B61611414CCC24C0073B857 /* TEMPLATE_PROJECT-ios */,
 			);
 		};
 /* End PBXProject section */
@@ -321,7 +321,7 @@
 			shellPath = /bin/sh;
 			shellScript = "touch -cm ${SRCROOT}/res";
 		};
-		5BAF20A3152F2FCE003E2AC3 /* Copy Gameplay Reousrces Run Script */ = {
+		5BAF20A3152F2FCE003E2AC3 /* Copy Gameplay Resources Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -333,7 +333,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "cp -rn GAMEPLAY_PATH/gameplay/res/shaders ${SRCROOT}/res\ncp -rn GAMEPLAY_PATH/gameplay/res/logo_powered_white.png ${SRCROOT}/res\ntouch -cm ${SRCROOT}/res";
+			shellScript = "cp -rn GAMEPLAY_PATH/gameplay/res/shaders ${SRCROOT}/res\ncp -rf GAMEPLAY_PATH/gameplay/res/logo_powered_white.png ${SRCROOT}/res\ntouch -cm ${SRCROOT}/res";
 		};
 		5BAF20D7152F30C3003E2AC3 /* Copy Gameplay Resources - Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -544,7 +544,7 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
-		42C932DA1491A0DB0098216A /* Build configuration list for PBXNativeTarget "TEMPLATE_PROJECT-MacOSX" */ = {
+		42C932DA1491A0DB0098216A /* Build configuration list for PBXNativeTarget "TEMPLATE_PROJECT-macosx" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
 				42C932DB1491A0DB0098216A /* Debug */,
@@ -553,7 +553,7 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
-		5B61612914CCC24C0073B857 /* Build configuration list for PBXNativeTarget "TEMPLATE_PROJECT-iOS" */ = {
+		5B61612914CCC24C0073B857 /* Build configuration list for PBXNativeTarget "TEMPLATE_PROJECT-ios" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
 				5B61612A14CCC24C0073B857 /* Debug */,

+ 39 - 82
gameplay.doxyfile

@@ -1,4 +1,4 @@
-# Doxyfile 1.8.0
+# Doxyfile 1.7.5.1
 
 # This file describes the settings to be used by the documentation system
 # doxygen (www.doxygen.org) for a project
@@ -32,7 +32,7 @@ PROJECT_NAME           = gameplay
 # This could be handy for archiving the generated documentation or 
 # if some version control system is used.
 
-PROJECT_NUMBER         = 1.2.0
+PROJECT_NUMBER         = 1.3.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description 
 # for a project that appears at the top of each page and should give viewer 
@@ -205,13 +205,6 @@ TAB_SIZE               = 8
 
 ALIASES                = 
 
-# This tag can be used to specify a number of word-keyword mappings (TCL only). 
-# A mapping has the form "name=value". For example adding 
-# "class=itcl::class" will allow you to use the command class in the 
-# itcl::class meaning.
-
-TCL_SUBST              = 
-
 # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
 # sources only. Doxygen will then generate output that is more tailored for C. 
 # For instance, some of the names that are used will be different. The list 
@@ -250,15 +243,6 @@ OPTIMIZE_OUTPUT_VHDL   = NO
 
 EXTENSION_MAPPING      = 
 
-# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all 
-# comments according to the Markdown format, which allows for more readable 
-# documentation. See http://daringfireball.net/projects/markdown/ for details. 
-# The output of markdown processing is further processed by doxygen, so you 
-# can mix doxygen, HTML, and XML commands with Markdown formatting. 
-# Disable only in case of backward compatibilities issues.
-
-MARKDOWN_SUPPORT       = YES
-
 # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want 
 # to include (a tag file for) the STL sources as input, then you should 
 # set this tag to YES in order to let doxygen match functions declarations and 
@@ -341,21 +325,10 @@ TYPEDEF_HIDES_STRUCT   = NO
 # a logarithmic scale so increasing the size by one will roughly double the 
 # memory usage. The cache size is given by this formula: 
 # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, 
-# corresponding to a cache size of 2^16 = 65536 symbols.
+# corresponding to a cache size of 2^16 = 65536 symbols
 
 SYMBOL_CACHE_SIZE      = 0
 
-# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be 
-# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given 
-# their name and scope. Since this can be an expensive process and often the 
-# same symbol appear multiple times in the code, doxygen keeps a cache of 
-# pre-resolved symbols. If the cache is too small doxygen will become slower. 
-# If the cache is too large, memory is wasted. The cache size is given by this 
-# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, 
-# corresponding to a cache size of 2^16 = 65536 symbols.
-
-LOOKUP_CACHE_SIZE      = 0
-
 #---------------------------------------------------------------------------
 # Build related configuration options
 #---------------------------------------------------------------------------
@@ -372,11 +345,6 @@ EXTRACT_ALL            = NO
 
 EXTRACT_PRIVATE        = NO
 
-# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
-# scope will be included in the documentation.
-
-EXTRACT_PACKAGE        = NO
-
 # If the EXTRACT_STATIC tag is set to YES all static members of a file 
 # will be included in the documentation.
 
@@ -606,8 +574,7 @@ LAYOUT_FILE            =
 # .bib extension is automatically appended if omitted. Using this command 
 # requires the bibtex tool to be installed. See also 
 # http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style 
-# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this 
-# feature you need bibtex and perl available in the search path.
+# of the bibliography can be controlled using LATEX_BIB_STYLE.
 
 CITE_BIB_FILES         = 
 
@@ -698,15 +665,14 @@ FILE_PATTERNS          = *.h \
 
 RECURSIVE              = NO
 
-# The EXCLUDE tag can be used to specify files and/or directories that should be 
+# The EXCLUDE tag can be used to specify files and/or directories that should 
 # excluded from the INPUT source files. This way you can easily exclude a 
 # subdirectory from a directory tree whose root is specified with the INPUT tag. 
-# Note that relative paths are relative to the directory from which doxygen is 
-# run.
+# Note that relative paths are relative to directory from which doxygen is run.
 
 EXCLUDE                = 
 
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or 
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
 # directories that are symbolic links (a Unix file system feature) are excluded 
 # from the input.
 
@@ -891,7 +857,7 @@ HTML_FILE_EXTENSION    = .html
 # standard header. Note that when using a custom header you are responsible  
 # for the proper inclusion of any scripts and style sheets that doxygen 
 # needs, which is dependent on the configuration options used. 
-# It is advised to generate a default header using "doxygen -w html 
+# It is adviced to generate a default header using "doxygen -w html 
 # header.html footer.html stylesheet.css YourConfigFile" and then modify 
 # that header. Note that the header is subject to change so you typically 
 # have to redo this when upgrading to a newer version of doxygen or when 
@@ -910,7 +876,7 @@ HTML_FOOTER            =
 # fine-tune the look of the HTML output. If the tag is left blank doxygen 
 # will generate a default style sheet. Note that doxygen will try to copy 
 # the style sheet file to the HTML output directory, so don't put your own 
-# style sheet in the HTML output directory as well, or it will be erased!
+# stylesheet in the HTML output directory as well, or it will be erased!
 
 HTML_STYLESHEET        = 
 
@@ -924,7 +890,7 @@ HTML_STYLESHEET        =
 HTML_EXTRA_FILES       = 
 
 # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. 
-# Doxygen will adjust the colors in the style sheet and background images 
+# Doxygen will adjust the colors in the stylesheet and background images 
 # according to this color. Hue is specified as an angle on a colorwheel, 
 # see http://en.wikipedia.org/wiki/Hue for more information. 
 # For instance the value 0 represents red, 60 is yellow, 120 is green, 
@@ -1119,33 +1085,29 @@ GENERATE_ECLIPSEHELP   = NO
 
 ECLIPSE_DOC_ID         = org.doxygen.Project
 
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) 
-# at top of each HTML page. The value NO (the default) enables the index and 
-# the value YES disables it. Since the tabs have the same information as the 
-# navigation tree you can set this option to NO if you already set 
-# GENERATE_TREEVIEW to YES.
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
 
 DISABLE_INDEX          = NO
 
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values 
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML 
+# documentation. Note that a value of 0 will completely suppress the enum 
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 4
+
 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index 
 # structure should be generated to display hierarchical information. 
 # If the tag value is set to YES, a side panel will be generated 
 # containing a tree-like index structure (just like the one that 
 # is generated for HTML Help). For this to work a browser that supports 
 # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). 
-# Windows users are probably better off using the HTML help feature. 
-# Since the tree basically has the same information as the tab index you 
-# could consider to set DISABLE_INDEX to NO when enabling this option.
+# Windows users are probably better off using the HTML help feature.
 
 GENERATE_TREEVIEW      = NO
 
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values 
-# (range [0,1..20]) that doxygen will group on one line in the generated HTML 
-# documentation. Note that a value of 0 will completely suppress the enum 
-# values from appearing in the overview section.
-
-ENUM_VALUES_PER_LINE   = 4
-
 # By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, 
 # and Class Hierarchy pages using a tree view instead of an ordered list.
 
@@ -1182,7 +1144,7 @@ FORMULA_TRANSPARENT    = YES
 # (see http://www.mathjax.org) which uses client side Javascript for the 
 # rendering instead of using prerendered bitmaps. Use this if you do not 
 # have LaTeX installed or if you want to formulas look prettier in the HTML 
-# output. When enabled you may also need to install MathJax separately and 
+# output. When enabled you also need to install MathJax separately and 
 # configure the path to it using the MATHJAX_RELPATH option.
 
 USE_MATHJAX            = NO
@@ -1191,10 +1153,10 @@ USE_MATHJAX            = NO
 # HTML output directory using the MATHJAX_RELPATH option. The destination 
 # directory should contain the MathJax.js script. For instance, if the mathjax 
 # directory is located at the same level as the HTML output directory, then 
-# MATHJAX_RELPATH should be ../mathjax. The default value points to 
-# the MathJax Content Delivery Network so you can quickly see the result without 
-# installing MathJax.  However, it is strongly recommended to install a local 
-# copy of MathJax from http://www.mathjax.org before deployment.
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the 
+# mathjax.org site, so you can quickly see the result without installing 
+# MathJax, but it is strongly recommended to install a local copy of MathJax 
+# before deployment.
 
 MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
 
@@ -1353,7 +1315,7 @@ COMPACT_RTF            = NO
 
 RTF_HYPERLINKS         = NO
 
-# Load style sheet definitions from file. Syntax is similar to doxygen's 
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
 # config file, i.e. a series of assignments. You only have to provide 
 # replacements, missing definitions are set to their default value.
 
@@ -1542,16 +1504,20 @@ SKIP_FUNCTION_MACROS   = YES
 # Configuration::additions related to external references
 #---------------------------------------------------------------------------
 
-# The TAGFILES option can be used to specify one or more tagfiles. For each 
-# tag file the location of the external documentation should be added. The 
-# format of a tag file without this location is as follows: 
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
 #   TAGFILES = file1 file2 ... 
 # Adding location for the tag files is done as follows: 
 #   TAGFILES = file1=loc1 "file2 = loc2" ... 
-# where "loc1" and "loc2" can be relative or absolute paths 
-# or URLs. Note that each tag file must have a unique name (where the name does 
-# NOT include the path). If a tag file is not located in the directory in which 
-# doxygen is run, you must also specify the path to the tagfile here.
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links. 
+# Note that each tag file must have a unique name 
+# (where the name does NOT include the path) 
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
 
 TAGFILES               = 
 
@@ -1642,7 +1608,7 @@ DOT_FONTPATH           =
 # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
 # will generate a graph for each documented class showing the direct and 
 # indirect inheritance relations. Setting this tag to YES will force the 
-# CLASS_DIAGRAMS tag to NO.
+# the CLASS_DIAGRAMS tag to NO.
 
 CLASS_GRAPH            = YES
 
@@ -1664,15 +1630,6 @@ GROUP_GRAPHS           = YES
 
 UML_LOOK               = NO
 
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside 
-# the class node. If there are many fields or methods and many nodes the 
-# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS 
-# threshold limits the number of items for each type to make the size more 
-# managable. Set this to 0 for no limit. Note that the threshold may be 
-# exceeded by 50% before the limit is enforced.
-
-UML_LIMIT_NUM_FIELDS   = 10
-
 # If set to YES, the inheritance and collaboration graphs will show the 
 # relations between templates and their instances.
 

+ 19 - 2
gameplay/.cproject

@@ -29,6 +29,10 @@
 									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
+								<option id="com.qnx.qcc.option.compiler.qccoptions.1968057343" name="QCC Options" superClass="com.qnx.qcc.option.compiler.qccoptions"/>
+								<option id="com.qnx.qcc.option.compiler.ccoptions.1078137668" name="Compiler Options (-Wc,)" superClass="com.qnx.qcc.option.compiler.ccoptions" valueType="stringList">
+									<listOptionValue builtIn="false" value="-mfpu=neon"/>
+								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.997142816" superClass="com.qnx.qcc.inputType.compiler"/>
 							</tool>
 							<tool id="com.qnx.qcc.tool.assembler.1988140188" name="QCC Assembler" superClass="com.qnx.qcc.tool.assembler">
@@ -82,6 +86,9 @@
 									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
+								<option id="com.qnx.qcc.option.compiler.ccoptions.1122311163" name="Compiler Options (-Wc,)" superClass="com.qnx.qcc.option.compiler.ccoptions" valueType="stringList">
+									<listOptionValue builtIn="false" value="-mfpu=neon"/>
+								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.1380846613" superClass="com.qnx.qcc.inputType.compiler"/>
 							</tool>
 							<tool id="com.qnx.qcc.tool.assembler.855139060" name="QCC Assembler" superClass="com.qnx.qcc.tool.assembler">
@@ -134,6 +141,9 @@
 									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
+								<option id="com.qnx.qcc.option.compiler.ccoptions.1956270067" name="Compiler Options (-Wc,)" superClass="com.qnx.qcc.option.compiler.ccoptions" valueType="stringList">
+									<listOptionValue builtIn="false" value="-mfpu=neon"/>
+								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.81809638" superClass="com.qnx.qcc.inputType.compiler"/>
 							</tool>
 							<tool id="com.qnx.qcc.tool.assembler.2145279747" name="QCC Assembler" superClass="com.qnx.qcc.tool.assembler">
@@ -189,6 +199,9 @@
 									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
+								<option id="com.qnx.qcc.option.compiler.ccoptions.47607907" name="Compiler Options (-Wc,)" superClass="com.qnx.qcc.option.compiler.ccoptions" valueType="stringList">
+									<listOptionValue builtIn="false" value="-mfpu=neon"/>
+								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.2007171407" superClass="com.qnx.qcc.inputType.compiler"/>
 							</tool>
 							<tool id="com.qnx.qcc.tool.assembler.1537562121" name="QCC Assembler" superClass="com.qnx.qcc.tool.assembler">
@@ -197,7 +210,7 @@
 							</tool>
 							<tool id="com.qnx.qcc.tool.linker.1976564730" name="QCC Linker" superClass="com.qnx.qcc.tool.linker">
 								<option id="com.qnx.qcc.option.linker.debug.483005272" name="Debug (-g)" superClass="com.qnx.qcc.option.linker.debug" value="true" valueType="boolean"/>
-								<option id="com.qnx.qcc.option.linker.coverage.1325683096" name="Build for Code Coverage (-ftest-coverage -fprofile-arcs -p)" superClass="com.qnx.qcc.option.linker.coverage" value="true" valueType="boolean"/>
+								<option id="com.qnx.qcc.option.linker.coverage.1325683096" name="Build for Code Coverage (-ftest-coverage -fprofile-arcs)" superClass="com.qnx.qcc.option.linker.coverage" value="true" valueType="boolean"/>
 								<option id="com.qnx.qcc.option.linker.langcpp.1336725462" name="C++ (-lang-c++)" superClass="com.qnx.qcc.option.linker.langcpp" value="true" valueType="boolean"/>
 								<option id="com.qnx.qcc.option.linker.security.261244208" name="Enhanced Security (-Wl,-z,relro -Wl,-z,now)" superClass="com.qnx.qcc.option.linker.security" value="true" valueType="boolean"/>
 								<option id="com.qnx.qcc.option.linker.libraryPaths.1333876349" name="Library Paths (-L)" superClass="com.qnx.qcc.option.linker.libraryPaths" valueType="libPaths">
@@ -242,6 +255,7 @@
 									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
+								<option id="com.qnx.qcc.option.compiler.ccoptions.1432778691" name="Compiler Options (-Wc,)" superClass="com.qnx.qcc.option.compiler.ccoptions" valueType="stringList"/>
 								<inputType id="com.qnx.qcc.inputType.compiler.1038720310" superClass="com.qnx.qcc.inputType.compiler"/>
 							</tool>
 							<tool id="com.qnx.qcc.tool.assembler.521146732" name="QCC Assembler" superClass="com.qnx.qcc.tool.assembler">
@@ -295,6 +309,7 @@
 									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
+								<option id="com.qnx.qcc.option.compiler.ccoptions.663337616" name="Compiler Options (-Wc,)" superClass="com.qnx.qcc.option.compiler.ccoptions" valueType="stringList"/>
 								<inputType id="com.qnx.qcc.inputType.compiler.1961855927" superClass="com.qnx.qcc.inputType.compiler"/>
 							</tool>
 							<tool id="com.qnx.qcc.tool.assembler.1089440729" name="QCC Assembler" superClass="com.qnx.qcc.tool.assembler">
@@ -349,6 +364,7 @@
 									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
+								<option id="com.qnx.qcc.option.compiler.ccoptions.346770186" name="Compiler Options (-Wc,)" superClass="com.qnx.qcc.option.compiler.ccoptions" valueType="stringList"/>
 								<inputType id="com.qnx.qcc.inputType.compiler.1658185881" superClass="com.qnx.qcc.inputType.compiler"/>
 							</tool>
 							<tool id="com.qnx.qcc.tool.assembler.746786008" name="QCC Assembler" superClass="com.qnx.qcc.tool.assembler">
@@ -357,7 +373,7 @@
 							</tool>
 							<tool id="com.qnx.qcc.tool.linker.499344619" name="QCC Linker" superClass="com.qnx.qcc.tool.linker">
 								<option id="com.qnx.qcc.option.linker.debug.1036858603" name="Debug (-g)" superClass="com.qnx.qcc.option.linker.debug" value="true" valueType="boolean"/>
-								<option id="com.qnx.qcc.option.linker.coverage.120064975" name="Build for Code Coverage (-ftest-coverage -fprofile-arcs -p)" superClass="com.qnx.qcc.option.linker.coverage" value="true" valueType="boolean"/>
+								<option id="com.qnx.qcc.option.linker.coverage.120064975" name="Build for Code Coverage (-ftest-coverage -fprofile-arcs)" superClass="com.qnx.qcc.option.linker.coverage" value="true" valueType="boolean"/>
 								<option id="com.qnx.qcc.option.linker.langcpp.732448976" name="C++ (-lang-c++)" superClass="com.qnx.qcc.option.linker.langcpp" value="true" valueType="boolean"/>
 								<option id="com.qnx.qcc.option.linker.security.2060919956" name="Enhanced Security (-Wl,-z,relro -Wl,-z,now)" superClass="com.qnx.qcc.option.linker.security" value="true" valueType="boolean"/>
 								<option id="com.qnx.qcc.option.linker.libraryPaths.2023922042" name="Library Paths (-L)" superClass="com.qnx.qcc.option.linker.libraryPaths" valueType="libPaths">
@@ -407,4 +423,5 @@
 		<resource resourceType="PROJECT" workspacePath="/gameplay"/>
 	</storageModule>
 	<storageModule moduleId="com.qnx.tools.ide.qde.core.QNXProjectProperties"/>
+	<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
 </cproject>

+ 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 := AbsoluteLayout.cpp Animation.cpp AnimationClip.cpp AnimationController.cpp AnimationTarget.cpp AnimationValue.cpp AudioBuffer.cpp AudioController.cpp AudioListener.cpp AudioSource.cpp BoundingBox.cpp BoundingSphere.cpp Bundle.cpp Button.cpp Camera.cpp CheckBox.cpp Container.cpp Control.cpp Curve.cpp DebugNew.cpp DepthStencilTarget.cpp Effect.cpp FileSystem.cpp FlowLayout.cpp Font.cpp Form.cpp FrameBuffer.cpp Frustum.cpp Game.cpp gameplay-main-android.cpp Image.cpp Joint.cpp Label.cpp Layout.cpp Light.cpp Material.cpp MaterialParameter.cpp Matrix.cpp Mesh.cpp MeshBatch.cpp MeshPart.cpp MeshSkin.cpp Model.cpp Node.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 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 ThemeStyle.cpp Transform.cpp Vector2.cpp Vector3.cpp Vector4.cpp VertexAttributeBinding.cpp VertexFormat.cpp VerticalLayout.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 Bundle.cpp Button.cpp Camera.cpp CheckBox.cpp Container.cpp Control.cpp Curve.cpp DebugNew.cpp DepthStencilTarget.cpp Effect.cpp FileSystem.cpp FlowLayout.cpp Font.cpp Form.cpp FrameBuffer.cpp Frustum.cpp Game.cpp gameplay-main-android.cpp Image.cpp Joint.cpp Joystick.cpp Label.cpp Layout.cpp Light.cpp Material.cpp MaterialParameter.cpp Matrix.cpp Mesh.cpp MeshBatch.cpp MeshPart.cpp MeshSkin.cpp Model.cpp Node.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 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 ThemeStyle.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" -I"../../external-deps/oggvorbis/include" -I"../../external-deps/openal/include"
 LOCAL_STATIC_LIBRARIES := android_native_app_glue
 

+ 7 - 1
gameplay/gameplay.vcxproj

@@ -48,6 +48,7 @@
     <ClCompile Include="src\gameplay-main-win32.cpp" />
     <ClCompile Include="src\Image.cpp" />
     <ClCompile Include="src\Joint.cpp" />
+    <ClCompile Include="src\Joystick.cpp" />
     <ClCompile Include="src\Label.cpp" />
     <ClCompile Include="src\Layout.cpp" />
     <ClCompile Include="src\Light.cpp" />
@@ -138,11 +139,13 @@
     <ClInclude Include="src\gameplay.h" />
     <ClInclude Include="src\Image.h" />
     <ClInclude Include="src\Joint.h" />
+    <ClInclude Include="src\Joystick.h" />
     <ClInclude Include="src\Keyboard.h" />
     <ClInclude Include="src\Label.h" />
     <ClInclude Include="src\Layout.h" />
     <ClInclude Include="src\Light.h" />
     <ClInclude Include="src\Material.h" />
+    <ClInclude Include="src\MathUtil.h" />
     <ClInclude Include="src\MeshBatch.h" />
     <ClInclude Include="src\Mouse.h" />
     <ClInclude Include="src\Pass.h" />
@@ -230,6 +233,9 @@
     <None Include="src\gameplay-main-ios.mm" />
     <None Include="src\gameplay-main-macosx.mm" />
     <None Include="src\Image.inl" />
+    <None Include="src\MathUtil.inl" />
+    <None Include="src\MathUtilNeon.inl" />
+    <None Include="src\Joystick.inl" />
     <None Include="src\Matrix.inl" />
     <None Include="src\MeshBatch.inl" />
     <None Include="src\Plane.inl" />
@@ -359,4 +365,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 19 - 1
gameplay/gameplay.vcxproj.filters

@@ -279,6 +279,9 @@
     <ClCompile Include="src\FlowLayout.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\Joystick.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -554,6 +557,12 @@
     <ClInclude Include="src\FlowLayout.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\Joystick.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\MathUtil.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\shaders\bumped-specular.vsh">
@@ -652,6 +661,15 @@
     <None Include="res\logo_white.png">
       <Filter>res</Filter>
     </None>
+    <None Include="src\MathUtil.inl">
+      <Filter>src</Filter>
+    </None>
+    <None Include="src\MathUtilNeon.inl">
+      <Filter>src</Filter>
+    </None>
+    <None Include="src\Joystick.inl">
+      <Filter>src</Filter>
+    </None>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\PhysicsFixedConstraint.inl">
@@ -697,4 +715,4 @@
       <Filter>src</Filter>
     </None>
   </ItemGroup>
-</Project>
+</Project>

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

@@ -18,6 +18,12 @@
 		422260D81537790F0011E3AB /* Bundle.h in Headers */ = {isa = PBXBuildFile; fileRef = 422260D51537790F0011E3AB /* Bundle.h */; };
 		422260D91537790F0011E3AB /* Bundle.h in Headers */ = {isa = PBXBuildFile; fileRef = 422260D51537790F0011E3AB /* Bundle.h */; };
 		4234D99E14686C52003031B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4234D99D14686C52003031B3 /* Cocoa.framework */; };
+		4239DDEC157545A1005EA3F6 /* Joystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4239DDE9157545A1005EA3F6 /* Joystick.cpp */; };
+		4239DDED157545A1005EA3F6 /* Joystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4239DDE9157545A1005EA3F6 /* Joystick.cpp */; };
+		4239DDEE157545A1005EA3F6 /* Joystick.h in Headers */ = {isa = PBXBuildFile; fileRef = 4239DDEA157545A1005EA3F6 /* Joystick.h */; };
+		4239DDEF157545A1005EA3F6 /* Joystick.h in Headers */ = {isa = PBXBuildFile; fileRef = 4239DDEA157545A1005EA3F6 /* Joystick.h */; };
+		4239DDF4157545C1005EA3F6 /* MathUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 4239DDF1157545C1005EA3F6 /* MathUtil.h */; };
+		4239DDF5157545C1005EA3F6 /* MathUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 4239DDF1157545C1005EA3F6 /* MathUtil.h */; };
 		4251B131152D049B002F6199 /* ScreenDisplayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 4251B12E152D049B002F6199 /* ScreenDisplayer.h */; };
 		4251B132152D049B002F6199 /* ScreenDisplayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 4251B12E152D049B002F6199 /* ScreenDisplayer.h */; };
 		4251B133152D049B002F6199 /* ThemeStyle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4251B12F152D049B002F6199 /* ThemeStyle.cpp */; };
@@ -399,6 +405,12 @@
 		422260D51537790F0011E3AB /* Bundle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Bundle.h; path = src/Bundle.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; };
+		4239DDE9157545A1005EA3F6 /* Joystick.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Joystick.cpp; path = src/Joystick.cpp; sourceTree = SOURCE_ROOT; };
+		4239DDEA157545A1005EA3F6 /* Joystick.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Joystick.h; path = src/Joystick.h; sourceTree = SOURCE_ROOT; };
+		4239DDEB157545A1005EA3F6 /* Joystick.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Joystick.inl; path = src/Joystick.inl; sourceTree = SOURCE_ROOT; };
+		4239DDF1157545C1005EA3F6 /* MathUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MathUtil.h; path = src/MathUtil.h; sourceTree = SOURCE_ROOT; };
+		4239DDF2157545C1005EA3F6 /* MathUtil.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = MathUtil.inl; path = src/MathUtil.inl; sourceTree = SOURCE_ROOT; };
+		4239DDF3157545C1005EA3F6 /* MathUtilNeon.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = MathUtilNeon.inl; path = src/MathUtilNeon.inl; sourceTree = SOURCE_ROOT; };
 		4251B12E152D049B002F6199 /* ScreenDisplayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScreenDisplayer.h; path = src/ScreenDisplayer.h; sourceTree = SOURCE_ROOT; };
 		4251B12F152D049B002F6199 /* ThemeStyle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ThemeStyle.cpp; path = src/ThemeStyle.cpp; sourceTree = SOURCE_ROOT; };
 		4251B130152D049B002F6199 /* ThemeStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ThemeStyle.h; path = src/ThemeStyle.h; sourceTree = SOURCE_ROOT; };
@@ -761,6 +773,9 @@
 				4208DEE814A4079F00D3C511 /* Image.inl */,
 				42CD0DE4147D8FF50000361E /* Joint.cpp */,
 				42CD0DE5147D8FF50000361E /* Joint.h */,
+				4239DDE9157545A1005EA3F6 /* Joystick.cpp */,
+				4239DDEA157545A1005EA3F6 /* Joystick.h */,
+				4239DDEB157545A1005EA3F6 /* Joystick.inl */,
 				4208DEEB14A407B900D3C511 /* Keyboard.h */,
 				5BD52641150F822A004C9099 /* Label.cpp */,
 				5BD52642150F822A004C9099 /* Label.h */,
@@ -772,6 +787,9 @@
 				42CD0DE9147D8FF50000361E /* Material.h */,
 				42CD0DEA147D8FF50000361E /* MaterialParameter.cpp */,
 				42CD0DEB147D8FF50000361E /* MaterialParameter.h */,
+				4239DDF1157545C1005EA3F6 /* MathUtil.h */,
+				4239DDF2157545C1005EA3F6 /* MathUtil.inl */,
+				4239DDF3157545C1005EA3F6 /* MathUtilNeon.inl */,
 				42CD0DEC147D8FF50000361E /* Matrix.cpp */,
 				42CD0DED147D8FF50000361E /* Matrix.h */,
 				42CD0DEE147D8FF50000361E /* Matrix.inl */,
@@ -1064,6 +1082,8 @@
 				4251B135152D049B002F6199 /* ThemeStyle.h in Headers */,
 				422260D81537790F0011E3AB /* Bundle.h in Headers */,
 				426878AE153F4BB300844500 /* FlowLayout.h in Headers */,
+				4239DDEE157545A1005EA3F6 /* Joystick.h in Headers */,
+				4239DDF4157545C1005EA3F6 /* MathUtil.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1161,6 +1181,8 @@
 				4251B136152D049B002F6199 /* ThemeStyle.h in Headers */,
 				422260D91537790F0011E3AB /* Bundle.h in Headers */,
 				426878AF153F4BB300844500 /* FlowLayout.h in Headers */,
+				4239DDEF157545A1005EA3F6 /* Joystick.h in Headers */,
+				4239DDF5157545C1005EA3F6 /* MathUtil.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1317,6 +1339,7 @@
 				4271C08E15337C8200B89DA7 /* Layout.cpp in Sources */,
 				422260D61537790F0011E3AB /* Bundle.cpp in Sources */,
 				426878AC153F4BB300844500 /* FlowLayout.cpp in Sources */,
+				4239DDEC157545A1005EA3F6 /* Joystick.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1409,6 +1432,7 @@
 				4271C08F15337C8200B89DA7 /* Layout.cpp in Sources */,
 				422260D71537790F0011E3AB /* Bundle.cpp in Sources */,
 				426878AD153F4BB300844500 /* FlowLayout.cpp in Sources */,
+				4239DDED157545A1005EA3F6 /* Joystick.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 41 - 39
gameplay/src/AbsoluteLayout.cpp

@@ -5,54 +5,56 @@
 
 namespace gameplay
 {
-    static AbsoluteLayout* __instance;
 
-    AbsoluteLayout::AbsoluteLayout()
-    {
-    }
+static AbsoluteLayout* __instance;
 
-    AbsoluteLayout::AbsoluteLayout(const AbsoluteLayout& copy)
-    {
-    }
+AbsoluteLayout::AbsoluteLayout()
+{
+}
 
-    AbsoluteLayout::~AbsoluteLayout()
-    {
-    }
+AbsoluteLayout::AbsoluteLayout(const AbsoluteLayout& copy)
+{
+}
+
+AbsoluteLayout::~AbsoluteLayout()
+{
+    __instance = NULL;
+}
 
-    AbsoluteLayout* AbsoluteLayout::create()
+AbsoluteLayout* AbsoluteLayout::create()
+{
+    if (!__instance)
     {
-        if (!__instance)
-        {
-            __instance = new AbsoluteLayout();
-        }
-        else
-        {
-            __instance->addRef();
-        }
-
-        return __instance;
+        __instance = new AbsoluteLayout();
     }
-
-    Layout::Type AbsoluteLayout::getType()
+    else
     {
-        return Layout::LAYOUT_ABSOLUTE;
+        __instance->addRef();
     }
 
-    void AbsoluteLayout::update(const Container* container)
+    return __instance;
+}
+
+Layout::Type AbsoluteLayout::getType()
+{
+    return Layout::LAYOUT_ABSOLUTE;
+}
+
+void AbsoluteLayout::update(const Container* container, const Vector2& offset)
+{
+    GP_ASSERT(container);
+
+    // An AbsoluteLayout does nothing to modify the layout of Controls.
+    std::vector<Control*> controls = container->getControls();
+    unsigned int controlsCount = controls.size();
+    for (unsigned int i = 0; i < controlsCount; i++)
     {
-        // An AbsoluteLayout does nothing to modify the layout of Controls.
-        std::vector<Control*> controls = container->getControls();
-        unsigned int controlsCount = controls.size();
-        for (unsigned int i = 0; i < controlsCount; i++)
-        {
-            Control* control = controls[i];
-
-            align(control, container);
-
-            if (control->isDirty() || control->isContainer())
-            {
-                control->update(container->getClip());
-            }
-        }
+        Control* control = controls[i];
+        GP_ASSERT(control);
+
+        align(control, container);
+        control->update(container, offset);
     }
+}
+
 }

+ 2 - 1
gameplay/src/AbsoluteLayout.h

@@ -40,8 +40,9 @@ protected:
      * It simply calls update() on any control that is dirty.
      *
      * @param container The container to update.
+     * @param offset The layout offset.
      */
-    void update(const Container* container);
+    void update(const Container* container, const Vector2& offset);
 
 private:
     

+ 47 - 15
gameplay/src/Animation.cpp

@@ -21,7 +21,7 @@ Animation::Animation(const char* id, AnimationTarget* target, int propertyId, un
     createChannel(target, propertyId, keyCount, keyTimes, keyValues, type);
     // Release the animation because a newly created animation has a ref count of 1 and the channels hold the ref to animation.
     release();
-    assert(getRefCount() == 1);
+    GP_ASSERT(getRefCount() == 1);
 }
 
 Animation::Animation(const char* id, AnimationTarget* target, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, float* keyInValue, float* keyOutValue, unsigned int type)
@@ -30,7 +30,7 @@ Animation::Animation(const char* id, AnimationTarget* target, int propertyId, un
     createChannel(target, propertyId, keyCount, keyTimes, keyValues, keyInValue, keyOutValue, type);
     // Release the animation because a newly created animation has a ref count of 1 and the channels hold the ref to animation.
     release();
-    assert(getRefCount() == 1);
+    GP_ASSERT(getRefCount() == 1);
 }
 
 Animation::Animation(const char* id)
@@ -45,7 +45,10 @@ Animation::~Animation()
     if (_defaultClip)
     {
         if (_defaultClip->isClipStateBitSet(AnimationClip::CLIP_IS_PLAYING_BIT))
+        {
+            GP_ASSERT(_controller);
             _controller->unschedule(_defaultClip);
+        }
         SAFE_RELEASE(_defaultClip);
     }
 
@@ -56,8 +59,12 @@ Animation::~Animation()
         while (clipIter != _clips->end())
         {   
             AnimationClip* clip = *clipIter;
+            GP_ASSERT(clip);
             if (clip->isClipStateBitSet(AnimationClip::CLIP_IS_PLAYING_BIT))
+            {
+                GP_ASSERT(_controller);
                 _controller->unschedule(clip);
+            }
             SAFE_RELEASE(clip);
             clipIter++;
         }
@@ -69,8 +76,12 @@ 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)
 {
+    GP_ASSERT(_animation);
+    GP_ASSERT(_target);
+    GP_ASSERT(_curve);
+
     // get property component count, and ensure the property exists on the AnimationTarget by getting the property component count.
-    assert(_target->getAnimationPropertyComponentCount(propertyId));
+    GP_ASSERT(_target->getAnimationPropertyComponentCount(propertyId));
     _curve->addRef();
     _target->addChannel(this);
     _animation->addRef();
@@ -79,6 +90,10 @@ Animation::Channel::Channel(Animation* animation, AnimationTarget* target, int p
 Animation::Channel::Channel(const Channel& copy, Animation* animation, AnimationTarget* target)
     : _animation(animation), _target(target), _propertyId(copy._propertyId), _curve(copy._curve), _duration(copy._duration)
 {
+    GP_ASSERT(_curve);
+    GP_ASSERT(_target);
+    GP_ASSERT(_animation);
+
     _curve->addRef();
     _target->addChannel(this);
     _animation->addRef();
@@ -107,16 +122,15 @@ unsigned long Animation::getDuration() const
 
 void Animation::createClips(const char* url)
 {
-    assert(url);
-
     Properties* properties = Properties::create(url);
-    assert(properties);
+    GP_ASSERT(properties);
 
     Properties* pAnimation = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
-    assert(pAnimation);
+    GP_ASSERT(pAnimation);
     
     int frameCount = pAnimation->getInt("frameCount");
-    assert(frameCount > 0);
+    if (frameCount <= 0)
+        GP_ERROR("The animation's frame count must be greater than 0.");
 
     createClips(pAnimation, (unsigned int)frameCount);
 
@@ -171,7 +185,7 @@ void Animation::play(const char* clipId)
     }
     else
     {
-        // Find animation clip.. and play.
+        // Find animation clip and play.
         AnimationClip* clip = findClip(clipId);
         if (clip != NULL)
             clip->play();
@@ -188,7 +202,7 @@ void Animation::stop(const char* clipId)
     }
     else
     {
-        // Find animation clip.. and play.
+        // Find animation clip and play.
         AnimationClip* clip = findClip(clipId);
         if (clip != NULL)
             clip->stop();
@@ -214,6 +228,7 @@ bool Animation::targets(AnimationTarget* target) const
 {
     for (std::vector<Animation::Channel*>::const_iterator itr = _channels.begin(); itr != _channels.end(); ++itr)
     {
+        GP_ASSERT(*itr);
         if ((*itr)->_target == target)
         {
             return true;
@@ -229,7 +244,7 @@ void Animation::createDefaultClip()
 
 void Animation::createClips(Properties* animationProperties, unsigned int frameCount)
 {
-    assert(animationProperties);
+    GP_ASSERT(animationProperties);
     
     Properties* pClip = animationProperties->getNextNamespace();
     
@@ -272,6 +287,7 @@ void Animation::addClip(AnimationClip* clip)
     if (_clips == NULL)
         _clips = new std::vector<AnimationClip*>;
 
+    GP_ASSERT(clip);
     _clips->push_back(clip);
 }
 
@@ -284,9 +300,10 @@ AnimationClip* Animation::findClip(const char* id) const
         for (unsigned int i = 0; i < clipCount; i++)
         {
             clip = _clips->at(i);
+            GP_ASSERT(clip);
             if (clip->_id.compare(id) == 0)
             {
-                return _clips->at(i);
+                return clip;
             }
         }
     }
@@ -295,10 +312,15 @@ AnimationClip* Animation::findClip(const char* id) const
 
 Animation::Channel* Animation::createChannel(AnimationTarget* target, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, unsigned int type)
 {
+    GP_ASSERT(target);
+    GP_ASSERT(keyTimes);
+    GP_ASSERT(keyValues);
+
     unsigned int propertyComponentCount = target->getAnimationPropertyComponentCount(propertyId);
-    assert(propertyComponentCount > 0);
+    GP_ASSERT(propertyComponentCount > 0);
 
     Curve* curve = Curve::create(keyCount, propertyComponentCount);
+    GP_ASSERT(curve);
     if (target->_targetType == AnimationTarget::TRANSFORM)
         setTransformRotationOffset(curve, propertyId);
 
@@ -332,10 +354,15 @@ Animation::Channel* Animation::createChannel(AnimationTarget* target, int proper
 
 Animation::Channel* Animation::createChannel(AnimationTarget* target, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, float* keyInValue, float* keyOutValue, unsigned int type)
 {
+    GP_ASSERT(target);
+    GP_ASSERT(keyTimes);
+    GP_ASSERT(keyValues);
+
     unsigned int propertyComponentCount = target->getAnimationPropertyComponentCount(propertyId);
-    assert(propertyComponentCount > 0);
+    GP_ASSERT(propertyComponentCount > 0);
 
     Curve* curve = Curve::create(keyCount, propertyComponentCount);
+    GP_ASSERT(curve);
     if (target->_targetType == AnimationTarget::TRANSFORM)
         setTransformRotationOffset(curve, propertyId);
     
@@ -369,6 +396,7 @@ Animation::Channel* Animation::createChannel(AnimationTarget* target, int proper
 
 void Animation::addChannel(Channel* channel)
 {
+    GP_ASSERT(channel);
     _channels.push_back(channel);
     
     if (channel->_duration > _duration)
@@ -395,6 +423,8 @@ void Animation::removeChannel(Channel* channel)
 
 void Animation::setTransformRotationOffset(Curve* curve, unsigned int propertyId)
 {
+    GP_ASSERT(curve);
+
     switch (propertyId)
     {
     case Transform::ANIMATE_ROTATE:
@@ -411,13 +441,15 @@ void Animation::setTransformRotationOffset(Curve* curve, unsigned int propertyId
 
 Animation* Animation::clone(Channel* channel, AnimationTarget* target)
 {
+    GP_ASSERT(channel);
+
     Animation* animation = new Animation(getId());
 
     Animation::Channel* channelCopy = new Animation::Channel(*channel, animation, target);
     animation->addChannel(channelCopy);
     // Release the animation because a newly created animation has a ref count of 1 and the channels hold the ref to animation.
     animation->release();
-    assert(animation->getRefCount() == 1);
+    GP_ASSERT(animation->getRefCount() == 1);
     return animation;
 }
 

+ 41 - 9
gameplay/src/AnimationClip.cpp

@@ -14,11 +14,14 @@ AnimationClip::AnimationClip(const char* id, Animation* animation, unsigned long
       _elapsedTime(0), _crossFadeToClip(NULL), _crossFadeOutElapsed(0), _crossFadeOutDuration(0), _blendWeight(1.0f), 
       _beginListeners(NULL), _endListeners(NULL), _listeners(NULL), _listenerItr(NULL)
 {
-    assert(0 <= startTime && startTime <= animation->_duration && 0 <= endTime && endTime <= animation->_duration);
+    GP_ASSERT(_animation);
+    GP_ASSERT(0 <= startTime && startTime <= _animation->_duration && 0 <= endTime && endTime <= _animation->_duration);
     
-    unsigned int channelCount = _animation->_channels.size();    
+    unsigned int channelCount = _animation->_channels.size();
     for (unsigned int i = 0; i < channelCount; i++)
     {
+        GP_ASSERT(_animation->_channels[i]);
+        GP_ASSERT(_animation->_channels[i]->getCurve());
         _values.push_back(new AnimationValue(_animation->_channels[i]->getCurve()->getComponentCount()));
     }
 }
@@ -88,7 +91,7 @@ unsigned long AnimationClip::getElaspedTime() const
 
 void AnimationClip::setRepeatCount(float repeatCount)
 {
-    assert(repeatCount == REPEAT_INDEFINITE || repeatCount > 0.0f);
+    GP_ASSERT(repeatCount == REPEAT_INDEFINITE || repeatCount > 0.0f);
 
     _repeatCount = repeatCount;
 
@@ -180,6 +183,8 @@ void AnimationClip::play()
     else
     {
         setClipStateBit(CLIP_IS_PLAYING_BIT);
+        GP_ASSERT(_animation);
+        GP_ASSERT(_animation->_controller);
         _animation->_controller->schedule(this);
     }
     
@@ -209,7 +214,7 @@ void AnimationClip::pause()
 
 void AnimationClip::crossFade(AnimationClip* clip, unsigned long duration)
 {
-    assert(clip);
+    GP_ASSERT(clip);
 
     // Check if the given clip is fading into this clip.
     // We should reset the clip from fading out, and this one from fading in 
@@ -250,8 +255,8 @@ void AnimationClip::crossFade(AnimationClip* clip, unsigned long duration)
 
 void AnimationClip::addListener(AnimationClip::Listener* listener, unsigned long eventTime)
 {
-    assert(listener);
-    assert(eventTime < _activeDuration);
+    GP_ASSERT(listener);
+    GP_ASSERT(eventTime < _activeDuration);
 
     ListenerEvent* listenerEvent = new ListenerEvent(listener, eventTime);
 
@@ -268,6 +273,7 @@ void AnimationClip::addListener(AnimationClip::Listener* listener, unsigned long
     {
         for (std::list<ListenerEvent*>::iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
         {
+            GP_ASSERT(*itr);
             if (eventTime < (*itr)->_eventTime)
             {
                 itr = _listeners->insert(itr, listenerEvent);
@@ -277,6 +283,7 @@ void AnimationClip::addListener(AnimationClip::Listener* listener, unsigned long
                 if (isClipStateBitSet(CLIP_IS_PLAYING_BIT))
                 {
                     unsigned long currentTime = _elapsedTime % _duration;
+                    GP_ASSERT(**_listenerItr || *_listenerItr == _listeners->end());
                     if ((_speed >= 0.0f && currentTime < eventTime && (*_listenerItr == _listeners->end() || eventTime < (**_listenerItr)->_eventTime)) || 
                         (_speed <= 0 && currentTime > eventTime && (*_listenerItr == _listeners->begin() || eventTime > (**_listenerItr)->_eventTime)))
                         *_listenerItr = itr;
@@ -293,6 +300,7 @@ void AnimationClip::addBeginListener(AnimationClip::Listener* listener)
     if (!_beginListeners)
         _beginListeners = new std::vector<Listener*>;
 
+    GP_ASSERT(listener);
     _beginListeners->push_back(listener);
 }
 
@@ -301,6 +309,7 @@ void AnimationClip::addEndListener(AnimationClip::Listener* listener)
     if (!_endListeners)
         _endListeners = new std::vector<Listener*>;
 
+    GP_ASSERT(listener);
     _endListeners->push_back(listener);
 }
 
@@ -334,6 +343,7 @@ bool AnimationClip::update(unsigned long elapsedTime)
     if (_repeatCount != REPEAT_INDEFINITE && ((_speed >= 0.0f && _elapsedTime >= (long) _activeDuration) || (_speed <= 0.0f && _elapsedTime <= 0L)))
     {
         resetClipStateBit(CLIP_IS_STARTED_BIT);
+        
         if (_speed >= 0.0f)
         {
             // If _duration == 0, we have a "pose". Just set currentTime to 0.
@@ -365,10 +375,16 @@ bool AnimationClip::update(unsigned long elapsedTime)
     // Notify any listeners of Animation events.
     if (_listeners)
     {
+        GP_ASSERT(_listenerItr);
+
         if (_speed >= 0.0f)
         {
             while (*_listenerItr != _listeners->end() && _elapsedTime >= (long) (**_listenerItr)->_eventTime)
             {
+                GP_ASSERT(_listenerItr);
+                GP_ASSERT(**_listenerItr);
+                GP_ASSERT((**_listenerItr)->_listener);
+
                 (**_listenerItr)->_listener->animationEvent(this, Listener::DEFAULT);
                 ++*_listenerItr;
             }
@@ -377,6 +393,10 @@ bool AnimationClip::update(unsigned long elapsedTime)
         {
             while (*_listenerItr != _listeners->begin() && _elapsedTime <= (long) (**_listenerItr)->_eventTime)
             {
+                GP_ASSERT(_listenerItr);
+                GP_ASSERT(**_listenerItr);
+                GP_ASSERT((**_listenerItr)->_listener);
+
                 (**_listenerItr)->_listener->animationEvent(this, Listener::DEFAULT);
                 --*_listenerItr;
             }
@@ -384,19 +404,25 @@ bool AnimationClip::update(unsigned long elapsedTime)
     }
 
     // Add back in start time, and divide by the total animation's duration to get the actual percentage complete
+    GP_ASSERT(_animation);
+    GP_ASSERT(_animation->_duration > 0);
     float percentComplete = (float)(_startTime + currentTime) / (float) _animation->_duration;
     
     if (isClipStateBitSet(CLIP_IS_FADING_OUT_BIT))
     {
+        GP_ASSERT(_crossFadeToClip);
+        GP_ASSERT(_crossFadeOutDuration > 0);
+
         if (isClipStateBitSet(CLIP_IS_FADING_OUT_STARTED_BIT)) // Calculate elapsed time since the fade out begin.
         {
-            _crossFadeOutElapsed = (Game::getGameTime() - _crossFadeToClip->_timeStarted) * abs(_speed); 
+            GP_ASSERT(_crossFadeToClip);
+            _crossFadeOutElapsed = (Game::getGameTime() - _crossFadeToClip->_timeStarted) * fabs(_speed); 
             resetClipStateBit(CLIP_IS_FADING_OUT_STARTED_BIT);
         }
         else
         {
             // continue tracking elapsed time.
-            _crossFadeOutElapsed += elapsedTime * abs(_speed);
+            _crossFadeOutElapsed += elapsedTime * fabs(_speed);
         }
 
         if (_crossFadeOutElapsed < _crossFadeOutDuration)
@@ -436,10 +462,14 @@ bool AnimationClip::update(unsigned long elapsedTime)
     for (unsigned int i = 0; i < channelCount; i++)
     {
         channel = _animation->_channels[i];
+        GP_ASSERT(channel);
         target = channel->_target;
+        GP_ASSERT(target);
         value = _values[i];
+        GP_ASSERT(value);
 
         // Evaluate the point on Curve
+        GP_ASSERT(channel->getCurve());
         channel->getCurve()->evaluate(percentComplete, value->_value);
         // Set the animation value on the target property.
         target->setAnimationPropertyValue(channel->_propertyId, value, _blendWeight);
@@ -474,12 +504,13 @@ void AnimationClip::onBegin()
             *_listenerItr = _listeners->end();
     }
     
-    // Notify begin listeners.. if any.
+    // Notify begin listeners if any.
     if (_beginListeners)
     {
         std::vector<Listener*>::iterator listener = _beginListeners->begin();
         while (listener != _beginListeners->end())
         {
+            GP_ASSERT(*listener);
             (*listener)->animationEvent(this, Listener::BEGIN);
             listener++;
         }
@@ -497,6 +528,7 @@ void AnimationClip::onEnd()
         std::vector<Listener*>::iterator listener = _endListeners->begin();
         while (listener != _endListeners->end())
         {
+            GP_ASSERT(*listener);
             (*listener)->animationEvent(this, Listener::END);
             listener++;
         }

+ 7 - 0
gameplay/src/AnimationController.cpp

@@ -21,6 +21,7 @@ void AnimationController::stopAllAnimations()
     while (clipIter != _runningClips.end())
     {
         AnimationClip* clip = *clipIter;
+        GP_ASSERT(clip);
         clip->stop();
         clipIter++;
     }
@@ -68,6 +69,7 @@ void AnimationController::schedule(AnimationClip* clip)
         _state = RUNNING;
     }
 
+    GP_ASSERT(clip);
     clip->addRef();
     _runningClips.push_back(clip);
 }
@@ -96,11 +98,14 @@ void AnimationController::update(long elapsedTime)
     if (_state != RUNNING)
         return;
 
+    Transform::suspendTransformChanged();
+
     // Loop through running clips and call update() on them.
     std::list<AnimationClip*>::iterator clipIter = _runningClips.begin();
     while (clipIter != _runningClips.end())
     {
         AnimationClip* clip = (*clipIter);
+        GP_ASSERT(clip);
         if (clip->isClipStateBitSet(AnimationClip::CLIP_IS_RESTARTED_BIT))
         {   // If the CLIP_IS_RESTARTED_BIT is set, we should end the clip and 
             // move it from where it is in the running clips list to the back.
@@ -120,6 +125,8 @@ void AnimationController::update(long elapsedTime)
         }
     }
 
+    Transform::resumeTransformChanged();
+
     if (_runningClips.empty())
         _state = IDLE;
 }

+ 141 - 17
gameplay/src/AnimationTarget.cpp

@@ -20,6 +20,8 @@ AnimationTarget::~AnimationTarget()
         while (itr != _animationChannels->end())
         {
             Animation::Channel* channel = (*itr);
+            GP_ASSERT(channel);
+            GP_ASSERT(channel->_animation);
             channel->_animation->removeChannel(channel);
             SAFE_DELETE(channel);
             itr++;
@@ -31,8 +33,8 @@ AnimationTarget::~AnimationTarget()
 
 Animation* AnimationTarget::createAnimation(const char* id, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, Curve::InterpolationType type)
 {
-    assert(type != Curve::BEZIER && type != Curve::HERMITE);
-    assert(keyCount >= 1 && keyTimes && keyValues);
+    GP_ASSERT(type != Curve::BEZIER && type != Curve::HERMITE);
+    GP_ASSERT(keyCount >= 1 && keyTimes && keyValues);
 
     Animation* animation = new Animation(id, this, propertyId, keyCount, keyTimes, keyValues, type);
 
@@ -41,7 +43,7 @@ Animation* AnimationTarget::createAnimation(const char* id, int propertyId, unsi
 
 Animation* AnimationTarget::createAnimation(const char* id, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, float* keyInValue, float* keyOutValue, Curve::InterpolationType type)
 {
-    assert(keyCount >= 1 && keyTimes && keyValues && keyInValue && keyOutValue);
+    GP_ASSERT(keyCount >= 1 && keyTimes && keyValues && keyInValue && keyOutValue);
     Animation* animation = new Animation(id, this, propertyId, keyCount, keyTimes, keyValues, keyInValue, keyOutValue, type);
 
     return animation;
@@ -49,10 +51,8 @@ Animation* AnimationTarget::createAnimation(const char* id, int propertyId, unsi
 
 Animation* AnimationTarget::createAnimation(const char* id, const char* url)
 {
-    assert(url);
-    
     Properties* p = Properties::create(url);
-    assert(p);
+    GP_ASSERT(p);
 
     Animation* animation = createAnimation(id, (strlen(p->getNamespace()) > 0) ? p : p->getNextNamespace());
 
@@ -63,7 +63,11 @@ Animation* AnimationTarget::createAnimation(const char* id, const char* url)
 
 Animation* AnimationTarget::createAnimationFromTo(const char* id, int propertyId, float* from, float* to, Curve::InterpolationType type, unsigned long duration)
 {
+    GP_ASSERT(from);
+    GP_ASSERT(to);
+
     const unsigned int propertyComponentCount = getAnimationPropertyComponentCount(propertyId);
+    GP_ASSERT(propertyComponentCount > 0);
     float* keyValues = new float[2 * propertyComponentCount];
 
     memcpy(keyValues, from, sizeof(float) * propertyComponentCount);
@@ -83,10 +87,16 @@ Animation* AnimationTarget::createAnimationFromTo(const char* id, int propertyId
 
 Animation* AnimationTarget::createAnimationFromBy(const char* id, int propertyId, float* from, float* by, Curve::InterpolationType type, unsigned long duration)
 {
+    GP_ASSERT(from);
+    GP_ASSERT(by);
+
     const unsigned int propertyComponentCount = getAnimationPropertyComponentCount(propertyId);
+    GP_ASSERT(propertyComponentCount > 0);
     float* keyValues = new float[2 * propertyComponentCount];
 
     memcpy(keyValues, from, sizeof(float) * propertyComponentCount);
+
+    convertByValues(propertyId, propertyComponentCount, from, by);
     memcpy(keyValues + propertyComponentCount, by, sizeof(float) * propertyComponentCount);
 
     unsigned long* keyTimes = new unsigned long[2];
@@ -103,27 +113,55 @@ Animation* AnimationTarget::createAnimationFromBy(const char* id, int propertyId
 
 Animation* AnimationTarget::createAnimation(const char* id, Properties* animationProperties)
 {
-    assert(animationProperties);
-    assert(std::strcmp(animationProperties->getNamespace(), "animation") == 0);
+    GP_ASSERT(animationProperties);
+    if (std::strcmp(animationProperties->getNamespace(), "animation") != 0)
+    {
+        GP_ERROR("Invalid animation namespace '%s'.", animationProperties->getNamespace());
+        return NULL;
+    }
     
     const char* propertyIdStr = animationProperties->getString("property");
-    assert(propertyIdStr);
+    if (propertyIdStr == NULL)
+    {
+        GP_ERROR("Attribute 'property' must be specified for an animation.");
+        return NULL;
+    }
     
     // Get animation target property id
     int propertyId = AnimationTarget::getPropertyId(_targetType, propertyIdStr);
-    assert(propertyId != -1);
+    if (propertyId == -1)
+    {
+        GP_ERROR("Property ID is invalid.");
+        return NULL;
+    }
     
     unsigned int keyCount = animationProperties->getInt("keyCount");
-    assert(keyCount > 0);
+    if (keyCount == 0)
+    {
+        GP_ERROR("Attribute 'keyCount' must be specified for an animation.");
+        return NULL;
+    }
 
     const char* keyTimesStr = animationProperties->getString("keyTimes");
-    assert(keyTimesStr);
+    if (keyTimesStr == NULL)
+    {
+        GP_ERROR("Attribute 'keyTimes' must be specified for an animation.");
+        return NULL;
+    }
     
     const char* keyValuesStr = animationProperties->getString("keyValues");
-    assert(keyValuesStr);
+    if (keyValuesStr == NULL)
+    {
+        GP_ERROR("Attribute 'keyValues' must be specified for an animation.");
+        return NULL;
+    }
     
     const char* curveStr = animationProperties->getString("curve");
-    assert(curveStr);
+    if (curveStr == NULL)
+    {
+        GP_ERROR("Attribute 'curve' must be specified for an animation.");
+        return NULL;
+    }
     
     char delimeter = ' ';
     unsigned int startOffset = 0;
@@ -148,7 +186,7 @@ Animation* AnimationTarget::createAnimation(const char* id, Properties* animatio
     endOffset = (unsigned int)std::string::npos;
     
     int componentCount = getAnimationPropertyComponentCount(propertyId);
-    assert(componentCount > 0);
+    GP_ASSERT(componentCount > 0);
     
     unsigned int components = keyCount * componentCount;
     
@@ -232,7 +270,11 @@ Animation* AnimationTarget::createAnimation(const char* id, Properties* animatio
     if (pClip && std::strcmp(pClip->getNamespace(), "clip") == 0)
     {
         int frameCount = animationProperties->getInt("frameCount");
-        assert(frameCount > 0);
+        if (frameCount <= 0)
+        {
+            GP_ERROR("Frame count must be greater than zero for a clip.");
+            return animation;
+        }
         animation->createClips(animationProperties, (unsigned int) frameCount);
     }
 
@@ -247,6 +289,7 @@ void AnimationTarget::destroyAnimation(const char* id)
         return;
 
     // Remove this target's channel from animation, and from the target's list of channels.
+    GP_ASSERT(channel->_animation);
     channel->_animation->removeChannel(channel);
     removeChannel(channel);
 
@@ -258,6 +301,7 @@ Animation* AnimationTarget::getAnimation(const char* id) const
     if (_animationChannels)
     {
         std::vector<Animation::Channel*>::iterator itr = _animationChannels->begin();
+        GP_ASSERT(*itr);
 
         if (id == NULL)
             return (*itr)->_animation;
@@ -266,6 +310,8 @@ Animation* AnimationTarget::getAnimation(const char* id) const
         for (; itr != _animationChannels->end(); itr++)
         {
             channel = (Animation::Channel*)(*itr);
+            GP_ASSERT(channel);
+            GP_ASSERT(channel->_animation);
             if (channel->_animation->_id.compare(id) == 0)
             {
                 return channel->_animation;
@@ -278,6 +324,8 @@ Animation* AnimationTarget::getAnimation(const char* id) const
 
 int AnimationTarget::getPropertyId(TargetType type, const char* propertyIdStr)
 {
+    GP_ASSERT(propertyIdStr);
+    
     if (type == AnimationTarget::TRANSFORM)
     {
         if (strcmp(propertyIdStr, "ANIMATE_SCALE") == 0)
@@ -341,6 +389,7 @@ void AnimationTarget::addChannel(Animation::Channel* channel)
     if (_animationChannels == NULL)
         _animationChannels = new std::vector<Animation::Channel*>;
 
+    GP_ASSERT(channel);
     _animationChannels->push_back(channel);
 }
 
@@ -378,6 +427,7 @@ Animation::Channel* AnimationTarget::getChannel(const char* id) const
         for (; itr != _animationChannels->end(); itr++)
         {
             channel = (Animation::Channel*)(*itr);
+            GP_ASSERT(channel);
             if (channel->_animation->_id.compare(id) == 0)
             {
                 return channel;
@@ -395,7 +445,8 @@ void AnimationTarget::cloneInto(AnimationTarget* target, NodeCloneContext &conte
         for (std::vector<Animation::Channel*>::const_iterator it = _animationChannels->begin(); it != _animationChannels->end(); ++it)
         {
             Animation::Channel* channel = *it;
-            assert(channel->_animation);
+            GP_ASSERT(channel);
+            GP_ASSERT(channel->_animation);
 
             Animation* animation = context.findClonedAnimation(channel->_animation);
             if (animation != NULL)
@@ -413,6 +464,79 @@ void AnimationTarget::cloneInto(AnimationTarget* target, NodeCloneContext &conte
     }
 }
 
+void AnimationTarget::convertByValues(unsigned int propertyId, unsigned int componentCount, float* from, float* by)
+{
+    if (_targetType == AnimationTarget::TRANSFORM)
+    {    
+        switch(propertyId)
+        {
+            case Transform::ANIMATE_SCALE:
+            case Transform::ANIMATE_SCALE_UNIT:
+            case Transform::ANIMATE_SCALE_X:
+            case Transform::ANIMATE_SCALE_Y:
+            case Transform::ANIMATE_SCALE_Z:
+            {
+                convertScaleByValues(from, by, componentCount);
+                break;
+            }
+            case Transform::ANIMATE_TRANSLATE:
+            case Transform::ANIMATE_TRANSLATE_X:
+            case Transform::ANIMATE_TRANSLATE_Y:
+            case Transform::ANIMATE_TRANSLATE_Z:
+            {
+                convertByValues(from, by, componentCount);
+                break;
+            }
+            case Transform::ANIMATE_ROTATE:
+            {
+                convertQuaternionByValues(from, by);
+                break;
+            }
+            case Transform::ANIMATE_ROTATE_TRANSLATE:
+            {
+                convertQuaternionByValues(from, by);
+                convertByValues(from + 4, by + 4, 3);
+                break;
+            }   
+            case Transform::ANIMATE_SCALE_ROTATE_TRANSLATE:
+            {
+                convertScaleByValues(from, by, 3);
+                convertQuaternionByValues(from + 3, by + 3);
+                convertByValues(from + 7, by + 7, 3);
+                break;
+            }
+        }
+    }
+    else
+    {
+        convertByValues(from, by, componentCount);
+    }
+}
+
+void AnimationTarget::convertQuaternionByValues(float* from, float* by)
+{
+    Quaternion qFrom(from);
+    Quaternion qBy(by);
+    qBy.multiply(qFrom);
+    memcpy(by, (float*)&qBy, sizeof(float) * 4);
+}
+
+void AnimationTarget::convertScaleByValues(float* from, float* by, unsigned int componentCount)
+{
+    for (unsigned int i = 0; i < componentCount; i++)
+    {
+        by[i] *= from[i];
+    }
+}
+
+void AnimationTarget::convertByValues(float* from, float* by, unsigned int componentCount)
+{
+    for (unsigned int i = 0; i < componentCount; i++)
+    {
+        by[i] += from[i];
+    }
+}
+
 }
 
 

+ 20 - 1
gameplay/src/AnimationTarget.h

@@ -19,7 +19,6 @@ class AnimationTarget
 {
     friend class Animation;
     friend class AnimationClip;
-    friend class AnimationController;
 
 public:
 
@@ -221,6 +220,26 @@ private:
      */
     static int getPropertyId(TargetType type, const char* propertyIdStr);
 
+    /**
+     * Converts by-value animations to to-value animations.
+     */
+    void convertByValues(unsigned int propertyId, unsigned int componentCount, float* from, float* by);
+
+    /**
+     * Converts a Quaternion by-value into a to-value.
+     */
+    void convertQuaternionByValues(float* from, float* by);
+
+    /**
+     * Converts a Scale by-value into a to-value.
+     */
+    void convertScaleByValues(float* from, float* by, unsigned int componentCount);
+
+    /**
+     * Converts a by-value into a to-value.
+     */
+    void convertByValues(float* from, float* by, unsigned int componentCount);
+
     std::vector<Animation::Channel*>* _animationChannels;   // Collection of all animation channels that target the AnimationTarget
     
 };

+ 7 - 4
gameplay/src/AnimationValue.cpp

@@ -7,6 +7,7 @@ namespace gameplay
 AnimationValue::AnimationValue(unsigned int componentCount)
   : _componentCount(componentCount), _componentSize(componentCount * sizeof(float))
 {
+    GP_ASSERT(_componentCount > 0);
     _value = new float[_componentCount];
 }
 
@@ -17,28 +18,30 @@ AnimationValue::~AnimationValue()
 
 float AnimationValue::getFloat(unsigned int index) const
 {
-    assert(index < _componentCount);
+    GP_ASSERT(index < _componentCount);
+    GP_ASSERT(_value);
 
     return _value[index];
 }
 
 void AnimationValue::setFloat(unsigned int index, float value)
 {
-    assert(index < _componentCount);
+    GP_ASSERT(index < _componentCount);
+    GP_ASSERT(_value);
 
     _value[index] = value;
 }
 
 void AnimationValue::getFloat(float* value, unsigned int offset, unsigned int length) const
 {
-    assert(value && offset < _componentCount && (offset + length) <= _componentCount);
+    GP_ASSERT(_value && value && offset < _componentCount && (offset + length) <= _componentCount);
 
     memcpy(value + offset, _value, length * sizeof(float));
 }
 
 void AnimationValue::setFloat(float* value, unsigned int offset, unsigned int length)
 {
-    assert(value && offset < _componentCount && (offset + length) <= _componentCount);
+    GP_ASSERT(_value && value && offset < _componentCount && (offset + length) <= _componentCount);
 
     memcpy(_value, value + offset, length * sizeof(float));
 }

+ 121 - 64
gameplay/src/AudioBuffer.cpp

@@ -28,14 +28,14 @@ AudioBuffer::~AudioBuffer()
 
     if (_alBuffer)
     {
-        alDeleteBuffers(1, &_alBuffer);
+        AL_CHECK( alDeleteBuffers(1, &_alBuffer) );
         _alBuffer = 0;
     }
 }
 
 AudioBuffer* AudioBuffer::create(const char* path)
 {
-    assert(path);
+    GP_ASSERT(path);
 
     // Search the cache for a stream from this file.
     unsigned int bufferCount = (unsigned int)__buffers.size();
@@ -43,6 +43,7 @@ AudioBuffer* AudioBuffer::create(const char* path)
     for (unsigned int i = 0; i < bufferCount; i++)
     {
         buffer = __buffers[i];
+        GP_ASSERT(buffer);
         if (buffer->_filePath.compare(path) == 0)
         {
             buffer->addRef();
@@ -51,15 +52,13 @@ AudioBuffer* AudioBuffer::create(const char* path)
     }
 
     ALuint alBuffer;
-    ALCenum al_error;
 
     // Load audio data into a buffer.
-    alGenBuffers(1, &alBuffer);
-    al_error = alGetError();
-    if (al_error != AL_NO_ERROR)
+    AL_CHECK( alGenBuffers(1, &alBuffer) );
+    if (AL_LAST_ERROR())
     {
-        LOG_ERROR_VARG("AudioBuffer alGenBuffers AL error: %d", al_error);
-        alDeleteBuffers(1, &alBuffer);
+        GP_ERROR("Failed to create OpenAL buffer; alGenBuffers error: %d", AL_LAST_ERROR());
+        AL_CHECK( alDeleteBuffers(1, &alBuffer) );
         return NULL;
     }
     
@@ -67,7 +66,7 @@ AudioBuffer* AudioBuffer::create(const char* path)
     FILE* file = FileSystem::openFile(path, "rb");
     if (!file)
     {
-        LOG_ERROR_VARG("Invalid audio buffer file: %s", path);
+        GP_ERROR("Failed to load audio file %s.", path);
         goto cleanup;
     }
     
@@ -75,7 +74,7 @@ AudioBuffer* AudioBuffer::create(const char* path)
     char header[12];
     if (fread(header, 1, 12, file) != 12)
     {
-        LOG_ERROR_VARG("Invalid audio buffer file: %s", path);
+        GP_ERROR("Invalid header for audio file %s.", path);
         goto cleanup;
     }
     
@@ -84,7 +83,7 @@ AudioBuffer* AudioBuffer::create(const char* path)
     {
         if (!AudioBuffer::loadWav(file, alBuffer))
         {
-            LOG_ERROR_VARG("Invalid wave file: %s", path);
+            GP_ERROR("Invalid wave file: %s", path);
             goto cleanup;
         }
     }
@@ -92,13 +91,14 @@ AudioBuffer* AudioBuffer::create(const char* path)
     {
         if (!AudioBuffer::loadOgg(file, alBuffer))
         {
-            LOG_ERROR_VARG("Invalid ogg file: %s", path);
+            GP_ERROR("Invalid ogg file: %s", path);
             goto cleanup;
         }
     }
     else
     {
-        LOG_ERROR_VARG("Unsupported audio file: %s", path);
+        GP_ERROR("Unsupported audio file: %s", path);
+        goto cleanup;
     }
     
     fclose(file);
@@ -115,17 +115,21 @@ cleanup:
     if (file)
         fclose(file);
     if (alBuffer)
-        alDeleteBuffers(1, &alBuffer);
+        AL_CHECK( alDeleteBuffers(1, &alBuffer) );
     return NULL;
 }
 
 bool AudioBuffer::loadWav(FILE* file, ALuint buffer)
 {
+    GP_ASSERT(file);
     unsigned char stream[12];
     
     // Verify the wave fmt magic value meaning format.
     if (fread(stream, 1, 8, file) != 8 || memcmp(stream, "fmt ", 4) != 0 )
+    {
+        GP_ERROR("Failed to verify the magic value for the wave file format.");
         return false;
+    }
     
     unsigned int section_size;
     section_size  = stream[7]<<24;
@@ -136,21 +140,27 @@ bool AudioBuffer::loadWav(FILE* file, ALuint buffer)
     // Check for a valid pcm format.
     if (fread(stream, 1, 2, file) != 2 || stream[1] != 0 || stream[0] != 1)
     {
-        LOG_ERROR("Unsupported audio file, not PCM format.");
+        GP_ERROR("Unsupported audio file format (must be a valid PCM format).");
         return false;
     }
     
-    // Get the channel count (16-bit little-endian)
+    // Get the channel count (16-bit little-endian).
     int channels;
     if (fread(stream, 1, 2, file) != 2)
+    {
+        GP_ERROR("Failed to read the wave file's channel count.");
         return false;
+    }
     channels  = stream[1]<<8;
     channels |= stream[0];
     
-    // Get the sample frequency (32-bit little-endian) 
+    // Get the sample frequency (32-bit little-endian).
     ALuint frequency;
     if (fread(stream, 1, 4, file) != 4)
+    {
+        GP_ERROR("Failed to read the wave file's sample frequency.");
         return false;
+    }
 
     frequency  = stream[3]<<24;
     frequency |= stream[2]<<16;
@@ -161,12 +171,18 @@ bool AudioBuffer::loadWav(FILE* file, ALuint buffer)
     // We don't need that info, so just read and ignore it. 
     // We could use this later if we need to know the duration.
     if (fread(stream, 1, 6, file) != 6)
+    {
+        GP_ERROR("Failed to read past the wave file's block size and bytes-per-second.");
         return false;
+    }
     
-    // Get the bit depth (16-bit little-endian)
+    // Get the bit depth (16-bit little-endian).
     int bits;
     if (fread(stream, 1, 2, file) != 2)
+    {
+        GP_ERROR("Failed to read the wave file's bit depth.");
         return false;
+    }
     bits  = stream[1]<<8;
     bits |= stream[0];
     
@@ -188,68 +204,109 @@ bool AudioBuffer::loadWav(FILE* file, ALuint buffer)
     }
     else
     {
-        LOG_ERROR_VARG("Incompatible format: (%d, %d)", channels, bits);
+        GP_ERROR("Incompatible wave file format: (%d, %d)", channels, bits);
         return false;
     }
     
-    // Check against the size of the format header as there may be more data that we need to read
+    // Check against the size of the format header as there may be more data that we need to read.
     if (section_size > 16)
     {
         unsigned int length = section_size - 16;
 
-        // extension size is 2 bytes
+        // Extension size is 2 bytes.
         if (fread(stream, 1, length, file) != length)
+        {
+            GP_ERROR("Failed to read extension size from wave file.");
             return false;
+        }
     }
 
-    if (fread(stream, 1, 4, file) != 4)
-        return false;
-
-    // read the next chunk, could be fact section or the data section
-    if (memcmp(stream, "fact", 4) == 0)
+    // Read in the rest of the file a chunk (section) at a time.
+    while (true)
     {
-        if (fread(stream, 1, 4, file) != 4)
-            return false;
-
-        section_size  = stream[3]<<24;
-        section_size |= stream[2]<<16;
-        section_size |= stream[1]<<8;
-        section_size |= stream[0];
-
-        // read in the rest of the fact section
-        if (fread(stream, 1, section_size, file) != section_size)
+        // Check if we are at the end of the file without reading the data.
+        if (feof(file))
+        {
+            GP_ERROR("Failed to load wave file; file appears to have no data.");
             return false;
+        }
 
+        // Read in the type of the next section of the file.
         if (fread(stream, 1, 4, file) != 4)
+        {
+            GP_ERROR("Failed to read next section type from wave file.");
             return false;
-    }
-
-    // should now be the data section which holds the decoded sample data
-    if (memcmp(stream, "data", 4) != 0)
-    {
-        LOG_ERROR("WAV file has no data.");
-        return false;
-    }
+        }
 
-    // Read how much data is remaining and buffer it up.
-    unsigned int dataSize;
-    fread(&dataSize, sizeof(int), 1, file);
+        // Data chunk.
+        if (memcmp(stream, "data", 4) == 0)
+        {
+            // Read how much data is remaining and buffer it up.
+            unsigned int dataSize;
+            if (fread(&dataSize, sizeof(int), 1, file) != 1)
+            {
+                GP_ERROR("Failed to read size of data section from wave file.");
+                return false;
+            }
+
+            char* data = new char[dataSize];
+            if (fread(data, sizeof(char), dataSize, file) != dataSize)
+            {
+                GP_ERROR("Failed to load wave file; file is missing data.");
+                SAFE_DELETE_ARRAY(data);
+                return false;
+            }
+
+            AL_CHECK( alBufferData(buffer, format, data, dataSize, frequency) );
+            SAFE_DELETE_ARRAY(data);
 
-    char* data = new char[dataSize];
-    if (fread(data, sizeof(char), dataSize, file) != dataSize)
-    {
-        LOG_ERROR("WAV file missing data.");
-        SAFE_DELETE_ARRAY(data);
-        return false;
+            // We've read the data, so return now.
+            return true;
+        }
+        // Other chunk - could be any of the following:
+        // - Fact ("fact")
+        // - Wave List ("wavl")
+        // - Silent ("slnt")
+        // - Cue ("cue ")
+        // - Playlist ("plst")
+        // - Associated Data List ("list")
+        // - Label ("labl")
+        // - Note ("note")
+        // - Labeled Text ("ltxt")
+        // - Sampler ("smpl")
+        // - Instrument ("inst")
+        else
+        {
+            // Store the name of the chunk so we can report errors informatively.
+            char chunk[5] = { 0 };
+            memcpy(chunk, stream, 4);
+
+            // Read the chunk size.
+            if (fread(stream, 1, 4, file) != 4)
+            {
+                GP_ERROR("Failed to read size of '%s' chunk from wave file.", chunk);
+                return false;
+            }
+
+            section_size  = stream[3]<<24;
+            section_size |= stream[2]<<16;
+            section_size |= stream[1]<<8;
+            section_size |= stream[0];
+
+            // Seek past the chunk.
+            if (fseek(file, section_size, SEEK_CUR) != 0)
+            {
+                GP_ERROR("Failed to seek past '%s' chunk in wave file.", chunk);
+                return false;
+            }
+        }
     }
-
-    alBufferData(buffer, format, data, dataSize, frequency);
-    SAFE_DELETE_ARRAY(data);
-    return true;
 }
     
 bool AudioBuffer::loadOgg(FILE* file, ALuint buffer)
 {
+    GP_ASSERT(file);
+
     OggVorbis_File ogg_file;
     vorbis_info* info;
     ALenum format;
@@ -262,18 +319,18 @@ bool AudioBuffer::loadOgg(FILE* file, ALuint buffer)
     if ((result = ov_open(file, &ogg_file, NULL, 0)) < 0)
     {
         fclose(file);
-        LOG_ERROR("Could not open Ogg stream.");
+        GP_ERROR("Failed to open ogg file.");
         return false;
     }
 
     info = ov_info(&ogg_file, -1);
-
+    GP_ASSERT(info);
     if (info->channels == 1)
         format = AL_FORMAT_MONO16;
     else
         format = AL_FORMAT_STEREO16;
 
-    // size = #samples * #channels * 2 (for 16 bit)
+    // size = #samples * #channels * 2 (for 16 bit).
     unsigned int data_size = ov_pcm_total(&ogg_file, -1) * info->channels * 2;
     char* data = new char[data_size];
 
@@ -287,7 +344,7 @@ bool AudioBuffer::loadOgg(FILE* file, ALuint buffer)
         else if (result < 0)
         {
             SAFE_DELETE_ARRAY(data);
-            LOG_ERROR("OGG file missing data.");
+            GP_ERROR("Failed to read ogg file; file is missing data.");
             return false;
         }
         else
@@ -299,16 +356,16 @@ bool AudioBuffer::loadOgg(FILE* file, ALuint buffer)
     if (size == 0)
     {
         SAFE_DELETE_ARRAY(data);
-        LOG_ERROR("Unable to read OGG data.");
+        GP_ERROR("Filed to read ogg file; unable to read any data.");
         return false;
     }
 
-    alBufferData(buffer, format, data, data_size, info->rate);
+    AL_CHECK( alBufferData(buffer, format, data, data_size, info->rate) );
 
     SAFE_DELETE_ARRAY(data);
     ov_clear(&ogg_file);
 
-    // ov_clear actually closes the file pointer as well
+    // ov_clear actually closes the file pointer as well.
     file = 0;
 
     return true;

+ 13 - 11
gameplay/src/AudioController.cpp

@@ -18,19 +18,19 @@ AudioController::~AudioController()
 
 void AudioController::initialize()
 {
-    _alcDevice = alcOpenDevice (NULL);
+    _alcDevice = alcOpenDevice(NULL);
     if (!_alcDevice)
     {
-        LOG_ERROR("AudioController::initialize() error. Unable to open OpenAL device.\n");
-        return;  
+        GP_ERROR("Unable to open OpenAL device.\n");
+        return;
     }
-        
+    
     _alcContext = alcCreateContext(_alcDevice, NULL);
     ALCenum alcErr = alcGetError(_alcDevice);
     if (!_alcContext || alcErr != ALC_NO_ERROR)
     {
-        alcCloseDevice (_alcDevice);
-        LOG_ERROR_VARG("AudioController::initialize() error. Unable to create OpenAL context. Error: %d\n", alcErr);
+        alcCloseDevice(_alcDevice);
+        GP_ERROR("Unable to create OpenAL context. Error: %d\n", alcErr);
         return;
     }
     
@@ -38,7 +38,7 @@ void AudioController::initialize()
     alcErr = alcGetError(_alcDevice);
     if (alcErr != ALC_NO_ERROR)
     {
-        LOG_ERROR_VARG("AudioController::initialize() error. Unable to make OpenAL context current. Error: %d\n", alcErr);
+        GP_ERROR("Unable to make OpenAL context current. Error: %d\n", alcErr);
     }
 }
 
@@ -65,6 +65,7 @@ void AudioController::pause()
     AudioSource* source = NULL;
     while (itr != _playingSources.end())
     {
+        GP_ASSERT(*itr);
         source = *itr;
         _pausingSource = source;
         source->pause();
@@ -83,6 +84,7 @@ void AudioController::resume()
     AudioSource* source = NULL;
     while (itr != _playingSources.end())
     {
+        GP_ASSERT(*itr);
         source = *itr;
         source->resume();
         itr++;
@@ -94,10 +96,10 @@ void AudioController::update(long elapsedTime)
     AudioListener* listener = AudioListener::getInstance();
     if (listener)
     {
-        alListenerf(AL_GAIN, listener->getGain());
-        alListenerfv(AL_ORIENTATION, (ALfloat*)listener->getOrientation());
-        alListenerfv(AL_VELOCITY, (ALfloat*)&listener->getVelocity());
-        alListenerfv(AL_POSITION, (ALfloat*)&listener->getPosition());
+        AL_CHECK( alListenerf(AL_GAIN, listener->getGain()) );
+        AL_CHECK( alListenerfv(AL_ORIENTATION, (ALfloat*)listener->getOrientation()) );
+        AL_CHECK( alListenerfv(AL_VELOCITY, (ALfloat*)&listener->getVelocity()) );
+        AL_CHECK( alListenerfv(AL_POSITION, (ALfloat*)&listener->getPosition()) );
     }
 }
 

+ 2 - 0
gameplay/src/AudioListener.cpp

@@ -89,6 +89,7 @@ void AudioListener::setCamera(Camera* c)
         // Disconnect our current camera.
         if (_camera)
         {
+            GP_ASSERT(_camera->getNode());
             _camera->getNode()->removeListener(this);
             SAFE_RELEASE(_camera);
         }
@@ -98,6 +99,7 @@ void AudioListener::setCamera(Camera* c)
 
         if (_camera)
         {
+            GP_ASSERT(_camera->getNode());
             _camera->addRef();
             _camera->getNode()->addListener(this);
         }

+ 38 - 40
gameplay/src/AudioSource.cpp

@@ -12,18 +12,19 @@ namespace gameplay
 AudioSource::AudioSource(AudioBuffer* buffer, ALuint source) 
     : _alSource(source), _buffer(buffer), _looped(true), _gain(1.0f), _pitch(1.0f), _node(NULL)
 {
-    alSourcei(_alSource, AL_BUFFER, buffer->_alBuffer);
-    alSourcei(_alSource, AL_LOOPING, _looped);
-    alSourcef(_alSource, AL_PITCH, _pitch);
-    alSourcef(_alSource, AL_GAIN, _gain);
-    alSourcefv(_alSource, AL_VELOCITY, (const ALfloat*)&_velocity);
+    GP_ASSERT(buffer);
+    AL_CHECK( alSourcei(_alSource, AL_BUFFER, buffer->_alBuffer) );
+    AL_CHECK( alSourcei(_alSource, AL_LOOPING, _looped) );
+    AL_CHECK( alSourcef(_alSource, AL_PITCH, _pitch) );
+    AL_CHECK( alSourcef(_alSource, AL_GAIN, _gain) );
+    AL_CHECK( alSourcefv(_alSource, AL_VELOCITY, (const ALfloat*)&_velocity) );
 }
 
 AudioSource::~AudioSource()
 {
     if (_alSource)
     {
-        alDeleteSources(1, &_alSource);
+        AL_CHECK( alDeleteSources(1, &_alSource) );
         _alSource = 0;
     }
     SAFE_RELEASE(_buffer);
@@ -31,16 +32,14 @@ AudioSource::~AudioSource()
 
 AudioSource* AudioSource::create(const char* url)
 {
-    assert(url);
-
     // Load from a .audio file.
     std::string pathStr = url;
     if (pathStr.find(".audio") != pathStr.npos)
     {
         Properties* properties = Properties::create(url);
-        assert(properties);
         if (properties == NULL)
         {
+            GP_ERROR("Failed to create audio source from .audio file.");
             return NULL;
         }
 
@@ -57,11 +56,11 @@ AudioSource* AudioSource::create(const char* url)
     // Load the audio source.
     ALuint alSource = 0;
 
-    alGenSources(1, &alSource);
-    if (alGetError() != AL_NO_ERROR)
+    AL_CHECK( alGenSources(1, &alSource) );
+    if (AL_LAST_ERROR())
     {
         SAFE_RELEASE(buffer);
-        LOG_ERROR("AudioSource::createAudioSource - Error generating audio source.");
+        GP_ERROR("Error generating audio source.");
         return NULL;
     }
     
@@ -71,17 +70,17 @@ AudioSource* AudioSource::create(const char* url)
 AudioSource* AudioSource::create(Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
-    assert(properties);
+    GP_ASSERT(properties);
     if (!properties || !(strcmp(properties->getNamespace(), "audio") == 0))
     {
-        WARN("Failed to load audio source from properties object: must be non-null object and have namespace equal to \'audio\'.");
+        GP_ERROR("Failed to load audio source from properties object: must be non-null object and have namespace equal to 'audio'.");
         return NULL;
     }
 
     const char* path = properties->getString("path");
     if (path == NULL)
     {
-        WARN("Audio file failed to load; the file path was not specified.");
+        GP_ERROR("Audio file failed to load; the file path was not specified.");
         return NULL;
     }
 
@@ -89,20 +88,20 @@ AudioSource* AudioSource::create(Properties* properties)
     AudioSource* audio = AudioSource::create(path);
     if (audio == NULL)
     {
-        WARN_VARG("Audio file '%s' failed to load properly.", path);
+        GP_ERROR("Audio file '%s' failed to load properly.", path);
         return NULL;
     }
 
     // Set any properties that the user specified in the .audio file.
-    if (properties->getString("looped") != NULL)
+    if (properties->exists("looped"))
     {
         audio->setLooped(properties->getBool("looped"));
     }
-    if (properties->getString("gain") != NULL)
+    if (properties->exists("gain"))
     {
         audio->setGain(properties->getFloat("gain"));
     }
-    if (properties->getString("pitch") != NULL)
+    if (properties->exists("pitch"))
     {
         audio->setPitch(properties->getFloat("pitch"));
     }
@@ -118,7 +117,7 @@ AudioSource* AudioSource::create(Properties* properties)
 AudioSource::State AudioSource::getState() const
 {
     ALint state;
-    alGetSourcei(_alSource, AL_SOURCE_STATE, &state);
+    AL_CHECK( alGetSourcei(_alSource, AL_SOURCE_STATE, &state) );
 
     switch (state)
     {
@@ -136,29 +135,28 @@ AudioSource::State AudioSource::getState() const
 
 void AudioSource::play()
 {
-    alSourcePlay(_alSource);
+    AL_CHECK( alSourcePlay(_alSource) );
 
     // Add the source to the controller's list of currently playing sources.
     AudioController* audioController = Game::getInstance()->getAudioController();
+    GP_ASSERT(audioController);
     if (audioController->_playingSources.find(this) == audioController->_playingSources.end())
         audioController->_playingSources.insert(this);
 }
 
 void AudioSource::pause()
 {
-    alSourcePause(_alSource);
+    AL_CHECK( alSourcePause(_alSource) );
 
     // Remove the source from the controller's set of currently playing sources
     // if the source is being paused by the user and not the controller itself.
     AudioController* audioController = Game::getInstance()->getAudioController();
+    GP_ASSERT(audioController);
     if (audioController->_pausingSource != this)
     {
         std::set<AudioSource*>::iterator iter = audioController->_playingSources.find(this);
         if (iter != audioController->_playingSources.end())
-        {
-            WARN("\n\nRemoving an audio source from the list of playing sources...\n\n\n");
             audioController->_playingSources.erase(iter);
-        }
     }
 }
 
@@ -172,10 +170,11 @@ void AudioSource::resume()
 
 void AudioSource::stop()
 {
-    alSourceStop(_alSource);
+    AL_CHECK( alSourceStop(_alSource) );
 
     // Remove the source from the controller's set of currently playing sources.
     AudioController* audioController = Game::getInstance()->getAudioController();
+    GP_ASSERT(audioController);
     std::set<AudioSource*>::iterator iter = audioController->_playingSources.find(this);
     if (iter != audioController->_playingSources.end())
         audioController->_playingSources.erase(iter);
@@ -183,7 +182,7 @@ void AudioSource::stop()
 
 void AudioSource::rewind()
 {
-    alSourceRewind(_alSource);
+    AL_CHECK( alSourceRewind(_alSource) );
 }
 
 bool AudioSource::isLooped() const
@@ -193,13 +192,10 @@ bool AudioSource::isLooped() const
 
 void AudioSource::setLooped(bool looped)
 {
-    alGetError();
-    alSourcei(_alSource, AL_LOOPING, (looped) ? AL_TRUE : AL_FALSE);
-
-    ALCenum error = alGetError();
-    if (error != AL_NO_ERROR)
+    AL_CHECK( alSourcei(_alSource, AL_LOOPING, (looped) ? AL_TRUE : AL_FALSE) );
+    if (AL_LAST_ERROR())
     {
-        LOG_ERROR_VARG("AudioSource::setLooped Error: %d", error);
+        GP_ERROR("Failed to set audio source's looped attribute with error: %d", AL_LAST_ERROR());
     }
     _looped = looped;
 }
@@ -211,7 +207,7 @@ float AudioSource::getGain() const
 
 void AudioSource::setGain(float gain)
 {
-    alSourcef(_alSource, AL_GAIN, gain);
+    AL_CHECK( alSourcef(_alSource, AL_GAIN, gain) );
     _gain = gain;
 }
 
@@ -222,7 +218,7 @@ float AudioSource::getPitch() const
 
 void AudioSource::setPitch(float pitch)
 {
-    alSourcef(_alSource, AL_PITCH, pitch);
+    AL_CHECK( alSourcef(_alSource, AL_PITCH, pitch) );
     _pitch = pitch;
 }
 
@@ -233,7 +229,7 @@ const Vector3& AudioSource::getVelocity() const
 
 void AudioSource::setVelocity(const Vector3& velocity)
 {
-    alSourcefv(_alSource, AL_VELOCITY, (ALfloat*)&velocity);
+    AL_CHECK( alSourcefv(_alSource, AL_VELOCITY, (ALfloat*)&velocity) );
     _velocity = velocity;
 }
 
@@ -269,17 +265,19 @@ void AudioSource::transformChanged(Transform* transform, long cookie)
     if (_node)
     {
         Vector3 translation = _node->getTranslationWorld();
-        alSourcefv(_alSource, AL_POSITION, (const ALfloat*)&translation.x);
+        AL_CHECK( alSourcefv(_alSource, AL_POSITION, (const ALfloat*)&translation.x) );
     }
 }
 
 AudioSource* AudioSource::clone(NodeCloneContext &context) const
 {
+    GP_ASSERT(_buffer);
+
     ALuint alSource = 0;
-    alGenSources(1, &alSource);
-    if (alGetError() != AL_NO_ERROR)
+    AL_CHECK( alGenSources(1, &alSource) );
+    if (AL_LAST_ERROR())
     {
-        LOG_ERROR("AudioSource::createAudioSource - Error generating audio source.");
+        GP_ERROR("Error generating audio source.");
         return NULL;
     }
     AudioSource* audioClone = new AudioSource(_buffer, alSource);

+ 79 - 76
gameplay/src/Base.h

@@ -51,51 +51,42 @@ namespace gameplay
 extern void printError(const char* format, ...);
 }
 
-#ifdef __ANDROID__
-#include <android/log.h>
-#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__))
-
-// System Errors
-#define LOG_ERROR(x) \
-    { \
-        LOGI(x); \
-        assert(#x == 0); \
-    }
-#define LOG_ERROR_VARG(x, ...) \
-    { \
-        LOGI(x, __VA_ARGS__); \
-        assert(#x == 0); \
-    }
-
-// Warning macro
-#ifdef WARN
-#undef WARN
+// Current function macro.
+#ifdef WIN32
+#define __current__func__ __FUNCTION__
+#else
+#define __current__func__ __func__
 #endif
-#define WARN(x) LOGI(x)
-#define WARN_VARG(x, ...) LOGI(x, __VA_ARGS__)
 
+// Assert macros.
+#ifdef _DEBUG
+#define GP_ASSERT(expression) assert(expression)
 #else
+#define GP_ASSERT(expression)
+#endif
 
-// System Errors
-#define LOG_ERROR(x) \
-    { \
-        printError(x); \
-        assert(#x == 0); \
-    }
-#define LOG_ERROR_VARG(x, ...) \
+// Error macro.
+#ifdef GP_ERRORS_AS_WARNINGS
+#define GP_ERROR GP_WARN
+#else
+#define GP_ERROR(...) do \
     { \
-        printError(x, __VA_ARGS__); \
-        assert(#x == 0); \
-    }
-
-// Warning macro
-#ifdef WARN
-#undef WARN
-#endif
-#define WARN(x) printError(x)
-#define WARN_VARG(x, ...) printError(x, __VA_ARGS__)
+        printError("%s -- ", __current__func__); \
+        printError(__VA_ARGS__); \
+        printError("\n"); \
+        assert(0); \
+        std::exit(-1); \
+    } while (0)
 #endif
 
+// Warning macro.
+#define GP_WARN(...) do \
+    { \
+        printError("%s -- ", __current__func__); \
+        printError(__VA_ARGS__); \
+        printError("\n"); \
+    } while (0)
+
 // Bullet Physics
 #include <btBulletDynamicsCommon.h>
 #include <BulletCollision/CollisionDispatch/btGhostObject.h>
@@ -176,11 +167,10 @@ extern void printError(const char* format, ...);
 #include <png.h>
 
 #define WINDOW_VSYNC        1
-#define WINDOW_FULLSCREEN   0
 
 // Graphics (OpenGL)
-#if defined (__QNX__) || defined(__ANDROID__)
-    #include <EGL/egl.h>
+#ifdef __QNX__
+#include <EGL/egl.h>
     #include <GLES2/gl2.h>
     #include <GLES2/gl2ext.h>
     extern PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray;
@@ -190,6 +180,19 @@ extern void printError(const char* format, ...);
     #define glClearDepth glClearDepthf
     #define OPENGL_ES
     #define USE_PVRTC
+    #ifdef __arm__
+        #define USE_NEON
+    #endif
+#elif __ANDROID__
+	#include <EGL/egl.h>
+    #include <GLES2/gl2.h>
+    #include <GLES2/gl2ext.h>
+    extern PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray;
+    extern PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays;
+    extern PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays;
+    extern PFNGLISVERTEXARRAYOESPROC glIsVertexArray;
+    #define glClearDepth glClearDepthf
+    #define OPENGL_ES
 #elif WIN32
     #define WIN32_LEAN_AND_MEAN
     #include <GL/glew.h>
@@ -205,8 +208,10 @@ extern void printError(const char* format, ...);
         #define glIsVertexArray glIsVertexArrayOES
         #define glClearDepth glClearDepthf
         #define OPENGL_ES
-        #define USE_PVRTC
         #define USE_VAO
+        #ifdef __arm__
+            #define USE_NEON
+        #endif
     #elif TARGET_OS_MAC
         #include <OpenGL/gl.h>
         #include <OpenGL/glext.h>
@@ -252,16 +257,12 @@ typedef GLuint RenderBufferHandle;
 #ifdef NDEBUG
 #define GL_ASSERT( gl_code ) gl_code
 #else
-#define GL_ASSERT( gl_code ) \
+#define GL_ASSERT( gl_code ) do \
     { \
         gl_code; \
         __gl_error_code = glGetError(); \
-        if (__gl_error_code != GL_NO_ERROR) \
-        { \
-            LOG_ERROR_VARG(#gl_code ": %d", (int)__gl_error_code); \
-        } \
-        assert(__gl_error_code == GL_NO_ERROR); \
-    }
+        GP_ASSERT(__gl_error_code == GL_NO_ERROR); \
+    } while(0)
 #endif
 
 /**
@@ -273,16 +274,16 @@ typedef GLuint RenderBufferHandle;
  * macro can be used afterwards to check whether a GL error was
  * encountered executing the specified code.
  */
-#define GL_CHECK( gl_code ) \
+#define GL_CHECK( gl_code ) do \
     { \
         while (glGetError() != GL_NO_ERROR) ; \
         gl_code; \
         __gl_error_code = glGetError(); \
         if (__gl_error_code != GL_NO_ERROR) \
         { \
-            LOG_ERROR_VARG(#gl_code ": %d", (int)__gl_error_code); \
+            GP_ERROR(#gl_code ": %d", (int)__gl_error_code); \
         } \
-    }
+    } while(0)
 
 // Global variable to hold GL errors
 extern GLenum __gl_error_code;
@@ -292,6 +293,32 @@ extern GLenum __gl_error_code;
  */
 #define GL_LAST_ERROR() __gl_error_code
 
+/**
+ * Executes the specified AL code and checks the AL error afterwards
+ * to ensure it succeeded.
+ *
+ * The AL_LAST_ERROR macro can be used afterwards to check whether a AL error was
+ * encountered executing the specified code.
+ */
+#define AL_CHECK( al_code ) do \
+    { \
+        while (alGetError() != AL_NO_ERROR) ; \
+        al_code; \
+        __al_error_code = alGetError(); \
+        if (__al_error_code != AL_NO_ERROR) \
+        { \
+            GP_ERROR(#al_code ": %d", (int)__al_error_code); \
+        } \
+    } while(0)
+
+// Global variable to hold AL errors
+extern ALenum __al_error_code;
+
+/**
+ * Accesses the most recently set global AL error.
+ */
+#define AL_LAST_ERROR() __al_error_code
+
 
 #if defined(WIN32)
     #pragma warning( disable : 4172 )
@@ -302,28 +329,4 @@ extern GLenum __gl_error_code;
     #pragma warning( disable : 4996 )
 #endif
 
-#ifdef __ANDROID__
-#include <android_native_app_glue.h>
-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 - 5
gameplay/src/BoundingBox.cpp

@@ -32,7 +32,7 @@ const BoundingBox& BoundingBox::empty()
 
 void BoundingBox::getCorners(Vector3* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     // Near face, specified counter-clockwise looking towards the origin from the positive z-axis.
     // Left-top-front.
@@ -64,6 +64,8 @@ Vector3 BoundingBox::getCenter() const
 
 void BoundingBox::getCenter(Vector3* dst) const
 {
+    GP_ASSERT(dst);
+
     dst->set(min, max);
     dst->scale(0.5f);
     dst->add(min);
@@ -247,6 +249,10 @@ void BoundingBox::set(const Vector3& min, const Vector3& max)
 
 void updateMinMax(Vector3* point, Vector3* min, Vector3* max)
 {
+    GP_ASSERT(point);
+    GP_ASSERT(min);
+    GP_ASSERT(max);
+
     // Leftmost point.
     if (point->x < min->x)
     {
@@ -292,10 +298,6 @@ void BoundingBox::set(const BoundingBox& box)
 
 void BoundingBox::set(const BoundingSphere& sphere)
 {
-    std::vector<int> v;
-    v.push_back(0);
-    std::vector<int> v2 = v;
-
     const Vector3& center = sphere.center;
     float radius = sphere.radius;
 

+ 8 - 0
gameplay/src/BoundingSphere.cpp

@@ -158,6 +158,9 @@ bool BoundingSphere::isEmpty() const
 
 void BoundingSphere::merge(const BoundingSphere& sphere)
 {
+    if (sphere.isEmpty())
+        return;
+
     // Calculate the distance between the two centers.
     float vx = center.x - sphere.center.x;
     float vy = center.y - sphere.center.y;
@@ -177,6 +180,7 @@ void BoundingSphere::merge(const BoundingSphere& sphere)
     }
 
     // Calculate the unit vector between the two centers.
+    GP_ASSERT(d != 0.0f);
     float dI = 1.0f / d;
     vx *= dI;
     vy *= dI;
@@ -200,6 +204,9 @@ void BoundingSphere::merge(const BoundingSphere& sphere)
 
 void BoundingSphere::merge(const BoundingBox& box)
 {
+    if (box.isEmpty())
+        return;
+
     const Vector3& min = box.min;
     const Vector3& max = box.max;
 
@@ -240,6 +247,7 @@ void BoundingSphere::merge(const BoundingBox& box)
     }
 
     // Calculate the unit vector between the center and the farthest point.
+    GP_ASSERT(distance != 0.0f);
     float dI = 1.0f / distance;
     v1x *= dI;
     v1y *= dI;

Diferenças do arquivo suprimidas por serem muito extensas
+ 240 - 111
gameplay/src/Bundle.cpp


+ 30 - 30
gameplay/src/Button.cpp

@@ -3,41 +3,41 @@
 
 namespace gameplay
 {
-    Button::Button()
-    {
-    }
 
-    Button::~Button()
-    {
-    }
+Button::Button()
+{
+}
 
-    Button* Button::create(Theme::Style* style, Properties* properties)
-    {
-        Button* button = new Button();
-        button->initialize(style, properties);
+Button::~Button()
+{
+}
 
-        return button;
+Button* Button::create(Theme::Style* style, Properties* properties)
+{
+    Button* button = new Button();
+    button->initialize(style, properties);
+
+    return button;
+}
+
+bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    if (!isEnabled())
+    {
+        return false;
     }
 
-    bool Button::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    switch (evt)
     {
-        if (!isEnabled())
-        {
-            return false;
-        }
-
-        switch (evt)
-        {
-        case Touch::TOUCH_PRESS:
-            _state = Control::ACTIVE;
-            _dirty = true;
-            break;
-        case Touch::TOUCH_RELEASE:
-            _dirty = true;
-            setState(Control::NORMAL);
-            break;
-        }
-
-        return Control::touchEvent(evt, x, y, contactIndex);
+    case Touch::TOUCH_PRESS:
+        setState(Control::ACTIVE);
+        break;
+    case Touch::TOUCH_RELEASE:
+        setState(Control::NORMAL);
+        break;
     }
+
+    return Control::touchEvent(evt, x, y, contactIndex);
+}
+
 }

+ 17 - 24
gameplay/src/Camera.cpp

@@ -53,14 +53,14 @@ Camera::Type Camera::getCameraType() const
 
 float Camera::getFieldOfView() const
 {
-    assert(_type == Camera::PERSPECTIVE);
+    GP_ASSERT(_type == Camera::PERSPECTIVE);
 
     return _fieldOfView;
 }
 
 void Camera::setFieldOfView(float fieldOfView)
 {
-    assert(_type == Camera::PERSPECTIVE);
+    GP_ASSERT(_type == Camera::PERSPECTIVE);
 
     _fieldOfView = fieldOfView;
     _dirtyBits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
@@ -68,14 +68,14 @@ void Camera::setFieldOfView(float fieldOfView)
 
 float Camera::getZoomX() const
 {
-    assert(_type == Camera::ORTHOGRAPHIC);
+    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
 
     return _zoom[0];
 }
 
 void Camera::setZoomX(float zoomX)
 {
-    assert(_type == Camera::ORTHOGRAPHIC);
+    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
 
     _zoom[0] = zoomX;
     _dirtyBits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
@@ -83,14 +83,14 @@ void Camera::setZoomX(float zoomX)
 
 float Camera::getZoomY() const
 {
-    assert(_type == Camera::ORTHOGRAPHIC);
+    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
 
     return _zoom[1];
 }
 
 void Camera::setZoomY(float zoomY)
 {
-    assert(_type == Camera::ORTHOGRAPHIC);
+    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
 
     _zoom[1] = zoomY;
     _dirtyBits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
@@ -245,23 +245,21 @@ const Frustum& Camera::getFrustum() const
 
 void Camera::project(const Rectangle& viewport, const Vector3& position, float* x, float* y, float* depth)
 {
-    // Determine viewport coords to use.
-    float vpx = viewport.x;
-    float vpy = viewport.y;
-    float vpw = viewport.width;
-    float vph = viewport.height;
+    GP_ASSERT(x);
+    GP_ASSERT(y);
 
     // Transform the point to clip-space.
     Vector4 clipPos;
     getViewProjectionMatrix().transformVector(Vector4(position.x, position.y, position.z, 1.0f), &clipPos);
 
     // Compute normalized device coordinates.
+    GP_ASSERT(clipPos.w != 0.0f);
     float ndcX = clipPos.x / clipPos.w;
     float ndcY = clipPos.y / clipPos.w;
 
     // Compute screen coordinates by applying our viewport transformation.
-    *x = vpx + (ndcX + 1.0f) * 0.5f * vpw;
-    *y = vpy + (1.0f - (ndcY + 1.0f) * 0.5f) * vph;
+    *x = viewport.x + (ndcX + 1.0f) * 0.5f * viewport.width;
+    *y = viewport.y + (1.0f - (ndcY + 1.0f) * 0.5f) * viewport.height;
     if (depth)
     {
         float ndcZ = clipPos.z / clipPos.w;
@@ -271,18 +269,11 @@ void Camera::project(const Rectangle& viewport, const Vector3& position, float*
 
 void Camera::unproject(const Rectangle& viewport, float x, float y, float depth, Vector3* dst)
 {
-    // Determine viewport coords to use.
-    float vpx = viewport.x;
-    float vpy = viewport.y;
-    float vpw = viewport.width;
-    float vph = viewport.height;
+    GP_ASSERT(dst);
     
     // Create our screen space position in NDC.
-    Vector4 screen(
-        ((float)x - (float)vpx) / (float)vpw,
-        ((float)(vph - y) - (float)vpy) / (float)vph,
-        depth,
-        1.0f );
+    GP_ASSERT(viewport.width != 0.0f && viewport.height != 0.0f);
+    Vector4 screen((x - viewport.x) / viewport.width, ((viewport.height - y) - viewport.y) / viewport.height, depth, 1.0f);
 
     // Map to range -1 to 1.
     screen.x = screen.x * 2.0f - 1.0f;
@@ -305,6 +296,8 @@ void Camera::unproject(const Rectangle& viewport, float x, float y, float depth,
 
 void Camera::pickRay(const Rectangle& viewport, float x, float y, Ray* dst)
 {
+    GP_ASSERT(dst);
+
     // Get the world-space position at the near clip plane.
     Vector3 nearPoint;
     unproject(viewport, x, y, 0.0f, &nearPoint);
@@ -332,7 +325,7 @@ Camera* Camera::clone(NodeCloneContext &context) const
     {
         cameraClone = createOrthographic(getZoomX(), getZoomY(), getAspectRatio(), _nearPlane, _farPlane);
     }
-    assert(cameraClone);
+    GP_ASSERT(cameraClone);
 
     if (Node* node = context.findClonedNode(getNode()))
     {

+ 14 - 19
gameplay/src/CheckBox.cpp

@@ -5,7 +5,7 @@
 namespace gameplay
 {
 
-CheckBox::CheckBox() : _checked(false)
+CheckBox::CheckBox() : _checked(false), _image(NULL)
 {
 }
 
@@ -21,6 +21,8 @@ CheckBox::~CheckBox()
 
 CheckBox* CheckBox::create(Theme::Style* style, Properties* properties)
 {
+    GP_ASSERT(properties);
+
     CheckBox* checkBox = new CheckBox();
     checkBox->initialize(style, properties);
     properties->getVector2("imageSize", &checkBox->_imageSize);
@@ -39,6 +41,7 @@ void CheckBox::setChecked(bool checked)
     if (_checked != checked)
     {
         _checked = checked;
+        _dirty = true;
         notifyListeners(Control::Listener::VALUE_CHANGED);
     }
 }
@@ -57,7 +60,7 @@ void CheckBox::addListener(Control::Listener* listener, int eventFlags)
 {
     if ((eventFlags & Control::Listener::TEXT_CHANGED) == Control::Listener::TEXT_CHANGED)
     {
-        assert("TEXT_CHANGED event is not applicable to CheckBox.");
+        GP_ERROR("TEXT_CHANGED event is not applicable to CheckBox.");
         eventFlags &= ~Control::Listener::TEXT_CHANGED;
     }
 
@@ -77,19 +80,11 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
         {
             if (_state == Control::ACTIVE)
             {
-                if (x > 0 && x <= _clipBounds.width &&
-                    y > 0 && y <= _clipBounds.height)
+                if (x > _clipBounds.x && x <= _clipBounds.x + _clipBounds.width &&
+                    y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
                 {
                     _checked = !_checked;
                     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);
-                    */
                 }
             }
         }
@@ -99,9 +94,9 @@ bool CheckBox::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
     return Button::touchEvent(evt, x, y, contactIndex);
 }
 
-void CheckBox::update(const Rectangle& clip)
+void CheckBox::update(const Control* container, const Vector2& offset)
 {
-    Label::update(clip);
+    Label::update(container, offset);
 
     Vector2 size;
     if (_imageSize.isZero())
@@ -138,10 +133,11 @@ void CheckBox::update(const Rectangle& clip)
 
 void CheckBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 {
+    GP_ASSERT(spriteBatch);
+    GP_ASSERT(_image);
+
     // Left, v-center.
     // TODO: Set an alignment for icons.
-    const Theme::Border& border = getBorder(_state);
-    const Theme::Padding padding = getPadding();
     
     const Rectangle& region = _image->getRegion();
     const Theme::UVs& uvs = _image->getUVs();
@@ -158,10 +154,9 @@ void CheckBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
         size.set(_imageSize);
     }
 
-    Vector2 pos(clip.x + _bounds.x + border.left + padding.left,
-        clip.y + _bounds.y + (_clipBounds.height - border.bottom - padding.bottom) / 2.0f - size.y / 2.0f);
+    Vector2 pos(_viewportBounds.x, _viewportBounds.y + _viewportBounds.height * 0.5f - size.y * 0.5f);
 
-    spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _clip);
+    spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
 }
 
 }

+ 3 - 2
gameplay/src/CheckBox.h

@@ -118,9 +118,10 @@ protected:
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.
      *
-     * @param clip The clipping rectangle of this control's parent container.
+     * @param container This control's parent container.
+     * @param offset The position offset.
      */
-    void update(const Rectangle& clip);
+    void update(const Control* container, const Vector2& offset);
 
     /**
      * Draw the checkbox icon associated with this control.

+ 770 - 259
gameplay/src/Container.cpp

@@ -10,394 +10,905 @@
 #include "RadioButton.h"
 #include "Slider.h"
 #include "TextBox.h"
+#include "Joystick.h"
+#include "Game.h"
 
 namespace gameplay
 {
-    Container::Container() : _layout(NULL)
+
+// If the user stops scrolling for this amount of time (in millis) before touch/click release, don't apply inertia.
+static const long SCROLL_INERTIA_DELAY = 100L;
+// Factor to multiply friction by before applying to velocity.
+static const float SCROLL_FRICTION_FACTOR = 5.0f;
+
+Container::Container()
+    : _layout(NULL), _scrollBarTopCap(NULL), _scrollBarVertical(NULL), _scrollBarBottomCap(NULL),
+      _scrollBarLeftCap(NULL), _scrollBarHorizontal(NULL), _scrollBarRightCap(NULL),
+      _scroll(SCROLL_NONE), _scrollBarBounds(Rectangle::empty()), _scrollPosition(Vector2::zero()),
+      _scrollBarsAutoHide(false), _scrollBarOpacity(1.0f), _scrolling(false),
+       _scrollingFirstX(0), _scrollingFirstY(0),
+      _scrollingLastX(0), _scrollingLastY(0),
+      _scrollingStartTimeX(0), _scrollingStartTimeY(0), _scrollingLastTime(0),
+      _scrollingVelocity(Vector2::zero()), _scrollingFriction(1.0f),
+      _scrollingRight(false), _scrollingDown(false), _scrollBarOpacityClip(NULL), _zIndexDefault(0)
+{
+}
+
+Container::Container(const Container& copy)
+{
+}
+
+Container::~Container()
+{
+    std::vector<Control*>::iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
+        SAFE_RELEASE((*it));
     }
+    SAFE_RELEASE(_layout);
+}
 
-    Container::Container(const Container& copy)
+Container* Container::create(Layout::Type type)
+{
+    Layout* layout = NULL;
+    switch (type)
     {
+    case Layout::LAYOUT_ABSOLUTE:
+        layout = AbsoluteLayout::create();
+        break;
+    case Layout::LAYOUT_FLOW:
+        layout = FlowLayout::create();
+        break;
+    case Layout::LAYOUT_VERTICAL:
+        layout = VerticalLayout::create();
+        break;
     }
 
-    Container::~Container()
-    {
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            SAFE_RELEASE((*it));
-        }
+    Container* container = new Container();
+    container->_layout = layout;
+
+    return container;
+}
 
-        SAFE_RELEASE(_layout);
+Container* Container::create(Theme::Style* style, Properties* properties, Theme* theme)
+{
+    GP_ASSERT(properties);
+
+    const char* layoutString = properties->getString("layout");
+    Container* container = Container::create(getLayoutType(layoutString));
+    container->initialize(style, properties);
+    container->_scroll = getScroll(properties->getString("scroll"));
+    container->_scrollBarsAutoHide = properties->getBool("scrollBarsAutoHide");
+    if (container->_scrollBarsAutoHide)
+    {
+        container->_scrollBarOpacity = 0.0f;
     }
+    container->addControls(theme, properties);
+    container->_layout->update(container, container->_scrollPosition);
 
-    Container* Container::create(Layout::Type type)
+    return container;
+}
+
+void Container::addControls(Theme* theme, Properties* properties)
+{
+    GP_ASSERT(theme);
+    GP_ASSERT(properties);
+
+    // Add all the controls to this container.
+    Properties* controlSpace = properties->getNextNamespace();
+    while (controlSpace != NULL)
     {
-        Layout* layout = NULL;
-        switch (type)
+        Control* control = NULL;
+
+        const char* controlStyleName = controlSpace->getString("style");
+        Theme::Style* controlStyle = NULL;
+        if (controlStyleName)
+        {
+            controlStyle = theme->getStyle(controlStyleName);
+        }
+        else
         {
-        case Layout::LAYOUT_ABSOLUTE:
-            layout = AbsoluteLayout::create();
-            break;
-        case Layout::LAYOUT_FLOW:
-            layout = FlowLayout::create();
-            break;
-        case Layout::LAYOUT_VERTICAL:
-            layout = VerticalLayout::create();
-            break;
+            Theme::Style::Overlay* overlay = Theme::Style::Overlay::create();
+            controlStyle = new Theme::Style(theme, "", 1.0f / theme->_texture->getWidth(), 1.0f / theme->_texture->getHeight(),
+                Theme::Margin::empty(), Theme::Border::empty(), overlay, overlay, overlay, overlay);
         }
 
-        Container* container = new Container();
-        container->_layout = layout;
+        std::string controlName(controlSpace->getNamespace());
+        std::transform(controlName.begin(), controlName.end(), controlName.begin(), (int(*)(int))toupper);
+        if (controlName == "LABEL")
+        {
+            control = Label::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "BUTTON")
+        {
+            control = Button::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "CHECKBOX")
+        {
+            control = CheckBox::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "RADIOBUTTON")
+        {
+            control = RadioButton::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "CONTAINER")
+        {
+            control = Container::create(controlStyle, controlSpace, theme);
+        }
+        else if (controlName == "SLIDER")
+        {
+            control = Slider::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "TEXTBOX")
+        {
+            control = TextBox::create(controlStyle, controlSpace);
+        }
+        else if (controlName == "JOYSTICK")
+        {
+            control = Joystick::create(controlStyle, controlSpace);
+        }
+        else
+        {
+            GP_ERROR("Failed to create control; unrecognized control name '%s'.", controlName.c_str());
+        }
+
+        // Add the new control to the form.
+        if (control)
+        {
+            addControl(control);
+
+            if (control->getZIndex() == -1)
+            {
+                control->setZIndex(_zIndexDefault++);
+            }
+        }
 
-        return container;
+        // Get the next control.
+        controlSpace = properties->getNextNamespace();
     }
 
-    Container* Container::create(Theme::Style* style, Properties* properties, Theme* theme)
-    {
-        const char* layoutString = properties->getString("layout");
-        Container* container = Container::create(getLayoutType(layoutString));
-        container->initialize(style, properties);
-        container->addControls(theme, properties);
+    // Sort controls by Z-Order.
+    std::sort(_controls.begin(), _controls.end(), &sortControlsByZOrder);
+}
+
+Layout* Container::getLayout()
+{
+    return _layout;
+}
+
+unsigned int Container::addControl(Control* control)
+{
+    GP_ASSERT(control);
+    _controls.push_back(control);
+
+    return _controls.size() - 1;
+}
 
-        return container;
+void Container::insertControl(Control* control, unsigned int index)
+{
+    GP_ASSERT(control);
+    std::vector<Control*>::iterator it = _controls.begin() + index;
+    _controls.insert(it, control);
+}
+
+void Container::removeControl(unsigned int index)
+{
+    std::vector<Control*>::iterator it = _controls.begin() + index;
+    _controls.erase(it);
+}
+
+void Container::removeControl(const char* id)
+{
+    std::vector<Control*>::iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
+    {
+        Control* c = *it;
+        if (strcmp(id, c->getID()) == 0)
+        {
+            _controls.erase(it);
+            return;
+        }
     }
+}
 
-    void Container::addControls(Theme* theme, Properties* properties)
+void Container::removeControl(Control* control)
+{
+    GP_ASSERT(control);
+    std::vector<Control*>::iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        // Add all the controls to this container.
-        Properties* controlSpace = properties->getNextNamespace();
-        while (controlSpace != NULL)
+        if (*it == control)
         {
-            Control* control = NULL;
+            _controls.erase(it);
+            return;
+        }
+    }
+}
 
-            const char* controlStyleName = controlSpace->getString("style");
-            Theme::Style* controlStyle = NULL;
-            if (controlStyleName)
-            {
-                 controlStyle = theme->getStyle(controlStyleName);
-            }
-            assert(controlStyle);
+Control* Container::getControl(unsigned int index) const
+{
+    std::vector<Control*>::const_iterator it = _controls.begin() + index;
+    return *it;
+}
 
-            std::string controlName(controlSpace->getNamespace());
-            std::transform(controlName.begin(), controlName.end(), controlName.begin(), (int(*)(int))toupper);
-            if (controlName == "LABEL")
-            {
-                control = Label::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "BUTTON")
-            {
-                control = Button::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "CHECKBOX")
-            {
-                control = CheckBox::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "RADIOBUTTON")
-            {
-                control = RadioButton::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "CONTAINER")
-            {
-                control = Container::create(controlStyle, controlSpace, theme);
-            }
-            else if (controlName == "SLIDER")
-            {
-                control = Slider::create(controlStyle, controlSpace);
-            }
-            else if (controlName == "TEXTBOX")
+Control* Container::getControl(const char* id) const
+{
+    GP_ASSERT(id);
+    std::vector<Control*>::const_iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
+    {
+        Control* c = *it;
+        GP_ASSERT(c);
+        if (strcmp(id, c->getID()) == 0)
+        {
+            return c;
+        }
+        else if (c->isContainer())
+        {
+            Control* cc = ((Container*)c)->getControl(id);
+            if (cc)
             {
-                control = TextBox::create(controlStyle, controlSpace);
+                return cc;
             }
+        }
+    }
 
-            // Add the new control to the form.
-            if (control)
-            {
-                addControl(control);
-            }
+    return NULL;
+}
 
-            // Get the next control.
-            controlSpace = properties->getNextNamespace();
-        }
+const std::vector<Control*>& Container::getControls() const
+{
+    return _controls;
+}
+
+void Container::setScroll(Scroll scroll)
+{
+    if (scroll != _scroll)
+    {
+        _scroll = scroll;
+        _dirty = true;
     }
+}
+
+Container::Scroll Container::getScroll() const
+{
+    return _scroll;
+}
 
-    Layout* Container::getLayout()
+void Container::setScrollBarsAutoHide(bool autoHide)
+{
+    if (autoHide != _scrollBarsAutoHide)
     {
-        return _layout;
+        _scrollBarsAutoHide = autoHide;
+        _dirty = true;
     }
+}
+
+bool Container::isScrollBarsAutoHide() const
+{
+    return _scrollBarsAutoHide;
+}
 
-    unsigned int Container::addControl(Control* control)
+Animation* Container::getAnimation(const char* id) const
+{
+    std::vector<Control*>::const_iterator itr = _controls.begin();
+    std::vector<Control*>::const_iterator end = _controls.end();
+        
+    Control* control = NULL;
+    for (; itr != end; itr++)
     {
-        _controls.push_back(control);
+        control = *itr;
+        GP_ASSERT(control);
+        Animation* animation = control->getAnimation(id);
+        if (animation)
+            return animation;
 
-        return _controls.size() - 1;
+        if (control->isContainer())
+        {
+            animation = ((Container*)control)->getAnimation(id);
+            if (animation)
+                return animation;
+        }
+    }
+
+    return NULL;
+}
+
+void Container::update(const Control* container, const Vector2& offset)
+{
+    // Update this container's viewport.
+    Control::update(container, offset);
+
+    // Get scrollbar images and diminish clipping bounds to make room for scrollbars.
+    if ((_scroll & SCROLL_HORIZONTAL) == SCROLL_HORIZONTAL)
+    {
+        _scrollBarLeftCap = getImage("scrollBarLeftCap", _state);
+        _scrollBarHorizontal = getImage("horizontalScrollBar", _state);
+        _scrollBarRightCap = getImage("scrollBarRightCap", _state);
+
+        GP_ASSERT(_scrollBarLeftCap && _scrollBarHorizontal && _scrollBarRightCap);
+
+        _viewportClipBounds.height -= _scrollBarHorizontal->getRegion().height;
     }
 
-    void Container::insertControl(Control* control, unsigned int index)
+    if ((_scroll & SCROLL_VERTICAL) == SCROLL_VERTICAL)
     {
-        std::vector<Control*>::iterator it = _controls.begin() + index;
-        _controls.insert(it, control);
+        _scrollBarTopCap = getImage("scrollBarTopCap", _state);
+        _scrollBarVertical = getImage("verticalScrollBar", _state);
+        _scrollBarBottomCap = getImage("scrollBarBottomCap", _state);
+
+        GP_ASSERT(_scrollBarTopCap && _scrollBarVertical && _scrollBarBottomCap);
+        
+        _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
     }
 
-    void Container::removeControl(unsigned int index)
+    // Sort controls by Z-Order.
+    std::sort(_controls.begin(), _controls.end(), &sortControlsByZOrder);
+
+    GP_ASSERT(_layout);
+    if (_scroll != SCROLL_NONE)
+        updateScroll();
+    else
+        _layout->update(this, Vector2::zero());
+}
+
+void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight)
+{
+    if (needsClear)
+    {
+        GL_ASSERT( glEnable(GL_SCISSOR_TEST) );
+        GL_ASSERT( glClearColor(0, 0, 0, 0) );
+        float clearY = targetHeight - _clearBounds.y - _clearBounds.height;
+        GL_ASSERT( glScissor(_clearBounds.x, clearY,
+            _clearBounds.width, _clearBounds.height) );
+        GL_ASSERT( glClear(GL_COLOR_BUFFER_BIT) );
+        GL_ASSERT( glDisable(GL_SCISSOR_TEST) );
+
+        needsClear = false;
+        cleared = true;
+    }
+    else if (!cleared)
     {
-        std::vector<Control*>::iterator it = _controls.begin() + index;
-        _controls.erase(it);
+        needsClear = true;
     }
 
-    void Container::removeControl(const char* id)
+    spriteBatch->begin();
+    Control::drawBorder(spriteBatch, clip);
+    spriteBatch->end();
+
+    std::vector<Control*>::const_iterator it;
+    Rectangle boundsUnion = Rectangle::empty();
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        Control* control = *it;
+        GP_ASSERT(control);
+        if (!needsClear || control->isDirty() || control->_clearBounds.intersects(boundsUnion))
         {
-            Control* c = *it;
-            if (strcmp(id, c->getID()) == 0)
-            {
-                _controls.erase(it);
-            }
+            control->draw(spriteBatch, _viewportClipBounds, needsClear, cleared, targetHeight);
+            Rectangle::combine(control->_clearBounds, boundsUnion, &boundsUnion);
         }
     }
 
-    void Container::removeControl(Control* control)
+    if (_scroll != SCROLL_NONE && (_scrollBarOpacity > 0.0f))
     {
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        // Draw scroll bars.
+        Rectangle clipRegion(_viewportClipBounds);
+
+        spriteBatch->begin();
+
+        if (_scrollBarBounds.height > 0)
         {
-            if (*it == control)
-            {
-                _controls.erase(it);
-            }
+            const Rectangle& topRegion = _scrollBarTopCap->getRegion();
+            const Theme::UVs& topUVs = _scrollBarTopCap->getUVs();
+            Vector4 topColor = _scrollBarTopCap->getColor();
+            topColor.w *= _scrollBarOpacity * _opacity;
+
+            const Rectangle& verticalRegion = _scrollBarVertical->getRegion();
+            const Theme::UVs& verticalUVs = _scrollBarVertical->getUVs();
+            Vector4 verticalColor = _scrollBarVertical->getColor();
+            verticalColor.w *= _scrollBarOpacity * _opacity;
+
+            const Rectangle& bottomRegion = _scrollBarBottomCap->getRegion();
+            const Theme::UVs& bottomUVs = _scrollBarBottomCap->getUVs();
+            Vector4 bottomColor = _scrollBarBottomCap->getColor();
+            bottomColor.w *= _scrollBarOpacity * _opacity;
+
+            clipRegion.width += verticalRegion.width;
+
+            Rectangle bounds(_viewportBounds.x + _viewportBounds.width - verticalRegion.width,
+                             _viewportBounds.y + _scrollBarBounds.y,
+                             topRegion.width, topRegion.height);
+            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, topUVs.u1, topUVs.v1, topUVs.u2, topUVs.v2, topColor, clipRegion);
+
+            bounds.y += topRegion.height;
+            bounds.height = _scrollBarBounds.height - topRegion.height - bottomRegion.height;
+            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, verticalUVs.u1, verticalUVs.v1, verticalUVs.u2, verticalUVs.v2, verticalColor, clipRegion);
+
+            bounds.y += bounds.height;
+            bounds.height = bottomRegion.height;
+            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, bottomUVs.u1, bottomUVs.v1, bottomUVs.u2, bottomUVs.v2, bottomColor, clipRegion);
         }
-    }
 
-    Control* Container::getControl(unsigned int index) const
+        if (_scrollBarBounds.width > 0)
+        {
+            const Rectangle& leftRegion = _scrollBarLeftCap->getRegion();
+            const Theme::UVs& leftUVs = _scrollBarLeftCap->getUVs();
+            Vector4 leftColor = _scrollBarLeftCap->getColor();
+            leftColor.w *= _scrollBarOpacity * _opacity;
+
+            const Rectangle& horizontalRegion = _scrollBarHorizontal->getRegion();
+            const Theme::UVs& horizontalUVs = _scrollBarHorizontal->getUVs();
+            Vector4 horizontalColor = _scrollBarHorizontal->getColor();
+            horizontalColor.w *= _scrollBarOpacity * _opacity;
+
+            const Rectangle& rightRegion = _scrollBarRightCap->getRegion();
+            const Theme::UVs& rightUVs = _scrollBarRightCap->getUVs();
+            Vector4 rightColor = _scrollBarRightCap->getColor();
+            rightColor.w *= _scrollBarOpacity * _opacity;
+
+            clipRegion.height += horizontalRegion.height;
+        
+            Rectangle bounds(_viewportBounds.x + _scrollBarBounds.x,
+                             _viewportBounds.y + _viewportBounds.height - horizontalRegion.height,
+                             leftRegion.width, leftRegion.height);
+            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, leftUVs.u1, leftUVs.v1, leftUVs.u2, leftUVs.v2, leftColor, clipRegion);
+
+            bounds.x += leftRegion.width;
+            bounds.width = _scrollBarBounds.width - leftRegion.width - rightRegion.width;
+            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, horizontalUVs.u1, horizontalUVs.v1, horizontalUVs.u2, horizontalUVs.v2, horizontalColor, clipRegion);
+
+            bounds.x += bounds.width;
+            bounds.width = rightRegion.width;
+            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, rightUVs.u1, rightUVs.v1, rightUVs.u2, rightUVs.v2, rightColor, clipRegion);
+        }
+
+        spriteBatch->end();
+
+        if (_scrollingVelocity.isZero())
+        {
+            _dirty = false;
+        }
+    }
+    else
     {
-        std::vector<Control*>::const_iterator it = _controls.begin() + index;
-        return *it;
+        _dirty = false;
     }
+}
 
-    Control* Container::getControl(const char* id) const
+bool Container::isDirty()
+{
+    if (_dirty)
+    {
+        return true;
+    }
+    else
     {
         std::vector<Control*>::const_iterator it;
         for (it = _controls.begin(); it < _controls.end(); it++)
         {
-            Control* c = *it;
-            if (strcmp(id, c->getID()) == 0)
-            {
-                return c;
-            }
-            else if (c->isContainer())
+            GP_ASSERT(*it);
+            if ((*it)->isDirty())
             {
-                Control* cc = ((Container*)c)->getControl(id);
-                if (cc)
-                {
-                    return cc;
-                }
+                return true;
             }
         }
+    }
 
-        return NULL;
+    return false;
+}
+
+bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    if (!isEnabled())
+    {
+        return false;
     }
 
-    std::vector<Control*> Container::getControls() const
+    bool eventConsumed = false;
+    const Theme::Border& border = getBorder(_state);
+    const Theme::Padding& padding = getPadding();
+    float xPos = border.left + padding.left;
+    float yPos = border.top + padding.top;
+
+    Vector2* offset = NULL;
+    if (_scroll != SCROLL_NONE)
     {
-        return _controls;
+        offset = &_scrollPosition;
     }
 
-    Animation* Container::getAnimation(const char* id) const
+    std::vector<Control*>::const_iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        std::vector<Control*>::const_iterator itr = _controls.begin();
-        std::vector<Control*>::const_iterator end = _controls.end();
-        
-        Control* control = NULL;
-        for (; itr != end; itr++)
+        Control* control = *it;
+        GP_ASSERT(control);
+        if (!control->isEnabled())
         {
-            control = *itr;
-            Animation* animation = control->getAnimation(id);
-            if (animation)
-                return animation;
+            continue;
+        }
 
-            if (control->isContainer())
-            {
-                animation = ((Container*)control)->getAnimation(id);
-                if (animation)
-                    return animation;
-            }
+        const Rectangle& bounds = control->getBounds();
+        float boundsX = bounds.x;
+        float boundsY = bounds.y;
+        if (offset)
+        {
+            boundsX += offset->x;
+            boundsY += offset->y;
         }
 
-        return NULL;
+        if (control->getState() != Control::NORMAL ||
+            (evt == Touch::TOUCH_PRESS &&
+                x >= xPos + boundsX &&
+                x <= xPos + boundsX + bounds.width &&
+                y >= yPos + boundsY &&
+                y <= yPos + boundsY + bounds.height))
+        {
+            // Pass on the event's clip relative to the control.
+            eventConsumed |= control->touchEvent(evt, x - xPos - boundsX, y - yPos - boundsY, contactIndex);
+        }
     }
 
-    void Container::update(const Rectangle& clip)
+    if (!isEnabled())
     {
-        // Update this container's viewport.
-        Control::update(clip);
-
-        _layout->update(this);
+        return (_consumeTouchEvents | eventConsumed);
     }
 
-    void Container::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
+    switch (evt)
     {
-        // First draw our own border.
-        Control::drawBorder(spriteBatch, clip);
+    case Touch::TOUCH_PRESS:
+        setState(Control::FOCUS);
+        break;
+    case Touch::TOUCH_RELEASE:
+        setState(Control::NORMAL);
+        break;
+    }
 
-        // Now call drawBorder on all controls within this container.
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+    if (!eventConsumed && _scroll != SCROLL_NONE)
+    {
+        if (touchEventScroll(evt, x - xPos, y - yPos, contactIndex))
         {
-            Control* control = *it;
-            control->drawBorder(spriteBatch, _clip);
+            _dirty = true;
         }
     }
 
-    void Container::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+    return (_consumeTouchEvents | eventConsumed);
+}
+
+void Container::keyEvent(Keyboard::KeyEvent evt, int key)
+{
+    std::vector<Control*>::const_iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        Control* control = *it;
+        GP_ASSERT(control);
+        if (!control->isEnabled())
         {
-            Control* control = *it;
-            control->drawImages(spriteBatch, _clip);
+            continue;
         }
 
-        _dirty = false;
+        if (control->isContainer() || control->getState() == Control::FOCUS)
+        {
+            control->keyEvent(evt, key);
+        }
     }
+}
 
-    void Container::drawText(const Rectangle& clip)
+bool Container::isContainer()
+{
+    return true;
+}
+
+Layout::Type Container::getLayoutType(const char* layoutString)
+{
+    if (!layoutString)
     {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            control->drawText(_clip);
-        }
+        return Layout::LAYOUT_ABSOLUTE;
+    }
 
-        _dirty = false;
+    std::string layoutName(layoutString);
+    std::transform(layoutName.begin(), layoutName.end(), layoutName.begin(), (int(*)(int))toupper);
+    if (layoutName == "LAYOUT_ABSOLUTE")
+    {
+        return Layout::LAYOUT_ABSOLUTE;
+    }
+    else if (layoutName == "LAYOUT_VERTICAL")
+    {
+        return Layout::LAYOUT_VERTICAL;
+    }
+    else if (layoutName == "LAYOUT_FLOW")
+    {
+        return Layout::LAYOUT_FLOW;
     }
+    else if (layoutName == "LAYOUT_SCROLL")
+    {
+        return Layout::LAYOUT_SCROLL;
+    }
+    else
+    {
+        // Default.
+        return Layout::LAYOUT_ABSOLUTE;
+    }
+}
 
-    bool Container::isDirty()
+void Container::updateScroll()
+{
+    // Update Time.
+    static long lastFrameTime = Game::getGameTime();
+    long frameTime = Game::getGameTime();
+    long elapsedTime = (frameTime - lastFrameTime);
+    lastFrameTime = frameTime;
+
+    const Theme::Border& containerBorder = getBorder(_state);
+    const Theme::Padding& containerPadding = getPadding();
+
+    // Calculate total width and height.
+    float totalWidth = 0;
+    float totalHeight = 0;
+    std::vector<Control*> controls = getControls();
+    unsigned int controlsCount = controls.size();
+    for (unsigned int i = 0; i < controlsCount; i++)
     {
-        if (_dirty)
+        Control* control = controls.at(i);
+
+        const Rectangle& bounds = control->getBounds();
+        const Theme::Margin& margin = control->getMargin();
+
+        float newWidth = bounds.x + bounds.width;
+        if (newWidth > totalWidth)
         {
-            return true;
+            totalWidth = newWidth;
         }
-        else
+
+        float newHeight = bounds.y + bounds.height;
+        if (newHeight > totalHeight)
         {
-            std::vector<Control*>::const_iterator it;
-            for (it = _controls.begin(); it < _controls.end(); it++)
-            {
-                if ((*it)->isDirty())
-                {
-                    return true;
-                }
-            }
+            totalHeight = newHeight;
         }
+    }
 
-        return false;
+    float vWidth = getImageRegion("verticalScrollBar", _state).width;
+    float hHeight = getImageRegion("horizontalScrollBar", _state).height;
+    float clipWidth = _bounds.width - containerBorder.left - containerBorder.right - containerPadding.left - containerPadding.right - vWidth;
+    float clipHeight = _bounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom - hHeight;
+
+    // Apply and dampen inertia.
+    if (!_scrolling && !_scrollingVelocity.isZero())
+    {
+        // Calculate the time passed since last update.
+        float elapsedSecs = (float)elapsedTime * 0.001f;
+
+        _scrollPosition.x += _scrollingVelocity.x * elapsedSecs;
+        _scrollPosition.y += _scrollingVelocity.y * elapsedSecs;
+
+        float dampening = 1.0f - _scrollingFriction * SCROLL_FRICTION_FACTOR * elapsedSecs;
+        _scrollingVelocity.x *= dampening;
+        _scrollingVelocity.y *= dampening;
+
+        if (fabs(_scrollingVelocity.x) < 100.0f)
+            _scrollingVelocity.x = 0.0f;
+        if (fabs(_scrollingVelocity.y) < 100.0f)
+            _scrollingVelocity.y = 0.0f;
     }
 
-    bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    // Stop scrolling when the far edge is reached.
+    if (-_scrollPosition.x > totalWidth - clipWidth)
     {
-        if (!isEnabled())
-        {
-            return false;
-        }
+        _scrollPosition.x = -(totalWidth - clipWidth);
+        _scrollingVelocity.x = 0;
+    }
+    
+    if (-_scrollPosition.y > totalHeight - clipHeight)
+    {
+        _scrollPosition.y = -(totalHeight - clipHeight);
+        _scrollingVelocity.y = 0;
+    }
 
-        bool eventConsumed = false;
+    if (_scrollPosition.x > 0)
+    {
+        _scrollPosition.x = 0;
+        _scrollingVelocity.x = 0;
+    }
 
-        const Theme::Border& border = getBorder(_state);
-        const Theme::Padding& padding = getPadding();
-        float xPos = border.left + padding.left;
-        float yPos = border.top + padding.top;
+    if (_scrollPosition.y > 0)
+    {
+        _scrollPosition.y = 0;
+        _scrollingVelocity.y = 0;
+    }
 
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+    float scrollWidth = 0;
+    if (clipWidth < totalWidth)
+        scrollWidth = (clipWidth / totalWidth) * clipWidth;
+
+    float scrollHeight = 0;
+    if (clipHeight < totalHeight)
+        scrollHeight = (clipHeight / totalHeight) * clipHeight;
+
+    _scrollBarBounds.set(((-_scrollPosition.x) / totalWidth) * clipWidth,
+                         ((-_scrollPosition.y) / totalHeight) * clipHeight,
+                         scrollWidth, scrollHeight);
+
+    // If scroll velocity is 0 and scrollbars are not always visible, trigger fade-out animation.
+    if (!_scrolling && _scrollingVelocity.isZero() && _scrollBarsAutoHide && _scrollBarOpacity == 1.0f)
+    {
+        float to = 0;
+        _scrollBarOpacity = 0.99f;
+        Animation* animation = createAnimationFromTo("scrollbar-fade-out", ANIMATE_OPACITY, &_scrollBarOpacity, &to, Curve::QUADRATIC_IN_OUT, 500L);
+        _scrollBarOpacityClip = animation->getClip();
+        _scrollBarOpacityClip->play();
+    }
+
+    // Position controls within scroll area.
+    _layout->update(this, _scrollPosition);
+}
+
+bool Container::touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    switch(evt)
+    {
+    case Touch::TOUCH_PRESS:
+        _scrollingLastX = _scrollingFirstX = x;
+        _scrollingLastY = _scrollingFirstY = y;
+        _scrollingVelocity.set(0, 0);
+        _scrolling = true;
+        _scrollingStartTimeX = _scrollingStartTimeY = 0;
+        
+        if (_scrollBarOpacityClip && _scrollBarOpacityClip->isPlaying())
+        {
+            _scrollBarOpacityClip->stop();
+            _scrollBarOpacityClip = NULL;
+        }
+        _scrollBarOpacity = 1.0f;
+        return true;
+
+    case Touch::TOUCH_MOVE:
+        if (_scrolling)
         {
-            Control* control = *it;
-            if (!control->isEnabled())
+            // Calculate the latest movement delta for the next update to use.
+            int vx = x - _scrollingLastX;
+            int vy = y - _scrollingLastY;
+            _scrollingVelocity.set(vx, vy);
+            _scrollPosition.x += vx;
+            _scrollPosition.y += vy;
+            _scrollingLastX = x;
+            _scrollingLastY = y;
+
+            // If the user changes direction, reset the start time and position.
+            bool goingRight = (vx > 0);
+            if (goingRight != _scrollingRight)
             {
-                continue;
+                _scrollingFirstX = x;
+                _scrollingRight = goingRight;
+                _scrollingStartTimeX = Game::getAbsoluteTime();
             }
 
-            const Rectangle& bounds = control->getClipBounds();
-            if (control->getState() != Control::NORMAL ||
-                (evt == Touch::TOUCH_PRESS &&
-                 x >= xPos + bounds.x &&
-                 x <= xPos + bounds.x + bounds.width &&
-                 y >= yPos + bounds.y &&
-                 y <= yPos + bounds.y + bounds.height))
+            bool goingDown = (vy > 0);
+            if (goingDown != _scrollingDown)
             {
-                // Pass on the event's clip relative to the control.
-                eventConsumed |= control->touchEvent(evt, x - xPos - bounds.x, y - yPos - bounds.y, contactIndex);
+                _scrollingFirstY = y;
+                _scrollingDown = goingDown;
+                _scrollingStartTimeY = Game::getAbsoluteTime();
             }
-        }
 
-        if (!isEnabled())
-        {
-            return (_consumeTouchEvents | eventConsumed);
+            if (!_scrollingStartTimeX)
+                _scrollingStartTimeX = Game::getAbsoluteTime();
+
+            if (!_scrollingStartTimeY)
+                _scrollingStartTimeY = Game::getAbsoluteTime();
+
+            _scrollingLastTime = Game::getAbsoluteTime();
+
+            return true;
         }
+        break;
 
-        switch (evt)
+    case Touch::TOUCH_RELEASE:
+        _scrolling = false;
+        long timeSinceLastMove = Game::getAbsoluteTime() - _scrollingLastTime;
+        if (timeSinceLastMove > SCROLL_INERTIA_DELAY)
         {
-        case Touch::TOUCH_PRESS:
-            setState(Control::FOCUS);
-            break;
-        case Touch::TOUCH_RELEASE:
-            setState(Control::NORMAL);
-            break;
+            _scrollingVelocity.set(0, 0);
+            return true;
         }
 
-        return (_consumeTouchEvents | eventConsumed);
-    }
+        int dx = _scrollingLastX - _scrollingFirstX;
+        int dy = _scrollingLastY - _scrollingFirstY;
 
-    void Container::keyEvent(Keyboard::KeyEvent evt, int key)
-    {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            if (!control->isEnabled())
-            {
-                continue;
-            }
+        long timeTakenX = Game::getAbsoluteTime() - _scrollingStartTimeX;
+        float elapsedSecsX = (float)timeTakenX * 0.001f;
+        long timeTakenY = Game::getAbsoluteTime() - _scrollingStartTimeY;
+        float elapsedSecsY = (float)timeTakenY * 0.001f;
 
-            if (control->isContainer() || control->getState() == Control::FOCUS)
-            {
-                control->keyEvent(evt, key);
-            }
-        }
+        float vx = dx;
+        float vy = dy;
+        if (elapsedSecsX > 0)
+            vx = (float)dx / elapsedSecsX;
+        if (elapsedSecsY > 0)
+            vy = (float)dy / elapsedSecsY;
+
+        _scrollingVelocity.set(vx, vy);
+
+        return true;
     }
 
-    bool Container::isContainer()
+    return false;
+}
+
+Container::Scroll Container::getScroll(const char* scroll)
+{
+    if (!scroll)
+        return Container::SCROLL_NONE;
+
+    if (strcmp(scroll, "SCROLL_NONE") == 0)
+    {
+        return Container::SCROLL_NONE;
+    }
+    else if (strcmp(scroll, "SCROLL_HORIZONTAL") == 0)
+    {
+        return Container::SCROLL_HORIZONTAL;
+    }
+    else if (strcmp(scroll, "SCROLL_VERTICAL") == 0)
     {
+        return Container::SCROLL_VERTICAL;
+    }
+    else if (strcmp(scroll, "SCROLL_BOTH") == 0)
+    {
+        return Container::SCROLL_BOTH;
+    }
+    else
+    {
+        GP_ERROR("Failed to get corresponding scroll state for unsupported value '%s'.", scroll);
+    }
+
+    return Container::SCROLL_NONE;
+}
+
+bool sortControlsByZOrder(Control* c1, Control* c2)
+{
+    if (c1->getZIndex() < c2->getZIndex())
         return true;
+
+    return false;
+}
+
+unsigned int Container::getAnimationPropertyComponentCount(int propertyId) const
+{
+    switch(propertyId)
+    {
+    case ANIMATE_OPACITY:
+        return 1;
+    default:
+        return Control::getAnimationPropertyComponentCount(propertyId);
     }
+}
 
-    Layout::Type Container::getLayoutType(const char* layoutString)
+void Container::getAnimationPropertyValue(int propertyId, AnimationValue* value)
+{
+    GP_ASSERT(value);
+
+    switch(propertyId)
     {
-        if (!layoutString)
-        {
-            return Layout::LAYOUT_ABSOLUTE;
-        }
+    case ANIMATE_OPACITY:
+        value->setFloat(0, _scrollBarOpacity);
+        break;
+    default:
+        Control::getAnimationPropertyValue(propertyId, value);
+        break;
+    }
+}
 
-        std::string layoutName(layoutString);
-        std::transform(layoutName.begin(), layoutName.end(), layoutName.begin(), (int(*)(int))toupper);
-        if (layoutName == "LAYOUT_ABSOLUTE")
-        {
-            return Layout::LAYOUT_ABSOLUTE;
-        }
-        else if (layoutName == "LAYOUT_VERTICAL")
-        {
-            return Layout::LAYOUT_VERTICAL;
-        }
-        else if (layoutName == "LAYOUT_FLOW")
-        {
-            return Layout::LAYOUT_FLOW;
-        }
-        else
-        {
-            // Default.
-            return Layout::LAYOUT_ABSOLUTE;
-        }
+void Container::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
+{
+    GP_ASSERT(value);
+
+    switch(propertyId)
+    {
+    case ANIMATE_OPACITY:
+        _scrollBarOpacity = Curve::lerp(blendWeight, _opacity, value->getFloat(0));
+        _dirty = true;
+        break;
+    default:
+        Control::setAnimationPropertyValue(propertyId, value, blendWeight);
+        break;
     }
 }
+
+}

+ 223 - 31
gameplay/src/Container.h

@@ -25,6 +25,8 @@ namespace gameplay
          size        = <width, height>   // Size of the container, measured in pixels.
          width       = <width>   // Can be used in place of 'size', e.g. with 'autoHeight = true'
          height      = <height>  // Can be used in place of 'size', e.g. with 'autoWidth = true'
+         scroll      = <Container::Scroll constant> // Whether scrolling is allowed and in which directions.
+         scrollBarsAutoHide = <bool>    // Whether scrollbars fade out when not in use.
   
          // All the nested controls within this container.
          container 
@@ -45,6 +47,22 @@ class Container : public Control
 {
 public:
 
+    /**
+     * Constant used to auto-hide scrollbars.
+     */
+    static const int ANIMATE_SCROLLBAR_OPACITY = 8;
+
+    /**
+     * The definition for container scrolling.
+     */
+    enum Scroll
+    {
+        SCROLL_NONE        = 0,
+        SCROLL_HORIZONTAL  = 0x01,
+        SCROLL_VERTICAL    = 0x02,
+        SCROLL_BOTH = SCROLL_HORIZONTAL | SCROLL_VERTICAL
+    };
+
     /**
      * Get this container's layout.
      *
@@ -112,16 +130,56 @@ public:
      *
      * @return The vector of the controls within this container.
      */
-    std::vector<Control*> getControls() const;
+    const std::vector<Control*>& getControls() const;
 
     /**
-     * Gets the first animation in the control with the specified ID.
+     * Sets the allowed scroll directions for this container.
      *
-     * @param id The ID of the animation to get. Returns the first animation if ID is NULL.
-     * @return The first animation with the specified ID.
+     * @param scroll The allowed scroll directions for this container.
+     */
+    void setScroll(Scroll scroll);
+
+    /**
+     * Gets the allowed scroll directions for this container.
+     *
+     * @return The allowed scroll directions for this container.
+     */
+    Scroll getScroll() const;
+
+    /**
+     * Set whether scrollbars auto hidden when they become static.
+     *
+     * @param autoHide true to auto hide the scrollbars when they become static.
+     */
+    void setScrollBarsAutoHide(bool autoHide);
+
+    /**
+     * Whether scrollbars are always visible, or only visible while scrolling.
+     *
+     * @return Whether scrollbars are always visible.
+     */
+    bool isScrollBarsAutoHide() const;
+
+    /**
+     * @see AnimationTarget#getAnimation
      */
     Animation* getAnimation(const char* id = NULL) const;
 
+    /**
+     * @see AnimationTarget#getAnimationPropertyComponentCount
+     */
+    virtual unsigned int getAnimationPropertyComponentCount(int propertyId) const;
+
+    /**
+     * @see AnimationTarget#getAnimationProperty
+     */
+    virtual void getAnimationPropertyValue(int propertyId, AnimationValue* value);
+
+    /**
+     * @see AnimationTarget#setAnimationProperty
+     */
+    virtual void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
+
 protected:
 
     /**
@@ -138,6 +196,8 @@ protected:
      * Create an empty container.  A container's layout type must be specified at creation time.
      *
      * @param type The container's layout type.
+     *
+     * @return The new container.
      */
     static Container* create(Layout::Type type);
 
@@ -156,32 +216,10 @@ protected:
      * Updates each control within this container,
      * and positions them according to the container's layout.
      *
-     * @param clip The clipping rectangle of this container's parent container.
-     */
-    virtual void update(const Rectangle& clip);
-
-    /**
-     * Draws the themed border and background of this container and all its controls.
-     *
-     * @param spriteBatch The sprite batch containing this container's border images.
-     * @param clip The clipping rectangle of this container's parent container.
+     * @param container This container's parent container.
+     * @param offset The offset.
      */
-    void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
-
-    /**
-     * Draws the icons of all controls within this container.
-     *
-     * @param spriteBatch The sprite batch containing this control's icons.
-     * @param clip The clipping rectangle of this container's parent container.
-     */
-    virtual void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
-
-    /**
-     * Draws the text of all controls within this container.
-     *
-     * @param clip The clipping rectangle of this container's parent container.
-     */
-    virtual void drawText(const Rectangle& clip);
+    virtual void update(const Control* container, const Vector2& offset);
 
     /**
      * Touch callback on touch events.  Controls return true if they consume the touch event.
@@ -211,41 +249,195 @@ protected:
 
     /**
      * Gets a Layout::Type enum from a matching string.
+     *
+     * @param layoutString The layout string to parse
      */
     static Layout::Type getLayoutType(const char* layoutString);
 
     /**
      * Returns whether this control is a container.
-     * This is true in this case.
+     * 
+     * @return true if this is a container, false if not.
      */
     bool isContainer();
 
     /**
      * Returns whether this container or any of its controls have been modified and require an update.
+     * 
+     * @return true if this container or any of its controls have been modified and require an update.
      */
     virtual bool isDirty();
 
     /**
      * Adds controls nested within a properties object to this container,
      * searching for styles within the given theme.
+     *
+     * @param theme The them to add controls from
+     * @param properties The properties to use.
      */
     void addControls(Theme* theme, Properties* properties);
 
+    /**
+     * Draws a sprite batch for the specified clipping rect .
+     *
+     * @param spriteBatch The sprite batch to use.
+     * @param clip The clipping rectangle.
+     * @param needsClear Whether it needs to be cleared.
+     * @param cleared Whether it was previously cleared
+     * @param targetHeight The targets height
+     */
+    virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight);
+
+    /**
+     * Update scroll position and velocity.
+     */
+    void updateScroll();
+
+    /**
+     * Applies touch events to scroll state.
+     *
+     * @param evt The touch event that occurred.
+     * @param x The x position of the touch in pixels. Left edge is zero.
+     * @param y The y position of the touch in pixels. Top edge is zero.
+     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
+     *
+     * @return Whether the touch event was consumed by scrolling within this container.
+     *
+     * @see Touch::TouchEvent
+     */
+    bool touchEventScroll(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    /**
+     * Get a Scroll enum from a matching string.
+     *
+     * @param scroll A string representing a Scroll enum.
+     *
+     * @return The Scroll enum value that matches the given string.
+     */
+    static Scroll getScroll(const char* scroll);
+
     /**
      * The container's layout.
      */
     Layout* _layout;
-
     /**
      * List of controls within the container.
      */
     std::vector<Control*> _controls;
+    /**
+     * Scrollbar top cap image.
+     */
+    Theme::ThemeImage* _scrollBarTopCap;
+    /**
+     * Scrollbar verticle image.
+     */
+    Theme::ThemeImage* _scrollBarVertical;
+    /**
+     * Scrollbar bottom cap image.
+     */
+    Theme::ThemeImage* _scrollBarBottomCap;
+    /**
+     * Scrollbar left cap image.
+     */
+    Theme::ThemeImage* _scrollBarLeftCap;
+    /**
+     * Scrollbar horizontal image.
+     */
+    Theme::ThemeImage* _scrollBarHorizontal;
+    /**
+     * Scrollbar horizontal image.
+     */
+    Theme::ThemeImage* _scrollBarRightCap;
+    /** 
+     * Flag representing whether scrolling is enabled, and in which directions.
+     */
+    Scroll _scroll;
+    /** 
+     * Scroll bar bounds
+     */
+    Rectangle _scrollBarBounds;
+    /** 
+     * How far this layout has been scrolled in each direction.
+     */
+    Vector2 _scrollPosition;
+    /** 
+     * Should the scrollbars auto hide. Default is false.
+     */
+    bool _scrollBarsAutoHide;
+    /** 
+     * Used to animate scrollbars fading out.
+     */
+    float _scrollBarOpacity;
+    /** 
+     * Whether the user is currently touching / holding the mouse down within this layout's container.
+     */
+    bool _scrolling;
+    /** 
+     * First scrolling touch x position
+     */ 
+    int _scrollingFirstX;
+    /** 
+     * First scrolling touch y position
+     */ 
+    int _scrollingFirstY;
+    /** 
+     * The last y position when scrolling
+     */ 
+    int _scrollingLastX;
+    /** 
+     * The last x position when scrolling
+     */ 
+    int _scrollingLastY;
+    /** 
+     * Time we started scrolling in the x
+     */ 
+    long _scrollingStartTimeX;
+    /** 
+     * Time we started scrolling in the y
+     */ 
+    long _scrollingStartTimeY;
+    /** 
+     * The last time we were scrolling
+     */
+    long _scrollingLastTime;
+    /** 
+     * Speed to continue scrolling at after touch release.
+     */ 
+    Vector2 _scrollingVelocity;
+    /** 
+     * Friction dampens velocity.
+     */ 
+    float _scrollingFriction;
+    /** 
+     * Are we scrolling to the right?
+     */ 
+    bool _scrollingRight;
+    /** 
+     * Are we scrolling down?
+     */ 
+    bool _scrollingDown;
 
 private:
 
+    /**
+     * Constructor.
+     */
     Container(const Container& copy);
+
+    AnimationClip* _scrollBarOpacityClip;
+    int _zIndexDefault;
 };
 
+
+/**
+ * Sort funtion for use with _controls.sort(), based on Z-Order.
+ * 
+ * @param c1 The first control
+ * @param c2 The second control
+ * return true if the first controls z index is less than the second.
+ */
+bool sortControlsByZOrder(Control* c1, Control* c2);
+
 }
 
 #endif

+ 1026 - 830
gameplay/src/Control.cpp

@@ -4,1116 +4,1312 @@
 
 namespace gameplay
 {
-    Control::Control()
-        : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _clip(Rectangle::empty()),
-            _dirty(true), _consumeTouchEvents(true), _listeners(NULL), _styleOverridden(false)
+
+Control::Control()
+    : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _viewportClipBounds(Rectangle::empty()),
+    _dirty(true), _consumeTouchEvents(true), _listeners(NULL), _styleOverridden(false), _skin(NULL), _clearBounds(Rectangle::empty())
+{
+}
+
+Control::Control(const Control& copy)
+{
+}
+
+Control::~Control()
+{
+    if (_listeners)
     {
+        for (std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
+        {
+            std::list<Listener*>* list = itr->second;
+            SAFE_DELETE(list);
+        }
+        SAFE_DELETE(_listeners);
     }
 
-    Control::Control(const Control& copy)
+    if (_styleOverridden)
     {
+        SAFE_DELETE(_style);
     }
+}
 
-    Control::~Control()
-    {
-        if (_listeners)
-        {
-            for (std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
-            {
-                std::list<Listener*>* list = itr->second;
-                SAFE_DELETE(list);
-            }
-            SAFE_DELETE(_listeners);
-        }
+void Control::initialize(Theme::Style* style, Properties* properties)
+{
+    GP_ASSERT(properties);
+    _style = style;
 
-        if (_styleOverridden)
-        {
-            SAFE_DELETE(_style);
-        }
+    // Properties not defined by the style.
+    _alignment = getAlignment(properties->getString("alignment"));
+    _autoWidth = properties->getBool("autoWidth");
+    _autoHeight = properties->getBool("autoHeight");
+
+    if (properties->exists("zIndex"))
+    {
+        _zIndex = properties->getInt("zIndex");
+    }
+    else
+    {
+        _zIndex = -1;
     }
 
-    void Control::initialize(Theme::Style* style, Properties* properties)
+    Vector2 position;
+    Vector2 size;
+    if (properties->exists("position"))
+    {
+        properties->getVector2("position", &position);
+    }
+    else
+    {
+        position.x = properties->getFloat("x");
+        position.y = properties->getFloat("y");
+    }
+        
+    if (properties->exists("size"))
     {
-        _style = style;
+        properties->getVector2("size", &size);
+    }
+    else
+    {
+        size.x = properties->getFloat("width");
+        size.y = properties->getFloat("height");
+    }
+    setBounds(Rectangle(position.x, position.y, size.x, size.y));
+
+    _state = Control::getState(properties->getString("state"));
 
-        // Properties not defined by the style.
-        _alignment = getAlignment(properties->getString("alignment"));
-        _autoWidth = properties->getBool("autoWidth");
-        _autoHeight = properties->getBool("autoHeight");
+    const char* id = properties->getId();
+    if (id)
+        _id = id;
 
-        Vector2 position;
-        Vector2 size;
-        if (properties->exists("position"))
+    // Potentially override themed properties for all states.
+    overrideThemedProperties(properties, STATE_ALL);
+
+    // Override themed properties on specific states.
+    Properties* innerSpace = properties->getNextNamespace();
+    while (innerSpace != NULL)
+    {
+        std::string spaceName(innerSpace->getNamespace());
+        std::transform(spaceName.begin(), spaceName.end(), spaceName.begin(), (int(*)(int))toupper);
+        if (spaceName == "STATENORMAL")
         {
-            properties->getVector2("position", &position);
+            overrideThemedProperties(innerSpace, NORMAL);
         }
-        else
+        else if (spaceName == "STATEFOCUS")
         {
-            position.x = properties->getFloat("x");
-            position.y = properties->getFloat("y");
+            overrideThemedProperties(innerSpace, FOCUS);
         }
-        
-        if (properties->exists("size"))
+        else if (spaceName == "STATEACTIVE")
         {
-            properties->getVector2("size", &size);
+            overrideThemedProperties(innerSpace, ACTIVE);
         }
-        else
+        else if (spaceName == "STATEDISABLED")
         {
-            size.x = properties->getFloat("width");
-            size.y = properties->getFloat("height");
+            overrideThemedProperties(innerSpace, DISABLED);
         }
-        _bounds.set(position.x, position.y, size.x, size.y);
-
-        _state = Control::getState(properties->getString("state"));
-
-        const char* id = properties->getId();
-        if (id)
-            _id = id;
-
-        // Potentially override themed properties for all states.
-        overrideThemedProperties(properties, STATE_ALL);
-
-        // Override themed properties on specific states.
-        Properties* stateSpace = properties->getNextNamespace();
-        while (stateSpace != NULL)
+        else if (spaceName == "MARGIN")
         {
-            std::string stateName(stateSpace->getNamespace());
-            std::transform(stateName.begin(), stateName.end(), stateName.begin(), (int(*)(int))toupper);
-            if (stateName == "STATENORMAL")
-            {
-                overrideThemedProperties(stateSpace, NORMAL);
-            }
-            else if (stateName == "STATEFOCUS")
-            {
-                overrideThemedProperties(stateSpace, FOCUS);
-            }
-            else if (stateName == "STATEACTIVE")
-            {
-                overrideThemedProperties(stateSpace, ACTIVE);
-            }
-            else if (stateName == "STATEDISABLED")
-            {
-                overrideThemedProperties(stateSpace, DISABLED);
-            }
-
-            stateSpace = properties->getNextNamespace();
+            setMargin(innerSpace->getFloat("top"), innerSpace->getFloat("bottom"),
+                innerSpace->getFloat("left"), innerSpace->getFloat("right"));
+        }
+        else if (spaceName == "PADDING")
+        {
+            setPadding(innerSpace->getFloat("top"), innerSpace->getFloat("bottom"),
+                innerSpace->getFloat("left"), innerSpace->getFloat("right"));
         }
-    }
 
-    const char* Control::getID() const
-    {
-        return _id.c_str();
+        innerSpace = properties->getNextNamespace();
     }
+}
+
+const char* Control::getID() const
+{
+    return _id.c_str();
+}
 
-    void Control::setPosition(float x, float y)
+void Control::setPosition(float x, float y)
+{
+    if (x != _bounds.x || y != _bounds.y)
     {
         _bounds.x = x;
         _bounds.y = y;
         _dirty = true;
     }
+}
 
-    void Control::setSize(float width, float height)
+void Control::setSize(float width, float height)
+{
+    if (width != _bounds.width || height != _bounds.height)
     {
         _bounds.width = width;
         _bounds.height = height;
         _dirty = true;
     }
+}
 
-    void Control::setBounds(const Rectangle& bounds)
+void Control::setBounds(const Rectangle& bounds)
+{
+    if (bounds != _bounds)
     {
         _bounds.set(bounds);
+        _dirty = true;
     }
+}
 
-    const Rectangle& Control::getBounds() const
-    {
-        return _bounds;
-    }
+const Rectangle& Control::getBounds() const
+{
+    return _bounds;
+}
 
-    float Control::getX() const
-    {
-        return _bounds.x;
-    }
+float Control::getX() const
+{
+    return _bounds.x;
+}
 
-    float Control::getY() const
-    {
-        return _bounds.y;
-    }
+float Control::getY() const
+{
+    return _bounds.y;
+}
 
-    float Control::getWidth() const
-    {
-        return _bounds.width;
-    }
+float Control::getWidth() const
+{
+    return _bounds.width;
+}
 
-    float Control::getHeight() const
-    {
-        return _bounds.height;
-    }
+float Control::getHeight() const
+{
+    return _bounds.height;
+}
 
-    void Control::setAlignment(Alignment alignment)
-    {
-        _alignment = alignment;
-    }
+void Control::setAlignment(Alignment alignment)
+{
+    _alignment = alignment;
+}
 
-    Control::Alignment Control::getAlignment() const
-    {
-        return _alignment;
-    }
+Control::Alignment Control::getAlignment() const
+{
+    return _alignment;
+}
 
-    void Control::setAutoWidth(bool autoWidth)
+void Control::setAutoWidth(bool autoWidth)
+{
+    if (_autoWidth != autoWidth)
     {
         _autoWidth = autoWidth;
+        _dirty = true;
     }
+}
 
-    bool Control::getAutoWidth() const
-    {
-        return _autoWidth;
-    }
+bool Control::getAutoWidth() const
+{
+    return _autoWidth;
+}
 
-    void Control::setAutoHeight(bool autoHeight)
+void Control::setAutoHeight(bool autoHeight)
+{
+    if (_autoHeight != autoHeight)
     {
         _autoHeight = autoHeight;
-    }
-
-    void Control::setOpacity(float opacity, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
-
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setOpacity(opacity);
-        }
-        
         _dirty = true;
     }
+}
 
-    float Control::getOpacity(State state) const
-    {
-        return getOverlay(state)->getOpacity();
-    }
+void Control::setOpacity(float opacity, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    void Control::setBorder(float top, float bottom, float left, float right, unsigned char states)
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+        overlays[i]->setOpacity(opacity);
+    }
+        
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setBorder(top, bottom, left, right);
-        }
+float Control::getOpacity(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getOpacity();
+}
 
-        _dirty = true;
-    }
+void Control::setBorder(float top, float bottom, float left, float right, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Theme::Border& Control::getBorder(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getBorder();
+        overlays[i]->setBorder(top, bottom, left, right);
     }
 
-    void Control::setSkinRegion(const Rectangle& region, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setSkinRegion(region, _style->_tw, _style->_th);
-        }
+const Theme::Border& Control::getBorder(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getBorder();
+}
 
-        _dirty = true;
-    }
+void Control::setSkinRegion(const Rectangle& region, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Rectangle& Control::getSkinRegion(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getSkinRegion();
+        overlays[i]->setSkinRegion(region, _style->_tw, _style->_th);
     }
 
-    const Theme::UVs& Control::getSkinUVs(Theme::Skin::SkinArea area, State state) const
-    {
-        return getOverlay(state)->getSkinUVs(area);
-    }
+    _dirty = true;
+}
 
-    void Control::setSkinColor(const Vector4& color, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+const Rectangle& Control::getSkinRegion(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getSkinRegion();
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setSkinColor(color);
-        }
+const Theme::UVs& Control::getSkinUVs(Theme::Skin::SkinArea area, State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getSkinUVs(area);
+}
 
-        _dirty = true;
-    }
+void Control::setSkinColor(const Vector4& color, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Vector4& Control::getSkinColor(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getSkinColor();
+        overlays[i]->setSkinColor(color);
     }
 
-    void Control::setMargin(float top, float bottom, float left, float right)
-    {
-        _style->setMargin(top, bottom, left, right);
-        _dirty = true;
-    }
+    _dirty = true;
+}
 
-    const Theme::Margin& Control::getMargin() const
-    {
-        return _style->getMargin();
-    }
+const Vector4& Control::getSkinColor(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getSkinColor();
+}
 
-    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::setMargin(float top, float bottom, float left, float right)
+{
+    GP_ASSERT(_style);
+    overrideStyle();
+    _style->setMargin(top, bottom, left, right);
+    _dirty = true;
+}
 
-    void Control::setImageRegion(const char* id, const Rectangle& region, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+const Theme::Margin& Control::getMargin() const
+{
+    GP_ASSERT(_style);
+    return _style->getMargin();
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setImageRegion(id, region, _style->_tw, _style->_th);
-        }
+void Control::setPadding(float top, float bottom, float left, float right)
+{
+    GP_ASSERT(_style);
+    overrideStyle();
+    _style->setPadding(top, bottom, left, right);
+    _dirty = true;
+}
+    
+const Theme::Padding& Control::getPadding() const
+{
+    GP_ASSERT(_style);
+    return _style->getPadding();
+}
 
-        _dirty = true;
-    }
+void Control::setImageRegion(const char* id, const Rectangle& region, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Rectangle& Control::getImageRegion(const char* id, State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getImageRegion(id);
+        overlays[i]->setImageRegion(id, region, _style->_tw, _style->_th);
     }
 
-    void Control::setImageColor(const char* id, const Vector4& color, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setImageColor(id, color);
-        }
+const Rectangle& Control::getImageRegion(const char* id, State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getImageRegion(id);
+}
 
-        _dirty = true;
-    }
+void Control::setImageColor(const char* id, const Vector4& color, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Vector4& Control::getImageColor(const char* id, State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getImageColor(id);
+        overlays[i]->setImageColor(id, color);
     }
 
-    const Theme::UVs& Control::getImageUVs(const char* id, State state) const
-    {
-        return getOverlay(state)->getImageUVs(id);
-    }
+    _dirty = true;
+}
 
-    void Control::setCursorRegion(const Rectangle& region, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+const Vector4& Control::getImageColor(const char* id, State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getImageColor(id);
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setCursorRegion(region, _style->_tw, _style->_th);
-        }
+const Theme::UVs& Control::getImageUVs(const char* id, State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getImageUVs(id);
+}
 
-        _dirty = true;
-    }
+void Control::setCursorRegion(const Rectangle& region, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Rectangle& Control::getCursorRegion(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getCursorRegion();
+        overlays[i]->setCursorRegion(region, _style->_tw, _style->_th);
     }
 
-    void Control::setCursorColor(const Vector4& color, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setCursorColor(color);
-        }
+const Rectangle& Control::getCursorRegion(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getCursorRegion();
+}
 
-        _dirty = true;
-    }
+void Control::setCursorColor(const Vector4& color, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Vector4& Control::getCursorColor(State state)
-    {
-        return getOverlay(state)->getCursorColor();
-    }
-    
-    const Theme::UVs& Control::getCursorUVs(State state)
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getCursorUVs();
+        overlays[i]->setCursorColor(color);
     }
 
-    void Control::setFont(Font* font, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setFont(font);
-        }
+const Vector4& Control::getCursorColor(State state)
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getCursorColor();
+}
+    
+const Theme::UVs& Control::getCursorUVs(State state)
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getCursorUVs();
+}
 
-        _dirty = true;
-    }
+void Control::setFont(Font* font, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    Font* Control::getFont(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getFont();
+        overlays[i]->setFont(font);
     }
 
-    void Control::setFontSize(unsigned int fontSize, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setFontSize(fontSize);
-        }
+Font* Control::getFont(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getFont();
+}
 
-        _dirty = true;
-    }
+void Control::setFontSize(unsigned int fontSize, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    unsigned int Control::getFontSize(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getFontSize();
+        overlays[i]->setFontSize(fontSize);
     }
 
-    void Control::setTextColor(const Vector4& color, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setTextColor(color);
-        }
+unsigned int Control::getFontSize(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getFontSize();
+}
 
-        _dirty = true;
-    }
+void Control::setTextColor(const Vector4& color, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Vector4& Control::getTextColor(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getTextColor();
+        overlays[i]->setTextColor(color);
     }
 
-    void Control::setTextAlignment(Font::Justify alignment, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setTextAlignment(alignment);
-        }
+const Vector4& Control::getTextColor(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getTextColor();
+}
 
-        _dirty = true;
-    }
+void Control::setTextAlignment(Font::Justify alignment, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    Font::Justify Control::getTextAlignment(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getTextAlignment();
+        overlays[i]->setTextAlignment(alignment);
     }
 
-    void Control::setTextRightToLeft(bool rightToLeft, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setTextRightToLeft(rightToLeft);
-        }
+Font::Justify Control::getTextAlignment(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getTextAlignment();
+}
 
-        _dirty = true;
-    }
+void Control::setTextRightToLeft(bool rightToLeft, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    bool Control::getTextRightToLeft(State state) const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return getOverlay(state)->getTextRightToLeft();
+        overlays[i]->setTextRightToLeft(rightToLeft);
     }
 
-    const Rectangle& Control::getClipBounds() const
-    {
-        return _clipBounds;
-    }
+    _dirty = true;
+}
 
-    const Rectangle& Control::getClip() const
+bool Control::getTextRightToLeft(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getTextRightToLeft();
+}
+
+const Rectangle& Control::getClipBounds() const
+{
+    return _clipBounds;
+}
+
+const Rectangle& Control::getClip() const
+{
+    return _viewportClipBounds;
+}
+
+void Control::setStyle(Theme::Style* style)
+{
+    if (style != _style)
     {
-        return _clip;
+        _dirty = true;
     }
 
-    void Control::setStyle(Theme::Style* style)
-    {
-        if (style != _style)
-        {
-            _dirty = true;
-        }
+    _style = style;
+}
 
-        _style = style;
-    }
+Theme::Style* Control::getStyle() const
+{
+    return _style;
+}
 
-    Theme::Style* Control::getStyle() const
-    {
-        return _style;
+void Control::setState(State state)
+{
+    if (getOverlay(_state) != getOverlay(state))
+        _dirty = true;
+
+    _state = state;
+}
+
+Control::State Control::getState() const
+{
+    return _state;
+}
+
+void Control::disable()
+{
+    _state = DISABLED;
+    _dirty = true;
+}
+
+void Control::enable()
+{
+    _state = NORMAL;
+    _dirty = true;
+}
+
+bool Control::isEnabled()
+{
+    return _state != DISABLED;
+}
+
+Theme::Style::OverlayType Control::getOverlayType() const
+{
+    switch (_state)
+    {
+    case Control::NORMAL:
+        return Theme::Style::OVERLAY_NORMAL;
+    case Control::FOCUS:
+        return Theme::Style::OVERLAY_FOCUS;
+    case Control::ACTIVE:
+        return Theme::Style::OVERLAY_ACTIVE;
+    case Control::DISABLED:
+        return Theme::Style::OVERLAY_DISABLED;
+    default:
+        return Theme::Style::OVERLAY_NORMAL;
     }
+}
+
+void Control::setConsumeTouchEvents(bool consume)
+{
+    _consumeTouchEvents = consume;
+}
+    
+bool Control::getConsumeTouchEvents()
+{
+    return _consumeTouchEvents;
+}
+
+int Control::getZIndex() const
+{
+    return _zIndex;
+}
 
-    void Control::setState(State state)
+void Control::setZIndex(int zIndex)
+{
+    if (zIndex != _zIndex)
     {
-        _state = state;
+        _zIndex = zIndex;
         _dirty = true;
     }
+}
+
+void Control::addListener(Control::Listener* listener, int eventFlags)
+{
+    GP_ASSERT(listener);
 
-    Control::State Control::getState() const
+    if ((eventFlags & Listener::PRESS) == Listener::PRESS)
     {
-        return _state;
+        addSpecificListener(listener, Listener::PRESS);
     }
 
-    void Control::disable()
+    if ((eventFlags & Listener::RELEASE) == Listener::RELEASE)
     {
-        _state = DISABLED;
-        _dirty = true;
+        addSpecificListener(listener, Listener::RELEASE);
     }
 
-    void Control::enable()
+    if ((eventFlags & Listener::CLICK) == Listener::CLICK)
     {
-        _state = NORMAL;
-        _dirty = true;
+        addSpecificListener(listener, Listener::CLICK);
     }
 
-    bool Control::isEnabled()
+    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
     {
-        return _state != DISABLED;
+        addSpecificListener(listener, Listener::VALUE_CHANGED);
     }
 
-    Theme::Style::OverlayType Control::getOverlayType() const
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
     {
-        switch (_state)
-        {
-        case Control::NORMAL:
-            return Theme::Style::OVERLAY_NORMAL;
-        case Control::FOCUS:
-            return Theme::Style::OVERLAY_FOCUS;
-        case Control::ACTIVE:
-            return Theme::Style::OVERLAY_ACTIVE;
-        case Control::DISABLED:
-            return Theme::Style::OVERLAY_DISABLED;
-        default:
-            return Theme::Style::OVERLAY_NORMAL;
-        }
+        addSpecificListener(listener, Listener::TEXT_CHANGED);
     }
+}
+
+void Control::addSpecificListener(Control::Listener* listener, Listener::EventType eventType)
+{
+    GP_ASSERT(listener);
 
-    void Control::setConsumeTouchEvents(bool consume)
+    if (!_listeners)
     {
-        _consumeTouchEvents = consume;
+        _listeners = new std::map<Listener::EventType, std::list<Listener*>*>();
     }
-    
-    bool Control::getConsumeTouchEvents()
+
+    std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->find(eventType);
+    if (itr == _listeners->end())
     {
-        return _consumeTouchEvents;
+        _listeners->insert(std::make_pair(eventType, new std::list<Listener*>()));
+        itr = _listeners->find(eventType);
     }
 
-    void Control::addListener(Control::Listener* listener, int eventFlags)
+    std::list<Listener*>* listenerList = itr->second;
+    listenerList->push_back(listener);
+}
+
+bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    if (!isEnabled())
     {
-        if ((eventFlags & Listener::PRESS) == Listener::PRESS)
-        {
-            addSpecificListener(listener, Listener::PRESS);
-        }
+        return false;
+    }
 
-        if ((eventFlags & Listener::RELEASE) == Listener::RELEASE)
-        {
-            addSpecificListener(listener, Listener::RELEASE);
-        }
+    switch (evt)
+    {
+    case Touch::TOUCH_PRESS:
+        notifyListeners(Listener::PRESS);
+        break;
+            
+    case Touch::TOUCH_RELEASE:
+        // Always trigger Listener::RELEASE
+        notifyListeners(Listener::RELEASE);
 
-        if ((eventFlags & Listener::CLICK) == Listener::CLICK)
+        // Only trigger Listener::CLICK if both PRESS and RELEASE took place within the control's bounds.
+        if (x > 0 && x <= _bounds.width &&
+            y > 0 && y <= _bounds.height)
         {
-            addSpecificListener(listener, Listener::CLICK);
+            notifyListeners(Listener::CLICK);
         }
+        break;
+    }
 
-        if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
-        {
-            addSpecificListener(listener, Listener::VALUE_CHANGED);
-        }
+    return _consumeTouchEvents;
+}
 
-        if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
-        {
-            addSpecificListener(listener, Listener::TEXT_CHANGED);
-        }
-    }
+void Control::keyEvent(Keyboard::KeyEvent evt, int key)
+{
+}
 
-    void Control::addSpecificListener(Control::Listener* listener, Listener::EventType eventType)
+void Control::notifyListeners(Listener::EventType eventType)
+{
+    if (_listeners)
     {
-        if (!_listeners)
-        {
-            _listeners = new std::map<Listener::EventType, std::list<Listener*>*>();
-        }
-
         std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->find(eventType);
-        if (itr == _listeners->end())
+        if (itr != _listeners->end())
         {
-            _listeners->insert(std::make_pair(eventType, new std::list<Listener*>()));
-            itr = _listeners->find(eventType);
+            std::list<Listener*>* listenerList = itr->second;
+            for (std::list<Listener*>::iterator listenerItr = listenerList->begin(); listenerItr != listenerList->end(); listenerItr++)
+            {
+                GP_ASSERT(*listenerItr);
+                (*listenerItr)->controlEvent(this, eventType);
+            }
         }
-
-        std::list<Listener*>* listenerList = itr->second;
-        listenerList->push_back(listener);
     }
+}
 
-    bool Control::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
-    {
-        if (!isEnabled())
-        {
-            return false;
-        }
+void Control::update(const Control* container, const Vector2& offset)
+{
+    const Rectangle& clip = container->getClip();
+    const Rectangle& absoluteViewport = container->_viewportBounds;
 
-        switch (evt)
-        {
-        case Touch::TOUCH_PRESS:
-            notifyListeners(Listener::PRESS);
-            break;
-            
-        case Touch::TOUCH_RELEASE:
-            // Always trigger Listener::RELEASE
-            notifyListeners(Listener::RELEASE);
+    _clearBounds.set(_absoluteClipBounds);
 
-            // Only trigger Listener::CLICK if both PRESS and RELEASE took place within the control's bounds.
-            if (x > 0 && x <= _clipBounds.width &&
-                y > 0 && y <= _clipBounds.height)
-            {
-                notifyListeners(Listener::CLICK);
-            }
-            break;
-        }
+    // Calculate the clipped bounds.
+    float x = _bounds.x + offset.x;
+    float y = _bounds.y + offset.y;
+    float width = _bounds.width;
+    float height = _bounds.height;
 
-        return _consumeTouchEvents;
-    }
+    float clipX2 = clip.x + clip.width;
+    float x2 = clip.x + x + width;
+    if (x2 > clipX2)
+        width -= x2 - clipX2;
 
-    void Control::keyEvent(Keyboard::KeyEvent evt, int key)
+    float clipY2 = clip.y + clip.height;
+    float y2 = clip.y + y + height;
+    if (y2 > clipY2)
+        height -= y2 - clipY2;
+
+    if (x < 0)
     {
+        width += x;
+        x = -x;
     }
-
-    void Control::notifyListeners(Listener::EventType eventType)
+    else
     {
-        if (_listeners)
-        {
-            std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->find(eventType);
-            if (itr != _listeners->end())
-            {
-                std::list<Listener*>* listenerList = itr->second;
-                for (std::list<Listener*>::iterator listenerItr = listenerList->begin(); listenerItr != listenerList->end(); listenerItr++)
-                {
-                    (*listenerItr)->controlEvent(this, eventType);
-                }
-            }
-        }
+        x = 0;
     }
 
-    void Control::update(const Rectangle& clip)
+    if (y < 0)
+    {
+        height += y;
+        y = -y;
+    }
+    else
     {
-        // Calculate the bounds.
-        float x = clip.x + _bounds.x;
-        float y = clip.y + _bounds.y;
-        float width = _bounds.width;
-        float height = _bounds.height;
+        y = 0;
+    }
 
-        float clipX2 = clip.x + clip.width;
-        float x2 = x + width;
-        if (x2 > clipX2)
-            width = clipX2 - x;
+    _clipBounds.set(x, y, width, height);
 
-        float clipY2 = clip.y + clip.height;
-        float y2 = y + height;
-        if (y2 > clipY2)
-            height = clipY2 - y;
+    // Calculate the absolute bounds.
+    x = _bounds.x + offset.x + absoluteViewport.x;
+    y = _bounds.y + offset.y + absoluteViewport.y;
+    _absoluteBounds.set(x, y, _bounds.width, _bounds.height);
 
-        _clipBounds.set(_bounds.x, _bounds.y, width, height);
+    // Calculate the absolute viewport bounds.
+    // Absolute bounds minus border and padding.
+    const Theme::Border& border = getBorder(_state);
+    const Theme::Padding& padding = getPadding();
 
-        // Calculate the clipping viewport.
-        const Theme::Border& border = getBorder(_state);
-        const Theme::Padding& padding = getPadding();
+    x += border.left + padding.left;
+    y += border.top + padding.top;
+    width = _bounds.width - border.left - padding.left - border.right - padding.right;
+    height = _bounds.height - border.top - padding.top - border.bottom - padding.bottom;
 
-        x +=  border.left + padding.left;
-        y +=  border.top + padding.top;
-        width = _bounds.width - border.left - padding.left - border.right - padding.right;
-        height = _bounds.height - border.top - padding.top - border.bottom - padding.bottom;
+    _viewportBounds.set(x, y, width, height);
 
-        _textBounds.set(x, y, width, height);
+    // Calculate the clip area.
+    // Absolute bounds, minus border and padding,
+    // clipped to the parent container's clip area.
+    clipX2 = clip.x + clip.width;
+    x2 = x + width;
+    if (x2 > clipX2)
+        width = clipX2 - x;
 
-        clipX2 = clip.x + clip.width;
-        x2 = x + width;
-        if (x2 > clipX2)
-            width = clipX2 - x;
+    clipY2 = clip.y + clip.height;
+    y2 = y + height;
+    if (y2 > clipY2)
+        height = clipY2 - y;
 
-        clipY2 = clip.y + clip.height;
-        y2 = y + height;
-        if (y2 > clipY2)
-            height = clipY2 - y;
+    if (x < clip.x)
+    {
+        float dx = clip.x - x;
+        width -= dx;
+        x = clip.x;
+    }
 
-        if (x < clip.x)
-            x = clip.x;
+    if (y < clip.y)
+    {
+        float dy = clip.y - y;
+        height -= dy;
+        y = clip.y;
+    }
+ 
+    _viewportClipBounds.set(x, y, width, height);
 
-        if (y < clip.y)
-            y = clip.y;
+    _absoluteClipBounds.set(x - border.left - padding.left, y - border.top - padding.top,
+        width + border.left + padding.left + border.right + padding.right,
+        height + border.top + padding.top + border.bottom + padding.bottom);
+    if (_clearBounds.isEmpty())
+    {
+        _clearBounds.set(_absoluteClipBounds);
+    }
 
-        _clip.set(x, y, width, height);
+    // Cache themed attributes for performance.
+    _skin = getSkin(_state);
+    _opacity = getOpacity(_state);
+}
 
-        _skin = getSkin(_state);
-        _opacity = getOpacity(_state);
+void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
+{
+    if (!spriteBatch || !_skin || _bounds.width <= 0 || _bounds.height <= 0)
+        return;
+
+    // Get the border and background images for this control's current state.
+    const Theme::UVs& topLeft = _skin->getUVs(Theme::Skin::TOP_LEFT);
+    const Theme::UVs& top = _skin->getUVs(Theme::Skin::TOP);
+    const Theme::UVs& topRight = _skin->getUVs(Theme::Skin::TOP_RIGHT);
+    const Theme::UVs& left = _skin->getUVs(Theme::Skin::LEFT);
+    const Theme::UVs& center = _skin->getUVs(Theme::Skin::CENTER);
+    const Theme::UVs& right = _skin->getUVs(Theme::Skin::RIGHT);
+    const Theme::UVs& bottomLeft = _skin->getUVs(Theme::Skin::BOTTOM_LEFT);
+    const Theme::UVs& bottom = _skin->getUVs(Theme::Skin::BOTTOM);
+    const Theme::UVs& bottomRight = _skin->getUVs(Theme::Skin::BOTTOM_RIGHT);
+
+    // Calculate screen-space positions.
+    const Theme::Border& border = getBorder(_state);
+    const Theme::Padding& padding = getPadding();
+    Vector4 skinColor = _skin->getColor();
+    skinColor.w *= _opacity;
+
+    float midWidth = _bounds.width - border.left - border.right;
+    float midHeight = _bounds.height - border.top - border.bottom;
+    float midX = _absoluteBounds.x + border.left;
+    float midY = _absoluteBounds.y + border.top;
+    float rightX = _absoluteBounds.x + _bounds.width - border.right;
+    float bottomY = _absoluteBounds.y + _bounds.height - border.bottom;
+
+    // Draw themed border sprites.
+    if (!border.left && !border.right && !border.top && !border.bottom)
+    {
+        // No border, just draw the image.
+        spriteBatch->draw(_absoluteBounds.x, _absoluteBounds.y, _bounds.width, _bounds.height, center.u1, center.v1, center.u2, center.v2, skinColor, clip);
+    }
+    else
+    {
+        if (border.left && border.top)
+            spriteBatch->draw(_absoluteBounds.x, _absoluteBounds.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, skinColor, clip);
+        if (border.top)
+            spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, skinColor, clip);
+        if (border.right && border.top)
+            spriteBatch->draw(rightX, _absoluteBounds.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
+        if (border.left)
+            spriteBatch->draw(_absoluteBounds.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(_absoluteBounds.x + border.left, _absoluteBounds.y + border.top, _bounds.width - border.left - border.right, _bounds.height - 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(_absoluteBounds.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::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
+void Control::drawImages(SpriteBatch* spriteBatch, const Rectangle& position)
+{
+}
+
+void Control::drawText(const Rectangle& position)
+{
+}
+
+void Control::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight)
+{
+    if (needsClear)
     {
-        if (!_skin || _bounds.width <= 0 || _bounds.height <= 0)
-            return;
+        GL_ASSERT( glEnable(GL_SCISSOR_TEST) );
+        GL_ASSERT( glClearColor(0, 0, 0, 0) );
+        GL_ASSERT( glScissor(_clearBounds.x, targetHeight - _clearBounds.y - _clearBounds.height,
+            _clearBounds.width, _clearBounds.height) );
+        GL_ASSERT( glClear(GL_COLOR_BUFFER_BIT) );
+        GL_ASSERT( glDisable(GL_SCISSOR_TEST) );
+    }
 
-        Vector2 pos(clip.x + _bounds.x, clip.y + _bounds.y);
+    spriteBatch->begin();
+    drawBorder(spriteBatch, clip);
+    drawImages(spriteBatch, clip);
+    spriteBatch->end();
 
-        // Get the border and background images for this control's current state.
-        const Theme::UVs& topLeft = _skin->getUVs(Theme::Skin::TOP_LEFT);
-        const Theme::UVs& top = _skin->getUVs(Theme::Skin::TOP);
-        const Theme::UVs& topRight = _skin->getUVs(Theme::Skin::TOP_RIGHT);
-        const Theme::UVs& left = _skin->getUVs(Theme::Skin::LEFT);
-        const Theme::UVs& center = _skin->getUVs(Theme::Skin::CENTER);
-        const Theme::UVs& right = _skin->getUVs(Theme::Skin::RIGHT);
-        const Theme::UVs& bottomLeft = _skin->getUVs(Theme::Skin::BOTTOM_LEFT);
-        const Theme::UVs& bottom = _skin->getUVs(Theme::Skin::BOTTOM);
-        const Theme::UVs& bottomRight = _skin->getUVs(Theme::Skin::BOTTOM_RIGHT);
+    drawText(clip);
+    _dirty = false;
+}
 
-        // Calculate screen-space positions.
-        const Theme::Border& border = getBorder(_state);
-        const Theme::Padding& padding = getPadding();
-        Vector4 skinColor = _skin->getColor();
-        skinColor.w *= _opacity;
+bool Control::isDirty()
+{
+    return _dirty;
+}
 
-        float midWidth = _bounds.width - border.left - border.right;
-        float midHeight = _bounds.height - border.top - border.bottom;
-        float midX = pos.x + border.left;
-        float midY = pos.y + border.top;
-        float rightX = pos.x + _bounds.width - border.right;
-        float bottomY = pos.y + _bounds.height - border.bottom;
+bool Control::isContainer()
+{
+    return false;
+}
 
-        // 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, _bounds.width, _bounds.height, 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, _bounds.width - border.left - border.right, _bounds.height - 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);
-        }
+Control::State Control::getState(const char* state)
+{
+    if (!state)
+    {
+        return NORMAL;
     }
 
-    void Control::drawImages(SpriteBatch* spriteBatch, const Rectangle& position)
+    if (strcmp(state, "NORMAL") == 0)
     {
+        return NORMAL;
     }
-
-    void Control::drawText(const Rectangle& position)
+    else if (strcmp(state, "ACTIVE") == 0)
     {
+        return ACTIVE;
     }
-
-    bool Control::isDirty()
+    else if (strcmp(state, "FOCUS") == 0)
     {
-        return _dirty;
+        return FOCUS;
     }
+    else if (strcmp(state, "DISABLED") == 0)
+    {
+        return DISABLED;
+    }
+
+    return NORMAL;
+}
+
+Theme::ThemeImage* Control::getImage(const char* id, State state)
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    
+    Theme::ImageList* imageList = overlay->getImageList();
+    if (!imageList)
+        return NULL;
+
+    return imageList->getImage(id);
+}
 
-    bool Control::isContainer()
+// Implementation of AnimationHandler
+unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
+{
+    switch(propertyId)
     {
-        return false;
+    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;
     }
+}
 
-    Control::State Control::getState(const char* state)
+void Control::getAnimationPropertyValue(int propertyId, AnimationValue* value)
+{
+    GP_ASSERT(value);
+
+    switch(propertyId)
+    {
+    case ANIMATE_POSITION:
+        value->setFloat(0, _bounds.x);
+        value->setFloat(1, _bounds.y);
+        break;
+    case ANIMATE_SIZE:
+        value->setFloat(0, _bounds.width);
+        value->setFloat(1, _bounds.height);
+        break;
+    case ANIMATE_POSITION_X:
+        value->setFloat(0, _bounds.x);
+        break;
+    case ANIMATE_POSITION_Y:
+        value->setFloat(0, _bounds.y);
+        break;
+    case ANIMATE_SIZE_WIDTH:
+        value->setFloat(0, _bounds.width);
+        break;
+    case ANIMATE_SIZE_HEIGHT:
+        value->setFloat(0, _bounds.height);
+        break;
+    case ANIMATE_OPACITY:
+    default:
+        break;
+    }
+}
+
+void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
+{
+    GP_ASSERT(value);
+
+    switch(propertyId)
     {
-        if (!state)
-        {
-            return NORMAL;
-        }
+    case ANIMATE_POSITION:
+        _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
+        _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(1));
+        _dirty = true;
+        break;
+    case ANIMATE_POSITION_X:
+        _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
+        _dirty = true;
+        break;
+    case ANIMATE_POSITION_Y:
+        _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(0));
+        _dirty = true;
+        break;
+    case ANIMATE_SIZE:
+        _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
+        _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(1));
+        _dirty = true;
+        break;
+    case ANIMATE_SIZE_WIDTH:
+        _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
+        _dirty = true;
+        break;
+    case ANIMATE_SIZE_HEIGHT:
+        _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(0));
+        _dirty = true;
+        break;
+    case ANIMATE_OPACITY:
+        _dirty = true;
+    default:
+        break;
+    }
+}
+    
 
-        if (strcmp(state, "NORMAL") == 0)
-        {
-            return NORMAL;
-        }
-        else if (strcmp(state, "ACTIVE") == 0)
-        {
-            return ACTIVE;
-        }
-        else if (strcmp(state, "FOCUS") == 0)
-        {
-            return FOCUS;
-        }
-        else if (strcmp(state, "DISABLED") == 0)
-        {
-            return DISABLED;
-        }
+Theme::Style::Overlay** Control::getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays)
+{
+    GP_ASSERT(overlays);
+    GP_ASSERT(_style);
 
-        return NORMAL;
+    unsigned int index = 0;
+    if ((overlayTypes & NORMAL) == NORMAL)
+    {
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
     }
 
-    Theme::ThemeImage* Control::getImage(const char* id, State state)
+    if ((overlayTypes & FOCUS) == FOCUS)
     {
-        return getOverlay(state)->getImageList()->getImage(id);
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
     }
 
-    // Implementation of AnimationHandler
-    unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
+    if ((overlayTypes & ACTIVE) == ACTIVE)
     {
-        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;
-        }
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
     }
 
-    void Control::getAnimationPropertyValue(int propertyId, AnimationValue* value)
+    if ((overlayTypes & DISABLED) == DISABLED)
     {
-        switch(propertyId)
-        {
-        case ANIMATE_POSITION:
-            value->setFloat(0, _bounds.x);
-            value->setFloat(1, _bounds.y);
-            break;
-        case ANIMATE_SIZE:
-            value->setFloat(0, _bounds.width);
-            value->setFloat(1, _bounds.height);
-            break;
-        case ANIMATE_POSITION_X:
-            value->setFloat(0, _bounds.x);
-            break;
-        case ANIMATE_POSITION_Y:
-            value->setFloat(0, _bounds.y);
-            break;
-        case ANIMATE_SIZE_WIDTH:
-            value->setFloat(0, _bounds.width);
-            break;
-        case ANIMATE_SIZE_HEIGHT:
-            value->setFloat(0, _bounds.height);
-            break;
-        case ANIMATE_OPACITY:
-        default:
-            break;
-        }
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
     }
 
-    void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
+    return overlays;
+}
+
+Theme::Style::Overlay* Control::getOverlay(State state) const
+{
+    GP_ASSERT(_style);
+
+    switch(state)
     {
-        switch(propertyId)
-        {
-        case ANIMATE_POSITION:
-            _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
-            _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(1));
-            _dirty = true;
-            break;
-        case ANIMATE_POSITION_X:
-            _bounds.x = Curve::lerp(blendWeight, _bounds.x, value->getFloat(0));
-            _dirty = true;
-            break;
-        case ANIMATE_POSITION_Y:
-            _bounds.y = Curve::lerp(blendWeight, _bounds.y, value->getFloat(0));
-            _dirty = true;
-            break;
-        case ANIMATE_SIZE:
-            _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
-            _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(1));
-            _dirty = true;
-            break;
-        case ANIMATE_SIZE_WIDTH:
-            _bounds.width = Curve::lerp(blendWeight, _bounds.width, value->getFloat(0));
-            _dirty = true;
-            break;
-        case ANIMATE_SIZE_HEIGHT:
-            _bounds.height = Curve::lerp(blendWeight, _bounds.height, value->getFloat(0));
-            _dirty = true;
-            break;
-        case ANIMATE_OPACITY:
-            _dirty = true;
-        default:
-            break;
-        }
+    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;
     }
-    
-    Theme::Style::Overlay** Control::getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays)
+}
+
+void Control::overrideStyle()
+{
+    if (_styleOverridden)
     {
-        unsigned int index = 0;
-        if ((overlayTypes & NORMAL) == NORMAL)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
-        }
+        return;
+    }
 
-        if ((overlayTypes & FOCUS) == FOCUS)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
-        }
+    // Copy the style.
+    GP_ASSERT(_style);
+    _style = new Theme::Style(*_style);
+    _styleOverridden = true;
+}
 
-        if ((overlayTypes & ACTIVE) == ACTIVE)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
-        }
+void Control::overrideThemedProperties(Properties* properties, unsigned char states)
+{
+    GP_ASSERT(properties);
+    GP_ASSERT(_style);
+    GP_ASSERT(_style->_theme);
 
-        if ((overlayTypes & DISABLED) == DISABLED)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
-        }
+    Theme::ImageList* imageList = NULL;
+    Theme::ThemeImage* cursor = NULL;
+    Theme::Skin* skin = NULL;
+    _style->_theme->lookUpSprites(properties, &imageList, &cursor, &skin);
 
-        return overlays;
+    if (imageList)
+    {
+        setImageList(imageList, states);
     }
 
-    Theme::Style::Overlay* Control::getOverlay(State state) const
+    if (cursor)
     {
-        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;
-        }
+        setCursor(cursor, states);
     }
 
-    void Control::overrideStyle()
+    if (skin)
     {
-        if (_styleOverridden)
-        {
-            return;
-        }
+        setSkin(skin, states);
+    }
 
-        // Copy the style.
-        WARN_VARG("%d", sizeof(Theme::Style::Overlay));
-        _style = new Theme::Style(*_style);
-        _styleOverridden = true;
+    if (properties->exists("font"))
+    {
+        Font* font = Font::create(properties->getString("font"));
+        setFont(font, states);
+        font->release();
     }
 
-    void Control::overrideThemedProperties(Properties* properties, unsigned char states)
+    if (properties->exists("fontSize"))
     {
-        Theme::ImageList* imageList = NULL;
-        Theme::ThemeImage* cursor = NULL;
-        Theme::Skin* skin = NULL;
-        _style->_theme->lookUpSprites(properties, &imageList, &cursor, &skin);
+        setFontSize(properties->getInt("fontSize"), states);
+    }
 
-        if (imageList)
-        {
-            setImageList(imageList, states);
-        }
+    if (properties->exists("textColor"))
+    {
+        Vector4 textColor(0, 0, 0, 1);
+        properties->getColor("textColor", &textColor);
+        setTextColor(textColor, states);
+    }
 
-        if (cursor)
-        {
-            setCursor(cursor, states);
-        }
+    if (properties->exists("textAlignment"))
+    {
+        setTextAlignment(Font::getJustify(properties->getString("textAlignment")), states);
+    }
 
-        if (skin)
-        {
-            setSkin(skin, states);
-        }
+    if (properties->exists("rightToLeft"))
+    {
+        setTextRightToLeft(properties->getBool("rightToLeft"), states);
+    }
 
-        if (properties->exists("font"))
-        {
-            Font* font = Font::create(properties->getString("font"));
-            setFont(font, states);
-            font->release();
-        }
+    if (properties->exists("opacity"))
+    {
+        setOpacity(properties->getFloat("opacity"), states);
+    }
+}
 
-        if (properties->exists("fontSize"))
-        {
-            setFontSize(properties->getInt("fontSize"), states);
-        }
+void Control::setImageList(Theme::ImageList* imageList, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-        if (properties->exists("textColor"))
-        {
-            Vector4 textColor(0, 0, 0, 1);
-            properties->getColor("textColor", &textColor);
-            setTextColor(textColor, states);
-        }
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
+    {
+        overlays[i]->setImageList(imageList);
+    }
 
-        if (properties->exists("textAlignment"))
-        {
-            setTextAlignment(Font::getJustify(properties->getString("textAlignment")), states);
-        }
+    _dirty = true;
+}
 
-        if (properties->exists("rightToLeft"))
-        {
-            setTextRightToLeft(properties->getBool("rightToLeft"), states);
-        }
+void Control::setCursor(Theme::ThemeImage* cursor, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-        if (properties->exists("opacity"))
-        {
-            setOpacity(properties->getFloat("opacity"), states);
-        }
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
+    {
+        overlays[i]->setCursor(cursor);
     }
 
-    void Control::setImageList(Theme::ImageList* imageList, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setImageList(imageList);
-        }
+void Control::setSkin(Theme::Skin* skin, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-        _dirty = true;
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
+    {
+        overlays[i]->setSkin(skin);
     }
 
-    void Control::setCursor(Theme::ThemeImage* cursor, unsigned char states)
-    {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
+    _dirty = true;
+}
 
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setCursor(cursor);
-        }
+Theme::Skin* Control::getSkin(State state)
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getSkin();
+}
 
-        _dirty = true;
+Control::Alignment Control::getAlignment(const char* alignment)
+{
+    if (!alignment)
+    {
+        return Control::ALIGN_TOP_LEFT;
     }
 
-    void Control::setSkin(Theme::Skin* skin, unsigned char states)
+    if (strcmp(alignment, "ALIGN_LEFT") == 0)
     {
-        overrideStyle();
-        Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
-        getOverlays(states, overlays);
-
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setSkin(skin);
-        }
-
-        _dirty = true;
+        return Control::ALIGN_LEFT;
     }
-
-    Theme::Skin* Control::getSkin(State state)
+    else if (strcmp(alignment, "ALIGN_HCENTER") == 0)
     {
-        return getOverlay(state)->getSkin();
+        return Control::ALIGN_HCENTER;
     }
-
-    Control::Alignment Control::getAlignment(const char* alignment)
+    else if (strcmp(alignment, "ALIGN_RIGHT") == 0)
+    {
+        return Control::ALIGN_RIGHT;
+    }
+    else if (strcmp(alignment, "ALIGN_TOP") == 0)
+    {
+        return Control::ALIGN_TOP;
+    }
+    else if (strcmp(alignment, "ALIGN_VCENTER") == 0)
+    {
+        return Control::ALIGN_VCENTER;
+    }
+    else if (strcmp(alignment, "ALIGN_BOTTOM") == 0)
+    {
+        return Control::ALIGN_BOTTOM;
+    }
+    else if (strcmp(alignment, "ALIGN_TOP_LEFT") == 0)
     {
-        if (!alignment)
-        {
-            return Control::ALIGN_TOP_LEFT;
-        }
-
-        if (strcmp(alignment, "ALIGN_LEFT") == 0)
-        {
-            return Control::ALIGN_LEFT;
-        }
-        else if (strcmp(alignment, "ALIGN_HCENTER") == 0)
-        {
-            return Control::ALIGN_HCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_RIGHT") == 0)
-        {
-            return Control::ALIGN_RIGHT;
-        }
-        else if (strcmp(alignment, "ALIGN_TOP") == 0)
-        {
-            return Control::ALIGN_TOP;
-        }
-        else if (strcmp(alignment, "ALIGN_VCENTER") == 0)
-        {
-            return Control::ALIGN_VCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_BOTTOM") == 0)
-        {
-            return Control::ALIGN_BOTTOM;
-        }
-        else if (strcmp(alignment, "ALIGN_TOP_LEFT") == 0)
-        {
-            return Control::ALIGN_TOP_LEFT;
-        }
-        else if (strcmp(alignment, "ALIGN_VCENTER_LEFT") == 0)
-        {
-            return Control::ALIGN_VCENTER_LEFT;
-        }
-        else if (strcmp(alignment, "ALIGN_BOTTOM_LEFT") == 0)
-        {
-            return Control::ALIGN_BOTTOM_LEFT;
-        }
-        else if (strcmp(alignment, "ALIGN_TOP_HCENTER") == 0)
-        {
-            return Control::ALIGN_TOP_HCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_VCENTER_HCENTER") == 0)
-        {
-            return Control::ALIGN_VCENTER_HCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_BOTTOM_HCENTER") == 0)
-        {
-            return Control::ALIGN_BOTTOM_HCENTER;
-        }
-        else if (strcmp(alignment, "ALIGN_TOP_RIGHT") == 0)
-        {
-            return Control::ALIGN_TOP_RIGHT;
-        }
-        else if (strcmp(alignment, "ALIGN_VCENTER_RIGHT") == 0)
-        {
-            return Control::ALIGN_VCENTER_RIGHT;
-        }
-        else if (strcmp(alignment, "ALIGN_BOTTOM_RIGHT") == 0)
-        {
-            return Control::ALIGN_BOTTOM_RIGHT;
-        }
-
-        // Default.
         return Control::ALIGN_TOP_LEFT;
     }
+    else if (strcmp(alignment, "ALIGN_VCENTER_LEFT") == 0)
+    {
+        return Control::ALIGN_VCENTER_LEFT;
+    }
+    else if (strcmp(alignment, "ALIGN_BOTTOM_LEFT") == 0)
+    {
+        return Control::ALIGN_BOTTOM_LEFT;
+    }
+    else if (strcmp(alignment, "ALIGN_TOP_HCENTER") == 0)
+    {
+        return Control::ALIGN_TOP_HCENTER;
+    }
+    else if (strcmp(alignment, "ALIGN_VCENTER_HCENTER") == 0)
+    {
+        return Control::ALIGN_VCENTER_HCENTER;
+    }
+    else if (strcmp(alignment, "ALIGN_BOTTOM_HCENTER") == 0)
+    {
+        return Control::ALIGN_BOTTOM_HCENTER;
+    }
+    else if (strcmp(alignment, "ALIGN_TOP_RIGHT") == 0)
+    {
+        return Control::ALIGN_TOP_RIGHT;
+    }
+    else if (strcmp(alignment, "ALIGN_VCENTER_RIGHT") == 0)
+    {
+        return Control::ALIGN_VCENTER_RIGHT;
+    }
+    else if (strcmp(alignment, "ALIGN_BOTTOM_RIGHT") == 0)
+    {
+        return Control::ALIGN_BOTTOM_RIGHT;
+    }
+    else
+    {
+        GP_ERROR("Failed to get corresponding control alignment for unsupported value '%s'.", alignment);
+    }
+
+    // Default.
+    return Control::ALIGN_TOP_LEFT;
+}
+
 }

+ 67 - 20
gameplay/src/Control.h

@@ -192,7 +192,7 @@ public:
      * @param width The width.
      * @param height The height.
      */
-    void setSize(float width, float height);
+    virtual void setSize(float width, float height);
 
     /**
      * Set the bounds of this control, relative to its parent container and including its
@@ -200,7 +200,7 @@ public:
      *
      * @param bounds The new bounds to set.
      */
-    void setBounds(const Rectangle& bounds);
+    virtual void setBounds(const Rectangle& bounds);
 
     /**
      * Get the bounds of this control, relative to its parent container and including its
@@ -257,7 +257,7 @@ public:
      *
      * @param autoWidth Whether to size this control to fit horizontally within its parent container.
      */
-    void setAutoWidth(bool autoWidth);
+    virtual void setAutoWidth(bool autoWidth);
 
     /**
      * Get whether this control's width is set to automatically adjust to
@@ -272,7 +272,7 @@ public:
      *
      * @param autoHeight Whether to size this control to fit vertically within its parent container.
      */
-    void setAutoHeight(bool autoHeight);
+    virtual void setAutoHeight(bool autoHeight);
 
     /**
      * Get whether this control's height is set to automatically adjust to
@@ -658,6 +658,20 @@ public:
      */
     Theme::Style* getStyle() const;
 
+    /**
+     * Get this control's z-index.
+     *
+     * @return This control's z-index.
+     */
+    int getZIndex() const;
+
+    /**
+     * Set this control's z-index.
+     *
+     * @param zIndex The new z-index.
+     */
+    void setZIndex(int zIndex);
+
     /**
      * Add a listener to be notified of specific events affecting
      * this control.  Event types can be OR'ed together.
@@ -673,17 +687,17 @@ public:
     /**
      * @see AnimationTarget#getAnimationPropertyComponentCount
      */
-    unsigned int getAnimationPropertyComponentCount(int propertyId) const;
+    virtual unsigned int getAnimationPropertyComponentCount(int propertyId) const;
 
     /**
      * @see AnimationTarget#getAnimationProperty
      */
-    void getAnimationPropertyValue(int propertyId, AnimationValue* value);
+    virtual void getAnimationPropertyValue(int propertyId, AnimationValue* value);
 
     /**
      * @see AnimationTarget#setAnimationProperty
      */
-    void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
+    virtual void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
 
 protected:
 
@@ -734,9 +748,10 @@ protected:
      * Called when a control's properties change.  Updates this control's internal rendering
      * properties, such as its text viewport.
      *
-     * @param clip The clipping rectangle of this control's parent container.
+     * @param container This control's parent container.
+     * @param offset Positioning offset to add to the control's position.
      */
-    virtual void update(const Rectangle& clip);
+    virtual void update(const Control* container, const Vector2& offset);
 
     /**
      * Draw the images associated with this control.
@@ -753,6 +768,17 @@ protected:
      */
     virtual void drawText(const Rectangle& clip);
 
+    /**
+     * Draws a sprite batch for the specified clipping rect .
+     *
+     * @param spriteBatch The sprite batch to use.
+     * @param clip The clipping rectangle.
+     * @param needsClear Whether it needs to be cleared.
+     * @param cleared Whether it was previously cleared
+     * @param targetHeight The targets height
+     */
+    virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, bool cleared, float targetHeight);
+
     /**
      * Initialize properties common to STATE_ALL Controls.
      */
@@ -784,8 +810,9 @@ protected:
     /**
      * Get a Theme::ThemeImage from its ID, for a given state.
      *
-     * @param id The ID of the image to retrieve
+     * @param id The ID of the image to retrieve.
      * @param state The state to get this image from.
+     *
      * @return The requested Theme::ThemeImage, or NULL if none was found.
      */
     Theme::ThemeImage* getImage(const char* id, State state);
@@ -819,24 +846,39 @@ protected:
      * Position, relative to parent container's clipping window, and desired size.
      */
     Rectangle _bounds;
-    
+
     /**
-     * The position and size of this control, relative to parent container's bounds, including border and padding, after clipping.
+     * Position, relative to parent container's clipping window, including border and padding, after clipping.
      */
     Rectangle _clipBounds;
-    
+
     /**
-     * The position and size of this control's text area, before clipping.  Used for text alignment.
+     * Absolute bounds, including border and padding, before clipping.
      */
-    Rectangle _textBounds;
-    
+    Rectangle _absoluteBounds;
+
     /**
-     * Clipping window of this control's content, after clipping.
+     * Absolute bounds, including border and padding, after clipping.
      */
-    Rectangle _clip;
-    
+    Rectangle _absoluteClipBounds;
+
     /**
-     * Flag for whether the Control is dirty.
+     * Absolute bounds of content area (i.e. without border and padding), before clipping.
+     */
+    Rectangle _viewportBounds;
+
+    /**
+     * Absolute bounds of content area (i.e. without border and padding), after clipping.
+     */
+    Rectangle _viewportClipBounds;
+
+    /**
+     * Previous frame's absolute clip bounds, to be cleared if necessary.
+     */
+    Rectangle _clearBounds;         
+
+    /**
+     * If the control is dirty and need updating.
      */
     bool _dirty;
     
@@ -874,6 +916,11 @@ protected:
      * The current opacity of the control.
      */
     float _opacity;
+    
+    /**
+     * The z-order of the control.
+     */
+    int _zIndex;
 
 private:
 

+ 1 - 5
gameplay/src/Curve.cpp

@@ -119,7 +119,7 @@ void Curve::setPoint(unsigned int index, float time, float* value, Interpolation
 
 void Curve::setPoint(unsigned int index, float time, float* value, InterpolationType type, float* inValue, float* outValue)
 {
-    assert(index < _pointCount && time >= 0.0f && time <= 1.0f && !(index == 0 && time != 0.0f) && !(_pointCount != 1 && index == _pointCount - 1 && time != 1.0f));
+    assert(index < _pointCount && time >= 0.0f && time <= 1.0f && !(_pointCount > 1 && index == 0 && time != 0.0f) && !(_pointCount != 1 && index == _pointCount - 1 && time != 1.0f));
 
     _points[index].time = time;
     _points[index].type = type;
@@ -1123,13 +1123,9 @@ void Curve::interpolateQuaternion(float s, float* from, float* to, float* dst) c
 {
     // Evaluate.
     if (s >= 0)
-    {
         Quaternion::slerp(from[0], from[1], from[2], from[3], to[0], to[1], to[2], to[3], s, dst, dst + 1, dst + 2, dst + 3);
-    }
     else
         Quaternion::slerp(to[0], to[1], to[2], to[3], from[0], from[1], from[2], from[3], s, dst, dst + 1, dst + 2, dst + 3);
-
-    //((Quaternion*) dst)->normalize();
 }
 
 int Curve::determineIndex(float time) const

+ 10 - 10
gameplay/src/DebugNew.cpp

@@ -122,7 +122,7 @@ void* debugAlloc(std::size_t size, const char* file, int line)
         if (!initialized)
         {
             if (!SymInitialize(GetCurrentProcess(), NULL, true))
-                gameplay::printError("Stack trace tracking will not work.");
+                gameplay::printError("Stack trace tracking will not work.\n");
             initialized = true;
         }
     
@@ -181,7 +181,7 @@ void debugFree(void* p)
     // Sanity check: ensure that address in record matches passed in address
     if (rec->address != (unsigned long)p)
     {
-        gameplay::printError("[memory] CORRUPTION: Attempting to free memory address with invalid memory allocation record.");
+        gameplay::printError("[memory] CORRUPTION: Attempting to free memory address with invalid memory allocation record.\n");
         return;
     }
 
@@ -221,7 +221,7 @@ void printStackTrace(MemoryAllocationRecord* rec)
         symbol->MaxNameLength = bufferSize;
         if (!SymGetSymFromAddr64(GetCurrentProcess(), pc, &displacement, symbol))
         {
-            gameplay::printError("[memory] STACK TRACE: <unknown location>");
+            gameplay::printError("[memory] STACK TRACE: <unknown location>\n");
         }
         else
         {
@@ -243,7 +243,7 @@ void printStackTrace(MemoryAllocationRecord* rec)
                     line.SizeOfStruct = sizeof(line);
                     if (!SymGetLineFromAddr64(GetCurrentProcess(), pc, &displacement, &line))
                     {
-                        gameplay::printError("[memory] STACK TRACE: %s - <unknown file>:<unknown line number>", symbol->Name);
+                        gameplay::printError("[memory] STACK TRACE: %s - <unknown file>:<unknown line number>\n", symbol->Name);
                     }
                     else
                     {
@@ -253,7 +253,7 @@ void printStackTrace(MemoryAllocationRecord* rec)
                         else
                             file++;
                         
-                        gameplay::printError("[memory] STACK TRACE: %s - %s:%d", symbol->Name, file, line.LineNumber);
+                        gameplay::printError("[memory] STACK TRACE: %s - %s:%d\n", symbol->Name, file, line.LineNumber);
                     }
                 }
             }
@@ -267,24 +267,24 @@ extern void printMemoryLeaks()
     // Dump general heap memory leaks
     if (__memoryAllocationCount == 0)
     {
-        gameplay::printError("[memory] All HEAP allocations successfully cleaned up (no leaks detected).");
+        gameplay::printError("[memory] All HEAP allocations successfully cleaned up (no leaks detected).\n");
     }
     else
     {
-        gameplay::printError("[memory] WARNING: %d HEAP allocations still active in memory.", __memoryAllocationCount);
+        gameplay::printError("[memory] WARNING: %d HEAP allocations still active in memory.\n", __memoryAllocationCount);
         MemoryAllocationRecord* rec = __memoryAllocations;
         while (rec)
         {
 #ifdef WIN32
             if (rec->trackStackTrace)
             {
-                gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d:", rec->address, rec->size);
+                gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d:\n", 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);
+                gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d from line %d in file '%s'.\n", 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);
+            gameplay::printError("[memory] LEAK: HEAP allocation leak at address %#x of size %d from line %d in file '%s'.\n", rec->address, rec->size, rec->line, rec->file);
 #endif
             rec = rec->next;
         }

+ 10 - 7
gameplay/src/DepthStencilTarget.cpp

@@ -7,7 +7,7 @@ namespace gameplay
 static std::vector<DepthStencilTarget*> __depthStencilTargets;
 
 DepthStencilTarget::DepthStencilTarget(const char* id, Format format)
-    : _id(id), _format(format), _depthTexture(NULL), _stencilBuffer(0)
+    : _id(id ? id : ""), _format(format), _depthTexture(NULL), _stencilBuffer(0)
 {
 }
 
@@ -38,29 +38,29 @@ DepthStencilTarget* DepthStencilTarget::create(const char* id, Format format, un
         return NULL;
     }
 
-    // Create stencil renderbuffer if format is DEPTH24_STENCIL8
+    // Create stencil renderbuffer if format is DEPTH24_STENCIL8.
     RenderBufferHandle stencilBuffer = 0;
     if (format == DEPTH24_STENCIL8)
     {
-        // Backup the existing render buffer
+        // Backup the existing render buffer.
         GLint currentRbo = 0;
         GL_ASSERT( glGetIntegerv(GL_RENDERBUFFER_BINDING, &currentRbo) );
 
-        // Create the new render buffer
+        // Create the new render buffer.
         GL_ASSERT( glGenRenderbuffers(1, &stencilBuffer) );
         GL_ASSERT( glBindRenderbuffer(GL_RENDERBUFFER, stencilBuffer) );
         GL_ASSERT( glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height) );
 
-        // Restore the old render buffer
+        // Restore the old render buffer.
         GL_ASSERT( glBindRenderbuffer(GL_RENDERBUFFER, currentRbo) );
     }
 
-    // Create the depth stencil target
+    // Create the depth stencil target.
     DepthStencilTarget* depthStencilTarget = new DepthStencilTarget(id, format);
     depthStencilTarget->_depthTexture = depthTexture;
     depthStencilTarget->_stencilBuffer = stencilBuffer;
 
-    // Add it to the cache
+    // Add it to the cache.
     __depthStencilTargets.push_back(depthStencilTarget);
 
     return depthStencilTarget;
@@ -68,11 +68,14 @@ DepthStencilTarget* DepthStencilTarget::create(const char* id, Format format, un
 
 DepthStencilTarget* DepthStencilTarget::getDepthStencilTarget(const char* id)
 {
+    GP_ASSERT(id);
+
     // Search the vector for a matching ID.
     std::vector<DepthStencilTarget*>::const_iterator it;
     for (it = __depthStencilTargets.begin(); it < __depthStencilTargets.end(); it++)
     {
         DepthStencilTarget* dst = *it;
+        GP_ASSERT(dst);
         if (strcmp(id, dst->getID()) == 0)
         {
             return dst;

+ 34 - 13
gameplay/src/Effect.cpp

@@ -42,6 +42,9 @@ Effect::~Effect()
 
 Effect* Effect::createFromFile(const char* vshPath, const char* fshPath, const char* defines)
 {
+    GP_ASSERT(vshPath);
+    GP_ASSERT(fshPath);
+
     // Search the effect cache for an identical effect that is already loaded.
     std::string uniqueId = vshPath;
     uniqueId += ';';
@@ -55,6 +58,7 @@ Effect* Effect::createFromFile(const char* vshPath, const char* fshPath, const c
     if (itr != __effectCache.end())
     {
         // Found an exiting effect with this id, so increase its ref count and return it.
+        GP_ASSERT(itr->second);
         itr->second->addRef();
         return itr->second;
     }
@@ -63,11 +67,13 @@ Effect* Effect::createFromFile(const char* vshPath, const char* fshPath, const c
     char* vshSource = FileSystem::readAll(vshPath);
     if (vshSource == NULL)
     {
+        GP_ERROR("Failed to read vertex shader from file '%s'.", vshPath);
         return NULL;
     }
     char* fshSource = FileSystem::readAll(fshPath);
     if (fshSource == NULL)
     {
+        GP_ERROR("Failed to read fragment shader from file '%s'.", fshPath);
         SAFE_DELETE_ARRAY(vshSource);
         return NULL;
     }
@@ -79,7 +85,7 @@ Effect* Effect::createFromFile(const char* vshPath, const char* fshPath, const c
 
     if (effect == NULL)
     {
-        LOG_ERROR_VARG("Failed to create effect from shaders: %s, %s", vshPath, fshPath);
+        GP_ERROR("Failed to create effect from shaders '%s', '%s'.", vshPath, fshPath);
     }
     else
     {
@@ -98,6 +104,9 @@ Effect* Effect::createFromSource(const char* vshSource, const char* fshSource, c
 
 Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, const char* fshPath, const char* fshSource, const char* defines)
 {
+    GP_ASSERT(vshSource);
+    GP_ASSERT(fshSource);
+
     const unsigned int SHADER_SOURCE_LENGTH = 3;
     const GLchar* shaderSource[SHADER_SOURCE_LENGTH];
     char* infoLog = NULL;
@@ -130,7 +139,7 @@ Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, con
             GL_ASSERT( glGetShaderInfoLog(vertexShader, length, NULL, infoLog) );
             infoLog[length-1] = '\0';
         }
-        LOG_ERROR_VARG("Compile failed for vertex shader (%s): %s", vshPath == NULL ? "NULL" : vshPath, infoLog == NULL ? "" : infoLog);
+        GP_ERROR("Compile failed for vertex shader '%s' with error '%s'.", vshPath == NULL ? "NULL" : vshPath, infoLog == NULL ? "" : infoLog);
         SAFE_DELETE_ARRAY(infoLog);
 
         // Clean up.
@@ -140,14 +149,6 @@ Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, con
     }
 
     // Compile the fragment shader.
-    definesStr = (defines == NULL) ? "" : defines;
-#ifdef OPENGL_ES
-    if (defines && strlen(defines) != 0)
-        definesStr += "\n";
-    definesStr+= OPENGL_ES_DEFINE;
-#endif
-    shaderSource[0] = definesStr.c_str();
-    shaderSource[1] = "\n";
     shaderSource[2] = fshSource;
     GL_ASSERT( fragmentShader = glCreateShader(GL_FRAGMENT_SHADER) );
     GL_ASSERT( glShaderSource(fragmentShader, SHADER_SOURCE_LENGTH, shaderSource, NULL) );
@@ -162,7 +163,7 @@ Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, con
             GL_ASSERT( glGetShaderInfoLog(fragmentShader, length, NULL, infoLog) );
             infoLog[length-1] = '\0';
         }
-        LOG_ERROR_VARG("Compile failed for fragment shader (%s): %s", fshPath == NULL ? "NULL" : fshPath, infoLog == NULL ? "" : infoLog);
+        GP_ERROR("Compile failed for fragment shader (%s): %s", fshPath == NULL ? "NULL" : fshPath, infoLog == NULL ? "" : infoLog);
         SAFE_DELETE_ARRAY(infoLog);
 
         // Clean up.
@@ -193,7 +194,7 @@ Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, con
             GL_ASSERT( glGetProgramInfoLog(program, length, NULL, infoLog) );
             infoLog[length-1] = '\0';
         }
-        LOG_ERROR_VARG("Linking program failed (%s,%s): %s", vshPath == NULL ? "NULL" : vshPath, fshPath == NULL ? "NULL" : fshPath, infoLog == NULL ? "" : infoLog);
+        GP_ERROR("Linking program failed (%s,%s): %s", vshPath == NULL ? "NULL" : vshPath, fshPath == NULL ? "NULL" : fshPath, infoLog == NULL ? "" : infoLog);
         SAFE_DELETE_ARRAY(infoLog);
 
         // Clean up.
@@ -327,67 +328,87 @@ unsigned int Effect::getUniformCount() const
 
 void Effect::setValue(Uniform* uniform, float value)
 {
+    GP_ASSERT(uniform);
     GL_ASSERT( glUniform1f(uniform->_location, value) );
 }
 
 void Effect::setValue(Uniform* uniform, const float* values, unsigned int count)
 {
+    GP_ASSERT(uniform);
+    GP_ASSERT(values);
     GL_ASSERT( glUniform1fv(uniform->_location, count, values) );
 }
 
 void Effect::setValue(Uniform* uniform, int value)
 {
+    GP_ASSERT(uniform);
     GL_ASSERT( glUniform1i(uniform->_location, value) );
 }
 
 void Effect::setValue(Uniform* uniform, const int* values, unsigned int count)
 {
+    GP_ASSERT(uniform);
+    GP_ASSERT(values);
     GL_ASSERT( glUniform1iv(uniform->_location, count, values) );
 }
 
 void Effect::setValue(Uniform* uniform, const Matrix& value)
 {
+    GP_ASSERT(uniform);
     GL_ASSERT( glUniformMatrix4fv(uniform->_location, 1, GL_FALSE, value.m) );
 }
 
 void Effect::setValue(Uniform* uniform, const Matrix* values, unsigned int count)
 {
+    GP_ASSERT(uniform);
+    GP_ASSERT(values);
     GL_ASSERT( glUniformMatrix4fv(uniform->_location, count, GL_FALSE, (GLfloat*)values) );
 }
 
 void Effect::setValue(Uniform* uniform, const Vector2& value)
 {
+    GP_ASSERT(uniform);
     GL_ASSERT( glUniform2f(uniform->_location, value.x, value.y) );
 }
 
 void Effect::setValue(Uniform* uniform, const Vector2* values, unsigned int count)
 {
+    GP_ASSERT(uniform);
+    GP_ASSERT(values);
     GL_ASSERT( glUniform2fv(uniform->_location, count, (GLfloat*)values) );
 }
 
 void Effect::setValue(Uniform* uniform, const Vector3& value)
 {
+    GP_ASSERT(uniform);
     GL_ASSERT( glUniform3f(uniform->_location, value.x, value.y, value.z) );
 }
 
 void Effect::setValue(Uniform* uniform, const Vector3* values, unsigned int count)
 {
+    GP_ASSERT(uniform);
+    GP_ASSERT(values);
     GL_ASSERT( glUniform3fv(uniform->_location, count, (GLfloat*)values) );
 }
 
 void Effect::setValue(Uniform* uniform, const Vector4& value)
 {
+    GP_ASSERT(uniform);
     GL_ASSERT( glUniform4f(uniform->_location, value.x, value.y, value.z, value.w) );
 }
 
 void Effect::setValue(Uniform* uniform, const Vector4* values, unsigned int count)
 {
+    GP_ASSERT(uniform);
+    GP_ASSERT(values);
     GL_ASSERT( glUniform4fv(uniform->_location, count, (GLfloat*)values) );
 }
 
 void Effect::setValue(Uniform* uniform, const Texture::Sampler* sampler)
 {
-    assert(uniform->_type == GL_SAMPLER_2D);
+    GP_ASSERT(uniform);
+    GP_ASSERT(uniform->_type == GL_SAMPLER_2D);
+    GP_ASSERT(sampler);
 
     GL_ASSERT( glActiveTexture(GL_TEXTURE0 + uniform->_index) );
 

+ 151 - 28
gameplay/src/FileSystem.cpp

@@ -1,25 +1,34 @@
 #include "Base.h"
 #include "FileSystem.h"
+#include "Properties.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #ifdef WIN32
     #include <windows.h>
     #include <tchar.h>
     #include <stdio.h>
+    #define gp_stat _stat
+    #define gp_stat_struct struct stat
 #else
     #include <dirent.h>
-    #include <sys/stat.h>
+    #define gp_stat stat
+    #define gp_stat_struct struct stat
 #endif
 
 #ifdef __ANDROID__
+#include <android/asset_manager.h>
 extern AAssetManager* __assetManager;
 #endif
 
 namespace gameplay
 {
 
+// Creates a file on the file system from the specified asset (Android-specific).
+static void createFileFromAsset(const char* path);
+
 #ifdef __ANDROID__
-#include <sys/types.h>
-#include <sys/stat.h>
 #include <unistd.h>
 
 void makepath(std::string path, int mode)
@@ -49,7 +58,7 @@ void makepath(std::string path, int mode)
             // Directory does not exist.
             if (mkdir(dirPath.c_str(), 0777) != 0)
             {
-                WARN_VARG("Failed to create directory: '%s'", dirPath.c_str());
+                GP_ERROR("Failed to create directory: '%s'", dirPath.c_str());
                 return;
             }
         }
@@ -60,6 +69,7 @@ void makepath(std::string path, int mode)
 #endif
 
 static std::string __resourcePath("./");
+static std::map<std::string, std::string> __aliases;
 
 FileSystem::FileSystem()
 {
@@ -79,6 +89,48 @@ const char* FileSystem::getResourcePath()
     return __resourcePath.c_str();
 }
 
+void FileSystem::loadResourceAliases(const char* aliasFilePath)
+{
+    Properties* properties = Properties::create(aliasFilePath);
+    if (properties)
+    {
+        Properties* aliases;
+        while ((aliases = properties->getNextNamespace()) != NULL)
+        {
+            loadResourceAliases(aliases);
+        }
+    }
+    SAFE_DELETE(properties);
+}
+
+void FileSystem::loadResourceAliases(Properties* properties)
+{
+    assert(properties);
+
+    const char* name;
+    while ((name = properties->getNextProperty()) != NULL)
+    {
+        __aliases[name] = properties->getString();
+    }
+}
+
+const char* FileSystem::resolvePath(const char* path)
+{
+    GP_ASSERT(path);
+
+    size_t len = strlen(path);
+    if (len > 1 && path[0] == '@')
+    {
+        std::string alias(path + 1);
+        std::map<std::string, std::string>::const_iterator itr = __aliases.find(alias);
+        if (itr == __aliases.end())
+            return path; // no matching alias found
+        return itr->second.c_str();
+    }
+
+    return path;
+}
+
 bool FileSystem::listFiles(const char* dirPath, std::vector<std::string>& files)
 {
     // TODO make this method work with absolute and relative paths.
@@ -148,32 +200,40 @@ bool FileSystem::listFiles(const char* dirPath, std::vector<std::string>& files)
 #endif
 }
 
-FILE* FileSystem::openFile(const char* path, const char* mode)
+bool FileSystem::fileExists(const char* path)
 {
+    GP_ASSERT(path);
+
     std::string fullPath(__resourcePath);
-    fullPath += path;
-    
-#ifdef __ANDROID__
-    std::string directoryPath = fullPath.substr(0, fullPath.rfind('/'));
-    struct stat s;
-    if (stat(directoryPath.c_str(), &s) != 0)
-        makepath(directoryPath.c_str(), 0777);
+    fullPath += resolvePath(path);
+
+    createFileFromAsset(path);
 
+    gp_stat_struct s;
+// Win32 doesn't support an asset or bundle definitions.
+#ifdef WIN32
     if (stat(fullPath.c_str(), &s) != 0)
     {
-        AAsset* asset = AAssetManager_open(__assetManager, path, AASSET_MODE_RANDOM);
-        if (asset)
-        {
-            const void* data = AAsset_getBuffer(asset);
-            int length = AAsset_getLength(asset);
-            FILE* file = fopen(fullPath.c_str(), "wb");
+        fullPath = __resourcePath;
+        fullPath += "../../gameplay/";
+        fullPath += path;
         
-            int ret = fwrite(data, sizeof(unsigned char), length, file);
-            assert(ret == length);
-            fclose(file);
-        }
+        return stat(fullPath.c_str(), &s) == 0;
     }
+    return true;
+#else
+    return stat(fullPath.c_str(), &s) == 0;
 #endif
+}
+
+FILE* FileSystem::openFile(const char* path, const char* mode)
+{
+    GP_ASSERT(path);
+
+    std::string fullPath(__resourcePath);
+    fullPath += resolvePath(path);
+
+    createFileFromAsset(path);
     
     FILE* fp = fopen(fullPath.c_str(), mode);
     
@@ -198,22 +258,29 @@ char* FileSystem::readAll(const char* filePath, int* fileSize)
     FILE* file = openFile(filePath, "rb");
     if (file == NULL)
     {
-        LOG_ERROR_VARG("Failed to load file: %s", filePath);
+        GP_ERROR("Failed to load file: %s", filePath);
         return NULL;
     }
 
     // Obtain file length.
-    fseek(file, 0, SEEK_END);
+    if (fseek(file, 0, SEEK_END) != 0)
+    {
+        GP_ERROR("Failed to seek to the end of the file '%s' to obtain the file length.", filePath);
+        return NULL;
+    }
     int size = (int)ftell(file);
-     fseek(file, 0, SEEK_SET);
+    if (fseek(file, 0, SEEK_SET) != 0)
+    {
+        GP_ERROR("Failed to seek to beginning of the file '%s' to begin reading in the entire file.", filePath);
+        return NULL;
+    }
 
     // Read entire file contents.
     char* buffer = new char[size + 1];
     int read = (int)fread(buffer, 1, size, file);
-    assert(read == size);
     if (read != size)
     {
-        LOG_ERROR_VARG("Read error for file: %s (%d < %d)", filePath, (int)read, (int)size);
+        GP_ERROR("Failed to read complete contents of file '%s' (amount read vs. file size: %d < %d).", filePath, (int)read, (int)size);
         SAFE_DELETE_ARRAY(buffer);
         return NULL;
     }
@@ -222,7 +289,11 @@ char* FileSystem::readAll(const char* filePath, int* fileSize)
     buffer[size] = '\0';
 
     // Close file and return.
-    fclose(file);
+    if (fclose(file) != 0)
+    {
+        GP_ERROR("Failed to close file '%s'.", filePath);
+    }
+
     if (fileSize)
     {
         *fileSize = size; 
@@ -230,4 +301,56 @@ char* FileSystem::readAll(const char* filePath, int* fileSize)
     return buffer;
 }
 
+void createFileFromAsset(const char* path)
+{
+#ifdef __ANDROID__
+    static std::set<std::string> upToDateAssets;
+
+    GP_ASSERT(path);
+    std::string fullPath(__resourcePath);
+    std::string resolvedPath = FileSystem::resolvePath(path);
+    fullPath += resolvedPath;
+
+    std::string directoryPath = fullPath.substr(0, fullPath.rfind('/'));
+    struct stat s;
+    if (stat(directoryPath.c_str(), &s) != 0)
+        makepath(directoryPath.c_str(), 0777);
+
+    // To ensure that the files on the file system corresponding to the assets in the APK bundle
+    // are always up to date (and in sync), we copy them from the APK to the file system once
+    // for each time the process (game) runs.
+    if (upToDateAssets.find(fullPath) == upToDateAssets.end())
+    {
+        AAsset* asset = AAssetManager_open(__assetManager, resolvedPath.c_str(), AASSET_MODE_RANDOM);
+        if (asset)
+        {
+            const void* data = AAsset_getBuffer(asset);
+            int length = AAsset_getLength(asset);
+            FILE* file = fopen(fullPath.c_str(), "wb");
+            if (file != NULL)
+            {
+                int ret = fwrite(data, sizeof(unsigned char), length, file);
+                if (fclose(file) != 0)
+                {
+                    GP_ERROR("Failed to close file on file system created from APK asset '%s'.", path);
+                    return;
+                }
+                if (ret != length)
+                {
+                    GP_ERROR("Failed to write all data from APK asset '%s' to file on file system.", path);
+                    return;
+                }
+            }
+            else
+            {
+                GP_ERROR("Failed to create file on file system from APK asset '%s'.", path);
+                return;
+            }
+
+            upToDateAssets.insert(fullPath);
+        }
+    }
+#endif
+}
+
 }

+ 57 - 0
gameplay/src/FileSystem.h

@@ -4,6 +4,8 @@
 namespace gameplay
 {
 
+class Properties;
+
 /**
  * Defines a set of functions for interacting with the device filesystem.
  */
@@ -33,6 +35,53 @@ public:
      */
     static const char* getResourcePath();
 
+    /**
+     * Loads a properties file containing a list of filesystem aliases.
+     *
+     * The specified aliases file is a valid properties file that contains one
+     * or more namespaces with a list of fielsystem aliases that will be used
+     * to establish soft links to files when reading files through this class.
+     *
+     * This can be helpful for managing loading of resources that may change
+     * from one platform to another (such as texture formats). An aliases
+     * file per-platform can be maintained and asset loading code can refer
+     * to the alias name instead of the actual hard file name.
+     *
+     * @param aliasFilePath Path to a properties file containing filesystem aliases.
+     * @see Properties
+     */
+    static void loadResourceAliases(const char* aliasFilePath);
+
+    /**
+     * Loads a set of filesystem aliases from the given Properties object.
+     *
+     * The specified properties object contains a single namespace with a list
+     * of fielsystem aliases that will be used to establish soft links to files
+     * when reading files through this class.
+     *
+     * This can be helpful for managing loading of resources that may change
+     * from one platform to another (such as texture formats). An aliases
+     * file per-platform can be maintained and asset loading code can refer
+     * to the alias name instead of the actual hard file name.
+     *
+     * @param properties Properties object containing filesystem aliases.
+     * @see Properties
+     */
+    static void loadResourceAliases(Properties* properties);
+
+    /**
+     * Resolves a filesystem path.
+     *
+     * If the specified path is a filesystem alias, the alias will be
+     * resolved and the physical file will be returned.
+     *
+     * Note that this method does not convert a relative path to an
+     * absolute filesystem path.
+     *
+     * @param path Path to resolve.
+     */
+    static const char* resolvePath(const char* path);
+
     /**
      * Lists the files in the specified directory and adds the files to the vector. Excludes directories.
      * 
@@ -43,6 +92,14 @@ public:
      */
     static bool listFiles(const char* dirPath, std::vector<std::string>& files);
 
+    /**
+     * Checks if the file at the given path exists.
+     * 
+     * @param path The path to the file.
+     * @return <code>true</code> if the file exists; <code>false</code> otherwise.
+     */
+    static bool fileExists(const char* path);
+
     /**
      * Opens the specified file.
      *

+ 7 - 6
gameplay/src/FlowLayout.cpp

@@ -39,9 +39,10 @@ Layout::Type FlowLayout::getType()
     return Layout::LAYOUT_FLOW;
 }
 
-void FlowLayout::update(const Container* container)
+void FlowLayout::update(const Container* container, const Vector2& offset)
 {
-    const Rectangle& containerBounds = container->getClipBounds();
+    GP_ASSERT(container);
+    const Rectangle& containerBounds = container->getBounds();
     const Theme::Border& containerBorder = container->getBorder(container->getState());
     const Theme::Padding& containerPadding = container->getPadding();
 
@@ -58,6 +59,9 @@ void FlowLayout::update(const Container* container)
     for (unsigned int i = 0; i < controlsCount; i++)
     {
         Control* control = controls.at(i);
+        GP_ASSERT(control);
+
+        //align(control, container);
 
         const Rectangle& bounds = control->getBounds();
         const Theme::Margin& margin = control->getMargin();
@@ -74,10 +78,7 @@ void FlowLayout::update(const Container* container)
         yPosition = rowY + margin.top;
 
         control->setPosition(xPosition, yPosition);
-        if (control->isDirty() || control->isContainer())
-        {
-            control->update(container->getClip());
-        }
+        control->update(container, offset);
 
         xPosition += bounds.width + margin.right;
 

+ 3 - 2
gameplay/src/FlowLayout.h

@@ -7,7 +7,7 @@ namespace gameplay
 {
 
 /**
- *  Defines a layout that arranges components in a left-to-right or right-to-left flow. 
+ * Defines a layout that arranges controls in order, left-to-right, row by row.
  */
 class FlowLayout : public Layout
 {
@@ -36,8 +36,9 @@ protected:
      * Update the controls contained by the specified container.
      *
      * @param container The container to update.
+     * @param offset The offset position.
      */
-    void update(const Container* container);
+    void update(const Container* container, const Vector2& offset);
 
 private:
 

Diferenças do arquivo suprimidas por serem muito extensas
+ 377 - 252
gameplay/src/Font.cpp


+ 70 - 6
gameplay/src/Font.h

@@ -76,6 +76,40 @@ public:
         float uvs[4];
     };
 
+    /**
+     * Vertex coordinates, UVs and indices can be computed and stored in a Text object.
+     * For static text labels that do not change frequently, this means these computations
+     * need not be performed every frame.
+     */
+    class Text
+    {
+        friend class Font;
+
+    public:
+        /**
+         * Constructor.
+         */
+        Text(const char* text);
+
+        /**
+         * Destructor.
+         */
+        ~Text();
+
+        /**
+         * Get the string that will be drawn from this Text object.
+         */
+        const char* getText();
+
+    private:
+        std::string _text;
+        unsigned int _vertexCount;
+        SpriteBatch::SpriteVertex* _vertices;
+        unsigned int _indexCount;
+        unsigned short* _indices;
+        Vector4 _color;
+    };
+
     /**
      * Creates a font from the given bundle.
      *
@@ -139,7 +173,7 @@ public:
 
     /**
      * Draws the specified text within a rectangular area, with a specified alignment and scale.
-     * Clips text outside the viewport.  Optionally wraps text to fit within the width of the viewport.
+     * Clips text outside the viewport. Optionally wraps text to fit within the width of the viewport.
      *
      * @param text The text to draw.
      * @param area The viewport area to draw within.  Text will be clipped outside this rectangle.
@@ -153,6 +187,33 @@ public:
     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);
 
+    /**
+     * Draw a string from a precomputed Text object.
+     *
+     * @param text The text to draw.
+     */
+    void drawText(Text* text);
+
+    /**
+     * Create an immutable Text object from a given string.
+     * Vertex coordinates, UVs and indices will be computed and stored in the Text object.
+     * For static text labels that do not change frequently, this means these computations
+     * need not be performed every frame.
+     *
+     * @param text The text to draw.
+     * @param area The viewport area to draw within.  Text will be clipped outside this rectangle.
+     * @param color The color of text.
+     * @param size The size to draw text (0 for default size).
+     * @param justify Justification of text within the viewport.
+     * @param wrap Wraps text to fit within the width of the viewport if true.
+     * @param rightToLeft Whether to draw text from right to left.
+     * @param clip A region to clip text within after applying justification to the viewport area.
+     *
+     * @return A Text object.
+     */
+    Text* createText(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.
      *
@@ -179,10 +240,10 @@ public:
                      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 character 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);
+    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.
@@ -225,8 +286,11 @@ private:
      */
     ~Font();
 
-    unsigned int getIndexOrLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
-                                    const int destIndex = -1, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+    void getMeasurementInfo(const char* text, const Rectangle& area, unsigned int size, Justify justify, bool wrap, bool rightToLeft,
+                            std::vector<int>* xPositions, int* yPosition, std::vector<unsigned int>* lineLengths);
+
+    int getIndexOrLocation(const char* text, const Rectangle& clip, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                           const int destIndex = -1, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
 
     unsigned int getTokenWidth(const char* token, unsigned int length, unsigned int size, float scale);
 

+ 533 - 276
gameplay/src/Form.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "Form.h"
 #include "AbsoluteLayout.h"
+#include "FlowLayout.h"
 #include "VerticalLayout.h"
 #include "Game.h"
 #include "Theme.h"
@@ -11,388 +12,644 @@
 
 namespace gameplay
 {
-    static std::vector<Form*> __forms;
 
-    Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL)
+static std::vector<Form*> __forms;
+
+Form::Form() : _theme(NULL), _quad(NULL), _node(NULL), _frameBuffer(NULL), _spriteBatch(NULL)
+{
+}
+
+Form::Form(const Form& copy)
+{
+}
+
+Form::~Form()
+{
+    SAFE_RELEASE(_quad);
+    SAFE_RELEASE(_node);
+    SAFE_RELEASE(_frameBuffer);
+    SAFE_RELEASE(_theme);
+    SAFE_DELETE(_spriteBatch);
+
+    // Remove this Form from the global list.
+    std::vector<Form*>::iterator it = std::find(__forms.begin(), __forms.end(), this);
+    if (it != __forms.end())
     {
+        __forms.erase(it);
     }
+}
 
-    Form::Form(const Form& copy)
+Form* Form::create(const char* url)
+{
+    // Load Form from .form file.
+    Properties* properties = Properties::create(url);
+    if (properties == NULL)
     {
+        GP_ASSERT(properties);
+        return NULL;
     }
 
-    Form::~Form()
+    // Check if the Properties is valid and has a valid namespace.
+    Properties* formProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
+    assert(formProperties);
+    if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
     {
-        SAFE_RELEASE(_quad);
-        SAFE_RELEASE(_node);
-        SAFE_RELEASE(_frameBuffer);
-        SAFE_RELEASE(_theme);
-
-        // Remove this Form from the global list.
-        std::vector<Form*>::iterator it = std::find(__forms.begin(), __forms.end(), this);
-        if (it != __forms.end())
-        {
-            __forms.erase(it);
-        }
+        GP_ASSERT(formProperties);
+        SAFE_DELETE(properties);
+        return NULL;
     }
 
-    Form* Form::create(const char* url)
+    // Create new form with given ID, theme and layout.
+    const char* themeFile = formProperties->getString("theme");
+    const char* layoutString = formProperties->getString("layout");
+        
+    Layout* layout;
+    switch (getLayoutType(layoutString))
     {
-        // Load Form from .form file.
-        assert(url);
-
-        Properties* properties = Properties::create(url);
-        assert(properties);
-        if (properties == NULL)
-            return NULL;
-
-        // Check if the Properties is valid and has a valid namespace.
-        Properties* formProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
-        assert(formProperties);
-        if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
-        {
-            SAFE_DELETE(properties);
-            return NULL;
-        }
+    case Layout::LAYOUT_ABSOLUTE:
+        layout = AbsoluteLayout::create();
+        break;
+    case Layout::LAYOUT_FLOW:
+        layout = FlowLayout::create();
+        break;
+    case Layout::LAYOUT_VERTICAL:
+        layout = VerticalLayout::create();
+        break;
+    default:
+        GP_ERROR("Unsupported layout type '%d'.", getLayoutType(layoutString));
+    }
 
-        // Create new form with given ID, theme and layout.
-        const char* themeFile = formProperties->getString("theme");
-        const char* layoutString = formProperties->getString("layout");
-        
-        Layout* layout;
-        switch (getLayoutType(layoutString))
-        {
-        case Layout::LAYOUT_ABSOLUTE:
-            layout = AbsoluteLayout::create();
-            break;
-        case Layout::LAYOUT_FLOW:
-            break;
-        case Layout::LAYOUT_VERTICAL:
-            layout = VerticalLayout::create();
-            break;
-        }
+    Theme* theme = Theme::create(themeFile);
+    GP_ASSERT(theme);
 
-        assert(themeFile);
-        Theme* theme = Theme::create(themeFile);
-        assert(theme);
+    Form* form = new Form();
+    form->_layout = layout;
+    form->_theme = theme;
 
-        Form* form = new Form();
-        form->_layout = layout;
-        form->_theme = theme;
+    // Get default projection matrix.
+    Game* game = Game::getInstance();
+    Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &form->_defaultProjectionMatrix);
 
-        //Theme* theme = form->_theme;
-        const char* styleName = formProperties->getString("style");
-        form->initialize(theme->getStyle(styleName), formProperties);
+    Theme::Style* style = NULL;
+    const char* styleName = formProperties->getString("style");
+    if (styleName)
+    {
+        style = theme->getStyle(styleName);
+    }
+    else
+    {
+        Theme::Style::Overlay* overlay = Theme::Style::Overlay::create();
+        style = new Theme::Style(theme, "", 1.0f / theme->_texture->getWidth(), 1.0f / theme->_texture->getHeight(),
+            Theme::Margin::empty(), Theme::Border::empty(), overlay, overlay, overlay, overlay);
+    }
+    form->initialize(style, formProperties);
 
-        if (form->_autoWidth)
-        {
-            form->_bounds.width = Game::getInstance()->getWidth();
-        }
+    // Alignment
+    if ((form->_alignment & Control::ALIGN_BOTTOM) == Control::ALIGN_BOTTOM)
+    {
+        form->_bounds.y = Game::getInstance()->getHeight() - form->_bounds.height;
+    }
+    else if ((form->_alignment & Control::ALIGN_VCENTER) == Control::ALIGN_VCENTER)
+    {
+        form->_bounds.y = Game::getInstance()->getHeight() * 0.5f - form->_bounds.height * 0.5f;
+    }
 
-        if (form->_autoHeight)
-        {
-            form->_bounds.height = Game::getInstance()->getHeight();
-        }
+    if ((form->_alignment & Control::ALIGN_RIGHT) == Control::ALIGN_RIGHT)
+    {
+        form->_bounds.x = Game::getInstance()->getWidth() - form->_bounds.width;
+    }
+    else if ((form->_alignment & Control::ALIGN_HCENTER) == Control::ALIGN_HCENTER)
+    {
+        form->_bounds.x = Game::getInstance()->getWidth() * 0.5f - form->_bounds.width * 0.5f;
+    }
 
-        // Add all the controls to the form.
-        form->addControls(theme, formProperties);
+    form->_scroll = getScroll(formProperties->getString("scroll"));
+    form->_scrollBarsAutoHide = formProperties->getBool("scrollBarsAutoHide");
+    if (form->_scrollBarsAutoHide)
+    {
+        form->_scrollBarOpacity = 0.0f;
+    }
 
-        SAFE_DELETE(properties);
+    // Add all the controls to the form.
+    form->addControls(theme, formProperties);
 
-        __forms.push_back(form);
+    form->update();
 
-        return form;
-    }
+    SAFE_DELETE(properties);
+
+    __forms.push_back(form);
+
+    return form;
+}
 
-    Form* Form::getForm(const char* id)
+Form* Form::getForm(const char* id)
+{
+    std::vector<Form*>::const_iterator it;
+    for (it = __forms.begin(); it < __forms.end(); it++)
     {
-        std::vector<Form*>::const_iterator it;
-        for (it = __forms.begin(); it < __forms.end(); it++)
+        Form* f = *it;
+        GP_ASSERT(f);
+        if (strcmp(id, f->getID()) == 0)
         {
-            Form* f = *it;
-            if (strcmp(id, f->getID()) == 0)
-            {
-                return f;
-            }
+            return f;
         }
-        
-        return NULL;
     }
+        
+    return NULL;
+}
 
-    void Form::setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4)
+void Form::setSize(float width, float height)
+{
+    if (_autoWidth)
     {
-        Mesh* mesh = Mesh::createQuad(p1, p2, p3, p4);
-        initializeQuad(mesh);
-        SAFE_RELEASE(mesh);
+        width = Game::getInstance()->getWidth();
     }
 
-    void Form::setQuad(float x, float y, float width, float height)
+    if (_autoHeight)
     {
-        Mesh* mesh = Mesh::createQuad(x, y, width, height);
-        initializeQuad(mesh);
-        SAFE_RELEASE(mesh);
+        height = Game::getInstance()->getHeight();
     }
 
-    void Form::setNode(Node* node)
+    if (width != _bounds.width || height != _bounds.height)
     {
-        _node = node;
+        // Width and height must be powers of two to create a texture.
+        int w = width;
+        int h = height;
 
-        if (_node && !_quad)
+        if (!((w & (w - 1)) == 0))
         {
-            // Set this Form up to be 3D by initializing a quad, projection matrix and viewport.
-            setQuad(0.0f, 0.0f, _bounds.width, _bounds.height);
+            w = nextHighestPowerOfTwo(w);
+        }
 
-            Matrix::createOrthographicOffCenter(0, _bounds.width, _bounds.height, 0, 0, 1, &_projectionMatrix);
-            _theme->setProjectionMatrix(_projectionMatrix);
-            
-            _node->setModel(_quad);
+        if (!((h & (h - 1)) == 0))
+        {
+            h = nextHighestPowerOfTwo(h);
         }
+
+        _u2 = width / (float)w;
+        _v1 = height / (float)h;
+
+        // Create framebuffer if necessary.
+        if (!_frameBuffer)
+        {
+            _frameBuffer = FrameBuffer::create(_id.c_str());
+            GP_ASSERT(_frameBuffer);
+        }
+     
+        // Re-create render target.
+        RenderTarget* rt = RenderTarget::create(_id.c_str(), w, h);
+        GP_ASSERT(rt);
+        _frameBuffer->setRenderTarget(rt);
+        SAFE_RELEASE(rt);
+
+        // Re-create projection matrix.
+        Matrix::createOrthographicOffCenter(0, width, height, 0, 0, 1, &_projectionMatrix);
+
+        // Re-create sprite batch.
+        SAFE_DELETE(_spriteBatch);
+        _spriteBatch = SpriteBatch::create(_frameBuffer->getRenderTarget()->getTexture());
+        GP_ASSERT(_spriteBatch);
+
+        // Clear FBO.
+        _frameBuffer->bind();
+        Game* game = Game::getInstance();
+        Rectangle prevViewport = game->getViewport();
+        game->setViewport(Rectangle(0, 0, width, height));
+        _theme->setProjectionMatrix(_projectionMatrix);
+        GL_ASSERT( glClearColor(0, 0, 0, 0) );
+        GL_ASSERT( glClear(GL_COLOR_BUFFER_BIT) );
+        GL_ASSERT( glClearColor(0, 0, 0, 1) );
+        _theme->setProjectionMatrix(_defaultProjectionMatrix);
+        FrameBuffer::bindDefault();
+        game->setViewport(prevViewport);
+
+        _bounds.width = width;
+        _bounds.height = height;
+        _dirty = true;
     }
+}
 
-    void Form::update()
+void Form::setBounds(const Rectangle& bounds)
+{
+    setPosition(bounds.x, bounds.y);
+    setSize(bounds.width, bounds.height);
+}
+
+void Form::setAutoWidth(bool autoWidth)
+{
+    if (_autoWidth != autoWidth)
     {
-        if (isDirty())
+        _autoWidth = autoWidth;
+        _dirty = true;
+
+        if (_autoWidth)
         {
-            Container::update(Rectangle(0, 0, _bounds.width, _bounds.height));
+            setSize(_bounds.width, Game::getInstance()->getWidth());
         }
     }
+}
 
-    void Form::draw()
+void Form::setAutoHeight(bool autoHeight)
+{
+    if (_autoHeight != autoHeight)
     {
-        // If this form has a node then it's a 3D form.  The contents will be rendered
-        // into a framebuffer which will be used to texture a quad.  The quad will be
-        // given the same dimensions as the form and must be transformed appropriately
-        // by the user, unless they call setQuad() themselves.
+        _autoHeight = autoHeight;
+        _dirty = true;
 
-        // On the other hand, if this form has not been set on a node it will render
-        // directly to the display.
-
-        if (_node)
+        if (_autoHeight)
         {
-            // Check whether this form has changed since the last call to draw()
-            // and if so, render into the framebuffer.
-            if (isDirty())
-            {
-                _frameBuffer->bind();
+            setSize(_bounds.width, Game::getInstance()->getHeight());
+        }
+    }
+}
+
+void Form::setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4)
+{
+    Mesh* mesh = Mesh::createQuad(p1, p2, p3, p4);
 
-                Game* game = Game::getInstance();
-                Rectangle prevViewport = game->getViewport();
-                
-                game->setViewport(Rectangle(_bounds.x, _bounds.y, _bounds.width, _bounds.height));
+    initializeQuad(mesh);
+    SAFE_RELEASE(mesh);
+}
 
-                draw(_theme->getSpriteBatch(), _clip);
+void Form::setQuad(float x, float y, float width, float height)
+{
+    float x2 = x + width;
+    float y2 = y + height;
 
-                // Rebind the default framebuffer and game viewport.
-                FrameBuffer::bindDefault();
+    float vertices[] =
+    {
+        x, y2, 0,   0, _v1,
+        x, y, 0,    0, 0,
+        x2, y2, 0,  _u2, _v1,
+        x2, y, 0,   _u2, 0
+    };
 
-                // restore the previous game viewport
-                game->setViewport(prevViewport);
-            }
+    VertexFormat::Element elements[] =
+    {
+        VertexFormat::Element(VertexFormat::POSITION, 3),
+        VertexFormat::Element(VertexFormat::TEXCOORD0, 2)
+    };
+    Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 2), 4, false);
+    assert(mesh);
 
-            _quad->draw();
+    mesh->setPrimitiveType(Mesh::TRIANGLE_STRIP);
+    mesh->setVertexData(vertices, 0, 4);
+
+    initializeQuad(mesh);
+    SAFE_RELEASE(mesh);
+}
+
+void Form::setNode(Node* node)
+{
+    _node = node;
+        
+    if (_node)
+    {
+        // Set this Form up to be 3D by initializing a quad.
+        setQuad(0.0f, 0.0f, _bounds.width, _bounds.height);
+        _node->setModel(_quad);
+    }
+}
+
+void Form::update()
+{
+    if (isDirty())
+    {
+        _clearBounds.set(_absoluteClipBounds);
+
+        // Calculate the clipped bounds.
+        float x = 0;
+        float y = 0;
+        float width = _bounds.width;
+        float height = _bounds.height;
+
+        Rectangle clip(0, 0, _bounds.width, _bounds.height);
+
+        float clipX2 = clip.x + clip.width;
+        float x2 = clip.x + x + width;
+        if (x2 > clipX2)
+            width -= x2 - clipX2;
+
+        float clipY2 = clip.y + clip.height;
+        float y2 = clip.y + y + height;
+        if (y2 > clipY2)
+            height -= y2 - clipY2;
+
+        if (x < 0)
+        {
+            width += x;
+            x = -x;
         }
         else
         {
-            draw(_theme->getSpriteBatch(), _clip);
+            x = 0;
         }
-    }
 
-    void Form::draw(SpriteBatch* spriteBatch, const Rectangle& clip)
-    {
-        std::vector<Control*>::const_iterator it;
+        if (y < 0)
+        {
+            height += y;
+            y = -y;
+        }
+        else
+        {
+            y = 0;
+        }
+
+        _clipBounds.set(x, y, width, height);
+
+        // Calculate the absolute bounds.
+        x = 0;
+        y = 0;
+        _absoluteBounds.set(x, y, _bounds.width, _bounds.height);
+
+        // Calculate the absolute viewport bounds.
+        // Absolute bounds minus border and padding.
+        const Theme::Border& border = getBorder(_state);
+        const Theme::Padding& padding = getPadding();
+
+        x += border.left + padding.left;
+        y += border.top + padding.top;
+        width = _bounds.width - border.left - padding.left - border.right - padding.right;
+        height = _bounds.height - border.top - padding.top - border.bottom - padding.bottom;
+
+        _viewportBounds.set(x, y, width, height);
 
-        // Batch for all themed border and background sprites.
-        spriteBatch->begin();
+        // Calculate the clip area.
+        // Absolute bounds, minus border and padding,
+        // clipped to the parent container's clip area.
+        clipX2 = clip.x + clip.width;
+        x2 = x + width;
+        if (x2 > clipX2)
+            width = clipX2 - x;
 
-        // Batch each font individually.
-        std::set<Font*>::const_iterator fontIter;
-        for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
+        clipY2 = clip.y + clip.height;
+        y2 = y + height;
+        if (y2 > clipY2)
+            height = clipY2 - y;
+
+        if (x < clip.x)
         {
-            Font* font = *fontIter;
-            if (font)
-            {
-                font->begin();
-            }
+            float dx = clip.x - x;
+            width -= dx;
+            x = clip.x;
         }
 
-        // Draw the form's border and background.
-        // We don't pass the form's position to itself or it will be applied twice!
-        Control::drawBorder(spriteBatch, Rectangle(0, 0, _bounds.width, _bounds.height));
+        if (y < clip.y)
+        {
+            float dy = clip.y - y;
+            height -= dy;
+            y = clip.y;
+        }
+ 
+        _viewportClipBounds.set(x, y, width, height);
 
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        _absoluteClipBounds.set(x - border.left - padding.left, y - border.top - padding.top,
+            width + border.left + padding.left + border.right + padding.right,
+            height + border.top + padding.top + border.bottom + padding.bottom);
+        if (_clearBounds.isEmpty())
         {
-            Control* control = *it;
+            _clearBounds.set(_absoluteClipBounds);
+        }
 
-            // Draw this control's border and background.
-            control->drawBorder(spriteBatch, clip);
+        // Cache themed attributes for performance.
+        _skin = getSkin(_state);
+        _opacity = getOpacity(_state);
 
-            // Add all themed foreground sprites (checkboxes etc.) to the same batch.
-            control->drawImages(spriteBatch, clip);
+        // Get scrollbar images and diminish clipping bounds to make room for scrollbars.
+        if ((_scroll & SCROLL_HORIZONTAL) == SCROLL_HORIZONTAL)
+        {
+            _scrollBarLeftCap = getImage("scrollBarLeftCap", _state);
+            _scrollBarHorizontal = getImage("horizontalScrollBar", _state);
+            _scrollBarRightCap = getImage("scrollBarRightCap", _state);
 
-            control->drawText(clip);
+            _viewportClipBounds.height -= _scrollBarHorizontal->getRegion().height;
         }
 
-        // Done all batching.
-        spriteBatch->end();
-
-        for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
+        if ((_scroll & SCROLL_VERTICAL) == SCROLL_VERTICAL)
         {
-            Font* font = *fontIter;
-            if (font)
-            {
-                font->end();
-            }
+            _scrollBarTopCap = getImage("scrollBarTopCap", _state);
+            _scrollBarVertical = getImage("verticalScrollBar", _state);
+            _scrollBarBottomCap = getImage("scrollBarBottomCap", _state);
+        
+            _viewportClipBounds.width -= _scrollBarVertical->getRegion().width;
         }
 
-        _dirty = false;
+        GP_ASSERT(_layout);
+        if (_scroll != SCROLL_NONE)
+            updateScroll();
+        else
+            _layout->update(this, Vector2::zero());
     }
+}
 
-    void Form::initializeQuad(Mesh* mesh)
-    {
-        // Release current model.
-        SAFE_RELEASE(_quad);
+void Form::draw()
+{
+    /*
+    The first time a form is drawn, its contents are rendered into a framebuffer.
+    The framebuffer will only be drawn into again when the contents of the form change.
 
-        // Create the model
-        _quad = Model::create(mesh);
+    If this form has a node then it's a 3D form and the framebuffer will be used
+    to texture a quad.  The quad will be given the same dimensions as the form and
+    must be transformed appropriately by the user, unless they call setQuad() themselves.
 
-        // Create the material
-        Material* material = _quad->setMaterial("res/shaders/textured.vsh", "res/shaders/textured.fsh");
+    On the other hand, if this form has not been set on a node, SpriteBatch will be used
+    to render the contents of the frambuffer directly to the display.
+    */
 
-        // Set the common render state block for the material
-        RenderState::StateBlock* stateBlock = _theme->getSpriteBatch()->getStateBlock();
-        stateBlock->setDepthWrite(true);
-        material->setStateBlock(stateBlock);
+    // Check whether this form has changed since the last call to draw()
+    // and if so, render into the framebuffer.
+    if (isDirty())
+    {
+        GP_ASSERT(_frameBuffer);
+        _frameBuffer->bind();
 
-        // Bind the WorldViewProjection matrix
-        material->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
+        Game* game = Game::getInstance();
+        Rectangle prevViewport = game->getViewport();
+        game->setViewport(Rectangle(0, 0, _bounds.width, _bounds.height));
 
-        // Create a FrameBuffer if necessary.
-        if (!_frameBuffer)
-        {
-            _frameBuffer = FrameBuffer::create(_id.c_str());
-        }
+        GP_ASSERT(_theme);
+        _theme->setProjectionMatrix(_projectionMatrix);
+        Container::draw(_theme->getSpriteBatch(), Rectangle(0, 0, _bounds.width, _bounds.height), _skin != NULL, false, _bounds.height);
+        _theme->setProjectionMatrix(_defaultProjectionMatrix);
+
+        // Rebind the default framebuffer and game viewport.
+        FrameBuffer::bindDefault();
+
+        // restore the previous game viewport
+        game->setViewport(prevViewport);
+    }
 
-        // Use the FrameBuffer to texture the quad.
-        if (!_frameBuffer->getRenderTarget())
+    if (_node)
+    {
+        GP_ASSERT(_quad);
+        _quad->draw();
+    }
+    else
+    {
+        if (!_spriteBatch)
         {
-            RenderTarget* rt = RenderTarget::create(_id.c_str(), _bounds.width, _bounds.height);
-            _frameBuffer->setRenderTarget(rt);
-            SAFE_RELEASE(rt);
+            _spriteBatch = SpriteBatch::create(_frameBuffer->getRenderTarget()->getTexture());
+            GP_ASSERT(_spriteBatch);
         }
 
-        Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
-        sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
-        material->getParameter("u_texture")->setValue(sampler);
-        material->getParameter("u_textureRepeat")->setValue(Vector2::one());
-        material->getParameter("u_textureTransform")->setValue(Vector2::zero());
-
-        SAFE_RELEASE(sampler);
+        _spriteBatch->begin();
+        _spriteBatch->draw(_bounds.x, _bounds.y, 0, _bounds.width, _bounds.height, 0, _v1, _u2, 0, Vector4::one());
+        _spriteBatch->end();
     }
+}
 
-    bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+void Form::initializeQuad(Mesh* mesh)
+{
+    // Release current model.
+    SAFE_RELEASE(_quad);
+
+    // Create the model.
+    _quad = Model::create(mesh);
+
+    // Create the material.
+    Material* material = _quad->setMaterial("res/shaders/textured.vsh", "res/shaders/textured.fsh");
+    GP_ASSERT(material);
+
+    // Set the common render state block for the material.
+    GP_ASSERT(_theme);
+    GP_ASSERT(_theme->getSpriteBatch());
+    RenderState::StateBlock* stateBlock = _theme->getSpriteBatch()->getStateBlock();
+    GP_ASSERT(stateBlock);
+    stateBlock->setDepthWrite(true);
+    material->setStateBlock(stateBlock);
+
+    // Bind the WorldViewProjection matrix.
+    material->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
+
+    // Bind the texture.
+    Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
+    GP_ASSERT(sampler);
+    sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
+    material->getParameter("u_diffuseTexture")->setValue(sampler);
+    material->getParameter("u_diffuseColor")->setValue(Vector4::one());
+
+    SAFE_RELEASE(sampler);
+}
+
+bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    // Check for a collision with each Form in __forms.
+    // Pass the event on.
+    std::vector<Form*>::const_iterator it;
+    for (it = __forms.begin(); it < __forms.end(); it++)
     {
-        // Check for a collision with each Form in __forms.
-        // Pass the event on.
-        std::vector<Form*>::const_iterator it;
-        for (it = __forms.begin(); it < __forms.end(); it++)
-        {
-            Form* form = *it;
+        Form* form = *it;
+        GP_ASSERT(form);
 
-            if (form->isEnabled())
+        if (form->isEnabled())
+        {
+            Node* node = form->_node;
+            if (node)
             {
-                Node* node = form->_node;
-                if (node)
-                {
-                    Scene* scene = node->getScene();
-                    Camera* camera = scene->getActiveCamera();
+                Scene* scene = node->getScene();
+                GP_ASSERT(scene);
+                Camera* camera = scene->getActiveCamera();
 
-                    if (camera)
+                if (camera)
+                {
+                    // Get info about the form's position.
+                    Matrix m = node->getMatrix();
+                    Vector3 min(0, 0, 0);
+                    m.transformPoint(&min);
+
+                    // Unproject point into world space.
+                    Ray ray;
+                    camera->pickRay(Game::getInstance()->getViewport(), x, y, &ray);
+
+                    // Find the quad's plane.
+                    // We know its normal is the quad's forward vector.
+                    Vector3 normal = node->getForwardVectorWorld();
+
+                    // To get the plane's distance from the origin,
+                    // we'll find the distance from the plane defined
+                    // by the quad's forward vector and one of its points
+                    // to the plane defined by the same vector and the origin.
+                    const float& a = normal.x; const float& b = normal.y; const float& c = normal.z;
+                    const float d = -(a*min.x) - (b*min.y) - (c*min.z);
+                    const float distance = fabs(d) /  sqrt(a*a + b*b + c*c);
+                    Plane plane(normal, -distance);
+
+                    // Check for collision with plane.
+                    float collisionDistance = ray.intersects(plane);
+                    if (collisionDistance != Ray::INTERSECTS_NONE)
                     {
-                        // Get info about the form's position.
-                        Matrix m = node->getMatrix();
-                        Vector3 min(0, 0, 0);
-                        m.transformPoint(&min);
-
-                        // Unproject point into world space.
-                        Ray ray;
-                        camera->pickRay(Game::getInstance()->getViewport(), x, y, &ray);
-
-                        // Find the quad's plane.
-                        // We know its normal is the quad's forward vector.
-                        Vector3 normal = node->getForwardVectorWorld();
-
-                        // To get the plane's distance from the origin,
-                        // we'll find the distance from the plane defined
-                        // by the quad's forward vector and one of its points
-                        // to the plane defined by the same vector and the origin.
-                        const float& a = normal.x; const float& b = normal.y; const float& c = normal.z;
-                        const float d = -(a*min.x) - (b*min.y) - (c*min.z);
-                        const float distance = abs(d) /  sqrt(a*a + b*b + c*c);
-                        Plane plane(normal, -distance);
-
-                        // Check for collision with plane.
-                        float collisionDistance = ray.intersects(plane);
-                        if (collisionDistance != Ray::INTERSECTS_NONE)
+                        // Multiply the ray's direction vector by collision distance
+                        // and add that to the ray's origin.
+                        Vector3 point = ray.getOrigin() + collisionDistance*ray.getDirection();
+
+                        // Project this point into the plane.
+                        m.invert();
+                        m.transformPoint(&point);
+
+                        // Pass the touch event on.
+                        const Rectangle& bounds = form->getBounds();
+                        if (form->getState() == Control::FOCUS ||
+                            (evt == Touch::TOUCH_PRESS &&
+                                point.x >= bounds.x &&
+                                point.x <= bounds.x + bounds.width &&
+                                point.y >= bounds.y &&
+                                point.y <= bounds.y + bounds.height))
                         {
-                            // Multiply the ray's direction vector by collision distance
-                            // and add that to the ray's origin.
-                            Vector3 point = ray.getOrigin() + collisionDistance*ray.getDirection();
-
-                            // Project this point into the plane.
-                            m.invert();
-                            m.transformPoint(&point);
-
-                            // Pass the touch event on.
-                            const Rectangle& bounds = form->getClipBounds();
-                            if (form->getState() == Control::FOCUS ||
-                                (evt == Touch::TOUCH_PRESS &&
-                                 point.x >= bounds.x &&
-                                 point.x <= bounds.x + bounds.width &&
-                                 point.y >= bounds.y &&
-                                 point.y <= bounds.y + bounds.height))
+                            if (form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex))
                             {
-                               if (form->touchEvent(evt, point.x - bounds.x, bounds.height - point.y - bounds.y, contactIndex))
-                               {
-                                   return true;
-                               }
+                                return true;
                             }
                         }
                     }
                 }
-                else
+            }
+            else
+            {
+                // Simply compare with the form's bounds.
+                const Rectangle& bounds = form->getBounds();
+                if (form->getState() == Control::FOCUS ||
+                    (evt == Touch::TOUCH_PRESS &&
+                        x >= bounds.x &&
+                        x <= bounds.x + bounds.width &&
+                        y >= bounds.y &&
+                        y <= bounds.y + bounds.height))
                 {
-                    // Simply compare with the form's bounds.
-                    const Rectangle& bounds = form->getClipBounds();
-                    if (form->getState() == Control::FOCUS ||
-                        (evt == Touch::TOUCH_PRESS &&
-                         x >= bounds.x &&
-                         x <= bounds.x + bounds.width &&
-                         y >= bounds.y &&
-                         y <= bounds.y + bounds.height))
+                    // Pass on the event's position relative to the form.
+                    if (form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex))
                     {
-                        // Pass on the event's position relative to the form.
-                        if (form->touchEvent(evt, x - bounds.x, y - bounds.y, contactIndex))
-                        {
-                            return true;
-                        }
+                        return true;
                     }
                 }
             }
         }
-
-        return false;
     }
 
-    void Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+    return false;
+}
+
+void Form::keyEventInternal(Keyboard::KeyEvent evt, int key)
+{
+    std::vector<Form*>::const_iterator it;
+    for (it = __forms.begin(); it < __forms.end(); it++)
     {
-        std::vector<Form*>::const_iterator it;
-        for (it = __forms.begin(); it < __forms.end(); it++)
+        Form* form = *it;
+        GP_ASSERT(form);
+        if (form->isEnabled())
         {
-            Form* form = *it;
-            if (form->isEnabled())
-            {
-                form->keyEvent(evt, key);
-            }
+            form->keyEvent(evt, key);
         }
     }
-}
+}
+
+int Form::nextHighestPowerOfTwo(int x)
+{
+    x--;
+    x |= x >> 1;
+    x |= x >> 2;
+    x |= x >> 4;
+    x |= x >> 8;
+    x |= x >> 16;
+    return x + 1;
+}
+
+}

+ 36 - 8
gameplay/src/Form.h

@@ -67,6 +67,35 @@ public:
      */
     static Form* getForm(const char* id);
 
+    /**
+     * Set the desired size of this form.
+     *
+     * @param width The width.
+     * @param height The height.
+     */
+    virtual void setSize(float width, float height);
+
+    /**
+     * Set the bounds of this form.
+     *
+     * @param bounds The new bounds to set.
+     */
+    virtual void setBounds(const Rectangle& bounds);
+
+    /**
+     * Set this form's width to that of the display.
+     *
+     * @param autoWidth Whether to set this form's width to that of the display.
+     */
+    virtual void setAutoWidth(bool autoWidth);
+
+    /**
+     * Set this form's height to that of the display.
+     *
+     * @param autoHeight Whether to set this form's height to that of the display.
+     */
+    virtual void setAutoHeight(bool autoHeight);
+
     /**
      * Create a 3D quad to texture with this Form.
      *
@@ -137,14 +166,6 @@ private:
      */
     void initializeQuad(Mesh* mesh);
 
-    /**
-     * Draw this form into the current framebuffer.
-     *
-     * @param spriteBatch The sprite batch containing this form's theme texture.
-     * @param clip The form's clipping rectangle.
-     */
-    void draw(SpriteBatch* spriteBatch, const Rectangle& clip);
-
     /**
      * Propagate touch events to enabled forms.
      *
@@ -157,11 +178,18 @@ private:
      */
     static void keyEventInternal(Keyboard::KeyEvent evt, int key);
 
+    static int nextHighestPowerOfTwo(int x);
+
     Theme* _theme;              // The Theme applied to this Form.
     Model* _quad;               // Quad for rendering this Form in world-space.
     Node* _node;                // Node for transforming this Form in world-space.
     FrameBuffer* _frameBuffer;  // FBO the Form is rendered into for texturing the quad.
     Matrix _projectionMatrix;   // Orthographic projection matrix to be set on SpriteBatch objects when rendering into the FBO.
+    Matrix _defaultProjectionMatrix;
+    SpriteBatch* _spriteBatch;
+
+    float _u2;
+    float _v1;
 };
 
 }

+ 29 - 11
gameplay/src/FrameBuffer.cpp

@@ -10,6 +10,7 @@ namespace gameplay
 
 static unsigned int __maxRenderTargets = 0;
 static std::vector<FrameBuffer*> __frameBuffers;
+static FrameBufferHandle __defaultHandle = 0;
 
 FrameBuffer::FrameBuffer(const char* id) :
     _id(id ? id : ""), _handle(0), _renderTargets(NULL), _depthStencilTarget(NULL)
@@ -41,6 +42,13 @@ FrameBuffer::~FrameBuffer()
     }
 }
 
+void FrameBuffer::initialize()
+{
+    GLint fbo;
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);
+    __defaultHandle = (FrameBufferHandle)fbo;
+}
+
 FrameBuffer* FrameBuffer::create(const char* id)
 {
     // Create GL FBO resource.
@@ -55,7 +63,7 @@ FrameBuffer* FrameBuffer::create(const char* id)
     memset(renderTargets, 0, sizeof(RenderTarget*) * __maxRenderTargets);
 
     // Create the new frame buffer
-    FrameBuffer* frameBuffer = new FrameBuffer(id ? id : "");
+    FrameBuffer* frameBuffer = new FrameBuffer(id);
     frameBuffer->_handle = handle;
     frameBuffer->_renderTargets = renderTargets;
 
@@ -67,21 +75,23 @@ FrameBuffer* FrameBuffer::create(const char* id)
 
 FrameBuffer* FrameBuffer::create(const char* id, unsigned int width, unsigned int height)
 {
-    // Create RenderTarget with same ID
+    // Create RenderTarget with same ID.
     RenderTarget* renderTarget = RenderTarget::create(id, width, height);
     if (renderTarget == NULL)
     {
+        GP_ERROR("Failed to create render target for frame buffer.");
         return NULL;
     }
 
-    // Create the frame buffer
+    // Create the frame buffer.
     FrameBuffer* frameBuffer = create(id);
     if (frameBuffer == NULL)
     {
+        GP_ERROR("Failed to create frame buffer.");
         return NULL;
     }
 
-    // Add the render target as the first color attachment
+    // Add the render target as the first color attachment.
     frameBuffer->setRenderTarget(renderTarget);
     SAFE_RELEASE(renderTarget);
 
@@ -90,11 +100,14 @@ FrameBuffer* FrameBuffer::create(const char* id, unsigned int width, unsigned in
 
 FrameBuffer* FrameBuffer::getFrameBuffer(const char* id)
 {
+    GP_ASSERT(id);
+
     // Search the vector for a matching ID.
     std::vector<FrameBuffer*>::const_iterator it;
     for (it = __frameBuffers.begin(); it < __frameBuffers.end(); it++)
     {
         FrameBuffer* fb = *it;
+        GP_ASSERT(fb);
         if (strcmp(id, fb->getID()) == 0)
         {
             return fb;
@@ -127,11 +140,12 @@ unsigned int FrameBuffer::getMaxRenderTargets()
 
 void FrameBuffer::setRenderTarget(RenderTarget* target, unsigned int index)
 {
-    assert(index < __maxRenderTargets);
+    GP_ASSERT(index < __maxRenderTargets);
+    GP_ASSERT(_renderTargets);
 
     if (_renderTargets[index] == target)
     {
-        // No change
+        // No change.
         return;
     }
 
@@ -152,6 +166,7 @@ void FrameBuffer::setRenderTarget(RenderTarget* target, unsigned int index)
         // Now set this target as the color attachment corresponding to index.
         GL_ASSERT( glBindFramebuffer(GL_FRAMEBUFFER, _handle) );
         GLenum attachment = GL_COLOR_ATTACHMENT0 + index;
+        GP_ASSERT(_renderTargets[index]->getTexture());
         GL_ASSERT( glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, _renderTargets[index]->getTexture()->getHandle(), 0) );
 
         // Restore the FBO binding
@@ -161,6 +176,7 @@ void FrameBuffer::setRenderTarget(RenderTarget* target, unsigned int index)
 
 RenderTarget* FrameBuffer::getRenderTarget(unsigned int index) const
 {
+    GP_ASSERT(_renderTargets);
     if (index < __maxRenderTargets)
     {
         return _renderTargets[index];
@@ -176,24 +192,26 @@ void FrameBuffer::setDepthStencilTarget(DepthStencilTarget* target)
         return; // No change
     }
 
-    // Release our existing depth stencil target
+    // Release our existing depth stencil target.
     SAFE_RELEASE(_depthStencilTarget);
 
     _depthStencilTarget = target;
 
     if (target)
     {
-        // The FrameBuffer now owns this DepthStencilTarget
+        // The FrameBuffer now owns this DepthStencilTarget.
         target->addRef();
 
-        // Store the current FBO binding so we can restore it
+        // Store the current FBO binding so we can restore it.
         GLint currentFbo;
         GL_ASSERT( glGetIntegerv(GL_FRAMEBUFFER_BINDING, &currentFbo) );
 
         // Now set this target as the color attachment corresponding to index.
         GL_ASSERT( glBindFramebuffer(GL_FRAMEBUFFER, _handle) );
 
-        // Bind the depth texture
+        // Bind the depth texture.
+        GP_ASSERT(_depthStencilTarget);
+        GP_ASSERT(_depthStencilTarget->getTexture());
         GL_ASSERT( glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _depthStencilTarget->getTexture()->getHandle(), 0) );
 
         // If the taget has a stencil buffer, bind that as well
@@ -220,7 +238,7 @@ void FrameBuffer::bind()
 
 void FrameBuffer::bindDefault()
 {
-    GL_ASSERT( glBindFramebuffer(GL_FRAMEBUFFER, 0) );
+    GL_ASSERT( glBindFramebuffer(GL_FRAMEBUFFER, __defaultHandle) );
 }
 
 }

+ 4 - 0
gameplay/src/FrameBuffer.h

@@ -19,6 +19,8 @@ namespace gameplay
  */
 class FrameBuffer : public Ref
 {
+    friend class Game;
+
 public:
 
     /**
@@ -118,6 +120,8 @@ private:
      */
     ~FrameBuffer();
 
+    static void initialize();
+
     std::string _id;
     FrameBufferHandle _handle;
     RenderTarget** _renderTargets;

+ 28 - 23
gameplay/src/Frustum.cpp

@@ -57,12 +57,13 @@ const Plane& Frustum::getTop() const
 
 void Frustum::getMatrix(Matrix* dst) const
 {
+    GP_ASSERT(dst);
     dst->set(_matrix);
 }
 
 void Frustum::getCorners(Vector3* corners) const
 {
-    assert(corners);
+    GP_ASSERT(corners);
 
     Plane::intersection(_near, _left, _top, &corners[0]);
     Plane::intersection(_near, _left, _bottom, &corners[1]);
@@ -74,6 +75,24 @@ void Frustum::getCorners(Vector3* corners) const
     Plane::intersection(_far, _left, _top, &corners[7]);
 }
 
+bool Frustum::intersects(const Vector3& point) const
+{
+    if (_near.distance(point) <= 0)
+        return false;
+    if (_far.distance(point) <= 0)
+        return false;
+    if (_left.distance(point) <= 0)
+        return false;
+    if (_right.distance(point) <= 0)
+        return false;
+    if (_top.distance(point) <= 0)
+        return false;
+    if (_bottom.distance(point) <= 0)
+        return false;
+
+    return true;
+}
+
 bool Frustum::intersects(const BoundingSphere& sphere) const
 {
     return sphere.intersects(*this);
@@ -105,23 +124,14 @@ void Frustum::set(const Frustum& frustum)
     _matrix.set(frustum._matrix);
 }
 
-void updatePlane(const Matrix& matrix, Plane* dst)
+void Frustum::updatePlanes()
 {
-    assert(dst);
-
-    dst->setNormal(Vector3(matrix.m[3] + matrix.m[2], matrix.m[7] + matrix.m[6], matrix.m[11] + matrix.m[10]));
-    dst->setDistance(matrix.m[15] + matrix.m[14]);
-
-    Vector3 normal = dst->getNormal();
-    if (!normal.isZero())
-    {
-        float normalizeFactor = 1.0f / sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
-        if (normalizeFactor != 1.0f)
-        {
-            dst->setNormal(Vector3(normal.x * normalizeFactor, normal.y * normalizeFactor, normal.z * normalizeFactor));
-            dst->setDistance(dst->getDistance() * normalizeFactor);
-        }
-    }
+    _near.set(Vector3(_matrix.m[3] + _matrix.m[2], _matrix.m[7] + _matrix.m[6], _matrix.m[11] + _matrix.m[10]), _matrix.m[15] + _matrix.m[14]);
+    _far.set(Vector3(_matrix.m[3] - _matrix.m[2], _matrix.m[7] - _matrix.m[6], _matrix.m[11] - _matrix.m[10]), _matrix.m[15] - _matrix.m[14]);
+    _bottom.set(Vector3(_matrix.m[3] + _matrix.m[1], _matrix.m[7] + _matrix.m[5], _matrix.m[11] + _matrix.m[9]), _matrix.m[15] + _matrix.m[13]);
+    _top.set(Vector3(_matrix.m[3] - _matrix.m[1], _matrix.m[7] - _matrix.m[5], _matrix.m[11] - _matrix.m[9]), _matrix.m[15] - _matrix.m[13]);
+    _left.set(Vector3(_matrix.m[3] + _matrix.m[0], _matrix.m[7] + _matrix.m[4], _matrix.m[11] + _matrix.m[8]), _matrix.m[15] + _matrix.m[12]);
+    _right.set(Vector3(_matrix.m[3] - _matrix.m[0], _matrix.m[7] - _matrix.m[4], _matrix.m[11] - _matrix.m[8]), _matrix.m[15] - _matrix.m[12]);
 }
 
 void Frustum::set(const Matrix& matrix)
@@ -129,12 +139,7 @@ void Frustum::set(const Matrix& matrix)
     _matrix.set(matrix);
 
     // Update the planes.
-    updatePlane(matrix, &_near);
-    updatePlane(matrix, &_far);
-    updatePlane(matrix, &_bottom);
-    updatePlane(matrix, &_top);
-    updatePlane(matrix, &_left);
-    updatePlane(matrix, &_right);
+    updatePlanes();
 }
 
 }

+ 9 - 0
gameplay/src/Frustum.h

@@ -113,6 +113,15 @@ public:
      */
     void getCorners(Vector3* corners) const;
 
+    /**
+     * Tests whether this frustum instersects the specified point.
+     *
+     * @param point The point to test intersection with.
+     *
+     * @return true if the specified point intersects this frustum; false otherwise.
+     */
+    bool intersects(const Vector3& point) const;
+
     /**
      * Tests whether this frustum intersects the specified bounding sphere.
      *

+ 64 - 17
gameplay/src/Game.cpp

@@ -2,9 +2,12 @@
 #include "Game.h"
 #include "Platform.h"
 #include "RenderState.h"
+#include "FileSystem.h"
+#include "FrameBuffer.h"
 
 // Extern global variables
 GLenum __gl_error_code = GL_NO_ERROR;
+ALenum __al_error_code = AL_NO_ERROR;
 
 namespace gameplay
 {
@@ -16,10 +19,10 @@ long Game::_pausedTimeTotal = 0L;
 Game::Game() 
     : _initialized(false), _state(UNINITIALIZED), 
       _frameLastFPS(0), _frameCount(0), _frameRate(0), 
-      _clearDepth(1.0f), _clearStencil(0),
+      _clearDepth(1.0f), _clearStencil(0), _properties(NULL),
       _animationController(NULL), _audioController(NULL), _physicsController(NULL), _audioListener(NULL)
 {
-    assert(__gameInstance == NULL);
+    GP_ASSERT(__gameInstance == NULL);
     __gameInstance = this;
     _timeEvents = new std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> >();
 }
@@ -32,7 +35,7 @@ Game::~Game()
 {
     // Do not call any virtual functions from the destructor.
     // Finalization is done from outside this class.
-    delete _timeEvents;
+    SAFE_DELETE(_timeEvents);
 #ifdef GAMEPLAY_MEM_LEAK_DETECTION
     Ref::printLeaks();
     printMemoryLeaks();
@@ -41,6 +44,7 @@ Game::~Game()
 
 Game* Game::getInstance()
 {
+    GP_ASSERT(__gameInstance);
     return __gameInstance;
 }
 
@@ -64,20 +68,15 @@ bool Game::isVsync()
     return Platform::isVsync();
 }
 
-int Game::run(int width, int height)
+int Game::run()
 {
     if (_state != UNINITIALIZED)
         return -1;
 
-    if (width == -1)
-        _width = Platform::getDisplayWidth();
-    else
-        _width = width;
-    
-    if (height == -1)
-        _height = Platform::getDisplayHeight();
-    else
-        _height = height;
+    loadConfig();
+
+    _width = Platform::getDisplayWidth();
+    _height = Platform::getDisplayHeight();
 
     // Start up game systems.
     if (!startup())
@@ -94,8 +93,8 @@ bool Game::startup()
         return false;
 
     setViewport(Rectangle(0.0f, 0.0f, (float)_width, (float)_height));
-
     RenderState::initialize();
+    FrameBuffer::initialize();
 
     _animationController = new AnimationController();
     _animationController->initialize();
@@ -116,6 +115,10 @@ void Game::shutdown()
     // Call user finalization.
     if (_state != UNINITIALIZED)
     {
+        GP_ASSERT(_animationController);
+        GP_ASSERT(_audioController);
+        GP_ASSERT(_physicsController);
+
         Platform::signalShutdown();
         finalize();
 
@@ -131,7 +134,9 @@ void Game::shutdown()
         SAFE_DELETE(_audioListener);
 
         RenderState::finalize();
-        
+
+        SAFE_DELETE(_properties);
+
         _state = UNINITIALIZED;
     }
 }
@@ -140,6 +145,10 @@ void Game::pause()
 {
     if (_state == RUNNING)
     {
+        GP_ASSERT(_animationController);
+        GP_ASSERT(_audioController);
+        GP_ASSERT(_physicsController);
+
         _state = PAUSED;
         _pausedTimeLast = Platform::getAbsoluteTime();
         _animationController->pause();
@@ -152,6 +161,10 @@ void Game::resume()
 {
     if (_state == PAUSED)
     {
+        GP_ASSERT(_animationController);
+        GP_ASSERT(_audioController);
+        GP_ASSERT(_physicsController);
+
         _state = RUNNING;
         _pausedTimeTotal += Platform::getAbsoluteTime() - _pausedTimeLast;
         _animationController->resume();
@@ -175,6 +188,10 @@ void Game::frame()
 
     if (_state == Game::RUNNING)
     {
+        GP_ASSERT(_animationController);
+        GP_ASSERT(_audioController);
+        GP_ASSERT(_physicsController);
+
         // Update Time.
         static long lastFrameTime = Game::getGameTime();
         long frameTime = Game::getGameTime();
@@ -288,7 +305,7 @@ void Game::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactI
 
 void Game::schedule(long timeOffset, TimeListener* timeListener, void* cookie)
 {
-    assert(timeListener);
+    GP_ASSERT(_timeEvents);
     TimeEvent timeEvent(getGameTime() + timeOffset, timeListener, cookie);
     _timeEvents->push(timeEvent);
 }
@@ -300,6 +317,10 @@ bool Game::mouseEvent(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
 
 void Game::updateOnce()
 {
+    GP_ASSERT(_animationController);
+    GP_ASSERT(_audioController);
+    GP_ASSERT(_physicsController);
+
     // Update Time.
     static long lastFrameTime = Game::getGameTime();
     long frameTime = Game::getGameTime();
@@ -312,6 +333,31 @@ void Game::updateOnce()
     _audioController->update(elapsedTime);
 }
 
+Properties* Game::getConfig() const
+{
+    if (_properties == NULL)
+        const_cast<Game*>(this)->loadConfig();
+
+    return _properties;
+}
+
+void Game::loadConfig()
+{
+    if (_properties == NULL)
+    {
+        // Try to load custom config from file.
+        if (FileSystem::fileExists("game.config"))
+        {
+            _properties = Properties::create("game.config");
+            
+            // Load filesystem aliases.
+            Properties* aliases = _properties->getNamespace("aliases", true);
+            if (aliases)
+                FileSystem::loadResourceAliases(aliases);
+        }
+    }
+}
+
 void Game::fireTimeEvents(long frameTime)
 {
     while (_timeEvents->size() > 0)
@@ -321,7 +367,8 @@ void Game::fireTimeEvents(long frameTime)
         {
             break;
         }
-        timeEvent->listener->timeEvent(frameTime - timeEvent->time, timeEvent->cookie);
+        if (timeEvent->listener)
+            timeEvent->listener->timeEvent(frameTime - timeEvent->time, timeEvent->cookie);
         _timeEvents->pop();
     }
 }

+ 17 - 4
gameplay/src/Game.h

@@ -99,14 +99,21 @@ public:
     inline State getState() const;
 
     /**
-     * Call this method to initialize the game, and begin running the game.
+     * Returns the game configuration object.
      *
-     * @param width The width of the game window to run at. Default is -1 meaning native resolution width.
-     * @param height The height of the game window to run at. Default is -1 meaning native resolution height.
+     * This method returns a Properties object containing the contents
+     * of the game.config file.
+     *
+     * @return The game conifguration Properties object.
+     */
+    Properties* getConfig() const;
+
+    /**
+     * Called to initialize the game, and begin running the game.
      * 
      * @return Zero for normal termination, or non-zero if an error occurred.
      */
-    int run(int width = -1, int height = -1);
+    int run();
 
     /**
      * Pauses the game after being run.
@@ -393,6 +400,11 @@ private:
      */
     void fireTimeEvents(long frameTime);
 
+    /**
+     * Loads the game configuration.
+     */
+    void loadConfig();
+
     bool _initialized;                          // If game has initialized yet.
     State _state;                               // The game state.
     static long _pausedTimeLast;                // The last time paused.
@@ -406,6 +418,7 @@ private:
     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.
+    Properties* _properties;                    // Game configuration properties object.
     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.

+ 1 - 0
gameplay/src/Game.inl

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

+ 32 - 8
gameplay/src/Image.cpp

@@ -7,10 +7,13 @@ namespace gameplay
 
 Image* Image::create(const char* path)
 {
+    GP_ASSERT(path);
+
     // Open the file.
     FILE* fp = FileSystem::openFile(path, "rb");
     if (fp == NULL)
     {
+        GP_ERROR("Failed to open image file '%s'.", path);
         return NULL;
     }
 
@@ -18,8 +21,11 @@ Image* Image::create(const char* path)
     unsigned char sig[8];
     if (fread(sig, 1, 8, fp) != 8 || png_sig_cmp(sig, 0, 8) != 0)
     {
-        LOG_ERROR_VARG("Texture is not a valid PNG: %s", path);
-        fclose(fp);
+        GP_ERROR("Failed to load file '%s'; not a valid PNG.", path);
+        if (fclose(fp) != 0)
+        {
+            GP_ERROR("Failed to close image file '%s'.", path);
+        }
         return NULL;
     }
 
@@ -27,7 +33,11 @@ Image* Image::create(const char* path)
     png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
     if (png == NULL)
     {
-        fclose(fp);
+        GP_ERROR("Failed to create PNG structure for reading PNG file '%s'.", path);
+        if (fclose(fp) != 0)
+        {
+            GP_ERROR("Failed to close image file '%s'.", path);
+        }
         return NULL;
     }
 
@@ -35,7 +45,11 @@ Image* Image::create(const char* path)
     png_infop info = png_create_info_struct(png);
     if (info == NULL)
     {
-        fclose(fp);
+        GP_ERROR("Failed to create PNG info structure for PNG file '%s'.", path);
+        if (fclose(fp) != 0)
+        {
+            GP_ERROR("Failed to close image file '%s'.", path);
+        }
         png_destroy_read_struct(&png, NULL, NULL);
         return NULL;
     }
@@ -43,7 +57,11 @@ Image* Image::create(const char* path)
     // Set up error handling (required without using custom error handlers above).
     if (setjmp(png_jmpbuf(png)))
     {
-        fclose(fp);
+        GP_ERROR("Failed to set up error handling for reading PNG file '%s'.", path);
+        if (fclose(fp) != 0)
+        {
+            GP_ERROR("Failed to close image file '%s'.", path);
+        }
         png_destroy_read_struct(&png, &info, NULL);
         return NULL;
     }
@@ -73,8 +91,11 @@ Image* Image::create(const char* path)
         break;
 
     default:
-        LOG_ERROR_VARG("Unsupported PNG color type (%d) for texture: %s", (int)colorType, path);
-        fclose(fp);
+        GP_ERROR("Unsupported PNG color type (%d) for image file '%s'.", (int)colorType, path);
+        if (fclose(fp) != 0)
+        {
+            GP_ERROR("Failed to close image file '%s'.", path);
+        }
         png_destroy_read_struct(&png, &info, NULL);
         return NULL;
     }
@@ -93,7 +114,10 @@ Image* Image::create(const char* path)
 
     // Clean up.
     png_destroy_read_struct(&png, &info, NULL);
-    fclose(fp);
+    if (fclose(fp) != 0)
+    {
+        GP_ERROR("Failed to close image file '%s'.", path);
+    }
 
     return image;
 }

+ 2 - 0
gameplay/src/Joint.cpp

@@ -22,6 +22,7 @@ Joint* Joint::create(const char* id)
 Node* Joint::cloneSingleNode(NodeCloneContext &context) const
 {
     Joint* copy = Joint::create(getId());
+    GP_ASSERT(copy);
     context.registerClonedNode(this, copy);
     copy->_bindPose = _bindPose;
     copy->_skinCount = _skinCount;
@@ -54,6 +55,7 @@ void Joint::updateJointMatrix(const Matrix& bindShape, Vector4* matrixPalette)
         Matrix::multiply(Node::getWorldMatrix(), getInverseBindPose(), &t);
         Matrix::multiply(t, bindShape, &t);
 
+        GP_ASSERT(matrixPalette);
         matrixPalette[0].set(t.m[0], t.m[4], t.m[8], t.m[12]);
         matrixPalette[1].set(t.m[1], t.m[5], t.m[9], t.m[13]);
         matrixPalette[2].set(t.m[2], t.m[6], t.m[10], t.m[14]);

+ 229 - 0
gameplay/src/Joystick.cpp

@@ -0,0 +1,229 @@
+#include "Base.h"
+#include "Joystick.h"
+
+#define INVALID_CONTACT_INDEX ((unsigned int)-1)
+
+namespace gameplay
+{
+
+Joystick::Joystick() : _contactIndex(INVALID_CONTACT_INDEX), _absolute(true)
+{
+}
+
+Joystick::Joystick(const Joystick& copy)
+{
+}
+
+Joystick::~Joystick()
+{
+}
+
+Joystick* Joystick::create(Theme::Style* style, Properties* properties)
+{
+    Joystick* joystick = new Joystick();
+    joystick->initialize(style, properties);
+    joystick->_consumeTouchEvents = false;
+
+    return joystick;
+}
+
+void Joystick::initialize(Theme::Style* style, Properties* properties)
+{
+    GP_ASSERT(properties);
+
+    Control::initialize(style, properties);
+
+    if (!properties->exists("radius"))
+    {
+        GP_ERROR("Failed to load joystick; required attribute 'radius' is missing.");
+        return;
+    }
+    _radius = properties->getFloat("radius");
+
+    Vector4 v;
+    if (properties->getVector4("region", &v))
+    {
+        _absolute = false;
+        _region = _bounds;
+        _bounds.x = v.x;
+        _bounds.y = v.y;
+        _bounds.width = v.z;
+        _bounds.height = v.w;
+    }
+}
+
+void Joystick::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    {
+        GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
+    }
+
+    _consumeTouchEvents = true;
+
+    Control::addListener(listener, eventFlags);
+}
+
+bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned int contactIndex)
+{
+    switch (touchEvent)
+    {
+        case Touch::TOUCH_PRESS:
+        {
+            float dx = 0.0f;
+            float dy = 0.0f;
+
+            if (_absolute)
+            {
+                dx = x - _bounds.width * 0.5f;
+                dy = _bounds.height * 0.5f - y;
+            }
+            else
+            {
+                _region.x = x + _bounds.x - _region.width * 0.5f;
+                _region.y = y + _bounds.y - _region.height * 0.5f;
+            }
+
+            if ((dx >= -_radius && dx <= _radius) && (dy >= -_radius && dy <= _radius) && 
+                _contactIndex == INVALID_CONTACT_INDEX)
+            {
+                _contactIndex = contactIndex;
+                _displacement.set(0.0f, 0.0f);
+                
+                Vector2 value(0.0f, 0.0f);
+                if (_value != value)
+                {
+                    _value.set(value);
+                    _dirty = true;
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
+                }
+
+                _state = ACTIVE;
+            }
+        }
+        case Touch::TOUCH_MOVE:
+        {
+            if (_contactIndex == contactIndex)
+            {
+                float dx = x - ((!_absolute) ? _region.x - _bounds.x : 0.0f) - _region.width * 0.5f;
+                float dy = -(y - ((!_absolute) ? _region.y - _bounds.y : 0.0f) - _region.height * 0.5f);
+                if (((dx * dx) + (dy * dy)) <= (_radius * _radius))
+                {
+                    GP_ASSERT(_radius);
+                    Vector2 value(dx, dy);
+                    value.scale(1.0f / _radius);
+                    if (_value != value)
+                    {
+                        _value.set(value);
+                        _dirty = true;
+                        notifyListeners(Control::Listener::VALUE_CHANGED);
+                    }
+                }
+                else
+                {
+                    Vector2 value(dx, dy);
+                    value.normalize();
+                    value.scale(_radius);
+                    value.normalize();
+                    if (_value != value)
+                    {
+                        _value.set(value);
+                        _dirty = true;
+                        notifyListeners(Control::Listener::VALUE_CHANGED);
+                    }
+                }
+
+                _displacement.set(dx, dy);
+            }
+        }
+        break;
+        case Touch::TOUCH_RELEASE:
+        {
+            if (_contactIndex == contactIndex)
+            {
+                // Reset displacement and direction vectors.
+                _contactIndex = INVALID_CONTACT_INDEX;
+                _displacement.set(0.0f, 0.0f);
+                
+                Vector2 value(0.0f, 0.0f);
+                if (_value != value)
+                {
+                    _value.set(value);
+                    _dirty = true;
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
+                }
+
+                _state = NORMAL;
+            }
+        }
+        break;
+    }
+
+    return Control::touchEvent(touchEvent, x, y, contactIndex);
+}
+
+void Joystick::update(const Control* container, const Vector2& offset)
+{
+    Control::update(container, offset);
+
+    _clearBounds.x -= _radius;
+    _clearBounds.y -= _radius;
+    float radiusx2 = _radius + _radius;
+    _clearBounds.width += radiusx2;
+    _clearBounds.height += radiusx2;
+}
+
+void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+{
+    GP_ASSERT(spriteBatch);
+    spriteBatch->begin();
+
+    // If the joystick is not absolute, then only draw if it is active.
+    if (_absolute || (!_absolute && _state == ACTIVE))
+    {
+        if (_absolute)
+            _region = _bounds;
+
+        // Draw the outer image.
+        Theme::ThemeImage* outer = getImage("outer", _state);
+        if (outer)
+        {
+            // Get the uvs and color and draw.
+            const Theme::UVs& uvs = outer->getUVs();
+            const Vector4& color = outer->getColor();
+            spriteBatch->draw(_region.x, _region.y, _region.width, _region.height, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+        }
+
+        // Draw the inner image.
+        Theme::ThemeImage* inner = getImage("inner", _state);
+        if (inner)
+        {
+            Rectangle region = _region;
+
+            // Adjust position to reflect displacement.
+            if (((_displacement.x * _displacement.x) + (_displacement.y * _displacement.y)) <= (_radius * _radius))
+            {
+                region.x += _displacement.x;
+                region.y += -_displacement.y;
+            }
+            else
+            {
+                // Find the point on the joystick's circular bound where the
+                // vector intersects. This is the position of the inner image.
+                Vector2 delta = Vector2(_displacement.x, -_displacement.y);
+                delta.normalize();
+                delta.scale(_radius);
+                region.x += delta.x;
+                region.y += delta.y;
+            }
+        
+            // Get the uvs and color and draw.
+            const Theme::UVs& uvs = inner->getUVs();
+            const Vector4& color = inner->getColor();
+            spriteBatch->draw(region.x, region.y, _region.width, _region.height, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+        }
+    }
+    spriteBatch->end();
+}
+
+}

+ 149 - 0
gameplay/src/Joystick.h

@@ -0,0 +1,149 @@
+#ifndef JOYSTICK_H_
+#define JOYSTICK_H_
+
+#include "Control.h"
+
+namespace gameplay
+{
+
+/**
+ * Defines a control representing a joystick (axis).
+ */
+class Joystick : public Control
+{
+    friend class Container;
+
+public:
+    
+    /**
+     * Add a listener to be notified of specific events affecting
+     * this control.  Event types can be OR'ed together.
+     * E.g. To listen to touch-press and touch-release events,
+     * pass <code>Control::Listener::TOUCH | Control::Listener::RELEASE</code>
+     * as the second parameter.
+     *
+     * @param listener The listener to add.
+     * @param eventFlags The events to listen for.
+     */
+    void addListener(Control::Listener* listener, int eventFlags);
+
+    /**
+     * Retrieves the value (2-dimensional direction) of the joystick.
+     * 
+     * @return The value of the joystick.
+     */
+    inline const Vector2& getValue() const;
+
+    /**
+     * Sets the region within which the joystick will be spontaneously created on a user's touch.
+     * 
+     * Note: This does not actually enable spontaneous joystick creation on touch input.
+     * To enable (or disable) absolute position explicitly, use #setAbsolute.
+     * 
+     * @param region The region to use.
+     */
+    inline void setRegion(const Rectangle& region);
+
+    /**
+     * Gets the region within which the joystick will be spontaneously created on a user's touch.
+     * 
+     * Note: just because the returned region is not empty does not mean that it is necessarily
+     * being used. If absolute positioning is not enabled, then it will be used (to check if
+     * absolute positioning is enabled, call #isAbsolute).
+     * 
+     * @return The region within which the joystick will be spontaneously created on a user's touch.
+     */
+    inline const Rectangle& getRegion() const;
+
+    /**
+     * Sets whether absolute positioning is enabled or not.
+     * 
+     * @param absolute Whether absolute positioning should be enabled or not.
+     */
+    inline void setAbsolute(bool absolute);
+
+    /**
+     * Retrieves whether absolute positioning is enabled or not.
+     * 
+     * @return <code>true</code> if absolute positioning is enabled; <code>false</code> otherwise.
+     */
+    inline bool isAbsolute() const;
+
+protected:
+    
+    /**
+     * Constructor.
+     */
+    Joystick();
+
+    /**
+     * Destructor.
+     */
+    virtual ~Joystick();
+
+    /**
+     * Create a joystick with a given style and properties.
+     *
+     * @param style The style to apply to this joystick.
+     * @param properties The properties to set on this joystick.
+     *
+     * @return The new joystick.
+     */
+    static Joystick* create(Theme::Style* style, Properties* properties);
+
+    /**
+     * Initialize this joystick.
+     */
+    virtual void initialize(Theme::Style* style, Properties* properties);
+
+    /**
+     * Touch callback on touch events.  Controls return true if they consume the touch event.
+     *
+     * @param evt The touch event that occurred.
+     * @param x The x position of the touch in pixels. Left edge is zero.
+     * @param y The y position of the touch in pixels. Top edge is zero.
+     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
+     *
+     * @return Whether the touch event was consumed by the control.
+     *
+     * @see Touch::TouchEvent
+     */
+    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    /**
+     * Called when a control's properties change.  Updates this control's internal rendering
+     * properties, such as its text viewport.
+     *
+     * @param container This control's parent container.
+     * @param offset Positioning offset to add to the control's position.
+     */
+    void update(const Control* container, const Vector2& offset);
+
+    /**
+     * 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.
+     */
+    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+
+private:
+
+    /**
+     * Copy constructor.
+     */
+    Joystick(const Joystick& copy);
+
+    float _radius;
+    unsigned int _contactIndex;
+    bool _absolute;
+    Vector2 _displacement;
+    Vector2 _value;
+    Rectangle _region;
+};
+
+}
+
+#include "Joystick.inl"
+
+#endif

+ 34 - 0
gameplay/src/Joystick.inl

@@ -0,0 +1,34 @@
+#include "Joystick.h"
+
+namespace gameplay
+{
+
+inline const Vector2& Joystick::getValue() const
+{
+    return _value;
+}
+
+inline void Joystick::setRegion(const Rectangle& region)
+{
+    if (_region.isEmpty())
+        _region = _bounds;
+
+    _bounds = region;
+}
+
+inline const Rectangle& Joystick::getRegion() const
+{
+    return _bounds;
+}
+
+inline void Joystick::setAbsolute(bool absolute)
+{
+    _absolute = absolute;
+}
+
+inline bool Joystick::isAbsolute() const
+{
+    return _absolute;
+}
+
+}

+ 73 - 62
gameplay/src/Label.cpp

@@ -3,87 +3,98 @@
 
 namespace gameplay
 {
-    Label::Label() : _text(""), _font(NULL)
-    {
-    }
 
-    Label::Label(const Label& copy)
-    {
-    }
+Label::Label() : _text(""), _font(NULL)
+{
+}
 
-    Label::~Label()
-    {
-    }
+Label::Label(const Label& copy)
+{
+}
 
-    Label* Label::create(Theme::Style* style, Properties* properties)
-    {
-        Label* label = new Label();
-        label->initialize(style, properties);
+Label::~Label()
+{
+}
 
-        return label;
-    }
+Label* Label::create(Theme::Style* style, Properties* properties)
+{
+    Label* label = new Label();
+    label->initialize(style, properties);
+    label->_consumeTouchEvents = false;
 
-    void Label::initialize(Theme::Style* style, Properties* properties)
-    {
-        Control::initialize(style, properties);
+    return label;
+}
 
-        const char* text = properties->getString("text");
-        if (text)
-        {
-            _text = text;
-        }
+void Label::initialize(Theme::Style* style, Properties* properties)
+{
+    GP_ASSERT(properties);
+
+    Control::initialize(style, properties);
+
+    const char* text = properties->getString("text");
+    if (text)
+    {
+        _text = text;
     }
+}
 
-    void Label::addListener(Control::Listener* listener, int eventFlags)
+void Label::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
     {
-        if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
-        {
-            assert("TEXT_CHANGED event is not applicable to this control.");
-            eventFlags &= ~Listener::TEXT_CHANGED;
-        }
-
-        if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
-        {
-            assert("VALUE_CHANGED event is not applicable to this control.");
-            eventFlags &= ~Listener::VALUE_CHANGED;
-        }
-
-        Control::addListener(listener, eventFlags);
+        GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
     }
-    
-    void Label::setText(const char* text)
+    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
     {
-        if (text)
-        {
-            _text = text;
-        }
+        GP_ERROR("VALUE_CHANGED event is not applicable to this control.");
     }
 
-    const char* Label::getText()
+    _consumeTouchEvents = true;
+
+    Control::addListener(listener, eventFlags);
+}
+    
+void Label::setText(const char* text)
+{
+    assert(text);
+
+    if (strcmp(text, _text.c_str()) != 0)
     {
-        return _text.c_str();
+        _text = text;
+        _dirty = true;
     }
+}
 
-    void Label::update(const Rectangle& clip)
-    {
-        Control::update(clip);
+const char* Label::getText()
+{
+    return _text.c_str();
+}
 
-        _font = getFont(_state);
-        _textColor = getTextColor(_state);
-        _textColor.w *= getOpacity(_state);
-    }
+void Label::update(const Control* container, const Vector2& offset)
+{
+    Control::update(container, offset);
 
-    void Label::drawText(const Rectangle& clip)
-    {
-        if (_text.size() <= 0)
-            return;
+    _textBounds.set(_viewportBounds);
+
+    _font = getFont(_state);
+    _textColor = getTextColor(_state);
+    _textColor.w *= getOpacity(_state);
+}
 
-        // Draw the text.
-        if (_font)
-        {
-            _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_clip);
-        }
+void Label::drawText(const Rectangle& clip)
+{
+    if (_text.size() <= 0)
+        return;
 
-        _dirty = false;
+    // Draw the text.
+    if (_font)
+    {
+        _font->begin();
+        _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_viewportClipBounds);
+        _font->end();
     }
+
+    _dirty = false;
+}
+
 }

+ 8 - 2
gameplay/src/Label.h

@@ -90,9 +90,10 @@ protected:
      * Called when a label's properties change. Updates this label's internal rendering
      * properties, such as its text viewport.
      *
-     * @param clip The clipping rectangle of this label's parent container.
+     * @param container This label's parent container.
+     * @param offset The scroll offset of this label's parent container.
      */
-    void update(const Rectangle& clip);
+    void update(const Control* container, const Vector2& offset);
 
     /**
      * Draw this label's text.
@@ -116,6 +117,11 @@ protected:
      */
     Vector4 _textColor;
 
+    /**
+     * The position and size of this control's text area, before clipping.  Used for text alignment.
+     */
+    Rectangle _textBounds;
+
 private:
 
     /**

+ 54 - 43
gameplay/src/Layout.cpp

@@ -5,50 +5,61 @@
 
 namespace gameplay
 {
-    void Layout::align(Control* control, const Container* container)
-    {
-        if (control->_alignment != Control::ALIGN_TOP_LEFT ||
-            control->_autoWidth || control->_autoHeight)
+
+void Layout::align(Control* control, const Container* container)
+{
+    GP_ASSERT(control);
+    GP_ASSERT(container);
+
+    if (control->_alignment != Control::ALIGN_TOP_LEFT ||
+        control->_autoWidth || control->_autoHeight)
+    {
+        Rectangle controlBounds = control->getBounds();
+        const Theme::Margin& controlMargin = control->getMargin();
+        const Rectangle& containerBounds = container->getBounds();
+        const Theme::Border& containerBorder = container->getBorder(container->getState());
+        const Theme::Padding& containerPadding = container->getPadding();
+
+        float clipWidth = containerBounds.width - containerBorder.left - containerBorder.right - containerPadding.left - containerPadding.right;
+        float clipHeight = containerBounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom;
+
+        if (control->_autoWidth)
+        {
+            controlBounds.width = clipWidth - controlMargin.left - controlMargin.right;
+        }
+
+        if (control->_autoHeight)
+        {
+            controlBounds.height = clipHeight - controlMargin.top - controlMargin.bottom;
+        }
+
+        // Vertical alignment
+        if ((control->_alignment & Control::ALIGN_BOTTOM) == Control::ALIGN_BOTTOM)
         {
-            Rectangle controlBounds = control->getBounds();
-            const Rectangle& containerBounds = container->getClipBounds();
-            const Theme::Border& containerBorder = container->getBorder(container->getState());
-            const Theme::Padding& containerPadding = container->getPadding();
-
-            float clipWidth = containerBounds.width - containerBorder.left - containerBorder.right - containerPadding.left - containerPadding.right;
-            float clipHeight = containerBounds.height - containerBorder.top - containerBorder.bottom - containerPadding.top - containerPadding.bottom;
-
-            if (control->_autoWidth)
-            {
-                controlBounds.width = clipWidth;
-            }
-
-            if (control->_autoHeight)
-            {
-                controlBounds.height = clipHeight;
-            }
-
-            // Vertical alignment
-            if ((control->_alignment & Control::ALIGN_BOTTOM) == Control::ALIGN_BOTTOM)
-            {
-                controlBounds.y = clipHeight - controlBounds.height;
-            }
-            else if ((control->_alignment & Control::ALIGN_VCENTER) == Control::ALIGN_VCENTER)
-            {
-                controlBounds.y = clipHeight * 0.5f - controlBounds.height * 0.5f;
-            }
-
-            // Horizontal alignment
-            if ((control->_alignment & Control::ALIGN_RIGHT) == Control::ALIGN_RIGHT)
-            {
-                controlBounds.x = clipWidth - controlBounds.width;
-            }
-            else if ((control->_alignment & Control::ALIGN_HCENTER) == Control::ALIGN_HCENTER)
-            {
-                controlBounds.x = clipWidth * 0.5f - controlBounds.width * 0.5f;
-            }
-
-            control->setBounds(controlBounds);
+            controlBounds.y = clipHeight - controlBounds.height;
         }
+        else if ((control->_alignment & Control::ALIGN_VCENTER) == Control::ALIGN_VCENTER)
+        {
+            controlBounds.y = clipHeight * 0.5f - controlBounds.height * 0.5f;
+        }
+
+        // Horizontal alignment
+        if ((control->_alignment & Control::ALIGN_RIGHT) == Control::ALIGN_RIGHT)
+        {
+            controlBounds.x = clipWidth - controlBounds.width - controlMargin.right;
+        }
+        else if ((control->_alignment & Control::ALIGN_HCENTER) == Control::ALIGN_HCENTER)
+        {
+            controlBounds.x = clipWidth * 0.5f - controlBounds.width * 0.5f;
+        }
+
+        control->setBounds(controlBounds);
     }
+}
+
+bool Layout::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    return false;
+}
+
 }

+ 28 - 2
gameplay/src/Layout.h

@@ -2,6 +2,8 @@
 #define LAYOUT_H_
 
 #include "Ref.h"
+#include "Touch.h"
+#include "Vector2.h"
 
 namespace gameplay
 {
@@ -18,6 +20,7 @@ class Control;
 class Layout : public Ref
 {
     friend class Container;
+    friend class Form;
 
 public:
     /**
@@ -42,7 +45,15 @@ public:
          * Absolute layout: Controls are not modified at all by this layout.
          * They must be positioned and sized manually.
          */
-        LAYOUT_ABSOLUTE
+        LAYOUT_ABSOLUTE,
+
+        /**
+         * Scroll layout: Controls may be placed outside the bounds of the container.
+         * The user can then touch and drag to scroll.  By default controls are placed
+         * based on absolute positions in the .form file, but vertical or horizontal
+         * automatic positioning is an available option.
+         */
+        LAYOUT_SCROLL
     };
 
     /**
@@ -53,12 +64,14 @@ public:
     virtual Type getType() = 0;
 
 protected:
+
     /**
      * Position, resize, and update the controls within a container.
      *
      * @param container The container to update.
+     * @param offset The update offset.
      */
-    virtual void update(const Container* container) = 0;
+    virtual void update(const Container* container, const Vector2& offset) = 0;
 
     /**
      * Align a control within a container.
@@ -67,6 +80,19 @@ protected:
      * @param container The container to align the control within.
      */
     virtual void align(Control* control, const Container* container);
+
+    /**
+     * Touch callback on touch events.  Coordinates are given relative to the container's
+     * content area.
+     *
+     * @param evt The touch event that occurred.
+     * @param x The x position of the touch in pixels. Left edge is zero.
+     * @param y The y position of the touch in pixels. Top edge is zero.
+     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
+     *
+     * @see Touch::TouchEvent
+     */
+    virtual bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 };
 
 }

+ 42 - 14
gameplay/src/Light.cpp

@@ -36,6 +36,9 @@ Light::~Light()
     case SPOT:
         SAFE_DELETE(_spot);
         break;
+    default:
+        GP_ERROR("Invalid light type (%d).", _type);
+        break;
     }
 }
 
@@ -75,13 +78,16 @@ const Vector3& Light::getColor() const
     switch (_type)
     {
     case DIRECTIONAL:
+        GP_ASSERT(_directional);
         return _directional->color;
     case POINT:
+        GP_ASSERT(_point);
         return _point->color;
     case SPOT:
+        GP_ASSERT(_spot);
         return _spot->color;
     default:
-        assert(0);
+        GP_ERROR("Unsupported light type (%d).", _type);
         return Vector3::zero();
 
     }
@@ -92,76 +98,95 @@ void Light::setColor(const Vector3& color)
     switch (_type)
     {
     case DIRECTIONAL:
+        GP_ASSERT(_directional);
         _directional->color = color;
         break;
     case POINT:
+        GP_ASSERT(_point);
         _point->color = color;
         break;
     case SPOT:
+        GP_ASSERT(_spot);
         _spot->color = color;
         break;
+    default:
+        GP_ERROR("Unsupported light type (%d).", _type);
+        break;
     }
 }
 
 float Light::getRange()  const
 {
-    assert(_type != DIRECTIONAL);
+    GP_ASSERT(_type != DIRECTIONAL);
 
     switch (_type)
     {
     case POINT:
+        GP_ASSERT(_point);
         return _point->range;
     case SPOT:
+        GP_ASSERT(_spot);
         return _spot->range;
     default:
-        assert(0);
+        GP_ERROR("Unsupported light type (%d).", _type);
         return 0.0f;
     }
 }
     
 void Light::setRange(float range)
 {
-    assert(_type != DIRECTIONAL);
+    GP_ASSERT(_type != DIRECTIONAL);
 
     switch (_type)
     {
     case POINT:
+        GP_ASSERT(_point);
+        GP_ASSERT(range);
+
         _point->range = range;
         _point->rangeInverse = 1.0f / range;
         break;
     case SPOT:
+        GP_ASSERT(_spot);
+        GP_ASSERT(range);
+
         _spot->range = range;
         _spot->rangeInverse = 1.0f / range;
         break;
+    default:
+        GP_ERROR("Unsupported light type (%d).", _type);
+        break;
     }
 }
 
 float Light::getRangeInverse() const
 {
-    assert(_type != DIRECTIONAL);
+    GP_ASSERT(_type != DIRECTIONAL);
 
     switch (_type)
     {
     case POINT:
+        GP_ASSERT(_point);
         return _point->rangeInverse;
     case SPOT:
+        GP_ASSERT(_spot);
         return _spot->rangeInverse;
     default:
-        assert(0);
+        GP_ERROR("Unsupported light type (%d).", _type);
         return 0.0f;
     }
 }
     
 float Light::getInnerAngle()  const
 {
-    assert(_type == SPOT);
+    GP_ASSERT(_type == SPOT);
 
     return _spot->innerAngle;
 }
 
 void Light::setInnerAngle(float innerAngle)
 {
-    assert(_type == SPOT);
+    GP_ASSERT(_type == SPOT);
 
     _spot->innerAngle = innerAngle;
     _spot->innerAngleCos = cos(innerAngle);
@@ -169,14 +194,14 @@ void Light::setInnerAngle(float innerAngle)
     
 float Light::getOuterAngle()  const
 {
-    assert(_type == SPOT);
+    GP_ASSERT(_type == SPOT);
 
     return _spot->outerAngle;
 }
 
 void Light::setOuterAngle(float outerAngle)
 {
-    assert(_type == SPOT);
+    GP_ASSERT(_type == SPOT);
 
     _spot->outerAngle = outerAngle;
     _spot->outerAngleCos = cos(outerAngle);
@@ -184,14 +209,14 @@ void Light::setOuterAngle(float outerAngle)
     
 float Light::getInnerAngleCos()  const
 {
-    assert(_type == SPOT);
+    GP_ASSERT(_type == SPOT);
 
     return _spot->innerAngleCos;
 }
     
 float Light::getOuterAngleCos()  const
 {
-    assert(_type == SPOT);
+    GP_ASSERT(_type == SPOT);
 
     return _spot->outerAngleCos;
 }
@@ -211,9 +236,10 @@ Light* Light::clone(NodeCloneContext &context) const
         lightClone = createSpot(getColor(), getRange(), getInnerAngle(), getOuterAngle());
         break;
     default:
-        assert(false);
+        GP_ERROR("Unsupported light type (%d).", _type);
+        return NULL;
     }
-    assert(lightClone);
+    GP_ASSERT(lightClone);
 
     if (Node* node = context.findClonedNode(getNode()))
     {
@@ -230,12 +256,14 @@ Light::Directional::Directional(const Vector3& color)
 Light::Point::Point(const Vector3& color, float range)
     : color(color), range(range)
 {
+    GP_ASSERT(range);
     rangeInverse = 1.0f / range;
 }
 
 Light::Spot::Spot(const Vector3& color, float range, float innerAngle, float outerAngle)
     : color(color), range(range), innerAngle(innerAngle), outerAngle(outerAngle)
 {
+    GP_ASSERT(range);
     rangeInverse = 1.0f / range;
     innerAngleCos = cos(innerAngle);
     outerAngleCos = cos(outerAngle);

+ 69 - 29
gameplay/src/Material.cpp

@@ -34,13 +34,11 @@ Material::~Material()
 
 Material* Material::create(const char* url)
 {
-    assert(url);
-
-    // Load the material properties from file
+    // Load the material properties from file.
     Properties* properties = Properties::create(url);
-    assert(properties);
     if (properties == NULL)
     {
+        GP_ERROR("Failed to create material from file.");
         return NULL;
     }
 
@@ -53,9 +51,9 @@ Material* Material::create(const char* url)
 Material* Material::create(Properties* materialProperties)
 {
     // Check if the Properties is valid and has a valid namespace.
-    assert(materialProperties);
     if (!materialProperties || !(strcmp(materialProperties->getNamespace(), "material") == 0))
     {
+        GP_ERROR("Properties object must be non-null and have namespace equal to 'material'.");
         return NULL;
     }
 
@@ -70,16 +68,17 @@ Material* Material::create(Properties* materialProperties)
         {
             if (!loadTechnique(material, techniqueProperties))
             {
+                GP_ERROR("Failed to load technique for material.");
                 SAFE_RELEASE(material);
                 return NULL;
             }
         }
     }
 
-    // Load uniform value parameters for this material
+    // Load uniform value parameters for this material.
     loadRenderState(material, materialProperties);
 
-    // Set the current technique to the first found technique
+    // Set the current technique to the first found technique.
     if (material->getTechniqueCount() > 0)
     {
         material->setTechnique((unsigned int)0);
@@ -90,7 +89,9 @@ Material* Material::create(Properties* materialProperties)
 
 Material* Material::create(Effect* effect)
 {
-    // Create a new material with a single technique and pass for the given effect
+    GP_ASSERT(effect);
+
+    // Create a new material with a single technique and pass for the given effect.
     Material* material = new Material();
 
     Technique* technique = new Technique(NULL, material);
@@ -116,6 +117,7 @@ Material* Material::create(const char* vshPath, const char* fshPath, const char*
     Pass* pass = Pass::create(NULL, technique, vshPath, fshPath, defines);
     if (!pass)
     {
+        GP_ERROR("Failed to create pass for material.");
         SAFE_RELEASE(material);
         return NULL;
     }
@@ -134,6 +136,7 @@ Material* Material::clone(NodeCloneContext &context) const
     for (std::vector<Technique*>::const_iterator it = _techniques.begin(); it != _techniques.end(); ++it)
     {
         const Technique* technique = *it;
+        GP_ASSERT(technique);
         Technique* techniqueClone = technique->clone(material, context);
         material->_techniques.push_back(techniqueClone);
         if (_currentTechnique == technique)
@@ -151,16 +154,17 @@ unsigned int Material::getTechniqueCount() const
 
 Technique* Material::getTechnique(unsigned int index) const
 {
-    assert(index < _techniques.size());
-
+    GP_ASSERT(index < _techniques.size());
     return _techniques[index];
 }
 
 Technique* Material::getTechnique(const char* id) const
 {
+    GP_ASSERT(id);
     for (unsigned int i = 0, count = _techniques.size(); i < count; ++i)
     {
         Technique* t = _techniques[i];
+        GP_ASSERT(t);
         if (strcmp(t->getId(), id) == 0)
         {
             return t;
@@ -195,7 +199,10 @@ void Material::setTechnique(unsigned int index)
 
 bool Material::loadTechnique(Material* material, Properties* techniqueProperties)
 {
-    // Create a new technique
+    GP_ASSERT(material);
+    GP_ASSERT(techniqueProperties);
+
+    // Create a new technique.
     Technique* technique = new Technique(techniqueProperties->getId(), material);
 
     // Go through all the properties and create passes under this technique.
@@ -208,16 +215,17 @@ bool Material::loadTechnique(Material* material, Properties* techniqueProperties
             // Create and load passes.
             if (!loadPass(technique, passProperties))
             {
+                GP_ERROR("Failed to create pass for technique.");
                 SAFE_RELEASE(technique);
                 return false;
             }
         }
     }
 
-    // Load uniform value parameters for this technique
+    // Load uniform value parameters for this technique.
     loadRenderState(technique, techniqueProperties);
 
-    // Add the new technique to the material
+    // Add the new technique to the material.
     material->_techniques.push_back(technique);
 
     return true;
@@ -225,11 +233,14 @@ bool Material::loadTechnique(Material* material, Properties* techniqueProperties
 
 bool Material::loadPass(Technique* technique, Properties* passProperties)
 {
+    GP_ASSERT(passProperties);
+    GP_ASSERT(technique);
+
     // Fetch shader info required to create the effect of this technique.
     const char* vertexShaderPath = passProperties->getString("vertexShader");
-    assert(vertexShaderPath);
+    GP_ASSERT(vertexShaderPath);
     const char* fragmentShaderPath = passProperties->getString("fragmentShader");
-    assert(fragmentShaderPath);
+    GP_ASSERT(fragmentShaderPath);
     const char* defines = passProperties->getString("defines");
     std::string define;
     if (defines != NULL)
@@ -244,17 +255,18 @@ bool Material::loadPass(Technique* technique, Properties* passProperties)
         define += "\n";
     }
 
-    // Create the pass
+    // Create the pass.
     Pass* pass = Pass::create(passProperties->getId(), technique, vertexShaderPath, fragmentShaderPath, define.c_str());
     if (!pass)
     {
+        GP_ERROR("Failed to create pass for technique.");
         return false;
     }
 
-    // Load render state
+    // Load render state.
     loadRenderState(pass, passProperties);
 
-    // Add the new pass to the technique
+    // Add the new pass to the technique.
     technique->_passes.push_back(pass);
 
     return true;
@@ -262,6 +274,8 @@ bool Material::loadPass(Technique* technique, Properties* passProperties)
 
 bool isMaterialKeyword(const char* str)
 {
+    GP_ASSERT(str);
+
     #define MATERIAL_KEYWORD_COUNT 3
     static const char* reservedKeywords[MATERIAL_KEYWORD_COUNT] =
     {
@@ -283,6 +297,7 @@ Texture::Filter parseTextureFilterMode(const char* str, Texture::Filter defaultV
 {
     if (str == NULL || strlen(str) == 0)
     {
+        GP_ERROR("Texture filter mode string must be non-null and non-empty.");
         return defaultValue;
     }
     else if (strcmp(str, "NEAREST") == 0)
@@ -309,13 +324,18 @@ Texture::Filter parseTextureFilterMode(const char* str, Texture::Filter defaultV
     {
         return Texture::LINEAR_MIPMAP_LINEAR;
     }
-    return defaultValue;
+    else
+    {
+        GP_ERROR("Unsupported texture filter mode string ('%s').", str);
+        return defaultValue;
+    }
 }
 
 Texture::Wrap parseTextureWrapMode(const char* str, Texture::Wrap defaultValue)
 {
     if (str == NULL || strlen(str) == 0)
     {
+        GP_ERROR("Texture wrap mode string must be non-null and non-empty.");
         return defaultValue;
     }
     else if (strcmp(str, "REPEAT") == 0)
@@ -326,12 +346,19 @@ Texture::Wrap parseTextureWrapMode(const char* str, Texture::Wrap defaultValue)
     {
         return Texture::CLAMP;
     }
-    return defaultValue;
+    else
+    {
+        GP_ERROR("Unsupported texture wrap mode string ('%s').", str);
+        return defaultValue;
+    }
 }
 
 void Material::loadRenderState(RenderState* renderState, Properties* properties)
 {
-    // Rewind the properties to start reading from the start
+    GP_ASSERT(renderState);
+    GP_ASSERT(properties);
+
+    // Rewind the properties to start reading from the start.
     properties->rewind();
 
     const char* name;
@@ -343,6 +370,7 @@ void Material::loadRenderState(RenderState* renderState, Properties* properties)
         switch (properties->getType())
         {
         case Properties::NUMBER:
+            GP_ASSERT(renderState->getParameter(name));
             renderState->getParameter(name)->setValue(properties->getFloat());
             break;
         case Properties::VECTOR2:
@@ -350,6 +378,7 @@ void Material::loadRenderState(RenderState* renderState, Properties* properties)
                 Vector2 vector2;
                 if (properties->getVector2(NULL, &vector2))
                 {
+                    GP_ASSERT(renderState->getParameter(name));
                     renderState->getParameter(name)->setValue(vector2);
                 }
             }
@@ -359,6 +388,7 @@ void Material::loadRenderState(RenderState* renderState, Properties* properties)
                 Vector3 vector3;
                 if (properties->getVector3(NULL, &vector3))
                 {
+                    GP_ASSERT(renderState->getParameter(name));
                     renderState->getParameter(name)->setValue(vector3);
                 }
             }
@@ -368,6 +398,7 @@ void Material::loadRenderState(RenderState* renderState, Properties* properties)
                 Vector4 vector4;
                 if (properties->getVector4(NULL, &vector4))
                 {
+                    GP_ASSERT(renderState->getParameter(name));
                     renderState->getParameter(name)->setValue(vector4);
                 }
             }
@@ -377,43 +408,51 @@ void Material::loadRenderState(RenderState* renderState, Properties* properties)
                 Matrix matrix;
                 if (properties->getMatrix(NULL, &matrix))
                 {
+                    GP_ASSERT(renderState->getParameter(name));
                     renderState->getParameter(name)->setValue(matrix);
                 }
             }
             break;
         default:
             {
-                // Assume this is a parameter auto-binding
+                // Assume this is a parameter auto-binding.
                 renderState->setParameterAutoBinding(name, properties->getString());
             }
             break;
         }
     }
 
-    // Iterate through all child namespaces searching for samplers and render state blocks
+    // Iterate through all child namespaces searching for samplers and render state blocks.
     Properties* ns;
     while (ns = properties->getNextNamespace())
     {
         if (strcmp(ns->getNamespace(), "sampler") == 0)
         {
-            // Read the texture uniform name
+            // Read the texture uniform name.
             name = ns->getId();
             if (strlen(name) == 0)
-                continue; // missing texture uniform name
+            {
+                GP_ERROR("Texture sampler is missing required uniform name.");
+                continue;
+            }
 
-            // Get the texture path
+            // Get the texture path.
             const char* path = ns->getString("path");
             if (path == NULL || strlen(path) == 0)
-                continue; // missing texture path
+            {
+                GP_ERROR("Texture sampler '%s' is missing required image file path.", name);
+                continue;
+            }
 
-            // Read texture state (booleans default to 'false' if not present)
+            // Read texture state (booleans default to 'false' if not present).
             bool mipmap = ns->getBool("mipmap");
             Texture::Wrap wrapS = parseTextureWrapMode(ns->getString("wrapS"), Texture::REPEAT);
             Texture::Wrap wrapT = parseTextureWrapMode(ns->getString("wrapT"), Texture::REPEAT);
             Texture::Filter minFilter = parseTextureFilterMode(ns->getString("minFilter"), mipmap ? Texture::NEAREST_MIPMAP_LINEAR : Texture::LINEAR);
             Texture::Filter magFilter = parseTextureFilterMode(ns->getString("magFilter"), Texture::LINEAR);
 
-            // Set the sampler parameter
+            // Set the sampler parameter.
+            GP_ASSERT(renderState->getParameter(name));
             Texture::Sampler* sampler = renderState->getParameter(name)->setValue(path, mipmap);
             if (sampler)
             {
@@ -425,6 +464,7 @@ void Material::loadRenderState(RenderState* renderState, Properties* properties)
         {
             while (name = ns->getNextProperty())
             {
+                GP_ASSERT(renderState->getStateBlock());
                 renderState->getStateBlock()->setState(name, ns->getString());
             }
         }

+ 55 - 14
gameplay/src/MaterialParameter.cpp

@@ -5,7 +5,7 @@ namespace gameplay
 {
 
 MaterialParameter::MaterialParameter(const char* name) :
-    _type(MaterialParameter::NONE), _count(1), _dynamic(false), _name(name), _uniform(NULL)
+    _type(MaterialParameter::NONE), _count(1), _dynamic(false), _name(name ? name : ""), _uniform(NULL)
 {
     clearValue();
 }
@@ -34,6 +34,9 @@ void MaterialParameter::clearValue()
         case MaterialParameter::METHOD:
             SAFE_RELEASE(_value.method);
             break;
+        default:
+            // Ignore all other cases.
+            break;
         }
 
         _dynamic = false;
@@ -49,6 +52,9 @@ void MaterialParameter::clearValue()
                 const_cast<Texture::Sampler*>(_value.samplerValue)->release();
             }
             break;
+        default:
+            // Ignore all other cases.
+            break;
         }
     }
 
@@ -111,6 +117,7 @@ void MaterialParameter::setValue(const Vector2& value)
 
 void MaterialParameter::setValue(const Vector2* values, unsigned int count)
 {
+    GP_ASSERT(values);
     clearValue();
 
     _value.floatPtrValue = const_cast<float*> (&values[0].x);
@@ -134,6 +141,7 @@ void MaterialParameter::setValue(const Vector3& value)
 
 void MaterialParameter::setValue(const Vector3* values, unsigned int count)
 {
+    GP_ASSERT(values);
     clearValue();
 
     _value.floatPtrValue = const_cast<float*> (&values[0].x);
@@ -157,6 +165,7 @@ void MaterialParameter::setValue(const Vector4& value)
 
 void MaterialParameter::setValue(const Vector4* values, unsigned int count)
 {
+    GP_ASSERT(values);
     clearValue();
 
     _value.floatPtrValue = const_cast<float*> (&values[0].x);
@@ -184,6 +193,7 @@ void MaterialParameter::setValue(const Matrix& value)
 
 void MaterialParameter::setValue(const Matrix* values, unsigned int count)
 {
+    GP_ASSERT(values);
     clearValue();
 
     _value.floatPtrValue = const_cast<Matrix&> (values[0]).m;
@@ -223,6 +233,8 @@ Texture::Sampler* MaterialParameter::setValue(const char* texturePath, bool gene
 
 void MaterialParameter::bind(Effect* effect)
 {
+    GP_ASSERT(effect);
+
     // If we had a Uniform cached that is not from the passed in effect,
     // we need to update our uniform to point to the new effect's uniform.
     if (!_uniform || _uniform->getEffect() != effect)
@@ -232,7 +244,7 @@ void MaterialParameter::bind(Effect* effect)
         if (!_uniform)
         {
             // This parameter was not found in the specified effect, so do nothing.
-            WARN_VARG("Warning: Material parameter '%s' not found in effect '%s'.", _name.c_str(), effect->getId());
+            GP_WARN("Warning: Material parameter '%s' not found in effect '%s'.", _name.c_str(), effect->getId());
             return;
         }
     }
@@ -246,7 +258,6 @@ void MaterialParameter::bind(Effect* effect)
         }
         else
         {
-            assert(_value.floatPtrValue);
             effect->setValue(_uniform, _value.floatPtrValue, _count);
         }
         break;
@@ -257,34 +268,31 @@ void MaterialParameter::bind(Effect* effect)
         }
         else
         {
-            assert(_value.intPtrValue);
             effect->setValue(_uniform, _value.intPtrValue, _count);
         }
         break;
     case MaterialParameter::VECTOR2:
-        assert(_value.floatPtrValue);
         effect->setValue(_uniform, reinterpret_cast<Vector2*>(_value.floatPtrValue), _count);
         break;
     case MaterialParameter::VECTOR3:
-        assert(_value.floatPtrValue);
         effect->setValue(_uniform, reinterpret_cast<Vector3*>(_value.floatPtrValue), _count);
         break;
     case MaterialParameter::VECTOR4:
-        assert(_value.floatPtrValue);
         effect->setValue(_uniform, reinterpret_cast<Vector4*>(_value.floatPtrValue), _count);
         break;
     case MaterialParameter::MATRIX:
-        assert(_value.floatPtrValue);
         effect->setValue(_uniform, reinterpret_cast<Matrix*>(_value.floatPtrValue), _count);
         break;
     case MaterialParameter::SAMPLER:
-        assert(_value.samplerValue);
         effect->setValue(_uniform, _value.samplerValue);
         break;
     case MaterialParameter::METHOD:
-        assert(_value.method);
+        GP_ASSERT(_value.method);
         _value.method->setValue(effect);
         break;
+    default:
+        GP_ERROR("Unsupported material parameter type (%d).", _type);
+        break;
     }
 }
 
@@ -312,6 +320,7 @@ unsigned int MaterialParameter::getAnimationPropertyComponentCount(int propertyI
                 case VECTOR4:
                     return 4 * _count;
                 default:
+                    GP_ERROR("Unsupported material parameter type (%d).", _type);
                     return 0;
             }
         }
@@ -323,6 +332,7 @@ unsigned int MaterialParameter::getAnimationPropertyComponentCount(int propertyI
 
 void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue* value)
 {
+    GP_ASSERT(value);
     switch (propertyId)
     {
         case ANIMATE_UNIFORM:
@@ -336,6 +346,7 @@ void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue
                     }
                     else
                     {
+                        GP_ASSERT(_value.floatPtrValue);
                         for (unsigned int i = 0; i < _count; i++)
                         {
                             value->setFloat(i, _value.floatPtrValue[i]);
@@ -349,6 +360,7 @@ void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue
                     }
                     else
                     {
+                        GP_ASSERT(_value.intPtrValue);
                         for (unsigned int i = 0; i < _count; i++)
                         {
                             value->setFloat(i, _value.intPtrValue[i]);
@@ -373,8 +385,15 @@ void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue
                         value->setFloat(_value.floatPtrValue, i * 4, 4);
                     }
                     break;
-
-                // UNSUPPORTED: NONE, MATRIX, METHOD, SAMPLER 
+                case NONE:
+                case MATRIX:
+                case METHOD:
+                case SAMPLER:
+                    // Unsupported material parameter types for animation.
+                    break;
+                default:
+                    GP_ERROR("Unsupported material parameter type (%d).", _type);
+                    break;
             }
         }
         break;
@@ -383,7 +402,8 @@ void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue
 
 void MaterialParameter::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
 {
-    assert(blendWeight >= 0.0f && blendWeight <= 1.0f);
+    GP_ASSERT(value);
+    GP_ASSERT(blendWeight >= 0.0f && blendWeight <= 1.0f);
 
     switch (propertyId)
     {
@@ -407,6 +427,7 @@ void MaterialParameter::setAnimationPropertyValue(int propertyId, AnimationValue
                     }
                     else
                     {
+                        GP_ASSERT(_value.intPtrValue);
                         for (unsigned int i = 0; i < _count; i++)
                             _value.intPtrValue[i] = Curve::lerp(blendWeight, _value.intPtrValue[i], value->getFloat(i));
                     }
@@ -427,7 +448,15 @@ void MaterialParameter::setAnimationPropertyValue(int propertyId, AnimationValue
                     applyAnimationValue(value, blendWeight, 4);
                     break;
                 }
-                // UNSUPPORTED: NONE, MATRIX, METHOD, SAMPLER 
+                case NONE:
+                case MATRIX:
+                case METHOD:
+                case SAMPLER:
+                    // Unsupported material parameter types for animation.
+                    break;
+                default:
+                    GP_ERROR("Unsupported material parameter type (%d).", _type);
+                    break;
             }
         }
         break;
@@ -436,6 +465,9 @@ void MaterialParameter::setAnimationPropertyValue(int propertyId, AnimationValue
 
 void MaterialParameter::applyAnimationValue(AnimationValue* value, float blendWeight, int components)
 {
+    GP_ASSERT(value);
+    GP_ASSERT(_value.floatPtrValue);
+
     unsigned int count = _count * components;
     for (unsigned int i = 0; i < count; i++)
         _value.floatPtrValue[i] = Curve::lerp(blendWeight, _value.floatPtrValue[i], value->getFloat(i));
@@ -443,6 +475,7 @@ void MaterialParameter::applyAnimationValue(AnimationValue* value, float blendWe
 
 void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
 {
+    GP_ASSERT(materialParameter);
     materialParameter->_type = _type;
     materialParameter->_count = _count;
     materialParameter->_dynamic = _dynamic;
@@ -462,6 +495,7 @@ void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
         Vector2* value = reinterpret_cast<Vector2*>(_value.floatPtrValue);
         if (_count == 1)
         {
+            GP_ASSERT(value);
             materialParameter->setValue(*value);
         }
         else
@@ -475,6 +509,7 @@ void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
         Vector3* value = reinterpret_cast<Vector3*>(_value.floatPtrValue);
         if (_count == 1)
         {
+            GP_ASSERT(value);
             materialParameter->setValue(*value);
         }
         else
@@ -488,6 +523,7 @@ void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
         Vector4* value = reinterpret_cast<Vector4*>(_value.floatPtrValue);
         if (_count == 1)
         {
+            GP_ASSERT(value);
             materialParameter->setValue(*value);
         }
         else
@@ -501,6 +537,7 @@ void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
         Matrix* value = reinterpret_cast<Matrix*>(_value.floatPtrValue);
         if (_count == 1)
         {
+            GP_ASSERT(value);
             materialParameter->setValue(*value);
         }
         else
@@ -514,8 +551,12 @@ void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
         break;
     case METHOD:
         materialParameter->_value.method = _value.method;
+        GP_ASSERT(materialParameter->_value.method);
         materialParameter->_value.method->addRef();
         break;
+    default:
+        GP_ERROR("Unsupported material parameter type(%d).", _type);
+        break;
     }
 }
 

+ 49 - 0
gameplay/src/MathUtil.h

@@ -0,0 +1,49 @@
+#ifndef MATHUTIL_H_
+#define MATHUTIL_H_
+
+namespace gameplay
+{
+/**
+ * Math utility class. Used for internal math optimizations.
+ */
+class MathUtil
+{
+	friend class Matrix;
+	friend class Vector3;
+
+private:
+
+	inline static void addMatrix(const float* m, float scalar, float* dst);
+
+	inline static void addMatrix(const float* m1, const float* m2, float* dst);
+
+	inline static void subtractMatrix(const float* m1, const float* m2, float* dst);
+
+	inline static void multiplyMatrix(const float* m, float scalar, float* dst);
+
+	inline static void multiplyMatrix(const float* m1, const float* m2, float* dst);
+
+	inline static void negateMatrix(const float* m, float* dst);
+
+	inline static void transposeMatrix(const float* m, float* dst);
+
+	inline static void transformVector4(const float* m, float x, float y, float z, float w, float* dst);
+
+	inline static void transformVector4(const float* m, const float* v, float* dst);
+
+	inline static void crossVector3(const float* v1, const float* v2, float* dst);
+
+	MathUtil();
+};
+
+}
+
+#define MATRIX_SIZE ( sizeof(float) * 16)
+
+#ifdef USE_NEON
+#include "MathUtilNeon.inl"
+#else
+#include "MathUtil.inl"
+#endif
+
+#endif

+ 177 - 0
gameplay/src/MathUtil.inl

@@ -0,0 +1,177 @@
+namespace gameplay
+{
+
+inline void MathUtil::addMatrix(const float* m, float scalar, float* dst)
+{
+	dst[0]  = m[0]  + scalar;
+	dst[1]  = m[1]  + scalar;
+	dst[2]  = m[2]  + scalar;
+	dst[3]  = m[3]  + scalar;
+	dst[4]  = m[4]  + scalar;
+	dst[5]  = m[5]  + scalar;
+	dst[6]  = m[6]  + scalar;
+	dst[7]  = m[7]  + scalar;
+	dst[8]  = m[8]  + scalar;
+	dst[9]  = m[9]  + scalar;
+	dst[10] = m[10] + scalar;
+	dst[11] = m[11] + scalar;
+	dst[12] = m[12] + scalar;
+	dst[13] = m[13] + scalar;
+	dst[14] = m[14] + scalar;
+	dst[15] = m[15] + scalar;
+}
+
+inline void MathUtil::addMatrix(const float* m1, const float* m2, float* dst)
+{
+	dst[0]  = m1[0]  + m2[0];
+	dst[1]  = m1[1]  + m2[1];
+	dst[2]  = m1[2]  + m2[2];
+	dst[3]  = m1[3]  + m2[3];
+	dst[4]  = m1[4]  + m2[4];
+	dst[5]  = m1[5]  + m2[5];
+	dst[6]  = m1[6]  + m2[6];
+	dst[7]  = m1[7]  + m2[7];
+	dst[8]  = m1[8]  + m2[8];
+	dst[9]  = m1[9]  + m2[9];
+	dst[10] = m1[10] + m2[10];
+	dst[11] = m1[11] + m2[11];
+	dst[12] = m1[12] + m2[12];
+	dst[13] = m1[13] + m2[13];
+	dst[14] = m1[14] + m2[14];
+	dst[15] = m1[15] + m2[15];
+}
+
+inline void MathUtil::subtractMatrix(const float* m1, const float* m2, float* dst)
+{
+	dst[0]  = m1[0]  - m2[0];
+	dst[1]  = m1[1]  - m2[1];
+	dst[2]  = m1[2]  - m2[2];
+	dst[3]  = m1[3]  - m2[3];
+	dst[4]  = m1[4]  - m2[4];
+	dst[5]  = m1[5]  - m2[5];
+	dst[6]  = m1[6]  - m2[6];
+	dst[7]  = m1[7]  - m2[7];
+	dst[8]  = m1[8]  - m2[8];
+	dst[9]  = m1[9]  - m2[9];
+	dst[10] = m1[10] - m2[10];
+	dst[11] = m1[11] - m2[11];
+	dst[12] = m1[12] - m2[12];
+	dst[13] = m1[13] - m2[13];
+	dst[14] = m1[14] - m2[14];
+	dst[15] = m1[15] - m2[15];
+}
+
+inline void MathUtil::multiplyMatrix(const float* m, float scalar, float* dst)
+{
+	dst[0]  = m[0]  * scalar;
+	dst[1]  = m[1]  * scalar;
+	dst[2]  = m[2]  * scalar;
+	dst[3]  = m[3]  * scalar;
+	dst[4]  = m[4]  * scalar;
+	dst[5]  = m[5]  * scalar;
+	dst[6]  = m[6]  * scalar;
+	dst[7]  = m[7]  * scalar;
+	dst[8]  = m[8]  * scalar;
+	dst[9]  = m[9]  * scalar;
+	dst[10] = m[10] * scalar;
+	dst[11] = m[11] * scalar;
+	dst[12] = m[12] * scalar;
+	dst[13] = m[13] * scalar;
+	dst[14] = m[14] * scalar;
+	dst[15] = m[15] * scalar;
+}
+
+inline void MathUtil::multiplyMatrix(const float* m1, const float* m2, float* dst)
+{
+	// Support the case where m1 or m2 is the same array as dst.
+	float product[16];
+
+	product[0]  = m1[0] * m2[0]  + m1[4] * m2[1] + m1[8]   * m2[2]  + m1[12] * m2[3];
+	product[1]  = m1[1] * m2[0]  + m1[5] * m2[1] + m1[9]   * m2[2]  + m1[13] * m2[3];
+	product[2]  = m1[2] * m2[0]  + m1[6] * m2[1] + m1[10]  * m2[2]  + m1[14] * m2[3];
+	product[3]  = m1[3] * m2[0]  + m1[7] * m2[1] + m1[11]  * m2[2]  + m1[15] * m2[3];
+
+	product[4]  = m1[0] * m2[4]  + m1[4] * m2[5] + m1[8]   * m2[6]  + m1[12] * m2[7];
+	product[5]  = m1[1] * m2[4]  + m1[5] * m2[5] + m1[9]   * m2[6]  + m1[13] * m2[7];
+	product[6]  = m1[2] * m2[4]  + m1[6] * m2[5] + m1[10]  * m2[6]  + m1[14] * m2[7];
+	product[7]  = m1[3] * m2[4]  + m1[7] * m2[5] + m1[11]  * m2[6]  + m1[15] * m2[7];
+
+	product[8]  = m1[0] * m2[8]  + m1[4] * m2[9] + m1[8]   * m2[10] + m1[12] * m2[11];
+	product[9]  = m1[1] * m2[8]  + m1[5] * m2[9] + m1[9]   * m2[10] + m1[13] * m2[11];
+	product[10] = m1[2] * m2[8]  + m1[6] * m2[9] + m1[10]  * m2[10] + m1[14] * m2[11];
+	product[11] = m1[3] * m2[8]  + m1[7] * m2[9] + m1[11]  * m2[10] + m1[15] * m2[11];
+
+	product[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8]  * m2[14] + m1[12] * m2[15];
+	product[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9]  * m2[14] + m1[13] * m2[15];
+	product[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
+	product[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
+
+	memcpy(dst, product, MATRIX_SIZE);
+}
+
+inline void MathUtil::negateMatrix(const float* m, float* dst)
+{
+	dst[0]  = -m[0];
+	dst[1]  = -m[1];
+	dst[2]  = -m[2];
+	dst[3]  = -m[3];
+	dst[4]  = -m[4];
+	dst[5]  = -m[5];
+	dst[6]  = -m[6];
+	dst[7]  = -m[7];
+	dst[8]  = -m[8];
+	dst[9]  = -m[9];
+	dst[10] = -m[10];
+	dst[11] = -m[11];
+	dst[12] = -m[12];
+	dst[13] = -m[13];
+	dst[14] = -m[14];
+	dst[15] = -m[15];
+}
+
+inline void MathUtil::transposeMatrix(const float* m, float* dst)
+{
+	float t[16] = {
+		m[0], m[4], m[8], m[12],
+		m[1], m[5], m[9], m[13],
+		m[2], m[6], m[10], m[14],
+		m[3], m[7], m[11], m[15]
+	};
+	memcpy(dst, t, MATRIX_SIZE);
+}
+
+inline void MathUtil::transformVector4(const float* m, float x, float y, float z, float w, float* dst)
+{
+	dst[0] = x * m[0] + y * m[4] + z * m[8] + w * m[12];
+	dst[1] = x * m[1] + y * m[5] + z * m[9] + w * m[13];
+	dst[2] = x * m[2] + y * m[6] + z * m[10] + w * m[14];
+}
+
+inline void MathUtil::transformVector4(const float* m, const float* v, float* dst)
+{
+    // Handle case where v == dst.
+    float x = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12];
+	float y = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13];
+    float z = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14];
+    float w = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15];
+
+    dst[0] = x;
+    dst[1] = y;
+	dst[2] = z;
+	dst[3] = w;
+}
+
+inline void MathUtil::crossVector3(const float* v1, const float* v2, float* dst)
+{
+	float x = (v1[1] * v2[2]) - (v1[2] * v2[1]);
+	float y = (v1[2] * v2[0]) - (v1[0] * v2[2]);
+	float z = (v1[0] * v2[1]) - (v1[1] * v2[0]);
+
+	dst[0] = x;
+	dst[1] = y;
+	dst[2] = z;
+}
+
+}
+
+

+ 229 - 0
gameplay/src/MathUtilNeon.inl

@@ -0,0 +1,229 @@
+namespace gameplay
+{
+
+inline void MathUtil::addMatrix(const float* m, float scalar, float* dst)
+{
+	asm volatile(
+		"vld1.32 {q0, q1}, [%1]! 	\n\t" // M[m0-m7]
+		"vld1.32 {q2, q3}, [%1] 	\n\t" // M[m8-m15]
+		"vld1.32 {d8[0]},  [%2] 	\n\t" // s
+		"vmov.f32 s17, s16          \n\t" // s
+		"vmov.f32 s18, s16          \n\t" // s
+		"vmov.f32 s19, s16          \n\t" // s
+
+		"vadd.f32 q8, q0, q4  		\n\t" // DST->M[m0-m3] = M[m0-m3] + s
+		"vadd.f32 q9, q1, q4 		\n\t" // DST->M[m4-m7] = M[m4-m7] + s
+		"vadd.f32 q10, q2, q4 		\n\t" // DST->M[m8-m11] = M[m8-m11] + s
+		"vadd.f32 q11, q3, q4 		\n\t" // DST->M[m12-m15] = M[m12-m15] + s
+
+		"vst1.32 {q8, q9}, [%0]!  	\n\t" // DST->M[m0-m7]
+		"vst1.32 {q10, q11}, [%0]   \n\t" // DST->M[m8-m15]
+		:
+		: "r"(dst), "r"(m), "r"(&scalar)
+		: "q0", "q1", "q2", "q3", "q4", "q8", "q9", "q10", "q11", "memory"
+	);
+}
+
+inline void MathUtil::addMatrix(const float* m1, const float* m2, float* dst)
+{
+	asm volatile(
+		"vld1.32 	{q0, q1}, 	[%1]! 	\n\t" // M1[m0-m7]
+		"vld1.32 	{q2, q3}, 	[%1] 	\n\t" // M1[m8-m15]
+		"vld1.32 	{q8, q9}, 	[%2]! 	\n\t" // M2[m0-m7]
+		"vld1.32 	{q10, q11}, [%2]  	\n\t" // M2[m8-m15]
+
+		"vadd.f32   q12, q0, q8 		\n\t" // DST->M[m0-m3] = M1[m0-m3] + M2[m0-m3]
+		"vadd.f32   q13, q1, q9			\n\t" // DST->M[m4-m7] = M1[m4-m7] + M2[m4-m7]
+		"vadd.f32   q14, q2, q10		\n\t" // DST->M[m8-m11] = M1[m8-m11] + M2[m8-m11]
+		"vadd.f32   q15, q3, q11		\n\t" // DST->M[m12-m15] = M1[m12-m15] + M2[m12-m15]
+
+		"vst1.32    {q12, q13}, [%0]!   \n\t" // DST->M[m0-m7]
+		"vst1.32    {q14, q15}, [%0]    \n\t" // DST->M[m8-m15]
+		:
+		: "r"(dst), "r"(m1), "r"(m2)
+		: "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15", "memory"
+	);
+}
+
+inline void MathUtil::subtractMatrix(const float* m1, const float* m2, float* dst)
+{
+	asm volatile(
+		"vld1.32 	{q0, q1}, 	[%1]! 	\n\t" // M1[m0-m7]
+		"vld1.32 	{q2, q3}, 	[%1] 	\n\t" // M1[m8-m15]
+		"vld1.32 	{q8, q9}, 	[%2]! 	\n\t" // M2[m0-m7]
+		"vld1.32 	{q10, q11}, [%2] 	\n\t" // M2[m8-m15]
+
+		"vsub.f32   q12, q0, q8 		\n\t" // DST->M[m0-m3] = M1[m0-m3] - M2[m0-m3]
+		"vsub.f32   q13, q1, q9			\n\t" // DST->M[m4-m7] = M1[m4-m7] - M2[m4-m7]
+		"vsub.f32   q14, q2, q10		\n\t" // DST->M[m8-m11] = M1[m8-m11] - M2[m8-m11]
+		"vsub.f32   q15, q3, q11		\n\t" // DST->M[m12-m15] = M1[m12-m15] - M2[m12-m15]
+
+		"vst1.32    {q12, q13}, [%0]!   \n\t" // DST->M[m0-m7]
+		"vst1.32    {q14, q15}, [%0]    \n\t" // DST->M[m8-m15]
+		:
+		: "r"(dst), "r"(m1), "r"(m2)
+		: "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15", "memory"
+	);
+}
+
+inline void MathUtil::multiplyMatrix(const float* m, float scalar, float* dst)
+{
+	asm volatile(
+		"vld1.32 	{d0[0]},	 	[%2]     	\n\t" // M[m0-m7]
+		"vld1.32	{q4-q5},  		[%1]!    	\n\t" // M[m8-m15]
+		"vld1.32	{q6-q7},  		[%1]		\n\t" // s
+
+		"vmul.f32 	q8, q4, d0[0]    			\n\t" // DST->M[m0-m3] = M[m0-m3] * s
+		"vmul.f32 	q9, q5, d0[0]    			\n\t" // DST->M[m4-m7] = M[m4-m7] * s
+		"vmul.f32 	q10, q6, d0[0]    			\n\t" // DST->M[m8-m11] = M[m8-m11] * s
+		"vmul.f32 	q11, q7, d0[0]   		 	\n\t" // DST->M[m12-m15] = M[m12-m15] * s
+
+		"vst1.32 	{q8-q9},   		[%0]! 		\n\t" // DST->M[m0-m7]
+		"vst1.32 	{q10-q11}, 		[%0]		\n\t" // DST->M[m8-m15]
+		:
+		: "r"(dst), "r"(m), "r"(&scalar)
+		: "q0", "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11", "memory"
+	);
+}
+
+inline void MathUtil::multiplyMatrix(const float* m1, const float* m2, float* dst)
+{
+	asm volatile(
+		"vld1.32	 {d16 - d19}, [%1]!	  \n\t"       // M1[m0-m7]
+		"vld1.32     {d20 - d23}, [%1]    \n\t"       // M1[m8-m15]
+		"vld1.32     {d0 - d3}, [%2]!     \n\t"       // M2[m0-m7]
+		"vld1.32     {d4 - d7}, [%2]      \n\t"       // M2[m8-m15]
+
+		"vmul.f32    q12, q8, d0[0]     \n\t"         // DST->M[m0-m3] = M1[m0-m3] * M2[m0]
+		"vmul.f32    q13, q8, d2[0]     \n\t"         // DST->M[m4-m7] = M1[m4-m7] * M2[m4]
+		"vmul.f32    q14, q8, d4[0]     \n\t"         // DST->M[m8-m11] = M1[m8-m11] * M2[m8]
+		"vmul.f32    q15, q8, d6[0]     \n\t"         // DST->M[m12-m15] = M1[m12-m15] * M2[m12]
+
+		"vmla.f32    q12, q9, d0[1]     \n\t"         // DST->M[m0-m3] += M1[m0-m3] * M2[m1]
+		"vmla.f32    q13, q9, d2[1]     \n\t"         // DST->M[m4-m7] += M1[m4-m7] * M2[m5]
+		"vmla.f32    q14, q9, d4[1]     \n\t"         // DST->M[m8-m11] += M1[m8-m11] * M2[m9]
+		"vmla.f32    q15, q9, d6[1]     \n\t"         // DST->M[m12-m15] += M1[m12-m15] * M2[m13]
+
+		"vmla.f32    q12, q10, d1[0]    \n\t"         // DST->M[m0-m3] += M1[m0-m3] * M2[m2]
+		"vmla.f32    q13, q10, d3[0]    \n\t"         // DST->M[m4-m7] += M1[m4-m7] * M2[m6]
+		"vmla.f32    q14, q10, d5[0]    \n\t"         // DST->M[m8-m11] += M1[m8-m11] * M2[m10]
+		"vmla.f32    q15, q10, d7[0]    \n\t"         // DST->M[m12-m15] += M1[m12-m15] * M2[m14]
+
+		"vmla.f32    q12, q11, d1[1]    \n\t"         // DST->M[m0-m3] += M1[m0-m3] * M2[m3]
+		"vmla.f32    q13, q11, d3[1]    \n\t"         // DST->M[m4-m7] += M1[m4-m7] * M2[m7]
+		"vmla.f32    q14, q11, d5[1]    \n\t"         // DST->M[m8-m11] += M1[m8-m11] * M2[m11]
+		"vmla.f32    q15, q11, d7[1]    \n\t"         // DST->M[m12-m15] += M1[m12-m15] * M2[m15]
+
+		"vst1.32    {d24 - d27}, [%0]!    \n\t"       // DST->M[m0-m7]
+		"vst1.32    {d28 - d31}, [%0]     \n\t"       // DST->M[m8-m15]
+
+		: // output
+		: "r"(dst), "r"(m1), "r"(m2) // input - note *value* of pointer doesn't change.
+		: "memory", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15"
+	);
+}
+
+inline void MathUtil::negateMatrix(const float* m, float* dst)
+{
+	asm volatile(
+		"vld1.32 	{q0-q1},  [%1]! 	\n\t" // load m0-m7
+		"vld1.32 	{q2-q3},  [%1]   	\n\t" // load m8-m15
+
+		"vneg.f32 	q4, q0 				\n\t" // negate m0-m3
+		"vneg.f32 	q5, q1 				\n\t" // negate m4-m7
+		"vneg.f32 	q6, q2 				\n\t" // negate m8-m15
+		"vneg.f32 	q7, q3 				\n\t" // negate m8-m15
+
+		"vst1.32 	{q4-q5},  [%0]!		\n\t" // store m0-m7
+		"vst1.32 	{q6-q7},  [%0]		\n\t" // store m8-m15
+		:
+		: "r"(dst), "r"(m)
+		: "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "memory"
+	);
+}
+
+inline void MathUtil::transposeMatrix(const float* m, float* dst)
+{
+	asm volatile(
+		"vld4.32 {d0[0], d2[0], d4[0], d6[0]}, [%1]! 	\n\t" // DST->M[m0, m4, m8, m12] = M[m0-m3]
+		"vld4.32 {d0[1], d2[1], d4[1], d6[1]}, [%1]!	\n\t" // DST->M[m1, m5, m9, m12] = M[m4-m7]
+		"vld4.32 {d1[0], d3[0], d5[0], d7[0]}, [%1]!	\n\t" // DST->M[m2, m6, m10, m12] = M[m8-m11]
+		"vld4.32 {d1[1], d3[1], d5[1], d7[1]}, [%1] 	\n\t" // DST->M[m3, m7, m11, m12] = M[m12-m15]
+
+		"vst1.32 {q0-q1}, [%0]! 						\n\t" // DST->M[m0-m7]
+		"vst1.32 {q2-q3}, [%0] 							\n\t" // DST->M[m8-m15]
+		:
+		: "r"(dst), "r"(m)
+		: "q0", "q1", "q2", "q3", "memory"
+	);
+}
+
+inline void MathUtil::transformVector4(const float* m, float x, float y, float z, float w, float* dst)
+{
+	asm volatile(
+		"vld1.32	{d0[0]},		[%1]	\n\t"	// V[x]
+		"vld1.32	{d0[1]},    	[%2]	\n\t"	// V[y]
+		"vld1.32	{d1[0]},		[%3]	\n\t"	// V[z]
+		"vld1.32	{d1[1]},		[%4]	\n\t"	// V[w]
+		"vld1.32	{d18 - d21},	[%5]!	\n\t"	// M[m0-m7]
+		"vld1.32	{d22 - d25},	[%5]	\n\t"	// M[m8-m15]
+
+		"vmul.f32 q13,  q9, d0[0]			\n\t"	// DST->V = M[m0-m3] * V[x]
+		"vmla.f32 q13, q10, d0[1]      		\n\t"	// DST->V += M[m4-m7] * V[y]
+		"vmla.f32 q13, q11, d1[0]      		\n\t"	// DST->V += M[m8-m11] * V[z]
+		"vmla.f32 q13, q12, d1[1]      		\n\t"	// DST->V += M[m12-m15] * V[w]
+
+		"vst1.32 {d26}, [%0]!        		\n\t"	// DST->V[x, y]
+		"vst1.32 {d27[0]}, [%0]        		\n\t"	// DST->V[z]
+		:
+		: "r"(dst), "r"(&x), "r"(&y), "r"(&z), "r"(&w), "r"(m)
+		: "q0", "q9", "q10","q11", "q12", "q13", "memory"
+	);
+}
+
+inline void MathUtil::transformVector4(const float* m, const float* v, float* dst)
+{
+	asm volatile
+	(
+		"vld1.32	{d0, d1}, [%1]		\n\t"   // V[x, y, z, w]
+		"vld1.32    {d18 - d21}, [%2]!  \n\t"   // M[m0-m7]
+		"vld1.32    {d22 - d25}, [%2]  \n\t"    // M[m8-m15]
+
+		"vmul.f32   q13, q9, d0[0]      \n\t"   // DST->V = M[m0-m3] * V[x]
+		"vmla.f32   q13, q10, d0[1]     \n\t"   // DST->V = M[m4-m7] * V[y]
+		"vmla.f32   q13, q11, d1[0]     \n\t"   // DST->V = M[m8-m11] * V[z]
+		"vmla.f32   q13, q12, d1[1]     \n\t"   // DST->V = M[m12-m15] * V[w]
+
+		"vst1.32    {d26, d27}, [%0]    \n\t"   // DST->V
+		:
+		: "r"(dst), "r"(v), "r"(m)
+		: "q0", "q9", "q10","q11", "q12", "q13", "memory"
+	);
+}
+
+inline void MathUtil::crossVector3(const float* v1, const float* v2, float* dst)
+{
+	asm volatile(
+		"vld1.32 {d1[1]},  [%1] 		\n\t" //
+		"vld1.32 {d0},     [%2]         \n\t" //
+		"vmov.f32 s2, s1                \n\t" // q0 = (v1y, v1z, v1z, v1x)
+
+		"vld1.32 {d2[1]},  [%3]	    	\n\t" //
+		"vld1.32 {d3},     [%4]         \n\t" //
+		"vmov.f32 s4, s7          		\n\t" // q1 = (v2z, v2x, v2y, v2z)
+
+		"vmul.f32 d4, d0, d2  			\n\t" // x = v1y * v2z, y = v1z * v2x
+		"vmls.f32 d4, d1, d3  			\n\t" // x -= v1z * v2y, y-= v1x - v2z
+
+		"vmul.f32 d5, d3, d1[1]			\n\t" // z = v1x * v2y
+		"vmls.f32 d5, d0, d2[1]         \n\t" // z-= v1y * vx
+
+		"vst1.32 {d4}, 	  [%0]!    		\n\t" // V[x, y]
+		"vst1.32 {d5[0]}, [%0]     		\n\t" // V[z]
+		:
+		: "r"(dst), "r"(v1), "r"((v1+1)), "r"(v2), "r"((v2+1))
+		: "q0", "q1", "q2", "memory"
+	);
+}
+
+}

+ 83 - 178
gameplay/src/Matrix.cpp

@@ -1,8 +1,7 @@
 #include "Base.h"
 #include "Matrix.h"
 #include "Quaternion.h"
-
-#define MATRIX_SIZE     ( sizeof(float) * 16 )
+#include "MathUtil.h"
 
 namespace gameplay
 {
@@ -68,7 +67,7 @@ void Matrix::createLookAt(float eyePositionX, float eyePositionY, float eyePosit
                           float targetPositionX, float targetPositionY, float targetPositionZ,
                           float upX, float upY, float upZ, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     Vector3 eye(eyePositionX, eyePositionY, eyePositionZ);
     Vector3 target(targetPositionX, targetPositionY, targetPositionZ);
@@ -111,13 +110,23 @@ void Matrix::createLookAt(float eyePositionX, float eyePositionY, float eyePosit
 void Matrix::createPerspective(float fieldOfView, float aspectRatio,
                                      float zNearPlane, float zFarPlane, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
+    GP_ASSERT(zFarPlane != zNearPlane);
 
     float f_n = 1.0f / (zFarPlane - zNearPlane);
-    float factor = 1.0f / tanf(MATH_DEG_TO_RAD(fieldOfView) * 0.5f);
+    float theta = MATH_DEG_TO_RAD(fieldOfView) * 0.5f;
+    if (fabs(fmod(theta, MATH_PIOVER2)) < MATH_EPSILON)
+    {
+        GP_ERROR("Invalid field of view value (%d) causes attempted calculation tan(%d), which is undefined.", fieldOfView, theta);
+        return;
+    }
+    float divisor = tan(theta);
+    GP_ASSERT(divisor);
+    float factor = 1.0f / divisor;
 
     memset(dst, 0, MATRIX_SIZE);
 
+    GP_ASSERT(aspectRatio);
     dst->m[0] = (1.0f / aspectRatio) * factor;
     dst->m[5] = factor;
     dst->m[10] = (-(zFarPlane + zNearPlane)) * f_n;
@@ -135,7 +144,10 @@ void Matrix::createOrthographic(float width, float height, float zNearPlane, flo
 void Matrix::createOrthographicOffCenter(float left, float right, float bottom, float top,
                                          float zNearPlane, float zFarPlane, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
+    GP_ASSERT(right != left);
+    GP_ASSERT(top != bottom);
+    GP_ASSERT(zFarPlane != zNearPlane);
 
     float r_l = 1.0f / (right - left);
     float t_b = 1.0f / (top - bottom);
@@ -153,7 +165,7 @@ void Matrix::createOrthographicOffCenter(float left, float right, float bottom,
 
 void Matrix::createScale(const Vector3& scale, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     memcpy(dst, MATRIX_IDENTITY, MATRIX_SIZE);
 
@@ -164,7 +176,7 @@ void Matrix::createScale(const Vector3& scale, Matrix* dst)
 
 void Matrix::createScale(float xScale, float yScale, float zScale, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     memcpy(dst, MATRIX_IDENTITY, MATRIX_SIZE);
 
@@ -176,7 +188,7 @@ void Matrix::createScale(float xScale, float yScale, float zScale, Matrix* dst)
 
 void Matrix::createRotation(const Quaternion& q, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     float x2 = q.x + q.x;
     float y2 = q.y + q.y;
@@ -215,7 +227,7 @@ void Matrix::createRotation(const Quaternion& q, Matrix* dst)
 
 void Matrix::createRotation(const Vector3& axis, float angle, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     float x = axis.x;
     float y = axis.y;
@@ -274,7 +286,7 @@ void Matrix::createRotation(const Vector3& axis, float angle, Matrix* dst)
 
 void Matrix::createRotationX(float angle, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     memcpy(dst, MATRIX_IDENTITY, MATRIX_SIZE);
 
@@ -289,7 +301,7 @@ void Matrix::createRotationX(float angle, Matrix* dst)
 
 void Matrix::createRotationY(float angle, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     memcpy(dst, MATRIX_IDENTITY, MATRIX_SIZE);
 
@@ -304,7 +316,7 @@ void Matrix::createRotationY(float angle, Matrix* dst)
 
 void Matrix::createRotationZ(float angle, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     memcpy(dst, MATRIX_IDENTITY, MATRIX_SIZE);
 
@@ -319,7 +331,7 @@ void Matrix::createRotationZ(float angle, Matrix* dst)
 
 void Matrix::createTranslation(const Vector3& translation, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     memcpy(dst, MATRIX_IDENTITY, MATRIX_SIZE);
 
@@ -330,7 +342,7 @@ void Matrix::createTranslation(const Vector3& translation, Matrix* dst)
 
 void Matrix::createTranslation(float xTranslation, float yTranslation, float zTranslation, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     memcpy(dst, MATRIX_IDENTITY, MATRIX_SIZE);
 
@@ -346,24 +358,9 @@ void Matrix::add(float scalar)
 
 void Matrix::add(float scalar, Matrix* dst)
 {
-    assert(dst);
-
-    dst->m[0]  = m[0]  + scalar;
-    dst->m[1]  = m[1]  + scalar;
-    dst->m[2]  = m[2]  + scalar;
-    dst->m[3]  = m[3]  + scalar;
-    dst->m[4]  = m[4]  + scalar;
-    dst->m[5]  = m[5]  + scalar;
-    dst->m[6]  = m[6]  + scalar;
-    dst->m[7]  = m[7]  + scalar;
-    dst->m[8]  = m[8]  + scalar;
-    dst->m[9]  = m[9]  + scalar;
-    dst->m[10] = m[10] + scalar;
-    dst->m[11] = m[11] + scalar;
-    dst->m[12] = m[12] + scalar;
-    dst->m[13] = m[13] + scalar;
-    dst->m[14] = m[14] + scalar;
-    dst->m[15] = m[15] + scalar;
+    GP_ASSERT(dst);
+
+    MathUtil::addMatrix(m, scalar, dst->m);
 }
 
 void Matrix::add(const Matrix& m)
@@ -373,24 +370,9 @@ void Matrix::add(const Matrix& m)
 
 void Matrix::add(const Matrix& m1, const Matrix& m2, Matrix* dst)
 {
-    assert(dst);
-
-    dst->m[0]  = m1.m[0]  + m2.m[0];
-    dst->m[1]  = m1.m[1]  + m2.m[1];
-    dst->m[2]  = m1.m[2]  + m2.m[2];
-    dst->m[3]  = m1.m[3]  + m2.m[3];
-    dst->m[4]  = m1.m[4]  + m2.m[4];
-    dst->m[5]  = m1.m[5]  + m2.m[5];
-    dst->m[6]  = m1.m[6]  + m2.m[6];
-    dst->m[7]  = m1.m[7]  + m2.m[7];
-    dst->m[8]  = m1.m[8]  + m2.m[8];
-    dst->m[9]  = m1.m[9]  + m2.m[9];
-    dst->m[10] = m1.m[10] + m2.m[10];
-    dst->m[11] = m1.m[11] + m2.m[11];
-    dst->m[12] = m1.m[12] + m2.m[12];
-    dst->m[13] = m1.m[13] + m2.m[13];
-    dst->m[14] = m1.m[14] + m2.m[14];
-    dst->m[15] = m1.m[15] + m2.m[15];
+    GP_ASSERT(dst);
+
+    MathUtil::addMatrix(m1.m, m2.m, dst->m);
 }
 
 bool Matrix::decompose(Vector3* scale, Quaternion* rotation, Vector3* translation) const
@@ -464,35 +446,37 @@ bool Matrix::decompose(Vector3* scale, Quaternion* rotation, Vector3* translatio
     {
         float s = 0.5f / sqrt(trace);
         rotation->w = 0.25f / s;
-        rotation->x = ( yaxis.z - zaxis.y ) * s;
-        rotation->y = ( zaxis.x - xaxis.z ) * s;
-        rotation->z = ( xaxis.y - yaxis.x ) * s;
+        rotation->x = (yaxis.z - zaxis.y) * s;
+        rotation->y = (zaxis.x - xaxis.z) * s;
+        rotation->z = (xaxis.y - yaxis.x) * s;
     }
     else
     {
+        // Note: since xaxis, yaxis, and zaxis are normalized, 
+        // we will never divide by zero in the code below.
         if (xaxis.x > yaxis.y && xaxis.x > zaxis.z)
         {
-            float s = 2.0f * sqrt(1.0f + xaxis.x - yaxis.y - zaxis.z);
-            rotation->w = (yaxis.z - zaxis.y ) / s;
-            rotation->x = 0.25f * s;
-            rotation->y = (yaxis.x + xaxis.y ) / s;
-            rotation->z = (zaxis.x + xaxis.z ) / s;
+            float s = 0.5f / sqrt(1.0f + xaxis.x - yaxis.y - zaxis.z);
+            rotation->w = (yaxis.z - zaxis.y) * s;
+            rotation->x = 0.25f / s;
+            rotation->y = (yaxis.x + xaxis.y) * s;
+            rotation->z = (zaxis.x + xaxis.z) * s;
         }
         else if (yaxis.y > zaxis.z)
         {
-            float s = 2.0f * sqrt(1.0f + yaxis.y - xaxis.x - zaxis.z);
-            rotation->w = (zaxis.x - xaxis.z ) / s;
-            rotation->x = (yaxis.x + xaxis.y ) / s;
-            rotation->y = 0.25f * s;
-            rotation->z = (zaxis.y + yaxis.z ) / s;
+            float s = 0.5f / sqrt(1.0f + yaxis.y - xaxis.x - zaxis.z);
+            rotation->w = (zaxis.x - xaxis.z) * s;
+            rotation->x = (yaxis.x + xaxis.y) * s;
+            rotation->y = 0.25f / s;
+            rotation->z = (zaxis.y + yaxis.z) * s;
         }
         else
         {
-            float s = 2.0f * sqrt(1.0f + zaxis.z - xaxis.x - yaxis.y );
-            rotation->w = (xaxis.y - yaxis.x ) / s;
-            rotation->x = (zaxis.x + xaxis.z ) / s;
-            rotation->y = (zaxis.y + yaxis.z ) / s;
-            rotation->z = 0.25f * s;
+            float s = 0.5f / sqrt(1.0f + zaxis.z - xaxis.x - yaxis.y );
+            rotation->w = (xaxis.y - yaxis.x ) * s;
+            rotation->x = (zaxis.x + xaxis.z ) * s;
+            rotation->y = (zaxis.y + yaxis.z ) * s;
+            rotation->z = 0.25f / s;
         }
     }
 
@@ -535,7 +519,7 @@ void Matrix::getTranslation(Vector3* translation) const
 
 void Matrix::getUpVector(Vector3* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     dst->x = m[4];
     dst->y = m[5];
@@ -544,7 +528,8 @@ void Matrix::getUpVector(Vector3* dst) const
 
 void Matrix::getDownVector(Vector3* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
+    
     dst->x = -m[4];
     dst->y = -m[5];
     dst->z = -m[6];
@@ -552,7 +537,7 @@ void Matrix::getDownVector(Vector3* dst) const
 
 void Matrix::getLeftVector(Vector3* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     dst->x = -m[0];
     dst->y = -m[1];
@@ -561,7 +546,7 @@ void Matrix::getLeftVector(Vector3* dst) const
 
 void Matrix::getRightVector(Vector3* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     dst->x = m[0];
     dst->y = m[1];
@@ -570,7 +555,7 @@ void Matrix::getRightVector(Vector3* dst) const
 
 void Matrix::getForwardVector(Vector3* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     dst->x = -m[8];
     dst->y = -m[9];
@@ -579,7 +564,7 @@ void Matrix::getForwardVector(Vector3* dst) const
 
 void Matrix::getBackVector(Vector3* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     dst->x = m[8];
     dst->y = m[9];
@@ -657,24 +642,9 @@ void Matrix::multiply(float scalar, Matrix* dst) const
 
 void Matrix::multiply(const Matrix& m, float scalar, Matrix* dst)
 {
-    assert(dst);
-
-    dst->m[0]  = m.m[0]  * scalar;
-    dst->m[1]  = m.m[1]  * scalar;
-    dst->m[2]  = m.m[2]  * scalar;
-    dst->m[3]  = m.m[3]  * scalar;
-    dst->m[4]  = m.m[4]  * scalar;
-    dst->m[5]  = m.m[5]  * scalar;
-    dst->m[6]  = m.m[6]  * scalar;
-    dst->m[7]  = m.m[7]  * scalar;
-    dst->m[8]  = m.m[8]  * scalar;
-    dst->m[9]  = m.m[9]  * scalar;
-    dst->m[10] = m.m[10] * scalar;
-    dst->m[11] = m.m[11] * scalar;
-    dst->m[12] = m.m[12] * scalar;
-    dst->m[13] = m.m[13] * scalar;
-    dst->m[14] = m.m[14] * scalar;
-    dst->m[15] = m.m[15] * scalar;
+    GP_ASSERT(dst);
+
+    MathUtil::multiplyMatrix(m.m, scalar, dst->m);
 }
 
 void Matrix::multiply(const Matrix& m)
@@ -684,32 +654,9 @@ void Matrix::multiply(const Matrix& m)
 
 void Matrix::multiply(const Matrix& m1, const Matrix& m2, Matrix* dst)
 {
-    assert(dst);
-
-    // Support the case where m1 or m2 is the same array as dst.
-    float product[16];
-
-    product[0]  = m1.m[0] * m2.m[0]  + m1.m[4] * m2.m[1] + m1.m[8]   * m2.m[2]  + m1.m[12] * m2.m[3];
-    product[1]  = m1.m[1] * m2.m[0]  + m1.m[5] * m2.m[1] + m1.m[9]   * m2.m[2]  + m1.m[13] * m2.m[3];
-    product[2]  = m1.m[2] * m2.m[0]  + m1.m[6] * m2.m[1] + m1.m[10]  * m2.m[2]  + m1.m[14] * m2.m[3];
-    product[3]  = m1.m[3] * m2.m[0]  + m1.m[7] * m2.m[1] + m1.m[11]  * m2.m[2]  + m1.m[15] * m2.m[3];
-
-    product[4]  = m1.m[0] * m2.m[4]  + m1.m[4] * m2.m[5] + m1.m[8]   * m2.m[6]  + m1.m[12] * m2.m[7];
-    product[5]  = m1.m[1] * m2.m[4]  + m1.m[5] * m2.m[5] + m1.m[9]   * m2.m[6]  + m1.m[13] * m2.m[7];
-    product[6]  = m1.m[2] * m2.m[4]  + m1.m[6] * m2.m[5] + m1.m[10]  * m2.m[6]  + m1.m[14] * m2.m[7];
-    product[7]  = m1.m[3] * m2.m[4]  + m1.m[7] * m2.m[5] + m1.m[11]  * m2.m[6]  + m1.m[15] * m2.m[7];
-
-    product[8]  = m1.m[0] * m2.m[8]  + m1.m[4] * m2.m[9] + m1.m[8]   * m2.m[10] + m1.m[12] * m2.m[11];
-    product[9]  = m1.m[1] * m2.m[8]  + m1.m[5] * m2.m[9] + m1.m[9]   * m2.m[10] + m1.m[13] * m2.m[11];
-    product[10] = m1.m[2] * m2.m[8]  + m1.m[6] * m2.m[9] + m1.m[10]  * m2.m[10] + m1.m[14] * m2.m[11];
-    product[11] = m1.m[3] * m2.m[8]  + m1.m[7] * m2.m[9] + m1.m[11]  * m2.m[10] + m1.m[15] * m2.m[11];
+	GP_ASSERT(dst);
 
-    product[12] = m1.m[0] * m2.m[12] + m1.m[4] * m2.m[13] + m1.m[8]  * m2.m[14] + m1.m[12] * m2.m[15];
-    product[13] = m1.m[1] * m2.m[12] + m1.m[5] * m2.m[13] + m1.m[9]  * m2.m[14] + m1.m[13] * m2.m[15];
-    product[14] = m1.m[2] * m2.m[12] + m1.m[6] * m2.m[13] + m1.m[10] * m2.m[14] + m1.m[14] * m2.m[15];
-    product[15] = m1.m[3] * m2.m[12] + m1.m[7] * m2.m[13] + m1.m[11] * m2.m[14] + m1.m[15] * m2.m[15];
-
-    memcpy(dst->m, product, MATRIX_SIZE);
+	MathUtil::multiplyMatrix(m1.m, m2.m, dst->m);
 }
 
 void Matrix::negate()
@@ -719,22 +666,9 @@ void Matrix::negate()
 
 void Matrix::negate(Matrix* dst) const
 {
-    dst->m[0]  = -m[0];
-    dst->m[1]  = -m[1];
-    dst->m[2]  = -m[2];
-    dst->m[3]  = -m[3];
-    dst->m[4]  = -m[4];
-    dst->m[5]  = -m[5];
-    dst->m[6]  = -m[6];
-    dst->m[7]  = -m[7];
-    dst->m[8]  = -m[8];
-    dst->m[9]  = -m[9];
-    dst->m[10] = -m[10];
-    dst->m[11] = -m[11];
-    dst->m[12] = -m[12];
-    dst->m[13] = -m[13];
-    dst->m[14] = -m[14];
-    dst->m[15] = -m[15];
+    GP_ASSERT(dst);
+
+    MathUtil::negateMatrix(m, dst->m);
 }
 
 void Matrix::rotate(const Quaternion& q)
@@ -792,8 +726,6 @@ void Matrix::rotateZ(float angle)
 
 void Matrix::rotateZ(float angle, Matrix* dst) const
 {
-    assert(dst);
-
     Matrix r;
     createRotationZ(angle, &r);
     multiply(*this, r, dst);
@@ -816,8 +748,6 @@ void Matrix::scale(float xScale, float yScale, float zScale)
 
 void Matrix::scale(float xScale, float yScale, float zScale, Matrix* dst) const
 {
-    assert(dst);
-
     Matrix s;
     createScale(xScale, yScale, zScale, &s);
     multiply(*this, s, dst);
@@ -855,7 +785,7 @@ void Matrix::set(float m11, float m12, float m13, float m14, float m21, float m2
 
 void Matrix::set(const float* m)
 {
-    assert(m);
+    GP_ASSERT(m);
     memcpy(this->m, m, MATRIX_SIZE);
 }
 
@@ -881,26 +811,14 @@ void Matrix::subtract(const Matrix& m)
 
 void Matrix::subtract(const Matrix& m1, const Matrix& m2, Matrix* dst)
 {
-    dst->m[0]  = m1.m[0]  - m2.m[0];
-    dst->m[1]  = m1.m[1]  - m2.m[1];
-    dst->m[2]  = m1.m[2]  - m2.m[2];
-    dst->m[3]  = m1.m[3]  - m2.m[3];
-    dst->m[4]  = m1.m[4]  - m2.m[4];
-    dst->m[5]  = m1.m[5]  - m2.m[5];
-    dst->m[6]  = m1.m[6]  - m2.m[6];
-    dst->m[7]  = m1.m[7]  - m2.m[7];
-    dst->m[8]  = m1.m[8]  - m2.m[8];
-    dst->m[9]  = m1.m[9]  - m2.m[9];
-    dst->m[10] = m1.m[10] - m2.m[10];
-    dst->m[11] = m1.m[11] - m2.m[11];
-    dst->m[12] = m1.m[12] - m2.m[12];
-    dst->m[13] = m1.m[13] - m2.m[13];
-    dst->m[14] = m1.m[14] - m2.m[14];
-    dst->m[15] = m1.m[15] - m2.m[15];
+    GP_ASSERT(dst);
+
+    MathUtil::subtractMatrix(m1.m, m2.m, dst->m);
 }
 
 void Matrix::transformPoint(Vector3* point) const
 {
+    GP_ASSERT(point);
     transformVector(point->x, point->y, point->z, 1.0f, point);
 }
 
@@ -911,6 +829,7 @@ void Matrix::transformPoint(const Vector3& point, Vector3* dst) const
 
 void Matrix::transformVector(Vector3* vector) const
 {
+    GP_ASSERT(vector);
     transformVector(vector->x, vector->y, vector->z, 0.0f, vector);
 }
 
@@ -921,28 +840,22 @@ void Matrix::transformVector(const Vector3& vector, Vector3* dst) const
 
 void Matrix::transformVector(float x, float y, float z, float w, Vector3* dst) const
 {
-    assert(dst);
-    
-    dst->set(
-        x * m[0] + y * m[4] + z * m[8] + w * m[12],
-        x * m[1] + y * m[5] + z * m[9] + w * m[13],
-        x * m[2] + y * m[6] + z * m[10] + w * m[14] );
+    GP_ASSERT(dst);
+
+    MathUtil::transformVector4(m, x, y, z, w, (float*)dst);
 }
 
 void Matrix::transformVector(Vector4* vector) const
 {
+    GP_ASSERT(vector);
     transformVector(*vector, vector);
 }
 
 void Matrix::transformVector(const Vector4& vector, Vector4* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
-    dst->set(
-        vector.x * m[0] + vector.y * m[4] + vector.z * m[8] + vector.w * m[12],
-        vector.x * m[1] + vector.y * m[5] + vector.z * m[9] + vector.w * m[13],
-        vector.x * m[2] + vector.y * m[6] + vector.z * m[10] + vector.w * m[14],
-        vector.x * m[3] + vector.y * m[7] + vector.z * m[11] + vector.w * m[15] );
+    MathUtil::transformVector4(m, (const float*) &vector, (float*)dst);
 }
 
 void Matrix::translate(float x, float y, float z)
@@ -952,8 +865,6 @@ void Matrix::translate(float x, float y, float z)
 
 void Matrix::translate(float x, float y, float z, Matrix* dst) const
 {
-    assert(dst);
-
     Matrix t;
     createTranslation(x, y, z, &t);
     multiply(*this, t, dst);
@@ -976,15 +887,9 @@ void Matrix::transpose()
 
 void Matrix::transpose(Matrix* dst) const
 {
-    assert(dst);
-    
-    float t[16] = {
-        m[0], m[4], m[8], m[12],
-        m[1], m[5], m[9], m[13],
-        m[2], m[6], m[10], m[14],
-        m[3], m[7], m[11], m[15]
-    };
-    memcpy(dst->m, t, MATRIX_SIZE);
+    GP_ASSERT(dst);
+
+    MathUtil::transposeMatrix(m, dst->m);
 }
 
 }

+ 18 - 3
gameplay/src/Mesh.cpp

@@ -24,11 +24,14 @@ Mesh::Mesh(const Mesh& copy) :
 
 Mesh::~Mesh()
 {
-    for (unsigned int i = 0; i < _partCount; ++i)
+    if (_parts)
     {
-        SAFE_DELETE(_parts[i]);
+        for (unsigned int i = 0; i < _partCount; ++i)
+        {
+            SAFE_DELETE(_parts[i]);
+        }
+        SAFE_DELETE_ARRAY(_parts);
     }
-    SAFE_DELETE_ARRAY(_parts);
 
     if (_vertexBuffer)
     {
@@ -43,12 +46,14 @@ Mesh* Mesh::createMesh(const VertexFormat& vertexFormat, unsigned int vertexCoun
     GL_ASSERT( glGenBuffers(1, &vbo) );
     if (GL_LAST_ERROR())
     {
+        GP_ERROR("Failed to create VBO for mesh with OpenGL error %d.", GL_LAST_ERROR());
         return NULL;
     }
 
     GL_ASSERT( glBindBuffer(GL_ARRAY_BUFFER, vbo) );
     if (GL_LAST_ERROR())
     {
+        GP_ERROR("Failed to bind VBO for mesh with OpenGL error %d.", GL_LAST_ERROR());
         glDeleteBuffers(1, &vbo);
         return NULL;
     }
@@ -56,6 +61,7 @@ Mesh* Mesh::createMesh(const VertexFormat& vertexFormat, unsigned int vertexCoun
     GL_CHECK( glBufferData(GL_ARRAY_BUFFER, vertexFormat.getVertexSize() * vertexCount, NULL, dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW) );
     if (GL_LAST_ERROR())
     {
+        GP_ERROR("Failed to load VBO with vertex data with OpenGL error %d.", GL_LAST_ERROR());
         glBindBuffer(GL_ARRAY_BUFFER, 0);
         glDeleteBuffers(1, &vbo);
         return NULL;
@@ -92,6 +98,7 @@ Mesh* Mesh::createQuad(float x, float y, float width, float height)
     Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 3), 4, false);
     if (mesh == NULL)
     {
+        GP_ERROR("Failed to create mesh.");
         return NULL;
     }
 
@@ -124,6 +131,7 @@ Mesh* Mesh::createQuadFullscreen()
     Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 2), 4, false);
     if (mesh == NULL)
     {
+        GP_ERROR("Failed to create mesh.");
         return NULL;
     }
 
@@ -160,6 +168,7 @@ Mesh* Mesh::createQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3,
     Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 3), 4, false);
     if (mesh == NULL)
     {
+        GP_ERROR("Failed to create mesh.");
         return NULL;
     }
 
@@ -171,6 +180,9 @@ Mesh* Mesh::createQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3,
 
 Mesh* Mesh::createLines(Vector3* points, unsigned int pointCount)
 {
+    GP_ASSERT(points);
+    GP_ASSERT(pointCount);
+
     float* vertices = new float[pointCount*3];
     memcpy(vertices, points, pointCount*3*sizeof(float));
 
@@ -181,6 +193,7 @@ Mesh* Mesh::createLines(Vector3* points, unsigned int pointCount)
     Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 1), pointCount, false);
     if (mesh == NULL)
     {
+        GP_ERROR("Failed to create mesh.");
         SAFE_DELETE_ARRAY(vertices);
         return NULL;
     }
@@ -226,6 +239,7 @@ Mesh* Mesh::createBoundingBox(const BoundingBox& box)
     Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 1), 18, false);
     if (mesh == NULL)
     {
+        GP_ERROR("Failed to create mesh.");
         return NULL;
     }
 
@@ -325,6 +339,7 @@ unsigned int Mesh::getPartCount() const
 
 MeshPart* Mesh::getPart(unsigned int index)
 {
+    GP_ASSERT(_parts);
     return _parts[index];
 }
 

+ 26 - 10
gameplay/src/MeshBatch.cpp

@@ -28,7 +28,10 @@ MeshBatch* MeshBatch::create(const VertexFormat& vertexFormat, Mesh::PrimitiveTy
 {
     Material* material = Material::create(materialPath);
     if (material == NULL)
+    {
+        GP_ERROR("Failed to create material for mesh batch from file '%s'.", materialPath);
         return NULL;
+    }
     MeshBatch* batch = create(vertexFormat, primitiveType, material, indexed, initialCapacity, growSize);
     SAFE_RELEASE(material); // batch now owns the material
     return batch;
@@ -36,7 +39,7 @@ MeshBatch* MeshBatch::create(const VertexFormat& vertexFormat, Mesh::PrimitiveTy
 
 MeshBatch* MeshBatch::create(const VertexFormat& vertexFormat, Mesh::PrimitiveType primitiveType, Material* material, bool indexed, unsigned int initialCapacity, unsigned int growSize)
 {
-    assert(material);
+    GP_ASSERT(material);
 
     MeshBatch* batch = new MeshBatch(vertexFormat, primitiveType, material, indexed, initialCapacity, growSize);
 
@@ -47,13 +50,17 @@ MeshBatch* MeshBatch::create(const VertexFormat& vertexFormat, Mesh::PrimitiveTy
 
 void MeshBatch::updateVertexAttributeBinding()
 {
-    // Update our vertex attribute bindings
+    GP_ASSERT(_material);
+
+    // Update our vertex attribute bindings.
     for (unsigned int i = 0, techniqueCount = _material->getTechniqueCount(); i < techniqueCount; ++i)
     {
         Technique* t = _material->getTechnique(i);
+        GP_ASSERT(t);
         for (unsigned int j = 0, passCount = t->getPassCount(); j < passCount; ++j)
         {
             Pass* p = t->getPass(j);
+            GP_ASSERT(p);
             VertexAttributeBinding* b = VertexAttributeBinding::create(_vertexFormat, _vertices, p->getEffect());
             p->setVertexAttributeBinding(b);
             SAFE_RELEASE(b);
@@ -73,14 +80,16 @@ void MeshBatch::setCapacity(unsigned int capacity)
 
 bool MeshBatch::resize(unsigned int capacity)
 {
-    assert(capacity > 0);
     if (capacity == 0)
+    {
+        GP_ERROR("Invalid resize capacity (0).");
         return false;
+    }
 
     if (capacity == _capacity)
         return true;
 
-    // Store old batch data
+    // Store old batch data.
     unsigned char* oldVertices = _vertices;
     unsigned short* oldIndices = _indices;
 
@@ -103,20 +112,21 @@ bool MeshBatch::resize(unsigned int capacity)
         vertexCapacity = capacity + 2;
         break;
     default:
-        assert(0); // unexpected
-        break;
+        GP_ERROR("Unsupported primitive type for mesh batch (%d).", _primitiveType);
+        return false;
     }
 
     // We have no way of knowing how many vertices will be stored in the batch
     // (we only know how many indices will be stored). Assume the worst case
     // for now, which is the same number of vertices as indices.
     unsigned int indexCapacity = vertexCapacity;
-
-    assert(indexCapacity <= USHRT_MAX);
     if (indexCapacity > USHRT_MAX)
+    {
+        GP_ERROR("Index capacity is greater than the maximum unsigned short value (%d > %d).", indexCapacity, USHRT_MAX);
         return false;
+    }
 
-    // Allocate new data and reset pointers
+    // Allocate new data and reset pointers.
     unsigned int voffset = _verticesPtr - _vertices;
     unsigned int vBytes = vertexCapacity * _vertexFormat.getVertexSize();
     _vertices = new unsigned char[vBytes];
@@ -173,12 +183,18 @@ void MeshBatch::draw()
     // ARRAY_BUFFER will be unbound automatically during pass->bind().
     GL_ASSERT( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0 ) );
 
-    // Bind the material
+    GP_ASSERT(_material);
+    if (_indexed)
+        GP_ASSERT(_indices);
+
+    // Bind the material.
     Technique* technique = _material->getTechnique();
+    GP_ASSERT(technique);
     unsigned int passCount = technique->getPassCount();
     for (unsigned int i = 0; i < passCount; ++i)
     {
         Pass* pass = technique->getPass(i);
+        GP_ASSERT(pass);
         pass->bind();
 
         if (_indexed)

+ 11 - 6
gameplay/src/MeshBatch.inl

@@ -11,7 +11,8 @@ Material* MeshBatch::getMaterial() const
 template <class T>
 void MeshBatch::add(T* vertices, unsigned int vertexCount, unsigned short* indices, unsigned int indexCount)
 {
-    assert(sizeof(T) == _vertexFormat.getVertexSize());
+    GP_ASSERT(vertices);
+    GP_ASSERT(sizeof(T) == _vertexFormat.getVertexSize());
     
     unsigned int newVertexCount = _vertexCount + vertexCount;
     unsigned int newIndexCount = _indexCount + indexCount;
@@ -27,16 +28,20 @@ void MeshBatch::add(T* vertices, unsigned int vertexCount, unsigned short* indic
             return; // failed to grow
     }
     
-    // Copy vertex data
+    // Copy vertex data.
+    GP_ASSERT(_verticesPtr);
     unsigned int vBytes = vertexCount * _vertexFormat.getVertexSize();
     memcpy(_verticesPtr, vertices, vBytes);
     
-    // Copy index data
+    // Copy index data.
     if (_indexed)
     {
+        GP_ASSERT(indices);
+        GP_ASSERT(_indicesPtr);
+
         if (_vertexCount == 0)
         {
-            // Simply copy values directly into the start of the index array
+            // Simply copy values directly into the start of the index array.
             memcpy(_indicesPtr, indices, indexCount * sizeof(unsigned short));
         }
         else
@@ -50,8 +55,8 @@ void MeshBatch::add(T* vertices, unsigned int vertexCount, unsigned short* indic
                 _indicesPtr += 2;
             }
             
-            // Loop through all indices and insert them, their their value offset by
-            // 'vertexCount' so that they are relative to the first newly insertted vertex
+            // Loop through all indices and insert them, with their values offset by
+            // 'vertexCount' so that they are relative to the first newly inserted vertex.
             for (unsigned int i = 0; i < indexCount; ++i)
             {
                 _indicesPtr[i] = indices[i] + _vertexCount;

+ 10 - 0
gameplay/src/MeshPart.cpp

@@ -29,12 +29,14 @@ MeshPart* MeshPart::create(Mesh* mesh, unsigned int meshIndex, Mesh::PrimitiveTy
     GL_ASSERT( glGenBuffers(1, &vbo) );
     if (GL_LAST_ERROR())
     {
+        GP_ERROR("Failed to create VBO for index buffer with OpenGL error %d.", GL_LAST_ERROR());
         return NULL;
     }
 
     GL_ASSERT( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo) );
     if (GL_LAST_ERROR())
     {
+        GP_ERROR("Failed to bind VBO for index buffer with OpenGL error %d.", GL_LAST_ERROR());
         glDeleteBuffers(1, &vbo);
         return NULL;
     }
@@ -51,10 +53,15 @@ MeshPart* MeshPart::create(Mesh* mesh, unsigned int meshIndex, Mesh::PrimitiveTy
     case Mesh::INDEX32:
         indexSize = 4;
         break;
+    default:
+        GP_ERROR("Unsupported index format (%d).", indexFormat);
+        glDeleteBuffers(1, &vbo);
+        return NULL;
     }
     GL_CHECK( glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize * indexCount, NULL, dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW) );
     if (GL_LAST_ERROR())
     {
+        GP_ERROR("Failed to load VBO with index data with OpenGL error %d.", GL_LAST_ERROR());
         glDeleteBuffers(1, &vbo);
         return NULL;
     }
@@ -117,6 +124,9 @@ void MeshPart::setIndexData(void* indexData, unsigned int indexStart, unsigned i
     case Mesh::INDEX32:
         indexSize = 4;
         break;
+    default:
+        GP_ERROR("Unsupported index format (%d).", _indexFormat);
+        return;
     }
 
     if (indexStart == 0 && indexCount == 0)

+ 12 - 8
gameplay/src/MeshSkin.cpp

@@ -37,13 +37,13 @@ unsigned int MeshSkin::getJointCount() const
 
 Joint* MeshSkin::getJoint(unsigned int index) const
 {
-    assert(index < _joints.size());
+    GP_ASSERT(index < _joints.size());
     return _joints[index];
 }
 
 Joint* MeshSkin::getJoint(const char* id) const
 {
-    assert(id);
+    GP_ASSERT(id);
 
     for (unsigned int i = 0, count = _joints.size(); i < count; ++i)
     {
@@ -66,7 +66,7 @@ MeshSkin* MeshSkin::clone(NodeCloneContext &context) const
         const unsigned int jointCount = getJointCount();
         skin->setJointCount(jointCount);
 
-        assert(skin->_rootNode == NULL);
+        GP_ASSERT(skin->_rootNode == NULL);
         
         // Check if the root node has already been cloned.
         if (Node* rootNode = context.findClonedNode(_rootNode))
@@ -80,11 +80,12 @@ MeshSkin* MeshSkin::clone(NodeCloneContext &context) const
         }
         
         Node* node = skin->_rootNode->findNode(_rootJoint->getId());
-        assert(node);
+        GP_ASSERT(node);
         skin->_rootJoint = static_cast<Joint*>(node);
         for (unsigned int i = 0; i < jointCount; ++i)
         {
             Joint* oldJoint = getJoint(i);
+            GP_ASSERT(oldJoint);
             
             Joint* newJoint = static_cast<Joint*>(skin->_rootJoint->findNode(oldJoint->getId()));
             if (!newJoint)
@@ -92,7 +93,7 @@ MeshSkin* MeshSkin::clone(NodeCloneContext &context) const
                 if (strcmp(skin->_rootJoint->getId(), oldJoint->getId()) == 0)
                     newJoint = static_cast<Joint*>(skin->_rootJoint);
             }
-            assert(newJoint);
+            GP_ASSERT(newJoint);
             skin->setJoint(newJoint, i);
         }
     }
@@ -101,10 +102,10 @@ MeshSkin* MeshSkin::clone(NodeCloneContext &context) const
 
 void MeshSkin::setJointCount(unsigned int jointCount)
 {
-    // Erase the joints vector and release all joints
+    // Erase the joints vector and release all joints.
     clearJoints();
 
-    // Resize the joints vector and initialize to NULL
+    // Resize the joints vector and initialize to NULL.
     _joints.resize(jointCount);
     for (unsigned int i = 0; i < jointCount; i++)
     {
@@ -128,7 +129,7 @@ void MeshSkin::setJointCount(unsigned int jointCount)
 
 void MeshSkin::setJoint(Joint* joint, unsigned int index)
 {
-    assert(index < _joints.size());
+    GP_ASSERT(index < _joints.size());
 
     if (_joints[index])
     {
@@ -147,9 +148,12 @@ void MeshSkin::setJoint(Joint* joint, unsigned int index)
 
 Vector4* MeshSkin::getMatrixPalette() const
 {
+    GP_ASSERT(_matrixPalette);
+
     unsigned int count = _joints.size();
     for (unsigned int i = 0; i < count; i++)
     {
+        GP_ASSERT(_joints[i]);
         _joints[i]->updateJointMatrix(getBindShape(), &_matrixPalette[i * PALETTE_ROWS]);
     }
     return _matrixPalette;

+ 50 - 9
gameplay/src/Model.cpp

@@ -12,6 +12,7 @@ namespace gameplay
 Model::Model(Mesh* mesh) :
     _mesh(mesh), _material(NULL), _partCount(0), _partMaterials(NULL), _node(NULL), _skin(NULL)
 {
+    GP_ASSERT(mesh);
     _partCount = mesh->getPartCount();
 }
 
@@ -35,6 +36,7 @@ Model::~Model()
 
 Model* Model::create(Mesh* mesh)
 {
+    GP_ASSERT(mesh);
     mesh->addRef();
     return new Model(mesh);
 }
@@ -46,12 +48,13 @@ Mesh* Model::getMesh() const
 
 unsigned int Model::getMeshPartCount() const
 {
+    GP_ASSERT(_mesh);
     return _mesh->getPartCount();
 }
 
 Material* Model::getMaterial(int partIndex)
 {
-    assert(partIndex == -1 || (partIndex >= 0 && partIndex < (int)getMeshPartCount()));
+    GP_ASSERT(partIndex == -1 || (partIndex >= 0 && partIndex < (int)getMeshPartCount()));
 
     Material* m = NULL;
 
@@ -75,7 +78,7 @@ Material* Model::getMaterial(int partIndex)
 
 void Model::setMaterial(Material* material, int partIndex)
 {
-    assert(partIndex == -1 || (partIndex >= 0 && partIndex < (int)getMeshPartCount()));
+    GP_ASSERT(partIndex == -1 || (partIndex >= 0 && partIndex < (int)getMeshPartCount()));
 
     Material* oldMaterial = NULL;
 
@@ -121,11 +124,13 @@ void Model::setMaterial(Material* material, int partIndex)
     // Release existing material and binding.
     if (oldMaterial)
     {
-        for (unsigned int i = 0, tCount = material->getTechniqueCount(); i < tCount; ++i)
+        for (unsigned int i = 0, tCount = oldMaterial->getTechniqueCount(); i < tCount; ++i)
         {
-            Technique* t = material->getTechnique(i);
+            Technique* t = oldMaterial->getTechnique(i);
+            GP_ASSERT(t);
             for (unsigned int j = 0, pCount = t->getPassCount(); j < pCount; ++j)
             {
+                GP_ASSERT(t->getPass(j));
                 t->getPass(j)->setVertexAttributeBinding(NULL);
             }
         }
@@ -138,9 +143,11 @@ void Model::setMaterial(Material* material, int partIndex)
         for (unsigned int i = 0, tCount = material->getTechniqueCount(); i < tCount; ++i)
         {
             Technique* t = material->getTechnique(i);
+            GP_ASSERT(t);
             for (unsigned int j = 0, pCount = t->getPassCount(); j < pCount; ++j)
             {
                 Pass* p = t->getPass(j);
+                GP_ASSERT(p);
                 VertexAttributeBinding* b = VertexAttributeBinding::create(_mesh, p->getEffect());
                 p->setVertexAttributeBinding(b);
                 SAFE_RELEASE(b);
@@ -161,6 +168,7 @@ Material* Model::setMaterial(const char* vshPath, const char* fshPath, const cha
     Material* material = Material::create(vshPath, fshPath, defines);
     if (material == NULL)
     {
+        GP_ERROR("Failed to create material for model.");
         return NULL;
     }
 
@@ -179,6 +187,7 @@ Material* Model::setMaterial(const char* materialPath, int partIndex)
     Material* material = Material::create(materialPath);
     if (material == NULL)
     {
+        GP_ERROR("Failed to create material for model.");
         return NULL;
     }
 
@@ -246,6 +255,8 @@ void Model::setNode(Node* node)
 
 void Model::draw(bool wireframe)
 {
+    GP_ASSERT(_mesh);
+
     unsigned int partCount = _mesh->getPartCount();
     if (partCount == 0)
     {
@@ -253,10 +264,12 @@ void Model::draw(bool wireframe)
         if (_material)
         {
             Technique* technique = _material->getTechnique();
+            GP_ASSERT(technique);
             unsigned int passCount = technique->getPassCount();
             for (unsigned int i = 0; i < passCount; ++i)
             {
                 Pass* pass = technique->getPass(i);
+                GP_ASSERT(pass);
                 pass->bind();
                 GL_ASSERT( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0) );
                 if (wireframe && (_mesh->getPrimitiveType() == Mesh::TRIANGLES || _mesh->getPrimitiveType() == Mesh::TRIANGLE_STRIP))
@@ -280,16 +293,19 @@ void Model::draw(bool wireframe)
         for (unsigned int i = 0; i < partCount; ++i)
         {
             MeshPart* part = _mesh->getPart(i);
+            GP_ASSERT(part);
 
             // Get the material for this mesh part.
             Material* material = getMaterial(i);
             if (material)
             {
                 Technique* technique = material->getTechnique();
+                GP_ASSERT(technique);
                 unsigned int passCount = technique->getPassCount();
                 for (unsigned int j = 0; j < passCount; ++j)
                 {
                     Pass* pass = technique->getPass(j);
+                    GP_ASSERT(pass);
                     pass->bind();
                     GL_ASSERT( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, part->_indexBuffer) );
                     if (wireframe && (_mesh->getPrimitiveType() == Mesh::TRIANGLES || _mesh->getPrimitiveType() == Mesh::TRIANGLE_STRIP))
@@ -307,6 +323,9 @@ void Model::draw(bool wireframe)
                         case Mesh::INDEX32:
                             indexSize = 4;
                             break;
+                        default:
+                            GP_ERROR("Unsupported index format (%d).", part->getIndexFormat());
+                            continue;
                         }
 
                         for (unsigned int k = 0; k < indexCount; k += 3)
@@ -327,6 +346,7 @@ void Model::draw(bool wireframe)
 
 void Model::validatePartCount()
 {
+    GP_ASSERT(_mesh);
     unsigned int partCount = _mesh->getPartCount();
 
     if (_partCount != partCount)
@@ -337,9 +357,12 @@ void Model::validatePartCount()
             Material** oldArray = _partMaterials;
             _partMaterials = new Material*[partCount];
             memset(_partMaterials, 0, sizeof(Material*) * partCount);
-            for (unsigned int i = 0; i < _partCount; ++i)
+            if (oldArray)
             {
-                _partMaterials[i] = oldArray[i];
+                for (unsigned int i = 0; i < _partCount; ++i)
+                {
+                    _partMaterials[i] = oldArray[i];
+                }
             }
             SAFE_DELETE_ARRAY(oldArray);
         }
@@ -352,18 +375,34 @@ void Model::validatePartCount()
 Model* Model::clone(NodeCloneContext &context)
 {
     Model* model = Model::create(getMesh());
+    if (!model)
+    {
+        GP_ERROR("Failed to clone model.");
+        return NULL;
+    }
+
     if (getSkin())
     {
         model->setSkin(getSkin()->clone(context));
     }
-    Material* materialClone = getMaterial()->clone(context);
-    model->setMaterial(materialClone); // TODO: Don't forget material parts
-    materialClone->release();
+    if (getMaterial())
+    {
+        Material* materialClone = getMaterial()->clone(context);
+        if (!materialClone)
+        {
+            GP_ERROR("Failed to clone material for model.");
+            return model;
+        }
+        model->setMaterial(materialClone); // TODO: Don't forget material parts
+        materialClone->release();
+    }
     return model;
 }
 
 void Model::setMaterialNodeBinding(Material *material)
 {
+    GP_ASSERT(material);
+
     if (_node)
     {
         material->setNodeBinding(_node);
@@ -372,6 +411,7 @@ void Model::setMaterialNodeBinding(Material *material)
         for (unsigned int i = 0; i < techniqueCount; ++i)
         {
             Technique* technique = material->getTechnique(i);
+            GP_ASSERT(technique);
             
             technique->setNodeBinding(_node);
 
@@ -379,6 +419,7 @@ void Model::setMaterialNodeBinding(Material *material)
             for (unsigned int j = 0; j < passCount; ++j)
             {
                 Pass* pass = technique->getPass(j);
+                GP_ASSERT(pass);
 
                 pass->setNodeBinding(_node);
             }

+ 66 - 37
gameplay/src/Node.cpp

@@ -87,7 +87,7 @@ Node::Type Node::getType() const
 
 void Node::addChild(Node* child)
 {
-    assert(child);
+    GP_ASSERT(child);
 
     if (child->_parent == this)
     {
@@ -285,7 +285,7 @@ unsigned int Node::getChildCount() const
 
 Node* Node::findNode(const char* id, bool recursive, bool exactMatch) const
 {
-    assert(id);
+    GP_ASSERT(id);
 
     // If the node has a model with a mesh skin, search the skin's hierarchy as well.
     Node* rootNode = NULL;
@@ -329,7 +329,7 @@ Node* Node::findNode(const char* id, bool recursive, bool exactMatch) const
 
 unsigned int Node::findNodes(const char* id, std::vector<Node*>& nodes, bool recursive, bool exactMatch) const
 {
-    assert(id);
+    GP_ASSERT(id);
     
     unsigned int count = 0;
 
@@ -402,11 +402,9 @@ const Matrix& Node::getWorldMatrix() const
 
         // Our world matrix was just updated, so call getWorldMatrix() on all child nodes
         // to force their resolved world matrices to be updated.
-        Node* node = getFirstChild();
-        while (node)
+        for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
         {
-            node->getWorldMatrix();
-            node = node->getNextSibling();
+            child->getWorldMatrix();
         }
     }
 
@@ -546,8 +544,6 @@ Vector3 Node::getForwardVectorView() const
     Vector3 vector;
     getWorldMatrix().getForwardVector(&vector);
     getViewMatrix().transformVector(&vector);
-    //getForwardVector(&vector);
-    //getWorldViewMatrix().transformVector(&vector);
     return vector;
 }
 
@@ -601,12 +597,21 @@ void Node::transformChanged()
     _dirtyBits |= NODE_DIRTY_WORLD | NODE_DIRTY_BOUNDS;
 
     // Notify our children that their transform has also changed (since transforms are inherited).
-    Joint* rootJoint = NULL;
-    Node* n = getFirstChild();
-    while (n)
+    for (Node* n = getFirstChild(); n != NULL; n = n->getNextSibling())
     {
-        n->transformChanged();
-        n = n->getNextSibling();
+        if (Transform::isTransformChangedSuspended())
+        {
+            // If the DIRTY_NOTIFY bit is not set
+            if (!n->isDirty(Transform::DIRTY_NOTIFY))
+            {
+                n->transformChanged();
+                suspendTransformChange(n);
+            }
+        }
+        else
+        {
+            n->transformChanged();
+        }
     }
 
     Transform::transformChanged();
@@ -636,10 +641,10 @@ Animation* Node::getAnimation(const char* id) const
         MeshSkin* skin = model->getSkin();
         if (skin)
         {
-            Joint* rootJoint = skin->getRootJoint();
-            if (rootJoint)
+            Node* rootNode = skin->_rootNode;
+            if (rootNode)
             {
-                animation = rootJoint->getAnimation(id);
+                animation = rootNode->getAnimation(id);
                 if (animation)
                     return animation;
             }
@@ -654,6 +659,7 @@ Animation* Node::getAnimation(const char* id) const
             std::vector<MaterialParameter*>::iterator itr = material->_parameters.begin();
             for (; itr != material->_parameters.end(); itr++)
             {
+                GP_ASSERT(*itr);
                 animation = ((MaterialParameter*)(*itr))->getAnimation(id);
                 if (animation)
                     return animation;
@@ -671,15 +677,11 @@ Animation* Node::getAnimation(const char* id) const
     }
 
     // Look through this node's children for an animation with the specified ID.
-    unsigned int childCount = this->getChildCount();
-    Node* child = this->getFirstChild();
-    for (unsigned int i = 0; i < childCount; i++)
+    for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
     {
         animation = child->getAnimation(id);
         if (animation)
             return animation;
-
-        child = child->getNextSibling();
     }
     
     return NULL;
@@ -822,6 +824,7 @@ const BoundingSphere& Node::getBoundingSphere() const
                 // since joint parent nodes that are not in the matrix pallette do not need to
                 // be considered as directly transforming vertices on the GPU (they can instead
                 // be applied directly to the bounding volume transformation below).
+                GP_ASSERT(_model->getSkin()->getRootJoint());
                 Node* jointParent = _model->getSkin()->getRootJoint()->getParent();
                 if (jointParent)
                 {
@@ -879,6 +882,7 @@ Node* Node::cloneSingleNode(NodeCloneContext &context) const
 Node* Node::cloneRecursive(NodeCloneContext &context) const
 {
     Node* copy = cloneSingleNode(context);
+    GP_ASSERT(copy);
 
     Node* lastChild = NULL;
     for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
@@ -889,6 +893,7 @@ Node* Node::cloneRecursive(NodeCloneContext &context) const
     for (Node* child = lastChild; child != NULL; child = child->getPreviousSibling())
     {
         Node* childCopy = child->cloneRecursive(context);
+        GP_ASSERT(childCopy);
         copy->addChild(childCopy);
         childCopy->release();
     }
@@ -897,6 +902,7 @@ Node* Node::cloneRecursive(NodeCloneContext &context) const
 
 void Node::cloneInto(Node* node, NodeCloneContext &context) const
 {
+    GP_ASSERT(node);
     Transform::cloneInto(node, context);
 
     // TODO: Clone the rest of the node data.
@@ -1007,6 +1013,9 @@ PhysicsCollisionObject* Node::setCollisionObject(PhysicsCollisionObject::Type ty
             _collisionObject = new PhysicsCharacter(this, shape, rigidBodyParameters ? rigidBodyParameters->mass : 1.0f);
         }
         break;
+
+    case PhysicsCollisionObject::NONE:
+        break;  // Already deleted, Just don't add a new collision object back.
     }
 
     return _collisionObject;
@@ -1016,10 +1025,9 @@ PhysicsCollisionObject* Node::setCollisionObject(const char* url)
 {
     // Load the collision object properties from file.
     Properties* properties = Properties::create(url);
-    assert(properties);
     if (properties == NULL)
     {
-        WARN_VARG("Failed to load collision object file: %s", url);
+        GP_ERROR("Failed to load collision object file: %s", url);
         return NULL;
     }
 
@@ -1034,27 +1042,38 @@ PhysicsCollisionObject* Node::setCollisionObject(Properties* properties)
     SAFE_DELETE(_collisionObject);
 
     // Check if the properties is valid.
-    if (!properties || 
-        !(strcmp(properties->getNamespace(), "character") == 0 || 
-        strcmp(properties->getNamespace(), "ghostObject") == 0 || 
-        strcmp(properties->getNamespace(), "rigidBody") == 0))
+    if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
     {
-        WARN("Failed to load collision object from properties object: must be non-null object and have namespace equal to \'character\', \'ghostObject\', or \'rigidBody\'.");
+        GP_ERROR("Failed to load collision object from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
         return NULL;
     }
 
-    if (strcmp(properties->getNamespace(), "character") == 0)
+    if (const char* type = properties->getString("type"))
     {
-        _collisionObject = PhysicsCharacter::create(this, properties);
-    }
-    else if (strcmp(properties->getNamespace(), "ghostObject") == 0)
-    {
-        _collisionObject = PhysicsGhostObject::create(this, properties);
+        if (strcmp(type, "CHARACTER") == 0)
+        {
+            _collisionObject = PhysicsCharacter::create(this, properties);
+        }
+        else if (strcmp(type, "GHOST_OBJECT") == 0)
+        {
+            _collisionObject = PhysicsGhostObject::create(this, properties);
+        }
+        else if (strcmp(type, "RIGID_BODY") == 0)
+        {
+            _collisionObject = PhysicsRigidBody::create(this, properties);
+        }
+        else
+        {
+            GP_ERROR("Unsupported collision object type '%s'.", type);
+            return NULL;
+        }
     }
-    else if (strcmp(properties->getNamespace(), "rigidBody") == 0)
+    else
     {
-        _collisionObject = PhysicsRigidBody::create(this, properties);
+        GP_ERROR("Failed to load collision object from properties object; required attribute 'type' is missing.");
+        return NULL;
     }
+    
     return _collisionObject;
 }
 
@@ -1070,23 +1089,33 @@ NodeCloneContext::~NodeCloneContext()
 
 Animation* NodeCloneContext::findClonedAnimation(const Animation* animation)
 {
+    GP_ASSERT(animation);
+
     AnimationMap::iterator it = _clonedAnimations.find(animation);
     return it != _clonedAnimations.end() ? it->second : NULL;
 }
 
 void NodeCloneContext::registerClonedAnimation(const Animation* original, Animation* clone)
 {
+    GP_ASSERT(original);
+    GP_ASSERT(clone);
+
     _clonedAnimations[original] = clone;
 }
 
 Node* NodeCloneContext::findClonedNode(const Node* node)
 {
+    GP_ASSERT(node);
+
     NodeMap::iterator it = _clonedNodes.find(node);
     return it != _clonedNodes.end() ? it->second : NULL;
 }
 
 void NodeCloneContext::registerClonedNode(const Node* original, Node* clone)
 {
+    GP_ASSERT(original);
+    GP_ASSERT(clone);
+
     _clonedNodes[original] = clone;
 }
 

+ 2 - 1
gameplay/src/Node.h

@@ -517,7 +517,8 @@ public:
      *        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);
+    PhysicsCollisionObject* setCollisionObject(PhysicsCollisionObject::Type type, const PhysicsCollisionShape::Definition& shape = PhysicsCollisionShape::box(), 
+                                               PhysicsRigidBody::Parameters* rigidBodyParameters = NULL);
 
     /**
      * Sets the physics collision object for this node using the data from the Properties object defined at the specified URL, 

+ 90 - 46
gameplay/src/ParticleEmitter.cpp

@@ -30,15 +30,13 @@ ParticleEmitter::ParticleEmitter(SpriteBatch* batch, unsigned int particleCountM
     _node(NULL), _orbitPosition(false), _orbitVelocity(false), _orbitAcceleration(false),
     _timePerEmission(PARTICLE_EMISSION_RATE_TIME_INTERVAL), _timeLast(0L), _timeRunning(0L)
 {
+    GP_ASSERT(particleCountMax);
     _particles = new Particle[particleCountMax];
 
+    GP_ASSERT(_spriteBatch);
+    GP_ASSERT(_spriteBatch->getStateBlock());
     _spriteBatch->getStateBlock()->setDepthWrite(false);
     _spriteBatch->getStateBlock()->setDepthTest(true);
-    /*
-    _spriteBatch->getStateBlock()->setBlend(true);
-    _spriteBatch->getStateBlock()->setBlendSrc(RenderState::BLEND_SRC_ALPHA);
-    _spriteBatch->getStateBlock()->setBlendDst(RenderState::BLEND_ONE_MINUS_SRC_ALPHA);
-    */
 }
 
 ParticleEmitter::~ParticleEmitter()
@@ -50,24 +48,24 @@ ParticleEmitter::~ParticleEmitter()
 
 ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlending textureBlending, unsigned int particleCountMax)
 {
-    assert(textureFile);
-
     Texture* texture = NULL;
-    texture = Texture::create(textureFile, true);    
+    texture = Texture::create(textureFile, false);
 
     if (!texture)
     {
-        LOG_ERROR_VARG("Error creating ParticleEmitter: Could not read texture file: %s", textureFile);
+        GP_ERROR("Failed to create texture for particle emitter.");
         return NULL;
     }
+    GP_ASSERT(texture->getWidth());
+    GP_ASSERT(texture->getHeight());
 
     // Use default SpriteBatch material.
     SpriteBatch* batch =  SpriteBatch::create(texture, NULL, particleCountMax);
     texture->release(); // batch owns the texture.
-    assert(batch);
+    GP_ASSERT(batch);
 
     ParticleEmitter* emitter = new ParticleEmitter(batch, particleCountMax);
-    assert(emitter);
+    GP_ASSERT(emitter);
 
     // By default assume only one frame which uses the entire texture.
     emitter->setTextureBlending(textureBlending);
@@ -84,12 +82,10 @@ ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlendin
 
 ParticleEmitter* ParticleEmitter::create(const char* url)
 {
-    assert(url);
-
     Properties* properties = Properties::create(url);
     if (!properties)
     {
-        LOG_ERROR_VARG("Error loading ParticleEmitter: Could not load file: %s", url);
+        GP_ERROR("Failed to create particle emitter from file.");
         return NULL;
     }
 
@@ -103,23 +99,23 @@ ParticleEmitter* ParticleEmitter::create(Properties* properties)
 {
     if (!properties || strcmp(properties->getNamespace(), "particle") != 0)
     {
-        LOG_ERROR("Error loading ParticleEmitter: No 'particle' namespace found");
+        GP_ERROR("Properties object must be non-null and have namespace equal to 'particle'.");
         return NULL;
     }
 
     Properties* sprite = properties->getNextNamespace();
     if (!sprite || strcmp(sprite->getNamespace(), "sprite") != 0)
     {
-        LOG_ERROR("Error loading ParticleEmitter: No 'sprite' namespace found");
+        GP_ERROR("Failed to load particle emitter: required namespace 'sprite' is missing.");
         return NULL;
     }
 
     // Load sprite properties.
     // Path to image file is required.
     const char* texturePath = sprite->getString("path");
-    if (strlen(texturePath) == 0)
+    if (!texturePath || strlen(texturePath) == 0)
     {
-        LOG_ERROR_VARG("Error loading ParticleEmitter: No texture path specified: %s", texturePath);
+        GP_ERROR("Failed to load particle emitter: required image file path ('path') is missing.");
         return NULL;
     }
 
@@ -148,7 +144,6 @@ ParticleEmitter* ParticleEmitter::create(Properties* properties)
     }
 
     bool ellipsoid = properties->getBool("ellipsoid");
-
     float sizeStartMin = properties->getFloat("sizeStartMin");
     float sizeStartMax = properties->getFloat("sizeStartMax");
     float sizeEndMin = properties->getFloat("sizeEndMin");
@@ -191,6 +186,11 @@ ParticleEmitter* ParticleEmitter::create(Properties* properties)
 
     // Apply all properties to a newly created ParticleEmitter.
     ParticleEmitter* emitter = ParticleEmitter::create(texturePath, textureBlending, particleCountMax);
+    if (!emitter)
+    {
+        GP_ERROR("Failed to create particle emitter.");
+        return NULL;
+    }
     emitter->setEmissionRate(emissionRate);
     emitter->setEllipsoid(ellipsoid);
     emitter->setSize(sizeStartMin, sizeStartMax, sizeEndMin, sizeEndMax);
@@ -220,6 +220,7 @@ unsigned int ParticleEmitter::getEmissionRate() const
 
 void ParticleEmitter::setEmissionRate(unsigned int rate)
 {
+    GP_ASSERT(rate);
     _emissionRate = rate;
     _timePerEmission = 1000.0f / (float)_emissionRate;
 }
@@ -248,6 +249,7 @@ bool ParticleEmitter::isActive() const
     if (!_node)
         return false;
 
+    GP_ASSERT(_particles);
     bool active = false;
     for (unsigned int i = 0; i < _particleCount; i++)
     {
@@ -263,6 +265,9 @@ bool ParticleEmitter::isActive() const
 
 void ParticleEmitter::emit(unsigned int particleCount)
 {
+    GP_ASSERT(_node);
+    GP_ASSERT(_particles);
+
     // Limit particleCount so as not to go over _particleCountMax.
     if (particleCount + _particleCount > _particleCountMax)
     {
@@ -282,6 +287,7 @@ void ParticleEmitter::emit(unsigned int particleCount)
     for (unsigned int i = 0; i < particleCount; i++)
     {
         Particle* p = &_particles[_particleCount];
+        p->_visible = true;
 
         generateColor(_colorStart, _colorStartVar, &p->_colorStart);
         generateColor(_colorEnd, _colorEndVar, &p->_colorEnd);
@@ -517,6 +523,9 @@ const Vector3& ParticleEmitter::getRotationAxisVariance() const
 
 void ParticleEmitter::setTextureBlending(TextureBlending textureBlending)
 {
+    GP_ASSERT(_spriteBatch);
+    GP_ASSERT(_spriteBatch->getStateBlock());
+
     switch (textureBlending)
     {
         case BLEND_OPAQUE:
@@ -537,6 +546,9 @@ void ParticleEmitter::setTextureBlending(TextureBlending textureBlending)
             _spriteBatch->getStateBlock()->setBlendSrc(RenderState::BLEND_ZERO);
             _spriteBatch->getStateBlock()->setBlendDst(RenderState::BLEND_SRC_COLOR);
             break;
+        default:
+            GP_ERROR("Unsupported texture blending mode (%d).", textureBlending);
+            break;
     }
 }
 
@@ -584,6 +596,9 @@ long ParticleEmitter::getSpriteFrameDuration() const
 
 void ParticleEmitter::setSpriteTexCoords(unsigned int frameCount, float* texCoords)
 {
+    GP_ASSERT(frameCount);
+    GP_ASSERT(texCoords);
+
     _spriteFrameCount = frameCount;
     _spritePercentPerFrame = 1.0f / (float)frameCount;
 
@@ -594,34 +609,30 @@ void ParticleEmitter::setSpriteTexCoords(unsigned int frameCount, float* texCoor
 
 void ParticleEmitter::setSpriteFrameCoords(unsigned int frameCount, Rectangle* frameCoords)
 {
+    GP_ASSERT(frameCount);
+    GP_ASSERT(frameCoords);
+
     _spriteFrameCount = frameCount;
     _spritePercentPerFrame = 1.0f / (float)frameCount;
 
-    float* texCoords = new float[frameCount * 4];
+    SAFE_DELETE_ARRAY(_spriteTextureCoords);
+    _spriteTextureCoords = new float[frameCount * 4];
 
     // Pre-compute texture coordinates from rects.
     for (unsigned int i = 0; i < frameCount; i++)
     {
-        float u1 = _spriteTextureWidthRatio * frameCoords[i].x;
-        float v1 = 1.0f - _spriteTextureHeightRatio * frameCoords[i].y;
-        float u2 = u1 + _spriteTextureWidthRatio * frameCoords[i].width;
-        float v2 = v1 - _spriteTextureHeightRatio * frameCoords[i].height;
-
-        texCoords[i*4] = u1;
-        texCoords[i*4 + 1] = v1;
-        texCoords[i*4 + 2] = u2;
-        texCoords[i*4 + 3] = v2;
+        _spriteTextureCoords[i*4] = _spriteTextureWidthRatio * frameCoords[i].x;
+        _spriteTextureCoords[i*4 + 1] = 1.0f - _spriteTextureHeightRatio * frameCoords[i].y;
+        _spriteTextureCoords[i*4 + 2] = _spriteTextureCoords[i*4] + _spriteTextureWidthRatio * frameCoords[i].width;
+        _spriteTextureCoords[i*4 + 3] = _spriteTextureCoords[i*4 + 1] - _spriteTextureHeightRatio * frameCoords[i].height;
     }
-
-    SAFE_DELETE_ARRAY(_spriteTextureCoords);
-    _spriteTextureCoords = new float[frameCount * 4];
-    memcpy(_spriteTextureCoords, texCoords, frameCount * 4 * sizeof(float));
-
-    SAFE_DELETE_ARRAY(texCoords);
 }
 
 void ParticleEmitter::setSpriteFrameCoords(unsigned int frameCount, int width, int height)
 {
+    GP_ASSERT(width);
+    GP_ASSERT(height);
+
     int x;
     int y;
     Rectangle* frameCoords = new Rectangle[frameCount];
@@ -695,6 +706,8 @@ float ParticleEmitter::generateScalar(float min, float max)
 
 void ParticleEmitter::generateVectorInRect(const Vector3& base, const Vector3& variance, Vector3* dst)
 {
+    GP_ASSERT(dst);
+
     // Scale each component of the variance vector by a random float
     // between -1 and 1, then add this to the corresponding base component.
     dst->x = base.x + variance.x * MATH_RANDOM_MINUS1_1();
@@ -704,6 +717,8 @@ void ParticleEmitter::generateVectorInRect(const Vector3& base, const Vector3& v
 
 void ParticleEmitter::generateVectorInEllipsoid(const Vector3& center, const Vector3& scale, Vector3* dst)
 {
+    GP_ASSERT(dst);
+
     // Generate a point within a unit cube, then reject if the point is not in a unit sphere.
     do
     {
@@ -735,6 +750,8 @@ void ParticleEmitter::generateVector(const Vector3& base, const Vector3& varianc
 
 void ParticleEmitter::generateColor(const Vector4& base, const Vector4& variance, Vector4* dst)
 {
+    GP_ASSERT(dst);
+
     // Scale each component of the variance color by a random float
     // between -1 and 1, then add this to the corresponding base component.
     dst->x = base.x + variance.x * MATH_RANDOM_MINUS1_1();
@@ -745,6 +762,8 @@ void ParticleEmitter::generateColor(const Vector4& base, const Vector4& variance
 
 ParticleEmitter::TextureBlending ParticleEmitter::getTextureBlendingFromString(const char* str)
 {
+    GP_ASSERT(str);
+
     if (strcmp(str, "BLEND_OPAQUE") == 0 || strcmp(str, "OPAQUE") == 0)
     {
         return BLEND_OPAQUE;
@@ -761,8 +780,10 @@ ParticleEmitter::TextureBlending ParticleEmitter::getTextureBlendingFromString(c
     {
         return BLEND_MULTIPLIED;
     }
-
-    return BLEND_TRANSPARENT;
+    else
+    {
+        return BLEND_TRANSPARENT;
+    }
 }
 
 
@@ -774,7 +795,7 @@ void ParticleEmitter::update(long elapsedTime)
     }
 
     // Calculate the time passed since last update.
-    float elapsedSecs = (float)elapsedTime / 1000.0f;
+    float elapsedSecs = (float)elapsedTime * 0.001f;
 
     if (_started && _emissionRate)
     {
@@ -782,17 +803,25 @@ void ParticleEmitter::update(long elapsedTime)
         _timeRunning += elapsedTime;
 
         // How many particles should we emit this frame?
+        GP_ASSERT(_timePerEmission);
         unsigned int emitCount = _timeRunning / _timePerEmission;
             
-        if ((int)_timePerEmission > 0)
+        if (emitCount)
         {
-            _timeRunning %= (int)_timePerEmission;
-        }
+            if ((int)_timePerEmission > 0)
+            {
+                _timeRunning %= (int)_timePerEmission;
+            }
 
-        emit(emitCount);
+            emit(emitCount);
+        }
     }
 
+    GP_ASSERT(_node && _node->getScene() && _node->getScene()->getActiveCamera());
+    const Frustum& frustum = _node->getScene()->getActiveCamera()->getFrustum();
+
     // Now update all currently living particles.
+    GP_ASSERT(_particles);
     for (unsigned int particlesIndex = 0; particlesIndex < _particleCount; ++particlesIndex)
     {
         Particle* p = &_particles[particlesIndex];
@@ -817,6 +846,12 @@ void ParticleEmitter::update(long elapsedTime)
             p->_position.y += p->_velocity.y * elapsedSecs;
             p->_position.z += p->_velocity.z * elapsedSecs;
 
+            if (!frustum.intersects(p->_position))
+            {
+                p->_visible = false;
+                continue;
+            }
+
             p->_angle += p->_rotationPerParticleSpeed * elapsedSecs;
 
             // Simple linear interpolation of color and size.
@@ -886,6 +921,10 @@ void ParticleEmitter::draw()
 
     if (_particleCount > 0)
     {
+        GP_ASSERT(_spriteBatch);
+        GP_ASSERT(_particles);
+        GP_ASSERT(_spriteTextureCoords);
+
         // Set our node's view projection matrix to this emitter's effect.
         if (_node)
         {
@@ -896,10 +935,12 @@ void ParticleEmitter::draw()
         _spriteBatch->begin();
 
         // 2D Rotation.
-        Vector2 pivot(0.5f, 0.5f);
+        static const Vector2 pivot(0.5f, 0.5f);
 
         // 3D Rotation so that particles always face the camera.
+        GP_ASSERT(_node && _node->getScene() && _node->getScene()->getActiveCamera() && _node->getScene()->getActiveCamera()->getNode());
         const Matrix& cameraWorldMatrix = _node->getScene()->getActiveCamera()->getNode()->getWorldMatrix();
+
         Vector3 right;
         cameraWorldMatrix.getRightVector(&right);
         Vector3 up;
@@ -909,9 +950,12 @@ void ParticleEmitter::draw()
         {
             Particle* p = &_particles[i];
 
-            _spriteBatch->draw(p->_position, right, up, p->_size, p->_size,
-                               _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3],
-                               p->_color, pivot, p->_angle);
+            if (p->_visible)
+            {
+                _spriteBatch->draw(p->_position, right, up, p->_size, p->_size,
+                                   _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3],
+                                   p->_color, pivot, p->_angle);
+            }
         }
 
         // Render.

+ 15 - 19
gameplay/src/ParticleEmitter.h

@@ -615,6 +615,16 @@ public:
      */
     void draw();
 
+    /**
+     * Gets a TextureBlending enum from a corresponding string.
+     */
+    static TextureBlending getTextureBlendingFromString(const char* src);
+
+    /**
+     * Sets a TextureBlending enum from a corresponding string.
+     */
+    void setTextureBlending(TextureBlending blending);
+
 private:
 
     /**
@@ -632,37 +642,22 @@ private:
      */
     void setNode(Node* node);
 
-    /**
-     * Generates a scalar within the range defined by min and max.
-     */
+    // Generates a scalar within the range defined by min and max.
     float generateScalar(float min, float max);
 
     long generateScalar(long min, long max);
 
-    /**
-     * Generates a vector within the domain defined by a base vector and its variance.
-     */
+    // Generates a vector within the domain defined by a base vector and its variance.
     void generateVectorInRect(const Vector3& base, const Vector3& variance, Vector3* dst);
 
-    /**
-     * Generates a vector within the ellipsoidal domain defined by a center point and scale vector.
-     */
+    // Generates a vector within the ellipsoidal domain defined by a center point and scale vector.
     void generateVectorInEllipsoid(const Vector3& center, const Vector3& scale, Vector3* dst);
 
     void generateVector(const Vector3& base, const Vector3& variance, Vector3* dst, bool ellipsoid);
 
-    /**
-     * Generates a color within the domain defined by a base vector and its variance.
-     */
+    // Generates a color within the domain defined by a base vector and its variance.
     void generateColor(const Vector4& base, const Vector4& variance, Vector4* dst);
 
-    /**
-     * Gets a BlendMode enum from a corresponding string.
-     */
-    static TextureBlending getTextureBlendingFromString(const char* src);
-
-    void setTextureBlending(TextureBlending blending);
-
     /**
      * Defines the data for a single particle in the system.
      */
@@ -687,6 +682,7 @@ private:
         float _size;
         unsigned int _frame;
         float _timeOnCurrentFrame;
+        bool _visible;
     };
 
     unsigned int _particleCountMax;

+ 7 - 6
gameplay/src/Pass.cpp

@@ -10,8 +10,6 @@ namespace gameplay
 Pass::Pass(const char* id, Technique* technique, Effect* effect) :
     _id(id ? id : ""), _technique(technique), _effect(effect), _vaBinding(NULL)
 {
-    assert(technique);
-
     RenderState::_parent = _technique;
 }
 
@@ -23,15 +21,15 @@ Pass::~Pass()
 
 Pass* Pass::create(const char* id, Technique* technique, const char* vshPath, const char* fshPath, const char* defines)
 {
-    // Attempt to create/load the effect
+    // Attempt to create/load the effect.
     Effect* effect = Effect::createFromFile(vshPath, fshPath, defines);
-    assert(effect);
     if (effect == NULL)
     {
+        GP_ERROR("Failed to create effect for pass.");
         return NULL;
     }
 
-    // Return the new pass
+    // Return the new pass.
     return new Pass(id, technique, effect);
 }
 
@@ -58,7 +56,9 @@ void Pass::setVertexAttributeBinding(VertexAttributeBinding* binding)
 
 void Pass::bind()
 {
-    // Bind our effect
+    GP_ASSERT(_effect);
+
+    // Bind our effect.
     _effect->bind();
 
     // Bind our render state
@@ -83,6 +83,7 @@ void Pass::unbind()
 Pass* Pass::clone(Technique* technique, NodeCloneContext &context) const
 {
     Effect* effect = getEffect();
+    GP_ASSERT(effect);
     effect->addRef();
     Pass* pass = new Pass(getId(), technique, effect);
     RenderState::cloneInto(pass, context);

+ 86 - 37
gameplay/src/PhysicsCharacter.cpp

@@ -31,7 +31,8 @@ public:
     btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
     {
         PhysicsCollisionObject* object = reinterpret_cast<PhysicsCollisionObject*>(convexResult.m_hitCollisionObject->getUserPointer());
-
+        
+        GP_ASSERT(object);
         if (object == _me || object->getType() == PhysicsCollisionObject::GHOST_OBJECT)
             return 1.0f;
 
@@ -71,26 +72,41 @@ PhysicsCharacter::PhysicsCharacter(Node* node, const PhysicsCollisionShape::Defi
 {
     setMaxSlopeAngle(45.0f);
 
-    // Set the collision flags on the ghost object to indicate it's a character
+    // Set the collision flags on the ghost object to indicate it's a character.
+    GP_ASSERT(_ghostObject);
     _ghostObject->setCollisionFlags(_ghostObject->getCollisionFlags() | btCollisionObject::CF_CHARACTER_OBJECT | btCollisionObject::CF_NO_CONTACT_RESPONSE);
 
-    // Register ourselves as an action on the physics world so we are called back during physics ticks
+    // Register ourselves as an action on the physics world so we are called back during physics ticks.
+    GP_ASSERT(Game::getInstance()->getPhysicsController() && Game::getInstance()->getPhysicsController()->_world);
     Game::getInstance()->getPhysicsController()->_world->addAction(this);
 }
 
 PhysicsCharacter::~PhysicsCharacter()
 {
-    // Unregister ourselves as action from world
+    // Unregister ourselves as action from world.
+    GP_ASSERT(Game::getInstance()->getPhysicsController() && Game::getInstance()->getPhysicsController()->_world);
     Game::getInstance()->getPhysicsController()->_world->removeAction(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))
+    if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
     {
-        WARN("Failed to load physics character from properties object: must be non-null object and have namespace equal to \'character\'.");
+        GP_ERROR("Failed to load physics character from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
+        return NULL;
+    }
+
+    // Check that the type is specified and correct.
+    const char* type = properties->getString("type");
+    if (!type)
+    {
+        GP_ERROR("Failed to load physics character from properties object; required attribute 'type' is missing.");
+        return NULL;
+    }
+    if (strcmp(type, "CHARACTER") != 0)
+    {
+        GP_ERROR("Failed to load physics character from properties object; attribute 'type' must be equal to 'CHARACTER'.");
         return NULL;
     }
 
@@ -98,7 +114,7 @@ PhysicsCharacter* PhysicsCharacter::create(Node* node, Properties* properties)
     PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
     if (shape == NULL)
     {
-        WARN("Failed to create collision shape during physics character creation.");
+        GP_ERROR("Failed to create collision shape during physics character creation.");
         return NULL;
     }
 
@@ -122,6 +138,10 @@ PhysicsCharacter* PhysicsCharacter::create(Node* node, Properties* properties)
         {
             maxSlopeAngle = properties->getFloat();
         }
+        else
+        {
+            // Ignore this case (the attributes for the character's collision shape would end up here).
+        }
     }
 
     // Create the physics character.
@@ -181,21 +201,25 @@ void PhysicsCharacter::setVelocity(const Vector3& velocity)
 
 void PhysicsCharacter::rotate(const Vector3& axis, float angle)
 {
+    GP_ASSERT(_node);
     _node->rotate(axis, angle);
 }
 
 void PhysicsCharacter::rotate(const Quaternion& rotation)
 {
+    GP_ASSERT(_node);
     _node->rotate(rotation);
 }
 
 void PhysicsCharacter::setRotation(const Vector3& axis, float angle)
 {
+    GP_ASSERT(_node);
     _node->setRotation(axis, angle);
 }
 
 void PhysicsCharacter::setRotation(const Quaternion& rotation)
 {
+    GP_ASSERT(_node);
     _node->setRotation(rotation);
 }
 
@@ -228,6 +252,7 @@ void PhysicsCharacter::jump(float height)
     //  v0 == initial velocity (zero for jumping)
     //  a == acceleration (inverse gravity)
     //  s == linear displacement (height)
+    GP_ASSERT(Game::getInstance()->getPhysicsController());
     Vector3 jumpVelocity = -Game::getInstance()->getPhysicsController()->getGravity() * height * 2.0f;
     jumpVelocity.set(
         jumpVelocity.x == 0 ? 0 : std::sqrt(jumpVelocity.x),
@@ -238,37 +263,39 @@ void PhysicsCharacter::jump(float height)
 
 void PhysicsCharacter::updateCurrentVelocity()
 {
+    GP_ASSERT(_node);
+    
     Vector3 temp;
     btScalar velocity2 = 0;
 
-    // Reset velocity vector
+    // Reset velocity vector.
     _normalizedVelocity.setValue(0, 0, 0);
 
-    // Add movement velocity contribution
+    // Add movement velocity contribution.
     if (!_moveVelocity.isZero())
     {
         _normalizedVelocity = _moveVelocity;
         velocity2 = _moveVelocity.length2();
     }
 
-    // Add forward velocity contribution
+    // Add forward velocity contribution.
     if (_forwardVelocity != 0)
     {
         _node->getWorldMatrix().getForwardVector(&temp);
         temp.normalize();
         temp *= -_forwardVelocity;
         _normalizedVelocity += btVector3(temp.x, temp.y, temp.z);
-        velocity2 = std::max(std::abs(velocity2), std::abs(_forwardVelocity*_forwardVelocity));
+        velocity2 = std::max(std::fabs(velocity2), std::fabs(_forwardVelocity*_forwardVelocity));
     }
 
-    // Add right velocity contribution
+    // Add right velocity contribution.
     if (_rightVelocity != 0)
     {
         _node->getWorldMatrix().getRightVector(&temp);
         temp.normalize();
         temp *= _rightVelocity;
         _normalizedVelocity += btVector3(temp.x, temp.y, temp.z);
-        velocity2 = std::max(std::abs(velocity2), std::abs(_rightVelocity*_rightVelocity));
+        velocity2 = std::max(std::fabs(velocity2), std::fabs(_rightVelocity*_rightVelocity));
     }
 
     // Compute final combined movement vectors
@@ -285,6 +312,9 @@ void PhysicsCharacter::updateCurrentVelocity()
 
 void PhysicsCharacter::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep)
 {
+    GP_ASSERT(_ghostObject);
+    GP_ASSERT(_node);
+
     // First check for existing collisions and attempt to respond/fix them.
     // Basically we are trying to move the character so that it does not penetrate
     // any other collision objects in the scene. We need to do this to ensure that
@@ -301,28 +331,28 @@ void PhysicsCharacter::updateAction(btCollisionWorld* collisionWorld, btScalar d
 
             if (++stepCount > 4)
             {
-                // Most likely we are wedged between a number of different collision objects
+                // Most likely we are wedged between a number of different collision objects.
                 break;
             }
         }
     }
 
-    // Update current and target world positions
+    // Update current and target world positions.
     btVector3 startPosition = _ghostObject->getWorldTransform().getOrigin();
     _currentPosition = startPosition;
 
-    // Process movement in the up direction
+    // Process movement in the up direction.
     if (_physicsEnabled)
         stepUp(collisionWorld, deltaTimeStep);
-
-    // Process horizontal movement
+    
+    // Process horizontal movement.
     stepForwardAndStrafe(collisionWorld, deltaTimeStep);
 
-    // Process movement in the down direction
+    // Process movement in the down direction.
     if (_physicsEnabled)
         stepDown(collisionWorld, deltaTimeStep);
 
-    // Set new position
+    // Set new position.
     btVector3 translation = _currentPosition - startPosition;
     _node->translate(translation.x(), translation.y(), translation.z());
 }
@@ -383,6 +413,10 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
 
     int maxIter = 10;
 
+    GP_ASSERT(_ghostObject && _ghostObject->getBroadphaseHandle());
+    GP_ASSERT(_collisionShape);
+    GP_ASSERT(collisionWorld);
+    GP_ASSERT(Game::getInstance()->getPhysicsController());
     while (fraction > btScalar(0.01) && maxIter-- > 0)
     {
         start.setOrigin(_currentPosition);
@@ -402,9 +436,11 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
         {
             Vector3 normal(callback.m_hitNormalWorld.x(), callback.m_hitNormalWorld.y(), callback.m_hitNormalWorld.z());
             PhysicsCollisionObject* o = Game::getInstance()->getPhysicsController()->getCollisionObject(callback.m_hitCollisionObject);
+            GP_ASSERT(o);
             if (o->getType() == PhysicsCollisionObject::RIGID_BODY && o->isDynamic())
             {
                 PhysicsRigidBody* rb = static_cast<PhysicsRigidBody*>(o);
+                GP_ASSERT(rb);
                 normal.normalize();
                 rb->applyImpulse(_mass * -normal * velocity.length());
             }
@@ -436,6 +472,11 @@ void PhysicsCharacter::stepForwardAndStrafe(btCollisionWorld* collisionWorld, fl
 
 void PhysicsCharacter::stepDown(btCollisionWorld* collisionWorld, btScalar time)
 {
+    GP_ASSERT(Game::getInstance()->getPhysicsController() && Game::getInstance()->getPhysicsController()->_world);
+    GP_ASSERT(_ghostObject && _ghostObject->getBroadphaseHandle());
+    GP_ASSERT(_collisionShape);
+    GP_ASSERT(collisionWorld);
+
     // Contribute gravity to vertical velocity.
     btVector3 gravity = Game::getInstance()->getPhysicsController()->_world->getGravity();
     _verticalVelocity += (gravity * time);
@@ -444,7 +485,7 @@ void PhysicsCharacter::stepDown(btCollisionWorld* collisionWorld, btScalar time)
     btVector3 targetPosition = _currentPosition + (_verticalVelocity * time);
     targetPosition -= btVector3(0, _stepHeight, 0);
 
-    // Perform a convex sweep test between current and target position
+    // Perform a convex sweep test between current and target position.
     btTransform start;
     btTransform end;
     start.setIdentity();
@@ -485,9 +526,11 @@ void PhysicsCharacter::stepDown(btCollisionWorld* collisionWorld, btScalar time)
             else
             {
                 PhysicsCollisionObject* o = Game::getInstance()->getPhysicsController()->getCollisionObject(callback.m_hitCollisionObject);
+                GP_ASSERT(o);
                 if (o->getType() == PhysicsCollisionObject::RIGID_BODY && o->isDynamic())
                 {
                     PhysicsRigidBody* rb = static_cast<PhysicsRigidBody*>(o);
+                    GP_ASSERT(rb);
                     normal.normalize();
                     rb->applyImpulse(_mass * -normal * sqrt(BV(normal).dot(_verticalVelocity)));
                 }
@@ -508,14 +551,14 @@ void PhysicsCharacter::stepDown(btCollisionWorld* collisionWorld, btScalar time)
     // want to keep increasing the vertical velocity until the character 
     // randomly drops through the floor when it can finally move due to its
     // vertical velocity having such a great magnitude.
-    if (!_verticalVelocity.isZero())
+    if (!_verticalVelocity.isZero() && time > 0.0f)
         _verticalVelocity = ((targetPosition + btVector3(0.0, _stepHeight, 0.0)) - _currentPosition) / time;
 
     _currentPosition = targetPosition;
 }
 
 /*
- * Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal'
+ * Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal'.
  */
 btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal)
 {
@@ -523,7 +566,7 @@ btVector3 computeReflectionDirection(const btVector3& direction, const btVector3
 }
 
 /*
- * Returns the portion of 'direction' that is parallel to 'normal'
+ * Returns the portion of 'direction' that is parallel to 'normal'.
  */
 btVector3 parallelComponent(const btVector3& direction, const btVector3& normal)
 {
@@ -532,7 +575,7 @@ btVector3 parallelComponent(const btVector3& direction, const btVector3& normal)
 }
 
 /*
- * Returns the portion of 'direction' that is perpindicular to 'normal'
+ * Returns the portion of 'direction' that is perpindicular to 'normal'.
  */
 btVector3 perpindicularComponent(const btVector3& direction, const btVector3& normal)
 {
@@ -566,25 +609,30 @@ void PhysicsCharacter::updateTargetPositionFromCollision(btVector3& targetPositi
 
 bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
 {
-    bool collision = false;
+    GP_ASSERT(_node);
+    GP_ASSERT(_ghostObject);
+    GP_ASSERT(world && world->getDispatcher());
+    GP_ASSERT(Game::getInstance()->getPhysicsController());
 
+    bool collision = false;
     btOverlappingPairCache* pairCache = _ghostObject->getOverlappingPairCache();
+    GP_ASSERT(pairCache);
 
-    // Tell the world to dispatch collision events for our ghost object
+    // Tell the world to dispatch collision events for our ghost object.
     world->getDispatcher()->dispatchAllCollisionPairs(pairCache, world->getDispatchInfo(), world->getDispatcher());
 
-    // Store our current world position
+    // Store our current world position.
     Vector3 startPosition;
     _node->getWorldMatrix().getTranslation(&startPosition);
     btVector3 currentPosition = BV(startPosition);
 
-    // Handle all collisions/overlappign pairs
+    // Handle all collisions/overlappign pairs.
     btScalar maxPenetration = btScalar(0.0);
     for (int i = 0, count = pairCache->getNumOverlappingPairs(); i < count; ++i)
     {
         _manifoldArray.resize(0);
 
-        // Query contacts between this overlapping pair (store in _manifoldArray)
+        // Query contacts between this overlapping pair (store in _manifoldArray).
         btBroadphasePair* collisionPair = &pairCache->getOverlappingPairArray()[i];
         if (collisionPair->m_algorithm)
         {
@@ -594,11 +642,12 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
         for (int j = 0, manifoldCount = _manifoldArray.size(); j < manifoldCount; ++j)
         {
             btPersistentManifold* manifold = _manifoldArray[j];
+            GP_ASSERT(manifold);
 
             // Get the direction of the contact points (used to scale normal vector in the correct direction).
             btScalar directionSign = manifold->getBody0() == _ghostObject ? -1.0f : 1.0f;
 
-            // Skip ghost objects
+            // Skip ghost objects.
             PhysicsCollisionObject* object = Game::getInstance()->getPhysicsController()->getCollisionObject(
                 (btCollisionObject*)(manifold->getBody0() == _ghostObject ? manifold->getBody1() : manifold->getBody0()));
             if (!object || object->getType() == PhysicsCollisionObject::GHOST_OBJECT)
@@ -608,20 +657,20 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
             {
                 const btManifoldPoint& pt = manifold->getContactPoint(p);
 
-                // Get penetration distance for this contact point
+                // Get penetration distance for this contact point.
                 btScalar dist = pt.getDistance();
 
                 if (dist < 0.0)
                 {
-                    // A negative distance means the objects are overlapping
+                    // A negative distance means the objects are overlapping.
                     if (dist < maxPenetration)
                     {
-                        // Store collision normal for this point
+                        // Store collision normal for this point.
                         maxPenetration = dist;
                         _collisionNormal = pt.m_normalWorldOnB * directionSign;
                     }
 
-                    // Calculate new position for object, which is translated back along the collision normal
+                    // Calculate new position for object, which is translated back along the collision normal.
                     currentPosition += pt.m_normalWorldOnB * directionSign * dist * 0.2f;
                     collision = true;
                 }
@@ -629,7 +678,7 @@ bool PhysicsCharacter::fixCollision(btCollisionWorld* world)
         }
     }
 
-    // Set the new world transformation to apply to fix the collision
+    // Set the new world transformation to apply to fix the collision.
     _node->translate(Vector3(currentPosition.x(), currentPosition.y(), currentPosition.z()) - startPosition);
 
     return collision;

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff