Selaa lähdekoodia

- Fixed an error in Matrix::createOrthographicOffCenter.
- Changed orthographic camera mode to have an origin of bottom left, with +Y up and +X to the right.
- Added configurable character spacing to the Font class.
- Changed Scene:addNode and Node:addChild to add nodes to the back of the list instead of the front, so that traversal order matches the order in which nodes are added.
- Added support for creating hierarchy (child nodes) in a .scene file.

sgrenier 12 vuotta sitten
vanhempi
sitoutus
445eade2fc

+ 1 - 0
gameplay/src/Camera.cpp

@@ -268,6 +268,7 @@ const Matrix& Camera::getProjectionMatrix() const
         }
         else
         {
+            // Create an ortho projection with the origin at the bottom left of the viewport, +X to the right and +Y up.
             Matrix::createOrthographic(_zoom[0], _zoom[1], _nearPlane, _farPlane, &_projection);
         }
 

+ 29 - 9
gameplay/src/Font.cpp

@@ -16,7 +16,7 @@ static std::vector<Font*> __fontCache;
 static Effect* __fontEffect = NULL;
 
 Font::Font() :
-    _style(PLAIN), _size(0), _glyphs(NULL), _glyphCount(0), _texture(NULL), _batch(NULL)
+    _style(PLAIN), _size(0), _spacing(0.125f), _glyphs(NULL), _glyphCount(0), _texture(NULL), _batch(NULL)
 {
 }
 
@@ -168,6 +168,7 @@ Font::Text* Font::createText(const char* text, const Rectangle& area, const Vect
         size = _size;
     GP_ASSERT(_size);
     float scale = (float)size / _size;
+    int spacing = (int)(size * _spacing);
     int yPos = area.y;
     const float areaHeight = area.height - size;
     std::vector<int> xPositions;
@@ -317,7 +318,7 @@ Font::Text* Font::createText(const char* text, const Rectangle& area, const Vect
 
                     }
                 }
-                xPos += (int)(g.width)*scale + (size >> 3);
+                xPos += (int)(g.width)*scale + spacing;
             }
         }
 
@@ -402,6 +403,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
     GP_ASSERT(_size);
     GP_ASSERT(text);
     float scale = (float)size / _size;
+    int spacing = (int)(size * _spacing);
     const char* cursor = NULL;
 
     if (rightToLeft)
@@ -497,7 +499,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                 {
                     Glyph& g = _glyphs[index];
                     _batch->draw(xPos, yPos, g.width * scale, size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
-                    xPos += floor(g.width * scale + (float)(size >> 3));
+                    xPos += floor(g.width * scale + spacing);
                     break;
                 }
                 break;
@@ -528,6 +530,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
         size = _size;
     GP_ASSERT(_size);
     float scale = (float)size / _size;
+    int spacing = (int)(size * _spacing);
     int yPos = area.y;
     const float areaHeight = area.height - size;
     std::vector<int> xPositions;
@@ -647,7 +650,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                         }
                     }
                 }
-                xPos += (int)(g.width)*scale + (size >> 3);
+                xPos += (int)(g.width)*scale + spacing;
             }
         }
 
@@ -1277,6 +1280,16 @@ void Font::getMeasurementInfo(const char* text, const Rectangle& area, unsigned
     }
 }
 
+float Font::getCharacterSpacing() const
+{
+    return _spacing;
+}
+
+void Font::setCharacterSpacing(float spacing)
+{
+    _spacing = spacing;
+}
+
 int Font::getIndexAtLocation(const char* text, const Rectangle& area, unsigned int size, const Vector2& inLocation, Vector2* outLocation,
                                       Justify justify, bool wrap, bool rightToLeft)
 {
@@ -1299,7 +1312,10 @@ int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned i
     unsigned int charIndex = 0;
 
     // Essentially need to measure text until we reach inLocation.
+    if (size == 0)
+        size = _size;
     float scale = (float)size / _size;
+    int spacing = (int)(size * _spacing);
     int yPos = area.y;
     const float areaHeight = area.height - size;
     std::vector<int> xPositions;
@@ -1354,7 +1370,7 @@ int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned i
 
         if (destIndex == (int)charIndex ||
             (destIndex == -1 &&
-             inLocation.x >= xPos && inLocation.x < floor(xPos + (float)(size >> 3)) &&
+             inLocation.x >= xPos && inLocation.x < xPos + spacing &&
              inLocation.y >= yPos && inLocation.y < yPos + size))
         {
             outLocation->x = xPos;
@@ -1426,7 +1442,7 @@ int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned i
                 // Check against inLocation.
                 if (destIndex == (int)charIndex ||
                     (destIndex == -1 &&
-                    inLocation.x >= xPos && inLocation.x < floor(xPos + g.width*scale + (float)(size >> 3)) &&
+                    inLocation.x >= xPos && inLocation.x < floor(xPos + g.width*scale + spacing) &&
                     inLocation.y >= yPos && inLocation.y < yPos + size))
                 {
                     outLocation->x = xPos;
@@ -1434,7 +1450,7 @@ int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned i
                     return charIndex;
                 }
 
-                xPos += floor(g.width*scale + (float)(size >> 3));
+                xPos += floor(g.width*scale + spacing);
                 charIndex++;
             }
         }
@@ -1508,7 +1524,7 @@ int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned i
 
     if (destIndex == (int)charIndex ||
         (destIndex == -1 &&
-         inLocation.x >= xPos && inLocation.x < floor(xPos + (float)(size >> 3)) &&
+         inLocation.x >= xPos && inLocation.x < xPos + spacing &&
          inLocation.y >= yPos && inLocation.y < yPos + size))
     {
         outLocation->x = xPos;
@@ -1524,6 +1540,10 @@ unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigne
     GP_ASSERT(token);
     GP_ASSERT(_glyphs);
 
+    if (size == 0)
+        size = _size;
+    int spacing = (int)(size * _spacing);
+
     // Calculate width of word or line.
     unsigned int tokenWidth = 0;
     for (unsigned int i = 0; i < length; ++i)
@@ -1542,7 +1562,7 @@ unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigne
             if (glyphIndex >= 0 && glyphIndex < (int)_glyphCount)
             {
                 Glyph& g = _glyphs[glyphIndex];
-                tokenWidth += floor(g.width * scale + (float)(size >> 3));
+                tokenWidth += floor(g.width * scale + spacing);
             }
             break;
         }

+ 23 - 0
gameplay/src/Font.h

@@ -220,6 +220,28 @@ public:
     void measureText(const char* text, const Rectangle& clip, unsigned int size, Rectangle* out,
                      Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool ignoreClip = false);
 
+    /**
+     * Returns current character spacing for this font in percentage of fonts size.
+     *
+     * @see setCharacterSpacing(float)
+     */
+    float getCharacterSpacing() const;
+
+    /**
+     * Sets the fixed character spacing for this font.
+     *
+     * Character spacing is the fixed amount of space that is inserted between characters. This is a simplified
+     * type of kerning and does not take adjacent characters into consideration. Character spacing is defined
+     * as a floating point value that is interpreted as a percentage of size used to draw the font. For example,
+     * a value of 0.1 would cause a spacing of 10% of the font size to be inserted between adjacent characters.
+     * For a font size of 20, this would equate to 2 pixels of extra space between characters.
+     *
+     * The default character spacing for fonts is 0.125.
+     *
+     * @param spacing New fixed character spacing, expressed as a percentage of font size.
+     */
+    void setCharacterSpacing(float spacing);
+
     /**
      * Get an character index into a string corresponding to the character nearest the given location within the clip region.
      */
