Selaa lähdekoodia

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

Conflicts:
	gameplay/src/FileSystem.cpp
	gameplay/src/Node.cpp
	gameplay/src/PlatformAndroid.cpp
	gameplay/src/PlatformWin32.cpp
	gameplay/src/Texture.cpp
Steve Grenier 13 vuotta sitten
vanhempi
sitoutus
5db193ce1d
100 muutettua tiedostoa jossa 4320 lisäystä ja 3439 poistoa
  1. 10 1
      .gitignore
  2. 3 2
      README.md
  3. 1 0
      gameplay-encoder/gameplay-bundle.txt
  4. 1 0
      gameplay-encoder/gameplay-encoder.vcxproj
  5. 1 0
      gameplay-encoder/gameplay-encoder.vcxproj.filters
  6. 131 80
      gameplay-encoder/src/DAESceneEncoder.cpp
  7. 3 1
      gameplay-encoder/src/DAESceneEncoder.h
  8. 77 14
      gameplay-encoder/src/DAEUtil.cpp
  9. 18 0
      gameplay-encoder/src/DAEUtil.h
  10. 6 2
      gameplay-encoder/src/FileIO.cpp
  11. 2 2
      gameplay-encoder/src/GPBDecoder.cpp
  12. 1 1
      gameplay-encoder/src/GPBFile.h
  13. 4 0
      gameplay-encoder/src/Node.cpp
  14. 1 0
      gameplay-encoder/src/Scene.cpp
  15. 3 9
      gameplay-encoder/src/TTFFontEncoder.cpp
  16. 0 8
      gameplay-encoder/src/TTFFontEncoder.h
  17. 1 1
      gameplay-encoder/src/main.cpp
  18. 18 2
      gameplay-template/android/jni/template.Android.mk
  19. 2 0
      gameplay-template/gameplay-template.vcxproj
  20. BIN
      gameplay-template/res/box.gpb
  21. 15 15
      gameplay/.cproject
  22. 2 2
      gameplay/android/AndroidManifest.xml
  23. 1 1
      gameplay/android/jni/Android.mk
  24. 2 0
      gameplay/gameplay.vcxproj
  25. 6 0
      gameplay/gameplay.vcxproj.filters
  26. 12 0
      gameplay/gameplay.xcodeproj/project.pbxproj
  27. 41 39
      gameplay/src/AbsoluteLayout.cpp
  28. 50 18
      gameplay/src/Animation.cpp
  29. 6 2
      gameplay/src/Animation.h
  30. 40 14
      gameplay/src/AnimationClip.cpp
  31. 1 1
      gameplay/src/AnimationClip.h
  32. 8 11
      gameplay/src/AnimationController.cpp
  33. 0 1
      gameplay/src/AnimationController.h
  34. 146 21
      gameplay/src/AnimationTarget.cpp
  35. 25 9
      gameplay/src/AnimationTarget.h
  36. 7 4
      gameplay/src/AnimationValue.cpp
  37. 74 99
      gameplay/src/AudioBuffer.cpp
  38. 0 14
      gameplay/src/AudioBuffer.h
  39. 22 165
      gameplay/src/AudioController.cpp
  40. 3 10
      gameplay/src/AudioController.h
  41. 2 0
      gameplay/src/AudioListener.cpp
  42. 64 281
      gameplay/src/AudioSource.cpp
  43. 5 22
      gameplay/src/AudioSource.h
  44. 60 80
      gameplay/src/Base.h
  45. 7 5
      gameplay/src/BoundingBox.cpp
  46. 8 0
      gameplay/src/BoundingSphere.cpp
  47. 398 101
      gameplay/src/Bundle.cpp
  48. 31 2
      gameplay/src/Bundle.h
  49. 30 30
      gameplay/src/Button.cpp
  50. 17 24
      gameplay/src/Camera.cpp
  51. 13 18
      gameplay/src/CheckBox.cpp
  52. 1 1
      gameplay/src/CheckBox.h
  53. 336 289
      gameplay/src/Container.cpp
  54. 7 5
      gameplay/src/Container.h
  55. 991 886
      gameplay/src/Control.cpp
  56. 31 32
      gameplay/src/Control.h
  57. 0 4
      gameplay/src/Curve.cpp
  58. 5 5
      gameplay/src/Effect.cpp
  59. 45 15
      gameplay/src/FileSystem.cpp
  60. 6 2
      gameplay/src/FlowLayout.cpp
  61. 0 3
      gameplay/src/FlowLayout.h
  62. 340 253
      gameplay/src/Font.cpp
  63. 70 6
      gameplay/src/Font.h
  64. 419 282
      gameplay/src/Form.cpp
  65. 42 4
      gameplay/src/Form.h
  66. 1 1
      gameplay/src/FrameBuffer.cpp
  67. 27 23
      gameplay/src/Frustum.cpp
  68. 9 0
      gameplay/src/Frustum.h
  69. 4 2
      gameplay/src/Game.cpp
  70. 2 2
      gameplay/src/Image.cpp
  71. 2 0
      gameplay/src/Joint.cpp
  72. 70 64
      gameplay/src/Label.cpp
  73. 3 1
      gameplay/src/Label.h
  74. 54 43
      gameplay/src/Layout.cpp
  75. 23 1
      gameplay/src/Layout.h
  76. 14 14
      gameplay/src/Light.cpp
  77. 9 9
      gameplay/src/Material.cpp
  78. 6 4
      gameplay/src/Material.h
  79. 16 55
      gameplay/src/MaterialParameter.cpp
  80. 1 3
      gameplay/src/MaterialParameter.h
  81. 29 29
      gameplay/src/Matrix.cpp
  82. 4 4
      gameplay/src/MeshBatch.cpp
  83. 1 1
      gameplay/src/MeshBatch.inl
  84. 19 8
      gameplay/src/MeshSkin.cpp
  85. 4 1
      gameplay/src/MeshSkin.h
  86. 3 3
      gameplay/src/Model.cpp
  87. 64 30
      gameplay/src/Node.cpp
  88. 5 3
      gameplay/src/Node.h
  89. 37 26
      gameplay/src/ParticleEmitter.cpp
  90. 14 11
      gameplay/src/ParticleEmitter.h
  91. 2 2
      gameplay/src/Pass.cpp
  92. 3 3
      gameplay/src/PhysicsCharacter.cpp
  93. 9 9
      gameplay/src/PhysicsCollisionShape.cpp
  94. 1 1
      gameplay/src/PhysicsConstraint.cpp
  95. 20 20
      gameplay/src/PhysicsController.cpp
  96. 3 3
      gameplay/src/PhysicsGhostObject.cpp
  97. 5 5
      gameplay/src/PhysicsRigidBody.cpp
  98. 2 2
      gameplay/src/Plane.cpp
  99. 0 7
      gameplay/src/Platform.h
  100. 182 150
      gameplay/src/PlatformAndroid.cpp

+ 10 - 1
.gitignore

@@ -156,4 +156,13 @@ Thumbs.db
 /gameplay-samples/sample04-particles/Device-Debug
 /gameplay-samples/sample04-particles/Debug
 /gameplay-samples/sample04-particles/DebugMem
-/gameplay-samples/sample03-character/res/gamepad.xcf
+/gameplay-samples/sample03-character/res/gamepad.xcf
+/gameplay-samples/sample04-particles/android/src
+/gameplay-samples/sample04-particles/android/assets
+/gameplay-samples/sample04-particles/android/bin
+/gameplay-samples/sample04-particles/android/gen
+/gameplay-samples/sample04-particles/android/libs
+/gameplay-samples/sample04-particles/android/obj
+/gameplay-samples/sample04-particles/android/proguard.cfg
+/gameplay-samples/sample04-particles/android/local.properties
+/gameplay-samples/sample04-particles/android/project.properties

+ 3 - 2
README.md

@@ -2,8 +2,8 @@
 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 (using BlackBerry Native SDK 2)
-- Google Android 2.3 (using Google Android NDK 7)
+- BlackBerry PlayBook 2.0 (using BlackBerry Native SDK 2)
+- Google Android 2.3 (using Google Android NDK r7, SDK API level 9 and up)
 - Apple iOS 5.1 (using Apple XCode 4.3.2)
 
 ## Supported Desktop Platforms
@@ -14,6 +14,7 @@ An open-source, cross-platform 3D native C++ game framework making it easy to le
 - Shadows
 - Lua Script Bindings
 - Terrain
+- AI
 - Editor
 - Performance/Optimizations
 

+ 1 - 0
gameplay-encoder/gameplay-bundle.txt

@@ -137,6 +137,7 @@ Reference
 2->Node
                 type                    enum NodeType
                 transform               float[16]
+                parent_id               string
                 children                Node[]
                 camera                  Camera
                 light                   Light

+ 1 - 0
gameplay-encoder/gameplay-encoder.vcxproj

@@ -98,6 +98,7 @@
     <ClInclude Include="src\VertexElement.h" />
   </ItemGroup>
   <ItemGroup>
+    <None Include="gameplay-bundle.txt" />
     <None Include="src\Curve.inl" />
     <None Include="src\Quaternion.inl" />
     <None Include="src\Vector2.inl" />

+ 1 - 0
gameplay-encoder/gameplay-encoder.vcxproj.filters

@@ -269,6 +269,7 @@
     <None Include="src\Curve.inl">
       <Filter>src</Filter>
     </None>
+    <None Include="gameplay-bundle.txt" />
   </ItemGroup>
   <ItemGroup>
     <Filter Include="src">

+ 131 - 80
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -261,13 +261,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)
@@ -366,89 +360,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)
     {
-        animation->setId(str);
+        // DAE_FBX nests 1 animation within another animation for some reason.
+        loadAnimation(animationArray.get(0), animationRef->getId());
+    }
+    else if ( animationCount > 1)
+    {
+        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 +704,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 +1013,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 +1211,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 +1715,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 +1816,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);
     
     /**

+ 77 - 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,56 @@ 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);
+            return visualScene.cast();
+        }
+    }
+    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

+ 6 - 2
gameplay-encoder/src/FileIO.cpp

@@ -64,8 +64,12 @@ void write(const std::string& str, FILE* file)
 {
     // Write the length of the string
     write(str.size(), file);
-    // Write the array of characters of the string
-    write(str.c_str(), file);
+    
+    if (str.size() > 0)
+    {
+        // Write the array of characters of the string
+        write(str.c_str(), file);
+    }
 }
 
 void writeZero(FILE* file)

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

+ 1 - 1
gameplay-encoder/src/GPBFile.h

@@ -21,7 +21,7 @@ namespace gameplay
  * Increment the version number when making a change that break binary compatibility.
  * [0] is major, [1] is minor.
  */
-const unsigned char GPB_VERSION[2] = {1, 1};
+const unsigned char GPB_VERSION[2] = {1, 2};
 
 /**
  * The GamePlay Binary file class handles writing the GamePlay Binary file.

+ 4 - 0
gameplay-encoder/src/Node.cpp

@@ -40,6 +40,10 @@ void Node::writeBinary(FILE* file)
     write(type, file);
 
     write(_transform.m, 16, file);
+
+    // write parent's id
+    write((_parent) ? _parent->getId() : std::string(), file);
+
     // children
     write(getChildCount(), file); // write number of children
     for (Node* node = getFirstChild(); node != NULL; node = node->getNextSibling())

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

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

+ 18 - 2
gameplay-template/android/jni/template.Android.mk

@@ -17,6 +17,8 @@ SAMPLE_PATH := $(call my-dir)/../../src
 LIBPNG_PATH := ../GAMEPLAY_PATH/external-deps/libpng/lib/android/arm
 ZLIB_PATH := ../GAMEPLAY_PATH/external-deps/zlib/lib/android/arm
 BULLET_PATH := ../GAMEPLAY_PATH/external-deps/bullet/lib/android/arm
+VORBIS_PATH := ../GAMEPLAY_PATH/external-deps/oggvorbis/lib/android/arm
+OPENAL_PATH := ../GAMEPLAY_PATH/external-deps/openal/lib/android/arm
 
 # gameplay
 LOCAL_PATH := ../GAMEPLAY_PATH/gameplay/android/obj/local/armeabi
@@ -46,6 +48,20 @@ LOCAL_MODULE    := libbullet
 LOCAL_SRC_FILES := libbullet.a
 include $(PREBUILT_STATIC_LIBRARY)
 
+# libvorbis
+LOCAL_PATH := $(VORBIS_PATH)
+include $(CLEAR_VARS)
+LOCAL_MODULE    := libvorbis
+LOCAL_SRC_FILES := libvorbis.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+# libOpenAL
+LOCAL_PATH := $(OPENAL_PATH)
+include $(CLEAR_VARS)
+LOCAL_MODULE    := libOpenAL
+LOCAL_SRC_FILES := libOpenAL.a
+include $(PREBUILT_STATIC_LIBRARY)
+
 # TEMPLATE_PROJECT
 LOCAL_PATH := $(SAMPLE_PATH)
 include $(CLEAR_VARS)
@@ -54,9 +70,9 @@ LOCAL_MODULE    := TEMPLATE_PROJECT
 LOCAL_SRC_FILES := ../GAMEPLAY_PATH/gameplay/src/gameplay-main-android.cpp TemplateGame.cpp
 
 LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv2 -lOpenSLES
-LOCAL_CFLAGS    := -D__ANDROID__ -I"../GAMEPLAY_PATH/external-deps/bullet/include" -I"../GAMEPLAY_PATH/external-deps/libpng/include" -I"../GAMEPLAY_PATH/gameplay/src"
+LOCAL_CFLAGS    := -D__ANDROID__ -I"../GAMEPLAY_PATH/external-deps/bullet/include" -I"../GAMEPLAY_PATH/external-deps/libpng/include" -I"../GAMEPLAY_PATH/external-deps/oggvorbis/include" -I"../GAMEPLAY_PATH/external-deps/openal/include" -I"../GAMEPLAY_PATH/gameplay/src"
 
-LOCAL_STATIC_LIBRARIES := android_native_app_glue libgameplay libpng libzlib libbullet
+LOCAL_STATIC_LIBRARIES := android_native_app_glue libgameplay libpng libzlib libbullet libvorbis libOpenAL
 
 include $(BUILD_SHARED_LIBRARY)
 $(call import-module,android/native_app_glue)

+ 2 - 0
gameplay-template/gameplay-template.vcxproj

@@ -76,6 +76,7 @@
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories>GAMEPLAY_PATH/external-deps/bullet/include;GAMEPLAY_PATH/gameplay/src;GAMEPLAY_PATH/external-deps/openal/include/AL;GAMEPLAY_PATH/external-deps/oggvorbis/include;GAMEPLAY_PATH/external-deps/libpng/include;GAMEPLAY_PATH/external-deps/zlib/include;GAMEPLAY_PATH/external-deps/glew/include</AdditionalIncludeDirectories>
       <RuntimeTypeInfo>true</RuntimeTypeInfo>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -107,6 +108,7 @@
       <RuntimeTypeInfo>true</RuntimeTypeInfo>
       <ShowIncludes>false</ShowIncludes>
       <PreprocessToFile>false</PreprocessToFile>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>

BIN
gameplay-template/res/box.gpb


+ 15 - 15
gameplay/.cproject

@@ -17,7 +17,7 @@
 						<toolChain id="com.qnx.qcc.toolChain.staticLib.debug.1686166742" name="QNX QCC" superClass="com.qnx.qcc.toolChain">
 							<option id="com.qnx.qcc.option.cpu.545743487" name="Target CPU:" superClass="com.qnx.qcc.option.cpu" value="com.qnx.qcc.option.gen.cpu.armle-v7" valueType="enumerated"/>
 							<targetPlatform archList="all" binaryParser="com.qnx.tools.ide.qde.core.QDEBynaryParser" id="com.qnx.qcc.targetPlatform.158841187" osList="all" superClass="com.qnx.qcc.targetPlatform"/>
-							<builder buildPath="${workspace_loc:/gameplay/Device-Debug}" id="org.eclipse.cdt.build.core.internal.builder.159190287" superClass="org.eclipse.cdt.build.core.internal.builder"/>
+							<builder buildPath="${workspace_loc:/gameplay/Device-Debug}" id="org.eclipse.cdt.build.core.internal.builder.159190287" keepEnvironmentInBuildfile="false" name="CDT Internal Builder" superClass="org.eclipse.cdt.build.core.internal.builder"/>
 							<tool id="com.qnx.qcc.tool.compiler.96907942" name="QCC Compiler" superClass="com.qnx.qcc.tool.compiler">
 								<option id="com.qnx.qcc.option.compile.debug.1765481355" name="Debug (-g)" superClass="com.qnx.qcc.option.compile.debug" value="true" valueType="boolean"/>
 								<option id="com.qnx.qcc.option.compiler.security.311918799" name="Enhanced Security (-fstack-protector-all)" superClass="com.qnx.qcc.option.compiler.security" value="true" valueType="boolean"/>
@@ -25,8 +25,8 @@
 									<listOptionValue builtIn="false" value="_FORTIFY_SOURCE=2"/>
 								</option>
 								<option id="com.qnx.qcc.option.compiler.includePath.2133604142" name="Include Directories (-I)" superClass="com.qnx.qcc.option.compiler.includePath" valueType="includePath">
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/bullet/include&quot;"/>
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/oggvorbis/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/bullet/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.997142816" superClass="com.qnx.qcc.inputType.compiler"/>
@@ -78,8 +78,8 @@
 									<listOptionValue builtIn="false" value="_FORTIFY_SOURCE=2"/>
 								</option>
 								<option id="com.qnx.qcc.option.compiler.includePath.1670164593" name="Include Directories (-I)" superClass="com.qnx.qcc.option.compiler.includePath" valueType="includePath">
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/bullet/include&quot;"/>
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/oggvorbis/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/bullet/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.1380846613" superClass="com.qnx.qcc.inputType.compiler"/>
@@ -130,8 +130,8 @@
 									<listOptionValue builtIn="false" value="_FORTIFY_SOURCE=2"/>
 								</option>
 								<option id="com.qnx.qcc.option.compiler.includePath.1503059677" name="Include Directories (-I)" superClass="com.qnx.qcc.option.compiler.includePath" valueType="includePath">
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/bullet/include&quot;"/>
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/oggvorbis/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/bullet/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.81809638" superClass="com.qnx.qcc.inputType.compiler"/>
@@ -185,8 +185,8 @@
 									<listOptionValue builtIn="false" value="_FORTIFY_SOURCE=2"/>
 								</option>
 								<option id="com.qnx.qcc.option.compiler.includePath.1769677874" name="Include Directories (-I)" superClass="com.qnx.qcc.option.compiler.includePath" valueType="includePath">
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/bullet/include&quot;"/>
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/oggvorbis/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/bullet/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.2007171407" superClass="com.qnx.qcc.inputType.compiler"/>
@@ -238,8 +238,8 @@
 									<listOptionValue builtIn="false" value="_FORTIFY_SOURCE=2"/>
 								</option>
 								<option id="com.qnx.qcc.option.compiler.includePath.847642559" name="Include Directories (-I)" superClass="com.qnx.qcc.option.compiler.includePath" valueType="includePath">
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/bullet/include&quot;"/>
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/oggvorbis/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/bullet/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.1038720310" superClass="com.qnx.qcc.inputType.compiler"/>
@@ -291,8 +291,8 @@
 									<listOptionValue builtIn="false" value="_FORTIFY_SOURCE=2"/>
 								</option>
 								<option id="com.qnx.qcc.option.compiler.includePath.513622172" name="Include Directories (-I)" superClass="com.qnx.qcc.option.compiler.includePath" valueType="includePath">
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/bullet/include&quot;"/>
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/oggvorbis/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/bullet/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.1961855927" superClass="com.qnx.qcc.inputType.compiler"/>
@@ -345,8 +345,8 @@
 									<listOptionValue builtIn="false" value="_FORTIFY_SOURCE=2"/>
 								</option>
 								<option id="com.qnx.qcc.option.compiler.includePath.1685994750" name="Include Directories (-I)" superClass="com.qnx.qcc.option.compiler.includePath" valueType="includePath">
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/bullet/include&quot;"/>
-									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}}/../external-deps/oggvorbis/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/bullet/include&quot;"/>
+									<listOptionValue builtIn="false" value="&quot;../../external-deps/oggvorbis/include&quot;"/>
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.compiler.1658185881" superClass="com.qnx.qcc.inputType.compiler"/>

+ 2 - 2
gameplay/android/AndroidManifest.xml

@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.gameplay"
+    package="org.gameplay3d.gameplay"
     android:versionCode="1"
     android:versionName="1.0" >
 
-    <uses-sdk android:minSdkVersion="15" />
+    <uses-sdk android:minSdkVersion="9" />
 
     <application
         android:icon="@drawable/ic_launcher"

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

@@ -17,7 +17,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_CFLAGS := -D__ANDROID__ -I"../../external-deps/bullet/include" -I"../../external-deps/libpng/include"
+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
 
 include $(BUILD_STATIC_LIBRARY)

+ 2 - 0
gameplay/gameplay.vcxproj

@@ -90,6 +90,7 @@
     <ClCompile Include="src\RenderTarget.cpp" />
     <ClCompile Include="src\Scene.cpp" />
     <ClCompile Include="src\SceneLoader.cpp" />
+    <ClCompile Include="src\ScrollLayout.cpp" />
     <ClCompile Include="src\Slider.cpp" />
     <ClCompile Include="src\SpriteBatch.cpp" />
     <ClCompile Include="src\Technique.cpp" />
@@ -181,6 +182,7 @@
     <ClInclude Include="src\Scene.h" />
     <ClInclude Include="src\SceneLoader.h" />
     <ClInclude Include="src\ScreenDisplayer.h" />
+    <ClInclude Include="src\ScrollLayout.h" />
     <ClInclude Include="src\Slider.h" />
     <ClInclude Include="src\SpriteBatch.h" />
     <ClInclude Include="src\Technique.h" />

+ 6 - 0
gameplay/gameplay.vcxproj.filters

@@ -279,6 +279,9 @@
     <ClCompile Include="src\FlowLayout.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\ScrollLayout.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -554,6 +557,9 @@
     <ClInclude Include="src\FlowLayout.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\ScrollLayout.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\shaders\bumped-specular.vsh">

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

@@ -28,6 +28,10 @@
 		42554EA2152BC35C000ED910 /* PhysicsCollisionShape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42554E9F152BC35C000ED910 /* PhysicsCollisionShape.cpp */; };
 		42554EA3152BC35C000ED910 /* PhysicsCollisionShape.h in Headers */ = {isa = PBXBuildFile; fileRef = 42554EA0152BC35C000ED910 /* PhysicsCollisionShape.h */; };
 		42554EA4152BC35C000ED910 /* PhysicsCollisionShape.h in Headers */ = {isa = PBXBuildFile; fileRef = 42554EA0152BC35C000ED910 /* PhysicsCollisionShape.h */; };
+		4265D9241559A3C300581EB0 /* ScrollLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4265D9221559A3C300581EB0 /* ScrollLayout.cpp */; };
+		4265D9251559A3C300581EB0 /* ScrollLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4265D9221559A3C300581EB0 /* ScrollLayout.cpp */; };
+		4265D9261559A3C300581EB0 /* ScrollLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 4265D9231559A3C300581EB0 /* ScrollLayout.h */; };
+		4265D9271559A3C300581EB0 /* ScrollLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 4265D9231559A3C300581EB0 /* ScrollLayout.h */; };
 		426878AC153F4BB300844500 /* FlowLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 426878AA153F4BB300844500 /* FlowLayout.cpp */; };
 		426878AD153F4BB300844500 /* FlowLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 426878AA153F4BB300844500 /* FlowLayout.cpp */; };
 		426878AE153F4BB300844500 /* FlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 426878AB153F4BB300844500 /* FlowLayout.h */; };
@@ -404,6 +408,8 @@
 		4251B130152D049B002F6199 /* ThemeStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ThemeStyle.h; path = src/ThemeStyle.h; sourceTree = SOURCE_ROOT; };
 		42554E9F152BC35C000ED910 /* PhysicsCollisionShape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PhysicsCollisionShape.cpp; path = src/PhysicsCollisionShape.cpp; sourceTree = SOURCE_ROOT; };
 		42554EA0152BC35C000ED910 /* PhysicsCollisionShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PhysicsCollisionShape.h; path = src/PhysicsCollisionShape.h; sourceTree = SOURCE_ROOT; };
+		4265D9221559A3C300581EB0 /* ScrollLayout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ScrollLayout.cpp; path = src/ScrollLayout.cpp; sourceTree = SOURCE_ROOT; };
+		4265D9231559A3C300581EB0 /* ScrollLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ScrollLayout.h; path = src/ScrollLayout.h; sourceTree = SOURCE_ROOT; };
 		426878AA153F4BB300844500 /* FlowLayout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FlowLayout.cpp; path = src/FlowLayout.cpp; sourceTree = SOURCE_ROOT; };
 		426878AB153F4BB300844500 /* FlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlowLayout.h; path = src/FlowLayout.h; sourceTree = SOURCE_ROOT; };
 		4271C08D15337C8200B89DA7 /* Layout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Layout.cpp; path = src/Layout.cpp; sourceTree = SOURCE_ROOT; };
@@ -856,6 +862,8 @@
 				428390971489D6E800E2B2F5 /* SceneLoader.cpp */,
 				428390981489D6E800E2B2F5 /* SceneLoader.h */,
 				4251B12E152D049B002F6199 /* ScreenDisplayer.h */,
+				4265D9221559A3C300581EB0 /* ScrollLayout.cpp */,
+				4265D9231559A3C300581EB0 /* ScrollLayout.h */,
 				5BD52646150F822A004C9099 /* Slider.cpp */,
 				5BD52647150F822A004C9099 /* Slider.h */,
 				42CD0E2F147D8FF50000361E /* SpriteBatch.cpp */,
@@ -1064,6 +1072,7 @@
 				4251B135152D049B002F6199 /* ThemeStyle.h in Headers */,
 				422260D81537790F0011E3AB /* Bundle.h in Headers */,
 				426878AE153F4BB300844500 /* FlowLayout.h in Headers */,
