2
0
Эх сурвалжийг харах

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

Adam Blake 13 жил өмнө
parent
commit
bb14f2e3e1

+ 67 - 49
gameplay/src/AudioBuffer.cpp

@@ -221,68 +221,86 @@ bool AudioBuffer::loadWav(FILE* file, ALuint buffer)
         }
     }
 
-    // Read in the type of the next section of the file.
-    if (fread(stream, 1, 4, file) != 4)
-    {
-        GP_ERROR("Failed to read next section type (fact or data) from wave file.");
-        return false;
-    }
-
-    // Read the fact section if it is there.
-    if (memcmp(stream, "fact", 4) == 0)
+    // Read in the rest of the file a chunk (section) at a time.
+    while (true)
     {
-        if (fread(stream, 1, 4, file) != 4)
-        {
-            GP_ERROR("Failed to read fact section size from wave file.");
-            return false;
-        }
-
-        section_size  = stream[3]<<24;
-        section_size |= stream[2]<<16;
-        section_size |= stream[1]<<8;
-        section_size |= stream[0];
-
-        // Read in the fact section.
-        if (fread(stream, 1, section_size, file) != section_size)
+        // Check if we are at the end of the file without reading the data.
+        if (feof(file))
         {
-            GP_ERROR("Failed to read fact section from wave file.");
+            GP_ERROR("Failed to load wave file; file appears to have no data.");
             return false;
         }
 
         // Read in the type of the next section of the file.
         if (fread(stream, 1, 4, file) != 4)
         {
-            GP_ERROR("Failed to read next section type (should be data) from wave file.");
+            GP_ERROR("Failed to read next section type from wave file.");
             return false;
         }
-    }
-
-    // Should now be the data section which holds the decoded sample data.
-    if (memcmp(stream, "data", 4) != 0)
-    {
-        GP_ERROR("Failed to load wave file; file appears to have no data.");
-        return false;
-    }
 
-    // Read how much data is remaining and buffer it up.
-    unsigned int dataSize;
-    if (fread(&dataSize, sizeof(int), 1, file) != 1)
-    {
-        GP_ERROR("Failed to read size of data section from wave file.");
-        return false;
-    }
+        // Data chunk.
+        if (memcmp(stream, "data", 4) == 0)
+        {
+            // Read how much data is remaining and buffer it up.
+            unsigned int dataSize;
+            if (fread(&dataSize, sizeof(int), 1, file) != 1)
+            {
+                GP_ERROR("Failed to read size of data section from wave file.");
+                return false;
+            }
+
+            char* data = new char[dataSize];
+            if (fread(data, sizeof(char), dataSize, file) != dataSize)
+            {
+                GP_ERROR("Failed to load wave file; file is missing data.");
+                SAFE_DELETE_ARRAY(data);
+                return false;
+            }
+
+            AL_CHECK( alBufferData(buffer, format, data, dataSize, frequency) );
+            SAFE_DELETE_ARRAY(data);
 
-    char* data = new char[dataSize];
-    if (fread(data, sizeof(char), dataSize, file) != dataSize)
-    {
-        GP_ERROR("Failed to load wave file; file is missing data.");
-        SAFE_DELETE_ARRAY(data);
-        return false;
+            // We've read the data, so return now.
+            return true;
+        }
+        // Other chunk - could be any of the following:
+        // - Fact ("fact")
+        // - Wave List ("wavl")
+        // - Silent ("slnt")
+        // - Cue ("cue ")
+        // - Playlist ("plst")
+        // - Associated Data List ("list")
+        // - Label ("labl")
+        // - Note ("note")
+        // - Labeled Text ("ltxt")
+        // - Sampler ("smpl")
+        // - Instrument ("inst")
+        else
+        {
+            // Store the name of the chunk so we can report errors informatively.
+            char chunk[5] = { 0 };
+            memcpy(chunk, stream, 4);
+
+            // Read the chunk size.
+            if (fread(stream, 1, 4, file) != 4)
+            {
+                GP_ERROR("Failed to read size of '%s' chunk from wave file.", chunk);
+                return false;
+            }
+
+            section_size  = stream[3]<<24;
+            section_size |= stream[2]<<16;
+            section_size |= stream[1]<<8;
+            section_size |= stream[0];
+
+            // Seek past the chunk.
+            if (fseek(file, section_size, SEEK_CUR) != 0)
+            {
+                GP_ERROR("Failed to seek past '%s' chunk in wave file.", chunk);
+                return false;
+            }
+        }
     }
-
-    AL_CHECK( alBufferData(buffer, format, data, dataSize, frequency) );
-    SAFE_DELETE_ARRAY(data);
-    return true;
 }
     
 bool AudioBuffer::loadOgg(FILE* file, ALuint buffer)

+ 24 - 13
gameplay/src/Node.cpp

@@ -1042,27 +1042,38 @@ PhysicsCollisionObject* Node::setCollisionObject(Properties* properties)
     SAFE_DELETE(_collisionObject);
 
     // Check if the properties is valid.