@@ -332,6 +354,7 @@ private:
     std::string _family;
     Style _style;
     unsigned int _size;
+    float _spacing;
     Glyph* _glyphs;
     unsigned int _glyphCount;
     Texture* _texture;

+ 7 - 11
gameplay/src/Matrix.cpp

@@ -152,18 +152,14 @@ void Matrix::createOrthographicOffCenter(float left, float right, float bottom,
     GP_ASSERT(top != bottom);
     GP_ASSERT(zFarPlane != zNearPlane);
 
-    float r_l = 1.0f / (right - left);
-    float t_b = 1.0f / (top - bottom);
-    float f_n = 1.0f / (zFarPlane - zNearPlane);
-
     memset(dst, 0, MATRIX_SIZE);
-    dst->m[0] = 2.0f * r_l;
-    dst->m[5] = 2.0f * t_b;
-    dst->m[10] = -2.0f * f_n;
-    dst->m[12] = (-(right + left)) * r_l;
-    dst->m[13] = (-(top + bottom)) * t_b;
-    dst->m[14] = (-(zFarPlane + zNearPlane)) * f_n;
-    dst->m[15] = 1.0f;
+    dst->m[0] = 2 / (right - left);
+    dst->m[5] = 2 / (top - bottom);
+    dst->m[12] = (left + right) / (left - right);
+    dst->m[10] = 1 / (zNearPlane - zFarPlane);
+    dst->m[13] = (top + bottom) / (bottom - top);
+    dst->m[14] = zNearPlane / (zNearPlane - zFarPlane);
+    dst->m[15] = 1;
 }
     
 void Matrix::createBillboard(const Vector3& objectPosition, const Vector3& cameraPosition,

+ 11 - 12
gameplay/src/Node.cpp

@@ -110,12 +110,18 @@ void Node::addChild(Node* child)
         child->_scene->removeNode(child);
     }
 
-    // Order is irrelevant, so add to the beginning of the list.
+    // Add child to the end of the list.
+    // NOTE: This is different than the original behavior which inserted nodes
+    // into the beginning of the list. Although slightly slower to add to the
+    // end of the list, it makes scene traversal and drawing order more
+    // predictable, so I've changed it.
     if (_firstChild)
     {
-        _firstChild->_prevSibling = child;
-        child->_nextSibling = _firstChild;
-        _firstChild = child;
+        Node* n = _firstChild;
+        while (n->_nextSibling)
+            n = n->_nextSibling;
+        n->_nextSibling = child;
+        child->_prevSibling = n;
     }
     else
     {
@@ -988,15 +994,8 @@ Node* Node::cloneRecursive(NodeCloneContext &context) const
     Node* copy = cloneSingleNode(context);
     GP_ASSERT(copy);
 
-    // Find our current last child
-    Node* lastChild = NULL;
+    // Add child nodes
     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);
         GP_ASSERT(childCopy);

+ 1 - 1
gameplay/src/Scene.cpp

@@ -254,7 +254,7 @@ void Scene::addNode(Node* node)
         node->getParent()->removeChild(node);
     }
 
