Prechádzať zdrojové kódy

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

Conflicts:
	gameplay.sln
Steve Grenier 13 rokov pred
rodič
commit
438c396b5a

+ 17 - 5
CHANGES.md

@@ -1,15 +1,27 @@
 ## v1.4.0
 
+- Lua script bindings for all gameplay interfaces.
+- Lua script binding generator tool (gameplay-luagen) for generating gameplay Lua bindings from doxygen xml output.
+- AIController, AIAgent, AIStateMachine, AIState and AIMessage clases for scripted AI support.
+- Sample for sample05-lua to demonstrate basic Lua with AI scripting.
+- Gamepad class with virtual gamepad support.
 - Pre-built versions gameplay-encoder added to bin folder with TTF, DAE and FBX support built-in.
-- Improved modular shader library with support for #include in shaders.
-- Adds Gamepad class for virtual gamepad support.
-- Adds the ability for cloning and wireframe the boy in sample03-character.
+- Improved modular shaders with support for #include in shaders. (breaks compat. for shaders)
+- LightMap support into colored-unlit.frag and textured-unlit.frag shaders.
+- Adds cloning and wireframing features to sample03-character.
+- Adds kick the ball on the sample03-character to demonstrate 2 buttons and more physics.
+- Fixes missing mouse events on UI controls.
 - Fixes to gameplay-encoder to prompt user for font size if not specified.
 - Fixes to add "-g" as short form argument for grouping animations.
 - Fixes node cloning.
 - Fixes to gameplay-encoder for output file path when encoding fonts.
 - Fixes to FrameBuffer, RenderTarget and DepthStencilTarget.
-
+- Fixes user switching in MacOSX to other applications with Apple-Tab.
+- Fixes measureText with empty string to be proper size.
+- Fixed for aliased text by applying linear filtering by default on Fonts.
+- Fixes RenderState::StateBlock::bindNoRestore() issue where blend function was not restored to the proper defaults.
+- Fixes some inconsistencies in Game event method names for menuEvent. (breaks compat. in Game)
+- Fixes some inconsistencies with AnimationClip::getID() to be same as Node::getId() and other classes. (breaks compat. in AnimationClip)
 
 ## v1.3.0
 
@@ -38,7 +50,7 @@
 - Bluetooth keyboard/mouse support on BlackBerry platform.
 - Developer guide.
 - Sample/turorial for sample03-character.
-- Sample for sample04-particles.
+- Sample for sample04-particles to demonstrate particle emitters.
 - Fixes for loading properties from URL.
 - Fixes on Win32/MacOSX for when mouse pointer leaves the window and returns.
 - Fixes to accelerometer for Android.

+ 4 - 3
README.md

@@ -11,9 +11,10 @@ An open-source, cross-platform 3D native C++ game framework making it easy to le
 - Apple MacOS X (using Apple XCode 4.3.2)
 
 ## Roadmap for 'next' branch
-- PhysicsVehicle
-- Terrain
-- ShadowMap
+- Linux support
+- Vehicle physics
+- Shadow mapping
+- AI NavMesh
 
 ## Licence
 The project is open sourced under the Apache 2.0 license.

+ 5 - 0
gameplay-encoder/src/Animations.cpp

@@ -62,4 +62,9 @@ Animation* Animations::getAnimation(unsigned int index) const
     return _animations[index];
 }
 
+void Animations::removeAnimation(unsigned int index)
+{
+    _animations.erase(_animations.begin() + index);
+}
+
 }

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

@@ -32,6 +32,7 @@ public:
     void add(Animation* animation);
     unsigned int getAnimationCount() const;
     Animation* getAnimation(unsigned int index) const;
+    void removeAnimation(unsigned int index);
 
 private:
 

+ 0 - 25
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -34,31 +34,6 @@ unsigned int getMaxOffset(domInputLocalOffset_Array& inputArray)
     return maxOffset;
 }
 