-    if (!properties || 
-        !(strcmp(properties->getNamespace(), "character") == 0 || 
-        strcmp(properties->getNamespace(), "ghostObject") == 0 || 
-        strcmp(properties->getNamespace(), "rigidBody") == 0))
+    if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
     {
-        GP_ERROR("Failed to load collision object from properties object: must be non-null object and have namespace equal to 'character', 'ghostObject', or 'rigidBody'.");
+        GP_ERROR("Failed to load collision object from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
         return NULL;
     }
 
-    if (strcmp(properties->getNamespace(), "character") == 0)
+    if (const char* type = properties->getString("type"))
     {
-        _collisionObject = PhysicsCharacter::create(this, properties);
-    }
-    else if (strcmp(properties->getNamespace(), "ghostObject") == 0)
-    {
-        _collisionObject = PhysicsGhostObject::create(this, properties);
+        if (strcmp(type, "CHARACTER") == 0)
+        {
+            _collisionObject = PhysicsCharacter::create(this, properties);
+        }
+        else if (strcmp(type, "GHOST_OBJECT") == 0)
+        {
+            _collisionObject = PhysicsGhostObject::create(this, properties);
+        }
+        else if (strcmp(type, "RIGID_BODY") == 0)
+        {
+            _collisionObject = PhysicsRigidBody::create(this, properties);
+        }
+        else
+        {
+            GP_ERROR("Unsupported collision object type '%s'.", type);
+            return NULL;
+        }
     }
-    else if (strcmp(properties->getNamespace(), "rigidBody") == 0)
+    else
     {
-        _collisionObject = PhysicsRigidBody::create(this, properties);
+        GP_ERROR("Failed to load collision object from properties object; required attribute 'type' is missing.");
+        return NULL;
     }
+    
     return _collisionObject;
 }
 

+ 15 - 2
gameplay/src/PhysicsCharacter.cpp

@@ -91,9 +91,22 @@ PhysicsCharacter::~PhysicsCharacter()
 PhysicsCharacter* PhysicsCharacter::create(Node* node, Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
-    if (!properties || !(strcmp(properties->getNamespace(), "character") == 0))
+    if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
     {
-        GP_ERROR("Failed to load physics character from properties object: must be non-null object and have namespace equal to 'character'.");
+        GP_ERROR("Failed to load physics character from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
+        return NULL;
+    }
+
+    // Check that the type is specified and correct.
+    const char* type = properties->getString("type");
+    if (!type)
+    {
+        GP_ERROR("Failed to load physics character from properties object; required attribute 'type' is missing.");
+        return NULL;
+    }
+    if (strcmp(type, "CHARACTER") != 0)
+    {
+        GP_ERROR("Failed to load physics character from properties object; attribute 'type' must be equal to 'CHARACTER'.");
         return NULL;
     }
 

+ 14 - 17
gameplay/src/PhysicsCollisionShape.cpp

@@ -124,12 +124,9 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
     GP_ASSERT(node);
 
     // Check if the properties is valid and has a valid namespace.
-    if (!properties || 
-        !(strcmp(properties->getNamespace(), "character") == 0 || 
-        strcmp(properties->getNamespace(), "ghostObject") == 0 || 
-        strcmp(properties->getNamespace(), "rigidBody") == 0))
+    if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
     {
-        GP_ERROR("Failed to load physics collision shape from properties object: must be non-null object and have namespace equal to 'character', 'ghostObject', or 'rigidBody'.");
+        GP_ERROR("Failed to load physics collision shape from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
         return NULL;
     }
 
@@ -141,33 +138,33 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
     float height = -1.0f;
     bool centerIsAbsolute = false;
     const char* imagePath = NULL;
-    bool typeSpecified = false;
+    bool shapeSpecified = false;
 
     // Load the defined properties.
     properties->rewind();
     const char* name;
     while (name = properties->getNextProperty())
     {
-        if (strcmp(name, "type") == 0)
+        if (strcmp(name, "shape") == 0)
         {
-            std::string typeStr = properties->getString();
-            if (typeStr == "BOX")
+            std::string shapeStr = properties->getString();
+            if (shapeStr == "BOX")
                 type = SHAPE_BOX;
-            else if (typeStr == "SPHERE")
+            else if (shapeStr == "SPHERE")
                 type = SHAPE_SPHERE;
-            else if (typeStr == "MESH")
+            else if (shapeStr == "MESH")
                 type = SHAPE_MESH;
-            else if (typeStr == "HEIGHTFIELD")
+            else if (shapeStr == "HEIGHTFIELD")
                 type = SHAPE_HEIGHTFIELD;
-            else if (typeStr == "CAPSULE")
+            else if (shapeStr == "CAPSULE")
                 type = SHAPE_CAPSULE;
             else
             {
-                GP_ERROR("Could not create physics collision shape; unsupported value for collision shape type: '%s'.", typeStr.c_str());
+                GP_ERROR("Could not create physics collision shape; unsupported value for collision shape type: '%s'.", shapeStr.c_str());
                 return NULL;
             }
 
-            typeSpecified = true;
+            shapeSpecified = true;
         }
         else if (strcmp(name, "image") == 0)
         {
@@ -201,9 +198,9 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
         }
     }
 
-    if (!typeSpecified)
+    if (!shapeSpecified)
     {
-        GP_ERROR("Missing 'type' specifier for collision shape definition.");
+        GP_ERROR("Missing 'shape' specifier for collision shape definition.");
         return NULL;
     }
 

+ 22 - 0
gameplay/src/PhysicsController.cpp

@@ -45,6 +45,22 @@ void PhysicsController::addStatusListener(Listener* listener)
     _listeners->push_back(listener);
 }
 
+void PhysicsController::removeStatusListener(Listener* listener)
+{
+    GP_ASSERT(listener);
+    if (!_listeners)
+        return;
+
+    for (std::vector<Listener*>::iterator iter = _listeners->begin(); iter != _listeners->end(); iter++)
+    {
+        if (*iter == listener)
+        {
+            _listeners->erase(iter);
+            return;
+        }
+    }
+}
+
 PhysicsFixedConstraint* PhysicsController::createFixedConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b)
 {
     checkConstraintRigidBodies(a, b);
@@ -1350,4 +1366,10 @@ int PhysicsController::DebugDrawer::getDebugMode() const
     return _mode;
 }
 
+PhysicsController::Listener::~Listener()
+{
+    GP_ASSERT(Game::getInstance()->getPhysicsController());
+    Game::getInstance()->getPhysicsController()->removeStatusListener(this);
+}
+
 }