+				4265D9261559A3C300581EB0 /* ScrollLayout.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1161,6 +1170,7 @@
 				4251B136152D049B002F6199 /* ThemeStyle.h in Headers */,
 				422260D91537790F0011E3AB /* Bundle.h in Headers */,
 				426878AF153F4BB300844500 /* FlowLayout.h in Headers */,
+				4265D9271559A3C300581EB0 /* ScrollLayout.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1317,6 +1327,7 @@
 				4271C08E15337C8200B89DA7 /* Layout.cpp in Sources */,
 				422260D61537790F0011E3AB /* Bundle.cpp in Sources */,
 				426878AC153F4BB300844500 /* FlowLayout.cpp in Sources */,
+				4265D9241559A3C300581EB0 /* ScrollLayout.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1409,6 +1420,7 @@
 				4271C08F15337C8200B89DA7 /* Layout.cpp in Sources */,
 				422260D71537790F0011E3AB /* Bundle.cpp in Sources */,
 				426878AD153F4BB300844500 /* FlowLayout.cpp in Sources */,
+				4265D9251559A3C300581EB0 /* ScrollLayout.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)
+{
+    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->getClip(), Vector2::zero());
     }
+}
+
 }

+ 50 - 18
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();
@@ -105,18 +120,17 @@ unsigned long Animation::getDuration() const
     return _duration;
 }
 
-void Animation::createClips(const char* animationFile)
+void Animation::createClips(const char* url)
 {
-    assert(animationFile);
+    Properties* properties = Properties::create(url);
+    GP_ASSERT(properties);
 
-    Properties* properties = Properties::create(animationFile);
-    assert(properties);
-
-    Properties* pAnimation = properties->getNextNamespace();
-    assert(pAnimation);
+    Properties* pAnimation = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
+    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;
 }
 

+ 6 - 2
gameplay/src/Animation.h

@@ -43,9 +43,13 @@ public:
     unsigned long getDuration() const;
 
     /**
-     * Creates an AnimationClip from an .animation file.
+     * Creates an AnimationClip from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional).
+     * 
+     * @param url The URL pointing to the Properties object containing the clip definitions.
      */
-    void createClips(const char* animationFile);
+    void createClips(const char* url);
     
     /**
      * Creates an AnimationClip from the Animation.

+ 40 - 14
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);
                     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,10 +309,11 @@ void AnimationClip::addEndListener(AnimationClip::Listener* listener)
     if (!_endListeners)
         _endListeners = new std::vector<Listener*>;
 
+    GP_ASSERT(listener);
     _endListeners->push_back(listener);
 }
 
-bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*>* activeTargets)
+bool AnimationClip::update(unsigned long elapsedTime)
 {
     if (isClipStateBitSet(CLIP_IS_PAUSED_BIT))
     {
@@ -365,10 +374,17 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
     // Notify any listeners of Animation events.
     if (_listeners)
     {
+        GP_ASSERT(_listenerItr);
+        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, std::list<AnimationTarget*
         {
             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,12 +404,18 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
     }
 
     // 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.
         {
+            GP_ASSERT(_crossFadeToClip);
             _crossFadeOutElapsed = (Game::getGameTime() - _crossFadeToClip->_timeStarted) * abs(_speed); 
             resetClipStateBit(CLIP_IS_FADING_OUT_STARTED_BIT);
         }
@@ -436,16 +462,14 @@ bool AnimationClip::update(unsigned long elapsedTime, std::list<AnimationTarget*
     for (unsigned int i = 0; i < channelCount; i++)
     {
         channel = _animation->_channels[i];
+        GP_ASSERT(channel);
         target = channel->_target;
+        GP_ASSERT(target);
         value = _values[i];
-
-        // If the target's _animationPropertyBitFlag is clear, we can assume that this is the first
-        // animation channel to act on the target and we can add the target to the list of
-        // active targets stored by the AnimationController.
-        if (target->_animationPropertyBitFlag == 0x00)
-            activeTargets->push_front(target);
+        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);
@@ -480,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++;
         }
@@ -503,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++;
         }

+ 1 - 1
gameplay/src/AnimationClip.h

@@ -283,7 +283,7 @@ private:
     /**
      * Updates the animation with the elapsed time.
      */
-    bool update(unsigned long elapsedTime, std::list<AnimationTarget*>* activeTargets);
+    bool update(unsigned long elapsedTime);
 
     /**
      * Handles when the AnimationClip begins.

+ 8 - 11
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.
@@ -109,7 +114,7 @@ void AnimationController::update(long elapsedTime)
             _runningClips.push_back(clip);
             clipIter = _runningClips.erase(clipIter);
         }
-        else if (clip->update(elapsedTime, &_activeTargets))
+        else if (clip->update(elapsedTime))
         {
             SAFE_RELEASE(clip);
             clipIter = _runningClips.erase(clipIter);
@@ -120,16 +125,8 @@ void AnimationController::update(long elapsedTime)
         }
     }
 
-    // Loop through active AnimationTarget's and reset their _animationPropertyBitFlag for the next frame.
-    std::list<AnimationTarget*>::iterator targetItr = _activeTargets.begin();
-    while (targetItr != _activeTargets.end())
-    {
-        AnimationTarget* target = (*targetItr);
-        target->_animationPropertyBitFlag = 0x00;
-        targetItr++;
-    }
-    _activeTargets.clear();
-    
+    Transform::resumeTransformChanged();
+
     if (_runningClips.empty())
         _state = IDLE;
 }

+ 0 - 1
gameplay/src/AnimationController.h

@@ -98,7 +98,6 @@ private:
     
     State _state;                                 // The current state of the AnimationController.
     std::list<AnimationClip*> _runningClips;      // A list of running AnimationClips.
-    std::list<AnimationTarget*> _activeTargets;   // A list of animating AnimationTargets.
 };
 
 }

+ 146 - 21
gameplay/src/AnimationTarget.cpp

@@ -8,7 +8,7 @@ namespace gameplay
 {
 
 AnimationTarget::AnimationTarget()
-    : _targetType(SCALAR), _animationPropertyBitFlag(0x00), _animationChannels(NULL)
+    : _targetType(SCALAR), _animationChannels(NULL)
 {
 }
 
@@ -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,20 +43,18 @@ 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;
 }
 
-Animation* AnimationTarget::createAnimation(const char* id, const char* animationFile)
+Animation* AnimationTarget::createAnimation(const char* id, const char* url)
 {
-    assert(animationFile);
-    
-    Properties* p = Properties::create(animationFile);
-    assert(p);
+    Properties* p = Properties::create(url);
+    GP_ASSERT(p);
 
-    Animation* animation = createAnimation(id, p->getNextNamespace());
+    Animation* animation = createAnimation(id, (strlen(p->getNamespace()) > 0) ? p : p->getNextNamespace());
 
     SAFE_DELETE(p);
 
@@ -63,7 +63,11 @@ Animation* AnimationTarget::createAnimation(const char* id, const char* animatio
 
 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);
 }
 
@@ -370,6 +419,7 @@ Animation::Channel* AnimationTarget::getChannel(const char* id) const
     if (_animationChannels)
     {
         std::vector<Animation::Channel*>::iterator itr = _animationChannels->begin();
+        GP_ASSERT(*itr);
 
         if (id == NULL)
             return (*itr);
@@ -378,6 +428,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 +446,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 +465,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];
+    }
+}
+
 }
 
 

+ 25 - 9
gameplay/src/AnimationTarget.h

@@ -19,7 +19,6 @@ class AnimationTarget
 {
     friend class Animation;
     friend class AnimationClip;
-    friend class AnimationController;
 
 public:
 
@@ -55,14 +54,16 @@ public:
     Animation* createAnimation(const char* id, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, float* keyInValue, float* keyOutValue, Curve::InterpolationType type);
 
     /**
-     * Creates an animation on this target using the data from the given properties object. 
+     * Creates an animation on this target using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
      * 
      * @param id The ID of the animation.
-     * @param animationFile The animation file defining the animation data.
+     * @param url The URL pointing to the Properties object defining the animation data.
      *
      * @return The newly created animation.
      */
-    Animation* createAnimation(const char* id, const char* animationFile);
+    Animation* createAnimation(const char* id, const char* url);
 
     /**
      * Creates an animation on this target using the data from the given properties object. 
@@ -202,11 +203,6 @@ protected:
      */
     TargetType _targetType;
 
-    /**
-     * Bit flag used to indicate which properties on the AnimationTarget are currently animating.
-     */ 
-    unsigned char _animationPropertyBitFlag;
-
 private:
 
     /**
@@ -224,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));
 }

+ 74 - 99
gameplay/src/AudioBuffer.cpp

@@ -2,26 +2,16 @@
 #include "AudioBuffer.h"
 #include "FileSystem.h"
 
-#ifdef __ANDROID__
-extern AAssetManager* __assetManager;
-#endif
-
 namespace gameplay
 {
 
 // Audio buffer cache
 static std::vector<AudioBuffer*> __buffers;
 
-#ifndef __ANDROID__
 AudioBuffer::AudioBuffer(const char* path, ALuint buffer)
     : _filePath(path), _alBuffer(buffer)
 {
 }
-#else
-AudioBuffer::AudioBuffer(const char* path) : _filePath(path)
-{
-}
-#endif
 
 AudioBuffer::~AudioBuffer()
 {
@@ -36,18 +26,16 @@ AudioBuffer::~AudioBuffer()
         }
     }
 
-#ifndef __ANDROID__
     if (_alBuffer)
     {
-        alDeleteBuffers(1, &_alBuffer);
+        AL_CHECK( alDeleteBuffers(1, &_alBuffer) );
         _alBuffer = 0;
     }
-#endif
 }
 
 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();
@@ -55,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();
@@ -62,17 +51,14 @@ AudioBuffer* AudioBuffer::create(const char* path)
         }
     }
 
-#ifndef __ANDROID__
     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;
     }
     
@@ -80,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;
     }
     
@@ -88,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;
     }
     
@@ -97,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;
         }
     }
@@ -105,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);
@@ -128,67 +115,21 @@ cleanup:
     if (file)
         fclose(file);
     if (alBuffer)
-        alDeleteBuffers(1, &alBuffer);
+        AL_CHECK( alDeleteBuffers(1, &alBuffer) );
     return NULL;
-#else
-    // Get the file header in order to determine the type.
-    AAsset* asset = AAssetManager_open(__assetManager, path, AASSET_MODE_RANDOM);
-    char header[12];
-    if (AAsset_read(asset, header, 12) != 12)
-    {
-        LOG_ERROR_VARG("Invalid audio buffer file: %s", path);
-        return NULL;
-    }
-
-    // Get the file descriptor for the audio file.
-    off_t start, length;
-    int fd = AAsset_openFileDescriptor(asset, &start, &length);
-    if (fd < 0)
-    {
-        LOG_ERROR_VARG("Failed to open file descriptor for asset: %s", path);
-        return NULL;
-    }
-    AAsset_close(asset);
-    SLDataLocator_AndroidFD data = {SL_DATALOCATOR_ANDROIDFD, fd, start, length};
-
-    // Set the appropriate mime type information.
-    SLDataFormat_MIME mime;
-    mime.formatType = SL_DATAFORMAT_MIME;
-    std::string pathStr = path;
-    if (memcmp(header, "RIFF", 4) == 0)
-    {
-        mime.mimeType = (SLchar*)"audio/x-wav";
-        mime.containerType = SL_CONTAINERTYPE_WAV;
-    }
-    else if (memcmp(header, "OggS", 4) == 0)
-    {
-        mime.mimeType = (SLchar*)"application/ogg";
-        mime.containerType = SL_CONTAINERTYPE_OGG;
-    }
-    else
-    {
-        LOG_ERROR_VARG("Unsupported audio file: %s", path);
-    }
-
-    buffer = new AudioBuffer(path);
-    buffer->_data = data;
-    buffer->_mime = mime;
-
-    // Add the buffer to the cache.
-    __buffers.push_back(buffer);
-
-    return buffer;
-#endif
 }
 
-#ifndef __ANDROID__
 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;
@@ -199,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;
@@ -224,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];
     
@@ -251,68 +204,91 @@ 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;
+        }
     }
 
+    // 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 (fact or data) from wave file.");
         return false;
+    }
 
-    // read the next chunk, could be fact section or the data section
+    // Read the fact section if it is there.
     if (memcmp(stream, "fact", 4) == 0)
     {
         if (fread(stream, 1, 4, file) != 4)
+        {
+            GP_ERROR("Failed to read fact section size from wave file.");
             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
+        // Read in the fact section.
         if (fread(stream, 1, section_size, file) != section_size)
+        {
+            GP_ERROR("Failed to read fact section from wave file.");
             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 (should be data) from wave file.");
             return false;
+        }
     }
 
-    // should now be the data section which holds the decoded sample data
+    // 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.");
+        GP_ERROR("Failed to load wave file; file appears to have no data.");
         return false;
     }
 
     // Read how much data is remaining and buffer it up.
     unsigned int dataSize;
-    fread(&dataSize, sizeof(int), 1, file);
+    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)
     {
-        LOG_ERROR("WAV file missing data.");
+        GP_ERROR("Failed to load wave file; file is missing data.");
         SAFE_DELETE_ARRAY(data);
         return false;
     }
 
-    alBufferData(buffer, format, data, dataSize, frequency);
+    AL_CHECK( 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;
@@ -325,18 +301,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];
 
@@ -350,7 +326,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
@@ -362,20 +338,19 @@ 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;
 }
-#endif
 
 }

+ 0 - 14
gameplay/src/AudioBuffer.h

@@ -19,17 +19,10 @@ class AudioBuffer : public Ref
 
 private:
     
-#ifndef __ANDROID__
     /**
      * Constructor.
      */
     AudioBuffer(const char* path, ALuint buffer);
-#else
-    /**
-     * Constructor.
-     */
-    AudioBuffer(const char* path);
-#endif
 
     /**
      * Destructor.
@@ -45,19 +38,12 @@ private:
      */
     static AudioBuffer* create(const char* path);
     
-#ifndef __ANDROID__
     static bool loadWav(FILE* file, ALuint buffer);
     
     static bool loadOgg(FILE* file, ALuint buffer);
-#endif
 
     std::string _filePath;
-#ifndef __ANDROID__
     ALuint _alBuffer;
-#else
-    SLDataLocator_AndroidFD _data;
-    SLDataFormat_MIME _mime;
-#endif
 };
 
 }

+ 22 - 165
gameplay/src/AudioController.cpp

@@ -4,25 +4,13 @@
 #include "AudioBuffer.h"
 #include "AudioSource.h"
 
-
 namespace gameplay
 {
 
-std::list<AudioSource*> AudioController::_playingSources;
-
-
-#ifndef __ANDROID__
-AudioController::AudioController() 
-    : _alcDevice(NULL), _alcContext(NULL)
-{
-}
-#else
 AudioController::AudioController() 
-    : _engineObject(NULL), _engineEngine(NULL), _outputMixObject(NULL), _listenerObject(NULL),
-    _listenerDoppler(NULL), _listenerLocation(NULL)
+    : _alcDevice(NULL), _alcContext(NULL), _pausingSource(NULL)
 {
 }
-#endif
 
 AudioController::~AudioController()
 {
@@ -30,20 +18,19 @@ AudioController::~AudioController()
 
 void AudioController::initialize()
 {
-#ifndef __ANDROID__
-    _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;
     }
     
@@ -51,54 +38,12 @@ 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);
-    }
-#else
-    // Create the engine.
-    SLresult result = slCreateEngine(&_engineObject, 0, NULL, 0, NULL, NULL);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        LOG_ERROR("AudioController::initialize() error. Unable to create OpenSL engine.");
-        return;
-    }
-
-    // Realize the engine.
-    result = (*_engineObject)->Realize(_engineObject, SL_BOOLEAN_FALSE);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        LOG_ERROR("AudioController::initialize() error. Unable to realize OpenSL engine.");
-        return;
-    }
-
-    // Get the engine interface in order to create other objects later on.
-    result = (*_engineObject)->GetInterface(_engineObject, SL_IID_ENGINE, &_engineEngine);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        LOG_ERROR("AudioController::initialize() error. Unable to retrieve OpenSL engine interface.");
-        return;
-    }
-
-    // Create the output mix.
-    result = (*_engineEngine)->CreateOutputMix(_engineEngine, &_outputMixObject, 0, NULL, NULL);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        LOG_ERROR("AudioController::initialize() error. Unable to create OpenSL output mix.");
-        return;
-    }
-
-    // Realize the output mix.
-    result = (*_outputMixObject)->Realize(_outputMixObject, SL_BOOLEAN_FALSE);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        LOG_ERROR("AudioController::initialize() error. Unable to realize OpenSL output mix.");
-        return;
+        GP_ERROR("Unable to make OpenAL context current. Error: %d\n", alcErr);
     }
-#endif
 }
 
 void AudioController::finalize()
 {
-#ifndef __ANDROID__
     alcMakeContextCurrent(NULL);
     if (_alcContext)
     {
@@ -110,55 +55,38 @@ void AudioController::finalize()
         alcCloseDevice(_alcDevice);
         _alcDevice = NULL;
     }
-#else
-    if (_outputMixObject != NULL)
-    {
-        (*_outputMixObject)->Destroy(_outputMixObject);
-        _outputMixObject = NULL;
-    }
-
-    if (_engineObject != NULL)
-    {
-        (*_engineObject)->Destroy(_engineObject);
-        _engineObject = NULL;
-        _engineEngine = NULL;
-    }
-#endif
 }
 
 void AudioController::pause()
 {
-    std::list<AudioSource*>::iterator itr = _playingSources.begin();
+    std::set<AudioSource*>::iterator itr = _playingSources.begin();
 
     // For each source that is playing, pause it.
     AudioSource* source = NULL;
     while (itr != _playingSources.end())
     {
+        GP_ASSERT(*itr);
         source = *itr;
-        if (source->getState() == AudioSource::PLAYING)
-        {
-            source->pause();
-        }
+        _pausingSource = source;
+        source->pause();
+        _pausingSource = NULL;
         itr++;
     }
 }
 
 void AudioController::resume()
-{
-#ifndef __ANDROID__    
+{   
     alcMakeContextCurrent(_alcContext);
-#endif
-    std::list<AudioSource*>::iterator itr = _playingSources.begin();
+
+    std::set<AudioSource*>::iterator itr = _playingSources.begin();
 
     // For each source that is playing, resume it.
     AudioSource* source = NULL;
     while (itr != _playingSources.end())
     {
+        GP_ASSERT(*itr);
         source = *itr;
-        if (source->getState() == AudioSource::PAUSED)
-        {
-            source->play();
-        }
+        source->resume();
         itr++;
     }
 }
@@ -168,81 +96,10 @@ void AudioController::update(long elapsedTime)
     AudioListener* listener = AudioListener::getInstance();
     if (listener)
     {
-#ifndef __ANDROID__
-        alListenerf(AL_GAIN, listener->getGain());
-        alListenerfv(AL_ORIENTATION, (ALfloat*)listener->getOrientation());
-        alListenerfv(AL_VELOCITY, (ALfloat*)&listener->getVelocity());
-        alListenerfv(AL_POSITION, (ALfloat*)&listener->getPosition());
-#else
-        if (!_listenerObject)
-        {
-            const SLInterfaceID interfaces[3] = {SL_IID_3DDOPPLER, SL_IID_3DLOCATION};
-            const SLboolean required[3] = {SL_BOOLEAN_FALSE, SL_BOOLEAN_FALSE};
-            SLresult result = (*_engineEngine)->CreateListener(_engineEngine, &_listenerObject, 2, interfaces, required);
-            if (result != SL_RESULT_SUCCESS)
-            {
-                WARN_VARG("AudioController: failed to create listener (%u).", result);
-                return;
-            }
-
-            result = (*_listenerObject)->Realize(_listenerObject, SL_BOOLEAN_FALSE);
-            if (result != SL_RESULT_SUCCESS)
-            {
-                WARN("AudioController: failed to realize listener.");
-                return;
-            }
-
-            // Get the doppler interface in order to set the listener's velocity.
-            result = (*_listenerObject)->GetInterface(_listenerObject, SL_IID_3DDOPPLER, &_listenerDoppler);
-            if (result != SL_RESULT_SUCCESS)
-            {
-                WARN("AudioController: Unable to retrieve listener doppler interface.");
-                return;
-            }
-
-            // Get the location interface in order to set the listener's position and orientation.
-            result = (*_listenerObject)->GetInterface(_listenerObject, SL_IID_3DLOCATION, &_listenerLocation);
-            if (result != SL_RESULT_SUCCESS)
-            {
-                WARN("AudioController: Unable to retrieve listener location interface.");
-                return;
-            }
-        }
-        
-        SLVec3D f;
-        f.x = listener->getOrientationForward().x;
-        f.y = listener->getOrientationForward().y;
-        f.z = listener->getOrientationForward().z;
-        SLVec3D a;
-        a.x = listener->getOrientationUp().x;
-        a.y = listener->getOrientationUp().y;
-        a.z = listener->getOrientationUp().z;
-        SLresult result = (*_listenerLocation)->SetOrientationVectors(_listenerLocation, &f, &a);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioController: Unable to set listener orientation.");
-        }
-
-        SLVec3D p;
-        p.x = listener->getPosition().x;
-        p.y = listener->getPosition().y;
-        p.z = listener->getPosition().z;
-        result = (*_listenerLocation)->SetLocationCartesian(_listenerLocation, &p);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioController: Unable to set listener location.");
-        }
-
-        SLVec3D v;
-        v.x = listener->getVelocity().x;
-        v.y = listener->getVelocity().y;
-        v.z = listener->getVelocity().z;
-        result = (*_listenerDoppler)->SetVelocityCartesian(_listenerDoppler, &v);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioController: Unable to set listener velocity.");
-        }
-#endif
+        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()) );
     }
 }
 

+ 3 - 10
gameplay/src/AudioController.h

@@ -54,18 +54,11 @@ private:
      */
     void update(long elapsedTime);
 
-#ifndef __ANDROID__
+
     ALCdevice* _alcDevice;
     ALCcontext* _alcContext;
-#else
-    SLObjectItf _engineObject;
-    SLEngineItf _engineEngine;
-    SLObjectItf _outputMixObject;
-    SLObjectItf _listenerObject;
-    SL3DDopplerItf _listenerDoppler;
-    SL3DLocationItf _listenerLocation;
-#endif
-    static std::list<AudioSource*> _playingSources;     // List of currently running sources.
+    std::set<AudioSource*> _playingSources;
+    AudioSource* _pausingSource;
 };
 
 }

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

+ 64 - 281
gameplay/src/AudioSource.cpp

@@ -9,180 +9,80 @@
 namespace gameplay
 {
 
-
-#ifndef __ANDROID__
 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) );
 }
-#else
-AudioSource::AudioSource(AudioBuffer* buffer, const SLObjectItf& player)
-    : _playerObject(player), _playerDoppler(NULL), _playerLocation(NULL), _playerPlay(NULL), _playerPitch(NULL),
-    _playerSeek(NULL), _playerVolume(NULL), _buffer(buffer), _looped(true), _gain(1.0f), _pitch(1.0f), _node(NULL)
-{
-    // Get the different interfaces for the OpenSL audio player that we need.
-    SLresult result = (*_playerObject)->GetInterface(_playerObject, SL_IID_3DDOPPLER, &_playerDoppler);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        WARN("AudioSource::AudioSource() - Failed to get 3D doppler interface for OpenSL audio player.");
-    }
-    
-    result = (*_playerObject)->GetInterface(_playerObject, SL_IID_3DLOCATION, &_playerLocation);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        WARN("AudioSource::AudioSource() - Failed to get 3D location interface for OpenSL audio player.");
-    }
-
-    result = (*_playerObject)->GetInterface(_playerObject, SL_IID_PLAY, &_playerPlay);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        WARN("AudioSource::AudioSource() - Failed to get play interface for OpenSL audio player.");
-    }
-
-    result = (*_playerObject)->GetInterface(_playerObject, SL_IID_PITCH, &_playerPitch);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        WARN("AudioSource::AudioSource() - Failed to get rate pitch interface for OpenSL audio player.");
-    }
-
-    result = (*_playerObject)->GetInterface(_playerObject, SL_IID_SEEK, &_playerSeek);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        WARN("AudioSource::AudioSource() - Failed to get seek interface for OpenSL audio player.");
-    }
-
-    result = (*_playerObject)->GetInterface(_playerObject, SL_IID_VOLUME, &_playerVolume);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        WARN("AudioSource::AudioSource() - Failed to get volume interface for OpenSL audio player.");
-    }
-
-    // Get the max volume level (used to convert from our API's parameter to OpenSL's expected units).
-    if (_playerVolume)
-    {
-        result = (*_playerVolume)->GetMaxVolumeLevel(_playerVolume, &_maxVolume);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::AudioSource() - Failed to get the max volume level for OpenSL audio player (needed for parameter conversion).");
-        }
-    }
-
-    setLooped(_looped);
-    setPitch(_pitch);
-    setGain(_gain);
-    setVelocity(_velocity);
-}
-#endif
 
 AudioSource::~AudioSource()
 {
-#ifndef __ANDROID__
     if (_alSource)
     {
-        alDeleteSources(1, &_alSource);
+        AL_CHECK( alDeleteSources(1, &_alSource) );
         _alSource = 0;
     }
-#else
-    if (_playerObject)
-    {
-        (*_playerObject)->Destroy(_playerObject);
-        _playerObject = NULL;
-        _playerDoppler = NULL;
-        _playerLocation = NULL;
-        _playerPlay = NULL;
-        _playerPitch = NULL;
-        _playerSeek = NULL;
-        _playerVolume = NULL;
-    }
-#endif
-
     SAFE_RELEASE(_buffer);
 }
 