-/**
- * Prompts the user if they want to group animations automatically.
- * If the user enters an invalid response, the question is asked again.
- * 
- * @return True if the user wants to group animations, false otherwise.
- */
-bool promptUserGroupAnimations()
-{
-    char buffer[80];
-    for (;;)
-    {
-        printf("Do you want to group animations ? (y/n)\n");
-        std::cin.getline(buffer, 80);
-        
-        if (buffer[0] == 'y' || buffer[0] == 'Y' || buffer[0] == '\0')
-        {
-            return true;
-        }
-        else if (buffer[0] == 'n' || buffer[0] == 'N')
-        {
-            return false;
-        }
-    }
-}
-
 void DAESceneEncoder::optimizeCOLLADA(const EncoderArguments& arguments, domCOLLADA* dom)
 {
     const std::vector<std::string>& groupAnimatioNodeIds = arguments.getGroupAnimationNodeId();

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

@@ -154,13 +154,24 @@ void addScaleChannel(Animation* animation, FbxNode* fbxNode, float startTime, fl
 
 void addTranslateChannel(Animation* animation, FbxNode* fbxNode, float startTime, float stopTime);
 
+/**
+ * Determines if it is possible to automatically group animations for mesh skins.
+ * 
+ * @param fbxScene The FBX scene to search.
+ * 
+ * @return True if there is at least one mesh skin that has animations that can be grouped.
+ */
+bool isGroupAnimationPossible(FbxScene* fbxScene);
+bool isGroupAnimationPossible(FbxNode* fbxNode);
+bool isGroupAnimationPossible(FbxMesh* fbxMesh);
+
 
 ////////////////////////////////////
 // Member Functions
 ////////////////////////////////////
 
 FBXSceneEncoder::FBXSceneEncoder()
-    : _groupAnimation(NULL)
+    : _groupAnimation(NULL), _autoGroupAnimations(false)
 {
 }
 
@@ -187,6 +198,16 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
     print("Loading FBX file.");
     importer->Import(fbxScene);
     importer->Destroy();
+
+    // Determine if animations should be grouped.
+    if (arguments.getGroupAnimationAnimationId().empty() && isGroupAnimationPossible(fbxScene))
+    {
+        if (promptUserGroupAnimations())
+        {
+            _autoGroupAnimations = true;
+        }
+    }
+
     print("Loading Scene.");
     loadScene(fbxScene);
     print("Loading animations.");
@@ -195,6 +216,10 @@ void FBXSceneEncoder::write(const std::string& filepath, const EncoderArguments&
 
     print("Optimizing GamePlay Binary.");
     _gamePlayFile.adjust();
+    if (_autoGroupAnimations)
+    {
+        _gamePlayFile.groupMeshSkinAnimations();
+    }
     
     std::string outputFilePath = arguments.getOutputFilePath();
 
@@ -1473,4 +1498,53 @@ void copyMatrix(const FbxMatrix& fbxMatrix, Matrix& matrix)
     }
 }
 
+bool isGroupAnimationPossible(FbxScene* fbxScene)
+{
+    FbxNode* rootNode = fbxScene->GetRootNode();
+    if (rootNode)
+    {
+        if (isGroupAnimationPossible(rootNode))
+            return true;
+    }
+    return false;
+}
+
+bool isGroupAnimationPossible(FbxNode* fbxNode)
+{
+    if (fbxNode)
+    {
+        FbxMesh* fbxMesh = fbxNode->GetMesh();
+        if (isGroupAnimationPossible(fbxMesh))
+            return true;
+        const int childCount = fbxNode->GetChildCount();
+        for (int i = 0; i < childCount; ++i)
+        {
+            if (isGroupAnimationPossible(fbxNode->GetChild(i)))
+                return true;
+        }
+    }
+    return false;
+}
+
+bool isGroupAnimationPossible(FbxMesh* fbxMesh)
+{
+    if (fbxMesh)
+    {
+        const int deformerCount = fbxMesh->GetDeformerCount();
+        for (int i = 0; i < deformerCount; ++i)
+        {
+            FbxDeformer* deformer = fbxMesh->GetDeformer(i);
+            if (deformer->GetDeformerType() == FbxDeformer::eSkin)
+            {
+                FbxSkin* fbxSkin = static_cast<FbxSkin*>(deformer);
+                if (fbxSkin)
+                {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
 #endif

+ 6 - 1
gameplay-encoder/src/FBXSceneEncoder.h

@@ -215,9 +215,14 @@ private:
     std::map<FbxUInt64, Mesh*> _meshes;
 
     /**
-     * The animation that channels should be added to it the user is using the -groupAnimation command line argument. May be NULL.
+     * The animation that channels should be added to if the user is using the -groupAnimation command line argument. May be NULL.
      */
     Animation* _groupAnimation;
+
+    /**
+     * Indicates if the animations for mesh skins should be grouped before writing out the GPB file.
+     */
+    bool _autoGroupAnimations;
 };
 
 #endif

+ 19 - 0
gameplay-encoder/src/FileIO.cpp

@@ -184,4 +184,23 @@ void writeVectorText(const Vector4& v, FILE* file)
     fprintf(file, "%f %f %f %f\n", v.x, v.y, v.z, v.w);
 }
 
+bool promptUserGroupAnimations()
+{
+    char buffer[80];
+    for (;;)
+    {
+        printf("Do you want to group animations? (y/n)\n");
+        std::cin.getline(buffer, 80);
+        
+        if (buffer[0] == 'y' || buffer[0] == 'Y' || buffer[0] == '\0')
+        {
+            return true;
+        }
+        else if (buffer[0] == 'n' || buffer[0] == 'N')
+        {
+            return false;
+        }
+    }
+}
+
 }

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

@@ -136,6 +136,14 @@ void writeVectorBinary(const Vector4& v, FILE* file);
 
 void writeVectorText(const Vector4& v, FILE* file);
 
+/**
+ * Prompts the user if they want to group animations automatically.
+ * If the user enters an invalid response, the question is asked again.
+ * 
+ * @return True if the user wants to group animations, false otherwise.
+ */
+bool promptUserGroupAnimations();
+
 }
 
 #endif

+ 106 - 9
gameplay-encoder/src/GPBFile.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "GPBFile.h"
 #include "Transform.h"
+#include "StringUtil.h"
 
 #define EPSILON 1.2e-7f;
 
@@ -14,6 +15,26 @@ static GPBFile* __instance = NULL;
  */
 static bool isAlmostOne(float value);
 
+/**
+ * Gets the common node ancestor for the given list of nodes.
+ * This function assumes that the nodes share a common ancestor.
+ * 
+ * @param nodes The list of nodes.
+ * 
+ * @return The common node ancestor or NULL if the list of was empty.
+ */
+static Node* getCommonNodeAncestor(const std::vector<Node*>& nodes);
+
+/**
+ * Gets the list of node ancestors for the given node.
+ * 
+ * @param node The node to get the ancestors for.
+ * @param ancestors The output list of ancestors. 
+ *                  The first element is the root node and the last element is the direct parent of the node.
+ */
+static void getNodeAncestors(Node* node, std::list<Node*>& ancestors);
+
+
 GPBFile::GPBFile(void)
     : _file(NULL), _animationsAdded(false)
 {
@@ -304,6 +325,30 @@ void GPBFile::adjust()
     //   This can be merged into one animation. Same for scale animations.
 }
 
+void GPBFile::groupMeshSkinAnimations()
+{
+    for (std::list<Node*>::iterator it = _nodes.begin(); it != _nodes.end(); ++it)
+    {
+        if (Model* model = (*it)->getModel())
+        {
+            if (MeshSkin* skin = model->getSkin())
+            {
+                const std::vector<Node*>& joints = skin->getJoints();
+                Node* commonAncestor = getCommonNodeAncestor(joints);
+                if (commonAncestor)
+                {
+                    // group the animation channels that target this common ancestor and its child nodes
+                    Animation* animation = new Animation();
+                    animation->setId("animations");
+
+                    moveAnimationChannels(commonAncestor, animation);
+                    _animations.add(animation);
+                }
+            }
+        }
+    }
+}
+
 void GPBFile::renameAnimations(std::vector<std::string>& animationIds, const char* newId)
 {
     const unsigned int animationCount = _animations.getAnimationCount();
@@ -328,14 +373,6 @@ void GPBFile::computeBounds(Node* node)
         {
             mesh->computeBounds();
         }
-        if (MeshSkin* skin = model->getSkin())
-        {
-            skin->computeBounds();
-        }
-    }
-    for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
-    {
-        computeBounds(child);
     }
 }
 
@@ -442,9 +479,69 @@ void GPBFile::decomposeTransformAnimationChannel(Animation* animation, const Ani
     animation->add(translateChannel);
 }
 
-static bool isAlmostOne(float value)
+void GPBFile::moveAnimationChannels(Node* node, Animation* dstAnimation)
+{
+    // Loop through the animations and channels backwards because they will be removed when found.
+    int animationCount = _animations.getAnimationCount();
+    for (int i = animationCount - 1; i >= 0; --i)
+    {
+        Animation* animation = _animations.getAnimation(i);
+        int channelCount = animation->getAnimationChannelCount();
+        for (int j = channelCount - 1; j >= 0; --j)
+        {
+            AnimationChannel* channel = animation->getAnimationChannel(j);
+            if (equals(channel->getTargetId(), node->getId()))
+            {
+                animation->remove(channel);
+                dstAnimation->add(channel);
+            }
+        }
+        if (animation->getAnimationChannelCount() == 0)
+        {
+            _animations.removeAnimation(i);
+        }
+    }
+    for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
+    {
+        moveAnimationChannels(child, dstAnimation);
+    }
+}
+
+bool isAlmostOne(float value)
 {
     return std::fabs(value - 1.0f) < EPSILON;
 }
 
+Node* getCommonNodeAncestor(const std::vector<Node*>& nodes)
+{
+    if (nodes.empty())
+        return NULL;
+    if (nodes.size() == 1)
+        return nodes.front();
+
+    std::list<Node*> ancestors;
+    size_t minAncestorCount = INT_MAX;
+    for (std::vector<Node*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
+    {
+        Node* node = *it;
+        getNodeAncestors(node, ancestors);
+        ancestors.push_back(node);
+        minAncestorCount = std::min(minAncestorCount, ancestors.size());
+    }
+    ancestors.resize(minAncestorCount);
+
+    return ancestors.back();
+}
+
+void getNodeAncestors(Node* node, std::list<Node*>& ancestors)
+{
+    ancestors.clear();
+    Node* parent = node->getParent();
+    while (parent != NULL)
+    {
+        ancestors.push_front(parent);
+        parent = parent->getParent();
+    }
+}
+
 }

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

@@ -102,6 +102,11 @@ public:
      */
     void adjust();
 
+    /**
+     * Groups the animations of all mesh skins to be under one animation per mesh skin.
+     */
+    void groupMeshSkinAnimations();
+
     /**
      * Renames the animations in the list of animation ids to the new animation id.
      * 
@@ -125,6 +130,14 @@ private:
      */
     void decomposeTransformAnimationChannel(Animation* animation, const AnimationChannel* channel);
 
+    /**
+     * Moves the animation channels that target the given node and its children to be under the given animation.
+     * 
+     * @param node The node to recursively search from.
+     * @param animation The animation to move the channels to.
+     */
+    void moveAnimationChannels(Node* node, Animation* animation);
+
 private:
 
     FILE* _file;
@@ -132,6 +145,9 @@ private:
     std::list<Camera*> _cameras;
     std::list<Light*> _lights;
     std::list<Mesh*> _geometry;
+    /**
+     * The flat list of all nodes.
+     */
     std::list<Node*> _nodes;
     Animations _animations;
     bool _animationsAdded;

+ 5 - 0
gameplay-encoder/src/MeshSkin.cpp

@@ -109,6 +109,11 @@ const std::vector<std::string>& MeshSkin::getJointNames()
     return _jointNames;
 }
 
