Pārlūkot izejas kodu

Changed character sample to use custom sandbox scene instead of seymour (still in progress).
Updates to SceneLoader to support wildcards on node URLs for matching multiple nodes in a scene (via asterisk at end of URL).
Other general cleanup in SceneLoader.
Changed and cleaned up how rigid body meshes are created (they are now loaded from GPB during creation).
Added default value for font size in Font::drawText.
Added some additional supported material parameter binding constants.
Fixed issue with defines when loading materials from properties files.
Fixed issue with different transform spaces being used in specular calculations in diffuse-specular shaders (other shaders still need to be updated similarly).

Steve Grenier 14 gadi atpakaļ
vecāks
revīzija
cb4d6cead0

+ 1 - 1
gameplay-encoder/gameplay-binary.txt

@@ -198,9 +198,9 @@ Reference
 34->Mesh
                 vertexFormat            VertexElement[] { enum VertexUsage usage, unint size }
                 vertices                byte[]
-                parts                   MeshPart[]
                 boundingBox             BoundingBox { float[3] min, float[3] max }
                 boundingSphere          BoundingSphere { float[3] center, float radius }
+                parts                   MeshPart[]
 ------------------------------------------------------------------------------------------------------
 35->MeshPart
                 primitiveType           enum PrimitiveType

+ 13 - 4
gameplay/res/shaders/colored-specular.fsh

@@ -5,12 +5,17 @@ precision highp float;
 // Uniforms
 uniform vec3 u_lightColor;                      // Light color
 uniform vec3 u_ambientColor;                    // Ambient color
-uniform float u_specularExponent;               // Specular exponent or shininess property.
+uniform float u_specularExponent;               // Specular exponent or shininess property
+#if !defined(VERTEX_COLOR)
 uniform vec4 u_diffuseColor;                    // Diffuse color
+#endif
 
 // Inputs
-varying vec3 v_normalVector;                    // NormalVector in view space.
+varying vec3 v_normalVector;                    // NormalVector in view space
 varying vec3 v_cameraDirection;                 // Camera direction
+#if defined(VERTEX_COLOR)
+varying vec4 v_color;							// Vertex color
+#endif
 
 // Global variables
 vec4 _baseColor;                                // Base color
@@ -103,8 +108,12 @@ void applyLight()
 
 void main()
 {
-    // Fetch diffuse color from texture.
-    _baseColor = u_diffuseColor;
+    // Set base diffuse color
+#if defined(VERTEX_COLOR)
+	_baseColor = v_color;
+#else
+	_baseColor = u_diffuseColor;
+#endif
 
     // Apply light
     applyLight();

+ 11 - 2
gameplay/res/shaders/colored-specular.vsh

@@ -7,11 +7,16 @@ uniform vec3 u_cameraPosition;                      // Position of the camera.
 // Inputs
 attribute vec4 a_position;                          // Vertex Position (x, y, z, w)
 attribute vec3 a_normal;                            // Vertex Normal (x, y, z)
-attribute vec2 a_texCoord;                          // Vertex Texture Coordinate (u, v)
+#if defined(VERTEX_COLOR)
+attribute vec4 a_color;
+#endif
 
 // Outputs
-varying vec3 v_normalVector;                        // NormalVector in view space.
+varying vec3 v_normalVector;                        // NormalVector in view space
 varying vec3 v_cameraDirection;                     // Camera direction
+#if defined(VERTEX_COLOR)
+varying vec4 v_color;								// Vertex color
+#endif
 
 #if defined(SKINNING)
 
@@ -189,6 +194,10 @@ void main()
     vec4 positionWorldSpace = u_worldMatrix * position;
     v_cameraDirection = u_cameraPosition - positionWorldSpace.xyz;
 
+#if defined(VERTEX_COLOR)
+	v_color = a_color;
+#endif
+
     // Apply light.
     applyLight(position);
 }

+ 1 - 1
gameplay/res/shaders/diffuse-specular.fsh

@@ -30,7 +30,7 @@ void lighting(vec3 normalVector, vec3 cameraDirection, vec3 lightDirection, floa
     _diffuseColor = u_lightColor * _baseColor.rgb * diffuseIntensity;
 
     // Specular
-    vec3 halfVector = normalize(cameraDirection + lightDirection);
+    vec3 halfVector = normalize(lightDirection + cameraDirection);
     float specularIntensity = attenuation * max(0.0, pow(dot(normalVector, halfVector), u_specularExponent));
     specularIntensity = max(0.0, specularIntensity);
     _specularColor = u_lightColor * _baseColor.rgb * specularIntensity;

+ 9 - 11
gameplay/res/shaders/diffuse-specular.vsh

@@ -1,8 +1,8 @@
 // Uniforms
 uniform mat4 u_worldViewProjectionMatrix;           // Matrix to transform a position to clip space.
 uniform mat4 u_inverseTransposeWorldViewMatrix;     // Matrix to transform a normal to view space.
-uniform mat4 u_worldMatrix;                         // Matrix to tranform a position to world space.
-uniform vec3 u_cameraPosition;                      // Position of the camera.
+uniform mat4 u_worldViewMatrix;                     // Matrix to tranform a position to view space.
+uniform vec3 u_cameraPosition;                      // Position of the camera in view space.
 
 // Inputs
 attribute vec4 a_position;                          // Vertex Position (x, y, z, w)
@@ -117,7 +117,6 @@ vec3 getNormal()
 
 #if defined(POINT_LIGHT)
 
-uniform mat4 u_worldViewMatrix;                         // Matrix to tranform a position to view space.
 uniform vec3 u_pointLightPosition;                      // Position
 uniform float u_pointLightRangeInverse;                 // Inverse of light range.
 varying vec4 v_vertexToPointLightDirection;             // Light direction w.r.t current vertex.
@@ -141,7 +140,6 @@ void applyLight(vec4 position)
 
 #elif defined(SPOT_LIGHT)
 
-uniform mat4 u_worldViewMatrix;                         // Matrix to tranform a position to view space.
 uniform vec3 u_spotLightPosition;                       // Position
 uniform float u_spotLightRangeInverse;                  // Inverse of light range.
 varying vec3 v_vertexToSpotLightDirection;              // Light direction w.r.t current vertex.
@@ -179,18 +177,18 @@ void main()
     gl_Position = u_worldViewProjectionMatrix * position;
 
     // Transform normal to view space.
-    mat3 inverseTransposeWorldViewMatrix = mat3(u_inverseTransposeWorldViewMatrix[0].xyz,
-                                                u_inverseTransposeWorldViewMatrix[1].xyz,
-                                                u_inverseTransposeWorldViewMatrix[2].xyz);
-    v_normalVector = inverseTransposeWorldViewMatrix * normal;
+    mat3 normalMatrix = mat3(u_inverseTransposeWorldViewMatrix[0].xyz,
+                             u_inverseTransposeWorldViewMatrix[1].xyz,
+                             u_inverseTransposeWorldViewMatrix[2].xyz);
+    v_normalVector = normalMatrix * normal;
 
     // Compute the camera direction.
-    vec4 positionWorldSpace = u_worldMatrix * position;
+    vec4 positionWorldSpace = u_worldViewMatrix * position;
     v_cameraDirection = u_cameraPosition - positionWorldSpace.xyz;
 
     // Apply light.
     applyLight(position);
 
-    // Pass on the texture coordinates to Fragment shader.
+	// Pass on the texture coordinates to Fragment shader.
     v_texCoord = a_texCoord;
-}
+}

+ 4 - 0
gameplay/src/Font.cpp