-AudioSource* AudioSource::create(const char* path)
+AudioSource* AudioSource::create(const char* url)
 {
-    assert(path);
+    GP_ASSERT(url);
 
     // Load from a .audio file.
-    std::string pathStr = path;
+    std::string pathStr = url;
     if (pathStr.find(".audio") != pathStr.npos)
     {
-        Properties* properties = Properties::create(path);
-        assert(properties);
+        Properties* properties = Properties::create(url);
+        GP_ASSERT(properties);
         if (properties == NULL)
         {
             return NULL;
         }
 
-        AudioSource* audioSource = create(properties->getNextNamespace());
+        AudioSource* audioSource = create((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
         SAFE_DELETE(properties);
         return audioSource;
     }
 
-    // Create an audio buffer from this path.
-    AudioBuffer* buffer = AudioBuffer::create(path);
+    // Create an audio buffer from this URL.
+    AudioBuffer* buffer = AudioBuffer::create(url);
     if (buffer == NULL)
         return NULL;
 
-#ifndef __ANDROID__
     // 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;
     }
     
     return new AudioSource(buffer, alSource);
-#else
-    AudioController* audioController = Game::getInstance()->getAudioController();
-    SLDataLocator_OutputMix locator = {SL_DATALOCATOR_OUTPUTMIX, audioController->_outputMixObject};
-
-    SLDataSource dataSource = {&buffer->_data, &buffer->_mime};
-    SLDataSink dataSink = {&locator, NULL};
-
-    SLObjectItf player;
-    const SLInterfaceID interfaces[] = {SL_IID_3DDOPPLER, SL_IID_3DLOCATION, SL_IID_PLAY, SL_IID_PITCH, SL_IID_SEEK, SL_IID_VOLUME};
-    const SLboolean required[] = {SL_BOOLEAN_FALSE, SL_BOOLEAN_FALSE, SL_BOOLEAN_FALSE, SL_BOOLEAN_FALSE, SL_BOOLEAN_FALSE, SL_BOOLEAN_FALSE};
-    SLresult result = (*audioController->_engineEngine)->CreateAudioPlayer(audioController->_engineEngine, &player, &dataSource, &dataSink, 6, interfaces, required);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        WARN("AudioSource::create - Failed to create OpenSL audio player.");
-        return NULL;
-    }
-
-    result = (*player)->Realize(player, SL_BOOLEAN_FALSE);
-    if (result != SL_RESULT_SUCCESS)
-    {
-        WARN("AudioSource::create - Failed to realize OpenSL audio player.");
-    }
-
-    return new AudioSource(buffer, player);
-#endif
 }
 
 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;
     }
 
@@ -190,20 +90,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"));
     }
@@ -218,9 +118,8 @@ AudioSource* AudioSource::create(Properties* properties)
 
 AudioSource::State AudioSource::getState() const
 {
-#ifndef __ANDROID__
     ALint state;
-    alGetSourcei(_alSource, AL_SOURCE_STATE, &state);
+    AL_CHECK( alGetSourcei(_alSource, AL_SOURCE_STATE, &state) );
 
     switch (state)
     {
@@ -233,63 +132,34 @@ AudioSource::State AudioSource::getState() const
         default:         
             return INITIAL;
     }
-#else
-    if (_playerPlay != NULL)
-    {
-        SLuint32 state;
-        SLresult result = (*_playerPlay)->GetPlayState(_playerPlay, &state);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::getState() failed to get player state.");
-        }
-
-        switch (state)
-        {
-            case SL_PLAYSTATE_PLAYING:
-                return PLAYING;
-            case SL_PLAYSTATE_PAUSED:
-                return PAUSED;
-            case SL_PLAYSTATE_STOPPED:
-                return STOPPED;
-            default:
-                return INITIAL;
-        }
-    }
-#endif
-
     return INITIAL;
 }
 
 void AudioSource::play()
 {
-#ifndef __ANDROID__
-    alSourcePlay(_alSource);
-#else
-    if (_playerPlay != NULL)
-    {
-        SLresult result = (*_playerPlay)->SetPlayState(_playerPlay, SL_PLAYSTATE_PLAYING);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::play() failed to set player state.");
-        }
-    }
-#endif
+    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()
 {
-#ifndef __ANDROID__
-    alSourcePause(_alSource);
-#else
-    if (_playerPlay != NULL)
+    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)
     {
-        SLresult result = (*_playerPlay)->SetPlayState(_playerPlay, SL_PLAYSTATE_PAUSED);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::pause() failed to set player state.");
-        }
+        std::set<AudioSource*>::iterator iter = audioController->_playingSources.find(this);
+        if (iter != audioController->_playingSources.end())
+            audioController->_playingSources.erase(iter);
     }
-#endif
 }
 
 void AudioSource::resume()
@@ -302,34 +172,19 @@ void AudioSource::resume()
 
 void AudioSource::stop()
 {
-#ifndef __ANDROID__
-    alSourceStop(_alSource);
-#else
-    if (_playerPlay != NULL)
-    {
-        SLresult result = (*_playerPlay)->SetPlayState(_playerPlay, SL_PLAYSTATE_STOPPED);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::stop() failed to set player state.");
-        }
-    }
-#endif 
+    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);
 }
 
 void AudioSource::rewind()
 {
-#ifndef __ANDROID__
-    alSourceRewind(_alSource);
-#else
-    if (_playerPlay != NULL)
-    {
-        SLresult result = (*_playerPlay)->SetMarkerPosition(_playerPlay, 0);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::rewind() failed to set player marker position.");
-        }
-    }
-#endif
+    AL_CHECK( alSourceRewind(_alSource) );
 }
 
 bool AudioSource::isLooped() const
@@ -339,27 +194,11 @@ bool AudioSource::isLooped() const
 
 void AudioSource::setLooped(bool looped)
 {
-#ifndef __ANDROID__
-     // Clear error state.
-    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());
     }
-#else
-    if (_playerSeek)
-    {
-        SLresult result = (*_playerSeek)->SetLoop(_playerSeek, looped, 0, SL_TIME_UNKNOWN);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::setLooped() failed.");
-        }
-    }
-#endif
-
     _looped = looped;
 }
 
@@ -370,19 +209,7 @@ float AudioSource::getGain() const
 
 void AudioSource::setGain(float gain)
 {
-#ifndef __ANDROID__
-    alSourcef(_alSource, AL_GAIN, gain);
-#else
-    if (_playerVolume)
-    {
-        SLmillibel volume = (gain < MATH_EPSILON) ? SL_MILLIBEL_MIN : (10.0f * log10(gain)) * 100;
-        SLresult result = (*_playerVolume)->SetVolumeLevel(_playerVolume, volume);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::setGain() failed to set player gain.");
-        }
-    }
-#endif
+    AL_CHECK( alSourcef(_alSource, AL_GAIN, gain) );
     _gain = gain;
 }
 
@@ -393,18 +220,7 @@ float AudioSource::getPitch() const
 
 void AudioSource::setPitch(float pitch)
 {
-#ifndef __ANDROID__
-    alSourcef(_alSource, AL_PITCH, pitch);
-#else
-    if (_playerPitch)
-    {
-        SLresult result = (*_playerPitch)->SetPitch(_playerPitch, (SLpermille)(pitch * 1000));
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::setPitch() failed to set player pitch.");
-        }
-    }
-#endif
+    AL_CHECK( alSourcef(_alSource, AL_PITCH, pitch) );
     _pitch = pitch;
 }
 
@@ -415,22 +231,7 @@ const Vector3& AudioSource::getVelocity() const
 
 void AudioSource::setVelocity(const Vector3& velocity)
 {
-#ifndef __ANDROID__
-    alSourcefv(_alSource, AL_VELOCITY, (ALfloat*)&velocity);
-#else
-    if (_playerDoppler)
-    {
-        SLVec3D v;
-        v.x = velocity.x;
-        v.y = velocity.y;
-        v.z = velocity.z;
-        SLresult result = (*_playerDoppler)->SetVelocityCartesian(_playerDoppler, &v);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::setVelocity - failed to set velocity.");
-        }
-    }
-#endif
+    AL_CHECK( alSourcefv(_alSource, AL_VELOCITY, (ALfloat*)&velocity) );
     _velocity = velocity;
 }
 
@@ -463,44 +264,26 @@ void AudioSource::setNode(Node* node)
 
 void AudioSource::transformChanged(Transform* transform, long cookie)
 {
-#ifndef __ANDROID__
     if (_node)
     {
         Vector3 translation = _node->getTranslationWorld();
-        alSourcefv(_alSource, AL_POSITION, (const ALfloat*)&translation.x);
+        AL_CHECK( alSourcefv(_alSource, AL_POSITION, (const ALfloat*)&translation.x) );
     }
-#else
-    if (_playerLocation)
-    {
-        SLVec3D position;
-        position.x = transform->getTranslationX();
-        position.y = transform->getTranslationY();
-        position.z = transform->getTranslationZ();
-        SLresult result = (*_playerLocation)->SetLocationCartesian(_playerLocation, &position);
-        if (result != SL_RESULT_SUCCESS)
-        {
-            WARN("AudioSource::transformChanged - failed to update location.");
-        }
-    }
-#endif
 }
 
 AudioSource* AudioSource::clone(NodeCloneContext &context) const
 {
-#ifndef __ANDROID__
+    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);
-#else
-    // TODO: Implement cloning audio source for Android
-    AudioSource* audioClone = new AudioSource(_buffer, _playerObject);
 
-#endif
     _buffer->addRef();
     audioClone->setLooped(isLooped());
     audioClone->setGain(getGain());

+ 5 - 22
gameplay/src/AudioSource.h

@@ -34,13 +34,14 @@ public:
     };
 
     /**
-     * Create an audio source. This is used to instantiate an Audio Source. Currently only wav, au, raw and .audio files are supported.
-     *
-     * @param path The relative location on disk of the sound file or .audio file.
+     * Create an audio source. This is used to instantiate an Audio Source. Currently only wav, au, and raw files are supported.
+     * Alternately, a URL specifying a Properties object that defines an audio source can be used (where the URL is of the format
+     * "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>" and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional).
      * 
+     * @param url The relative location on disk of the sound file or a URL specifying a Properties object defining an audio source.
      * @return The newly created audio source, or NULL if an audio source cannot be created.
      */
-    static AudioSource* create(const char* path);
+    static AudioSource* create(const char* url);
 
     /**
      * Create an audio source from the given properties object.
@@ -147,17 +148,10 @@ public:
 
 private:
 
-#ifndef __ANDROID__
     /**
      * Constructor that takes an AudioBuffer.
      */
     AudioSource(AudioBuffer* buffer, ALuint source);
-#else
-    /**
-     * Constructor that takes an AudioBuffer.
-     */
-    AudioSource(AudioBuffer* buffer, const SLObjectItf& player);
-#endif
 
     /**
      * Destructor.
@@ -183,18 +177,7 @@ private:
      */
     AudioSource* clone(NodeCloneContext &context) const;
 
-#ifndef __ANDROID__
     ALuint _alSource;
-#else
-    SLObjectItf _playerObject;
-    SL3DDopplerItf _playerDoppler;
-    SL3DLocationItf _playerLocation;
-    SLPlayItf _playerPlay;
-    SLPitchItf _playerPitch;
-    SLSeekItf _playerSeek;
-    SLVolumeItf _playerVolume;
-    SLmillibel _maxVolume;
-#endif
     AudioBuffer* _buffer;
     bool _looped;
     float _gain;

+ 60 - 80
gameplay/src/Base.h

@@ -40,10 +40,6 @@ using std::min;
 using std::max;
 using std::modf;
 
-#ifdef __ANDROID__
-#include <android/asset_manager.h>
-#endif
-
 // Common
 #ifndef NULL
 #define NULL     0
@@ -55,50 +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
+// Assert macros.
+#ifdef _DEBUG
+#ifdef WIN32
+#define GP_FORCE_ASSERTION_FAILURE do { __debugbreak(); } while (0)
+#else
+#define GP_FORCE_ASSERTION_FAILURE do { assert(0); } while (0)
 #endif
-#define WARN(x) LOGI(x)
-#define WARN_VARG(x, ...) LOGI(x, __VA_ARGS__)
 
+#define GP_ASSERT(expression) do { \
+    if (!(expression)) \
+    { \
+        printError("%s -- Assertion '" #expression "' failed.\n", __FUNCTION__); \
+        GP_FORCE_ASSERTION_FAILURE; \
+    } } while (0)
 #else
+#define GP_FORCE_ASSERTION_FAILURE do { (void)sizeof(int); } while (0)
+#define GP_ASSERT(expression) do { (void)sizeof(expression); } while (0)
+#endif
 
-// System Errors
-#define LOG_ERROR(x) \
+// Error macro.
+#define GP_ERROR(...) do \
     { \
-        printError(x); \
-        assert(#x == 0); \
-    }
-#define LOG_ERROR_VARG(x, ...) \
+        printError("%s -- ", __FUNCTION__); \
+        printError(__VA_ARGS__); \
+        printError("\n"); \
+        GP_FORCE_ASSERTION_FAILURE; \
+        std::exit(-1); \
+    } while (0)
+
+// Warning macro.
+#define GP_WARN(...) 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__)
-#endif
+        printError("%s -- ", __FUNCTION__); \
+        printError(__VA_ARGS__); \
+        printError("\n"); \
+    } while (0)
 
 // Bullet Physics
 #include <btBulletDynamicsCommon.h>
@@ -163,12 +151,8 @@ extern void printError(const char* format, ...);
     #define NOMINMAX
 #endif
 
-// Audio (OpenAL, OpenSL, OggVorbis)
-#ifdef __ANDROID__
-#include <SLES/OpenSLES.h>
-#include <SLES/OpenSLES_Android.h>
-#else
-#ifdef __QNX__
+// Audio (OpenAL/Vorbis)
+#if defined (__QNX__) || defined(__ANDROID__)
 #include <AL/al.h>
 #include <AL/alc.h>
 #elif WIN32
@@ -179,13 +163,11 @@ extern void printError(const char* format, ...);
 #include <OpenAL/alc.h>
 #endif
 #include <vorbis/vorbisfile.h>
-#endif
 
 // Image
 #include <png.h>
 
 #define WINDOW_VSYNC        1
-#define WINDOW_FULLSCREEN   0
 
 // Graphics (OpenGL)
 #if defined (__QNX__) || defined(__ANDROID__)
@@ -263,11 +245,7 @@ typedef GLuint RenderBufferHandle;
     { \
         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); \
     }
 #endif
 
@@ -287,7 +265,7 @@ typedef GLuint RenderBufferHandle;
         __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); \
         } \
     }
 
@@ -299,6 +277,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 ) \
+    { \
+        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); \
+        } \
+    }
+
+// 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 )
@@ -309,28 +313,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;

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 398 - 101
gameplay/src/Bundle.cpp


+ 31 - 2
gameplay/src/Bundle.h

@@ -16,6 +16,7 @@ namespace gameplay
 class Bundle : public Ref
 {
     friend class PhysicsController;
+    friend class SceneLoader;
 
 public:
 
@@ -205,6 +206,11 @@ private:
      */
     Node* loadNode(const char* id, Scene* sceneContext, Node* nodeContext);
 
+    /**
+     * Internal method for SceneLoader to load a node into a scene.
+     */
+    Node* loadNode(const char* id, Scene* sceneContext);
+
     /**
      * Loads a mesh with the specified ID from the bundle.
      *
@@ -321,7 +327,7 @@ private:
      * 
      * @return A pointer to a new model or NULL if there was an error.
      */
-    Model* readModel(Scene* sceneContext, Node* nodeContext, const char* nodeId);
+    Model* readModel(const char* nodeId);
 
     /**
      * Reads mesh data from the current file position.
@@ -346,7 +352,7 @@ private:
      *
      * @return A pointer to a new mesh skin or NULL if there was an error.
      */
-    MeshSkin* readMeshSkin(Scene* sceneContext, Node* nodeContext);
+    MeshSkin* readMeshSkin();
 
     /**
      * Reads an animation from the current file position.
@@ -373,6 +379,21 @@ private:
      */
     Animation* readAnimationChannel(Scene* scene, Animation* animation, const char* animationId);
 
+    /**
+     * Reads the animation channel data at the current file position into the given animation
+     * (with the given animation target and target attribute).
+     * 
+     * Note: this is used by #loadNode(const char*, Scene*) and #readAnimationChannel(Scene*, Animation*, const char*).
+     * 
+     * @param animation The animation to the load channel into.
+     * @param id The ID of the animation that this channel is loaded into.
+     * @param target The animation target.
+     * @param targetAttribute The target attribute being animated.
+     * 
+     * @return The animation that the channel was loaded into.
+     */
+    Animation* readAnimationChannelData(Animation* animation, const char* id, AnimationTarget* target, unsigned int targetAttribute);
+
     /**
      * Sets the transformation matrix.
      *
@@ -388,12 +409,20 @@ private:
 
 private:
 
+    /**
+     * Skips over a Node's data within a bundle.
+     *
+     * @return True if the Node was successfully skipped; false otherwise.
+     */
+    bool skipNode();
+
     std::string _path;
     unsigned int _referenceCount;
     Reference* _references;
     FILE* _file;
 
     std::vector<MeshSkinData*> _meshSkins;
+    std::map<std::string, Node*>* _trackedNodes;
 };
 
 }

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

+ 13 - 18
gameplay/src/CheckBox.cpp

@@ -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 Rectangle& clip, const Vector2& offset)
 {
-    Label::update(clip);
+    Label::update(clip, 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);
 }
 
 }

+ 1 - 1
gameplay/src/CheckBox.h

@@ -120,7 +120,7 @@ protected:
      *
      * @param clip The clipping rectangle of this control's parent container.
      */
-    void update(const Rectangle& clip);
+    void update(const Rectangle& clip, const Vector2& offset = Vector2::zero());
 
     /**
      * Draw the checkbox icon associated with this control.

+ 336 - 289
gameplay/src/Container.cpp

@@ -4,400 +4,447 @@
 #include "AbsoluteLayout.h"
 #include "FlowLayout.h"
 #include "VerticalLayout.h"
+#include "ScrollLayout.h"
 #include "Label.h"
 #include "Button.h"
 #include "CheckBox.h"
 #include "RadioButton.h"
 #include "Slider.h"
 #include "TextBox.h"
+#include "Game.h"
 
 namespace gameplay
 {
-    Container::Container() : _layout(NULL)
-    {
-    }
 
-    Container::Container(const Container& copy)
+Container::Container() : _layout(NULL)
+{
+}
+
+Container::Container(const Container& copy)
+{
+}
+
+Container::~Container()
+{
+    std::vector<Control*>::iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
+        SAFE_RELEASE((*it));
     }
 
-    Container::~Container()
-    {
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            SAFE_RELEASE((*it));
-        }
+    SAFE_RELEASE(_layout);
+}
 
-        SAFE_RELEASE(_layout);
+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;
+    case Layout::LAYOUT_SCROLL:
+        layout = ScrollLayout::create();
+        break;
     }
 
-    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 = new Container();
+    container->_layout = layout;
 
-        Container* container = new Container();
-        container->_layout = layout;
+    return container;
+}
 
-        return container;
-    }
+Container* Container::create(Theme::Style* style, Properties* properties, Theme* theme)
+{
+    GP_ASSERT(properties);
 
-    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);
+    const char* layoutString = properties->getString("layout");
+    Container* container = Container::create(getLayoutType(layoutString));
+    container->initialize(style, properties);
+    container->addControls(theme, properties);
 
-        return container;
-    }
+    return container;
+}
 
-    void Container::addControls(Theme* theme, Properties* properties)
-    {
-        // Add all the controls to this container.
-        Properties* controlSpace = properties->getNextNamespace();
-        while (controlSpace != NULL)
-        {
-            Control* control = NULL;
+void Container::addControls(Theme* theme, Properties* properties)
+{
+    GP_ASSERT(theme);
+    GP_ASSERT(properties);
 
-            const char* controlStyleName = controlSpace->getString("style");
-            Theme::Style* controlStyle = NULL;
-            if (controlStyleName)
-            {
-                 controlStyle = theme->getStyle(controlStyleName);
-            }
-            assert(controlStyle);
+    // Add all the controls to this container.
+    Properties* controlSpace = properties->getNextNamespace();
+    while (controlSpace != NULL)
+    {
+        Control* control = NULL;
 
-            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);
-            }
+        const char* controlStyleName = controlSpace->getString("style");
+        Theme::Style* controlStyle = NULL;
+        GP_ASSERT(controlStyleName);
+        controlStyle = theme->getStyle(controlStyleName);
+        GP_ASSERT(controlStyle);
 
-            // Add the new control to the form.
-            if (control)
-            {
-                addControl(control);
-            }
+        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
+        {
+            GP_ERROR("Failed to create control; unrecognized control name '%s'.", controlName.c_str());
+        }
 
-            // Get the next control.
-            controlSpace = properties->getNextNamespace();
+        // Add the new control to the form.
+        if (control)
+        {
+            addControl(control);
         }
-    }
 
-    Layout* Container::getLayout()
-    {
-        return _layout;
+        // Get the next control.
+        controlSpace = properties->getNextNamespace();
     }
+}
 
-    unsigned int Container::addControl(Control* control)
-    {
-        _controls.push_back(control);
+Layout* Container::getLayout()
+{
+    return _layout;
+}
 
-        return _controls.size() - 1;
-    }
+unsigned int Container::addControl(Control* control)
+{
+    GP_ASSERT(control);
+    _controls.push_back(control);
 
-    void Container::insertControl(Control* control, unsigned int index)
-    {
-        std::vector<Control*>::iterator it = _controls.begin() + index;
-        _controls.insert(it, control);
-    }
+    return _controls.size() - 1;
+}
 
-    void Container::removeControl(unsigned int index)
-    {
-        std::vector<Control*>::iterator it = _controls.begin() + index;
-        _controls.erase(it);
-    }
+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(const char* id)
+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++)
     {
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        Control* c = *it;
+        if (strcmp(id, c->getID()) == 0)
         {
-            Control* c = *it;
-            if (strcmp(id, c->getID()) == 0)
-            {
-                _controls.erase(it);
-            }
+            _controls.erase(it);
+            return;
         }
     }
+}
 
-    void Container::removeControl(Control* control)
+void Container::removeControl(Control* control)
+{
+    GP_ASSERT(control);
+    std::vector<Control*>::iterator it;
+    for (it = _controls.begin(); it < _controls.end(); it++)
     {
-        std::vector<Control*>::iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
+        if (*it == control)
         {
-            if (*it == control)
-            {
-                _controls.erase(it);
-            }
+            _controls.erase(it);
+            return;
         }
     }
+}
 
-    Control* Container::getControl(unsigned int index) const
-    {
-        std::vector<Control*>::const_iterator it = _controls.begin() + index;
-        return *it;
-    }
+Control* Container::getControl(unsigned int index) const
+{
+    std::vector<Control*>::const_iterator it = _controls.begin() + index;
+    return *it;
+}
 
-    Control* Container::getControl(const char* id) const
+Control* Container::getControl(const char* id) const
+{
+    GP_ASSERT(id);
+    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* c = *it;
+        GP_ASSERT(c);
+        if (strcmp(id, c->getID()) == 0)
         {
-            Control* c = *it;
-            if (strcmp(id, c->getID()) == 0)
-            {
-                return c;
-            }
-            else if (c->isContainer())
+            return c;
+        }
+        else if (c->isContainer())
+        {
+            Control* cc = ((Container*)c)->getControl(id);
+            if (cc)
             {
-                Control* cc = ((Container*)c)->getControl(id);
-                if (cc)
-                {
-                    return cc;
-                }
+                return cc;
             }
         }
-
-        return NULL;
     }
 
-    std::vector<Control*> Container::getControls() const
-    {
-        return _controls;
-    }
+    return NULL;
+}
 
-    Animation* Container::getAnimation(const char* id) const
-    {
-        std::vector<Control*>::const_iterator itr = _controls.begin();
-        std::vector<Control*>::const_iterator end = _controls.end();
+const std::vector<Control*>& Container::getControls() const
+{
+    return _controls;
+}
+
+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++)
+    Control* control = NULL;
+    for (; itr != end; itr++)
+    {
+        control = *itr;
+        GP_ASSERT(control);
+        Animation* animation = control->getAnimation(id);
+        if (animation)
+            return animation;
+
+        if (control->isContainer())
         {
-            control = *itr;
-            Animation* animation = control->getAnimation(id);
+            animation = ((Container*)control)->getAnimation(id);
             if (animation)
                 return animation;
-
-            if (control->isContainer())
-            {
-                animation = ((Container*)control)->getAnimation(id);
-                if (animation)
-                    return animation;
-            }
         }
-
-        return NULL;
     }
 
-    void Container::update(const Rectangle& clip)
-    {
-        // Update this container's viewport.
-        Control::update(clip);
+    return NULL;
+}
 
-        _layout->update(this);
-    }
+void Container::update(const Rectangle& clip, const Vector2& offset)
+{
+    // Update this container's viewport.
+    Control::update(clip, offset);
 
-    void Container::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
-    {
-        // First draw our own border.
-        Control::drawBorder(spriteBatch, clip);
+    GP_ASSERT(_layout);
+    _layout->update(this);
+}
 
-        // Now call drawBorder on all controls within this container.
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            control->drawBorder(spriteBatch, _clip);
-        }
+void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, float targetHeight)
+{
+    if (_skin && needsClear)
+    {
+        GL_ASSERT( glEnable(GL_SCISSOR_TEST) );
+        GL_ASSERT( glClearColor(0, 0, 0, 1) );
+        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;
     }
 
-    void Container::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+    Control::drawBorder(spriteBatch, clip);
+
+    std::vector<Control*>::const_iterator it;
+    Rectangle boundsUnion = Rectangle::empty();
+    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 (!needsClear || control->isDirty() || control->_clearBounds.intersects(boundsUnion))
         {
-            Control* control = *it;
-            control->drawImages(spriteBatch, _clip);
+            control->draw(spriteBatch, _viewportClipBounds, needsClear, targetHeight);
+            Rectangle::combine(control->_clearBounds, boundsUnion, &boundsUnion);
         }
-
-        _dirty = false;
     }
 
-    void Container::drawText(const Rectangle& clip)
-    {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            control->drawText(_clip);
-        }
+    _dirty = false;
+}
 
-        _dirty = false;
+bool Container::isDirty()
+{
+    if (_dirty)
+    {
+        return true;
     }
-
-    bool Container::isDirty()
+    else
     {
-        if (_dirty)
-        {
-            return true;
-        }
-        else
+        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++)
+            GP_ASSERT(*it);
+            if ((*it)->isDirty())
             {
-                if ((*it)->isDirty())
-                {
-                    return true;
-                }
+                return true;
             }
         }
-
-        return false;
     }
 
-    bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    return false;
+}
+
+bool Container::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+{
+    if (!isEnabled())
     {
-        if (!isEnabled())
-        {
-            return false;
-        }
+        return false;
+    }
 
-        bool eventConsumed = false;
+    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;
 
-        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 (_layout->getType() == Layout::LAYOUT_SCROLL)
+    {
+        offset = &((ScrollLayout*)_layout)->_scrollPosition;
+    }
 
-        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;
-            if (!control->isEnabled())
-            {
-                continue;
-            }
-
-            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))
-            {
-                // Pass on the event's clip relative to the control.
-                eventConsumed |= control->touchEvent(evt, x - xPos - bounds.x, y - yPos - bounds.y, contactIndex);
-            }
+            continue;
         }
 
-        if (!isEnabled())
+        const Rectangle& bounds = control->getBounds();
+        float boundsX = bounds.x;
+        float boundsY = bounds.y;
+        if (offset)
         {
-            return (_consumeTouchEvents | eventConsumed);
+            boundsX += offset->x;
+            boundsY += offset->y;
         }
 
-        switch (evt)
+        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))
         {
-        case Touch::TOUCH_PRESS:
-            setState(Control::FOCUS);
-            break;
-        case Touch::TOUCH_RELEASE:
-            setState(Control::NORMAL);
-            break;
+            // Pass on the event's clip relative to the control.
+            eventConsumed |= control->touchEvent(evt, x - xPos - boundsX, y - yPos - boundsY, contactIndex);
         }
-
-        return (_consumeTouchEvents | eventConsumed);
     }
 
-    void Container::keyEvent(Keyboard::KeyEvent evt, int key)
+    if (!isEnabled())
     {
-        std::vector<Control*>::const_iterator it;
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
-            if (!control->isEnabled())
-            {
-                continue;
-            }
-
-            if (control->isContainer() || control->getState() == Control::FOCUS)
-            {
-                control->keyEvent(evt, key);
-            }
-        }
+        return (_consumeTouchEvents | eventConsumed);
     }
 
-    bool Container::isContainer()
+    switch (evt)
     {
-        return true;
+    case Touch::TOUCH_PRESS:
+        setState(Control::FOCUS);
+        break;
+    case Touch::TOUCH_RELEASE:
+        setState(Control::NORMAL);
+        break;
     }
 
-    Layout::Type Container::getLayoutType(const char* layoutString)
+    if (!eventConsumed)
     {
-        if (!layoutString)
+        // Pass the event on to the layout.
+        if (_layout->touchEvent(evt, x - xPos, y - yPos, contactIndex))
         {
-            return Layout::LAYOUT_ABSOLUTE;
+            _dirty = true;
         }
+    }
 
-        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 (_consumeTouchEvents | eventConsumed);
+}
+
+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;
+        GP_ASSERT(control);
+        if (!control->isEnabled())
         {
-            return Layout::LAYOUT_FLOW;
+            continue;
         }
-        else
+
+        if (control->isContainer() || control->getState() == Control::FOCUS)
         {
-            // Default.
-            return Layout::LAYOUT_ABSOLUTE;
+            control->keyEvent(evt, key);
         }
     }
 }
+
+bool Container::isContainer()
+{
+    return true;
+}
+
+Layout::Type Container::getLayoutType(const char* layoutString)
+{
+    if (!layoutString)
+    {
+        return Layout::LAYOUT_ABSOLUTE;
+    }
+
+    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;
+    }
+}
+
+}

+ 7 - 5
gameplay/src/Container.h

@@ -112,7 +112,7 @@ 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.
@@ -158,7 +158,7 @@ protected:
      *
      * @param clip The clipping rectangle of this container's parent container.
      */