+const std::vector<Node*>& MeshSkin::getJoints() const
+{
+    return _joints;
+}
+
 void MeshSkin::setJoints(const std::vector<Node*>& list)
 {
     _joints = list;

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

@@ -38,9 +38,11 @@ public:
 
     void setVertexInfluenceCount(unsigned int count);
 
+    const std::vector<std::string>& getJointNames();
+
     void setJointNames(const std::vector<std::string>& list);
 
-    const std::vector<std::string>& getJointNames();
+    const std::vector<Node*>& getJoints() const;
 
     void setJoints(const std::vector<Node*>& list);
 

+ 7 - 7
gameplay.sln

@@ -28,7 +28,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample00-mesh", "gameplay-s
 		{1032BA4B-57EB-4348-9E03-29DD63E80E4A} = {1032BA4B-57EB-4348-9E03-29DD63E80E4A}
 	EndProjectSection
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample05-lua", "gameplay-samples\sample05-lua\sample05-lua.vcxproj", "{04EAF3E5-0F9E-AF4D-53F9-269CE114211F}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample05-lua", "gameplay-samples\sample05-lua\sample05-lua.vcxproj", "{C6121A62-AA46-BA6D-A1CE-8000544456AA}"
 	ProjectSection(ProjectDependencies) = postProject
 		{1032BA4B-57EB-4348-9E03-29DD63E80E4A} = {1032BA4B-57EB-4348-9E03-29DD63E80E4A}
 	EndProjectSection
@@ -76,12 +76,12 @@ Global
 		{D672DC66-3CE0-4878-B0D2-813CA731012F}.DebugMem|Win32.Build.0 = DebugMem|Win32
 		{D672DC66-3CE0-4878-B0D2-813CA731012F}.Release|Win32.ActiveCfg = Release|Win32
 		{D672DC66-3CE0-4878-B0D2-813CA731012F}.Release|Win32.Build.0 = Release|Win32
-		{04EAF3E5-0F9E-AF4D-53F9-269CE114211F}.Debug|Win32.ActiveCfg = Debug|Win32
-		{04EAF3E5-0F9E-AF4D-53F9-269CE114211F}.Debug|Win32.Build.0 = Debug|Win32
-		{04EAF3E5-0F9E-AF4D-53F9-269CE114211F}.DebugMem|Win32.ActiveCfg = DebugMem|Win32
-		{04EAF3E5-0F9E-AF4D-53F9-269CE114211F}.DebugMem|Win32.Build.0 = DebugMem|Win32
-		{04EAF3E5-0F9E-AF4D-53F9-269CE114211F}.Release|Win32.ActiveCfg = Release|Win32
-		{04EAF3E5-0F9E-AF4D-53F9-269CE114211F}.Release|Win32.Build.0 = Release|Win32
+		{C6121A62-AA46-BA6D-A1CE-8000544456AA}.Debug|Win32.ActiveCfg = Debug|Win32
+		{C6121A62-AA46-BA6D-A1CE-8000544456AA}.Debug|Win32.Build.0 = Debug|Win32
+		{C6121A62-AA46-BA6D-A1CE-8000544456AA}.DebugMem|Win32.ActiveCfg = DebugMem|Win32
+		{C6121A62-AA46-BA6D-A1CE-8000544456AA}.DebugMem|Win32.Build.0 = DebugMem|Win32
+		{C6121A62-AA46-BA6D-A1CE-8000544456AA}.Release|Win32.ActiveCfg = Release|Win32
+		{C6121A62-AA46-BA6D-A1CE-8000544456AA}.Release|Win32.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 30 - 17
gameplay/src/Container.cpp

@@ -40,7 +40,8 @@ Container::Container()
       _scrollingVelocity(Vector2::zero()), _scrollingFriction(1.0f),
       _scrollingRight(false), _scrollingDown(false),
       _scrollingMouseVertically(false), _scrollingMouseHorizontally(false),
-      _scrollBarOpacityClip(NULL), _zIndexDefault(0), _focusIndexDefault(0), _focusIndexMax(0), _totalWidth(0), _totalHeight(0)
+      _scrollBarOpacityClip(NULL), _zIndexDefault(0), _focusIndexDefault(0), _focusIndexMax(0), _totalWidth(0), _totalHeight(0),
+      _contactIndices(0)
 {
 }
 
@@ -971,8 +972,9 @@ bool Container::pointerEvent(bool mouse, char evt, int x, int y, int data)
         }
 
         Control::State currentState = control->getState();