+ 14 - 0
gameplay/src/PhysicsController.h

@@ -54,6 +54,13 @@ public:
          * Handles when a physics world status event occurs.
          */
         virtual void statusEvent(EventType type) = 0;
+
+    protected:
+
+        /**
+         * Destructor.
+         */
+        virtual ~Listener();
     };
 
     /**
@@ -89,6 +96,13 @@ public:
      */
     void addStatusListener(PhysicsController::Listener* listener);
 
+    /**
+     * Removes a listener to the physics controller.
+     * 
+     * @param listener The listener to remove.
+     */
+    void removeStatusListener(Listener* listener);
+
     /**
      * Creates a fixed constraint.
      * 

+ 15 - 2
gameplay/src/PhysicsGhostObject.cpp

@@ -47,9 +47,22 @@ PhysicsGhostObject::~PhysicsGhostObject()
 PhysicsGhostObject* PhysicsGhostObject::create(Node* node, Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
-    if (!properties || !(strcmp(properties->getNamespace(), "ghostObject") == 0))
+    if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
     {
-        GP_ERROR("Failed to load ghost object from properties object: must be non-null object and have namespace equal to 'ghost'.");
+        GP_ERROR("Failed to load ghost object from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
+        return NULL;
+    }
+
+    // Check that the type is specified and correct.
+    const char* type = properties->getString("type");
+    if (!type)
+    {
+        GP_ERROR("Failed to load ghost object from properties object; required attribute 'type' is missing.");
+        return NULL;
+    }
+    if (strcmp(type, "GHOST_OBJECT") != 0)
+    {
+        GP_ERROR("Failed to load ghost object from properties object; attribute 'type' must be equal to 'GHOST_OBJECT'.");
         return NULL;
     }
 

+ 23 - 6
gameplay/src/PhysicsRigidBody.cpp

@@ -11,7 +11,7 @@ namespace gameplay
 {
 
 PhysicsRigidBody::PhysicsRigidBody(Node* node, const PhysicsCollisionShape::Definition& shape, const Parameters& parameters)
-        : PhysicsCollisionObject(node), _body(NULL), _mass(parameters.mass), _constraints(NULL)
+        : PhysicsCollisionObject(node), _body(NULL), _mass(parameters.mass), _constraints(NULL), _inDestructor(false)
 {
     GP_ASSERT(Game::getInstance()->getPhysicsController());
     GP_ASSERT(_node);
@@ -64,6 +64,7 @@ PhysicsRigidBody::~PhysicsRigidBody()
     GP_ASSERT(_node);
 
     // Clean up all constraints linked to this rigid body.
+    _inDestructor = true;
     if (_constraints)
     {
         for (unsigned int i = 0; i < _constraints->size(); ++i)
@@ -73,13 +74,13 @@ PhysicsRigidBody::~PhysicsRigidBody()
         SAFE_DELETE(_constraints);
     }
 
-    // Remove collision object from physics controller
+    // Remove collision object from physics controller.
     Game::getInstance()->getPhysicsController()->removeCollisionObject(this);
 
     // Clean up the rigid body and its related objects.
     SAFE_DELETE(_body);
 
-    // Unregister node listener (only registered for heihgtfield collision shape types)
+    // Unregister node listener (only registered for heihgtfield collision shape types).
     if (_collisionShape->getType() == PhysicsCollisionShape::SHAPE_HEIGHTFIELD)
     {
         _node->removeListener(this);
@@ -155,9 +156,22 @@ void PhysicsRigidBody::applyTorqueImpulse(const Vector3& torque)
 PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties)
 {
     // Check if the properties is valid and has a valid namespace.
-    if (!properties || !(strcmp(properties->getNamespace(), "rigidBody") == 0))
+    if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
     {
-        GP_ERROR("Failed to load rigid body from properties object: must be non-null object and have namespace equal to 'rigidBody'.");
+        GP_ERROR("Failed to load rigid body from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
+        return NULL;
+    }
+
+    // Check that the type is specified and correct.
+    const char* type = properties->getString("type");
+    if (!type)
+    {
+        GP_ERROR("Failed to load physics rigid body from properties object; required attribute 'type' is missing.");
+        return NULL;
+    }
+    if (strcmp(type, "RIGID_BODY") != 0)
+    {
+        GP_ERROR("Failed to load physics rigid body from properties object; attribute 'type' must be equal to 'RIGID_BODY'.");
         return NULL;
     }
 
@@ -290,7 +304,10 @@ void PhysicsRigidBody::addConstraint(PhysicsConstraint* constraint)
 
 void PhysicsRigidBody::removeConstraint(PhysicsConstraint* constraint)
 {
-    if (_constraints)
+    // Ensure that the rigid body has constraints and that we are
+    // not currently in the rigid body's destructor (in this case,
+    // the constraints will be destroyed from there).
+    if (_constraints && !_inDestructor)
     {
         for (unsigned int i = 0; i < _constraints->size(); ++i)
         {

+ 1 - 0
gameplay/src/PhysicsRigidBody.h

@@ -314,6 +314,7 @@ private:
     btRigidBody* _body;
     float _mass;
     std::vector<PhysicsConstraint*>* _constraints;
+    bool _inDestructor;
 
 };
 

+ 75 - 0
gameplay/src/Properties.cpp

@@ -145,6 +145,7 @@ void Properties::readProperties(FILE* file)
     char* parentID;
     char* rc;
     char* rcc;
+    char* rccc;
 
     while (true)
     {
@@ -205,6 +206,9 @@ void Properties::readProperties(FILE* file)
             {
                 parentID = NULL;
 
+                // Get the last character on the line (ignoring whitespace).
+                const char* lineEnd = trimWhiteSpace(line) + (strlen(trimWhiteSpace(line)) - 1);
+
                 // This line might begin or end a namespace,
                 // or it might be a key/value pair without '='.
 
@@ -213,6 +217,9 @@ void Properties::readProperties(FILE* file)
 
                 // Check for inheritance: ':'
                 rcc = strchr(line, ':');
+
+                // Check for '}' on same line.
+                rccc = strchr(line, '}');
             
                 // Get the name of the namespace.
                 name = strtok(line, " \t\n{");
@@ -231,6 +238,8 @@ void Properties::readProperties(FILE* file)
                 // Get its ID if it has one.
                 value = strtok(NULL, ":{");
                 value = trimWhiteSpace(value);
+
+                // Get its parent ID if it has one.
                 if (rcc != NULL)
                 {
                     parentID = strtok(NULL, "{");
@@ -239,18 +248,84 @@ void Properties::readProperties(FILE* file)
 
                 if (value != NULL && value[0] == '{')
                 {
+                    // If the namespace ends on this line, seek back to right before the '}' character.
+                    if (rccc && rccc == lineEnd)
+                    {
+                        if (fseek(file, -1, SEEK_CUR) != 0)
+                        {
+                            GP_ERROR("Failed to seek back to before a '}' character in properties file.");
+                            return;
+                        }
+                        while (fgetc(file) != '}')
+                        {
+                            if (fseek(file, -2, SEEK_CUR) != 0)
+                            {
+                                GP_ERROR("Failed to seek back to before a '}' character in properties file.");
+                                return;
+                            }
+                        }
+                        if (fseek(file, -1, SEEK_CUR) != 0)
+                        {
+                            GP_ERROR("Failed to seek back to before a '}' character in properties file.");
+                            return;
+                        }
+                    }
+
                     // New namespace without an ID.
                     Properties* space = new Properties(file, name, NULL, parentID);
                     _namespaces.push_back(space);
+
+                    // If the namespace ends on this line, seek to right after the '}' character.
+                    if (rccc && rccc == lineEnd)
+                    {
+                        if (fseek(file, 1, SEEK_CUR) != 0)
+                        {
+                            GP_ERROR("Failed to seek to immediately after a '}' character in properties file.");
+                            return;
+                        }
+                    }
                 }
                 else
                 {
                     // If '{' appears on the same line.
                     if (rc != NULL)
                     {
+                        // If the namespace ends on this line, seek back to right before the '}' character.
+                        if (rccc && rccc == lineEnd)
+                        {
+                            if (fseek(file, -1, SEEK_CUR) != 0)
+                            {
+                                GP_ERROR("Failed to seek back to before a '}' character in properties file.");
+                                return;
+                            }
+                            while (fgetc(file) != '}')
+                            {
+                                if (fseek(file, -2, SEEK_CUR) != 0)
+                                {
+                                    GP_ERROR("Failed to seek back to before a '}' character in properties file.");
+                                    return;
+                                }
+                            }
+                            if (fseek(file, -1, SEEK_CUR) != 0)
+                            {
+                                GP_ERROR("Failed to seek back to before a '}' character in properties file.");
+                                return;
+                            }
+                        }
+
                         // Create new namespace.
                         Properties* space = new Properties(file, name, value, parentID);
                         _namespaces.push_back(space);
+
+                        // If the namespace ends on this line, seek to right after the '}' character.
+                        if (rccc && rccc == lineEnd)
+                        {
+                            if (fseek(file, 1, SEEK_CUR) != 0)
+                            {
+                                GP_ERROR("Failed to seek to immediately after a '}' character in properties file.");
+                                return;
+                            }
+                        }
                     }
                     else
                     {

+ 116 - 245
gameplay/src/SceneLoader.cpp

@@ -6,13 +6,19 @@
 namespace gameplay
 {
 
-std::map<std::string, Properties*> SceneLoader::_propertiesFromFile;
+std::map<std::string, Properties*> SceneLoader::_properties;
 std::vector<SceneLoader::SceneAnimation> SceneLoader::_animations;
 std::vector<SceneLoader::SceneNode> SceneLoader::_sceneNodes;
+std::string SceneLoader::_gpbPath;
 std::string SceneLoader::_path;
 
 Scene* SceneLoader::load(const char* url)
 {
+    // Get the file part of the url that we are loading the scene from.
+    std::string urlStr = url ? url : "";
+    std::string id;
+    splitURL(urlStr, &_path, &id);
+
     // Load the scene properties from file.
     Properties* properties = Properties::create(url);
     if (properties == NULL)
@@ -31,9 +37,9 @@ Scene* SceneLoader::load(const char* url)
     }
 
     // Get the path to the main GPB.
-    _path = sceneProperties->getString("path");
+    _gpbPath = 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/store the inline properties objects.
     buildReferenceTables(sceneProperties);
     loadReferencedFiles();
 
@@ -61,7 +67,7 @@ Scene* SceneLoader::load(const char* url)
         SceneNodeProperty::TRANSLATE | 
         SceneNodeProperty::TRANSPARENT |
         SceneNodeProperty::DYNAMIC);
-    applyNodeProperties(scene, sceneProperties, SceneNodeProperty::CHARACTER | SceneNodeProperty::GHOSTOBJECT | SceneNodeProperty::RIGIDBODY);
+    applyNodeProperties(scene, sceneProperties, SceneNodeProperty::COLLISION_OBJECT);
     createAnimations(scene);
 
     // Find the physics properties object.
@@ -83,17 +89,18 @@ Scene* SceneLoader::load(const char* url)
         loadPhysics(physics, scene);
 
     // Clean up all loaded properties objects.
-    std::map<std::string, Properties*>::iterator iter = _propertiesFromFile.begin();
-    for (; iter != _propertiesFromFile.end(); iter++)
+    std::map<std::string, Properties*>::iterator iter = _properties.begin();
+    for (; iter != _properties.end(); iter++)
     {
-        SAFE_DELETE(iter->second);
+        if (iter->first.find(_path) == iter->first.npos)
+            SAFE_DELETE(iter->second);
     }
 
     // Clean up the .scene file's properties object.
     SAFE_DELETE(properties);
 
     // Clear all temporary data stores.
-    _propertiesFromFile.clear();
+    _properties.clear();
     _animations.clear();
     _sceneNodes.clear();
 
@@ -102,46 +109,28 @@ Scene* SceneLoader::load(const char* url)
 
 void SceneLoader::addSceneAnimation(const char* animationID, const char* targetID, const char* url)
 {
-    // Calculate the file and id from the given url.
-    std::string file;
-    std::string id;
-    splitURL(url, &file, &id);
+    std::string urlStr = url ? url : "";
 
     // If there is a file that needs to be loaded later, add an 
     // empty entry to the properties table to signify it.
-    if (file.length() > 0 && _propertiesFromFile.count(file) == 0)
-        _propertiesFromFile[file] = NULL;
+    if (urlStr.length() > 0 && _properties.count(urlStr) == 0)
+        _properties[urlStr] = NULL;
 
     // Add the animation to the list of animations to be resolved later.
-    _animations.push_back(SceneAnimation(animationID, targetID, file, id));
+    _animations.push_back(SceneAnimation(animationID, targetID, urlStr));
 }
 
 void SceneLoader::addSceneNodeProperty(SceneNode& sceneNode, SceneNodeProperty::Type type, const char* url, int index)
 {
-    // Calculate the file and id from the given url.
-    std::string file;
-    std::string id;
-    splitURL(url, &file, &id);
+    std::string urlStr = url ? url : "";
 
     // If there is a non-GPB file that needs to be loaded later, add an 
     // empty entry to the properties table to signify it.
-    if (file.length() > 0 && file.find(".gpb") == file.npos && _propertiesFromFile.count(file) == 0)
-        _propertiesFromFile[file] = NULL;
-
-    SceneNodeProperty prop(type, file, id, index);
-
-    // Parse for wildcharacter character (only supported on the URL attribute)
-    if (type == SceneNodeProperty::URL)
-    {
-        if (id.length() > 1 && id.at(id.length()-1) == '*')
-        {
-            prop._id = id.substr(0, id.length()-1);
-            sceneNode._exactMatch = false;
-        }
-    }
+    if (urlStr.length() > 0 && urlStr.find(".gpb") == urlStr.npos && _properties.count(urlStr) == 0)
+        _properties[urlStr] = NULL;
 
     // Add the node property to the list of node properties to be resolved later.
-    sceneNode._properties.push_back(prop);
+    sceneNode._properties.push_back(SceneNodeProperty(type, urlStr, index));
 }
 
 void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* sceneProperties, unsigned int typeFlags)
@@ -151,43 +140,19 @@ void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* scen
         SceneNode& sceneNode = _sceneNodes[i];
         GP_ASSERT(sceneNode._nodeID);
 
-        if (sceneNode._exactMatch)
+        // Find the node matching the specified ID.
+        Node* node = scene->findNode(sceneNode._nodeID);
+        if (!node)
         {
-            // Find the node matching the specified ID exactly.
-            Node* node = scene->findNode(sceneNode._nodeID);
-            if (!node)
-            {
-                GP_ERROR("Failed to set property for node '%s', which does not exist in the scene.", sceneNode._nodeID);
-                continue;
-            }
-
-            for (unsigned int j = 0, pcount = sceneNode._properties.size(); j < pcount; ++j)
-            {
-                SceneNodeProperty& snp = sceneNode._properties[j];
-                if (typeFlags & snp._type)
-                    applyNodeProperty(sceneNode, node, sceneProperties, snp, scene);
-            }
+            GP_ERROR("Failed to set property for node '%s', which does not exist in the scene.", sceneNode._nodeID);
+            continue;
         }
-        else
-        {
-            // Find all nodes matching the specified ID.
-            std::vector<Node*> nodes;
-            unsigned int nodeCount = scene->findNodes(sceneNode._nodeID, nodes, true, false);
-            if (nodeCount == 0)
-            {
-                GP_ERROR("Failed to set property for nodes with id matching '%s'; no such nodes exist in the scene.", sceneNode._nodeID);
-                continue;
-            }
-            
-            for (unsigned int j = 0, pcount = sceneNode._properties.size(); j < pcount; ++j)
-            {
-                SceneNodeProperty& snp = sceneNode._properties[j];
-                if ((typeFlags & snp._type) == 0)
-                    continue;
 
-                for (unsigned int k = 0; k < nodeCount; ++k)
-                    applyNodeProperty(sceneNode, nodes[k], sceneProperties, snp, scene);
-            }
+        for (unsigned int j = 0, pcount = sceneNode._properties.size(); j < pcount; ++j)
+        {
+            SceneNodeProperty& snp = sceneNode._properties[j];
+            if (typeFlags & snp._type)
+                applyNodeProperty(sceneNode, node, sceneProperties, snp, scene);
         }
     }
 }
@@ -197,35 +162,16 @@ void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Prop
     if (snp._type == SceneNodeProperty::AUDIO ||
         snp._type == SceneNodeProperty::MATERIAL ||
         snp._type == SceneNodeProperty::PARTICLE ||
-        snp._type == SceneNodeProperty::CHARACTER ||
-        snp._type == SceneNodeProperty::GHOSTOBJECT ||
-        snp._type == SceneNodeProperty::RIGIDBODY)
+        snp._type == SceneNodeProperty::COLLISION_OBJECT)
     {
         // Check to make sure the referenced properties object was loaded properly.
-        Properties* p = _propertiesFromFile[snp._file];
+        Properties* p = _properties[snp._url];
         if (!p)
         {
-            GP_ERROR("The referenced node data in file '%s' failed to load.", snp._file.c_str());
+            GP_ERROR("The referenced node data at url '%s' failed to load.", snp._url.c_str());
             return;
         }
 
-        // If a specific namespace within the file was specified, load that namespace.
-        if (snp._id.size() > 0)
-        {
-            p = p->getNamespace(snp._id.c_str());
-            if (!p)
-            {
-                GP_ERROR("The referenced node data at '%s#%s' failed to load.", snp._file.c_str(), snp._id.c_str());
-                return;
-            }
-        }
-        else
-        {
-            // Otherwise, use the first namespace.
-            p->rewind();
-            p = p->getNextNamespace();
-        }
-
         switch (snp._type)
         {
         case SceneNodeProperty::AUDIO:
@@ -255,49 +201,12 @@ void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Prop
             SAFE_RELEASE(particleEmitter);
             break;
         }
-        case SceneNodeProperty::CHARACTER:
-        case SceneNodeProperty::GHOSTOBJECT:
-        case SceneNodeProperty::RIGIDBODY:
+        case SceneNodeProperty::COLLISION_OBJECT:
         {
-            // Check to make sure the referenced properties object was loaded properly.
-            Properties* p = _propertiesFromFile[snp._file];
-            if (!p)
-            {
-                GP_ERROR("The referenced node data in file '%s' failed to load.", snp._file.c_str());
-                return;
-            }
-
-            // If a specific namespace within the file was specified, load that namespace.
-            if (snp._id.size() > 0)
-            {
-                p = p->getNamespace(snp._id.c_str());
-                if (!p)
-                {
-                    GP_ERROR("The referenced node data at '%s#%s' failed to load.", snp._file.c_str(), snp._id.c_str());
-                    return;
-                }
-            }
-            else
-            {
-                // Otherwise, use the first namespace.
-                p->rewind();
-                p = p->getNextNamespace();
-            }
-
             // Check to make sure the type of the namespace used to load the physics collision object is correct.
-            if (snp._type == SceneNodeProperty::CHARACTER && strcmp(p->getNamespace(), "character") != 0)
-            {
-                GP_ERROR("Attempting to set a 'character' (physics collision object attribute) on a node using a '%s' definition.", p->getNamespace());
-                return;
-            }
-            else if (snp._type == SceneNodeProperty::GHOSTOBJECT && strcmp(p->getNamespace(), "ghostObject") != 0)
+            if (snp._type == SceneNodeProperty::COLLISION_OBJECT && strcmp(p->getNamespace(), "collisionObject") != 0)
             {
-                GP_ERROR("Attempting to set a 'ghostObject' (physics collision object attribute) on a node using a '%s' definition.", p->getNamespace());
-                return;
-            }
-            else if (snp._type == SceneNodeProperty::RIGIDBODY && strcmp(p->getNamespace(), "rigidBody") != 0)
-            {
-                GP_ERROR("Attempting to set a 'rigidBody' (physics collision object attribute) on a node using a '%s' definition.", p->getNamespace());
+                GP_ERROR("Attempting to set a physics collision object on a node using a '%s' definition.", p->getNamespace());
                 return;
             }
             else
@@ -416,48 +325,24 @@ void SceneLoader::applyNodeUrls(Scene* scene)
             if (snp._type != SceneNodeProperty::URL)
                 continue;
 
-            if (snp._file.empty())
+            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.
 
                 // 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 = scene->findNode(id.c_str());
+                if (node)
                 {
-                    Node* node = scene->findNode(snp._id.c_str());
-                    if (node)
-                    {
-                        node->setId(sceneNode._nodeID);
-                    }
-                    else
-                    {
-                        GP_ERROR("Could not find node '%s' in main scene GPB file.", snp._id.c_str());
-                    }
+                    node->setId(sceneNode._nodeID);
                 }
                 else
                 {
-                    // Search for nodes using a partial match
-                    std::string partialMatch = snp._id;
-                    std::vector<Node*> nodes;
-                    unsigned int nodeCount = scene->findNodes(snp._id.c_str(), nodes, true, false);
-                    if (nodeCount > 0)
-                    {
-                        GP_ASSERT(sceneNode._nodeID);
-
-                        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];
-                            GP_ASSERT(node);
-                            std::string newID(sceneNode._nodeID);
-                            newID += (node->getId() + snp._id.length());
-                            node->setId(newID.c_str());
-                        }
-                    }
-                    else
-                    {
-                        GP_ERROR("Could not find any nodes matching '%s' in main scene GPB file.", snp._id.c_str());
-                    }
+                    GP_ERROR("Could not find node '%s' in main scene GPB file.", id.c_str());
                 }
             }
             else
@@ -467,60 +352,26 @@ void SceneLoader::applyNodeUrls(Scene* scene)
                 // 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(snp._file.c_str());
+                Bundle* tmpBundle = Bundle::create(file.c_str());
                 if (tmpBundle)
                 {
-                    if (sceneNode._exactMatch)
+                    Node* node = tmpBundle->loadNode(id.c_str(), scene);
+                    if (node)
                     {
-                        Node* node = tmpBundle->loadNode(snp._id.c_str(), scene);
-                        if (node)
-                        {
-                            node->setId(sceneNode._nodeID);
-                            scene->addNode(node);
-                            SAFE_RELEASE(node);
-                        }
-                        else
-                        {
-                            GP_ERROR("Could not load node '%s' from GPB file '%s'.", snp._id.c_str(), snp._file.c_str());
-                        }
+                        node->setId(sceneNode._nodeID);
+                        scene->addNode(node);
+                        SAFE_RELEASE(node);
                     }
                     else
                     {
-                        // Search for nodes in the bundle using a partial match
-                        std::string partialMatch = snp._id;
-                        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, snp._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() + snp._id.length());
-                                    node->setId(newID.c_str());
-                                    scene->addNode(node);
-                                    SAFE_RELEASE(node);
-                                    matchCount++;
-                                }
-                            }
-                        }
-                        if (matchCount == 0)
-                        {
-                            GP_ERROR("Could not find any nodes matching '%s' in GPB file '%s'.", snp._id.c_str(), snp._file.c_str());
-                        }
+                        GP_ERROR("Could not load node '%s' from GPB file '%s'.", id.c_str(), file.c_str());
                     }
 
                     SAFE_RELEASE(tmpBundle);
                 }
                 else
                 {
-                    GP_ERROR("Failed to load GPB file '%s' for node stitching.", snp._file.c_str());
+                    GP_ERROR("Failed to load GPB file '%s' for node stitching.", file.c_str());
                 }
             }
 
@@ -550,6 +401,42 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
             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(), "collisionObject") == 0)
+                {
+                    propertyUrl += "collisionObject/" + std::string(subns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::COLLISION_OBJECT, propertyUrl.c_str());
+                    _properties[propertyUrl] = subns;
+                }
+                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)
@@ -576,17 +463,9 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
                 {
                     addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, ns->getString());
                 }
-                else if (strcmp(name, "character") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::CHARACTER, ns->getString());
-                }
-                else if (strcmp(name, "ghostObject") == 0)
+                else if (strcmp(name, "collisionObject") == 0)
                 {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::GHOSTOBJECT, ns->getString());
-                }
-                else if (strcmp(name, "rigidBody") == 0)
-                {
-                    addSceneNodeProperty(sceneNode, SceneNodeProperty::RIGIDBODY, ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::COLLISION_OBJECT, ns->getString());
                 }
                 else if (strcmp(name, "rigidBodyModel") == 0)
                 {
@@ -682,21 +561,12 @@ void SceneLoader::createAnimations(const Scene* scene)
         }
 
         // Check to make sure the referenced properties object was loaded properly.
-        Properties* p = _propertiesFromFile[_animations[i]._file];
+        Properties* p = _properties[_animations[i]._url];
         if (!p)
         {
-            GP_ERROR("The referenced animation data in file '%s' failed to load.", _animations[i]._file.c_str());
+            GP_ERROR("The referenced animation data at url '%s' failed to load.", _animations[i]._url.c_str());
             continue;
         }
-        if (_animations[i]._id.size() > 0)
-        {
-            p = p->getNamespace(_animations[i]._id.c_str());
-            if (!p)
-            {
-                GP_ERROR("The referenced animation data at '%s#%s' failed to load.", _animations[i]._file.c_str(), _animations[i]._id.c_str());
-                continue;
-            }
-        }
 
         node->createAnimation(_animations[i]._animationID, p);
     }
@@ -811,10 +681,10 @@ Scene* SceneLoader::loadMainSceneData(const Properties* sceneProperties)
     GP_ASSERT(sceneProperties);
 
     // Load the main scene from the specified path.
-    Bundle* bundle = Bundle::create(_path.c_str());
+    Bundle* bundle = Bundle::create(_gpbPath.c_str());
     if (!bundle)
     {
-        GP_ERROR("Failed to load scene GPB file '%s'.", _path.c_str());
+        GP_ERROR("Failed to load scene GPB file '%s'.", _gpbPath.c_str());
         return NULL;
     }
 
@@ -822,7 +692,7 @@ Scene* SceneLoader::loadMainSceneData(const Properties* sceneProperties)
     Scene* scene = bundle->loadScene(NULL);
     if (!scene)
     {
-        GP_ERROR("Failed to load scene from '%s'.", _path.c_str());
+        GP_ERROR("Failed to load scene from '%s'.", _gpbPath.c_str());
         SAFE_RELEASE(bundle);
         return NULL;
     }
@@ -952,14 +822,17 @@ void SceneLoader::loadPhysics(Properties* physics, Scene* scene)
 void SceneLoader::loadReferencedFiles()
 {
     // Load all referenced properties files.
-    std::map<std::string, Properties*>::iterator iter = _propertiesFromFile.begin();
-    for (; iter != _propertiesFromFile.end(); iter++)
+    std::map<std::string, Properties*>::iterator iter = _properties.begin();
+    for (; iter != _properties.end(); iter++)
     {
-        Properties* p = Properties::create(iter->first.c_str());
-        if (p == NULL)
-            GP_ERROR("Failed to load referenced file '%s'.", iter->first.c_str());
+        if (iter->second == NULL)
+        {
+            Properties* p = Properties::create(iter->first.c_str());
+            if (p == NULL)
+                GP_ERROR("Failed to load referenced file '%s'.", iter->first.c_str());
 
-        iter->second = p;
+            iter->second = p;
+        }
     }
 }
 
@@ -1065,28 +938,26 @@ PhysicsConstraint* SceneLoader::loadSpringConstraint(const Properties* constrain
     return physicsConstraint;
 }
 
-void SceneLoader::splitURL(const char* url, std::string* file, std::string* id)
+void SceneLoader::splitURL(const std::string& url, std::string* file, std::string* id)
 {
-    if (!url)
+    if (url.empty())
     {
         // This is allowed since many scene node properties do not use the URL.
         return;
     }
 
-    std::string urlString = url;
-
     // Check if the url references a file (otherwise, it only references a node within the main GPB).
-    unsigned int loc = urlString.rfind(".");
-    if (loc != urlString.npos)
+    unsigned int loc = url.rfind(".");
+    if (loc != url.npos)
     {
         // If the url references a specific namespace within the file,
         // set the id out parameter appropriately. Otherwise, set the id out
         // parameter to the empty string so we know to load the first namespace.
-        loc = urlString.rfind("#");
-        if (loc != urlString.npos)
+        loc = url.rfind("#");
+        if (loc != url.npos)
         {
-            *file = urlString.substr(0, loc);
-            *id = urlString.substr(loc + 1);
+            *file = url.substr(0, loc);
+            *id = url.substr(loc + 1);
         }
         else
         {

+ 17 - 21
gameplay/src/SceneLoader.h

@@ -33,13 +33,12 @@ private:
      */
     struct SceneAnimation
     {
-        SceneAnimation(const char* animationID, const char* targetID, std::string file, std::string id)
-            : _animationID(animationID), _targetID(targetID), _file(file), _id(id) {}
+        SceneAnimation(const char* animationID, const char* targetID, std::string url)
+            : _animationID(animationID), _targetID(targetID), _url(url) {}
 
         const char* _animationID;
         const char* _targetID;
-        std::string _file;
-        std::string _id;
+        std::string _url;
     };
 
     struct SceneNodeProperty