-    virtual void update(const Rectangle& clip);
+    virtual void update(const Rectangle& clip, const Vector2& offset);
 
     /**
      * Draws the themed border and background of this container and all its controls.
@@ -166,7 +166,7 @@ protected:
      * @param spriteBatch The sprite batch containing this container's border images.
      * @param clip The clipping rectangle of this container's parent container.
      */
-    void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
+    //void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip, const Vector2& offset = Vector2::zero());
 
     /**
      * Draws the icons of all controls within this container.
@@ -174,14 +174,14 @@ protected:
      * @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);
+    //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 drawText(const Rectangle& clip);
 
     /**
      * Touch callback on touch events.  Controls return true if they consume the touch event.
@@ -244,6 +244,8 @@ protected:
 private:
 
     Container(const Container& copy);
+
+    virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, float targetHeight);
 };
 
 }

+ 991 - 886
gameplay/src/Control.cpp

@@ -4,1175 +4,1280 @@
 
 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(const Control& copy)
-    {
-    }
+Control::Control()
+    : _id(""), _state(Control::NORMAL), _bounds(Rectangle::empty()), _clipBounds(Rectangle::empty()), _viewportClipBounds(Rectangle::empty()),
+        _dirty(true), _consumeTouchEvents(true), _listeners(NULL), _styleOverridden(false)
+{
+}
+
+Control::Control(const Control& copy)
+{
+}
 
-    Control::~Control()
+Control::~Control()
+{
+    if (_listeners)
     {
-        if (_listeners)
+        for (std::map<Listener::EventType, std::list<Listener*>*>::const_iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
         {
-            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);
+            std::list<Listener*>* list = itr->second;
+            SAFE_DELETE(list);
         }
+        SAFE_DELETE(_listeners);
+    }
 
-        if (_styleOverridden)
-        {
-            SAFE_DELETE(_style);
-        }
+    if (_styleOverridden)
+    {
+        SAFE_DELETE(_style);
     }
+}
+
+void Control::initialize(Theme::Style* style, Properties* properties)
+{
+    GP_ASSERT(properties);
+    _style = style;
+
+    // Properties not defined by the style.
+    _alignment = getAlignment(properties->getString("alignment"));
+    _autoWidth = properties->getBool("autoWidth");
+    _autoHeight = properties->getBool("autoHeight");
 
-    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"))
+    {
+        properties->getVector2("size", &size);
+    }
+    else
     {
-        _style = style;
+        size.x = properties->getFloat("width");
+        size.y = properties->getFloat("height");
+    }
+    setBounds(Rectangle(position.x, position.y, size.x, size.y));
 
-        // Properties not defined by the style.
-        _alignment = getAlignment(properties->getString("alignment"));
-        _autoWidth = properties->getBool("autoWidth");
-        _autoHeight = properties->getBool("autoHeight");
+    _state = Control::getState(properties->getString("state"));
 
-        Vector2 position;
-        Vector2 size;
-        if (properties->exists("position"))
+    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* 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);
-
-        for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
-        {
-            overlays[i]->setTextRightToLeft(rightToLeft);
-        }
+    _dirty = true;
+}
 
-        _dirty = true;
-    }
+Font::Justify Control::getTextAlignment(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getTextAlignment();
+}
 
-    bool Control::getTextRightToLeft(State state) const
-    {
-        return getOverlay(state)->getTextRightToLeft();
-    }
+void Control::setTextRightToLeft(bool rightToLeft, unsigned char states)
+{
+    overrideStyle();
+    Theme::Style::Overlay* overlays[Theme::Style::OVERLAY_MAX] = { 0 };
+    getOverlays(states, overlays);
 
-    const Rectangle& Control::getClipBounds() const
+    for (int i = 0; i < Theme::Style::OVERLAY_MAX - 1 && overlays[i]; ++i)
     {
-        return _clipBounds;
+        overlays[i]->setTextRightToLeft(rightToLeft);
     }
 
-    const Rectangle& Control::getClip() const
-    {
-        return _clip;
-    }
+    _dirty = true;
+}
 
-    void Control::setStyle(Theme::Style* style)
-    {
-        if (style != _style)
-        {
-            _dirty = true;
-        }
+bool Control::getTextRightToLeft(State state) const
+{
+    Theme::Style::Overlay* overlay = getOverlay(state);
+    GP_ASSERT(overlay);
+    return overlay->getTextRightToLeft();
+}
 
-        _style = style;
-    }
+const Rectangle& Control::getClipBounds() const
+{
+    return _clipBounds;
+}
+
+const Rectangle& Control::getClip() const
+{
+    return _viewportClipBounds;
+}
 
-    Theme::Style* Control::getStyle() const
+void Control::setStyle(Theme::Style* style)
+{
+    if (style != _style)
     {
-        return _style;
+        _dirty = true;
     }
 
-    void Control::setState(State state)
-    {
-        _state = state;
+    _style = 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;
+}
+
+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::setConsumeTouchEvents(bool consume)
+void Control::addSpecificListener(Control::Listener* listener, Listener::EventType eventType)
+{
+    GP_ASSERT(listener);
+
+    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 Rectangle& clip, const Vector2& offset)
+{
+    _clearBounds.set(_absoluteClipBounds);
 
-        switch (evt)
-        {
-        case Touch::TOUCH_PRESS:
-            notifyListeners(Listener::PRESS);
-            break;
-            
-        case Touch::TOUCH_RELEASE:
-            // Always trigger Listener::RELEASE
-            notifyListeners(Listener::RELEASE);
+    // Calculate the clipped bounds.
+    float x = _bounds.x + offset.x;
+    float y = _bounds.y + offset.y;
+    float width = _bounds.width;
+    float height = _bounds.height;
 
-            // 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;
-        }
+    float clipX2 = clip.x + clip.width;
+    float x2 = clip.x + x + width;
+    if (x2 > clipX2)
+        width -= x2 - clipX2;
 
-        return _consumeTouchEvents;
-    }
+    float clipY2 = clip.y + clip.height;
+    float y2 = clip.y + y + height;
+    if (y2 > clipY2)
+        height -= y2 - clipY2;
 
-    void Control::keyEvent(Keyboard::KeyEvent evt, int key)
+    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 + clip.x;
+    y = _bounds.y + offset.y + clip.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)
-    {
-        if (!_skin || _bounds.width <= 0 || _bounds.height <= 0)
-            return;
+void Control::drawImages(SpriteBatch* spriteBatch, const Rectangle& position)
+{
+}
 
-        Vector2 pos(clip.x + _bounds.x, clip.y + _bounds.y);
+void Control::drawText(const Rectangle& position)
+{
+}
 
-        // 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);
+void Control::draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, float targetHeight)
+{
+    if (needsClear)
+    {
+        GL_ASSERT( glEnable(GL_SCISSOR_TEST) );
+        GL_ASSERT( glClearColor(0, 0, 0, 1) );
+        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) );
+    }
 
-        // Calculate screen-space positions.
-        const Theme::Border& border = getBorder(_state);
-        const Theme::Padding& padding = getPadding();
-        Vector4 skinColor = _skin->getColor();
-        skinColor.w *= _opacity;
+    drawBorder(spriteBatch, clip);
+    drawImages(spriteBatch, clip);
+    drawText(clip);
+    _dirty = false;
+}
 
-        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::isDirty()
+{
+    return _dirty;
+}
 
-        // 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);
-        }
-    }
+bool Control::isContainer()
+{
+    return false;
+}
 
-    void Control::drawImages(SpriteBatch* spriteBatch, const Rectangle& position)
+Control::State Control::getState(const char* state)
+{
+    if (!state)
     {
+        return NORMAL;
     }
 
-    void Control::drawText(const Rectangle& position)
+    if (strcmp(state, "NORMAL") == 0)
     {
+        return NORMAL;
     }
-
-    bool Control::isDirty()
+    else if (strcmp(state, "ACTIVE") == 0)
     {
-        return _dirty;
+        return ACTIVE;
     }
-
-    bool Control::isContainer()
+    else if (strcmp(state, "FOCUS") == 0)
     {
-        return false;
+        return FOCUS;
     }
-
-    Control::State Control::getState(const char* state)
+    else if (strcmp(state, "DISABLED") == 0)
     {
-        if (!state)
-        {
-            return NORMAL;
-        }
+        return DISABLED;
+    }
 
-        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;
-        }
+    return NORMAL;
+}
 
-        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();
+    GP_ASSERT(imageList);
+    return imageList->getImage(id);
+}
 
-    Theme::ThemeImage* Control::getImage(const char* id, State state)
+// Implementation of AnimationHandler
+unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
+{
+    switch(propertyId)
     {
-        return getOverlay(state)->getImageList()->getImage(id);
+    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;
     }
+}
 
-    // Implementation of AnimationHandler
-    unsigned int Control::getAnimationPropertyComponentCount(int propertyId) const
-    {
-        switch(propertyId)
-        {
-        case ANIMATE_POSITION:
-        case ANIMATE_SIZE:
-            return 2;
-
-        case ANIMATE_POSITION_X:
-        case ANIMATE_POSITION_Y:
-        case ANIMATE_SIZE_WIDTH:
-        case ANIMATE_SIZE_HEIGHT:
-        case ANIMATE_OPACITY:
-            return 1;
-
-        default:
-            return -1;
-        }
+void Control::getAnimationPropertyValue(int propertyId, AnimationValue* value)
+{
+    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::getAnimationPropertyValue(int propertyId, AnimationValue* value)
+void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
+{
+    GP_ASSERT(value);
+
+    switch(propertyId)
     {
-        switch(propertyId)
-        {
-        case ANIMATE_POSITION:
-            value->setFloat(0, _bounds.x);
-            value->setFloat(1, _bounds.y);
-            break;
-        case ANIMATE_SIZE:
-            value->setFloat(0, _clipBounds.width);
-            value->setFloat(1, _clipBounds.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, _clipBounds.width);
-            break;
-        case ANIMATE_SIZE_HEIGHT:
-            value->setFloat(0, _clipBounds.height);
-            break;
-        case ANIMATE_OPACITY:
-        default:
-            break;
-        }
+    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;
     }
+}
+    
+
+Theme::Style::Overlay** Control::getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays)
+{
+    GP_ASSERT(overlays);
+    GP_ASSERT(_style);
 
-    void Control::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
+    unsigned int index = 0;
+    if ((overlayTypes & NORMAL) == NORMAL)
     {
-        switch(propertyId)
-        {
-        case ANIMATE_POSITION:
-            applyAnimationValuePositionX(value->getFloat(0), blendWeight);
-            applyAnimationValuePositionY(value->getFloat(1), blendWeight);
-            break;
-        case ANIMATE_POSITION_X:
-            applyAnimationValuePositionX(value->getFloat(0), blendWeight);
-            break;
-        case ANIMATE_POSITION_Y:
-            applyAnimationValuePositionY(value->getFloat(0), blendWeight);
-            break;
-        case ANIMATE_SIZE:
-            applyAnimationValueSizeWidth(value->getFloat(0), blendWeight);
-            applyAnimationValueSizeHeight(value->getFloat(1), blendWeight);
-            break;
-        case ANIMATE_SIZE_WIDTH:
-            applyAnimationValueSizeWidth(value->getFloat(0), blendWeight);
-            break;
-        case ANIMATE_SIZE_HEIGHT:
-            applyAnimationValueSizeHeight(value->getFloat(0), blendWeight);
-            break;
-        case ANIMATE_OPACITY:
-            applyAnimationValueOpacity();
-        default:
-            break;
-        }
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
     }
 
-    void Control::applyAnimationValuePositionX(float x, float blendWeight)
+    if ((overlayTypes & FOCUS) == FOCUS)
     {
-        if ((_animationPropertyBitFlag & ANIMATION_POSITION_X_BIT) != ANIMATION_POSITION_X_BIT)
-        {
-            _animationPropertyBitFlag |= ANIMATION_POSITION_X_BIT;
-        }
-        else
-        {
-            x = Curve::lerp(blendWeight, _bounds.x, x);
-        }
-        _bounds.x = x;
-        _dirty = true;
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
     }
-    
-    void Control::applyAnimationValuePositionY(float y, float blendWeight)
+
+    if ((overlayTypes & ACTIVE) == ACTIVE)
     {
-        if ((_animationPropertyBitFlag & ANIMATION_POSITION_Y_BIT) != ANIMATION_POSITION_Y_BIT)
-        {
-            _animationPropertyBitFlag |= ANIMATION_POSITION_Y_BIT;
-        }
-        else
-        {
-            y = Curve::lerp(blendWeight, _bounds.y, y);
-        }
-        _bounds.y = y;
-        _dirty = true;
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
     }
-    
-    void Control::applyAnimationValueSizeWidth(float width, float blendWeight)
+
+    if ((overlayTypes & DISABLED) == DISABLED)
     {
-        if ((_animationPropertyBitFlag & ANIMATION_SIZE_WIDTH_BIT) != ANIMATION_SIZE_WIDTH_BIT)
-        {
-            _animationPropertyBitFlag |= ANIMATION_SIZE_WIDTH_BIT;
-        }
-        else
-        {
-            width = Curve::lerp(blendWeight, _clipBounds.width, width);
-        }
-        _clipBounds.width = width;
-        _dirty = true;
+        overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
     }
 
-    void Control::applyAnimationValueSizeHeight(float height, float blendWeight)
+    return overlays;
+}
+
+Theme::Style::Overlay* Control::getOverlay(State state) const
+{
+    GP_ASSERT(_style);
+
+    switch(state)
     {
-        if ((_animationPropertyBitFlag & ANIMATION_SIZE_HEIGHT_BIT) != ANIMATION_SIZE_HEIGHT_BIT)
-        {
-            _animationPropertyBitFlag |= ANIMATION_SIZE_HEIGHT_BIT;
-        }
-        else
-        {
-            height = Curve::lerp(blendWeight, _clipBounds.height, height);
-        }
-        _clipBounds.height = height;
-        _dirty = true;
+    case Control::NORMAL:
+        return _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
+    case Control::FOCUS:
+        return _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
+    case Control::ACTIVE:
+        return _style->getOverlay(Theme::Style::OVERLAY_ACTIVE);
+    case Control::DISABLED:
+        return _style->getOverlay(Theme::Style::OVERLAY_DISABLED);
+    default:
+        return NULL;
     }
+}
 
-    void Control::applyAnimationValueOpacity()
+void Control::overrideStyle()
+{
+    if (_styleOverridden)
     {
-        if ((_animationPropertyBitFlag & ANIMATION_OPACITY_BIT) != ANIMATION_OPACITY_BIT)
-        {
-            _animationPropertyBitFlag |= ANIMATION_OPACITY_BIT;
-        }
-        _dirty = true;
+        return;
     }
-    
-    Theme::Style::Overlay** Control::getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays)
-    {
-        unsigned int index = 0;
-        if ((overlayTypes & NORMAL) == NORMAL)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_NORMAL);
-        }
 
-        if ((overlayTypes & FOCUS) == FOCUS)
-        {
-            overlays[index++] = _style->getOverlay(Theme::Style::OVERLAY_FOCUS);
-        }
+    // 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;
+}
+
 }

+ 31 - 32
gameplay/src/Control.h

@@ -24,6 +24,7 @@ class Control : public Ref, public AnimationTarget
     friend class AbsoluteLayout;
     friend class VerticalLayout;
     friend class FlowLayout;
+    friend class ScrollLayout;
 
 public:
 
@@ -192,7 +193,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 +201,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 +258,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 +273,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
@@ -735,14 +736,16 @@ protected:
      * properties, such as its text viewport.
      *
      * @param clip The clipping rectangle of this control's parent container.
+     * @param offset Layout-computed positioning offset to add to the control's position.
      */
-    virtual void update(const Rectangle& clip);
+    virtual void update(const Rectangle& clip, 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.
+     * @param offset Layout-computed positioning offset to add to the control's position.
      */
     virtual void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
 
@@ -750,6 +753,7 @@ protected:
      * Draw this control's text.
      *
      * @param clip The clipping rectangle of this control's parent container.
+     * @param offset Layout-computed positioning offset to add to the control's position.
      */
     virtual void drawText(const Rectangle& clip);
 
@@ -784,8 +788,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,25 +824,32 @@ 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;
+
+    /**
+     * Absolute bounds of content area (i.e. without border and padding), before clipping.
+     */
+    Rectangle _viewportBounds;
+
     /**
-     * Flag for whether the Control is dirty.
+     * Absolute bounds of content area (i.e. without border and padding), after clipping.
      */
+    Rectangle _viewportClipBounds;
+
     bool _dirty;
     
     /**
@@ -877,26 +889,10 @@ protected:
 
 private:
 
-    static const char ANIMATION_POSITION_X_BIT = 0x01;
-    static const char ANIMATION_POSITION_Y_BIT = 0x02;
-    static const char ANIMATION_SIZE_WIDTH_BIT = 0x04;
-    static const char ANIMATION_SIZE_HEIGHT_BIT = 0x08;
-    static const char ANIMATION_OPACITY_BIT = 0x10;
-
     /*
      * Constructor.
      */    
     Control(const Control& copy);
-    
-    void applyAnimationValuePositionX(float x, float blendWeight);
-    
-    void applyAnimationValuePositionY(float y, float blendWeight);
-    
-    void applyAnimationValueSizeWidth(float width, float blendWeight);
-    
-    void applyAnimationValueSizeHeight(float height, float blendWeight);
-    
-    void applyAnimationValueOpacity();
 
     Theme::Style::Overlay** getOverlays(unsigned char overlayTypes, Theme::Style::Overlay** overlays);
 
@@ -923,9 +919,12 @@ private:
      * @param clip The clipping rectangle of this control's parent container.
      */
     virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
+
+    virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, bool needsClear, float targetHeight);
     
     bool _styleOverridden;
     Theme::Skin* _skin;
+    Rectangle _clearBounds;         // Previous frame's absolute clip bounds, to be cleared if necessary.
 };
 
 }

+ 0 - 4
gameplay/src/Curve.cpp

@@ -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

+ 5 - 5
gameplay/src/Effect.cpp

@@ -79,7 +79,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
     {
@@ -130,7 +130,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): %s", vshPath == NULL ? "NULL" : vshPath, infoLog == NULL ? "" : infoLog);
         SAFE_DELETE_ARRAY(infoLog);
 
         // Clean up.
@@ -162,7 +162,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 +193,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.
@@ -387,7 +387,7 @@ void Effect::setValue(Uniform* uniform, const Vector4* values, unsigned int coun
 
 void Effect::setValue(Uniform* uniform, const Texture::Sampler* sampler)
 {
-    assert(uniform->_type == GL_SAMPLER_2D);
+    GP_ASSERT(uniform->_type == GL_SAMPLER_2D);
 
     GL_ASSERT( glActiveTexture(GL_TEXTURE0 + uniform->_index) );
 

+ 45 - 15
gameplay/src/FileSystem.cpp

@@ -12,6 +12,7 @@
 #endif
 
 #ifdef __ANDROID__
+#include <android/asset_manager.h>
 extern AAssetManager* __assetManager;
 #endif
 
@@ -50,7 +51,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;
             }
         }
@@ -192,7 +193,7 @@ bool FileSystem::listFiles(const char* dirPath, std::vector<std::string>& files)
 
 FILE* FileSystem::openFile(const char* path, const char* mode)
 {
-    assert(path != NULL);
+    GP_ASSERT(path);
 
     std::string fullPath(__resourcePath);
     fullPath += resolvePath(path);
@@ -206,13 +207,31 @@ FILE* FileSystem::openFile(const char* path, const char* mode)
     if (stat(fullPath.c_str(), &s) != 0)
     {
         AAsset* asset = AAssetManager_open(__assetManager, path, AASSET_MODE_RANDOM);
-        const void* data = AAsset_getBuffer(asset);
-        int length = AAsset_getLength(asset);
-        FILE* file = fopen(fullPath.c_str(), "wb");
-        
-        int ret = fwrite(data, sizeof(unsigned char), length, file);
-        assert(ret == length);
-        fclose(file);
+        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.");
+                    return NULL;
+                }
+                if (ret != length)
+                {
+                    GP_ERROR("Failed to write all data from APK asset to file on file system.");
+                    return NULL;
+                }
+            }
+            else
+            {
+                GP_ERROR("Failed to create file on file system from APK asset.");
+                return NULL;
+            }
+        }
     }
 #endif
     
@@ -239,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;
     }
@@ -263,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; 

+ 6 - 2
gameplay/src/FlowLayout.cpp