-    // Link the new node into our list.
+    // Link the new node into the end of our list.
     if (_lastNode)
     {
         _lastNode->_nextSibling = node;

+ 319 - 283
gameplay/src/SceneLoader.cpp

@@ -13,6 +13,10 @@ namespace gameplay
 extern void calculateNamespacePath(const std::string& urlString, std::string& fileString, std::vector<std::string>& namespacePath);
 extern Properties* getPropertiesFromNamespacePath(Properties* properties, const std::vector<std::string>& namespacePath);
 
+SceneLoader::SceneLoader() : _scene(NULL)
+{
+}
+
 Scene* SceneLoader::load(const char* url)
 {
     SceneLoader loader;
@@ -55,12 +59,11 @@ Scene* SceneLoader::loadInternal(const char* url)
     loadReferencedFiles();
 
     // Load the main scene data from GPB and apply the global scene properties.
-    Scene* scene = NULL;
     if (!_gpbPath.empty())
     {
         // Load scene from bundle
-        scene = loadMainSceneData(sceneProperties);
-        if (!scene)
+        _scene = loadMainSceneData(sceneProperties);
+        if (!_scene)
         {
             GP_ERROR("Failed to load main scene from bundle.");
             SAFE_DELETE(properties);
@@ -70,7 +73,7 @@ Scene* SceneLoader::loadInternal(const char* url)
     else
     {
         // Create a new empty scene
-        scene = Scene::create(sceneProperties->getId());
+        _scene = Scene::create(sceneProperties->getId());
     }
 
     // First apply the node url properties. Following that,
@@ -78,8 +81,8 @@ Scene* SceneLoader::loadInternal(const char* url)
     // We apply physics properties after all other node properties
     // so that the transform (SRT) properties get applied before
     // processing physics collision objects.
-    applyNodeUrls(scene);
-    applyNodeProperties(scene, sceneProperties, 
+    applyNodeUrls();
+    applyNodeProperties(sceneProperties, 
         SceneNodeProperty::AUDIO | 
         SceneNodeProperty::MATERIAL | 
         SceneNodeProperty::PARTICLE |
@@ -89,9 +92,9 @@ Scene* SceneLoader::loadInternal(const char* url)
         SceneNodeProperty::ROTATE |
         SceneNodeProperty::SCALE |
         SceneNodeProperty::TRANSLATE);
-    applyNodeProperties(scene, sceneProperties, SceneNodeProperty::COLLISION_OBJECT);
+    applyNodeProperties(sceneProperties, SceneNodeProperty::COLLISION_OBJECT);
 
-    // Apply node tags
+    // Apply node tags (TODO : update for child nodes)
     for (size_t i = 0, sncount = _sceneNodes.size(); i < sncount; ++i)
     {
         SceneNode& sceneNode = _sceneNodes[i];
@@ -106,22 +109,22 @@ Scene* SceneLoader::loadInternal(const char* url)
     const char* activeCamera = sceneProperties->getString("activeCamera");
     if (activeCamera)
     {
-        Node* camera = scene->findNode(activeCamera);
+        Node* camera = _scene->findNode(activeCamera);
         if (camera && camera->getCamera())
-            scene->setActiveCamera(camera->getCamera());
+            _scene->setActiveCamera(camera->getCamera());
     }
 
     // Set ambient and light properties
     Vector3 vec3;
     if (sceneProperties->getVector3("ambientColor", &vec3))
-        scene->setAmbientColor(vec3.x, vec3.y, vec3.z);
+        _scene->setAmbientColor(vec3.x, vec3.y, vec3.z);
     if (sceneProperties->getVector3("lightColor", &vec3))
-        scene->setLightColor(vec3.x, vec3.y, vec3.z);
+        _scene->setLightColor(vec3.x, vec3.y, vec3.z);
     if (sceneProperties->getVector3("lightDirection", &vec3))
-        scene->setLightDirection(vec3);
+        _scene->setLightDirection(vec3);
 
     // Create animations for scene
-    createAnimations(scene);
+    createAnimations();
 
     // Find the physics properties object.
     Properties* physics = NULL;
@@ -138,7 +141,7 @@ Scene* SceneLoader::loadInternal(const char* url)
 
     // Load physics properties and constraints.
     if (physics)
-        loadPhysics(physics, scene);
+        loadPhysics(physics);
 
     // Clean up all loaded properties objects.
     std::map<std::string, Properties*>::iterator iter = _propertiesFromFile.begin();
@@ -150,7 +153,7 @@ Scene* SceneLoader::loadInternal(const char* url)
     // Clean up the .scene file's properties object.
     SAFE_DELETE(properties);
 
-    return scene;
+    return _scene;
 }
 
 void SceneLoader::addSceneAnimation(const char* animationID, const char* targetID, const char* url)
@@ -191,25 +194,35 @@ void SceneLoader::addSceneNodeProperty(SceneNode& sceneNode, SceneNodeProperty::
     sceneNode._properties.push_back(prop);
 }
 
-void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* sceneProperties, unsigned int typeFlags)
+void SceneLoader::applyNodeProperties(const Properties* sceneProperties, unsigned int typeFlags)
 {
-    for (size_t i = 0, sncount = _sceneNodes.size(); i < sncount; ++i)
+    for (size_t i = 0, count = _sceneNodes.size(); i < count; ++i)
     {
-        SceneNode& sceneNode = _sceneNodes[i];
+        applyNodeProperties(_sceneNodes[i], sceneProperties, typeFlags);
+    }
+}
 
-        for (size_t p = 0, pcount = sceneNode._properties.size(); p < pcount; ++p)
+void SceneLoader::applyNodeProperties(SceneNode& sceneNode, const Properties* sceneProperties, unsigned int typeFlags)
+{
+    // Apply properties for this node
+    for (size_t i = 0, pcount = sceneNode._properties.size(); i < pcount; ++i)
+    {
+        SceneNodeProperty& snp = sceneNode._properties[i];
+        if (typeFlags & snp._type)
         {
-            SceneNodeProperty& snp = sceneNode._properties[p];
-            if (typeFlags & snp._type)
-            {
-                for (size_t n = 0, ncount = sceneNode._nodes.size(); n < ncount; ++n)
-                    applyNodeProperty(sceneNode, sceneNode._nodes[n], sceneProperties, snp, scene);
-            }
+            for (size_t k = 0, ncount = sceneNode._nodes.size(); k < ncount; ++k)
+                applyNodeProperty(sceneNode, sceneNode._nodes[k], sceneProperties, snp);
         }
     }
+
+    // Apply properties to child nodes
+    for (size_t i = 0, ccount = sceneNode._children.size(); i < ccount; ++i)
+    {
+        applyNodeProperties(sceneNode._children[i], sceneProperties, typeFlags);
+    }
 }
 
-void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Properties* sceneProperties, const SceneNodeProperty& snp, const Scene* scene)
+void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Properties* sceneProperties, const SceneNodeProperty& snp)
 {
     if (snp._type == SceneNodeProperty::AUDIO ||
         snp._type == SceneNodeProperty::MATERIAL ||
@@ -303,8 +316,7 @@ void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Prop
 
                 if (name)
                 {
-                    GP_ASSERT(scene);
-                    Node* modelNode = scene->findNode(name);
+                    Node* modelNode = _scene->findNode(name);
                     if (!modelNode)
                     {
                         GP_ERROR("Node '%s' does not exist; attempting to use its model for collision object creation.", name);
@@ -383,158 +395,172 @@ void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Prop
     }
 }
 
-void SceneLoader::applyNodeUrls(Scene* scene)
+void SceneLoader::applyNodeUrls()
 {
-    GP_ASSERT(scene);
-
     // Apply all URL node properties so that when we go to apply
     // the other node properties, the node is in the scene.
-    for (size_t i = 0, ncount = _sceneNodes.size(); i < ncount; ++i)
+    for (size_t i = 0, count = _sceneNodes.size(); i < count; ++i)
     {
-        SceneNode& sceneNode = _sceneNodes[i];
+        applyNodeUrls(_sceneNodes[i], NULL);
+    }
+}
 
-        // Iterate backwards over the properties list so we can remove properties as we go
-        // without danger of indexing out of bounds.
-        bool hasURL = false;
-        for (int j = (int)sceneNode._properties.size() - 1; j >= 0; --j)
-        {
-            SceneNodeProperty& snp = sceneNode._properties[j];
-            if (snp._type != SceneNodeProperty::URL)
-                continue;
+void SceneLoader::applyNodeUrls(SceneNode& sceneNode, Node* parent)
+{
+    // Iterate backwards over the properties list so we can remove properties as we go
+    // without danger of indexing out of bounds.
+    bool hasURL = false;
+    for (int j = (int)sceneNode._properties.size() - 1; j >= 0; --j)
+    {
+        SceneNodeProperty& snp = sceneNode._properties[j];
+        if (snp._type != SceneNodeProperty::URL)
+            continue; // skip nodes without urls
+
+        hasURL = true;
 
-            hasURL = true;
+        std::string file;
+        std::string id;
+        splitURL(snp._url, &file, &id);
 
-            std::string file;
-            std::string id;
-            splitURL(snp._url, &file, &id);
+        if (file.empty())
+        {
+            // The node is from the main GPB and should just be renamed.
 
-            if (file.empty())
+            // TODO: Should we do all nodes with this case first to allow users to stitch in nodes with
+            // IDs equal to IDs that were in the original GPB file but were changed in the scene file?
+            if (sceneNode._exactMatch)
+            {
+                Node* node = parent ? parent->findNode(id.c_str()) : _scene->findNode(id.c_str());
+                if (node)
+                {
+                    node->setId(sceneNode._nodeID);
+                }
+                else
+                {
+                    GP_ERROR("Could not find node '%s' in main scene GPB file.", id.c_str());
+                }
+                sceneNode._nodes.push_back(node);
+            }
+            else
             {
-                // The node is from the main GPB and should just be renamed.
+                // Search for nodes using a partial match
+                std::vector<Node*> nodes;
+                unsigned int nodeCount = parent ? parent->findNodes(id.c_str(), nodes, true, false) : _scene->findNodes(id.c_str(), nodes, true, false);
+                if (nodeCount > 0)
+                {
+                    for (unsigned int k = 0; k < nodeCount; ++k)
+                    {
+                        // Construct a new node ID using _nodeID plus the remainder of the partial match.
+                        Node* node = nodes[k];
+                        std::string newID(sceneNode._nodeID);
+                        newID += (node->getId() + id.length());
+                        node->setId(newID.c_str());
+                        sceneNode._nodes.push_back(node);
+                    }
+                }
+                else
+                {
+                    GP_ERROR("Could not find any nodes matching '%s' in main scene GPB file.", id.c_str());
+                }
+            }
+        }
+        else
+        {
+            // An external file was referenced, so load the node(s) from file and then insert it into the scene with the new ID.
 
-                // TODO: Should we do all nodes with this case first to allow users to stitch in nodes with
-                // IDs equal to IDs that were in the original GPB file but were changed in the scene file?
+            // TODO: Revisit this to determine if we should cache Bundle objects for the duration of the scene
+            // load to prevent constantly creating/destroying the same externally referenced bundles each time
+            // a url with a file is encountered.
+            Bundle* tmpBundle = Bundle::create(file.c_str());
+            if (tmpBundle)
+            {
                 if (sceneNode._exactMatch)
                 {
-                    Node* node = scene->findNode(id.c_str());
+                    Node* node = tmpBundle->loadNode(id.c_str(), _scene);
                     if (node)
                     {
                         node->setId(sceneNode._nodeID);
+                        parent ? parent->addChild(node) : _scene->addNode(node);
+                        sceneNode._nodes.push_back(node);
+                        SAFE_RELEASE(node);
                     }
                     else
                     {
-                        GP_ERROR("Could not find node '%s' in main scene GPB file.", id.c_str());
+                        GP_ERROR("Could not load node '%s' from GPB file '%s'.", id.c_str(), file.c_str());
                     }
-                    sceneNode._nodes.push_back(node);
                 }
                 else
                 {
-                    // Search for nodes using a partial match
-                    std::vector<Node*> nodes;
-                    unsigned int nodeCount = scene->findNodes(id.c_str(), nodes, true, false);
-                    if (nodeCount > 0)
+                    // Search for nodes in the package using a partial match
+                    unsigned int objectCount = tmpBundle->getObjectCount();
+                    unsigned int matchCount = 0;
+                    for (unsigned int k = 0; k < objectCount; ++k)
                     {
-                        for (unsigned int k = 0; k < nodeCount; ++k)
+                        const char* objid = tmpBundle->getObjectId(k);
+                        if (strstr(objid, id.c_str()) == objid)
                         {
-                            // Construct a new node ID using _nodeID plus the remainder of the partial match.
-                            Node* node = nodes[k];
-                            std::string newID(sceneNode._nodeID);
-                            newID += (node->getId() + id.length());
-                            node->setId(newID.c_str());
-                            sceneNode._nodes.push_back(node);
+                            // This object ID matches (starts with).
+                            // Try to load this object as a Node.
+                            Node* node = tmpBundle->loadNode(objid);
+                            if (node)
+                            {
+                                // Construct a new node ID using _nodeID plus the remainder of the partial match.
+                                std::string newID(sceneNode._nodeID);
+                                newID += (node->getId() + id.length());
+                                node->setId(newID.c_str());
+                                parent ? parent->addChild(node) : _scene->addNode(node);
+                                sceneNode._nodes.push_back(node);
+                                SAFE_RELEASE(node);
+                                matchCount++;
+                            }
                         }
                     }
-                    else
+                    if (matchCount == 0)
                     {
-                        GP_ERROR("Could not find any nodes matching '%s' in main scene GPB file.", id.c_str());
+                        GP_ERROR("Could not find any nodes matching '%s' in GPB file '%s'.", id.c_str(), file.c_str());
                     }
                 }
+
+                SAFE_RELEASE(tmpBundle);
             }
             else
             {
-                // An external file was referenced, so load the node(s) from file and then insert it into the scene with the new ID.
-
-                // TODO: Revisit this to determine if we should cache Bundle objects for the duration of the scene
-                // load to prevent constantly creating/destroying the same externally referenced bundles each time
-                // a url with a file is encountered.
-                Bundle* tmpBundle = Bundle::create(file.c_str());
-                if (tmpBundle)
-                {
-                    if (sceneNode._exactMatch)
-                    {
-                        Node* node = tmpBundle->loadNode(id.c_str(), scene);
-                        if (node)
-                        {
-                            node->setId(sceneNode._nodeID);
-                            scene->addNode(node);
-                            sceneNode._nodes.push_back(node);
-                            SAFE_RELEASE(node);
-                        }
-                        else
-                        {
-                            GP_ERROR("Could not load node '%s' from GPB file '%s'.", id.c_str(), file.c_str());
-                        }
-                    }
-                    else
-                    {
-                        // Search for nodes in the package using a partial match
-                        unsigned int objectCount = tmpBundle->getObjectCount();
-                        unsigned int matchCount = 0;
-                        for (unsigned int k = 0; k < objectCount; ++k)
-                        {
-                            const char* objid = tmpBundle->getObjectId(k);
-                            if (strstr(objid, id.c_str()) == objid)
-                            {
-                                // This object ID matches (starts with).
-                                // Try to load this object as a Node.
-                                Node* node = tmpBundle->loadNode(objid);
-                                if (node)
-                                {
-                                    // Construct a new node ID using _nodeID plus the remainder of the partial match.
-                                    std::string newID(sceneNode._nodeID);
-                                    newID += (node->getId() + id.length());
-                                    node->setId(newID.c_str());
-                                    scene->addNode(node);
-                                    sceneNode._nodes.push_back(node);
-                                    SAFE_RELEASE(node);
-                                    matchCount++;
-                                }
-                            }
-                        }
-                        if (matchCount == 0)
-                        {
-                            GP_ERROR("Could not find any nodes matching '%s' in GPB file '%s'.", id.c_str(), file.c_str());
-                        }
-                    }
-
-                    SAFE_RELEASE(tmpBundle);
-                }
-                else
-                {
-                    GP_ERROR("Failed to load GPB file '%s' for node stitching.", file.c_str());
-                }
+                GP_ERROR("Failed to load GPB file '%s' for node stitching.", file.c_str());
             }
+        }
 
-            // Remove the 'url' node property since we are done applying it.
-            sceneNode._properties.erase(sceneNode._properties.begin() + j);
+        // Remove the 'url' node property since we are done applying it.
+        sceneNode._properties.erase(sceneNode._properties.begin() + j);
 
-            // Processed URL property, no need to inspect remaining properties
-            break;
+        // Processed URL property, no need to inspect remaining properties
+        break;
+    }
+
+    if (!hasURL)
+    {
+        // No explicit URL, find the node in the main scene with the existing ID
+        Node* node = parent ? parent->findNode(sceneNode._nodeID) : _scene->findNode(sceneNode._nodeID);
+        if (node)
+        {
+            sceneNode._nodes.push_back(node);
         }
+        else
+        {
+            // There is no node in the scene with this ID, so create a new empty node
+            node = Node::create(sceneNode._nodeID);
+            parent ? parent->addChild(node) : _scene->addNode(node);
+            node->release();
+            sceneNode._nodes.push_back(node);
+        }
+    }
 
-        if (!hasURL)
+    // Apply to child nodes
+    for (size_t i = 0, count = sceneNode._nodes.size(); i < count; ++i)
+    {
+        Node* parent = sceneNode._nodes[i];
+        for (size_t j = 0, childCount = sceneNode._children.size(); j < childCount; ++j)
         {
-            // No explicit URL, find the node in the main scene with the existing ID
-            Node* node = scene->findNode(sceneNode._nodeID);
-            if (node)
-            {
-                sceneNode._nodes.push_back(node);
-            }
-            else
-            {
-                // There is no node in the scene with this ID, so create a new empty node
-                sceneNode._nodes.push_back(scene->addNode(sceneNode._nodeID));
-            }
+            applyNodeUrls(sceneNode._children[j], parent);
         }
     }
 }
@@ -543,7 +569,6 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
 {
     // Go through the child namespaces of the scene.
     Properties* ns;
-    const char* name = NULL;
     while ((ns = sceneProperties->getNextNamespace()) != NULL)
     {
         if (strcmp(ns->getNamespace(), "node") == 0)
@@ -554,139 +579,7 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
                 continue;
             }
 
-            // Add a SceneNode to the end of the list.
-            _sceneNodes.resize(_sceneNodes.size() + 1);
-            SceneNode& sceneNode = _sceneNodes[_sceneNodes.size()-1];
-            sceneNode._nodeID = ns->getId();
-
-            // Parse the node's sub-namespaces.
-            Properties* subns;
-            std::string propertyUrl = _path + "#" + ns->getId() + "/";
-            while ((subns = ns->getNextNamespace()) != NULL)
-            {
-                if (strcmp(subns->getNamespace(), "audio") == 0)
-                {
-                    propertyUrl += "audio/" + std::string(subns->getId());
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::AUDIO, propertyUrl.c_str());
-                    _properties[propertyUrl] = subns;
-                }
-                else if (strcmp(subns->getNamespace(), "material") == 0)
-                {
-                    propertyUrl += "material/" + std::string(subns->getId());
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::MATERIAL, propertyUrl.c_str());
-                    _properties[propertyUrl] = subns;
-                }
-                else if (strcmp(subns->getNamespace(), "particle") == 0)
-                {
-                    propertyUrl += "particle/" + std::string(subns->getId());
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, propertyUrl.c_str());
-                    _properties[propertyUrl] = subns;
-                }
-                else if (strcmp(subns->getNamespace(), "terrain") == 0)
-                {
-                    propertyUrl += "terrain/" + std::string(subns->getId());
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::TERRAIN, propertyUrl.c_str());
-                    _properties[propertyUrl] = subns;
-                }
-                else if (strcmp(subns->getNamespace(), "light") == 0)
-                {
-                    propertyUrl += "light/" + std::string(subns->getId());
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::LIGHT, propertyUrl.c_str());
-                    _properties[propertyUrl] = subns;
-                }
-                else if (strcmp(subns->getNamespace(), "camera") == 0)
-                {
-                    propertyUrl += "camera/" + std::string(subns->getId());
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::CAMERA, propertyUrl.c_str());
-                    _properties[propertyUrl] = subns;
-                }
-                else if (strcmp(subns->getNamespace(), "collisionObject") == 0)
-                {
-                    propertyUrl += "collisionObject/" + std::string(subns->getId());
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::COLLISION_OBJECT, propertyUrl.c_str());
-                    _properties[propertyUrl] = subns;
-                }
-                else if (strcmp(subns->getNamespace(), "tags") == 0)
-                {
-                    while ((name = subns->getNextProperty()) != NULL)
-                    {
-                        sceneNode._tags[name] = subns->getString();
-                    }
-                }
-                else
-                {
-                    GP_ERROR("Unsupported child namespace '%s' of 'node' namespace.", subns->getNamespace());
-                }
-            }
-
-            // Parse the node's attributes.
-            while ((name = ns->getNextProperty()) != NULL)
-            {
-                if (strcmp(name, "url") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::URL, ns->getString());
-                }
-                else if (strcmp(name, "audio") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::AUDIO, ns->getString());
-                }
-                else if (strncmp(name, "material", 8) == 0)
-                {
-                    int materialIndex = -1;
-                    name = strchr(name, '[');
-                    if (name && strlen(name) >= 3)
-                    {
-                        std::string indexString(name);
-                        indexString = indexString.substr(1, indexString.size()-2);
-                        materialIndex = (unsigned int)atoi(indexString.c_str());
-                    }
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::MATERIAL, ns->getString(), materialIndex);
-                }
-                else if (strcmp(name, "particle") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, ns->getString());
-                }
-                else if (strcmp(name, "terrain") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::TERRAIN, ns->getString());
-                }
-                else if (strcmp(name, "light") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::LIGHT, ns->getString());
-                }
-                else if (strcmp(name, "camera") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::CAMERA, ns->getString());
-                }
-                else if (strcmp(name, "collisionObject") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::COLLISION_OBJECT, ns->getString());
-                }
-                else if (strcmp(name, "rigidBodyModel") == 0)
-                {
-                    // Ignore this for now. We process this when we do rigid body creation.
-                }
-                else if (strcmp(name, "collisionMesh") == 0)
-                {
-                    // Ignore this for now (new alias for rigidBodyModel). We process this when we do rigid body creation.
-                }
-                else if (strcmp(name, "translate") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::TRANSLATE);
-                }
-                else if (strcmp(name, "rotate") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::ROTATE);
-                }
-                else if (strcmp(name, "scale") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::SCALE);
-                }
-                else
-                {
-                    GP_ERROR("Unsupported node property: %s = %s", name, ns->getString());
-                }
-            }
+            parseNode(ns, NULL, _path + "#" + ns->getId() + "/");
         }
         else if (strcmp(ns->getNamespace(), "animations") == 0)
         {
@@ -737,14 +630,158 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
     }
 }
 
