Bläddra i källkod

Merge pull request #381 from blackberry-gaming/next-cculy

Next cculy
Sean Paul Taylor 13 år sedan
förälder
incheckning
bb6ac6db64

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

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

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

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

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

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

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

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

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

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

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

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

+ 4 - 4
gameplay/src/Animation.cpp

@@ -105,14 +105,14 @@ unsigned long Animation::getDuration() const
     return _duration;
     return _duration;
 }
 }
 
 
-void Animation::createClips(const char* animationFile)
+void Animation::createClips(const char* url)
 {
 {
-    assert(animationFile);
+    assert(url);
 
 
-    Properties* properties = Properties::create(animationFile);
+    Properties* properties = Properties::create(url);
     assert(properties);
     assert(properties);
 
 
-    Properties* pAnimation = properties->getNextNamespace();
+    Properties* pAnimation = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
     assert(pAnimation);
     assert(pAnimation);
     
     
     int frameCount = pAnimation->getInt("frameCount");
     int frameCount = pAnimation->getInt("frameCount");

+ 6 - 2
gameplay/src/Animation.h

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

+ 4 - 4
gameplay/src/AnimationTarget.cpp

@@ -47,14 +47,14 @@ Animation* AnimationTarget::createAnimation(const char* id, int propertyId, unsi
     return animation;
     return animation;
 }
 }
 
 
-Animation* AnimationTarget::createAnimation(const char* id, const char* animationFile)
+Animation* AnimationTarget::createAnimation(const char* id, const char* url)
 {
 {
-    assert(animationFile);
+    assert(url);
     
     
-    Properties* p = Properties::create(animationFile);
+    Properties* p = Properties::create(url);
     assert(p);
     assert(p);
 
 
-    Animation* animation = createAnimation(id, p->getNextNamespace());
+    Animation* animation = createAnimation(id, (strlen(p->getNamespace()) > 0) ? p : p->getNextNamespace());
 
 
     SAFE_DELETE(p);
     SAFE_DELETE(p);
 
 

+ 5 - 3
gameplay/src/AnimationTarget.h

@@ -55,14 +55,16 @@ public:
     Animation* createAnimation(const char* id, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, float* keyInValue, float* keyOutValue, Curve::InterpolationType type);
     Animation* createAnimation(const char* id, int propertyId, unsigned int keyCount, unsigned long* keyTimes, float* keyValues, float* keyInValue, float* keyOutValue, Curve::InterpolationType type);
 
 
     /**
     /**
-     * Creates an animation on this target using the data from the given properties object. 
+     * Creates an animation on this target using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
      * 
      * 
      * @param id The ID of the animation.
      * @param id The ID of the animation.
-     * @param animationFile The animation file defining the animation data.
+     * @param url The URL pointing to the Properties object defining the animation data.
      *
      *
      * @return The newly created animation.
      * @return The newly created animation.
      */
      */
-    Animation* createAnimation(const char* id, const char* animationFile);
+    Animation* createAnimation(const char* id, const char* url);
 
 
     /**
     /**
      * Creates an animation on this target using the data from the given properties object. 
      * Creates an animation on this target using the data from the given properties object. 

+ 7 - 7
gameplay/src/AudioSource.cpp

@@ -104,28 +104,28 @@ AudioSource::~AudioSource()
     SAFE_RELEASE(_buffer);
     SAFE_RELEASE(_buffer);
 }
 }
 
 
-AudioSource* AudioSource::create(const char* path)
+AudioSource* AudioSource::create(const char* url)
 {
 {
-    assert(path);
+    assert(url);
 
 
     // Load from a .audio file.
     // Load from a .audio file.
-    std::string pathStr = path;
+    std::string pathStr = url;
     if (pathStr.find(".audio") != pathStr.npos)
     if (pathStr.find(".audio") != pathStr.npos)
     {
     {
-        Properties* properties = Properties::create(path);
+        Properties* properties = Properties::create(url);
         assert(properties);
         assert(properties);
         if (properties == NULL)
         if (properties == NULL)
         {
         {
             return NULL;
             return NULL;
         }
         }
 
 
-        AudioSource* audioSource = create(properties->getNextNamespace());
+        AudioSource* audioSource = create((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
         SAFE_DELETE(properties);
         SAFE_DELETE(properties);
         return audioSource;
         return audioSource;
     }
     }
 
 
-    // Create an audio buffer from this path.
-    AudioBuffer* buffer = AudioBuffer::create(path);
+    // Create an audio buffer from this URL.
+    AudioBuffer* buffer = AudioBuffer::create(url);
     if (buffer == NULL)
     if (buffer == NULL)
         return NULL;
         return NULL;
 
 

+ 5 - 4
gameplay/src/AudioSource.h

@@ -34,13 +34,14 @@ public:
     };
     };
 
 
     /**
     /**
-     * Create an audio source. This is used to instantiate an Audio Source. Currently only wav, au, raw and .audio files are supported.
-     *
-     * @param path The relative location on disk of the sound file or .audio file.
+     * Create an audio source. This is used to instantiate an Audio Source. Currently only wav, au, and raw files are supported.
+     * Alternately, a URL specifying a Properties object that defines an audio source can be used (where the URL is of the format
+     * "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>" and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional).
      * 
      * 
+     * @param url The relative location on disk of the sound file or a URL specifying a Properties object defining an audio source.
      * @return The newly created audio source, or NULL if an audio source cannot be created.
      * @return The newly created audio source, or NULL if an audio source cannot be created.
      */
      */
-    static AudioSource* create(const char* path);
+    static AudioSource* create(const char* url);
 
 
     /**
     /**
      * Create an audio source from the given properties object.
      * Create an audio source from the given properties object.

+ 250 - 17
gameplay/src/Bundle.cpp

@@ -6,7 +6,7 @@
 #include "Joint.h"
 #include "Joint.h"
 
 
 #define BUNDLE_VERSION_MAJOR            1
 #define BUNDLE_VERSION_MAJOR            1
-#define BUNDLE_VERSION_MINOR            1
+#define BUNDLE_VERSION_MINOR            2
 
 
 #define BUNDLE_TYPE_SCENE               1
 #define BUNDLE_TYPE_SCENE               1
 #define BUNDLE_TYPE_NODE                2
 #define BUNDLE_TYPE_NODE                2
@@ -32,7 +32,7 @@ namespace gameplay
 static std::vector<Bundle*> __bundleCache;
 static std::vector<Bundle*> __bundleCache;
 
 
 Bundle::Bundle(const char* path) :
 Bundle::Bundle(const char* path) :
-    _path(path), _referenceCount(0), _references(NULL), _file(NULL)
+    _path(path), _referenceCount(0), _references(NULL), _file(NULL), _trackedNodes(NULL)
 {
 {
 }
 }
 
 
@@ -411,18 +411,106 @@ Scene* Bundle::loadScene(const char* id)
 }
 }
 
 
 Node* Bundle::loadNode(const char* id)
 Node* Bundle::loadNode(const char* id)
+{
+    return loadNode(id, NULL);
+}
+
+Node* Bundle::loadNode(const char* id, Scene* sceneContext)
 {
 {
     assert(id);
     assert(id);
 
 
     clearLoadSession();
     clearLoadSession();
 
 
-    Node* node = loadNode(id, NULL, NULL);
-   
+    // Load the node and any referenced joints with node tracking enabled.
+    _trackedNodes = new std::map<std::string, Node*>();
+    Node* node = loadNode(id, sceneContext, NULL);
     if (node)
     if (node)
+        resolveJointReferences(sceneContext, node);
+
+    // Load all animations targeting any nodes or mesh skins under this node's hierarchy.
+    for (unsigned int i = 0; i < _referenceCount; i++)
     {
     {
-        resolveJointReferences(NULL, node);
+        Reference* ref = &_references[i];
+        if (ref->type == BUNDLE_TYPE_ANIMATIONS)
+        {
+            if (fseek(_file, ref->offset, SEEK_SET) != 0)
+            {
+                LOG_ERROR_VARG("Failed to seek to object '%s' in bundle '%s'.", ref->id.c_str(), _path.c_str());
+                SAFE_DELETE(_trackedNodes);
+                return NULL;
+            }
+            
+            // Read the number of animations in this object.
+            unsigned int animationCount;
+            if (!read(&animationCount))
+            {
+                LOG_ERROR_VARG("Failed to read %s for %s: %s", "animationCount", "Animations");
+                SAFE_DELETE(_trackedNodes);
+                return NULL;
+            }
+
+            for (unsigned int j = 0; j < animationCount; j++)
+            {
+                const std::string id = readString(_file);
+
+                // Read the number of animation channels in this animation.
+                unsigned int animationChannelCount;
+                if (!read(&animationChannelCount))
+                {
+                    LOG_ERROR_VARG("Failed to read %s for %s: %s", "animationChannelCount", "animation", id.c_str());
+                    SAFE_DELETE(_trackedNodes);
+                    return NULL;
+                }
+
+                Animation* animation = NULL;
+                for (unsigned int k = 0; k < animationChannelCount; k++)
+                {
+                    // read targetId
+                    std::string targetId = readString(_file);
+                    if (targetId.empty())
+                    {
+                        LOG_ERROR_VARG("Failed to read %s for %s: %s", "targetId", "animation", id.c_str());
+                        SAFE_DELETE(_trackedNodes);
+                        return NULL;
+                    }
+
+                    // If the target is one of the loaded nodes/joints, then load the animation.
+                    std::map<std::string, Node*>::iterator iter = _trackedNodes->find(targetId);
+                    if (iter != _trackedNodes->end())
+                    {
+                        // Read target attribute
+                        unsigned int targetAttribute;
+                        if (!read(&targetAttribute))
+                        {
+                            LOG_ERROR_VARG("Failed to read %s for %s: %s", "targetAttribute", "animation", id.c_str());
+                            SAFE_DELETE(_trackedNodes);
+                            return NULL;
+                        }
+
+                        AnimationTarget* target = iter->second;
+                        if (!target)
+                        {
+                            LOG_ERROR_VARG("Failed to read %s for %s: %s", "animation target", targetId.c_str(), id.c_str());
+                            SAFE_DELETE(_trackedNodes);
+                            return NULL;
+                        }
+
+                        animation = readAnimationChannelData(animation, id.c_str(), target, targetAttribute);
+                    }
+                    else
+                    {
+                        // Skip the animation channel (passing a target attribute of 
+                        // 0 causes the animation to not be created).
+                        unsigned int data;
+                        read(&data);
+                        readAnimationChannelData(NULL, id.c_str(), NULL, 0);
+                    }
+                }
+            }
+        }
     }
     }
 
 
+    SAFE_DELETE(_trackedNodes);
     return node;
     return node;
 }
 }
 
 
@@ -458,6 +546,44 @@ Node* Bundle::loadNode(const char* id, Scene* sceneContext, Node* nodeContext)
     return node;
     return node;
 }
 }
 
 
+bool Bundle::skipNode()
+{
+    const char* id = getIdFromOffset();
+
+    // Skip the node's type.
+    unsigned int nodeType;
+    if (!read(&nodeType))
+    {
+        return false;
+    }
+    
+    // Skip over the node's transform and parent ID.
+    fseek(_file, sizeof(float) * 16, SEEK_CUR);
+    readString(_file);
+
+    // Skip over the node's children.
+    unsigned int childrenCount;
+    if (!read(&childrenCount))
+    {
+        return false;
+    }
+    else if (childrenCount > 0)
+    {
+        for (unsigned int i = 0; i < childrenCount; i++)
+        {
+            if (!skipNode())
+                return false;
+        }
+    }
+    
+    // Skip over the node's camera, light, and model attachments.
+    Camera* camera = readCamera(); SAFE_RELEASE(camera);
+    Light* light = readLight(); SAFE_RELEASE(light);
+    Model* model = readModel(id); SAFE_RELEASE(model);
+
+    return true;
+}
+
 Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
 Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
 {
 {
     const char* id = getIdFromOffset();
     const char* id = getIdFromOffset();
@@ -482,6 +608,39 @@ Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
         return NULL;
         return NULL;
     }
     }
 
 
+    // If we are tracking nodes and it's not in the set yet, add it.
+    if (_trackedNodes)
+    {
+        std::map<std::string, Node*>::iterator iter = _trackedNodes->find(id);
+        if (iter != _trackedNodes->end())
+        {
+            SAFE_RELEASE(node);
+
+            // Skip over the node's transform and parent ID.
+            fseek(_file, sizeof(float) * 16, SEEK_CUR);
+            readString(_file);
+
+            // Skip over the node's children.
+            unsigned int childrenCount;
+            if (!read(&childrenCount))
+            {
+                return NULL;
+            }
+            else if (childrenCount > 0)
+            {
+                for (unsigned int i = 0; i < childrenCount; i++)
+                {
+                    if (!skipNode())
+                        return NULL;
+                }
+            }
+
+            return iter->second;
+        }
+        else
+            _trackedNodes->insert(std::make_pair(id, node));
+    }
+
     // If no loading context is set, set this node to the loading context
     // If no loading context is set, set this node to the loading context
     if (sceneContext == NULL && nodeContext == NULL)
     if (sceneContext == NULL && nodeContext == NULL)
     {
     {
@@ -497,6 +656,9 @@ Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
     }
     }
     setTransform(transform, node);
     setTransform(transform, node);
 
 
+    // Skip over the parent ID.
+    readString(_file);
+
     // Read children
     // Read children
     unsigned int childrenCount;
     unsigned int childrenCount;
     if (!read(&childrenCount))
     if (!read(&childrenCount))
@@ -509,7 +671,29 @@ Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
         // Read each child
         // Read each child
         for (unsigned int i = 0; i < childrenCount; i++)
         for (unsigned int i = 0; i < childrenCount; i++)
         {
         {
-            Node* child = readNode(sceneContext, nodeContext);
+            // Search the passed in loading contexts (scene/node) first to see
+            // if we've already loaded this child node during this load session.
+            Node* child = NULL;
+            id = getIdFromOffset();
+
+            if (sceneContext)
+            {
+                child = sceneContext->findNode(id, true);
+            }
+            else if (nodeContext)
+            {
+                child = nodeContext->findNode(id, true);
+            }
+            
+            // If the child node wasn't already loaded, load it.
+            if (!child)
+                child = readNode(sceneContext, nodeContext);
+            else
+            {
+                // Otherwise, skip over its data in the file.
+                readNode(NULL, NULL);
+            }
+
             if (child)
             if (child)
             {
             {
                 node->addChild(child);
                 node->addChild(child);
@@ -535,7 +719,7 @@ Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
     }
     }
 
 
     // Read model
     // Read model
-    Model* model = readModel(sceneContext, nodeContext, node->getId());
+    Model* model = readModel(node->getId());
     if (model)
     if (model)
     {
     {
         node->setModel(model);
         node->setModel(model);
@@ -666,7 +850,7 @@ Light* Bundle::readLight()
     return light;
     return light;
 }
 }
 
 
-Model* Bundle::readModel(Scene* sceneContext, Node* nodeContext, const char* nodeId)
+Model* Bundle::readModel(const char* nodeId)
 {
 {
     // Read mesh
     // Read mesh
     Mesh* mesh = NULL;
     Mesh* mesh = NULL;
@@ -688,7 +872,7 @@ Model* Bundle::readModel(Scene* sceneContext, Node* nodeContext, const char* nod
             }
             }
             if (hasSkin)
             if (hasSkin)
             {
             {
-                MeshSkin* skin = readMeshSkin(sceneContext, nodeContext);
+                MeshSkin* skin = readMeshSkin();
                 if (skin)
                 if (skin)
                 {
                 {
                     model->setSkin(skin);
                     model->setSkin(skin);
@@ -712,7 +896,7 @@ Model* Bundle::readModel(Scene* sceneContext, Node* nodeContext, const char* nod
     return NULL;
     return NULL;
 }
 }
 
 
-MeshSkin* Bundle::readMeshSkin(Scene* sceneContext, Node* nodeContext)
+MeshSkin* Bundle::readMeshSkin()
 {
 {
     MeshSkin* meshSkin = new MeshSkin();
     MeshSkin* meshSkin = new MeshSkin();
 
 
@@ -815,19 +999,63 @@ void Bundle::resolveJointReferences(Scene* sceneContext, Node* nodeContext)
         if (jointCount > 0)
         if (jointCount > 0)
         {
         {
             Joint* rootJoint = skinData->skin->getJoint((unsigned int)0);
             Joint* rootJoint = skinData->skin->getJoint((unsigned int)0);
-            Node* parent = rootJoint->getParent();
-            while (parent)
+            Node* node = rootJoint;
+            Node* parent = node->getParent();
+            
+            while (true)
             {
             {
-                if (skinData->skin->getJointIndex(static_cast<Joint*>(parent)) != -1)
+                if (parent)
+                {
+                    if (skinData->skin->getJointIndex(static_cast<Joint*>(parent)) != -1)
+                    {
+                        // Parent is a joint in the MeshSkin, so treat it as the new root
+                        rootJoint = static_cast<Joint*>(parent);
+                    }
+
+                    node = parent;
+                    parent = node->getParent();
+                }
+                else
                 {
                 {
-                    // Parent is a joint in the MeshSkin, so treat it as the new root
-                    rootJoint = static_cast<Joint*>(parent);
+                    std::string nodeID = node->getId();
+
+                    while (true)
+                    {
+                        // Get the node's type.
+                        Reference* ref = find(nodeID.c_str());
+                        if (ref == NULL)
+                        {
+                            LOG_ERROR_VARG("No object with name '%s' in bundle '%s'.", nodeID.c_str(), _path.c_str());
+                            break;
+                        }
+
+                        // Seek to the current node in the file so we can get it's parent ID.
+                        seekTo(nodeID.c_str(), ref->type);
+
+                        // Skip over the node type (1 unsigned int) and transform (16 floats) and read the parent id.
+                        fseek(_file, sizeof(unsigned int) + sizeof(float)*16, SEEK_CUR);
+                        std::string parentID = readString(_file);
+                        
+                        if (parentID.size() > 0)
+                            nodeID = parentID;
+                        else
+                            break;
+                    }
+
+                    if (nodeID != rootJoint->getId())
+                        loadNode(nodeID.c_str(), sceneContext, nodeContext);
+
+                    break;
                 }
                 }
-                parent = parent->getParent();
             }
             }
+
             skinData->skin->setRootJoint(rootJoint);
             skinData->skin->setRootJoint(rootJoint);
         }
         }
 
 
+        // Remove the joint hierarchy from the scene since it is owned by the mesh skin.
+        if (sceneContext)
+            sceneContext->removeNode(skinData->skin->_rootNode);
+
         // Done with this MeshSkinData entry
         // Done with this MeshSkinData entry
         SAFE_DELETE(_meshSkins[i]);
         SAFE_DELETE(_meshSkins[i]);
     }
     }
@@ -906,6 +1134,11 @@ Animation* Bundle::readAnimationChannel(Scene* scene, Animation* animation, cons
         }
         }
     }
     }
 
 
+    return readAnimationChannelData(animation, animationId, target, targetAttribute);
+}
+
+Animation* Bundle::readAnimationChannelData(Animation* animation, const char* id, AnimationTarget* target, unsigned int targetAttribute)
+{
     std::vector<unsigned long> keyTimes;
     std::vector<unsigned long> keyTimes;
     std::vector<float> values;
     std::vector<float> values;
     std::vector<float> tangentsIn;
     std::vector<float> tangentsIn;
@@ -964,7 +1197,7 @@ Animation* Bundle::readAnimationChannel(Scene* scene, Animation* animation, cons
         if (animation == NULL)
         if (animation == NULL)
         {
         {
             // TODO: This code currently assumes LINEAR only
             // TODO: This code currently assumes LINEAR only
-            animation = target->createAnimation(animationId, targetAttribute, keyTimesCount, &keyTimes[0], &values[0], Curve::LINEAR);
+            animation = target->createAnimation(id, targetAttribute, keyTimesCount, &keyTimes[0], &values[0], Curve::LINEAR);
         }
         }
         else
         else
         {
         {

+ 31 - 2
gameplay/src/Bundle.h

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

+ 4 - 4
gameplay/src/Form.cpp

@@ -36,18 +36,18 @@ namespace gameplay
         }
         }
     }
     }
 
 
-    Form* Form::create(const char* path)
+    Form* Form::create(const char* url)
     {
     {
         // Load Form from .form file.
         // Load Form from .form file.
-        assert(path);
+        assert(url);
 
 
-        Properties* properties = Properties::create(path);
+        Properties* properties = Properties::create(url);
         assert(properties);
         assert(properties);
         if (properties == NULL)
         if (properties == NULL)
             return NULL;
             return NULL;
 
 
         // Check if the Properties is valid and has a valid namespace.
         // Check if the Properties is valid and has a valid namespace.
-        Properties* formProperties = properties->getNextNamespace();
+        Properties* formProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
         assert(formProperties);
         assert(formProperties);
         if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
         if (!formProperties || !(strcmp(formProperties->getNamespace(), "form") == 0))
         {
         {

+ 6 - 4
gameplay/src/Form.h

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

+ 4 - 4
gameplay/src/Material.cpp

@@ -32,19 +32,19 @@ Material::~Material()
     }
     }
 }
 }
 
 
-Material* Material::create(const char* materialPath)
+Material* Material::create(const char* url)
 {
 {
-    assert(materialPath);
+    assert(url);
 
 
     // Load the material properties from file
     // Load the material properties from file
-    Properties* properties = Properties::create(materialPath);
+    Properties* properties = Properties::create(url);
     assert(properties);
     assert(properties);
     if (properties == NULL)
     if (properties == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    Material* material = create(properties->getNextNamespace());
+    Material* material = create((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
     SAFE_DELETE(properties);
     SAFE_DELETE(properties);
 
 
     return material;
     return material;

+ 6 - 4
gameplay/src/Material.h

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

+ 1 - 0
gameplay/src/MeshSkin.h

@@ -20,6 +20,7 @@ class MeshSkin : public Transform::Listener
     friend class Bundle;
     friend class Bundle;
     friend class Model;
     friend class Model;
     friend class Joint;
     friend class Joint;
+    friend class Node;
 
 
 public:
 public:
 
 

+ 18 - 4
gameplay/src/Node.cpp

@@ -286,6 +286,20 @@ unsigned int Node::getChildCount() const
 Node* Node::findNode(const char* id, bool recursive, bool exactMatch) const
 Node* Node::findNode(const char* id, bool recursive, bool exactMatch) const
 {
 {
     assert(id);
     assert(id);
+
+    // If the node has a model with a mesh skin, search the skin's hierarchy as well.
+    Node* rootNode = NULL;
+    if (_model != NULL && _model->getSkin() != NULL && (rootNode = _model->getSkin()->_rootNode) != NULL)
+    {
+        if ((exactMatch && rootNode->_id == id) || (!exactMatch && rootNode->_id.find(id) == 0))
+            return rootNode;
+        
+        Node* match = rootNode->findNode(id, true, exactMatch);
+        if (match)
+        {
+            return match;
+        }
+    }
     
     
     // Search immediate children first.
     // Search immediate children first.
     for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
     for (Node* child = getFirstChild(); child != NULL; child = child->getNextSibling())
@@ -998,18 +1012,18 @@ PhysicsCollisionObject* Node::setCollisionObject(PhysicsCollisionObject::Type ty
     return _collisionObject;
     return _collisionObject;
 }
 }
 
 
-PhysicsCollisionObject* Node::setCollisionObject(const char* filePath)
+PhysicsCollisionObject* Node::setCollisionObject(const char* url)
 {
 {
     // Load the collision object properties from file.
     // Load the collision object properties from file.
-    Properties* properties = Properties::create(filePath);
+    Properties* properties = Properties::create(url);
     assert(properties);
     assert(properties);
     if (properties == NULL)
     if (properties == NULL)
     {
     {
-        WARN_VARG("Failed to load collision object file: %s", filePath);
+        WARN_VARG("Failed to load collision object file: %s", url);
         return NULL;
         return NULL;
     }
     }
 
 
-    PhysicsCollisionObject* collisionObject = setCollisionObject(properties->getNextNamespace());
+    PhysicsCollisionObject* collisionObject = setCollisionObject((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
     SAFE_DELETE(properties);
     SAFE_DELETE(properties);
 
 
     return collisionObject;
     return collisionObject;

+ 5 - 3
gameplay/src/Node.h

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

+ 5 - 5
gameplay/src/ParticleEmitter.cpp

@@ -82,18 +82,18 @@ ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlendin
     return emitter;
     return emitter;
 }
 }
 
 
-ParticleEmitter* ParticleEmitter::create(const char* particleFile)
+ParticleEmitter* ParticleEmitter::create(const char* url)
 {
 {
-    assert(particleFile);
+    assert(url);
 
 
-    Properties* properties = Properties::create(particleFile);
+    Properties* properties = Properties::create(url);
     if (!properties)
     if (!properties)
     {
     {
-        LOG_ERROR_VARG("Error loading ParticleEmitter: Could not load file: %s", particleFile);
+        LOG_ERROR_VARG("Error loading ParticleEmitter: Could not load file: %s", url);
         return NULL;
         return NULL;
     }
     }
 
 
-    ParticleEmitter* particle = create(properties->getNextNamespace());
+    ParticleEmitter* particle = create((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
     SAFE_DELETE(properties);
     SAFE_DELETE(properties);
 
 
     return particle;
     return particle;

+ 6 - 4
gameplay/src/ParticleEmitter.h

@@ -154,13 +154,15 @@ public:
     };
     };
 
 
     /**
     /**
-     * Creates a particle emitter from a .particle file.
-     *
-     * @param particleFile The .particle file to load.
+     * Creates a particle emitter using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
+     * 
+     * @param url The URL pointing to the Properties object defining the particle emitter.
      * 
      * 
      * @return An initialized ParticleEmitter.
      * @return An initialized ParticleEmitter.
      */
      */
-    static ParticleEmitter* create(const char* particleFile);
+    static ParticleEmitter* create(const char* url);
 
 
     /**
     /**
      * Creates a particle emitter from the specified properties object.
      * Creates a particle emitter from the specified properties object.

+ 92 - 4
gameplay/src/Properties.cpp

@@ -46,11 +46,40 @@ Properties::Properties(FILE* file, const char* name, const char* id, const char*
     rewind();
     rewind();
 }
 }
 
 
-Properties* Properties::create(const char* filePath)
+Properties* Properties::create(const char* url)
 {
 {
-    assert(filePath);
+    assert(url);
 
 
-    FILE* file = FileSystem::openFile(filePath, "rb");
+    if (!url || strlen(url) == 0)
+    {
+        WARN("Attempting to create a Properties object from an empty URL!");
+        return NULL;
+    }
+
+    std::string urlString = url;
+    std::string fileString;
+    std::vector<std::string> namespacePath;
+
+    // If the url references a specific namespace within the file,
+    // calculate the full namespace path to the final namespace.
+    unsigned int loc = urlString.rfind("#");
+    if (loc != urlString.npos)
+    {
+        fileString = urlString.substr(0, loc);
+        std::string namespacePathString = urlString.substr(loc + 1);
+        while ((loc = namespacePathString.find("/")) != namespacePathString.npos)
+        {
+            namespacePath.push_back(namespacePathString.substr(0, loc));
+            namespacePathString = namespacePathString.substr(loc + 1);
+        }
+        namespacePath.push_back(namespacePathString);
+    }
+    else
+    {
+        fileString = url;
+    }
+
+    FILE* file = FileSystem::openFile(fileString.c_str(), "rb");
     if (!file)
     if (!file)
     {
     {
         return NULL;
         return NULL;
@@ -62,7 +91,46 @@ Properties* Properties::create(const char* filePath)
 
 
     fclose(file);
     fclose(file);
 
 
-    return properties;
+    // If the url references a specific namespace within the file,
+    // return the specified namespace or notify the user if it cannot be found.
+    Properties* originalProperties = properties;
+    if (namespacePath.size() > 0)
+    {
+        unsigned int size = namespacePath.size();
+        Properties* iter = properties->getNextNamespace();
+        for (unsigned int i = 0; i < size;)
+        {
+            while (true)
+            {
+                if (strcmp(iter->getId(), namespacePath[i].c_str()) == 0)
+                {
+                    if (i != size - 1)
+                    {
+                        properties = iter->getNextNamespace();
+                        iter = properties;
+                    }
+                    else
+                        properties = iter;
+
+                    i++;
+                    break;
+                }
+                
+                iter = properties->getNextNamespace();
+                if (iter == NULL)
+                {
+                    WARN_VARG("Failed to load Properties object from URL '%s'.", url);
+                    return NULL;
+                }
+            }
+        }
+
+        properties = properties->clone();
+        SAFE_DELETE(originalProperties);
+        return properties;
+    }
+    else
+        return properties;
 }
 }
 
 
 void Properties::readProperties(FILE* file)
 void Properties::readProperties(FILE* file)
@@ -817,4 +885,24 @@ bool Properties::getColor(const char* name, Vector4* out) const
     return false;
     return false;
 }
 }
 
 
+Properties* Properties::clone()
+{
+    Properties* p = new Properties();
+    
+    p->_namespace = _namespace;
+    p->_id = _id;
+    p->_parentID = _parentID;
+    p->_properties = _properties;
+    p->_propertiesItr = p->_properties.end();
+
+    unsigned int count = _namespaces.size();
+    for (unsigned int i = 0; i < count; i++)
+    {
+        p->_namespaces.push_back(_namespaces[i]->clone());
+    }
+    p->_namespacesItr = p->_namespaces.end();
+
+    return p;
+}
+
 }
 }

+ 9 - 4
gameplay/src/Properties.h

@@ -142,11 +142,13 @@ public:
     };
     };
 
 
     /**
     /**
-     * Creates a Properties runtime settings from a specified file path.
-     *
-     * @param filePath The file to create the properties from.
+     * Creates a Properties runtime settings from the specified URL, where the URL is of
+     * the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional).
+     * 
+     * @param url The URL to create the properties from.
      */
      */
-    static Properties* create(const char* filePath);
+    static Properties* create(const char* url);
 
 
     /**
     /**
      * Destructor.
      * Destructor.
@@ -391,6 +393,9 @@ private:
     // Called by resolveInheritance().
     // Called by resolveInheritance().
     void mergeWith(Properties* overrides);
     void mergeWith(Properties* overrides);
 
 
+    // Clones the Properties object.
+    Properties* clone();
+
     std::string _namespace;
     std::string _namespace;
     std::string _id;
     std::string _id;
     std::string _parentID;
     std::string _parentID;

+ 10 - 9
gameplay/src/SceneLoader.cpp

@@ -11,21 +11,21 @@ std::vector<SceneLoader::SceneAnimation> SceneLoader::_animations;
 std::vector<SceneLoader::SceneNode> SceneLoader::_sceneNodes;
 std::vector<SceneLoader::SceneNode> SceneLoader::_sceneNodes;
 std::string SceneLoader::_path;
 std::string SceneLoader::_path;
 
 
-Scene* SceneLoader::load(const char* filePath)
+Scene* SceneLoader::load(const char* url)
 {
 {
-    assert(filePath);
+    assert(url);
 
 
     // Load the scene properties from file.
     // Load the scene properties from file.
-    Properties* properties = Properties::create(filePath);
+    Properties* properties = Properties::create(url);
     assert(properties);
     assert(properties);
     if (properties == NULL)
     if (properties == NULL)
     {
     {
-        WARN_VARG("Failed to load scene file: %s", filePath);
+        WARN_VARG("Failed to load scene file: %s", url);
         return NULL;
         return NULL;
     }
     }
 
 
     // Check if the properties object is valid and has a valid namespace.
     // Check if the properties object is valid and has a valid namespace.
-    Properties* sceneProperties = properties->getNextNamespace();
+    Properties* sceneProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
     assert(sceneProperties);
     assert(sceneProperties);
     if (!sceneProperties || !(strcmp(sceneProperties->getNamespace(), "scene") == 0))
     if (!sceneProperties || !(strcmp(sceneProperties->getNamespace(), "scene") == 0))
     {
     {
@@ -36,6 +36,7 @@ Scene* SceneLoader::load(const char* filePath)
 
 
     // Get the path to the main GPB.
     // Get the path to the main GPB.
     _path = sceneProperties->getString("path");
     _path = sceneProperties->getString("path");
+
     // Build the node URL/property and animation reference tables and load the referenced files.
     // Build the node URL/property and animation reference tables and load the referenced files.
     buildReferenceTables(sceneProperties);
     buildReferenceTables(sceneProperties);
     loadReferencedFiles();
     loadReferencedFiles();
@@ -452,7 +453,7 @@ void SceneLoader::applyNodeUrls(Scene* scene)
                 {
                 {
                     if (sceneNode._exactMatch)
                     if (sceneNode._exactMatch)
                     {
                     {
-                        Node* node = tmpBundle->loadNode(snp._id.c_str());
+                        Node* node = tmpBundle->loadNode(snp._id.c_str(), scene);
                         if (node)
                         if (node)
                         {
                         {
                             node->setId(sceneNode._nodeID);
                             node->setId(sceneNode._nodeID);
@@ -641,7 +642,7 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
         }
         }
         else
         else
         {
         {
-            // TODO: Should we ignore these items? They could be used for generic properties file inheritence.
+            // TODO: Should we ignore these items? They could be used for generic properties file inheritance.
             WARN_VARG("Unsupported child namespace (of 'scene'): %s", ns->getNamespace());
             WARN_VARG("Unsupported child namespace (of 'scene'): %s", ns->getNamespace());
         }
         }
     }
     }
@@ -772,8 +773,8 @@ Scene* SceneLoader::loadMainSceneData(const Properties* sceneProperties)
         return NULL;
         return NULL;
     }
     }
 
 
-    const char* sceneID = strlen(sceneProperties->getId()) == 0 ? NULL : sceneProperties->getId();
-    Scene* scene = bundle->loadScene(sceneID);
+    // TODO: Support loading a specific scene from a GPB file using the URL syntax (i.e. "res/scene.gpb#myscene").
+    Scene* scene = bundle->loadScene(NULL);
     if (!scene)
     if (!scene)
     {
     {
         WARN_VARG("Failed to load scene from '%s'.", _path.c_str());
         WARN_VARG("Failed to load scene from '%s'.", _path.c_str());

+ 6 - 2
gameplay/src/SceneLoader.h

@@ -20,9 +20,13 @@ class SceneLoader
 private:
 private:
 
 
     /**
     /**
-     * Loads a scene file file.
+     * Loads a scene using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
+     * 
+     * @param url The URL pointing to the Properties object defining the scene.
      */
      */
-    static Scene* load(const char* filePath);
+    static Scene* load(const char* url);
     
     
     /**
     /**
      * Helper structures and functions for SceneLoader::load(const char*).
      * Helper structures and functions for SceneLoader::load(const char*).

+ 6 - 6
gameplay/src/Theme.cpp

@@ -52,15 +52,15 @@ namespace gameplay
         }
         }
     }
     }
 
 
-    Theme* Theme::create(const char* path)
+    Theme* Theme::create(const char* url)
     {
     {
-        assert(path);
+        assert(url);
 
 
         // Search theme cache first.
         // Search theme cache first.
         for (unsigned int i = 0, count = __themeCache.size(); i < count; ++i)
         for (unsigned int i = 0, count = __themeCache.size(); i < count; ++i)
         {
         {
             Theme* t = __themeCache[i];
             Theme* t = __themeCache[i];
-            if (t->_path == path)
+            if (t->_url == url)
             {
             {
                 // Found a match.
                 // Found a match.
                 t->addRef();
                 t->addRef();
@@ -70,7 +70,7 @@ namespace gameplay
         }
         }
 
 
         // Load theme properties from file path.
         // Load theme properties from file path.
-        Properties* properties = Properties::create(path);
+        Properties* properties = Properties::create(url);
         assert(properties);
         assert(properties);
         if (properties == NULL)
         if (properties == NULL)
         {
         {
@@ -78,7 +78,7 @@ namespace gameplay
         }
         }
 
 
         // Check if the Properties is valid and has a valid namespace.
         // Check if the Properties is valid and has a valid namespace.
-        Properties* themeProperties = properties->getNextNamespace();
+        Properties* themeProperties = (strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace();
         assert(themeProperties);
         assert(themeProperties);
         if (!themeProperties || !(strcmp(themeProperties->getNamespace(), "theme") == 0))
         if (!themeProperties || !(strcmp(themeProperties->getNamespace(), "theme") == 0))
         {
         {
@@ -88,7 +88,7 @@ namespace gameplay
 
 
         // Create a new theme.
         // Create a new theme.
         Theme* theme = new Theme();
         Theme* theme = new Theme();
-        theme->_path = path;
+        theme->_url = url;
         
         
         // Parse the Properties object and set up the theme.
         // Parse the Properties object and set up the theme.
         const char* textureFile = themeProperties->getString("texture");
         const char* textureFile = themeProperties->getString("texture");

+ 7 - 5
gameplay/src/Theme.h

@@ -407,13 +407,15 @@ private:
     ~Theme();
     ~Theme();
 
 
     /**
     /**
-     * Creates an instance of a Theme from a theme file.
-     *
-     * @param path Path to a theme file.
+     * Creates an instance of a Theme using the data from the Properties object defined at the specified URL, 
+     * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
+     * (and "#<namespace-id>/<namespace-id>/.../<namespace-id>" is optional). 
+     * 
+     * @param url The URL pointing to the Properties object defining the theme.
      *
      *
      * @return A new Theme.
      * @return A new Theme.
      */
      */
-    static Theme* create(const char* path);
+    static Theme* create(const char* url);
 
 
     Theme::Style* getStyle(const char* id) const;
     Theme::Style* getStyle(const char* id) const;
 
 
@@ -425,7 +427,7 @@ private:
 
 
     void lookUpSprites(const Properties* overlaySpace, ImageList** imageList, ThemeImage** mouseCursor, Skin** skin);
     void lookUpSprites(const Properties* overlaySpace, ImageList** imageList, ThemeImage** mouseCursor, Skin** skin);
 
 
-    std::string _path;
+    std::string _url;
     Texture* _texture;
     Texture* _texture;
     SpriteBatch* _spriteBatch;
     SpriteBatch* _spriteBatch;
     std::vector<Style*> _styles;
     std::vector<Style*> _styles;