@@ -41,7 +41,8 @@ Layout::Type FlowLayout::getType()
 
 void FlowLayout::update(const Container* container)
 {
-    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();
@@ -76,7 +80,7 @@ void FlowLayout::update(const Container* container)
         control->setPosition(xPosition, yPosition);
         if (control->isDirty() || control->isContainer())
         {
-            control->update(container->getClip());
+            control->update(container->getClip(), Vector2::zero());
         }
 
         xPosition += bounds.width + margin.right;

+ 0 - 3
gameplay/src/FlowLayout.h

@@ -6,9 +6,6 @@
 namespace gameplay
 {
 
-/**
- *  Defines a layout that arranges components in a left-to-right or right-to-left flow. 
- */
 class FlowLayout : public Layout
 {
     friend class Form;

+ 340 - 253
gameplay/src/Font.cpp

@@ -124,7 +124,7 @@ Font* Font::create(const char* family, Style style, unsigned int size, Glyph* gl
         __fontEffect = Effect::createFromSource(FONT_VSH, FONT_FSH);
         if (__fontEffect == NULL)
         {
-            LOG_ERROR("Failed to create effect for font.");
+            GP_ERROR("Failed to create effect for font.");
             SAFE_RELEASE(texture);
             return NULL;
         }
@@ -142,7 +142,7 @@ Font* Font::create(const char* family, Style style, unsigned int size, Glyph* gl
 
     if (batch == NULL)
     {
-        LOG_ERROR("Failed to create batch for font.");
+        GP_ERROR("Failed to create batch for font.");
         return NULL;
     }
 
@@ -174,6 +174,235 @@ void Font::begin()
     _batch->begin();
 }
 
+Font::Text* Font::createText(const char* text, const Rectangle& area, const Vector4& color, unsigned int size, Justify justify,
+    bool wrap, bool rightToLeft, const Rectangle* clip)
+{
+    if (size == 0)
+        size = _size;
+    float scale = (float)size / _size;
+    const int length = strlen(text);
+    int yPos = area.y;
+    const float areaHeight = area.height - size;
+    std::vector<int> xPositions;
+    std::vector<unsigned int> lineLengths;
+
+    getMeasurementInfo(text, area, size, justify, wrap, rightToLeft, &xPositions, &yPos, &lineLengths);
+
+    Text* batch = new Text(text);
+
+    int xPos = area.x;
+    std::vector<int>::const_iterator xPositionsIt = xPositions.begin();
+    if (xPositionsIt != xPositions.end())
+    {
+        xPos = *xPositionsIt++;
+    }
+
+    const char* token = text;
+    int iteration = 1;
+    unsigned int lineLength;
+    unsigned int currentLineLength = 0;
+    const char* lineStart;
+    std::vector<unsigned int>::const_iterator lineLengthsIt;
+    if (rightToLeft)
+    {
+        lineStart = token;
+        lineLengthsIt = lineLengths.begin();
+        lineLength = *lineLengthsIt++;
+        token += lineLength - 1;
+        iteration = -1;
+    }
+
+    while (token[0] != 0)
+    {
+        // Handle delimiters until next token.
+        if (!handleDelimiters(&token, size, iteration, area.x, &xPos, &yPos, &currentLineLength, &xPositionsIt, xPositions.end()))
+        {
+            break;
+        }
+
+        bool truncated = false;
+        unsigned int tokenLength;
+        unsigned int tokenWidth;
+        unsigned int startIndex;
+        if (rightToLeft)
+        {
+            tokenLength = getReversedTokenLength(token, text);
+            currentLineLength += tokenLength;
+            token -= (tokenLength - 1);
+            tokenWidth = getTokenWidth(token, tokenLength, size, scale);
+            iteration = -1;
+            startIndex = tokenLength - 1;
+        }
+        else
+        {
+            tokenLength = strcspn(token, " \r\n\t");
+            tokenWidth = getTokenWidth(token, tokenLength, size, scale);
+            iteration = 1;
+            startIndex = 0;
+        }
+
+        // Wrap if necessary.
+        if (wrap && (xPos + (int)tokenWidth > area.x + area.width || (rightToLeft && currentLineLength > lineLength)))
+        {
+            yPos += (int)size;
+            currentLineLength = tokenLength;
+
+            if (xPositionsIt != xPositions.end())
+            {
+                xPos = *xPositionsIt++;
+            }
+            else
+            {
+                xPos = area.x;
+            }
+        }
+
+        bool draw = true;
+        if (yPos < area.y)
+        {
+            // Skip drawing until line break or wrap.
+            draw = false;
+        }
+        else if (yPos > area.y + areaHeight)
+        {
+            // Truncate below area's vertical limit.
+            break;
+        }
+
+        for (int i = startIndex; i < (int)tokenLength && i >= 0; i += iteration)
+        {
+            char c = token[i];
+            int glyphIndex = c - 32; // HACK for ASCII
+        
+            if (glyphIndex >= 0 && glyphIndex < (int)_glyphCount)
+            {
+                Glyph& g = _glyphs[glyphIndex];
+
+                if (xPos + (int)(g.width*scale) > area.x + area.width)
+                {
+                    // Truncate this line and go on to the next one.
+                    truncated = true;
+                    break;
+                }
+                else if (xPos >= area.x)
+                {
+                    // Draw this character.
+                    if (draw)
+                    {
+                        if (clip)
+                        {
+                            _batch->addSprite(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color, *clip, &batch->_vertices[batch->_vertexCount]);
+                        }
+                        else
+                        {
+                            _batch->addSprite(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color, &batch->_vertices[batch->_vertexCount]);
+                        }
+
+                        if (batch->_vertexCount == 0)
+                        {
+                            // Simply copy values directly into the start of the index array
+                            batch->_indices[batch->_vertexCount] = batch->_vertexCount;
+                            batch->_indices[batch->_vertexCount] = batch->_vertexCount + 1;
+                            batch->_indices[batch->_vertexCount] = batch->_vertexCount + 2;
+                            batch->_indices[batch->_vertexCount] = batch->_vertexCount + 3;
+                            batch->_vertexCount += 4;
+                            batch->_indexCount += 4;
+                        }
+                        else
+                        {
+                            // Create a degenerate triangle to connect separate triangle strips
+                            // by duplicating the previous and next vertices.
+                            batch->_indices[batch->_indexCount] = batch->_indices[batch->_indexCount - 1];
+                            batch->_indices[batch->_indexCount + 1] = batch->_vertexCount;
+            
+                            // Loop through all indices and insert them, their their value offset by
+                            // 'vertexCount' so that they are relative to the first newly insertted vertex
+                            for (unsigned int i = 0; i < 4; ++i)
+                            {
+                                batch->_indices[batch->_indexCount + 2 + i] = i + batch->_vertexCount;
+                            }
+
+                            batch->_indexCount += 6;
+                            batch->_vertexCount += 4;
+                        }
+
+                    }
+                }
+                xPos += (int)(g.width)*scale + (size >> 3);
+            }
+        }
+
+        if (!truncated)
+        {
+            if (rightToLeft)
+            {
+                if (token == lineStart)
+                {
+                    token += lineLength;
+                    
+                    // Now handle delimiters going forwards.
+                    if (!handleDelimiters(&token, size, 1, area.x, &xPos, &yPos, &currentLineLength, &xPositionsIt, xPositions.end()))
+                    {
+                        break;
+                    }
+
+                    if (lineLengthsIt != lineLengths.end())
+                    {
+                        lineLength = *lineLengthsIt++;
+                    }
+                    lineStart = token;
+                    token += lineLength-1;
+                }
+                else
+                {
+                    token--;
+                }
+            }
+            else
+            {
+                token += tokenLength;
+            }
+        }
+        else
+        {
+            if (rightToLeft)
+            {
+                token = lineStart + lineLength;
+                
+                if (!handleDelimiters(&token, size, 1, area.x, &xPos, &yPos, &currentLineLength, &xPositionsIt, xPositions.end()))
+                {
+                    break;
+                }
+
+                if (lineLengthsIt != lineLengths.end())
+                {
+                    lineLength = *lineLengthsIt++;
+                }
+                lineStart = token;
+                token += lineLength-1;
+            }
+            else
+            {
+                // Skip the rest of this line.
+                unsigned int tokenLength = strcspn(token, "\n");
+
+                if (tokenLength > 0)
+                {                
+                    // Get first token of next line.
+                    token += tokenLength;
+                }
+            }
+        }
+    }
+
+    return batch;
+}
+
+void Font::drawText(Text* text)
+{
+    _batch->draw(text->_vertices, text->_vertexCount, text->_indices, text->_indexCount);
+}
+
 void Font::drawText(const char* text, int x, int y, const Vector4& color, unsigned int size, bool rightToLeft)
 {
     if (size == 0)
@@ -207,7 +436,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 switch (delimiter)
                 {
                 case ' ':
-                    xPos += (float)size*0.5f;
+                    xPos += size >> 1;
                     break;
                 case '\r':
                 case '\n':
@@ -215,7 +444,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                     xPos = x;
                     break;
                 case '\t':
-                    xPos += ((float)size*0.5f)*4;
+                    xPos += (size >> 1)*4;
                     break;
                 case 0:
                     done = true;
@@ -256,7 +485,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
             switch (c)
             {
             case ' ':
-                xPos += (float)size*0.5f;
+                xPos += size >> 1;
                 break;
             case '\r':
             case '\n':
@@ -264,7 +493,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 xPos = x;
                 break;
             case '\t':
-                xPos += ((float)size*0.5f)*4;
+                xPos += (size >> 1)*4;
                 break;
             default:
                 int index = c - 32; // HACK for ASCII
@@ -272,9 +501,10 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 {
                     Glyph& g = _glyphs[index];
                     _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
-                    xPos += g.width * scale + ((float)size*0.125f);
+                    xPos += floor(g.width * scale + (float)(size >> 3));
                     break;
                 }
+                break;
             }
         }
 
@@ -294,185 +524,13 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
     if (size == 0)
         size = _size;
     float scale = (float)size / _size;
-    const char* token = text;
     const int length = strlen(text);
     int yPos = area.y;
     const float areaHeight = area.height - size;
-
-    Justify vAlign = static_cast<Justify>(justify & 0xF0);
-    if (vAlign == 0)
-    {
-        vAlign = ALIGN_TOP;
-    }
-
-    Justify hAlign = static_cast<Justify>(justify & 0x0F);
-    if (hAlign == 0)
-    {
-        hAlign = ALIGN_LEFT;
-    }
-
-    token = text;
-
-    // For alignments other than top-left, need to calculate the y position to begin drawing from
-    // and the starting x position of each line.  For right-to-left text, need to determine
-    // the number of characters on each line.
     std::vector<int> xPositions;
     std::vector<unsigned int> lineLengths;
-    if (vAlign != ALIGN_TOP || hAlign != ALIGN_LEFT || rightToLeft)
-    {
-        int lineWidth = 0;
-        int delimWidth = 0;
-
-        if (wrap)
-        {
-            // Go a word at a time.
-            bool reachedEOF = false;
-            unsigned int lineLength = 0;
-            while (token[0] != 0)
-            {
-                unsigned int tokenWidth = 0;
-
-                // Handle delimiters until next token.
-                char delimiter = token[0];
-                while (delimiter == ' ' ||
-                       delimiter == '\t' ||
-                       delimiter == '\r' ||
-                       delimiter == '\n' ||
-                       delimiter == 0)
-                {
-                    switch (delimiter)
-                    {
-                        case ' ':
-                            delimWidth += (float)size*0.5f;
-                            lineLength++;
-                            break;
-                        case '\r':
-                        case '\n':
-                            yPos += size;
-
-                            if (lineWidth > 0)
-                            {
-                                addLineInfo(area, lineWidth, lineLength, hAlign, &xPositions, &lineLengths, rightToLeft);
-                            }
-
-                            lineWidth = 0;
-                            lineLength = 0;
-                            delimWidth = 0;
-                            break;
-                        case '\t':
-                            delimWidth += ((float)size*0.5f)*4;
-                            lineLength++;
-                            break;
-                        case 0:
-                            reachedEOF = true;
-                            break;
-                    }
-
-                    if (reachedEOF)
-                    {
-                        break;
-                    }
-
-                    token++;
-                    delimiter = token[0];
-                }
-
-                if (reachedEOF || token == NULL)
-                {
-                    break;
-                }
-
-                unsigned int tokenLength = strcspn(token, " \r\n\t");
-                tokenWidth += getTokenWidth(token, tokenLength, size, scale);
-
-                // Wrap if necessary.
-                if (lineWidth + tokenWidth + delimWidth > area.width)
-                {
-                    yPos += size;
-
-                    // Push position of current line.
-                    if (lineLength)
-                    {
-                        addLineInfo(area, lineWidth, lineLength-1, hAlign, &xPositions, &lineLengths, rightToLeft);
-                    }
-                    else
-                    {
-                        addLineInfo(area, lineWidth, tokenLength, hAlign, &xPositions, &lineLengths, rightToLeft);
-                    }
-
-                    // Move token to the next line.
-                    lineWidth = 0;
-                    lineLength = 0;
-                    delimWidth = 0;
-                }
-                else
-                {
-                    lineWidth += delimWidth;
-                    delimWidth = 0;
-                }
-
-                lineWidth += tokenWidth;
-                lineLength += tokenLength;
-                token += tokenLength;
-            }
-
-            // Final calculation of vertical position.
-            int textHeight = yPos - area.y;
-            int vWhiteSpace = areaHeight - textHeight;
-            if (vAlign == ALIGN_VCENTER)
-            {
-                yPos = area.y + vWhiteSpace / 2;
-            }
-            else if (vAlign == ALIGN_BOTTOM)
-            {
-                yPos = area.y + vWhiteSpace;
-            }
-
-            // Calculation of final horizontal position.
-            addLineInfo(area, lineWidth, lineLength, hAlign, &xPositions, &lineLengths, rightToLeft);
-        }
-        else
-        {
-            // Go a line at a time.
-            while (token[0] != 0)
-            {
-                char delimiter = token[0];
-                while (delimiter == '\n')
-                {
-                    yPos += size;
-                    ++token;
-                    delimiter = token[0];
-                }
-
-                unsigned int tokenLength = strcspn(token, "\n");
-                if (tokenLength == 0)
-                {
-                    tokenLength = strlen(token);
-                }
-
-                int lineWidth = getTokenWidth(token, tokenLength, size, scale);
-                addLineInfo(area, lineWidth, tokenLength, hAlign, &xPositions, &lineLengths, rightToLeft);
-
-                token += tokenLength;
-            }
-
-            int textHeight = yPos - area.y;
-            int vWhiteSpace = areaHeight - textHeight;
-            if (vAlign == ALIGN_VCENTER)
-            {
-                yPos = area.y + vWhiteSpace / 2;
-            }
-            else if (vAlign == ALIGN_BOTTOM)
-            {
-                yPos = area.y + vWhiteSpace;
-            }
-        }
 
-        if (vAlign == ALIGN_TOP)
-        {
-            yPos = area.y;
-        }
-    }
+    getMeasurementInfo(text, area, size, justify, wrap, rightToLeft, &xPositions, &yPos, &lineLengths);
 
     // Now we have the info we need in order to render.
     int xPos = area.x;
@@ -482,8 +540,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
         xPos = *xPositionsIt++;
     }
 
-    token = text;
-    
+    const char* token = text;
     int iteration = 1;
     unsigned int lineLength;
     unsigned int currentLineLength = 0;
@@ -528,11 +585,9 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
         }
 
         // Wrap if necessary.
-        if (wrap &&
-            xPos + tokenWidth > area.x + area.width ||
-            (rightToLeft && currentLineLength > lineLength))
+        if (wrap && (xPos + (int)tokenWidth > area.x + area.width || (rightToLeft && currentLineLength > lineLength)))
         {
-            yPos += size;
+            yPos += (int)size;
             currentLineLength = tokenLength;
 
             if (xPositionsIt != xPositions.end())
@@ -587,7 +642,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                         }
                     }
                 }
-                xPos += g.width*scale + ((float)size*0.125f);
+                xPos += (int)(g.width)*scale + (size >> 3);
             }
         }
 
@@ -729,7 +784,7 @@ void Font::measureText(const char* text, const Rectangle& clip, unsigned int siz
                 switch (delimiter)
                 {
                     case ' ':
-                        delimWidth += (float)size*0.5f;
+                        delimWidth += size >> 1;
                         break;
                     case '\r':
                     case '\n':
@@ -765,7 +820,7 @@ void Font::measureText(const char* text, const Rectangle& clip, unsigned int siz
                         delimWidth = 0;
                         break;
                     case '\t':
-                        delimWidth += ((float)size*0.5f)*4;
+                        delimWidth += (size >> 1)*4;
                         break;
                     case 0:
                         reachedEOF = true;
@@ -1010,29 +1065,10 @@ void Font::measureText(const char* text, const Rectangle& clip, unsigned int siz
     }
 }
 
-unsigned int Font::getIndexAtLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
-                                      Justify justify, bool wrap, bool rightToLeft)
+void Font::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)
 {
-    return getIndexOrLocation(text, area, size, inLocation, outLocation, -1, justify, wrap, rightToLeft);
-}
-
-void Font::getLocationAtIndex(const char* text, const Rectangle& clip, unsigned int size, Vector2* outLocation, const unsigned int destIndex,
-                              Justify justify, bool wrap, bool rightToLeft)
-{
-    getIndexOrLocation(text, clip, size, *outLocation, outLocation, (const int)destIndex, justify, wrap, rightToLeft);
-}
-
-unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
-                                      const int destIndex, Justify justify, bool wrap, bool rightToLeft)
-{
-    unsigned int charIndex = 0;
-
-    // Essentially need to measure text until we reach inLocation.
     float scale = (float)size / _size;
-    const char* token = text;
-    const int length = strlen(text);
-    int yPos = area.y;
-    const float areaHeight = area.height - size;
 
     Justify vAlign = static_cast<Justify>(justify & 0xF0);
     if (vAlign == 0)
@@ -1046,13 +1082,12 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
         hAlign = ALIGN_LEFT;
     }
 
-    token = text;
+    const char* token = text;
+    const float areaHeight = area.height - size;
 
     // For alignments other than top-left, need to calculate the y position to begin drawing from
     // and the starting x position of each line.  For right-to-left text, need to determine
     // the number of characters on each line.
-    std::vector<int> xPositions;
-    std::vector<unsigned int> lineLengths;
     if (vAlign != ALIGN_TOP || hAlign != ALIGN_LEFT || rightToLeft)
     {
         int lineWidth = 0;
@@ -1078,16 +1113,16 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                     switch (delimiter)
                     {
                         case ' ':
-                            delimWidth += (float)size*0.5f;
+                            delimWidth += size >> 1;
                             lineLength++;
                             break;
                         case '\r':
                         case '\n':
-                            yPos += size;
+                            *yPosition += size;
 
                             if (lineWidth > 0)
                             {
-                                addLineInfo(area, lineWidth, lineLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+                                addLineInfo(area, lineWidth, lineLength, hAlign, xPositions, lineLengths, rightToLeft);
                             }
 
                             lineWidth = 0;
@@ -1095,7 +1130,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                             delimWidth = 0;
                             break;
                         case '\t':
-                            delimWidth += ((float)size*0.5f)*4;
+                            delimWidth += (size >> 1)*4;
                             lineLength++;
                             break;
                         case 0:
@@ -1123,16 +1158,16 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                 // Wrap if necessary.
                 if (lineWidth + tokenWidth + delimWidth > area.width)
                 {
-                    yPos += size;
+                    *yPosition += size;
 
                     // Push position of current line.
                     if (lineLength)
                     {
-                        addLineInfo(area, lineWidth, lineLength-1, hAlign, &xPositions, &lineLengths, rightToLeft);
+                        addLineInfo(area, lineWidth, lineLength-1, hAlign, xPositions, lineLengths, rightToLeft);
                     }
                     else
                     {
-                        addLineInfo(area, lineWidth, tokenLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+                        addLineInfo(area, lineWidth, tokenLength, hAlign, xPositions, lineLengths, rightToLeft);
                     }
 
                     // Move token to the next line.
@@ -1152,19 +1187,19 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
             }
 
             // Final calculation of vertical position.
-            int textHeight = yPos - area.y;
+            int textHeight = *yPosition - area.y;
             int vWhiteSpace = areaHeight - textHeight;
             if (vAlign == ALIGN_VCENTER)
             {
-                yPos = area.y + vWhiteSpace / 2;
+                *yPosition = area.y + vWhiteSpace / 2;
             }
             else if (vAlign == ALIGN_BOTTOM)
             {
-                yPos = area.y + vWhiteSpace;
+                *yPosition = area.y + vWhiteSpace;
             }
 
             // Calculation of final horizontal position.
-            addLineInfo(area, lineWidth, lineLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+            addLineInfo(area, lineWidth, lineLength, hAlign, xPositions, lineLengths, rightToLeft);
         }
         else
         {
@@ -1174,7 +1209,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                 char delimiter = token[0];
                 while (delimiter == '\n')
                 {
-                    yPos += size;
+                    *yPosition += size;
                     ++token;
                     delimiter = token[0];
                 }
@@ -1186,30 +1221,57 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                 }
 
                 int lineWidth = getTokenWidth(token, tokenLength, size, scale);
-                addLineInfo(area, lineWidth, tokenLength, hAlign, &xPositions, &lineLengths, rightToLeft);
+                addLineInfo(area, lineWidth, tokenLength, hAlign, xPositions, lineLengths, rightToLeft);
 
                 token += tokenLength;
             }
 
-            int textHeight = yPos - area.y;
+            int textHeight = *yPosition - area.y;
             int vWhiteSpace = areaHeight - textHeight;
             if (vAlign == ALIGN_VCENTER)
             {
-                yPos = area.y + vWhiteSpace / 2;
+                *yPosition = area.y + vWhiteSpace / 2;
             }
             else if (vAlign == ALIGN_BOTTOM)
             {
-                yPos = area.y + vWhiteSpace;
+                *yPosition = area.y + vWhiteSpace;
             }
         }
 
         if (vAlign == ALIGN_TOP)
         {
-            yPos = area.y;
+            *yPosition = area.y;
         }
     }
+}
+
+int Font::getIndexAtLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                      Justify justify, bool wrap, bool rightToLeft)
+{
+    return getIndexOrLocation(text, area, size, inLocation, outLocation, -1, justify, wrap, rightToLeft);
+}
+
+void Font::getLocationAtIndex(const char* text, const Rectangle& clip, unsigned int size, Vector2* outLocation, const unsigned int destIndex,
+                              Justify justify, bool wrap, bool rightToLeft)
+{
+    getIndexOrLocation(text, clip, size, *outLocation, outLocation, (const int)destIndex, justify, wrap, rightToLeft);
+}
+
+int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
+                                      const int destIndex, Justify justify, bool wrap, bool rightToLeft)
+{
+    unsigned int charIndex = 0;
+
+    // Essentially need to measure text until we reach inLocation.
+    float scale = (float)size / _size;
+    const int length = strlen(text);
+    int yPos = area.y;
+    const float areaHeight = area.height - size;
+    std::vector<int> xPositions;
+    std::vector<unsigned int> lineLengths;
+
+    getMeasurementInfo(text, area, size, justify, wrap, rightToLeft, &xPositions, &yPos, &lineLengths);
 
-    // Now we have the info we need in order to render.
     int xPos = area.x;
     std::vector<int>::const_iterator xPositionsIt = xPositions.begin();
     if (xPositionsIt != xPositions.end())
@@ -1217,7 +1279,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
         xPos = *xPositionsIt++;
     }
 
-    token = text;
+    const char* token = text;
     
     int iteration = 1;
     unsigned int lineLength;
@@ -1248,11 +1310,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
         }
 
         currentLineLength += delimLength;
-        if (result == 0)
-        {
-            break;
-        }
-        else if (result == 2)
+        if (result == 0 || result == 2)
         {
             outLocation->x = xPos;
             outLocation->y = yPos;
@@ -1261,7 +1319,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
 
         if (destIndex == (int)charIndex ||
             (destIndex == -1 &&
-             inLocation.x >= xPos && inLocation.x < floor(xPos + ((float)size*0.125f)) &&
+             inLocation.x >= xPos && inLocation.x < floor(xPos + (float)(size >> 3)) &&
              inLocation.y >= yPos && inLocation.y < yPos + size))
         {
             outLocation->x = xPos;
@@ -1292,9 +1350,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
         }
 
         // Wrap if necessary.
-        if (wrap &&
-            xPos + tokenWidth > area.x + area.width ||
-            (rightToLeft && currentLineLength > lineLength))
+        if (wrap && (xPos + (int)tokenWidth > area.x + area.width || (rightToLeft && currentLineLength > lineLength)))
         {
             yPos += size;
             currentLineLength = tokenLength;
@@ -1334,7 +1390,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                 // Check against inLocation.
                 if (destIndex == (int)charIndex ||
                     (destIndex == -1 &&
-                    inLocation.x >= xPos && inLocation.x < floor(xPos + g.width*scale + ((float)size*0.125f)) &&
+                    inLocation.x >= xPos && inLocation.x < floor(xPos + g.width*scale + (float)(size >> 3)) &&
                     inLocation.y >= yPos && inLocation.y < yPos + size))
                 {
                     outLocation->x = xPos;
@@ -1342,7 +1398,7 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
                     return charIndex;
                 }
 
-                xPos += g.width*scale + ((float)size*0.125f);
+                xPos += floor(g.width*scale + (float)(size >> 3));
                 charIndex++;
             }
         }
@@ -1413,9 +1469,18 @@ unsigned int Font::getIndexOrLocation(const char* text, const Rectangle& area, u
         }
     }
 
-    outLocation->x = xPos;
-    outLocation->y = yPos;
-    return charIndex;
+
+    if (destIndex == (int)charIndex ||
+        (destIndex == -1 &&
+         inLocation.x >= xPos && inLocation.x < floor(xPos + (float)(size >> 3)) &&
+         inLocation.y >= yPos && inLocation.y < yPos + size))
+    {
+        outLocation->x = xPos;
+        outLocation->y = yPos;
+        return charIndex;
+    }
+    
+    return -1;
 }
 
 unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigned int size, float scale)
@@ -1428,17 +1493,17 @@ unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigne
         switch (c)
         {
         case ' ':
-            tokenWidth += (float)size*0.5f;
+            tokenWidth += size >> 1;
             break;
         case '\t':
-            tokenWidth += ((float)size*0.5f)*4;
+            tokenWidth += (size >> 1)*4;
             break;
         default:
             int glyphIndex = c - 32;
             if (glyphIndex >= 0 && glyphIndex < (int)_glyphCount)
             {
                 Glyph& g = _glyphs[glyphIndex];
-                tokenWidth += g.width * scale + ((float)size*0.125f);
+                tokenWidth += floor(g.width * scale + (float)(size >> 3));
             }
             break;
         }