-void SceneLoader::createAnimations(const Scene* scene)
+void SceneLoader::parseNode(Properties* ns, SceneNode* parent, const std::string& path)
+{
+    std::string propertyUrl;
+    const char* name = NULL;
+
+    // Add a SceneNode to the end of the list.
+    std::vector<SceneNode>& list = parent ? parent->_children : _sceneNodes;
+    list.push_back(SceneNode());
+    SceneNode& sceneNode = list[list.size()-1];
+    sceneNode._nodeID = ns->getId();
+
+    // Parse the node's sub-namespaces.
+    Properties* subns;
+    while ((subns = ns->getNextNamespace()) != NULL)
+    {
+        if (strcmp(subns->getNamespace(), "node") == 0)
+        {
+            parseNode(subns, &sceneNode, path + subns->getId() + "/");
+        }
+        else if (strcmp(subns->getNamespace(), "audio") == 0)
+        {
+            propertyUrl = path + "audio/" + std::string(subns->getId());
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::AUDIO, propertyUrl.c_str());
+            _properties[propertyUrl] = subns;
+        }
+        else if (strcmp(subns->getNamespace(), "material") == 0)
+        {
+            propertyUrl = path + "material/" + std::string(subns->getId());
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::MATERIAL, propertyUrl.c_str());
+            _properties[propertyUrl] = subns;
+        }
+        else if (strcmp(subns->getNamespace(), "particle") == 0)
+        {
+            propertyUrl = path + "particle/" + std::string(subns->getId());
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, propertyUrl.c_str());
+            _properties[propertyUrl] = subns;
+        }
+        else if (strcmp(subns->getNamespace(), "terrain") == 0)
+        {
+            propertyUrl = path + "terrain/" + std::string(subns->getId());
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::TERRAIN, propertyUrl.c_str());
+            _properties[propertyUrl] = subns;
+        }
+        else if (strcmp(subns->getNamespace(), "light") == 0)
+        {
+            propertyUrl = path + "light/" + std::string(subns->getId());
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::LIGHT, propertyUrl.c_str());
+            _properties[propertyUrl] = subns;
+        }
+        else if (strcmp(subns->getNamespace(), "camera") == 0)
+        {
+            propertyUrl = path + "camera/" + std::string(subns->getId());
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::CAMERA, propertyUrl.c_str());
+            _properties[propertyUrl] = subns;
+        }
+        else if (strcmp(subns->getNamespace(), "collisionObject") == 0)
+        {
+            propertyUrl = path + "collisionObject/" + std::string(subns->getId());
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::COLLISION_OBJECT, propertyUrl.c_str());
+            _properties[propertyUrl] = subns;
+        }
+        else if (strcmp(subns->getNamespace(), "tags") == 0)
+        {
+            while ((name = subns->getNextProperty()) != NULL)
+            {
+                sceneNode._tags[name] = subns->getString();
+            }
+        }
+        else
+        {
+            GP_ERROR("Unsupported child namespace '%s' of 'node' namespace.", subns->getNamespace());
+        }
+    }
+
+    // Parse the node's attributes.
+    while ((name = ns->getNextProperty()) != NULL)
+    {
+        if (strcmp(name, "url") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::URL, ns->getString());
+        }
+        else if (strcmp(name, "audio") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::AUDIO, ns->getString());
+        }
+        else if (strncmp(name, "material", 8) == 0)
+        {
+            int materialIndex = -1;
+            name = strchr(name, '[');
+            if (name && strlen(name) >= 3)
+            {
+                std::string indexString(name);
+                indexString = indexString.substr(1, indexString.size()-2);
+                materialIndex = (unsigned int)atoi(indexString.c_str());
+            }
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::MATERIAL, ns->getString(), materialIndex);
+        }
+        else if (strcmp(name, "particle") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, ns->getString());
+        }
+        else if (strcmp(name, "terrain") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::TERRAIN, ns->getString());
+        }
+        else if (strcmp(name, "light") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::LIGHT, ns->getString());
+        }
+        else if (strcmp(name, "camera") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::CAMERA, ns->getString());
+        }
+        else if (strcmp(name, "collisionObject") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::COLLISION_OBJECT, ns->getString());
+        }
+        else if (strcmp(name, "rigidBodyModel") == 0)
+        {
+            // Ignore this for now. We process this when we do rigid body creation.
+        }
+        else if (strcmp(name, "collisionMesh") == 0)
+        {
+            // Ignore this for now (new alias for rigidBodyModel). We process this when we do rigid body creation.
+        }
+        else if (strcmp(name, "translate") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::TRANSLATE);
+        }
+        else if (strcmp(name, "rotate") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::ROTATE);
+        }
+        else if (strcmp(name, "scale") == 0)
+        {
+            addSceneNodeProperty(sceneNode, SceneNodeProperty::SCALE);
+        }
+        else
+        {
+            GP_ERROR("Unsupported node property: %s = %s", name, ns->getString());
+        }
+    }
+}
+
+void SceneLoader::createAnimations()
 {
     // Create the scene animations.
     for (size_t i = 0, count = _animations.size(); i < count; i++)
     {
         // If the target node doesn't exist in the scene, then we
         // can't do anything so we skip to the next animation.
-        Node* node = scene->findNode(_animations[i]._targetID);
+        Node* node = _scene->findNode(_animations[i]._targetID);
         if (!node)
         {
             GP_ERROR("Attempting to create an animation targeting node '%s', which does not exist in the scene.", _animations[i]._targetID);
@@ -892,10 +929,9 @@ Scene* SceneLoader::loadMainSceneData(const Properties* sceneProperties)
     return scene;
 }
 
-void SceneLoader::loadPhysics(Properties* physics, Scene* scene)
+void SceneLoader::loadPhysics(Properties* physics)
 {
     GP_ASSERT(physics);
-    GP_ASSERT(scene);
     GP_ASSERT(Game::getInstance()->getPhysicsController());
 
     // Go through the supported global physics properties and apply them.
@@ -920,7 +956,7 @@ void SceneLoader::loadPhysics(Properties* physics, Scene* scene)
                 GP_ERROR("Missing property 'rigidBodyA' for constraint '%s'.", constraint->getId());
                 continue;
             }
-            Node* rbANode = scene->findNode(name);
+            Node* rbANode = _scene->findNode(name);
             if (!rbANode)
             {
                 GP_ERROR("Node '%s' to be used as 'rigidBodyA' for constraint '%s' cannot be found.", name, constraint->getId());
@@ -941,7 +977,7 @@ void SceneLoader::loadPhysics(Properties* physics, Scene* scene)
             PhysicsRigidBody* rbB = NULL;
             if (name)
             {
-                Node* rbBNode = scene->findNode(name);
+                Node* rbBNode = _scene->findNode(name);
                 if (!rbBNode)
                 {
                     GP_ERROR("Node '%s' to be used as 'rigidBodyB' for constraint '%s' cannot be found.", name, constraint->getId());

+ 23 - 12
gameplay/src/SceneLoader.h

@@ -72,28 +72,38 @@ private:
 
         const char* _nodeID;
         bool _exactMatch;
-        std::vector<Node*> _nodes;
+        Properties* _namespace;
+        std::vector<Node*> _nodes; // list of nodes sharing properties defined in this SceneNode
+        std::vector<SceneNode> _children; // list of unique child nodes
         std::vector<SceneNodeProperty> _properties;
         std::map<std::string, std::string> _tags;
     };
 
+    SceneLoader();
+
     Scene* loadInternal(const char* url);
 
     void addSceneAnimation(const char* animationID, const char* targetID, const char* url);
 
     void addSceneNodeProperty(SceneNode& sceneNode, SceneNodeProperty::Type type, const char* url = NULL, int index = 0);
 
-    void applyNodeProperties(const Scene* scene, const Properties* sceneProperties, unsigned int typeFlags);
+    void applyNodeProperties(const Properties* sceneProperties, unsigned int typeFlags);
+
+    void applyNodeProperties(SceneNode& sceneNode, const Properties* sceneProperties, unsigned int typeFlags);
 
-    void applyNodeProperty(SceneNode& sceneNode, Node* node, const Properties* sceneProperties, const SceneNodeProperty& snp, const Scene* scene);
+    void applyNodeProperty(SceneNode& sceneNode, Node* node, const Properties* sceneProperties, const SceneNodeProperty& snp);
 
-    void applyNodeUrls(Scene* scene);
+    void applyNodeUrls();
+
+    void applyNodeUrls(SceneNode& sceneNode, Node* parent);
 
     void buildReferenceTables(Properties* sceneProperties);
 
+    void parseNode(Properties* ns, SceneNode* parent, const std::string& path);
+
     void calculateNodesWithMeshRigidBodies(const Properties* sceneProperties);
 
-    void createAnimations(const Scene* scene);
+    void createAnimations();
 
     PhysicsConstraint* loadGenericConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
 
@@ -101,7 +111,7 @@ private:
 
     Scene* loadMainSceneData(const Properties* sceneProperties);
 
-    void loadPhysics(Properties* physics, Scene* scene);
+    void loadPhysics(Properties* physics);
 
     void loadReferencedFiles();
 
@@ -109,12 +119,13 @@ private:
 
     PhysicsConstraint* loadSpringConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
 
-    std::map<std::string, Properties*> _propertiesFromFile;      // Holds the properties object for a given file.
-    std::map<std::string, Properties*> _properties;              // Holds the properties object for a given URL.
-    std::vector<SceneAnimation> _animations;                     // Holds the animations declared in the .scene file.
-    std::vector<SceneNode> _sceneNodes;                          // Holds all the nodes+properties declared in the .scene file.
-    std::string _gpbPath;                                        // The path of the main GPB for the scene being loaded.
-    std::string _path;                                           // The path of the scene file being loaded.
+    std::map<std::string, Properties*> _propertiesFromFile; // Holds the properties object for a given file.
+    std::map<std::string, Properties*> _properties;         // Holds the properties object for a given URL.
+    std::vector<SceneAnimation> _animations;                // Holds the animations declared in the .scene file.
+    std::vector<SceneNode> _sceneNodes;                     // Holds all the nodes+properties declared in the .scene file.
+    std::string _gpbPath;                                   // The path of the main GPB for the scene being loaded.
+    std::string _path;                                      // The path of the scene file being loaded.
+    Scene* _scene;                                          // The scene being loaded
 };
 
 /**

+ 3 - 0
gameplay/src/TextBox.cpp

@@ -4,6 +4,9 @@
 namespace gameplay
 {
 
+/**
+ * @script{ignore}
+ */
 bool space(char c) {
     return isspace(c);
 }

+ 73 - 0
gameplay/src/lua/lua_Font.cpp

@@ -20,6 +20,7 @@ void luaRegister_Font()
         {"createText", lua_Font_createText},
         {"drawText", lua_Font_drawText},
         {"finish", lua_Font_finish},
+        {"getCharacterSpacing", lua_Font_getCharacterSpacing},
         {"getIndexAtLocation", lua_Font_getIndexAtLocation},
         {"getLocationAtIndex", lua_Font_getLocationAtIndex},
         {"getRefCount", lua_Font_getRefCount},
@@ -27,6 +28,7 @@ void luaRegister_Font()
         {"getSpriteBatch", lua_Font_getSpriteBatch},
         {"measureText", lua_Font_measureText},
         {"release", lua_Font_release},
+        {"setCharacterSpacing", lua_Font_setCharacterSpacing},
         {"start", lua_Font_start},
         {NULL, NULL}
     };
@@ -1086,6 +1088,41 @@ int lua_Font_finish(lua_State* state)
     return 0;
 }
 
+int lua_Font_getCharacterSpacing(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Font* instance = getInstance(state);
+                float result = instance->getCharacterSpacing();
+
+                // Push the return value onto the stack.
+                lua_pushnumber(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Font_getCharacterSpacing - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Font_getIndexAtLocation(lua_State* state)
 {
     // Get the number of parameters.
@@ -1945,6 +1982,42 @@ int lua_Font_release(lua_State* state)
     return 0;
 }
 
+int lua_Font_setCharacterSpacing(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 2:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                lua_type(state, 2) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                float param1 = (float)luaL_checknumber(state, 2);
+
+                Font* instance = getInstance(state);
+                instance->setCharacterSpacing(param1);
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_Font_setCharacterSpacing - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 2).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Font_start(lua_State* state)
 {
     // Get the number of parameters.

+ 2 - 0
gameplay/src/lua/lua_Font.h

@@ -10,6 +10,7 @@ int lua_Font_addRef(lua_State* state);
 int lua_Font_createText(lua_State* state);
 int lua_Font_drawText(lua_State* state);
 int lua_Font_finish(lua_State* state);
+int lua_Font_getCharacterSpacing(lua_State* state);
 int lua_Font_getIndexAtLocation(lua_State* state);
 int lua_Font_getLocationAtIndex(lua_State* state);
 int lua_Font_getRefCount(lua_State* state);
@@ -17,6 +18,7 @@ int lua_Font_getSize(lua_State* state);
 int lua_Font_getSpriteBatch(lua_State* state);
 int lua_Font_measureText(lua_State* state);
 int lua_Font_release(lua_State* state);
+int lua_Font_setCharacterSpacing(lua_State* state);
 int lua_Font_start(lua_State* state);
 int lua_Font_static_create(lua_State* state);
 int lua_Font_static_getJustify(lua_State* state);

+ 107 - 1
gameplay/src/lua/lua_Joint.cpp

@@ -5124,9 +5124,115 @@ int lua_Joint_setCollisionObject(lua_State* state)
             lua_error(state);
             break;
         }
+        case 5:
+        {
+            do
+            {
+                if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                    (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL) &&
+                    (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL) &&
+                    (lua_type(state, 4) == LUA_TUSERDATA || lua_type(state, 4) == LUA_TTABLE || lua_type(state, 4) == LUA_TNIL) &&
+                    lua_type(state, 5) == LUA_TNUMBER)
+                {
+                    // Get parameter 1 off the stack.
+                    PhysicsCollisionObject::Type param1 = (PhysicsCollisionObject::Type)lua_enumFromString_PhysicsCollisionObjectType(luaL_checkstring(state, 2));
+
+                    // Get parameter 2 off the stack.
+                    bool param2Valid;
+                    gameplay::ScriptUtil::LuaArray<PhysicsCollisionShape::Definition> param2 = gameplay::ScriptUtil::getObjectPointer<PhysicsCollisionShape::Definition>(3, "PhysicsCollisionShapeDefinition", true, &param2Valid);
+                    if (!param2Valid)
+                        break;
+
+                    // Get parameter 3 off the stack.
+                    bool param3Valid;
+                    gameplay::ScriptUtil::LuaArray<PhysicsRigidBody::Parameters> param3 = gameplay::ScriptUtil::getObjectPointer<PhysicsRigidBody::Parameters>(4, "PhysicsRigidBodyParameters", false, &param3Valid);
+                    if (!param3Valid)
+                        break;
+
+                    // Get parameter 4 off the stack.
+                    int param4 = (int)luaL_checkint(state, 5);
+
+                    Joint* instance = getInstance(state);
+                    void* returnPtr = (void*)instance->setCollisionObject(param1, *param2, param3, param4);
+                    if (returnPtr)
+                    {
+                        gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                        object->instance = returnPtr;
+                        object->owns = false;
+                        luaL_getmetatable(state, "PhysicsCollisionObject");
+                        lua_setmetatable(state, -2);
+                    }
+                    else
+                    {
+                        lua_pushnil(state);
+                    }
+
+                    return 1;
+                }
+            } while (0);
+
+            lua_pushstring(state, "lua_Joint_setCollisionObject - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        case 6:
+        {
+            do
+            {
+                if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                    (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL) &&
+                    (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL) &&
+                    (lua_type(state, 4) == LUA_TUSERDATA || lua_type(state, 4) == LUA_TTABLE || lua_type(state, 4) == LUA_TNIL) &&
+                    lua_type(state, 5) == LUA_TNUMBER &&
+                    lua_type(state, 6) == LUA_TNUMBER)
+                {
+                    // Get parameter 1 off the stack.
+                    PhysicsCollisionObject::Type param1 = (PhysicsCollisionObject::Type)lua_enumFromString_PhysicsCollisionObjectType(luaL_checkstring(state, 2));
+
+                    // Get parameter 2 off the stack.
+                    bool param2Valid;
+                    gameplay::ScriptUtil::LuaArray<PhysicsCollisionShape::Definition> param2 = gameplay::ScriptUtil::getObjectPointer<PhysicsCollisionShape::Definition>(3, "PhysicsCollisionShapeDefinition", true, &param2Valid);
+                    if (!param2Valid)
+                        break;
+
+                    // Get parameter 3 off the stack.
+                    bool param3Valid;
+                    gameplay::ScriptUtil::LuaArray<PhysicsRigidBody::Parameters> param3 = gameplay::ScriptUtil::getObjectPointer<PhysicsRigidBody::Parameters>(4, "PhysicsRigidBodyParameters", false, &param3Valid);
+                    if (!param3Valid)
+                        break;
+
+                    // Get parameter 4 off the stack.
+                    int param4 = (int)luaL_checkint(state, 5);
+
+                    // Get parameter 5 off the stack.
+                    int param5 = (int)luaL_checkint(state, 6);
+
+                    Joint* instance = getInstance(state);
+                    void* returnPtr = (void*)instance->setCollisionObject(param1, *param2, param3, param4, param5);
+                    if (returnPtr)
+                    {
+                        gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                        object->instance = returnPtr;
+                        object->owns = false;
+                        luaL_getmetatable(state, "PhysicsCollisionObject");
+                        lua_setmetatable(state, -2);
+                    }
+                    else
+                    {
+                        lua_pushnil(state);
+                    }
+
+                    return 1;
+                }
+            } while (0);
+
+            lua_pushstring(state, "lua_Joint_setCollisionObject - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
         default:
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 2, 3 or 4).");
+            lua_pushstring(state, "Invalid number of parameters (expected 2, 3, 4, 5 or 6).");
             lua_error(state);
             break;
         }

+ 107 - 1
gameplay/src/lua/lua_Node.cpp

@@ -5079,9 +5079,115 @@ int lua_Node_setCollisionObject(lua_State* state)
             lua_error(state);
             break;
         }
+        case 5:
+        {
+            do
+            {
+                if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                    (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL) &&
+                    (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL) &&
+                    (lua_type(state, 4) == LUA_TUSERDATA || lua_type(state, 4) == LUA_TTABLE || lua_type(state, 4) == LUA_TNIL) &&
+                    lua_type(state, 5) == LUA_TNUMBER)
+                {
+                    // Get parameter 1 off the stack.
+                    PhysicsCollisionObject::Type param1 = (PhysicsCollisionObject::Type)lua_enumFromString_PhysicsCollisionObjectType(luaL_checkstring(state, 2));
+
+                    // Get parameter 2 off the stack.
+                    bool param2Valid;
+                    gameplay::ScriptUtil::LuaArray<PhysicsCollisionShape::Definition> param2 = gameplay::ScriptUtil::getObjectPointer<PhysicsCollisionShape::Definition>(3, "PhysicsCollisionShapeDefinition", true, &param2Valid);
+                    if (!param2Valid)
+                        break;
+
+                    // Get parameter 3 off the stack.
+                    bool param3Valid;
+                    gameplay::ScriptUtil::LuaArray<PhysicsRigidBody::Parameters> param3 = gameplay::ScriptUtil::getObjectPointer<PhysicsRigidBody::Parameters>(4, "PhysicsRigidBodyParameters", false, &param3Valid);
+                    if (!param3Valid)
+                        break;
+
+                    // Get parameter 4 off the stack.
+                    int param4 = (int)luaL_checkint(state, 5);
+
+                    Node* instance = getInstance(state);
+                    void* returnPtr = (void*)instance->setCollisionObject(param1, *param2, param3, param4);
+                    if (returnPtr)
+                    {
+                        gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                        object->instance = returnPtr;
+                        object->owns = false;
+                        luaL_getmetatable(state, "PhysicsCollisionObject");
+                        lua_setmetatable(state, -2);
+                    }
+                    else
+                    {
+                        lua_pushnil(state);
+                    }
+
+                    return 1;
+                }
+            } while (0);
+
+            lua_pushstring(state, "lua_Node_setCollisionObject - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        case 6:
+        {
+            do
+            {
+                if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                    (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL) &&
+                    (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL) &&
+                    (lua_type(state, 4) == LUA_TUSERDATA || lua_type(state, 4) == LUA_TTABLE || lua_type(state, 4) == LUA_TNIL) &&
+                    lua_type(state, 5) == LUA_TNUMBER &&
+                    lua_type(state, 6) == LUA_TNUMBER)
+                {
+                    // Get parameter 1 off the stack.
+                    PhysicsCollisionObject::Type param1 = (PhysicsCollisionObject::Type)lua_enumFromString_PhysicsCollisionObjectType(luaL_checkstring(state, 2));
+
+                    // Get parameter 2 off the stack.
+                    bool param2Valid;
+                    gameplay::ScriptUtil::LuaArray<PhysicsCollisionShape::Definition> param2 = gameplay::ScriptUtil::getObjectPointer<PhysicsCollisionShape::Definition>(3, "PhysicsCollisionShapeDefinition", true, &param2Valid);
+                    if (!param2Valid)
+                        break;
+
+                    // Get parameter 3 off the stack.
+                    bool param3Valid;
+                    gameplay::ScriptUtil::LuaArray<PhysicsRigidBody::Parameters> param3 = gameplay::ScriptUtil::getObjectPointer<PhysicsRigidBody::Parameters>(4, "PhysicsRigidBodyParameters", false, &param3Valid);
+                    if (!param3Valid)
+                        break;
+
+                    // Get parameter 4 off the stack.
+                    int param4 = (int)luaL_checkint(state, 5);
+
+                    // Get parameter 5 off the stack.
+                    int param5 = (int)luaL_checkint(state, 6);
+
+                    Node* instance = getInstance(state);
+                    void* returnPtr = (void*)instance->setCollisionObject(param1, *param2, param3, param4, param5);
+                    if (returnPtr)
+                    {
+                        gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                        object->instance = returnPtr;
+                        object->owns = false;
+                        luaL_getmetatable(state, "PhysicsCollisionObject");
+                        lua_setmetatable(state, -2);
+                    }
+                    else
+                    {
+                        lua_pushnil(state);
+                    }
+
+                    return 1;
+                }
+            } while (0);
+
+            lua_pushstring(state, "lua_Node_setCollisionObject - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
         default:
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 2, 3 or 4).");
+            lua_pushstring(state, "Invalid number of parameters (expected 2, 3, 4, 5 or 6).");
             lua_error(state);
             break;
         }