@@ -49,31 +48,27 @@ private:
             AUDIO = 1,
             MATERIAL = 2,
             PARTICLE = 4,
-            CHARACTER = 8,
-            GHOSTOBJECT = 16,
-            RIGIDBODY = 32,
-            TRANSLATE = 64,
-            ROTATE = 128,
-            SCALE = 256,
-            URL = 512,
-            TRANSPARENT = 1024,
-            DYNAMIC = 2048
+            COLLISION_OBJECT = 8,
+            TRANSLATE = 16,
+            ROTATE = 32,
+            SCALE = 64,
+            URL = 128,
+            TRANSPARENT = 256,
+            DYNAMIC = 512
         };
 
-        SceneNodeProperty(Type type, std::string file, std::string id, int index) : _type(type), _file(file), _id(id), _index(index) { }
+        SceneNodeProperty(Type type, std::string url, int index) : _type(type), _url(url), _index(index) { }
 
         Type _type;
-        std::string _file;
-        std::string _id;
+        std::string _url;
         int _index;
     };
 
     struct SceneNode
     {
-        SceneNode() : _nodeID(""), _exactMatch(true) { }
+        SceneNode() : _nodeID("") { }
 
         const char* _nodeID;
-        bool _exactMatch;
         std::vector<SceneNodeProperty> _properties;
     };
 
@@ -107,13 +102,14 @@ private:
 
     static PhysicsConstraint* loadSpringConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
 
-    static void splitURL(const char* url, std::string* file, std::string* id);
+    static void splitURL(const std::string& url, std::string* file, std::string* id);
     
     
-    static std::map<std::string, Properties*> _propertiesFromFile;      // Holds the properties object for a given file path.
+    static std::map<std::string, Properties*> _properties;              // Holds the properties object for a given URL.
     static std::vector<SceneAnimation> _animations;                     // Holds the animations declared in the .scene file.
     static std::vector<SceneNode> _sceneNodes;                          // Holds all the nodes+properties declared in the .scene file.
-    static std::string _path;                                           // The path of the main GPB for the scene being loaded.
+    static std::string _gpbPath;                                        // The path of the main GPB for the scene being loaded.
+    static std::string _path;                                           // The path of the scene file being loaded.
 };
 
 }