@@ -1481,8 +1546,8 @@ int Font::handleDelimiters(const char** token, const unsigned int size, const in
             delimiter == 0)
     {
         if ((stopAtPosition &&
-            stopAtPosition->x >= *xPos && stopAtPosition->x < floor(*xPos + ((float)size*0.5f)) &&
-            stopAtPosition->y >= *yPos && stopAtPosition->y < *yPos + size) ||
+            stopAtPosition->x >= *xPos && stopAtPosition->x < *xPos + ((int)size >> 1) &&
+            stopAtPosition->y >= *yPos && stopAtPosition->y < *yPos + (int)size) ||
             (currentIndex >= 0 && destIndex >= 0 && currentIndex + (int)*lineLength == destIndex))
         {
             // Success + stopAtPosition was reached.
@@ -1492,7 +1557,7 @@ int Font::handleDelimiters(const char** token, const unsigned int size, const in
         switch (delimiter)
         {
             case ' ':
-                *xPos += (float)size*0.5f;
+                *xPos += size >> 1;
                 (*lineLength)++;
                 if (charIndex)
                 {
@@ -1524,7 +1589,7 @@ int Font::handleDelimiters(const char** token, const unsigned int size, const in
                 }
                 break;
             case '\t':
-                *xPos += ((float)size*0.5f)*4;
+                *xPos += (size >> 1)*4;
                 (*lineLength)++;
                 if (charIndex)
                 {
@@ -1635,9 +1700,31 @@ Font::Justify Font::getJustify(const char* justify)
     {
         return Font::ALIGN_BOTTOM_RIGHT;
     }
+    else
+    {
+        GP_ERROR("Failed to get corresponding font justification for unsupported value '%s'.", justify);
+    }
 
     // Default.
     return Font::ALIGN_TOP_LEFT;
 }
 
+Font::Text::Text(const char* text) : _text(text), _vertexCount(0), _vertices(NULL), _indexCount(0), _indices(NULL)
+{
+    const int length = strlen(text);
+    _vertices = new SpriteBatch::SpriteVertex[length * 4];
+    _indices = new unsigned short[((length - 1) * 6) + 4];
+}
+
+Font::Text::~Text()
+{
+    SAFE_DELETE_ARRAY(_vertices);
+    SAFE_DELETE_ARRAY(_indices);
+}
+
+const char* Font::Text::getText()
+{
+    return _text.c_str();
+}
+
 }

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

+ 419 - 282
gameplay/src/Form.cpp

@@ -1,6 +1,8 @@
 #include "Base.h"
 #include "Form.h"
 #include "AbsoluteLayout.h"
+#include "FlowLayout.h"
+#include "ScrollLayout.h"
 #include "VerticalLayout.h"
 #include "Game.h"
 #include "Theme.h"
@@ -11,388 +13,523 @@
 
 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(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()
+Form* Form::create(const char* url)
+{
+    // Load Form from .form file.
+    Properties* properties = Properties::create(url);
+    if (properties == NULL)
     {
-        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(properties);
+        return NULL;
     }
 
-    Form* Form::create(const char* path)
+    // 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))
     {
-        // Load Form from .form file.
-        assert(path);
-
-        Properties* properties = Properties::create(path);
-        assert(properties);
-        if (properties == NULL)
-            return NULL;
-
-        // Check if the Properties is valid and has a valid namespace.
-        Properties* formProperties = properties->getNextNamespace();
-        assert(formProperties);
-        if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
-        {
-            SAFE_DELETE(properties);
-            return NULL;
-        }
+        GP_ASSERT(formProperties);
+        SAFE_DELETE(properties);
+        return NULL;
+    }
 
-        // Create new form with given ID, theme and layout.
-        const char* themeFile = formProperties->getString("theme");
-        const char* layoutString = formProperties->getString("layout");
+    // 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;
-        }
-
-        assert(themeFile);
-        Theme* theme = Theme::create(themeFile);
-        assert(theme);
+    Layout* layout;
+    switch (getLayoutType(layoutString))
+    {
+    case Layout::LAYOUT_ABSOLUTE:
+        layout = AbsoluteLayout::create();
+        break;
+    case Layout::LAYOUT_FLOW:
+        layout = FlowLayout::create();
+        break;
+    case Layout::LAYOUT_VERTICAL:
+        layout = VerticalLayout::create();
+        break;
+    case Layout::LAYOUT_SCROLL:
+        layout = ScrollLayout::create();
+        break;
+    default:
+        GP_ERROR("Unsupported layout type '%d'.", getLayoutType(layoutString));
+    }
 
-        Form* form = new Form();
-        form->_layout = layout;
-        form->_theme = theme;
+    Theme* theme = Theme::create(themeFile);
+    GP_ASSERT(theme);
 
-        //Theme* theme = form->_theme;
-        const char* styleName = formProperties->getString("style");
-        form->initialize(theme->getStyle(styleName), formProperties);
+    Form* form = new Form();
+    form->_layout = layout;
+    form->_theme = theme;
 
-        if (form->_autoWidth)
-        {
-            form->_bounds.width = Game::getInstance()->getWidth();
-        }
+    const char* styleName = formProperties->getString("style");
+    form->initialize(theme->getStyle(styleName), formProperties);
 
-        if (form->_autoHeight)
-        {
-            form->_bounds.height = Game::getInstance()->getHeight();
-        }
+    // Add all the controls to the form.
+    form->addControls(theme, formProperties);
 
-        // Add all the controls to the form.
-        form->addControls(theme, formProperties);
+    Game* game = Game::getInstance();
+    Matrix::createOrthographicOffCenter(0, game->getWidth(), game->getHeight(), 0, 0, 1, &form->_defaultProjectionMatrix);
 
-        SAFE_DELETE(properties);
+    SAFE_DELETE(properties);
 
-        __forms.push_back(form);
+    __forms.push_back(form);
 
-        return 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 (!((w & (w - 1)) == 0))
+        {
+            w = nextHighestPowerOfTwo(w);
+        }
 
-        if (_node && !_quad)
+        if (!((h & (h - 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);
+            h = nextHighestPowerOfTwo(h);
+        }
 
-            Matrix::createOrthographicOffCenter(0, _bounds.width, _bounds.height, 0, 0, 1, &_projectionMatrix);
-            _theme->setProjectionMatrix(_projectionMatrix);
-            
-            _node->setModel(_quad);
+        _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);
+
+        _bounds.width = width;
+        _bounds.height = height;
+        _dirty = true;
     }
+}
+
+void Form::setBounds(const Rectangle& bounds)
+{
+    setPosition(bounds.x, bounds.y);
+    setSize(bounds.width, bounds.height);
+}
 
-    void Form::update()
+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());
+        }
+    }
+}
 
-                Game* game = Game::getInstance();
-                Rectangle prevViewport = game->getViewport();
-                
-                game->setViewport(Rectangle(_bounds.x, _bounds.y, _bounds.width, _bounds.height));
+void Form::setQuad(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4)
+{
+    Mesh* mesh = Mesh::createQuad(p1, p2, p3, p4);
 
-                draw(_theme->getSpriteBatch(), _clip);
+    initializeQuad(mesh);
+    SAFE_RELEASE(mesh);
+}
 
-                // Rebind the default framebuffer and game viewport.
-                FrameBuffer::bindDefault();
+void Form::setQuad(float x, float y, float width, float height)
+{
+    float x2 = x + width;
+    float y2 = y + height;
 
-                // restore the previous game viewport
-                game->setViewport(prevViewport);
-            }
+    float vertices[] =
+    {
+        x, y2, 0,   0, _v1,
+        x, y, 0,    0, 0,
+        x2, y2, 0,  _u2, _v1,
+        x2, y, 0,   _u2, 0
+    };
 
-            _quad->draw();
-        }
-        else
-        {
-            draw(_theme->getSpriteBatch(), _clip);
-        }
+    VertexFormat::Element elements[] =
+    {
+        VertexFormat::Element(VertexFormat::POSITION, 3),
+        VertexFormat::Element(VertexFormat::TEXCOORD0, 2)
+    };
+    Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 2), 4, false);
+    assert(mesh);
+
+    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::draw(SpriteBatch* spriteBatch, const Rectangle& clip)
+void Form::update()
+{
+    if (isDirty())
     {
-        std::vector<Control*>::const_iterator it;
+        Container::update(Rectangle(0, 0, _bounds.width, _bounds.height), Vector2::zero());
+    }
+}
 
-        // Batch for all themed border and background sprites.
-        spriteBatch->begin();
+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.
 
-        // Batch each font individually.
-        std::set<Font*>::const_iterator fontIter;
-        for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
-        {
-            Font* font = *fontIter;
-            if (font)
-            {
-                font->begin();
-            }
-        }
+    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.
 
-        // 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));
+    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.
+    */
 
-        for (it = _controls.begin(); it < _controls.end(); it++)
-        {
-            Control* control = *it;
+    // 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();
 
-            // Draw this control's border and background.
-            control->drawBorder(spriteBatch, clip);
+        Game* game = Game::getInstance();
+        Rectangle prevViewport = game->getViewport();
+        game->setViewport(Rectangle(_bounds.x, _bounds.y, _bounds.width, _bounds.height));
 
-            // Add all themed foreground sprites (checkboxes etc.) to the same batch.
-            control->drawImages(spriteBatch, clip);
+        GP_ASSERT(_theme);
+        _theme->setProjectionMatrix(_projectionMatrix);
+        draw(_theme->getSpriteBatch(), _viewportClipBounds);
+        _theme->setProjectionMatrix(_defaultProjectionMatrix);
 
-            control->drawText(clip);
-        }
+        // Rebind the default framebuffer and game viewport.
+        FrameBuffer::bindDefault();
 
-        // Done all batching.
-        spriteBatch->end();
+        // restore the previous game viewport
+        game->setViewport(prevViewport);
+    }
 
-        for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
+    if (_node)
+    {
+        GP_ASSERT(_quad);
+        _quad->draw();
+    }
+    else
+    {
+        if (!_spriteBatch)
         {
-            Font* font = *fontIter;
-            if (font)
-            {
-                font->end();
-            }
+            _spriteBatch = SpriteBatch::create(_frameBuffer->getRenderTarget()->getTexture());
+            GP_ASSERT(_spriteBatch);
         }
 
-        _dirty = false;
+        _spriteBatch->begin();
+        _spriteBatch->draw(_bounds.x, _bounds.y, 0, _bounds.width, _bounds.height, 0, _v1, _u2, 0, Vector4::one());
+        _spriteBatch->end();
     }
+}
 
-    void Form::initializeQuad(Mesh* mesh)
-    {
-        // Release current model.
-        SAFE_RELEASE(_quad);
+void Form::draw(SpriteBatch* spriteBatch, const Rectangle& clip)
+{
+    GP_ASSERT(spriteBatch);
 
-        // Create the model
-        _quad = Model::create(mesh);
+    std::vector<Control*>::const_iterator it;
 
-        // Create the material
-        Material* material = _quad->setMaterial("res/shaders/textured.vsh", "res/shaders/textured.fsh");
+    // Batch each font individually.
+    std::set<Font*>::const_iterator fontIter;
+    for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
+    {
+        Font* font = *fontIter;
+        if (font)
+        {
+            font->begin();
+        }
+    }
 
-        // Set the common render state block for the material
-        RenderState::StateBlock* stateBlock = _theme->getSpriteBatch()->getStateBlock();
-        stateBlock->setDepthWrite(true);
-        material->setStateBlock(stateBlock);
+    // Batch for all themed border and background sprites.
+    spriteBatch->begin();
 
-        // Bind the WorldViewProjection matrix
-        material->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
+    // 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));
 
-        // Create a FrameBuffer if necessary.
-        if (!_frameBuffer)
-        {
-            _frameBuffer = FrameBuffer::create(_id.c_str());
-        }
+    Rectangle boundsUnion = Rectangle::empty();
+    for (it = _controls.begin(); it < _controls.end(); it++)
+    {
+        Control* control = *it;
+        GP_ASSERT(control);
 
-        // Use the FrameBuffer to texture the quad.
-        if (!_frameBuffer->getRenderTarget())
+        if (_skin || control->isDirty() || control->_clearBounds.intersects(boundsUnion))
         {
-            RenderTarget* rt = RenderTarget::create(_id.c_str(), _bounds.width, _bounds.height);
-            _frameBuffer->setRenderTarget(rt);
-            SAFE_RELEASE(rt);
+            control->draw(spriteBatch, clip, _skin == NULL, _bounds.height);
+            Rectangle::combine(control->_clearBounds, boundsUnion, &boundsUnion);
         }
+    }
 
-        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());
+    // Done all batching.
+    spriteBatch->end();
 
-        SAFE_RELEASE(sampler);
+    for (fontIter = _theme->_fonts.begin(); fontIter != _theme->_fonts.end(); fontIter++)
+    {
+        Font* font = *fontIter;
+        if (font)
+        {
+            font->end();
+        }
     }
 
-    bool Form::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
+    _dirty = false;
+}
+
+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 = 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)
                     {
-                        // 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;
+}
+
+}

+ 42 - 4
gameplay/src/Form.h

@@ -50,11 +50,13 @@ class Form : public Container
 public:
 
     /**
-     * Create from properties file.
-     *
-     * @param path Path to the properties file to create a new form from.
+     * Creates a form using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
+     * 
+     * @param url The URL pointing to the Properties object defining the animation data. 
      */
-    static Form* create(const char* path);
+    static Form* create(const char* url);
 
     /**
      * Get a form from its ID.
@@ -65,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.
      *
@@ -155,11 +186,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;
 };
 
 }

+ 1 - 1
gameplay/src/FrameBuffer.cpp

@@ -127,7 +127,7 @@ unsigned int FrameBuffer::getMaxRenderTargets()
 
 void FrameBuffer::setRenderTarget(RenderTarget* target, unsigned int index)
 {
-    assert(index < __maxRenderTargets);
+    GP_ASSERT(index < __maxRenderTargets);
 
     if (_renderTargets[index] == target)
     {

+ 27 - 23
gameplay/src/Frustum.cpp

@@ -62,7 +62,7 @@ void Frustum::getMatrix(Matrix* dst) const
 
 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 +74,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 +123,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 +138,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.
      *

+ 4 - 2
gameplay/src/Game.cpp

@@ -6,6 +6,7 @@
 
 // Extern global variables
 GLenum __gl_error_code = GL_NO_ERROR;
+ALenum __al_error_code = AL_NO_ERROR;
 
 namespace gameplay
 {
@@ -20,7 +21,7 @@ Game::Game()
       _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> >();
 }
@@ -42,6 +43,7 @@ Game::~Game()
 
 Game* Game::getInstance()
 {
+    GP_ASSERT(__gameInstance);
     return __gameInstance;
 }
 
@@ -286,7 +288,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(timeListener);
     TimeEvent timeEvent(getGameTime() + timeOffset, timeListener, cookie);
     _timeEvents->push(timeEvent);
 }

+ 2 - 2
gameplay/src/Image.cpp

@@ -18,7 +18,7 @@ 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);
+        GP_ERROR("Texture is not a valid PNG: %s", path);
         fclose(fp);
         return NULL;
     }
@@ -73,7 +73,7 @@ Image* Image::create(const char* path)
         break;
 
     default:
-        LOG_ERROR_VARG("Unsupported PNG color type (%d) for texture: %s", (int)colorType, path);
+        GP_ERROR("Unsupported PNG color type (%d) for texture: %s", (int)colorType, path);
         fclose(fp);
         png_destroy_read_struct(&png, &info, NULL);
         return NULL;

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

+ 70 - 64
gameplay/src/Label.cpp

@@ -3,87 +3,93 @@
 
 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);
 
-    void Label::addListener(Control::Listener* listener, int eventFlags)
+    const char* text = properties->getString("text");
+    if (text)
     {
-        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);
+        _text = text;
     }
-    
-    void Label::setText(const char* text)
+}
+
+void Label::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
     {
-        if (text)
-        {
-            _text = text;
-        }
+        GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
     }
-
-    const char* Label::getText()
+    if ((eventFlags & Listener::VALUE_CHANGED) == Listener::VALUE_CHANGED)
     {
-        return _text.c_str();
+        GP_ERROR("VALUE_CHANGED event is not applicable to this control.");
     }
 
-    void Label::update(const Rectangle& clip)
-    {
-        Control::update(clip);
+    _consumeTouchEvents = true;
 
-        _font = getFont(_state);
-        _textColor = getTextColor(_state);
-        _textColor.w *= getOpacity(_state);
-    }
+    Control::addListener(listener, eventFlags);
+}
+    
+void Label::setText(const char* text)
+{
+    assert(text);
 
-    void Label::drawText(const Rectangle& clip)
-    {
-        if (_text.size() <= 0)
-            return;
+    _text = text;
+    _dirty = true;
+}
 
-        // Draw the text.
-        if (_font)
-        {
-            _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_clip);
-        }
+const char* Label::getText()
+{
+    return _text.c_str();
+}
 
-        _dirty = false;
+void Label::update(const Rectangle& clip, const Vector2& offset)
+{
+    Control::update(clip, offset);
+
+    _textBounds.set(_viewportBounds);
+
+    _font = getFont(_state);
+    _textColor = getTextColor(_state);
+    _textColor.w *= getOpacity(_state);
+}
+
+void Label::drawText(const Rectangle& clip)
+{
+    if (_text.size() <= 0)
+        return;
+
+    // Draw the text.
+    if (_font)
+    {
+        _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(_state), getTextAlignment(_state), true, getTextRightToLeft(_state), &_viewportClipBounds);
     }
+
+    _dirty = false;
+}
+
 }

+ 3 - 1
gameplay/src/Label.h

@@ -91,8 +91,9 @@ protected:
      * properties, such as its text viewport.
      *
      * @param clip The clipping rectangle of this label's parent container.
+     * @param offset The scroll offset of this label's parent container.
      */
-    void update(const Rectangle& clip);
+    void update(const Rectangle& clip, const Vector2& offset);
 
     /**
      * Draw this label's text.
@@ -115,6 +116,7 @@ protected:
      * The text color being used to display the label.
      */
     Vector4 _textColor;
+    Rectangle _textBounds;  // The position and size of this control's text area, before clipping.  Used for text alignment.
 
 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;
+        }
+
+        if (control->_autoHeight)
+        {
+            controlBounds.height = clipHeight;
+        }
+
+        // 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;
+}
+
 }

+ 23 - 1
gameplay/src/Layout.h

@@ -2,6 +2,7 @@
 #define LAYOUT_H_
 
 #include "Ref.h"
+#include "Touch.h"
 
 namespace gameplay
 {
@@ -42,7 +43,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
     };
 
     /**
@@ -67,6 +76,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);
 };
 
 }

+ 14 - 14
gameplay/src/Light.cpp

@@ -81,7 +81,7 @@ const Vector3& Light::getColor() const
     case SPOT:
         return _spot->color;
     default:
-        assert(0);
+        GP_ASSERT(0);
         return Vector3::zero();
 
     }
@@ -105,7 +105,7 @@ void Light::setColor(const Vector3& color)
 
 float Light::getRange()  const
 {
-    assert(_type != DIRECTIONAL);
+    GP_ASSERT(_type != DIRECTIONAL);
 
     switch (_type)
     {
@@ -114,14 +114,14 @@ float Light::getRange()  const
     case SPOT:
         return _spot->range;
     default:
-        assert(0);
+        GP_ASSERT(0);
         return 0.0f;
     }
 }
     
 void Light::setRange(float range)
 {
-    assert(_type != DIRECTIONAL);
+    GP_ASSERT(_type != DIRECTIONAL);
 
     switch (_type)
     {
@@ -138,7 +138,7 @@ void Light::setRange(float range)
 
 float Light::getRangeInverse() const
 {
-    assert(_type != DIRECTIONAL);
+    GP_ASSERT(_type != DIRECTIONAL);
 
     switch (_type)
     {
@@ -147,21 +147,21 @@ float Light::getRangeInverse() const
     case SPOT:
         return _spot->rangeInverse;
     default:
-        assert(0);
+        GP_ASSERT(0);
         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 +169,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 +184,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 +211,9 @@ Light* Light::clone(NodeCloneContext &context) const
         lightClone = createSpot(getColor(), getRange(), getInnerAngle(), getOuterAngle());
         break;
     default:
-        assert(false);
+        GP_ASSERT(false);
     }
-    assert(lightClone);
+    GP_ASSERT(lightClone);
 
     if (Node* node = context.findClonedNode(getNode()))
     {

+ 9 - 9
gameplay/src/Material.cpp

@@ -32,19 +32,19 @@ Material::~Material()
     }
 }
 
-Material* Material::create(const char* materialPath)
+Material* Material::create(const char* url)
 {
-    assert(materialPath);
+    GP_ASSERT(url);
 
     // Load the material properties from file
-    Properties* properties = Properties::create(materialPath);
-    assert(properties);
+    Properties* properties = Properties::create(url);
+    GP_ASSERT(properties);
     if (properties == NULL)
     {
         return NULL;
     }
 
-    Material* material = create(properties->getNextNamespace());
+    Material* material = create((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
     SAFE_DELETE(properties);
 
     return material;
@@ -53,7 +53,7 @@ Material* Material::create(const char* materialPath)
 Material* Material::create(Properties* materialProperties)
 {
     // Check if the Properties is valid and has a valid namespace.
-    assert(materialProperties);
+    GP_ASSERT(materialProperties);
     if (!materialProperties || !(strcmp(materialProperties->getNamespace(), "material") == 0))
     {
         return NULL;
@@ -151,7 +151,7 @@ unsigned int Material::getTechniqueCount() const
 
 Technique* Material::getTechnique(unsigned int index) const
 {
-    assert(index < _techniques.size());
+    GP_ASSERT(index < _techniques.size());
 
     return _techniques[index];
 }
@@ -227,9 +227,9 @@ bool Material::loadPass(Technique* technique, Properties* passProperties)
 {
     // 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)

+ 6 - 4
gameplay/src/Material.h

@@ -28,13 +28,15 @@ class Material : public RenderState
 public:
 
     /**
-     * Creates a material from a specified file path.
-     *
-     * @param materialPath Path path to the material file.
+     * Creates a material using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
+     * 
+     * @param url The URL pointing to the Properties object defining the material.
      * 
      * @return A new Material.
      */
-    static Material* create(const char* materialPath);
+    static Material* create(const char* url);
 
     /**
      * Creates a material from the specified properties object.

+ 16 - 55
gameplay/src/MaterialParameter.cpp

@@ -232,7 +232,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 +246,7 @@ void MaterialParameter::bind(Effect* effect)
         }
         else
         {
-            assert(_value.floatPtrValue);
+            GP_ASSERT(_value.floatPtrValue);
             effect->setValue(_uniform, _value.floatPtrValue, _count);
         }
         break;
@@ -257,32 +257,32 @@ void MaterialParameter::bind(Effect* effect)
         }
         else
         {
-            assert(_value.intPtrValue);
+            GP_ASSERT(_value.intPtrValue);
             effect->setValue(_uniform, _value.intPtrValue, _count);
         }
         break;
     case MaterialParameter::VECTOR2:
-        assert(_value.floatPtrValue);
+        GP_ASSERT(_value.floatPtrValue);
         effect->setValue(_uniform, reinterpret_cast<Vector2*>(_value.floatPtrValue), _count);
         break;
     case MaterialParameter::VECTOR3:
-        assert(_value.floatPtrValue);
+        GP_ASSERT(_value.floatPtrValue);
         effect->setValue(_uniform, reinterpret_cast<Vector3*>(_value.floatPtrValue), _count);
         break;
     case MaterialParameter::VECTOR4:
-        assert(_value.floatPtrValue);
+        GP_ASSERT(_value.floatPtrValue);
         effect->setValue(_uniform, reinterpret_cast<Vector4*>(_value.floatPtrValue), _count);
         break;
     case MaterialParameter::MATRIX:
-        assert(_value.floatPtrValue);
+        GP_ASSERT(_value.floatPtrValue);
         effect->setValue(_uniform, reinterpret_cast<Matrix*>(_value.floatPtrValue), _count);
         break;
     case MaterialParameter::SAMPLER:
-        assert(_value.samplerValue);
+        GP_ASSERT(_value.samplerValue);
         effect->setValue(_uniform, _value.samplerValue);
         break;
     case MaterialParameter::METHOD:
-        assert(_value.method);
+        GP_ASSERT(_value.method);
         _value.method->setValue(effect);
         break;
     }
@@ -383,7 +383,7 @@ void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue
 
 void MaterialParameter::setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight)
 {
-    assert(blendWeight >= 0.0f && blendWeight <= 1.0f);
+    GP_ASSERT(blendWeight >= 0.0f && blendWeight <= 1.0f);
 
     switch (propertyId)
     {
@@ -394,50 +394,21 @@ void MaterialParameter::setAnimationPropertyValue(int propertyId, AnimationValue
                 case FLOAT:
                 {
                     if (_count == 1)
-                    {
-                        if ((_animationPropertyBitFlag & ANIMATION_UNIFORM_BIT) != ANIMATION_UNIFORM_BIT)
-                        {
-                            _animationPropertyBitFlag |= ANIMATION_UNIFORM_BIT;
-                            _value.floatValue = value->getFloat(0);
-                        }
-                        else
-                        {
-                            _value.floatValue = Curve::lerp(blendWeight, _value.floatValue, value->getFloat(0));
-                        }
-                    }
+                        _value.floatValue = Curve::lerp(blendWeight, _value.floatValue, value->getFloat(0));
                     else
-                    {
                         applyAnimationValue(value, blendWeight, 1);
-                    }                    
                     break;
                 }
                 case INT:
                 {
                     if (_count == 1)
                     {
-                        if ((_animationPropertyBitFlag & ANIMATION_UNIFORM_BIT) != ANIMATION_UNIFORM_BIT)
-                        {
-                            _animationPropertyBitFlag |= ANIMATION_UNIFORM_BIT;
-                            _value.intValue = value->getFloat(0);
-                        }
-                        else
-                        {
-                            _value.intValue = Curve::lerp(blendWeight, _value.intValue, value->getFloat(0));
-                        }
+                        _value.intValue = Curve::lerp(blendWeight, _value.intValue, value->getFloat(0));
                     }
                     else
                     {
-                        if ((_animationPropertyBitFlag & ANIMATION_UNIFORM_BIT) != ANIMATION_UNIFORM_BIT)
-                        {
-                            _animationPropertyBitFlag |= ANIMATION_UNIFORM_BIT;
-                            for (unsigned int i = 0; i < _count; i++)
-                                _value.intPtrValue[i] = value->getFloat(i);
-                        }
-                        else
-                        {
-                            for (unsigned int i = 0; i < _count; i++)
-                                _value.intPtrValue[i] = Curve::lerp(blendWeight, _value.intPtrValue[i], value->getFloat(i));
-                        }
+                        for (unsigned int i = 0; i < _count; i++)
+                            _value.intPtrValue[i] = Curve::lerp(blendWeight, _value.intPtrValue[i], value->getFloat(i));
                     }
                     break;
                 }
@@ -466,18 +437,8 @@ void MaterialParameter::setAnimationPropertyValue(int propertyId, AnimationValue
 void MaterialParameter::applyAnimationValue(AnimationValue* value, float blendWeight, int components)
 {
     unsigned int count = _count * components;
-    if ((_animationPropertyBitFlag & ANIMATION_UNIFORM_BIT) != ANIMATION_UNIFORM_BIT)
-    {
-        _animationPropertyBitFlag |= ANIMATION_UNIFORM_BIT;
-
-        for (unsigned int i = 0; i < count; i++)
-            _value.floatPtrValue[i] = value->getFloat(i);
-    }
-    else
-    {
-        for (unsigned int i = 0; i < count; i++)
-            _value.floatPtrValue[i] = Curve::lerp(blendWeight, _value.floatPtrValue[i], value->getFloat(i));
-    }
+    for (unsigned int i = 0; i < count; i++)
+        _value.floatPtrValue[i] = Curve::lerp(blendWeight, _value.floatPtrValue[i], value->getFloat(i));
 }
 
 void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const

+ 1 - 3
gameplay/src/MaterialParameter.h

@@ -167,9 +167,7 @@ public:
     void setAnimationPropertyValue(int propertyId, AnimationValue* value, float blendWeight = 1.0f);
 
 private:
-
-    static const char ANIMATION_UNIFORM_BIT = 0x01;
-    
+   
     /**
      * Constructor.
      */