-        if ((currentState != Control::NORMAL && control->_contactIndex == data) ||
-            ((control->isContainer() || evt == Touch::TOUCH_PRESS ||
+        if ((control->isContainer() && currentState == Control::FOCUS) || 
+            (currentState != Control::NORMAL && control->_contactIndex == data) ||
+            ((evt == Touch::TOUCH_PRESS ||
               evt == Mouse::MOUSE_PRESS_LEFT_BUTTON ||
               evt == Mouse::MOUSE_PRESS_MIDDLE_BUTTON ||
               evt == Mouse::MOUSE_PRESS_RIGHT_BUTTON ||
@@ -984,9 +986,9 @@ bool Container::pointerEvent(bool mouse, char evt, int x, int y, int data)
         {
             // Pass on the event's clip relative to the control.
             if (mouse)
-                eventConsumed = control->mouseEvent((Mouse::MouseEvent)evt, x - xPos - boundsX, y - yPos - boundsY, data);
+                eventConsumed |= control->mouseEvent((Mouse::MouseEvent)evt, x - xPos - boundsX, y - yPos - boundsY, data);
             else
-                eventConsumed = control->touchEvent((Touch::TouchEvent)evt, x - xPos - boundsX, y - yPos - boundsY, (unsigned int)data);
+                eventConsumed |= control->touchEvent((Touch::TouchEvent)evt, x - xPos - boundsX, y - yPos - boundsY, (unsigned int)data);
         }
     }
 
@@ -994,17 +996,7 @@ bool Container::pointerEvent(bool mouse, char evt, int x, int y, int data)
     {
         return (_consumeInputEvents | eventConsumed);
     }
-
-    if (!eventConsumed && _scroll != SCROLL_NONE)
-    {
-        if ((mouse && mouseEventScroll((Mouse::MouseEvent)evt, x - xPos, y - yPos, data)) ||
-            (!mouse && touchEventScroll((Touch::TouchEvent)evt, x - xPos, y - yPos, (unsigned int)data)))
-        {
-            _dirty = true;
-            eventConsumed = true;
-        }
-    }
-
+    
     switch (evt)
     {
     case Touch::TOUCH_PRESS:
@@ -1012,13 +1004,34 @@ bool Container::pointerEvent(bool mouse, char evt, int x, int y, int data)
             y > _clipBounds.y && y <= _clipBounds.y + _clipBounds.height)
         {
             setState(Control::FOCUS);
+            if (eventConsumed)
+                _contactIndices++;
         }
-        else
+        else if (_contactIndices == 0)
         {
             setState(Control::NORMAL);
             return false;
         }
         break;
+    case Touch::TOUCH_RELEASE:
+        {
+            if (eventConsumed)
+            {
+                if (_contactIndices > 0)
+                    _contactIndices--;
+            }
+        }
+        break;
+    }
+
+    if (!eventConsumed && _scroll != SCROLL_NONE && getState() == Control::FOCUS)
+    {
+        if ((mouse && mouseEventScroll((Mouse::MouseEvent)evt, x - xPos, y - yPos, data)) ||
+            (!mouse && touchEventScroll((Touch::TouchEvent)evt, x - xPos, y - yPos, (unsigned int)data)))
+        {
+            _dirty = true;
+            eventConsumed = true;
+        }
     }
 
     return (_consumeInputEvents | eventConsumed);

+ 2 - 0
gameplay/src/Container.h

@@ -504,6 +504,8 @@ private:
 
     float _totalWidth;
     float _totalHeight;
+
+    int _contactIndices;
 };
 
 }

+ 4 - 0
gameplay/src/Font.cpp

@@ -140,6 +140,10 @@ Font* Font::create(const char* family, Style style, unsigned int size, Glyph* gl
     // Create batch for the font.
     SpriteBatch* batch = SpriteBatch::create(texture, __fontEffect, 128);
 
+    // Add linear filtering for better font quality.
+    Texture::Sampler* sampler = batch->getSampler();
+    sampler->setFilterMode(Texture::LINEAR, Texture::LINEAR);
+
     // Release __fontEffect since the SpriteBatch keeps a reference to it
     SAFE_RELEASE(__fontEffect);
 

+ 6 - 11
gameplay/src/Font.h

@@ -32,17 +32,12 @@ public:
      */
     enum Justify
     {
-        // Specify horizontal alignment, use default vertical alignment (ALIGN_TOP).
         ALIGN_LEFT = 0x01,
         ALIGN_HCENTER = 0x02,
         ALIGN_RIGHT = 0x04,
-    
-        // Specify vertical alignment, use default horizontal alignment (ALIGN_LEFT).
         ALIGN_TOP = 0x10,
         ALIGN_VCENTER = 0x20,
         ALIGN_BOTTOM = 0x40,
-
-        // Specify both vertical and horizontal alignment.
         ALIGN_TOP_LEFT = ALIGN_TOP | ALIGN_LEFT,
         ALIGN_VCENTER_LEFT = ALIGN_VCENTER | ALIGN_LEFT,
         ALIGN_BOTTOM_LEFT = ALIGN_BOTTOM | ALIGN_LEFT,
@@ -121,11 +116,6 @@ public:
      */
     void start();
 
-    /**
-     * Finishes text batching for this font and renders all drawn text.
-     */
-    void finish();
-
     /**
      * Draws the specified text in a solid color, with a scaling factor.
      *
@@ -181,6 +171,11 @@ public:
     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);
 
+    /**
+     * Finishes text batching for this font and renders all drawn text.
+     */
+    void finish();
+
     /**
      * Measures a string's width and height without alignment, wrapping or clipping.
      *
@@ -235,8 +230,8 @@ public:
      */
     static Justify getJustify(const char* justify);
 
-
 private:
+
     /**
      * Defines a font glyph within the texture map for a font.
      */

+ 9 - 3
gameplay/src/SpriteBatch.cpp

@@ -48,13 +48,14 @@ namespace gameplay
 static Effect* __spriteEffect = NULL;
 
 SpriteBatch::SpriteBatch()
-    : _batch(NULL), _textureWidthRatio(0.0f), _textureHeightRatio(0.0f)
+    : _batch(NULL), _sampler(NULL), _textureWidthRatio(0.0f), _textureHeightRatio(0.0f)
 {
 }
 
 SpriteBatch::~SpriteBatch()
 {
     SAFE_DELETE(_batch);
+    SAFE_RELEASE(_sampler);
     if (!_customEffect)
     {
         if (__spriteEffect && __spriteEffect->getRefCount() == 1)
@@ -131,8 +132,7 @@ SpriteBatch* SpriteBatch::create(Texture* texture, Effect* effect, unsigned int
     // Bind the texture to the material as a sampler
     Texture::Sampler* sampler = Texture::Sampler::create(texture); // +ref texture
     material->getParameter(samplerUniform->getName())->setValue(sampler);
-    SAFE_RELEASE(sampler);
-
+    
     // Define the vertex format for the batch
     VertexFormat::Element vertexElements[] =
     {
@@ -148,6 +148,7 @@ SpriteBatch* SpriteBatch::create(Texture* texture, Effect* effect, unsigned int
 
     // Create the batch
     SpriteBatch* batch = new SpriteBatch();
+    batch->_sampler = sampler;
     batch->_customEffect = customEffect;
     batch->_batch = meshBatch;
     batch->_textureWidthRatio = 1.0f / (float)texture->getWidth();
@@ -395,6 +396,11 @@ RenderState::StateBlock* SpriteBatch::getStateBlock() const
     return _batch->getMaterial()->getStateBlock();
 }
 
+Texture::Sampler* SpriteBatch::getSampler() const
+{
+    return _sampler;
+}
+
 Material* SpriteBatch::getMaterial() const
 {
     return _batch->getMaterial();

+ 11 - 1
gameplay/src/SpriteBatch.h

@@ -246,7 +246,16 @@ public:
     void finish();
 
     /**
-     * Returns the StateBlock for the SpriteBatch.
+     * Gets the texture sampler. 
+     *
+     * This return texture sampler is used when sampling the texture in the
+     * effect. This can be modified for controlling sampler setting such as
+     * filtering modes.
+     */
+    Texture::Sampler* getSampler() const;
+
+    /**
+     * Gets the StateBlock for the SpriteBatch.
      *
      * The returned state block controls the renderer state used when drawing items
      * with this sprite batch. Modification can be made to the returned state block
@@ -363,6 +372,7 @@ private:
     bool clipSprite(const Rectangle& clip, float& x, float& y, float& width, float& height, float& u1, float& v1, float& u2, float& v2);
 
     MeshBatch* _batch;
+    Texture::Sampler* _sampler;
     bool _customEffect;
     float _textureWidthRatio;
     float _textureHeightRatio;

+ 8 - 2
gameplay/src/Texture.h

@@ -63,6 +63,11 @@ public:
 
     public:
 
+        /**
+         * Destructor.
+         */
+        virtual ~Sampler();
+
         /**
          * Creates a sampler for the specified texture.
          *
@@ -114,10 +119,11 @@ public:
 
     private:
 
+        /**
+         * Constructor.
+         */
         Sampler(Texture* texture);
 
-        ~Sampler();
-
         /**
          * Hidden copy assignment operator.
          */