@@ -176,6 +176,8 @@ void Font::begin()
 
 void Font::drawText(const char* text, int x, int y, const Vector4& color, unsigned int size, bool rightToLeft)
 {
+    if (size == 0)
+        size = _size;
     float scale = (float)size / _size;
     char* cursor = NULL;
     if (rightToLeft)
@@ -288,6 +290,8 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
 
 void Font::drawText(const char* text, const Rectangle& area, const Vector4& color, unsigned int size, Justify justify, bool wrap, bool rightToLeft)
 {
+    if (size == 0)
+        size = _size;
     float scale = (float)size / _size;
     char* token = const_cast<char*>(text);
     const int length = strlen(text);

+ 5 - 5
gameplay/src/Font.h

@@ -131,9 +131,9 @@ public:
      * @param x The viewport x position to draw text at.
      * @param y The viewport y position to draw text at.
      * @param color The color of text.
-     * @param size The size to draw text.
+     * @param size The size to draw text (0 for default size).
      */
-    void drawText(const char* text, int x, int y, const Vector4& color, unsigned int size, bool rightToLeft = false);
+    void drawText(const char* text, int x, int y, const Vector4& color, unsigned int size = 0, bool rightToLeft = false);
 
     /**
      * Draws the specified text within a rectangular area, with a specified alignment and scale.
@@ -142,13 +142,13 @@ public:
      * @param text The text to draw.
      * @param clip The viewport area to draw within.  Text will be clipped outside this rectangle.
      * @param color The color of text.
-     * @param size The size to draw text.
+     * @param size The size to draw text (0 for default size).
      * @param justify Justification of text within the viewport.
      * @param wrap Wraps text to fit within the width of the viewport if true.
      * @param rightToLeft
      */
-    void drawText(const char* text, const Rectangle& clip, const Vector4& color, unsigned int size,
-                  Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
+    void drawText(const char* text, const Rectangle& clip, const Vector4& color,
+        unsigned int size = 0, Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
 
     /**
      * Measures a string's width and height without alignment, wrapping or clipping.

+ 7 - 7
gameplay/src/Material.cpp

@@ -212,17 +212,17 @@ bool Material::loadPass(Technique* technique, Properties* passProperties)
     const char* fragmentShaderPath = passProperties->getString("fragmentShader");
     assert(fragmentShaderPath);
     const char* defines = passProperties->getString("defines");
-    std::string define = "";
+    std::string define;
     if (defines != NULL)
     {
-        char* token = strtok((char*)defines, ";");
-        while (token)
+        define = defines;
+        define.insert(0, "#define ");
+        unsigned int pos;
+        while ((pos = define.find(';')) != std::string::npos)
         {
-            define += "#define ";
-            define += token;
-            define += "\n";
-            token = strtok(NULL, ";");
+            define.replace(pos, 1, "\n#define ");
         }
+        define += "\n";
     }
 
     // Create the pass

+ 5 - 0
gameplay/src/Mesh.cpp

@@ -235,6 +235,11 @@ Mesh* Mesh::createBoundingBox(const BoundingBox& box)
     return mesh;
 }
 
+const char* Mesh::getUrl() const
+{
+    return _url.c_str();
+}
+
 const VertexFormat& Mesh::getVertexFormat() const
 {
     return _vertexFormat;

+ 10 - 0
gameplay/src/Mesh.h

@@ -123,6 +123,15 @@ public:
      */
     static Mesh* createBoundingBox(const BoundingBox& box);
 
+    /**
+     * Returns a URL from which the mesh was loaded from.
+     *
+     * For meshes loaded from a Package, this URL will point
+     * to the file and ID of the mesh within the package. For
+     * all other meshes, an empty string will be returned.
+     */
+    const char* getUrl() const;
+
     /**
      * Gets the vertex format for the mesh.
      *
@@ -295,6 +304,7 @@ private:
      */
     Mesh(const Mesh& copy);
 
+    std::string _url;
     const VertexFormat _vertexFormat;
     unsigned int _vertexCount;
     VertexBufferHandle _vertexBuffer;

+ 8 - 3
gameplay/src/Model.cpp

@@ -190,6 +190,11 @@ Material* Model::setMaterial(const char* materialPath, int partIndex)
     return material;
 }
 
+bool Model::hasPartMaterial(unsigned int partIndex) const
+{
+    return (partIndex >= 0 && partIndex < _partCount && _partMaterials && _partMaterials[partIndex]);
+}
+
 MeshSkin* Model::getSkin()
 {
     return _skin;
@@ -276,15 +281,15 @@ void Model::draw(bool wireframe)
             MeshPart* part = _mesh->getPart(i);
 
             // Get the material for this mesh part.
-            Material* material;
-            if (_partMaterials && i < _partCount && _partMaterials[i])
+            Material* material = getMaterial(i);
+            /*if (_partMaterials && i < _partCount && _partMaterials[i])
             {
                 material = _partMaterials[i]; // Use part material
             }
             else
             {
                 material = _material; // Use shared material
-            }
+            }*/
 
             if (material)
             {

+ 11 - 2
gameplay/src/Model.h

@@ -109,6 +109,15 @@ public:
      */
     Material* setMaterial(const char* materialPath, int partIndex = -1);
 
+    /**
+     * Determines if a custom (non-shared) material is set for the specified part index.
+     *
+     * @param partIndex MeshPart index.
+     *
+     * @return True if a custom MeshPart material is set for the specified index, false otherwise.
+     */
+    bool hasPartMaterial(unsigned int partIndex) const;
+
     /**
      * Returns the MeshSkin.
      * 
@@ -146,13 +155,13 @@ private:
      * Destructor. Hidden use release() instead.
      */
     ~Model();
-
+
     /**
      * Sets the MeshSkin for this model.
      * 
      * @param skin The MeshSkin for this model.
      */
-    void setSkin(MeshSkin* skin);
+    void setSkin(MeshSkin* skin);
 
     /**
      * Sets the node that is associated with this model.

+ 31 - 7
gameplay/src/Node.cpp

@@ -310,7 +310,7 @@ const Matrix& Node::getWorldMatrix() const
 const Matrix& Node::getWorldViewMatrix() const
 {
     static Matrix worldView;
-    
+
     Matrix::multiply(getViewMatrix(), getWorldMatrix(), &worldView);
 
     return worldView;
@@ -319,18 +319,21 @@ const Matrix& Node::getWorldViewMatrix() const
 const Matrix& Node::getInverseTransposeWorldViewMatrix() const
 {
     static Matrix invTransWorldView;
-
-    // Assume the matrix is always dirty since the camera is moving
-    // almost every frame in most games.
-    //    
-    // TODO: Optimize here to NOT calculate the inverse transpose if the matrix is orthogonal.
     Matrix::multiply(getViewMatrix(), getWorldMatrix(), &invTransWorldView);
     invTransWorldView.invert();
     invTransWorldView.transpose();
-
     return invTransWorldView;
 }
 
+const Matrix& Node::getInverseTransposeWorldMatrix() const
+{
+    static Matrix invTransWorld;
+    invTransWorld = getWorldMatrix();
+    invTransWorld.invert();
+    invTransWorld.transpose();
+    return invTransWorld;
+}
+
 const Matrix& Node::getViewMatrix() const
 {
     Scene* scene = getScene();
@@ -437,6 +440,8 @@ Vector3 Node::getForwardVectorView() const
     Vector3 vector;
     getWorldMatrix().getForwardVector(&vector);
     getViewMatrix().transformVector(&vector);
+    //getForwardVector(&vector);
+    //getWorldViewMatrix().transformVector(&vector);
     return vector;
 }
 
@@ -459,6 +464,25 @@ Vector3 Node::getActiveCameraTranslationWorld() const
     return Vector3::zero();
 }
 
+Vector3 Node::getActiveCameraTranslationView() const
+{
+    Scene* scene = getScene();
+    if (scene)
+    {
+        Camera* camera = scene->getActiveCamera();
+        if (camera)
+        {
+            Node* cameraNode = camera->getNode();
+            if (cameraNode)
+            {
+                return cameraNode->getTranslationView();
+            }
+        }
+    }
+
+    return Vector3::zero();
+}
+
 void Node::hierarchyChanged()
 {
     // When our hierarchy changes our world transform is affected, so we must dirty it.

+ 17 - 1
gameplay/src/Node.h

@@ -171,10 +171,19 @@ public:
      */
     const Matrix& getWorldViewMatrix() const;
 
+    /**
+     * Gets the inverse transpose world matrix corresponding to this node.
+     *
+     * This matrix is typically used to transform normal vectors into world space.
+     *
+     * @return The inverse world matrix of this node.
+     */
+    const Matrix& getInverseTransposeWorldMatrix() const;
+
     /**
      * Gets the inverse transpose world view matrix corresponding to this node.
      *
-     * This matrix is typically used to transform normal vectors.
+     * This matrix is typically used to transform normal vectors into view space.
      *
      * @return The inverse world view matrix of this node.
      */
@@ -259,6 +268,13 @@ public:
      */
     Vector3 getActiveCameraTranslationWorld() const;
 
+    /**
+     * Returns the view-space translation vector of the currently active camera for this node's scene.
+     *
+     * @return The translation vector of the scene's active camera, in view-space.
+     */
+    Vector3 getActiveCameraTranslationView() const;
+
     /**
      * Returns the pointer to this node's camera.
      *

+ 151 - 104
gameplay/src/Package.cpp

@@ -3,7 +3,6 @@
 #include "FileSystem.h"
 #include "MeshPart.h"
 #include "Scene.h"
-#include "SceneLoader.h"
 #include "Joint.h"
 
 #define GPB_PACKAGE_VERSION_MAJOR 1
@@ -299,11 +298,6 @@ bool Package::readMatrix(float* m)
 }
 
 Scene* Package::loadScene(const char* id)
-{
-    return loadScene(id, NULL);
-}
-
-Scene* Package::loadScene(const char* id, const std::vector<std::string>* nodesWithMeshRB)
 {
     clearLoadSession();
 
@@ -335,7 +329,7 @@ Scene* Package::loadScene(const char* id, const std::vector<std::string>* nodesW
         // Read each child directly into the scene
         for (unsigned int i = 0; i < childrenCount; i++)
         {
-            Node* node = readNode(scene, NULL, nodesWithMeshRB);
+            Node* node = readNode(scene, NULL);
             if (node)
             {
                 scene->addNode(node);
@@ -397,17 +391,12 @@ Scene* Package::loadScene(const char* id, const std::vector<std::string>* nodesW
 }
 
 Node* Package::loadNode(const char* id)
-{
-    return loadNode(id, false);
-}
-
-Node* Package::loadNode(const char* id, bool loadWithMeshRBSupport)
 {
     assert(id);
 
     clearLoadSession();
 
-    Node* node = loadNode(id, NULL, NULL, loadWithMeshRBSupport);
+    Node* node = loadNode(id, NULL, NULL);
    
     if (node)
     {
@@ -417,7 +406,7 @@ Node* Package::loadNode(const char* id, bool loadWithMeshRBSupport)
     return node;
 }
 
-Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport)
+Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext)
 {
     assert(id);
 
@@ -443,22 +432,13 @@ Node* Package::loadNode(const char* id, Scene* sceneContext, Node* nodeContext,
             return NULL;
         }
 
-        if (loadWithMeshRBSupport)
-        {
-            std::vector<std::string> nodesWithMeshRBSupport;
-            nodesWithMeshRBSupport.push_back(id);
-            node = readNode(sceneContext, nodeContext, &nodesWithMeshRBSupport);
-        }
-        else
-        {
-            node = readNode(sceneContext, nodeContext, NULL);
-        }
+        node = readNode(sceneContext, nodeContext);
     }
 
     return node;
 }
 
-Node* Package::readNode(Scene* sceneContext, Node* nodeContext, const std::vector<std::string>* nodesWithMeshRB)
+Node* Package::readNode(Scene* sceneContext, Node* nodeContext)
 {
     const char* id = getIdFromOffset();
 
@@ -509,7 +489,7 @@ Node* Package::readNode(Scene* sceneContext, Node* nodeContext, const std::vecto
         // Read each child
         for (unsigned int i = 0; i < childrenCount; i++)
         {
-            Node* child = readNode(sceneContext, nodeContext, nodesWithMeshRB);
+            Node* child = readNode(sceneContext, nodeContext);
             if (child)
             {
                 node->addChild(child);
@@ -534,23 +514,8 @@ Node* Package::readNode(Scene* sceneContext, Node* nodeContext, const std::vecto
         SAFE_RELEASE(light);
     }
 
-    // Check if this node's id is in the list of nodes to be loaded with
-    // mesh rigid body support so that when we load the model we keep the proper data.
-    bool loadWithMeshRBSupport = false;
-    if (nodesWithMeshRB)
-    {
-        for (unsigned int i = 0; i < nodesWithMeshRB->size(); i++)
-        {
-            if (strcmp((*nodesWithMeshRB)[i].c_str(), id) == 0)
-            {
-                loadWithMeshRBSupport = true;
-                break;
-            }
-        }
-    }
-
     // Read model
-    Model* model = readModel(sceneContext, nodeContext, loadWithMeshRBSupport, node->getId());
+    Model* model = readModel(sceneContext, nodeContext, node->getId());
     if (model)
     {
         node->setModel(model);
@@ -681,14 +646,14 @@ Light* Package::readLight()
     return light;
 }
 
-Model* Package::readModel(Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport, const char* nodeId)
+Model* Package::readModel(Scene* sceneContext, Node* nodeContext, const char* nodeId)
 {
     // Read mesh
     Mesh* mesh = NULL;
     std::string xref = readString(_file);
     if (xref.length() > 1 && xref[0] == '#') // TODO: Handle full xrefs
     {
-        mesh = loadMesh(xref.c_str() + 1, loadWithMeshRBSupport, nodeId);
+        mesh = loadMesh(xref.c_str() + 1, nodeId);
         if (mesh)
         {
             Model* model = Model::create(mesh);
@@ -816,7 +781,7 @@ void Package::resolveJointReferences(Scene* sceneContext, Node* nodeContext)
             {
                 jointId = jointId.substr(1, jointId.length() - 1);
 
-                Node* n = loadNode(jointId.c_str(), sceneContext, nodeContext, false);
+                Node* n = loadNode(jointId.c_str(), sceneContext, nodeContext);
                 if (n && n->getType() == Node::JOINT)
                 {
                     Joint* joint = static_cast<Joint*>(n);
@@ -992,12 +957,12 @@ Animation* Package::readAnimationChannel(Scene* scene, Animation* animation, con
 
 Mesh* Package::loadMesh(const char* id)
 {
-    return loadMesh(id, false, NULL);
+    return loadMesh(id, false);
 }
 
-Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char* nodeId)
+Mesh* Package::loadMesh(const char* id, const char* nodeId)
 {
-    // save the file position
+    // Save the file position
     long position = ftell(_file);
 
     // Seek to the specified Mesh
@@ -1007,6 +972,56 @@ Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char*
         return NULL;
     }
 
+    // Read mesh data
+    MeshData* meshData = readMeshData();
+    if (meshData == NULL)
+    {
+        return NULL;
+    }
+
+    // Create Mesh
+    Mesh* mesh = Mesh::createMesh(meshData->vertexFormat, meshData->vertexCount, false);
+    if (mesh == NULL)
+    {
+        LOG_ERROR_VARG("Failed to create mesh: %s", id);
+        SAFE_DELETE_ARRAY(meshData);
+        return NULL;
+    }
+
+    mesh->_url = _path;
+    mesh->_url += "#";
+    mesh->_url += id;
+
+    mesh->setVertexData(meshData->vertexData, 0, meshData->vertexCount);
+
+    mesh->_boundingBox.set(meshData->boundingBox);
+    mesh->_boundingSphere.set(meshData->boundingSphere);
+
+    // Create mesh parts
+    for (unsigned int i = 0; i < meshData->parts.size(); ++i)
+    {
+        MeshPartData* partData = meshData->parts[i];
+
+        MeshPart* part = mesh->addPart(partData->primitiveType, partData->indexFormat, partData->indexCount, false);
+        if (part == NULL)
+        {
+            LOG_ERROR_VARG("Failed to create mesh part (i=%d): %s", i, id);
+            SAFE_DELETE(meshData);
+            return NULL;
+        }
+        part->setIndexData(partData->indexData, 0, partData->indexCount);
+    }
+
+    SAFE_DELETE(meshData);
+
+    // Restore file pointer
+    fseek(_file, position, SEEK_SET);
+
+    return mesh;
+}
+
+Package::MeshData* Package::readMeshData()
+{
     // Read vertex format/elements
     unsigned int vertexElementCount;
     if (fread(&vertexElementCount, 4, 1, _file) != 1 || vertexElementCount < 1)
@@ -1027,60 +1042,42 @@ Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char*
         vertexElements[i].size = vSize;
     }
 
-    // Create VertexFormat
-    VertexFormat vertexFormat(vertexElements, vertexElementCount);
+    MeshData* meshData = new MeshData(VertexFormat(vertexElements, vertexElementCount));
+
     SAFE_DELETE_ARRAY(vertexElements);
 
     // Read vertex data
     unsigned int vertexByteCount;
     if (fread(&vertexByteCount, 4, 1, _file) != 1 || vertexByteCount == 0)
     {
+        SAFE_DELETE(meshData);
         return NULL;
     }
-    unsigned char* vertexData = new unsigned char[vertexByteCount];
-    if (fread(vertexData, 1, vertexByteCount, _file) != vertexByteCount)
+    meshData->vertexCount = vertexByteCount / meshData->vertexFormat.getVertexSize();
+    meshData->vertexData = new unsigned char[vertexByteCount];
+    if (fread(meshData->vertexData, 1, vertexByteCount, _file) != vertexByteCount)
     {
-        LOG_ERROR_VARG("Failed to read %d vertex data bytes for mesh: %s", vertexByteCount, id);
+        SAFE_DELETE(meshData);
         return NULL;
     }
 
     // Read mesh bounds (bounding box and bounding sphere)
-    Vector3 boundsMin, boundsMax, boundsCenter;
-    float boundsRadius = 0.0f;
-    if (fread(&boundsMin.x, 4, 3, _file) != 3 || fread(&boundsMax.x, 4, 3, _file) != 3)
-    {
-        LOG_ERROR_VARG("Failed to read bounding box for mesh: %s", id);
-        return NULL;
-    }
-    if (fread(&boundsCenter.x, 4, 3, _file) != 3 || fread(&boundsRadius, 4, 1, _file) != 1)
+    if (fread(&meshData->boundingBox.min.x, 4, 3, _file) != 3 || fread(&meshData->boundingBox.max.x, 4, 3, _file) != 3)
     {
-        LOG_ERROR_VARG("Failed to read bounding sphere for mesh: %s", id);
+        SAFE_DELETE(meshData);
         return NULL;
     }
-
-    // Create Mesh
-    int vertexCount = vertexByteCount / vertexFormat.getVertexSize();
-    Mesh* mesh = Mesh::createMesh(vertexFormat, vertexCount, false);
-    if (mesh == NULL)
+    if (fread(&meshData->boundingSphere.center.x, 4, 3, _file) != 3 || fread(&meshData->boundingSphere.radius, 4, 1, _file) != 1)
     {
-        LOG_ERROR_VARG("Failed to create mesh: %s", id);
-        SAFE_DELETE_ARRAY(vertexData);
+        SAFE_DELETE(meshData);
         return NULL;
     }
-    mesh->setVertexData(vertexData, 0, vertexCount);
-    if (loadWithMeshRBSupport)
-        SceneLoader::addMeshRigidBodyData(nodeId, mesh, vertexData, vertexByteCount);
-    SAFE_DELETE_ARRAY(vertexData);
-
-    // Set mesh bounding volumes
-    mesh->_boundingBox.set(boundsMin, boundsMax);
-    mesh->_boundingSphere.set(boundsCenter, boundsRadius);
 
     // Read mesh parts
     unsigned int meshPartCount;
     if (fread(&meshPartCount, 4, 1, _file) != 1)
     {
-        SAFE_RELEASE(mesh);
+        SAFE_DELETE(meshData);
         return NULL;
     }
     for (unsigned int i = 0; i < meshPartCount; ++i)
@@ -1091,23 +1088,18 @@ Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char*
             fread(&iFormat, 4, 1, _file) != 1 ||
             fread(&iByteCount, 4, 1, _file) != 1)
         {
-            LOG_ERROR_VARG("Failed to read mesh part (i=%d): %s", i, id);
-            SAFE_RELEASE(mesh);
+            SAFE_DELETE(meshData);
             return NULL;
         }
 
-        unsigned char* indexData = new unsigned char[iByteCount];
-        if (fread(indexData, 1, iByteCount, _file) != iByteCount)
-        {
-            LOG_ERROR_VARG("Failed to read %d index data bytes for mesh part (i=%d): %s", iByteCount, i, id);
-            SAFE_DELETE_ARRAY(indexData);
-            SAFE_RELEASE(mesh);
-            return NULL;
-        }
+        MeshPartData* partData = new MeshPartData();
+        meshData->parts.push_back(partData);
 
-        Mesh::IndexFormat indexFormat = (Mesh::IndexFormat)iFormat;
+        partData->primitiveType = (Mesh::PrimitiveType)pType;
+        partData->indexFormat = (Mesh::IndexFormat)iFormat;
+        
         unsigned int indexSize = 0;
-        switch (indexFormat)
+        switch (partData->indexFormat)
         {
         case Mesh::INDEX8:
             indexSize = 1;
@@ -1119,23 +1111,53 @@ Mesh* Package::loadMesh(const char* id, bool loadWithMeshRBSupport, const char*
             indexSize = 4;
             break;
         }
-        unsigned int indexCount = iByteCount / indexSize;
-        MeshPart* part = mesh->addPart((Mesh::PrimitiveType)pType, indexFormat, indexCount, false);
-        if (part == NULL)
+
+        partData->indexCount = iByteCount / indexSize;
+
+        partData->indexData = new unsigned char[iByteCount];
+        if (fread(partData->indexData, 1, iByteCount, _file) != iByteCount)
         {
-            LOG_ERROR_VARG("Failed to create mesh part (i=%d): %s", i, id);
-            SAFE_DELETE_ARRAY(indexData);
-            SAFE_RELEASE(mesh);
+            SAFE_DELETE(meshData);
             return NULL;
         }
-        part->setIndexData(indexData, 0, indexCount);
-        if (loadWithMeshRBSupport)
-            SceneLoader::addMeshRigidBodyData(nodeId, indexData, iByteCount);
-        SAFE_DELETE_ARRAY(indexData);
     }
 
-    fseek(_file, position, SEEK_SET);
-    return mesh;
+    return meshData;
+}
+
+Package::MeshData* Package::readMeshData(const char* url)
+{
+    assert(url);
+
+    unsigned int len = strlen(url);
+    if (len == 0)
+        return NULL;
+
+    // Parse URL (formatted as 'package#id')
+    std::string urlstring(url);
+    unsigned int pos = urlstring.find('#');
+    if (pos == std::string::npos)
+        return NULL;
+
+    std::string file = urlstring.substr(0, pos);
+    std::string id = urlstring.substr(pos + 1);
+
+    // Load package
+    Package* pkg = Package::create(file.c_str());
+    if (pkg == NULL)
+        return NULL;
+
+    // Seek to mesh with specified ID in package
+    Reference* ref = pkg->seekTo(id.c_str(), PACKAGE_TYPE_MESH);
+    if (ref == NULL)
+        return NULL;
+
+    // Read mesh data from current file position
+    MeshData* meshData = pkg->readMeshData();
+
+    SAFE_RELEASE(pkg);
+
+    return meshData;
 }
 
 Font* Package::loadFont(const char* id)
@@ -1265,8 +1287,8 @@ const char* Package::getObjectID(unsigned int index) const
     return (index >= _referenceCount ? NULL : _references[index].id.c_str());
 }
 
-Package::Reference::Reference() :
-    type(0), offset(0)
+Package::Reference::Reference()
+    : type(0), offset(0)
 {
 }
 
@@ -1274,4 +1296,29 @@ Package::Reference::~Reference()
 {
 }
 
+Package::MeshPartData::MeshPartData() :
+    indexCount(0), indexData(NULL)
+{
+}
+
+Package::MeshPartData::~MeshPartData()
+{
+    SAFE_DELETE_ARRAY(indexData);
+}
+
+Package::MeshData::MeshData(const VertexFormat& vertexFormat)
+    : vertexFormat(vertexFormat), vertexCount(0), vertexData(NULL)
+{
+}
+
+Package::MeshData::~MeshData()
+{
+    SAFE_DELETE_ARRAY(vertexData);
+
+    for (unsigned int i = 0; i < parts.size(); ++i)
+    {
+        SAFE_DELETE(parts[i]);
+    }
+}
+
 }

+ 49 - 29
gameplay/src/Package.h

@@ -15,7 +15,7 @@ namespace gameplay
  */
 class Package : public Ref
 {
-    friend class SceneLoader;
+    friend class PhysicsController;
 
 public:
 
@@ -118,6 +118,31 @@ private:
         std::vector<Matrix> inverseBindPoseMatrices;
     };
 
+    struct MeshPartData
+    {
+        MeshPartData();
+        ~MeshPartData();
+
+        Mesh::PrimitiveType primitiveType;
+        Mesh::IndexFormat indexFormat;
+        unsigned int indexCount;
+        unsigned char* indexData;
+    };
+
+    struct MeshData
+    {
+        MeshData(const VertexFormat& vertexFormat);
+        ~MeshData();
+
+        VertexFormat vertexFormat;
+        unsigned int vertexCount;
+        unsigned char* vertexData;
+        BoundingBox boundingBox;
+        BoundingSphere boundingSphere;
+        Mesh::PrimitiveType primitiveType;
+        std::vector<MeshPartData*> parts;
+    };
+
     Package(const char* path);
 
     /**
@@ -173,46 +198,22 @@ private:
      */
     Reference* seekToFirstType(unsigned int type);
 
-    /**
-     * Loads the scene with the specified ID from the package, and loads the specified nodes with mesh rigid body support.
-     * If id is NULL then the first scene found is loaded.
-     * 
-     * @param id The ID of the scene to load (NULL to load the first scene).
-     * @param nodesWithMeshRB A list of the IDs of the nodes within the scene that 
-     *      should be loaded with support for triangle mesh rigid bodies.
-     * 
-     * @return The loaded scene, or NULL if the scene could not be loaded.
-     */
-    Scene* loadScene(const char* id, const std::vector<std::string>* nodesWithMeshRB);
-
-    /**
-     * Loads a node with the specified ID from the package, optionally with mesh rigid body support.
-     *
-     * @param id The ID of the node to load in the package.
-     * @param loadWithMeshRBSupport Whether or not to load the node with mesh rigid body support.
-     * 
-     * @return The loaded node, or NULL if the node could not be loaded.
-     */
-    Node* loadNode(const char* id, bool loadWithMeshRBSupport);
-
     /**
      * Internal method to load a node.
      *
      * Only one of node or scene should be passed as non-NULL (or neither).
      */
-    Node* loadNode(const char* id, Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport);
+    Node* loadNode(const char* id, Scene* sceneContext, Node* nodeContext);
 
     /**
      * Loads a mesh with the specified ID from the package.
      *
      * @param id The ID of the mesh to load.
-     * @param loadWithMeshRBSupport Whether to load the mesh with 
-     *      support for triangle mesh rigid bodies or not.
      * @param nodeId The id of the mesh's model's parent node.
      * 
      * @return The loaded mesh, or NULL if the mesh could not be loaded.
      */
-    Mesh* loadMesh(const char* id, bool loadWithMeshRBSupport, const char* nodeId);
+    Mesh* loadMesh(const char* id, const char* nodeId);
 
     /**
      * Reads an unsigned int from the current file position.
@@ -287,7 +288,7 @@ private:
      * 
      * @return A pointer to new node or NULL if there was an error.
      */
-    Node* readNode(Scene* sceneContext, Node* nodeContext, const std::vector<std::string>* nodesWithMeshRB);
+    Node* readNode(Scene* sceneContext, Node* nodeContext);
 
     /**
      * Reads a camera from the current file position.
@@ -308,7 +309,25 @@ private:
      * 
      * @return A pointer to a new model or NULL if there was an error.
      */
-    Model* readModel(Scene* sceneContext, Node* nodeContext, bool loadWithMeshRBSupport, const char* nodeId);
+    Model* readModel(Scene* sceneContext, Node* nodeContext, const char* nodeId);
+
+    /**
+     * Reads mesh data from the current file position.
+     */
+    MeshData* readMeshData();
+
+    /**
+     * Reads mesh data for the specified URL.
+     *
+     * The specified URL should be formatted as 'package#id', where
+     * 'package' is the package file containing the mesh and 'id' is the ID
+     * of the mesh to read data for.
+     *
+     * @param url The URL to read mesh data from.
+     *
+     * @return The mesh rigid body data.
+     */
+    static MeshData* readMeshData(const char* url);
 
     /**
      * Reads a mesh skin from the current file position.
@@ -356,6 +375,7 @@ private:
     void resolveJointReferences(Scene* sceneContext, Node* nodeContext);
 
 private:
+
     std::string _path;
     unsigned int _referenceCount;
     Reference* _references;

+ 56 - 25
gameplay/src/PhysicsController.cpp

@@ -3,7 +3,7 @@
 #include "MeshPart.h"
 #include "PhysicsController.h"
 #include "PhysicsMotionState.h"
-#include "SceneLoader.h"
+#include "Package.h"
 
 // The initial capacity of the Bullet debug drawer's vertex batch.
 #define INITIAL_CAPACITY 280
@@ -403,23 +403,51 @@ btCollisionShape* PhysicsController::createSphere(float radius, const btVector3&
     // Create the sphere shape and add it to the cache.
     btSphereShape* sphere = bullet_new<btSphereShape>(uniformScale * radius);
     _shapes.push_back(new PhysicsCollisionShape(sphere));
-    
+
     return sphere;
 }
 
 btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
 {
-    // Retrieve the mesh rigid body data from the loaded scene.
-    const SceneLoader::MeshRigidBodyData* data = SceneLoader::getMeshRigidBodyData(body->_node->getId());
+    assert(body);
+
+    // Retrieve the mesh rigid body data from the node's mesh.
+    Model* model = body->_node ? body->_node->getModel() : NULL;
+    Mesh* mesh = model ? model->getMesh() : NULL;
+    if (mesh == NULL)
+    {
+        LOG_ERROR("Cannot create mesh rigid body for node without model/mesh.");
+        return NULL;
+    }
+
+    // Only support meshes with triangle list primitive types
+    if (mesh->getPrimitiveType() != Mesh::TRIANGLES)
+    {
+        LOG_ERROR("Cannot create mesh rigid body for mesh without TRIANGLES primitive type.");
+        return NULL;
+    }
+
+    // The mesh must have a valid URL (i.e. it must have been loaded from a Package)
+    // in order to fetch mesh data for computing mesh rigid body.
+    if (strlen(mesh->getUrl()) == 0)
+    {
+        LOG_ERROR("Cannot create mesh rigid body for mesh without valid URL.");
+        return NULL;
+    }
+
+    Package::MeshData* data = Package::readMeshData(mesh->getUrl());
+    if (data == NULL)
+    {
+        return NULL;
+    }
 
     // Copy the scaled vertex position data to the rigid body's local buffer.
     Matrix m;
     Matrix::createScale(body->_node->getScaleX(), body->_node->getScaleY(), body->_node->getScaleZ(), &m);
-    unsigned int vertexCount = data->mesh->getVertexCount();
-    body->_vertexData = new float[vertexCount * 3];
+    body->_vertexData = new float[data->vertexCount * 3];
     Vector3 v;
-    int vertexStride = data->mesh->getVertexFormat().getVertexSize();
-    for (unsigned int i = 0; i < vertexCount; i++)
+    int vertexStride = data->vertexFormat.getVertexSize();
+    for (unsigned int i = 0; i < data->vertexCount; i++)
     {
         v.set(*((float*)&data->vertexData[i * vertexStride + 0 * sizeof(float)]),
               *((float*)&data->vertexData[i * vertexStride + 1 * sizeof(float)]),
@@ -427,19 +455,20 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
         v *= m;
         memcpy(&(body->_vertexData[i * 3]), &v, sizeof(float) * 3);
     }
-    
+
     btTriangleIndexVertexArray* meshInterface = bullet_new<btTriangleIndexVertexArray>();
 
-    if (data->mesh->getPartCount() > 0)
+    unsigned int partCount = data->parts.size();
+    if (partCount > 0)
     {
         PHY_ScalarType indexType = PHY_UCHAR;
         int indexStride = 0;
-        MeshPart* meshPart = NULL;
-        for (unsigned int i = 0; i < data->mesh->getPartCount(); i++)
+        Package::MeshPartData* meshPart = NULL;
+        for (unsigned int i = 0; i < partCount; i++)
         {
-            meshPart = data->mesh->getPart(i);
+            meshPart = data->parts[i];
 
-            switch (meshPart->getIndexFormat())
+            switch (meshPart->indexFormat)
             {
             case Mesh::INDEX8:
                 indexType = PHY_UCHAR;
@@ -455,17 +484,16 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
                 break;
             }
 
-            // Copy the index data to the rigid body's local buffer.
-            unsigned int indexDataSize = meshPart->getIndexCount() * indexStride;
-            unsigned char* indexData = new unsigned char[indexDataSize];
-            memcpy(indexData, data->indexData[i], indexDataSize);
-            body->_indexData.push_back(indexData);
+            // Move the index data into the rigid body's local buffer.
+            // Set it to NULL in the MeshPartData so it is not released when the data is freed.
+            body->_indexData.push_back(meshPart->indexData);
+            meshPart->indexData = NULL;
 
             // Create a btIndexedMesh object for the current mesh part.
             btIndexedMesh indexedMesh;
             indexedMesh.m_indexType = indexType;
-            indexedMesh.m_numTriangles = meshPart->getIndexCount() / 3;
-            indexedMesh.m_numVertices = meshPart->getIndexCount();
+            indexedMesh.m_numTriangles = meshPart->indexCount / 3; // assume TRIANGLES primitive type
+            indexedMesh.m_numVertices = meshPart->indexCount;
             indexedMesh.m_triangleIndexBase = (const unsigned char*)body->_indexData[i];
             indexedMesh.m_triangleIndexStride = indexStride;
             indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
@@ -479,8 +507,8 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
     else
     {
         // Generate index data for the mesh locally in the rigid body.
-        unsigned int* indexData = new unsigned int[data->mesh->getVertexCount()];
-        for (unsigned int i = 0; i < data->mesh->getVertexCount(); i++)
+        unsigned int* indexData = new unsigned int[data->vertexCount];
+        for (unsigned int i = 0; i < data->vertexCount; i++)
         {
             indexData[i] = i;
         }
@@ -489,8 +517,8 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
         // Create a single btIndexedMesh object for the mesh interface.
         btIndexedMesh indexedMesh;
         indexedMesh.m_indexType = PHY_INTEGER;
-        indexedMesh.m_numTriangles = data->mesh->getVertexCount() / 3;
-        indexedMesh.m_numVertices = data->mesh->getVertexCount();
+        indexedMesh.m_numTriangles = data->vertexCount / 3; // assume TRIANGLES primitive type
+        indexedMesh.m_numVertices = data->vertexCount;
         indexedMesh.m_triangleIndexBase = body->_indexData[0];
         indexedMesh.m_triangleIndexStride = sizeof(unsigned int);
         indexedMesh.m_vertexBase = (const unsigned char*)body->_vertexData;
@@ -504,6 +532,9 @@ btCollisionShape* PhysicsController::createMesh(PhysicsRigidBody* body)
     btBvhTriangleMeshShape* shape = bullet_new<btBvhTriangleMeshShape>(meshInterface, true);
     _shapes.push_back(new PhysicsCollisionShape(shape));
 
+    // Free the temporary mesh data now that it's stored in physics system
+    SAFE_DELETE(data);
+
     return shape;
 }
 

+ 16 - 0
gameplay/src/RenderState.cpp

@@ -130,6 +130,10 @@ void RenderState::setParameterAutoBinding(const char* name, const char* autoBind
     {
         value = WORLD_VIEW_PROJECTION_MATRIX;
     }
+    else if (strcmp(autoBinding, "INVERSE_TRANSPOSE_WORLD_MATRIX") == 0)
+    {
+        value = INVERSE_TRANSPOSE_WORLD_MATRIX;
+    }
     else if (strcmp(autoBinding, "INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX") == 0)
     {
         value = INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX;
@@ -138,6 +142,10 @@ void RenderState::setParameterAutoBinding(const char* name, const char* autoBind
     {
         value = CAMERA_WORLD_POSITION;
     }
+    else if (strcmp(autoBinding, "CAMERA_VIEW_POSITION") == 0)
+    {
+        value = CAMERA_VIEW_POSITION;
+    }
     else if (strcmp(autoBinding, "MATRIX_PALETTE") == 0)
     {
         value = MATRIX_PALETTE;
@@ -218,6 +226,10 @@ void RenderState::applyAutoBinding(const char* uniformName, AutoBinding autoBind
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getWorldViewProjectionMatrix);
         break;
 
+    case INVERSE_TRANSPOSE_WORLD_MATRIX:
+        getParameter(uniformName)->bindValue(_nodeBinding, &Node::getInverseTransposeWorldMatrix);
+        break;
+
     case INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX:
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getInverseTransposeWorldViewMatrix);
         break;
@@ -226,6 +238,10 @@ void RenderState::applyAutoBinding(const char* uniformName, AutoBinding autoBind
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getActiveCameraTranslationWorld);
         break;
 
+    case CAMERA_VIEW_POSITION:
+        getParameter(uniformName)->bindValue(_nodeBinding, &Node::getActiveCameraTranslationView);
+        break;
+
     case MATRIX_PALETTE:
         {
             Model* model = _nodeBinding->getModel();

+ 10 - 0
gameplay/src/RenderState.h

@@ -57,6 +57,11 @@ public:
          */
         WORLD_VIEW_PROJECTION_MATRIX,
 
+        /**
+         * Binds a node's InverseTransposeWorl matrix.
+         */
+        INVERSE_TRANSPOSE_WORLD_MATRIX,
+
         /**
          * Binds a node's InverseTransposeWorldView matrix.
          */
@@ -67,6 +72,11 @@ public:
          */
         CAMERA_WORLD_POSITION,
 
+        /**
+         * Binds the view-space position (Vector3) of the active camera for the node's scene.
+         */
+        CAMERA_VIEW_POSITION,
+
         /**
          * Binds the matrix palette of MeshSkin attached to a node's model.
          */

+ 1 - 1
gameplay/src/Scene.cpp

@@ -278,7 +278,7 @@ void Scene::setViewport(const Viewport& viewport)
     _viewport = viewport;
 }
 
-const Vector3& Scene::getAmbientColor()
+const Vector3& Scene::getAmbientColor() const
 {
     return _ambientColor;
 }

+ 1 - 1
gameplay/src/Scene.h

@@ -151,7 +151,7 @@ public:
      * 
      * @return The ambient color of the scene.
      */
-    const Vector3& getAmbientColor();
+    const Vector3& getAmbientColor() const;
 
     /**
      * Sets the ambient color of the scene.

+ 284 - 281
gameplay/src/SceneLoader.cpp

@@ -9,9 +9,7 @@ namespace gameplay
 // Static member variables.
 std::map<std::string, Properties*> SceneLoader::_propertiesFromFile;
 std::vector<SceneLoader::SceneAnimation> SceneLoader::_animations;
-std::vector<SceneLoader::SceneNodeProperty> SceneLoader::_nodeProperties;
-std::vector<std::string> SceneLoader::_nodesWithMeshRB;
-std::map<std::string, SceneLoader::MeshRigidBodyData>* SceneLoader::_meshRigidBodyData = NULL;
+std::vector<SceneLoader::SceneNode> SceneLoader::_sceneNodes;
 
 Scene* SceneLoader::load(const char* filePath)
 {
@@ -35,27 +33,11 @@ Scene* SceneLoader::load(const char* filePath)
         SAFE_DELETE(properties);
         return NULL;
     }
-    
 
     // Build the node URL/property and animation reference tables and load the referenced files.
     buildReferenceTables(sceneProperties);
     loadReferencedFiles();
 
-    // Calculate the node IDs that need to be loaded with mesh rigid body support.
-    calculateNodesWithMeshRigidBodies(sceneProperties);
-
-    // Set up for storing the mesh rigid body data.
-    if (_nodesWithMeshRB.size() > 0)
-    {
-        // We do not currently support loading more than one scene simultaneously.
-        if (_meshRigidBodyData)
-        {
-            WARN("Attempting to load multiple scenes simultaneously; mesh rigid bodies will not load properly.");
-        }
-
-        _meshRigidBodyData = new std::map<std::string, MeshRigidBodyData>();
-    }
-
     // Load the main scene data from GPB and apply the global scene properties.
     Scene* scene = loadMainSceneData(sceneProperties);
     if (!scene)
@@ -66,8 +48,18 @@ Scene* SceneLoader::load(const char* filePath)
 
     // First apply the node url properties. Following that,
     // apply the normal node properties and create the animations.
+    // We apply rigid body properties after all other node properties
+    // so that the transform (SRT) properties get applied before
+    // processing rigid bodies.
     applyNodeUrls(scene);
-    applyNodeProperties(scene, sceneProperties);
+    applyNodeProperties(scene, sceneProperties, 
+        SceneNodeProperty::AUDIO | 
+        SceneNodeProperty::MATERIAL | 
+        SceneNodeProperty::PARTICLE |
+        SceneNodeProperty::ROTATE |
+        SceneNodeProperty::SCALE |
+        SceneNodeProperty::TRANSLATE);
+    applyNodeProperties(scene, sceneProperties, SceneNodeProperty::RIGIDBODY);
     createAnimations(scene);
 
     // Find the physics properties object.
@@ -98,65 +90,21 @@ Scene* SceneLoader::load(const char* filePath)
     // Clean up the .scene file's properties object.
     SAFE_DELETE(properties);
 
-    // Clean up mesh rigid body data.
-    if (_meshRigidBodyData)
-    {
-        std::map<std::string, MeshRigidBodyData>::iterator iter = _meshRigidBodyData->begin();
-        for (; iter != _meshRigidBodyData->end(); iter++)
-        {
-            for (unsigned int i = 0; i < iter->second.indexData.size(); i++)
-            {
-                SAFE_DELETE_ARRAY(iter->second.indexData[i]);
-            }
-
-            SAFE_DELETE_ARRAY(iter->second.vertexData);
-        }
-
-        SAFE_DELETE(_meshRigidBodyData);
-    }
-
     // Clear all temporary data stores.
     _propertiesFromFile.clear();
     _animations.clear();
-    _nodeProperties.clear();
-    _nodesWithMeshRB.clear();
+    _sceneNodes.clear();
 
     return scene;
 }
 
-void SceneLoader::addMeshRigidBodyData(std::string id, Mesh* mesh, unsigned char* vertexData, unsigned int vertexByteCount)
-{
-    if (!_meshRigidBodyData)
-    {
-        WARN("Attempting to add mesh rigid body data outside of scene loading; ignoring request.");
-        return;
-    }
-
-    (*_meshRigidBodyData)[id].mesh = mesh;
-    (*_meshRigidBodyData)[id].vertexData = new unsigned char[vertexByteCount];
-    memcpy((*_meshRigidBodyData)[id].vertexData, vertexData, vertexByteCount);
-}
-
-void SceneLoader::addMeshRigidBodyData(std::string id, unsigned char* indexData, unsigned int indexByteCount)
-{
-    if (!_meshRigidBodyData)
-    {
-        WARN("Attempting to add mesh rigid body data outside of scene loading; ignoring request.");
-        return;
-    }
-
-    unsigned char* indexDataCopy = new unsigned char[indexByteCount];
-    memcpy(indexDataCopy, indexData, indexByteCount);
-    (*_meshRigidBodyData)[id].indexData.push_back(indexDataCopy);
-}
-
 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);
-    
+
     // 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)
@@ -166,166 +114,154 @@ void SceneLoader::addSceneAnimation(const char* animationID, const char* targetI
     _animations.push_back(SceneAnimation(animationID, targetID, file, id));
 }
 
-void SceneLoader::addSceneNodeProperty(SceneNodeProperty::Type type, const char* nodeID, const char* url)
+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);
-    
+
     // 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;
+        }
+    }
+
     // Add the node property to the list of node properties to be resolved later.
-    _nodeProperties.push_back(SceneNodeProperty(type, nodeID, file, id));
+    sceneNode._properties.push_back(prop);
 }
 
-void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* sceneProperties)
+void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* sceneProperties, unsigned int typeFlags)
 {
-    // Apply all of the remaining scene node properties except rigid body (we apply that last).
-    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
+    for (unsigned int i = 0, ncount = _sceneNodes.size(); i < ncount; ++i)
     {
-        // If the referenced node doesn't exist in the scene, then we
-        // can't do anything so we skip to the next scene node property.
-        Node* node = scene->findNode(_nodeProperties[i]._nodeID);
-        if (!node)
-        {
-            WARN_VARG("Attempting to set a property for node '%s', which does not exist in the scene.", _nodeProperties[i]._nodeID);
-            continue;
-        }
+        SceneNode& sceneNode = _sceneNodes[i];
 
-        if (_nodeProperties[i]._type == SceneNodeProperty::AUDIO ||
-            _nodeProperties[i]._type == SceneNodeProperty::MATERIAL ||
-            _nodeProperties[i]._type == SceneNodeProperty::PARTICLE ||
-            _nodeProperties[i]._type == SceneNodeProperty::RIGIDBODY)
+        if (sceneNode._exactMatch)
         {
-            // Check to make sure the referenced properties object was loaded properly.
-            Properties* p = _propertiesFromFile[_nodeProperties[i]._file];
-            if (!p)
+            // Find the node matching the specified ID exactly
+            Node* node = scene->findNode(sceneNode._nodeID);
+            if (!node)
             {
-                WARN_VARG("The referenced node data in file '%s' failed to load.", _nodeProperties[i]._file.c_str());
+                WARN_VARG("Attempting to set a property for node '%s', which does not exist in the scene.", sceneNode._nodeID);
                 continue;
             }
 
-            // If a specific namespace within the file was specified, load that namespace.
-            if (_nodeProperties[i]._id.size() > 0)
+            for (unsigned int j = 0, pcount = sceneNode._properties.size(); j < pcount; ++j)
             {
-                p = p->getNamespace(_nodeProperties[i]._id.c_str());
-                if (!p)
-                {
-                    WARN_VARG("The referenced node data at '%s#%s' failed to load.", _nodeProperties[i]._file.c_str(), _nodeProperties[i]._id.c_str());
-                    continue;
-                }
+                SceneNodeProperty& snp = sceneNode._properties[j];
+                if ((typeFlags & snp._type) == snp._type)
+                    applyNodeProperty(sceneNode, node, sceneProperties, snp);
             }
-            else
+        }
+        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)
+                continue;
+            
+            for (unsigned int j = 0, pcount = sceneNode._properties.size(); j < pcount; ++j)
             {
-                // Otherwise, use the first namespace.
-                p->rewind();
-                p = p->getNextNamespace();
-            }
+                SceneNodeProperty& snp = sceneNode._properties[j];
+                if ((typeFlags & snp._type) == 0)
+                    continue;
 
-            switch (_nodeProperties[i]._type)
-            {
-            case SceneNodeProperty::AUDIO:
-            {
-                AudioSource* audioSource = AudioSource::create(p);
-                node->setAudioSource(audioSource);
-                SAFE_RELEASE(audioSource);
-                break;
+                for (unsigned int k = 0; k < nodeCount; ++k)
+                    applyNodeProperty(sceneNode, nodes[k], sceneProperties, snp);
             }
-            case SceneNodeProperty::MATERIAL:
-                if (!node->getModel())
-                    WARN_VARG("Attempting to set a material on node '%s', which has no model.", _nodeProperties[i]._nodeID);
-                else
-                {
-                    Material* material = Material::create(p);
-                    node->getModel()->setMaterial(material);
-                    SAFE_RELEASE(material);
-                }
+        }
+    }
+}
 
-                break;
-            case SceneNodeProperty::PARTICLE:
+void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Properties* sceneProperties, const SceneNodeProperty& snp)
+{
+    if (snp._type == SceneNodeProperty::AUDIO ||
+        snp._type == SceneNodeProperty::MATERIAL ||
+        snp._type == SceneNodeProperty::PARTICLE ||
+        snp._type == SceneNodeProperty::RIGIDBODY)
+    {
+        // Check to make sure the referenced properties object was loaded properly.
+        Properties* p = _propertiesFromFile[snp._file];
+        if (!p)
+        {
+            WARN_VARG("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)
             {
-                ParticleEmitter* particleEmitter = ParticleEmitter::create(p);
-                node->setParticleEmitter(particleEmitter);
-                SAFE_RELEASE(particleEmitter);
-                break;
-            }
-            case SceneNodeProperty::RIGIDBODY:
-                // Process this last in a separate loop to allow scale, translate, rotate to be applied first.
-                break;
-            default:
-                // This cannot happen.
-                break;
+                WARN_VARG("The referenced node data at '%s#%s' failed to load.", snp._file.c_str(), snp._id.c_str());
+                return;
             }
         }
         else
         {
-            Properties* np = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
-            const char* name = NULL;
-
-            switch (_nodeProperties[i]._type)
-            {
-            case SceneNodeProperty::TRANSLATE:
-            {
-                Vector3 t;
-                if (np && np->getVector3("translate", &t))
-                    node->setTranslation(t);
-                break;
-            }
-            case SceneNodeProperty::ROTATE:
-            {
-                Quaternion r;
-                if (np && np->getQuaternionFromAxisAngle("rotate", &r))
-                    node->setRotation(r);
-                break;
-            }
-            case SceneNodeProperty::SCALE:
-            {
-                Vector3 s;
-                if (np && np->getVector3("scale", &s))
-                    node->setScale(s);
-                break;
-            }
-            default:
-                WARN_VARG("Unsupported node property type: %d.", _nodeProperties[i]._type);
-                break;
-            }
+            // Otherwise, use the first namespace.
+            p->rewind();
+            p = p->getNextNamespace();
         }
-    }
 
-    // Process rigid body properties.
-    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
-    {
-        if (_nodeProperties[i]._type == SceneNodeProperty::RIGIDBODY)
+        switch (snp._type)
         {
-            // If the referenced node doesn't exist in the scene, then we
-            // can't do anything so we skip to the next scene node property.
-            Node* node = scene->findNode(_nodeProperties[i]._nodeID);
-            if (!node)
+        case SceneNodeProperty::AUDIO:
+        {
+            AudioSource* audioSource = AudioSource::create(p);
+            node->setAudioSource(audioSource);
+            SAFE_RELEASE(audioSource);
+            break;
+        }
+        case SceneNodeProperty::MATERIAL:
+            if (!node->getModel())
+                WARN_VARG("Attempting to set a material on node '%s', which has no model.", sceneNode._nodeID);
+            else
             {
-                WARN_VARG("Attempting to set a property for node '%s', which does not exist in the scene.", _nodeProperties[i]._nodeID);
-                continue;
+                Material* material = Material::create(p);
+                node->getModel()->setMaterial(material, snp._index);
+                SAFE_RELEASE(material);
             }
-
+            break;
+        case SceneNodeProperty::PARTICLE:
+        {
+            ParticleEmitter* particleEmitter = ParticleEmitter::create(p);
+            node->setParticleEmitter(particleEmitter);
+            SAFE_RELEASE(particleEmitter);
+            break;
+        }
+        case SceneNodeProperty::RIGIDBODY:
+        {
             // Check to make sure the referenced properties object was loaded properly.
-            Properties* p = _propertiesFromFile[_nodeProperties[i]._file];
+            Properties* p = _propertiesFromFile[snp._file];
             if (!p)
             {
-                WARN_VARG("The referenced node data in file '%s' failed to load.", _nodeProperties[i]._file.c_str());
-                continue;
+                WARN_VARG("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 (_nodeProperties[i]._id.size() > 0)
+            if (snp._id.size() > 0)
             {
-                p = p->getNamespace(_nodeProperties[i]._id.c_str());
+                p = p->getNamespace(snp._id.c_str());
                 if (!p)
                 {
-                    WARN_VARG("The referenced node data at '%s#%s' failed to load.", _nodeProperties[i]._file.c_str(), _nodeProperties[i]._id.c_str());
-                    continue;
+                    WARN_VARG("The referenced node data at '%s#%s' failed to load.", snp._file.c_str(), snp._id.c_str());
+                    return;
                 }
             }
             else
@@ -336,11 +272,11 @@ void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* scen
             }
 
             // If the scene file specifies a rigid body model, use it for creating the rigid body.
-            Properties* np = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
+            Properties* np = sceneProperties->getNamespace(sceneNode._nodeID);
             const char* name = NULL;
             if (np && (name = np->getString("rigidbodymodel")))
             {
-                Node* modelNode = scene->findNode(name);
+                Node* modelNode = node->getScene()->findNode(name);
                 if (!modelNode)
                     WARN_VARG("Node '%s' does not exist; attempting to use its model for rigid body creation.", name);
                 else
@@ -358,9 +294,48 @@ void SceneLoader::applyNodeProperties(const Scene* scene, const Properties* scen
                 }
             }
             else if (!node->getModel())
-                WARN_VARG("Attempting to set a rigid body on node '%s', which has no model.", _nodeProperties[i]._nodeID);
+                WARN_VARG("Attempting to set a rigid body on node '%s', which has no model.", sceneNode._nodeID);
             else
                 node->setPhysicsRigidBody(p);
+            break;
+        }
+        default:
+            // This cannot happen.
+            break;
+        }
+    }
+    else
+    {
+        // Handle Scale, Rotate and Translate
+        Properties* np = sceneProperties->getNamespace(sceneNode._nodeID);
+        const char* name = NULL;
+
+        switch (snp._type)
+        {
+        case SceneNodeProperty::TRANSLATE:
+        {
+            Vector3 t;
+            if (np && np->getVector3("translate", &t))
+                node->setTranslation(t);
+            break;
+        }
+        case SceneNodeProperty::ROTATE:
+        {
+            Quaternion r;
+            if (np && np->getQuaternionFromAxisAngle("rotate", &r))
+                node->setRotation(r);
+            break;
+        }
+        case SceneNodeProperty::SCALE:
+        {
+            Vector3 s;
+            if (np && np->getVector3("scale", &s))
+                node->setScale(s);
+            break;
+        }
+        default:
+            WARN_VARG("Unsupported node property type: %d.", snp._type);
+            break;
         }
     }
 }
@@ -369,65 +344,126 @@ void SceneLoader::applyNodeUrls(Scene* scene)
 {
     // Apply all URL node properties so that when we go to apply
     // the other node properties, the node is in the scene.
-    for (unsigned int i = 0; i < _nodeProperties.size(); )
+    for (unsigned int i = 0, ncount = _sceneNodes.size(); i < ncount; ++i)
     {
-        if (_nodeProperties[i]._type == SceneNodeProperty::URL)
+        SceneNode& sceneNode = _sceneNodes[i];
+
+        // Iterate backwards over the properties list so we can remove properties as we go
+        // without danger of indexing out of bounds.
+        for (int j = sceneNode._properties.size() - 1; j >= 0; --j)
         {
-            // Make sure that the ID we are using to insert the node into the scene with is unique.
-            if (scene->findNode(_nodeProperties[i]._nodeID) != NULL)
-                WARN_VARG("Attempting to insert or rename a node to an ID that already exists: ID='%s'", _nodeProperties[i]._nodeID);
-            else
+            SceneNodeProperty& snp = sceneNode._properties[j];
+            if (snp._type != SceneNodeProperty::URL)
+                continue;
+
+            if (snp._file.empty())
             {
-                // If a file was specified, load the node from file and then insert it into the scene with the new ID.
-                if (_nodeProperties[i]._file.size() > 0)
+                // 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)
                 {
-                    Package* tmpPackage = Package::create(_nodeProperties[i]._file.c_str());
-                    if (!tmpPackage)
-                        WARN_VARG("Failed to load GPB file '%s' for node stitching.", _nodeProperties[i]._file.c_str());
+                    Node* node = scene->findNode(snp._id.c_str());
+                    if (node)
+                    {
+                        node->setId(sceneNode._nodeID);
+                    }
                     else
                     {
-                        bool loadWithMeshRBSupport = false;
-                        for (unsigned int j = 0; j < _nodesWithMeshRB.size(); j++)
+                        WARN_VARG("Could not find node '%s' in main scene GPB file.", snp._id.c_str());
+                    }
+                }
+                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)
+                    {
+                        for (unsigned int k = 0; k < nodeCount; ++k)
                         {
-                            if (_nodeProperties[i]._id == _nodesWithMeshRB[j])
-                            {
-                                loadWithMeshRBSupport = true;
-                                break;
-                            }
+                            // Construct a new node ID using _nodeID plus the remainder of the partial match.
+                            Node* node = nodes[k];
+                            std::string newID(sceneNode._nodeID);
+                            newID += (node->getId() + snp._id.length());
+                            node->setId(newID.c_str());
                         }
+                    }
+                    else
+                    {
+                        WARN_VARG("Could not find any nodes matching '%s' in main scene GPB file.", snp._id.c_str());
+                    }
+                }
+            }
+            else
+            {
+                // An external file was referenced, so load the node from file and then insert it into the scene with the new ID.
 
-                        Node* node = tmpPackage->loadNode(_nodeProperties[i]._id.c_str(), loadWithMeshRBSupport);
-                        if (!node)
-                            WARN_VARG("Could not load node '%s' in GPB file '%s'.", _nodeProperties[i]._id.c_str(), _nodeProperties[i]._file.c_str());
-                        else
+                // TODO: Revisit this to determine if we should cache Package objects for the duration of the scene
+                // load to prevent constantly creating/destroying the same externally referenced packages each time
+                // a url with a file is encountered.
+                Package* tmpPackage = Package::create(snp._file.c_str());
+                if (tmpPackage)
+                {
+                    if (sceneNode._exactMatch)
+                    {
+                        Node* node = tmpPackage->loadNode(snp._id.c_str());
+                        if (node)
                         {
-                            node->setId(_nodeProperties[i]._nodeID);
+                            node->setId(sceneNode._nodeID);
                             scene->addNode(node);
                             SAFE_RELEASE(node);
                         }
-                        
-                        SAFE_RELEASE(tmpPackage);
+                        else
+                        {
+                            WARN_VARG("Could not load node '%s' in GPB file '%s'.", snp._id.c_str(), snp._file.c_str());
+                        }
                     }
+                    else
+                    {
+                        // Search for nodes in the package using a partial match
+                        std::string partialMatch = snp._id;
+                        unsigned int objectCount = tmpPackage->getObjectCount();
+                        unsigned int matchCount = 0;
+                        for (unsigned int k = 0; k < objectCount; ++k)
+                        {
+                            const char* objid = tmpPackage->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 = tmpPackage->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)
+                        {
+                            WARN_VARG("Could not find any nodes matching '%s' in GPB file '%s'.", snp._id.c_str(), snp._file.c_str());
+                        }
+                    }
+
+                    SAFE_RELEASE(tmpPackage);
                 }
                 else
                 {
-                    // 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?
-
-                    // Otherwise, the node is from the main GPB and should just be renamed.
-                    Node* node = scene->findNode(_nodeProperties[i]._id.c_str());
-                    if (!node)
-                        WARN_VARG("Could not find node '%s' in main scene GPB file.", _nodeProperties[i]._id.c_str());
-                    else
-                        node->setId(_nodeProperties[i]._nodeID);
+                    WARN_VARG("Failed to load GPB file '%s' for node stitching.", snp._file.c_str());
                 }
             }
 
             // Remove the node property since we are done applying it.
-            _nodeProperties.erase(_nodeProperties.begin() + i);
+            sceneNode._properties.erase(sceneNode._properties.begin() + j);
         }
-        else
-            i++;
     }
 }
 
@@ -446,27 +482,40 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
                 continue;
             }
 
+            // Add a SceneNode to the end of the list
+            _sceneNodes.resize(_sceneNodes.size() + 1);
+            SceneNode& sceneNode = _sceneNodes[_sceneNodes.size()-1];
+            sceneNode._nodeID = ns->getId();
+
             while (name = ns->getNextProperty())
             {
                 if (strcmp(name, "url") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::URL, ns->getId(), ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::URL, ns->getString());
                 }
                 else if (strcmp(name, "audio") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::AUDIO, ns->getId(), ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::AUDIO, ns->getString());
                 }
-                else if (strcmp(name, "material") == 0)
+                else if (strncmp(name, "material", 8) == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::MATERIAL, ns->getId(), ns->getString());
+                    int materialIndex = -1;
+                    name = strchr(name, '[');
+                    if (name && strlen(name) >= 3)
+                    {
+                        std::string indexString(name);
+                        indexString = indexString.substr(1, indexString.size()-2);
+                        materialIndex = (unsigned int)atoi(indexString.c_str());
+                    }
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::MATERIAL, ns->getString(), materialIndex);
                 }
                 else if (strcmp(name, "particle") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::PARTICLE, ns->getId(), ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, ns->getString());
                 }
                 else if (strcmp(name, "rigidbody") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::RIGIDBODY, ns->getId(), ns->getString());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::RIGIDBODY, ns->getString());
                 }
                 else if (strcmp(name, "rigidbodymodel") == 0)
                 {
@@ -474,15 +523,15 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
                 }
                 else if (strcmp(name, "translate") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::TRANSLATE, ns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::TRANSLATE);
                 }
                 else if (strcmp(name, "rotate") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::ROTATE, ns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::ROTATE);
                 }
                 else if (strcmp(name, "scale") == 0)
                 {
-                    addSceneNodeProperty(SceneNodeProperty::SCALE, ns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::SCALE);
                 }
                 else
                 {
@@ -533,52 +582,16 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
         }
         else
         {
+            // TODO: Should we ignore these items? They could be used for generic properties file inheritence.
             WARN_VARG("Unsupported child namespace (of 'scene'): %s", ns->getNamespace());
         }
     }
 }
 
-void SceneLoader::calculateNodesWithMeshRigidBodies(const Properties* sceneProperties)
-{
-    const char* name = NULL;
-
-    // Make a list of all nodes with triangle mesh rigid bodies.
-    for (unsigned int i = 0; i < _nodeProperties.size(); i++)
-    {
-        if (_nodeProperties[i]._type == SceneNodeProperty::RIGIDBODY)
-        {
-            Properties* p = _propertiesFromFile[_nodeProperties[i]._file];
-            if (p)
-            {
-                if (_nodeProperties[i]._id.size() > 0)
-                {
-                    p = p->getNamespace(_nodeProperties[i]._id.c_str());
-                }
-                else
-                {
-                    p = p->getNextNamespace();
-                }
-
-                if (p && strcmp(p->getNamespace(), "rigidbody") == 0 &&
-                    strcmp(p->getString("type"), "MESH") == 0)
-                {
-                    // If the node specifies a rigidbodymodel, then use
-                    // that node's ID; otherwise, use its ID.
-                    Properties* p = sceneProperties->getNamespace(_nodeProperties[i]._nodeID);
-                    if (p && (name = p->getString("rigidbodymodel")))
-                        _nodesWithMeshRB.push_back(name);
-                    else
-                        _nodesWithMeshRB.push_back(_nodeProperties[i]._nodeID);
-                }
-            }
-        }
-    }
-}
-
 void SceneLoader::createAnimations(const Scene* scene)
 {
     // Create the scene animations.
-    for (unsigned int i = 0; i < _animations.size(); i++)
+    for (unsigned int i = 0, count = _animations.size(); i < count; i++)
     {
         // If the target node doesn't exist in the scene, then we
         // can't do anything so we skip to the next animation.
@@ -610,17 +623,6 @@ void SceneLoader::createAnimations(const Scene* scene)
     }
 }
 
-const SceneLoader::MeshRigidBodyData* SceneLoader::getMeshRigidBodyData(std::string id)
-{
-    if (!_meshRigidBodyData)
-    {
-        WARN("Attempting to get mesh rigid body data, but none has been loaded; ignoring request.");
-        return NULL;
-    }
-
-    return (_meshRigidBodyData->count(id) > 0) ? &(*_meshRigidBodyData)[id] : NULL;
-}
-
 PhysicsConstraint* SceneLoader::loadGenericConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
 {
     PhysicsGenericConstraint* physicsConstraint;
@@ -711,8 +713,9 @@ Scene* SceneLoader::loadMainSceneData(const Properties* sceneProperties)
         WARN_VARG("Failed to load scene GPB file '%s'.", path);
         return NULL;
     }
-    
-    Scene* scene = package->loadScene(NULL, &_nodesWithMeshRB);
+
+    const char* sceneID = strlen(sceneProperties->getId()) == 0 ? NULL : sceneProperties->getId();
+    Scene* scene = package->loadScene(sceneID);
     if (!scene)
     {
         WARN_VARG("Failed to load scene from '%s'.", path);
@@ -818,7 +821,7 @@ void SceneLoader::loadPhysics(Properties* physics, Scene* scene)
             
             // If the constraint failed to load, continue on to the next one.
             if (!physicsConstraint)
-                    continue;
+                continue;
 
             // If the breaking impulse was specified, apply it to the constraint.
             if (constraint->getString("breakingImpulse"))

+ 28 - 26
gameplay/src/SceneLoader.h

@@ -15,8 +15,6 @@ namespace gameplay
  */
 class SceneLoader
 {
-    friend class Package;
-    friend class PhysicsController;
     friend class Scene;
 
 private:
@@ -27,13 +25,6 @@ private:
     // ------------------------------------------------------------------------
     // Helper structures and functions for SceneLoader::load(const char*).
 
-    struct MeshRigidBodyData
-    {
-        Mesh* mesh;
-        unsigned char* vertexData;
-        std::vector<unsigned char*> indexData;
-    };
-
     struct SceneAnimation
     {
         SceneAnimation(const char* animationID, const char* targetID, std::string file, std::string id)
@@ -47,27 +38,43 @@ private:
 
     struct SceneNodeProperty
     {
-        enum Type { AUDIO, MATERIAL, PARTICLE, RIGIDBODY, TRANSLATE, ROTATE, SCALE, URL };
-
-        SceneNodeProperty(Type type, const char* nodeID, std::string file, std::string id)
-            : _type(type), _nodeID(nodeID), _file(file), _id(id) {}
+        enum Type
+        {
+            AUDIO = 1,
+            MATERIAL = 2,
+            PARTICLE = 4,
+            RIGIDBODY = 8,
+            TRANSLATE = 16,
+            ROTATE = 32,
+            SCALE = 64,
+            URL = 128
+        };
+
+        SceneNodeProperty(Type type, std::string file, std::string id, int index) : _type(type), _file(file), _id(id), _index(index) { }
 
         Type _type;
-        const char* _nodeID;
         std::string _file;
         std::string _id;
+        int _index;
+    };
+
+    struct SceneNode
+    {
+        SceneNode() : _nodeID(""), _exactMatch(true) { }
+
+        const char* _nodeID;
+        bool _exactMatch;
+        std::vector<SceneNodeProperty> _properties;
     };
 
-    static void addMeshRigidBodyData(std::string id, Mesh* mesh, unsigned char* vertexData, unsigned int vertexByteCount);
-    static void addMeshRigidBodyData(std::string id, unsigned char* indexData, unsigned int indexByteCount);
     static void addSceneAnimation(const char* animationID, const char* targetID, const char* url);
-    static void addSceneNodeProperty(SceneNodeProperty::Type type, const char* nodeID, const char* url = NULL);
-    static void applyNodeProperties(const Scene* scene, const Properties* sceneProperties);
+    static void addSceneNodeProperty(SceneNode& sceneNode, SceneNodeProperty::Type type, const char* url = NULL, int index = 0);
+    static void applyNodeProperties(const Scene* scene, const Properties* sceneProperties, unsigned int typeFlags);
+    static void applyNodeProperty(SceneNode& sceneNode, Node* node, const Properties* sceneProperties, const SceneNodeProperty& snp);
     static void applyNodeUrls(Scene* scene);
     static void buildReferenceTables(Properties* sceneProperties);
     static void calculateNodesWithMeshRigidBodies(const Properties* sceneProperties);
     static void createAnimations(const Scene* scene);
-    static const MeshRigidBodyData* getMeshRigidBodyData(std::string id);
     static PhysicsConstraint* loadGenericConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
     static PhysicsConstraint* loadHingeConstraint(const Properties* constraint, PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
     static Scene* loadMainSceneData(const Properties* sceneProperties);
@@ -85,14 +92,9 @@ private:
     // Holds the animations declared in the .scene file.
     static std::vector<SceneAnimation> _animations;
 
-    // Holds all the node properties declared in the .scene file.
-    static std::vector<SceneNodeProperty> _nodeProperties;
-
-    // Holds the node IDs that need to be loaded with mesh rigid body support.
-    static std::vector<std::string> _nodesWithMeshRB;
+    // Holds all the nodes+properties declared in the .scene file.
+    static std::vector<SceneNode> _sceneNodes;
 
-    // Stores the mesh data needed for triangle mesh rigid body support.
-    static std::map<std::string, MeshRigidBodyData>* _meshRigidBodyData;
 };
 
 }