+ 29 - 29
gameplay/src/Matrix.cpp

@@ -68,7 +68,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,7 +111,7 @@ 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);
 
     float f_n = 1.0f / (zFarPlane - zNearPlane);
     float factor = 1.0f / tanf(MATH_DEG_TO_RAD(fieldOfView) * 0.5f);
@@ -135,7 +135,7 @@ 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);
 
     float r_l = 1.0f / (right - left);
     float t_b = 1.0f / (top - bottom);
@@ -153,7 +153,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 +164,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 +176,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 +215,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 +274,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 +289,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 +304,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 +319,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 +330,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,7 +346,7 @@ void Matrix::add(float scalar)
 
 void Matrix::add(float scalar, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     dst->m[0]  = m[0]  + scalar;
     dst->m[1]  = m[1]  + scalar;
@@ -373,7 +373,7 @@ void Matrix::add(const Matrix& m)
 
 void Matrix::add(const Matrix& m1, const Matrix& m2, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     dst->m[0]  = m1.m[0]  + m2.m[0];
     dst->m[1]  = m1.m[1]  + m2.m[1];
@@ -535,7 +535,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 +544,7 @@ 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 +552,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 +561,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 +570,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 +579,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,7 +657,7 @@ void Matrix::multiply(float scalar, Matrix* dst) const
 
 void Matrix::multiply(const Matrix& m, float scalar, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     dst->m[0]  = m.m[0]  * scalar;
     dst->m[1]  = m.m[1]  * scalar;
@@ -684,7 +684,7 @@ void Matrix::multiply(const Matrix& m)
 
 void Matrix::multiply(const Matrix& m1, const Matrix& m2, Matrix* dst)
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     // Support the case where m1 or m2 is the same array as dst.
     float product[16];
@@ -792,7 +792,7 @@ void Matrix::rotateZ(float angle)
 
 void Matrix::rotateZ(float angle, Matrix* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     Matrix r;
     createRotationZ(angle, &r);
@@ -816,7 +816,7 @@ void Matrix::scale(float xScale, float yScale, float zScale)
 
 void Matrix::scale(float xScale, float yScale, float zScale, Matrix* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     Matrix s;
     createScale(xScale, yScale, zScale, &s);
@@ -855,7 +855,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);
 }
 
@@ -921,7 +921,7 @@ 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);
+    GP_ASSERT(dst);
     
     dst->set(
         x * m[0] + y * m[4] + z * m[8] + w * m[12],
@@ -936,7 +936,7 @@ void Matrix::transformVector(Vector4* vector) const
 
 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],
@@ -952,7 +952,7 @@ void Matrix::translate(float x, float y, float z)
 
 void Matrix::translate(float x, float y, float z, Matrix* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
 
     Matrix t;
     createTranslation(x, y, z, &t);
@@ -976,7 +976,7 @@ void Matrix::transpose()
 
 void Matrix::transpose(Matrix* dst) const
 {
-    assert(dst);
+    GP_ASSERT(dst);
     
     float t[16] = {
         m[0], m[4], m[8], m[12],

+ 4 - 4
gameplay/src/MeshBatch.cpp

@@ -36,7 +36,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);
 
@@ -73,7 +73,7 @@ void MeshBatch::setCapacity(unsigned int capacity)
 
 bool MeshBatch::resize(unsigned int capacity)
 {
-    assert(capacity > 0);
+    GP_ASSERT(capacity > 0);
     if (capacity == 0)
         return false;
 
@@ -103,7 +103,7 @@ bool MeshBatch::resize(unsigned int capacity)
         vertexCapacity = capacity + 2;
         break;
     default:
-        assert(0); // unexpected
+        GP_ASSERT(0); // unexpected
         break;
     }
 
@@ -112,7 +112,7 @@ bool MeshBatch::resize(unsigned int capacity)
     // for now, which is the same number of vertices as indices.
     unsigned int indexCapacity = vertexCapacity;
 
-    assert(indexCapacity <= USHRT_MAX);
+    GP_ASSERT(indexCapacity <= USHRT_MAX);
     if (indexCapacity > USHRT_MAX)
         return false;
 

+ 1 - 1
gameplay/src/MeshBatch.inl

@@ -11,7 +11,7 @@ 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(sizeof(T) == _vertexFormat.getVertexSize());
     
     unsigned int newVertexCount = _vertexCount + vertexCount;
     unsigned int newIndexCount = _indexCount + indexCount;

+ 19 - 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)
     {
@@ -57,7 +57,7 @@ Joint* MeshSkin::getJoint(const char* id) const
     return NULL;
 }
 
-MeshSkin* MeshSkin::clone() const
+MeshSkin* MeshSkin::clone(NodeCloneContext &context) const
 {
     MeshSkin* skin = new MeshSkin();
     skin->_bindShape = _bindShape;
@@ -66,10 +66,21 @@ MeshSkin* MeshSkin::clone() const
         const unsigned int jointCount = getJointCount();
         skin->setJointCount(jointCount);
 
-        assert(skin->_rootNode == NULL);
-        skin->_rootNode = _rootNode->clone();
+        GP_ASSERT(skin->_rootNode == NULL);
+        
+        // Check if the root node has already been cloned.
+        if (Node* rootNode = context.findClonedNode(_rootNode))
+        {
+            skin->_rootNode = rootNode;
+            rootNode->addRef();
+        }
+        else
+        {
+            skin->_rootNode = _rootNode->cloneRecursive(context);
+        }
+        
         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)
         {
@@ -81,7 +92,7 @@ MeshSkin* MeshSkin::clone() const
                 if (strcmp(skin->_rootJoint->getId(), oldJoint->getId()) == 0)
                     newJoint = static_cast<Joint*>(skin->_rootJoint);
             }
-            assert(newJoint);
+            GP_ASSERT(newJoint);
             skin->setJoint(newJoint, i);
         }
     }
@@ -117,7 +128,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])
     {

+ 4 - 1
gameplay/src/MeshSkin.h

@@ -20,6 +20,7 @@ class MeshSkin : public Transform::Listener
     friend class Bundle;
     friend class Model;
     friend class Joint;
+    friend class Node;
 
 public:
 
@@ -134,9 +135,11 @@ private:
     /**
      * Clones the MeshSkin and the joints that it references.
      * 
+     * @param context The clone context.
+     * 
      * @return The newly created MeshSkin.
      */
-    MeshSkin* clone() const;
+    MeshSkin* clone(NodeCloneContext &context) const;
 
     /**
      * Sets the number of joints that can be stored in this skin.

+ 3 - 3
gameplay/src/Model.cpp

@@ -51,7 +51,7 @@ unsigned int Model::getMeshPartCount() const
 
 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 +75,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;
 
@@ -354,7 +354,7 @@ Model* Model::clone(NodeCloneContext &context)
     Model* model = Model::create(getMesh());
     if (getSkin())
     {
-        model->setSkin(getSkin()->clone());
+        model->setSkin(getSkin()->clone(context));
     }
     Material* materialClone = getMaterial()->clone(context);
     model->setMaterial(materialClone); // TODO: Don't forget material parts

+ 64 - 30
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,21 @@ 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;
+    if (_model != NULL && _model->getSkin() != NULL && (rootNode = _model->getSkin()->_rootNode) != NULL)
+    {
+        if ((exactMatch && rootNode->_id == id) || (!exactMatch && rootNode->_id.find(id) == 0))
+            return rootNode;
+        
+        Node* match = rootNode->findNode(id, true, exactMatch);
+        if (match)
+        {
+            return match;
+        }
+    }
     
     // Search immediate children first.
     for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
@@ -315,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;
 
@@ -388,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();
         }
     }
 
@@ -532,8 +544,6 @@ Vector3 Node::getForwardVectorView() const
     Vector3 vector;
     getWorldMatrix().getForwardVector(&vector);
     getViewMatrix().transformVector(&vector);
-    //getForwardVector(&vector);
-    //getWorldViewMatrix().transformVector(&vector);
     return vector;
 }
 
@@ -587,11 +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).
-    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();
@@ -621,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;
             }
@@ -639,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;
@@ -656,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;
@@ -807,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)
                 {
@@ -864,11 +882,19 @@ 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())
+    {
+        lastChild = child;
+    }
+    // Loop through the nodes backwards because addChild adds the node to the front.
+    for (Node* child = lastChild; child != NULL; child = child->getPreviousSibling())
     {
         Node* childCopy = child->cloneRecursive(context);
-        copy->addChild(childCopy); // TODO: Does child order matter?
+        GP_ASSERT(childCopy);
+        copy->addChild(childCopy);
         childCopy->release();
     }
     return copy;
@@ -876,11 +902,10 @@ 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.
-    //node->setCamera(getCamera());
-    //node->setLight(getLight());
 
     if (Camera* camera = getCamera())
     {
@@ -993,18 +1018,17 @@ PhysicsCollisionObject* Node::setCollisionObject(PhysicsCollisionObject::Type ty
     return _collisionObject;
 }
 
-PhysicsCollisionObject* Node::setCollisionObject(const char* filePath)
+PhysicsCollisionObject* Node::setCollisionObject(const char* url)
 {
     // Load the collision object properties from file.
-    Properties* properties = Properties::create(filePath);
-    assert(properties);
+    Properties* properties = Properties::create(url);
     if (properties == NULL)
     {
-        WARN_VARG("Failed to load collision object file: %s", filePath);
+        GP_ERROR("Failed to load collision object file: %s", url);
         return NULL;
     }
 
-    PhysicsCollisionObject* collisionObject = setCollisionObject(properties->getNextNamespace());
+    PhysicsCollisionObject* collisionObject = setCollisionObject((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
     SAFE_DELETE(properties);
 
     return collisionObject;
@@ -1020,7 +1044,7 @@ PhysicsCollisionObject* Node::setCollisionObject(Properties* properties)
         strcmp(properties->getNamespace(), "ghostObject") == 0 || 
         strcmp(properties->getNamespace(), "rigidBody") == 0))
     {
-        WARN("Failed to load collision object from properties object: must be non-null object and have namespace equal to \'character\', \'ghostObject\', or \'rigidBody\'.");
+        GP_ERROR("Failed to load collision object from properties object: must be non-null object and have namespace equal to 'character', 'ghostObject', or 'rigidBody'.");
         return NULL;
     }
 
@@ -1051,23 +1075,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;
 }
 

+ 5 - 3
gameplay/src/Node.h

@@ -520,11 +520,13 @@ public:
     PhysicsCollisionObject* setCollisionObject(PhysicsCollisionObject::Type type, const PhysicsCollisionShape::Definition& shape, PhysicsRigidBody::Parameters* rigidBodyParameters = NULL);
 
     /**
-     * Sets the physics collision object for this node using the definition in the given file.
+     * Sets the physics collision object for this node using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
      * 
-     * @param filePath The path to the file that set the collision object definition.
+     * @param url The URL pointing to the Properties object defining the physics collision object.
      */
-    PhysicsCollisionObject* setCollisionObject(const char* filePath);
+    PhysicsCollisionObject* setCollisionObject(const char* url);
 
     /**
      * Sets the physics collision object for this node from the given properties object.

+ 37 - 26
gameplay/src/ParticleEmitter.cpp

@@ -34,11 +34,6 @@ ParticleEmitter::ParticleEmitter(SpriteBatch* batch, unsigned int particleCountM
 
     _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 +45,24 @@ ParticleEmitter::~ParticleEmitter()
 
 ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlending textureBlending, unsigned int particleCountMax)
 {
-    assert(textureFile);
+    GP_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("Error creating ParticleEmitter: Could not read texture file: %s", textureFile);
         return NULL;
     }
 
     // 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);
@@ -82,18 +77,18 @@ ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlendin
     return emitter;
 }
 
-ParticleEmitter* ParticleEmitter::create(const char* particleFile)
+ParticleEmitter* ParticleEmitter::create(const char* url)
 {
-    assert(particleFile);
+    GP_ASSERT(url);
 
-    Properties* properties = Properties::create(particleFile);
+    Properties* properties = Properties::create(url);
     if (!properties)
     {
-        LOG_ERROR_VARG("Error loading ParticleEmitter: Could not load file: %s", particleFile);
+        GP_ERROR("Error loading ParticleEmitter: Could not load file: %s", url);
         return NULL;
     }
 
-    ParticleEmitter* particle = create(properties->getNextNamespace());
+    ParticleEmitter* particle = create((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
     SAFE_DELETE(properties);
 
     return particle;
@@ -103,14 +98,14 @@ ParticleEmitter* ParticleEmitter::create(Properties* properties)
 {
     if (!properties || strcmp(properties->getNamespace(), "particle") != 0)
     {
-        LOG_ERROR("Error loading ParticleEmitter: No 'particle' namespace found");
+        GP_ERROR("Error loading ParticleEmitter: No 'particle' namespace found");
         return NULL;
     }
 
     Properties* sprite = properties->getNextNamespace();
     if (!sprite || strcmp(sprite->getNamespace(), "sprite") != 0)
     {
-        LOG_ERROR("Error loading ParticleEmitter: No 'sprite' namespace found");
+        GP_ERROR("Error loading ParticleEmitter: No 'sprite' namespace found");
         return NULL;
     }
 
@@ -119,7 +114,7 @@ ParticleEmitter* ParticleEmitter::create(Properties* properties)
     const char* texturePath = sprite->getString("path");
     if (strlen(texturePath) == 0)
     {
-        LOG_ERROR_VARG("Error loading ParticleEmitter: No texture path specified: %s", texturePath);
+        GP_ERROR("Error loading ParticleEmitter: No texture path specified: %s", texturePath);
         return NULL;
     }
 
@@ -282,6 +277,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);
@@ -784,14 +780,19 @@ void ParticleEmitter::update(long elapsedTime)
         // How many particles should we emit this frame?
         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);
+        }
     }
 
+    const Frustum& frustum = _node->getScene()->getActiveCamera()->getFrustum();
+
     // Now update all currently living particles.
     for (unsigned int particlesIndex = 0; particlesIndex < _particleCount; ++particlesIndex)
     {
@@ -817,6 +818,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.
@@ -896,10 +903,11 @@ 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.
         const Matrix& cameraWorldMatrix = _node->getScene()->getActiveCamera()->getNode()->getWorldMatrix();
+
         Vector3 right;
         cameraWorldMatrix.getRightVector(&right);
         Vector3 up;
@@ -909,9 +917,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.

+ 14 - 11
gameplay/src/ParticleEmitter.h

@@ -154,13 +154,15 @@ public:
     };
 
     /**
-     * Creates a particle emitter from a .particle file.
-     *
-     * @param particleFile The .particle file to load.
+     * Creates a particle emitter using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
+     * 
+     * @param url The URL pointing to the Properties object defining the particle emitter.
      * 
      * @return An initialized ParticleEmitter.
      */
-    static ParticleEmitter* create(const char* particleFile);
+    static ParticleEmitter* create(const char* url);
 
     /**
      * Creates a particle emitter from the specified properties object.
@@ -613,6 +615,13 @@ public:
      */
     void draw();
 
+    /**
+     * Gets a BlendMode enum from a corresponding string.
+     */
+    static TextureBlending getTextureBlendingFromString(const char* src);
+
+    void setTextureBlending(TextureBlending blending);
+
 private:
 
     /**
@@ -654,13 +663,6 @@ private:
      */
     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.
      */
@@ -685,6 +687,7 @@ private:
         float _size;
         unsigned int _frame;
         float _timeOnCurrentFrame;
+        bool _visible;
     };
 
     unsigned int _particleCountMax;

+ 2 - 2
gameplay/src/Pass.cpp

@@ -10,7 +10,7 @@ namespace gameplay
 Pass::Pass(const char* id, Technique* technique, Effect* effect) :
     _id(id ? id : ""), _technique(technique), _effect(effect), _vaBinding(NULL)
 {
-    assert(technique);
+    GP_ASSERT(technique);
 
     RenderState::_parent = _technique;
 }
@@ -25,7 +25,7 @@ Pass* Pass::create(const char* id, Technique* technique, const char* vshPath, co
 {
     // Attempt to create/load the effect
     Effect* effect = Effect::createFromFile(vshPath, fshPath, defines);
-    assert(effect);
+    GP_ASSERT(effect);
     if (effect == NULL)
     {
         return NULL;

+ 3 - 3
gameplay/src/PhysicsCharacter.cpp

@@ -87,10 +87,10 @@ PhysicsCharacter::~PhysicsCharacter()
 PhysicsCharacter* PhysicsCharacter::create(Node* node, Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
-    assert(properties);
+    GP_ASSERT(properties);
     if (!properties || !(strcmp(properties->getNamespace(), "character") == 0))
     {
-        WARN("Failed to load physics character from properties object: must be non-null object and have namespace equal to \'character\'.");
+        GP_WARN("Failed to load physics character from properties object: must be non-null object and have namespace equal to 'character'.");
         return NULL;
     }
 
@@ -98,7 +98,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_WARN("Failed to create collision shape during physics character creation.");
         return NULL;
     }
 

+ 9 - 9
gameplay/src/PhysicsCollisionShape.cpp

@@ -118,13 +118,13 @@ PhysicsCollisionShape::Definition& PhysicsCollisionShape::Definition::operator=(
 PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Node* node, Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
-    assert(properties);
+    GP_ASSERT(properties);
     if (!properties || 
         !(strcmp(properties->getNamespace(), "character") == 0 || 
         strcmp(properties->getNamespace(), "ghostObject") == 0 || 
         strcmp(properties->getNamespace(), "rigidBody") == 0))
     {
-        WARN("Failed to load physics collision shape from properties object: must be non-null object and have namespace equal to \'character\', \'ghostObject\', or \'rigidBody\'.");
+        GP_WARN("Failed to load physics collision shape from properties object: must be non-null object and have namespace equal to 'character', 'ghostObject', or 'rigidBody'.");
         return NULL;
     }
 
@@ -158,7 +158,7 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
                 type = SHAPE_CAPSULE;
             else
             {
-                WARN_VARG("Could not create physics collision shape; unsupported value for collision shape type: '%s'.", typeStr.c_str());
+                GP_WARN("Could not create physics collision shape; unsupported value for collision shape type: '%s'.", typeStr.c_str());
                 return NULL;
             }
 
@@ -194,7 +194,7 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
 
     if (!typeSpecified)
     {
-        WARN("Missing 'type' specifier for collision shape definition.");
+        GP_WARN("Missing 'type' specifier for collision shape definition.");
         return NULL;
     }
 
@@ -259,7 +259,7 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
             Mesh* nodeMesh = node->getModel() ? node->getModel()->getMesh() : NULL;
             if (nodeMesh == NULL)
             {
-                WARN("Cannot create mesh rigid body for node without mode/mesh.");
+                GP_WARN("Cannot create mesh rigid body for node without mode/mesh.");
                 return NULL;
             }
 
@@ -275,7 +275,7 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
                 case Mesh::LINE_STRIP:
                 case Mesh::POINTS:
                 case Mesh::TRIANGLE_STRIP:
-                    WARN("Mesh rigid bodies are currently only supported on meshes with primitive type equal to TRIANGLES.");
+                    GP_WARN("Mesh rigid bodies are currently only supported on meshes with primitive type equal to TRIANGLES.");
                     SAFE_DELETE(shape);
                     break;
             }
@@ -285,7 +285,7 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
         case SHAPE_HEIGHTFIELD:
             if (imagePath == NULL)
             {
-                WARN("Heightfield rigid body requires an image path.");
+                GP_WARN("Heightfield rigid body requires an image path.");
             }
             else
             {
@@ -301,7 +301,7 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
                     case Image::RGBA:
                         break;
                     default:
-                        WARN_VARG("Heightmap: pixel format is not supported: %d", image->getFormat());
+                        GP_WARN("Heightmap: pixel format is not supported: %d", image->getFormat());
                         return NULL;
                 }
 
@@ -310,7 +310,7 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
             }
             break;
         default:
-            WARN("Unsupported value for physics collision shape type.");
+            GP_WARN("Unsupported value for physics collision shape type.");
             break;
     }
 

+ 1 - 1
gameplay/src/PhysicsConstraint.cpp

@@ -145,7 +145,7 @@ Vector3 PhysicsConstraint::getWorldCenterOfMass(const Model* model)
         else
         {
             // Warn the user that the model has no bounding volume.
-            WARN_VARG("Model \'%s\' has no bounding volume - center of mass is defaulting to local coordinate origin.", model->getNode()->getId());
+            GP_WARN("Model '%s' has no bounding volume - center of mass is defaulting to local coordinate origin.", model->getNode()->getId());
             model->getNode()->getWorldMatrix().transformPoint(&center);
         }
     }

+ 20 - 20
gameplay/src/PhysicsController.cpp

@@ -160,22 +160,22 @@ bool PhysicsController::sweepTest(PhysicsCollisionObject* object, const Vector3&
     {
     public:
 
-	    SweepTestCallback(PhysicsCollisionObject* me)
+        SweepTestCallback(PhysicsCollisionObject* me)
             : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), me(me)
-	    {
-	    }
+        {
+        }
 
-	    btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
-	    {
+        btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
+        {
             PhysicsCollisionObject* object = reinterpret_cast<PhysicsCollisionObject*>(convexResult.m_hitCollisionObject->getUserPointer());
 
-		    if (object == me)
-			    return 1.0f;
+            if (object == me)
+                return 1.0f;
 
-		    return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
-	    }
+            return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
+        }
 
-	    PhysicsCollisionObject* me;
+        PhysicsCollisionObject* me;
     };
 
     PhysicsCollisionShape* shape = object->getCollisionShape();
@@ -507,7 +507,7 @@ void PhysicsController::addCollisionObject(PhysicsCollisionObject* object)
         break;
 
     default:
-        assert(0); // unexpected (new type?)
+        GP_ASSERT(0); // unexpected (new type?)
         break;
     }
 }
@@ -529,7 +529,7 @@ void PhysicsController::removeCollisionObject(PhysicsCollisionObject* object)
             break;
 
         default:
-            assert(0); // unexpected (new type?)
+            GP_ASSERT(0); // unexpected (new type?)
             break;
         }
     }
@@ -851,7 +851,7 @@ PhysicsCollisionShape* PhysicsController::createHeightfield(Node* node, Image* i
             pixelSize = 4;
             break;
         default:
-            LOG_ERROR("Unsupported pixel format for heightmap image.");
+            GP_ERROR("Unsupported pixel format for heightmap image.");
             return NULL;
     }
 
@@ -914,7 +914,7 @@ PhysicsCollisionShape* PhysicsController::createHeightfield(Node* node, Image* i
 
 PhysicsCollisionShape* PhysicsController::createMesh(Mesh* mesh, const Vector3& scale)
 {
-    assert(mesh);
+    GP_ASSERT(mesh);
 
     // Only support meshes with triangle list primitive types
     bool triMesh = true;
@@ -936,7 +936,7 @@ PhysicsCollisionShape* PhysicsController::createMesh(Mesh* mesh, const Vector3&
 
     if (!triMesh)
     {
-        LOG_ERROR("Mesh rigid bodies are currently only supported on meshes with TRIANGLES primitive type.");
+        GP_ERROR("Mesh rigid bodies are currently only supported on meshes with TRIANGLES primitive type.");
         return NULL;
     }
 
@@ -944,7 +944,7 @@ PhysicsCollisionShape* PhysicsController::createMesh(Mesh* mesh, const Vector3&
     // in order to fetch mesh data for computing mesh rigid body.
     if (strlen(mesh->getUrl()) == 0)
     {
-        LOG_ERROR("Cannot create mesh rigid body for mesh without valid URL.");
+        GP_ERROR("Cannot create mesh rigid body for mesh without valid URL.");
         return NULL;
     }
 
@@ -1122,13 +1122,13 @@ bool PhysicsController::checkConstraintRigidBodies(PhysicsRigidBody* a, PhysicsR
 {
     if (!a->supportsConstraints())
     {
-        WARN_VARG("Rigid body '%s' does not support constraints; unexpected behavior may occur.", a->_node->getId());
+        GP_WARN("Rigid body '%s' does not support constraints; unexpected behavior may occur.", a->_node->getId());
         return false;
     }
     
     if (b && !b->supportsConstraints())
     {
-        WARN_VARG("Rigid body '%s' does not support constraints; unexpected behavior may occur.", b->_node->getId());
+        GP_WARN("Rigid body '%s' does not support constraints; unexpected behavior may occur.", b->_node->getId());
         return false;
     }
 
@@ -1247,12 +1247,12 @@ void PhysicsController::DebugDrawer::drawContactPoint(const btVector3& pointOnB,
 
 void PhysicsController::DebugDrawer::reportErrorWarning(const char* warningString)
 {
-    WARN(warningString);
+    GP_WARN(warningString);
 }
 
 void PhysicsController::DebugDrawer::draw3dText(const btVector3& location, const char* textString)
 {
-    WARN("Physics debug drawing: 3D text is not supported.");
+    GP_WARN("Physics debug drawing: 3D text is not supported.");
 }
 
 void PhysicsController::DebugDrawer::setDebugMode(int mode)

+ 3 - 3
gameplay/src/PhysicsGhostObject.cpp

@@ -42,10 +42,10 @@ PhysicsGhostObject::~PhysicsGhostObject()
 PhysicsGhostObject* PhysicsGhostObject::create(Node* node, Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
-    assert(properties);
+    GP_ASSERT(properties);
     if (!properties || !(strcmp(properties->getNamespace(), "ghostObject") == 0))
     {
-        WARN("Failed to load ghost object from properties object: must be non-null object and have namespace equal to \'ghost\'.");
+        GP_WARN("Failed to load ghost object from properties object: must be non-null object and have namespace equal to 'ghost'.");
         return NULL;
     }
 
@@ -53,7 +53,7 @@ PhysicsGhostObject* PhysicsGhostObject::create(Node* node, Properties* propertie
     PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
     if (shape == NULL)
     {
-        WARN("Failed to create collision shape during ghost object creation.");
+        GP_WARN("Failed to create collision shape during ghost object creation.");
         return NULL;
     }
 

+ 5 - 5
gameplay/src/PhysicsRigidBody.cpp

@@ -144,10 +144,10 @@ void PhysicsRigidBody::applyTorqueImpulse(const Vector3& torque)
 PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
-    assert(properties);
+    GP_ASSERT(properties);
     if (!properties || !(strcmp(properties->getNamespace(), "rigidBody") == 0))
     {
-        WARN("Failed to load rigid body from properties object: must be non-null object and have namespace equal to \'rigidBody\'.");
+        GP_WARN("Failed to load rigid body from properties object: must be non-null object and have namespace equal to 'rigidBody'.");
         return NULL;
     }
 
@@ -155,7 +155,7 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
     PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
     if (shape == NULL)
     {
-        WARN("Failed to create collision shape during rigid body creation.");
+        GP_WARN("Failed to create collision shape during rigid body creation.");
         return NULL;
     }
 
@@ -227,7 +227,7 @@ float PhysicsRigidBody::getHeight(float x, float y) const
     // This function is only supported for heightfield rigid bodies.
     if (_collisionShape->getType() != PhysicsCollisionShape::SHAPE_HEIGHTFIELD)
     {
-        WARN("Attempting to get the height of a non-heightfield rigid body.");
+        GP_WARN("Attempting to get the height of a non-heightfield rigid body.");
         return 0.0f;
     }
 
@@ -248,7 +248,7 @@ float PhysicsRigidBody::getHeight(float x, float y) const
     // Check that the x, y position is within the bounds.
     if (x < 0.0f || x > w || y < 0.0f || y > h)
     {
-        WARN_VARG("Attempting to get height at point '%f, %f', which is outside the range of the heightfield with width %d and height %d.", x, y, w, h);
+        GP_WARN("Attempting to get height at point '%f, %f', which is outside the range of the heightfield with width %d and height %d.", x, y, w, h);
         return 0.0f;
     }
 

+ 2 - 2
gameplay/src/Plane.cpp

@@ -251,7 +251,7 @@ void Plane::transform(const Matrix& matrix)
         float nx = _normal.x * inverted.m[0] + _normal.y * inverted.m[1] + _normal.z * inverted.m[2] + _distance * inverted.m[3];
         float ny = _normal.x * inverted.m[4] + _normal.y * inverted.m[5] + _normal.z * inverted.m[6] + _distance * inverted.m[7];
         float nz = _normal.x * inverted.m[8] + _normal.y * inverted.m[9] + _normal.z * inverted.m[10] + _distance * inverted.m[11];
-        float d = _normal.x * inverted.m[12]+ _normal.y * inverted.m[13] + _normal.z * inverted.m[14]+ _distance * inverted.m[15];
+        float d = _normal.x * inverted.m[12]+ _normal.y * inverted.m[13] + _normal.z * inverted.m[14] + _distance * inverted.m[15];
         float factor = 1.0f / sqrt(nx * nx + ny * ny + nz * nz);
 
         _normal.x = nx * factor;
@@ -271,7 +271,7 @@ void Plane::normalize()
 
     if (normalizeFactor != 1.0f)
     {
-        _normal.x*= normalizeFactor;
+        _normal.x *= normalizeFactor;
         _normal.y *= normalizeFactor;
         _normal.z *= normalizeFactor;
         _distance *= normalizeFactor;

+ 0 - 7
gameplay/src/Platform.h

@@ -89,13 +89,6 @@ public:
      */
     static void setVsync(bool enable);
 
-    /**
-     * Gets the orientation angle the device is currently in.
-     * 
-     * @return The orientation angle.
-     */
-    static int getOrientationAngle();
-
     /**
      * Set if multi-touch is enabled on the platform
      */

+ 182 - 150
gameplay/src/PlatformAndroid.cpp

@@ -13,38 +13,32 @@
 #include <android/log.h>
 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__))
 
-#define TOUCH_COUNT_MAX     4
-
-using namespace std;
-
+// Externally referenced global variables.
 struct android_app* __state;
 AAssetManager* __assetManager;
-std::string __assetsPath;
-bool __initialized = false;
-bool __destroyed = false;
 
+static bool __initialized;
+static bool __suspended;
 static EGLDisplay __eglDisplay = EGL_NO_DISPLAY;
 static EGLContext __eglContext = EGL_NO_CONTEXT;
 static EGLSurface __eglSurface = EGL_NO_SURFACE;
 static EGLConfig __eglConfig = 0;
-int __width;
-int __height;
-
-struct timespec __timespec;
+static int __width;
+static int __height;
+static struct timespec __timespec;
 static long __timeStart;
 static long __timeAbsolute;
 static bool __vsync = WINDOW_VSYNC;
-
-ASensorManager* __sensorManager;
-ASensorEventQueue* __sensorEventQueue;
-ASensorEvent __sensorEvent;
-const ASensor* __accelerometerSensor;
-
-static int __orientationAngle = 90; // Landscape by default.
+static ASensorManager* __sensorManager;
+static ASensorEventQueue* __sensorEventQueue;
+static ASensorEvent __sensorEvent;
+static const ASensor* __accelerometerSensor;
+static int __orientationAngle = 90;
 static bool __multiTouch = false;
 static int __primaryTouchId = -1;
-bool __displayKeyboard = false;
+static bool __displayKeyboard = false;
 
+// OpenGL VAO functions.
 static const char* __glExtensions;
 PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL;
 PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL;
@@ -54,7 +48,7 @@ PFNGLISVERTEXARRAYOESPROC glIsVertexArray = NULL;
 namespace gameplay
 {
 
-long timespec2millis(struct timespec *a)
+static long timespec2millis(struct timespec *a)
 {
     return a->tv_sec*1000 + a->tv_nsec/1000000;
 }
@@ -67,7 +61,7 @@ extern void printError(const char* format, ...)
     va_end(argptr);
 }
 
-EGLenum checkErrorEGL(const char* msg)
+static EGLenum checkErrorEGL(const char* msg)
 {
     static const char* errmsg[] =
     {
@@ -93,7 +87,7 @@ EGLenum checkErrorEGL(const char* msg)
 }
 
 // Initialized EGL resources.
-bool initEGL()
+static bool initEGL()
 {
     // Hard-coded to 32-bit/OpenGL ES 2.0.
     const EGLint eglConfigAttrs[] =
@@ -122,31 +116,34 @@ bool initEGL()
         EGL_NONE
     };
 
-    // Get the EGL display and initialize.
-    __eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    if (__eglDisplay == EGL_NO_DISPLAY)
+    if (__eglDisplay == EGL_NO_DISPLAY && __eglContext == EGL_NO_CONTEXT)
     {
-        checkErrorEGL("eglGetDisplay");
-        goto error;
-    }
+        // Get the EGL display and initialize.
+        __eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        if (__eglDisplay == EGL_NO_DISPLAY)
+        {
+            checkErrorEGL("eglGetDisplay");
+            goto error;
+        }
     
-    if (eglInitialize(__eglDisplay, NULL, NULL) != EGL_TRUE)
-    {
-        checkErrorEGL("eglInitialize");
-        goto error;
-    }
+        if (eglInitialize(__eglDisplay, NULL, NULL) != EGL_TRUE)
+        {
+            checkErrorEGL("eglInitialize");
+            goto error;
+        }
     
-    if (eglChooseConfig(__eglDisplay, eglConfigAttrs, &__eglConfig, 1, &eglConfigCount) != EGL_TRUE || eglConfigCount == 0)
-    {
-        checkErrorEGL("eglChooseConfig");
-        goto error;
-    }
+        if (eglChooseConfig(__eglDisplay, eglConfigAttrs, &__eglConfig, 1, &eglConfigCount) != EGL_TRUE || eglConfigCount == 0)
+        {
+            checkErrorEGL("eglChooseConfig");
+            goto error;
+        }
     
-    __eglContext = eglCreateContext(__eglDisplay, __eglConfig, EGL_NO_CONTEXT, eglContextAttrs);
-    if (__eglContext == EGL_NO_CONTEXT)
-    {
-        checkErrorEGL("eglCreateContext");
-        goto error;
+        __eglContext = eglCreateContext(__eglDisplay, __eglConfig, EGL_NO_CONTEXT, eglContextAttrs);
+        if (__eglContext == EGL_NO_CONTEXT)
+        {
+            checkErrorEGL("eglCreateContext");
+            goto error;
+        }
     }
     
     // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
@@ -172,6 +169,9 @@ bool initEGL()
     
     eglQuerySurface(__eglDisplay, __eglSurface, EGL_WIDTH, &__width);
     eglQuerySurface(__eglDisplay, __eglSurface, EGL_HEIGHT, &__height);
+
+    if (__width < __height)
+        __orientationAngle = 0;
     
     // Set vsync.
     eglSwapInterval(__eglDisplay, WINDOW_VSYNC ? 1 : 0);
@@ -191,78 +191,102 @@ bool initEGL()
     return true;
     
 error:
-
     return false;
 }
 
+static void destroyEGLSurface()
+{
+    if (__eglDisplay != EGL_NO_DISPLAY)
+    {
+        eglMakeCurrent(__eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+    }
+
+    if (__eglSurface != EGL_NO_SURFACE)
+    {
+        eglDestroySurface(__eglDisplay, __eglSurface);
+        __eglSurface = EGL_NO_SURFACE;
+    }
+}
+
+static void destroyEGLMain()
+{
+    destroyEGLSurface();
+
+    if (__eglContext != EGL_NO_CONTEXT)
+    {
+        eglDestroyContext(__eglDisplay, __eglContext);
+        __eglContext = EGL_NO_CONTEXT;
+    }
+
+    if (__eglDisplay != EGL_NO_DISPLAY)
+    {
+        eglTerminate(__eglDisplay);
+        __eglDisplay = EGL_NO_DISPLAY;
+    }
+}
+
 // Display the android virtual keyboard.
-void displayKeyboard(android_app* state, bool pShow)
+static void displayKeyboard(android_app* state, bool show)
 { 
-    
-    // The following functions is supposed to show / hide functins from a native activity.. but currently
-    // do not work. 
+    // The following functions is supposed to show / hide functins from a native activity.. but currently do not work. 
     // ANativeActivity_showSoftInput(state->activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT);
     // ANativeActivity_hideSoftInput(state->activity, ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY);
     
     // Show or hide the keyboard by calling the appropriate Java method through JNI instead.
-    // Attaches the current thread to the JVM.
-    jint lResult;
-    jint lFlags = 0;
-    JavaVM* lJavaVM = state->activity->vm;
-    JNIEnv* lJNIEnv = state->activity->env; 
-    JavaVMAttachArgs lJavaVMAttachArgs;
-    lJavaVMAttachArgs.version = JNI_VERSION_1_6;
-    lJavaVMAttachArgs.name = "NativeThread";
-    lJavaVMAttachArgs.group = NULL;
-    lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs); 
-    if (lResult == JNI_ERR)
+    jint result;
+    jint flags = 0;
+    JavaVM* jvm = state->activity->vm;
+    JNIEnv* env;
+    jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
+    jvm->AttachCurrentThread(&env, NULL);
+    if (result == JNI_ERR)
     { 
         return; 
     } 
     // Retrieves NativeActivity. 
     jobject lNativeActivity = state->activity->clazz;
-    jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity);
+    jclass ClassNativeActivity = env->GetObjectClass(lNativeActivity);
 
     // Retrieves Context.INPUT_METHOD_SERVICE.
-    jclass ClassContext = lJNIEnv->FindClass("android/content/Context");
-    jfieldID FieldINPUT_METHOD_SERVICE = lJNIEnv->GetStaticFieldID(ClassContext, "INPUT_METHOD_SERVICE", "Ljava/lang/String;");
-    jobject INPUT_METHOD_SERVICE = lJNIEnv->GetStaticObjectField(ClassContext, FieldINPUT_METHOD_SERVICE);
+    jclass ClassContext = env->FindClass("android/content/Context");
+    jfieldID FieldINPUT_METHOD_SERVICE = env->GetStaticFieldID(ClassContext, "INPUT_METHOD_SERVICE", "Ljava/lang/String;");
+    jobject INPUT_METHOD_SERVICE = env->GetStaticObjectField(ClassContext, FieldINPUT_METHOD_SERVICE);
     
     // Runs getSystemService(Context.INPUT_METHOD_SERVICE).
-    jclass ClassInputMethodManager = lJNIEnv->FindClass("android/view/inputmethod/InputMethodManager");
-    jmethodID MethodGetSystemService = lJNIEnv->GetMethodID(ClassNativeActivity, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
-    jobject lInputMethodManager = lJNIEnv->CallObjectMethod(lNativeActivity, MethodGetSystemService, INPUT_METHOD_SERVICE);
+    jclass ClassInputMethodManager = env->FindClass("android/view/inputmethod/InputMethodManager");
+    jmethodID MethodGetSystemService = env->GetMethodID(ClassNativeActivity, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
+    jobject lInputMethodManager = env->CallObjectMethod(lNativeActivity, MethodGetSystemService, INPUT_METHOD_SERVICE);
     
     // Runs getWindow().getDecorView().
-    jmethodID MethodGetWindow = lJNIEnv->GetMethodID(ClassNativeActivity, "getWindow", "()Landroid/view/Window;");
-    jobject lWindow = lJNIEnv->CallObjectMethod(lNativeActivity, MethodGetWindow);
-    jclass ClassWindow = lJNIEnv->FindClass("android/view/Window");
-    jmethodID MethodGetDecorView = lJNIEnv->GetMethodID(ClassWindow, "getDecorView", "()Landroid/view/View;");
-    jobject lDecorView = lJNIEnv->CallObjectMethod(lWindow, MethodGetDecorView);
-    if (pShow)
+    jmethodID MethodGetWindow = env->GetMethodID(ClassNativeActivity, "getWindow", "()Landroid/view/Window;");
+    jobject lWindow = env->CallObjectMethod(lNativeActivity, MethodGetWindow);
+    jclass ClassWindow = env->FindClass("android/view/Window");
+    jmethodID MethodGetDecorView = env->GetMethodID(ClassWindow, "getDecorView", "()Landroid/view/View;");
+    jobject lDecorView = env->CallObjectMethod(lWindow, MethodGetDecorView);
+    if (show)
     {
         // Runs lInputMethodManager.showSoftInput(...).
-        jmethodID MethodShowSoftInput = lJNIEnv->GetMethodID( ClassInputMethodManager, "showSoftInput", "(Landroid/view/View;I)Z");
-        jboolean lResult = lJNIEnv->CallBooleanMethod(lInputMethodManager, MethodShowSoftInput, lDecorView, lFlags); 
+        jmethodID MethodShowSoftInput = env->GetMethodID( ClassInputMethodManager, "showSoftInput", "(Landroid/view/View;I)Z");
+        jboolean result = env->CallBooleanMethod(lInputMethodManager, MethodShowSoftInput, lDecorView, flags); 
     } 
     else 
     { 
         // Runs lWindow.getViewToken() 
-        jclass ClassView = lJNIEnv->FindClass("android/view/View");
-        jmethodID MethodGetWindowToken = lJNIEnv->GetMethodID(ClassView, "getWindowToken", "()Landroid/os/IBinder;");
-        jobject lBinder = lJNIEnv->CallObjectMethod(lDecorView, MethodGetWindowToken); 
+        jclass ClassView = env->FindClass("android/view/View");
+        jmethodID MethodGetWindowToken = env->GetMethodID(ClassView, "getWindowToken", "()Landroid/os/IBinder;");
+        jobject lBinder = env->CallObjectMethod(lDecorView, MethodGetWindowToken); 
         
         // lInputMethodManager.hideSoftInput(...). 
-        jmethodID MethodHideSoftInput = lJNIEnv->GetMethodID(ClassInputMethodManager, "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z"); 
-        jboolean lRes = lJNIEnv->CallBooleanMethod( lInputMethodManager, MethodHideSoftInput, lBinder, lFlags); 
+        jmethodID MethodHideSoftInput = env->GetMethodID(ClassInputMethodManager, "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z"); 
+        jboolean lRes = env->CallBooleanMethod( lInputMethodManager, MethodHideSoftInput, lBinder, flags); 
     }
     
     // Finished with the JVM.
-    lJavaVM->DetachCurrentThread(); 
+    jvm->DetachCurrentThread(); 
 }
 
 // Gets the Keyboard::Key enumeration constant that corresponds to the given Android key code.
-Keyboard::Key getKey(int keycode, int metastate)
+static Keyboard::Key getKey(int keycode, int metastate)
 {
     bool shiftOn = (metastate == AMETA_SHIFT_ON);
     
@@ -610,7 +634,7 @@ static int32_t engine_handle_input(struct android_app* app, AInputEvent* event)
 }
 
 // Process the next main command.
-static void engine_handle_cmd(struct android_app* app, int32_t cmd) 
+static void engine_handle_cmd(struct android_app* app, int32_t cmd)
 {
     switch (cmd) 
     {
@@ -618,23 +642,47 @@ static void engine_handle_cmd(struct android_app* app, int32_t cmd)
             // The window is being shown, get it ready.
             if (app->window != NULL)
             {
+                initEGL();
                 __initialized = true;
             }
             break;
         case APP_CMD_TERM_WINDOW:
-            {
-                __destroyed = true;
-                break;
-            }
+            destroyEGLSurface();
+            __initialized = false;
+            break;
+        case APP_CMD_DESTROY:
+            Game::getInstance()->exit();
+            destroyEGLMain();
+            __initialized = false;
+            break;
         case APP_CMD_GAINED_FOCUS:
             // When our app gains focus, we start monitoring the accelerometer.
             if (__accelerometerSensor != NULL) 
             {
                 ASensorEventQueue_enableSensor(__sensorEventQueue, __accelerometerSensor);
-                // We'd like to get 60 events per second (in us).
+                // We'd like to get 60 events per second (in microseconds).
                 ASensorEventQueue_setEventRate(__sensorEventQueue, __accelerometerSensor, (1000L/60)*1000);
             }
-            Game::getInstance()->resume();
+
+            if (Game::getInstance()->getState() == Game::UNINITIALIZED)
+            {
+                Game::getInstance()->run();
+            }
+            else
+            {
+                Game::getInstance()->resume();
+            }
+            break;
+        case APP_CMD_RESUME:
+            if (__initialized)
+            {
+                Game::getInstance()->resume();
+            }
+            __suspended = false;
+            break;
+        case APP_CMD_PAUSE:
+            Game::getInstance()->pause();
+            __suspended = true;
             break;
         case APP_CMD_LOST_FOCUS:
             // When our app loses focus, we stop monitoring the accelerometer.
@@ -643,7 +691,6 @@ static void engine_handle_cmd(struct android_app* app, int32_t cmd)
             {
                 ASensorEventQueue_disableSensor(__sensorEventQueue, __accelerometerSensor);
             }
-            Game::getInstance()->pause();
             break;
     }
 }
@@ -660,28 +707,6 @@ Platform::Platform(const Platform& copy)
 
 Platform::~Platform()
 {
-    if (__eglDisplay != EGL_NO_DISPLAY)
-    {
-        eglMakeCurrent(__eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-    }
-
-    if (__eglSurface != EGL_NO_SURFACE)
-    {
-        eglDestroySurface(__eglDisplay, __eglSurface);
-        __eglSurface = EGL_NO_SURFACE;
-    }
-
-    if (__eglContext != EGL_NO_CONTEXT)
-    {
-        eglDestroyContext(__eglDisplay, __eglContext);
-        __eglContext = EGL_NO_CONTEXT;
-    }
-
-    if (__eglDisplay != EGL_NO_DISPLAY)
-    {
-        eglTerminate(__eglDisplay);
-        __eglDisplay = EGL_NO_DISPLAY;
-    }
 }
 
 Platform* Platform::create(Game* game)
@@ -693,9 +718,15 @@ Platform* Platform::create(Game* game)
 
 int Platform::enterMessagePump()
 {
+    __initialized = false;
+    __suspended = false;
+
     // Get the android application's activity.
     ANativeActivity* activity = __state->activity;
-    JNIEnv* env = activity->env;
+    JavaVM* jvm = __state->activity->vm;
+    JNIEnv* env;
+    jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
+    jvm->AttachCurrentThread(&env, NULL);
 
     // Get the package name for this app from Java.
     jclass clazz = env->GetObjectClass(activity->clazz);
@@ -705,12 +736,13 @@ int Platform::enterMessagePump()
     const char* packageName;
     jboolean isCopy;
     packageName = env->GetStringUTFChars((jstring)result, &isCopy);
+    jvm->DetachCurrentThread();
     
     // Set the default path to store the resources.
-    __assetsPath = "/mnt/sdcard/android/data/";
-    __assetsPath += packageName;
-    __assetsPath += "/";
-    FileSystem::setResourcePath(__assetsPath.c_str());    
+    std::string assetsPath = "/mnt/sdcard/android/data/";
+    assetsPath += packageName;
+    assetsPath += "/";
+    FileSystem::setResourcePath(assetsPath.c_str());    
         
     // Get the asset manager to get the resources from the .apk file.
     __assetManager = __state->activity->assetManager; 
@@ -729,8 +761,6 @@ int Platform::enterMessagePump()
     __timeStart = timespec2millis(&__timespec);
     __timeAbsolute = 0L;
     
-    bool initializeGame = true;
-    
     while (true)
     {
         // Read all pending events.
@@ -738,12 +768,10 @@ int Platform::enterMessagePump()
         int events;
         struct android_poll_source* source;
         
-        bool _shouldPoll = !(__initialized && Game::getInstance()->getState() == Game::UNINITIALIZED) && (Game::getInstance()->getState() != Game::PAUSED);
-        
-        while ((ident=ALooper_pollAll( _shouldPoll ? 0 : -1, NULL, &events, (void**)&source)) >= 0) 
+        while ((ident=ALooper_pollAll(!__suspended ? 0 : -1, NULL, &events, (void**)&source)) >= 0) 
         {
             // Process this event.
-            if (source != NULL) 
+            if (source != NULL)
                 source->process(__state, source);
             
             // If a sensor has data, process it now.
@@ -751,20 +779,14 @@ int Platform::enterMessagePump()
                 ASensorEventQueue_getEvents(__sensorEventQueue, &__sensorEvent, 1);
             
             if (__state->destroyRequested != 0)
-                break;
-        }
-        
-        if (__initialized && initializeGame)
-        {
-            gameplay::initEGL();
-            WARN_VARG("Platform::enterMessagePump() - width: %d  height: %d assetsPath: %s", __width, __height, __assetsPath.c_str());
-            _game->run();
-            initializeGame = false;
+            {
+                return 0;
+            }
         }
         
         // Idle time (no events left to process) is spent rendering.
         // We skip rendering when the app is paused.
-        if (__initialized && _game->getState() != Game::PAUSED)
+        if (__initialized && !__suspended)
         {
             _game->frame();
 
@@ -784,17 +806,23 @@ int Platform::enterMessagePump()
             int rc = eglSwapBuffers(__eglDisplay, __eglSurface);
             if (rc != EGL_TRUE)
             {
-                perror("eglSwapBuffers");
-                _game->exit();
-                break;
+                EGLint error = eglGetError();
+                if (error == EGL_BAD_NATIVE_WINDOW)
+                {
+                    if (__state->window != NULL)
+                    {
+                        destroyEGLSurface();
+                        initEGL();
+                    }
+                    __initialized = true;
+                }
+                else
+                {
+                    perror("eglSwapBuffers");
+                    break;
+                }
             }
         }
-        
-        // Check if we are exiting.
-        if ((__state->destroyRequested != 0) || (__initialized && Game::getInstance()->getState() == Game::UNINITIALIZED))
-        {
-            break;
-        }
             
         // Display the keyboard.
         gameplay::displayKeyboard(__state, __displayKeyboard);
@@ -841,11 +869,6 @@ void Platform::setVsync(bool enable)
     __vsync = enable;
 }
 
-int Platform::getOrientationAngle()
-{
-    return __orientationAngle;
-}
-
 void Platform::setMultiTouch(bool enabled)
 {
     __multiTouch = enabled;
@@ -863,10 +886,19 @@ void Platform::getAccelerometerValues(float* pitch, float* roll)
     
     // By default, android accelerometer values are oriented to the portrait mode.
     // flipping the x and y to get the desired landscape mode values.
-    tx = -__sensorEvent.acceleration.y;
-    ty = __sensorEvent.acceleration.x;
-    tz = -__sensorEvent.acceleration.z;
-    
+    if (__orientationAngle == 90)
+    {
+        tx = -__sensorEvent.acceleration.y;
+        ty = __sensorEvent.acceleration.x;
+    }
+    else
+    {
+        // 0
+        tx = __sensorEvent.acceleration.x;
+        ty = __sensorEvent.acceleration.y;
+    }
+    tz = __sensorEvent.acceleration.z;
+
     if (pitch != NULL)
         *pitch = -atan(ty / sqrt(tx * tx + tz * tz)) * 180.0f * M_1_PI;
     if (roll != NULL)

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä