Selaa lähdekoodia

Improved support for bounding volume computation for skinned meshes.
Various changes to gameplay encoder to support bounding volume computation for animated skinned meshes.
Increased revision of gameplay binary format from 1.0 to 1.1.
Changed Transform::Listener interface to add a "long cookie" parameter to the transformChagned event.
Fixed depth testing issue with ParticleEmitter.

Steve Grenier 14 vuotta sitten
vanhempi
sitoutus
cb26019194

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

@@ -16,7 +16,7 @@ Section      Name            Type
 ------------------------------------------------------------------------------------------------------
 ------------------------------------------------------------------------------------------------------
 Header
 Header
              Identifier      byte[9]     = { '«', 'G', 'P', 'B', '»', '\r', '\n', '\x1A', '\n' } 
              Identifier      byte[9]     = { '«', 'G', 'P', 'B', '»', '\r', '\n', '\x1A', '\n' } 
-             Version         byte[2]     = { 1, 0 }
+             Version         byte[2]     = { 1, 1 }
              References      Reference[]
              References      Reference[]
 Data
 Data
              Objects         Object[]
              Objects         Object[]

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

@@ -79,6 +79,7 @@
     <ClInclude Include="src\MaterialParameter.h" />
     <ClInclude Include="src\MaterialParameter.h" />
     <ClInclude Include="src\Matrix.h" />
     <ClInclude Include="src\Matrix.h" />
     <ClInclude Include="src\Mesh.h" />
     <ClInclude Include="src\Mesh.h" />
+    <ClInclude Include="src\Miniball.h" />
     <ClInclude Include="src\Model.h" />
     <ClInclude Include="src\Model.h" />
     <ClInclude Include="src\MeshPart.h" />
     <ClInclude Include="src\MeshPart.h" />
     <ClInclude Include="src\MeshSkin.h" />
     <ClInclude Include="src\MeshSkin.h" />

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

@@ -194,6 +194,7 @@
       <Filter>Objects\Animation</Filter>
       <Filter>Objects\Animation</Filter>
     </ClInclude>
     </ClInclude>
     <ClInclude Include="..\gameplay\src\Curve.h" />
     <ClInclude Include="..\gameplay\src\Curve.h" />
+    <ClInclude Include="src\Miniball.h" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <Filter Include="Objects">
     <Filter Include="Objects">

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

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <LocalDebuggerCommandArguments>C:\git\GamePlay\gameplay-samples\sample03-character\res\models\Seymour.dae</LocalDebuggerCommandArguments>
+    <LocalDebuggerCommandArguments>-groupAnimations Bip01 dragon_animations C:\git\GamePlay\gameplay-demos\demo00-dragon\res\models\demo00-dragon.dae</LocalDebuggerCommandArguments>
     <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
     <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
   </PropertyGroup>
   </PropertyGroup>
 </Project>
 </Project>

+ 8 - 3
gameplay-encoder/src/Base.h

@@ -17,7 +17,6 @@
 #include <math.h>
 #include <math.h>
 #include <float.h>
 #include <float.h>
 
 
-
 #ifndef M_1_PI        
 #ifndef M_1_PI        
 #define M_1_PI                      0.31830988618379067154
 #define M_1_PI                      0.31830988618379067154
 #endif
 #endif
@@ -59,10 +58,16 @@ enum VertexUsage
     TEXCOORD7 = 15
     TEXCOORD7 = 15
 };
 };
 
 
-
 void fillArray(float values[], float value, size_t length);
 void fillArray(float values[], float value, size_t length);
 void setIdentityMatrix(float values[]);
 void setIdentityMatrix(float values[]);
 
 
-}
+#define ISZERO(x) (fabs(x) < 0.000001f)
+
+#ifdef NDEBUG
+#define DEBUGPRINT (x, ...)
+#else
+#define DEBUGPRINT(x, ...) printf(x, __VA_ARGS__)
 #endif
 #endif
 
 
+}
+#endif

+ 6 - 7
gameplay-encoder/src/DAESceneEncoder.cpp

@@ -1266,9 +1266,9 @@ void DAESceneEncoder::loadSkeleton(domInstance_controller::domSkeleton* skeleton
     }
     }
 
 
     // Resolve and set joints array for skin
     // Resolve and set joints array for skin
-    std::list<Node*> _joints;
-    const std::list<std::string>& jointNames = skin->getJointNames();
-    for (std::list<std::string>::const_iterator i = jointNames.begin(); i != jointNames.end(); i++)
+    std::vector<Node*> _joints;
+    const std::vector<std::string>& jointNames = skin->getJointNames();
+    for (std::vector<std::string>::const_iterator i = jointNames.begin(); i != jointNames.end(); i++)
     {
     {
         Object* obj = _gamePlayFile.getFromRefTable(*i);
         Object* obj = _gamePlayFile.getFromRefTable(*i);
         if (obj)
         if (obj)
@@ -1302,7 +1302,6 @@ Model* DAESceneEncoder::loadSkin(const domSkin* skinElement)
     domSkin::domJointsRef _joints = skinElement->getJoints();
     domSkin::domJointsRef _joints = skinElement->getJoints();
     domInputLocal_Array& jointInputs = _joints->getInput_array();
     domInputLocal_Array& jointInputs = _joints->getInput_array();
 
 
-
     // Process "JOINT" input semantic first (we need to do this to set the joint count)
     // Process "JOINT" input semantic first (we need to do this to set the joint count)
     unsigned int jointCount = 0;
     unsigned int jointCount = 0;
     for (unsigned int i = 0; i < jointInputs.getCount(); i++)
     for (unsigned int i = 0; i < jointInputs.getCount(); i++)
@@ -1316,12 +1315,12 @@ Model* DAESceneEncoder::loadSkin(const domSkin* skinElement)
         if (equals(inputSemantic, "JOINT"))
         if (equals(inputSemantic, "JOINT"))
         {
         {
             // Get the joint Ids's
             // Get the joint Ids's
-            std::list<std::string> list;
+            std::vector<std::string> list;
             getJointNames(source, list);
             getJointNames(source, list);
 
 
             // Go through the joint list and conver them from sid to id because the sid information is
             // Go through the joint list and conver them from sid to id because the sid information is
             // lost when converting to the gameplay binary format.
             // lost when converting to the gameplay binary format.
-            for (std::list<std::string>::iterator i = list.begin(); i != list.end(); i++)
+            for (std::vector<std::string>::iterator i = list.begin(); i != list.end(); i++)
             {
             {
                 daeSIDResolver resolver(source->getDocument()->getDomRoot(), i->c_str());
                 daeSIDResolver resolver(source->getDocument()->getDomRoot(), i->c_str());
                 daeElement* element = resolver.getElement();
                 daeElement* element = resolver.getElement();
@@ -1340,7 +1339,7 @@ Model* DAESceneEncoder::loadSkin(const domSkin* skinElement)
             jointCount = list.size();
             jointCount = list.size();
             _jointInverseBindPoseMatrices.reserve(jointCount);
             _jointInverseBindPoseMatrices.reserve(jointCount);
             unsigned int j = 0;
             unsigned int j = 0;
-            for (std::list<std::string>::const_iterator i = list.begin(); i != list.end(); i++)
+            for (std::vector<std::string>::const_iterator i = list.begin(); i != list.end(); i++)
             {
             {
                 _jointLookupTable[*i] = j++;
                 _jointLookupTable[*i] = j++;
             }
             }

+ 2 - 2
gameplay-encoder/src/DAEUtil.cpp

@@ -11,7 +11,7 @@
  */
  */
 int getIndex(const domInstance_controller::domSkeleton_Array& skeletonArray, const domNodeRef& node);
 int getIndex(const domInstance_controller::domSkeleton_Array& skeletonArray, const domNodeRef& node);
 
 
-void getJointNames(const domSourceRef source, std::list<std::string>& list)
+void getJointNames(const domSourceRef source, std::vector<std::string>& list)
 {
 {
     // BLENDER used name_array
     // BLENDER used name_array
     const domName_arrayRef& nameArray = source->getName_array();
     const domName_arrayRef& nameArray = source->getName_array();
@@ -40,7 +40,7 @@ void getJointNames(const domSourceRef source, std::list<std::string>& list)
     }
     }
 }
 }
 
 
-void getJointNames(const domSkin* skin, std::list<std::string>& list)
+void getJointNames(const domSkin* skin, std::vector<std::string>& list)
 {
 {
     const domSkin::domJointsRef& joints = skin->getJoints();
     const domSkin::domJointsRef& joints = skin->getJoints();
     const domInputLocal_Array& inputArray = joints->getInput_array();
     const domInputLocal_Array& inputArray = joints->getInput_array();

+ 2 - 2
gameplay-encoder/src/DAEUtil.h

@@ -27,7 +27,7 @@ using namespace gameplay;
  * @param source The source element to search in.
  * @param source The source element to search in.
  * @param list The list to append the joint names to.
  * @param list The list to append the joint names to.
  */
  */
-void getJointNames(const domSourceRef source, std::list<std::string>& list);
+void getJointNames(const domSourceRef source, std::vector<std::string>& list);
 
 
 /**
 /**
  * Gets the joint names for the given skin and appends them to the given list.
  * Gets the joint names for the given skin and appends them to the given list.
@@ -35,7 +35,7 @@ void getJointNames(const domSourceRef source, std::list<std::string>& list);
  * @param skin The skin element to search in.
  * @param skin The skin element to search in.
  * @param list The list to append the joint names to.
  * @param list The list to append the joint names to.
  */
  */
-void getJointNames(const domSkin* skin, std::list<std::string>& list);
+void getJointNames(const domSkin* skin, std::vector<std::string>& list);
 
 
 /**
 /**
  * Gets the input source from the given channel.
  * Gets the input source from the given channel.

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

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

+ 343 - 257
gameplay-encoder/src/MeshSkin.cpp

@@ -35,15 +35,28 @@ void MeshSkin::writeBinary(FILE* file)
     Object::writeBinary(file);
     Object::writeBinary(file);
     write(_bindShape, 16, file);
     write(_bindShape, 16, file);
     write(_joints.size(), file);
     write(_joints.size(), file);
-    for (std::list<Node*>::const_iterator i = _joints.begin(); i != _joints.end(); i++)
+    for (std::vector<Node*>::const_iterator i = _joints.begin(); i != _joints.end(); i++)
     {
     {
         (*i)->writeBinaryXref(file);
         (*i)->writeBinaryXref(file);
     }
     }
     write(_bindPoses.size() * 16, file);
     write(_bindPoses.size() * 16, file);
-    for (std::list<Matrix>::const_iterator i = _bindPoses.begin(); i != _bindPoses.end(); i++)
+    for (std::vector<Matrix>::const_iterator i = _bindPoses.begin(); i != _bindPoses.end(); i++)
     {
     {
         write(i->m, 16, file);
         write(i->m, 16, file);
     }
     }
+
+    /*
+    // Write joint bounds
+    write((unsigned int)_jointBounds.size(), file);
+    for (unsigned int i = 0; i < _jointBounds.size(); ++i)
+    {
+        BoundingSphere& s = _jointBounds[i];
+        write(s.center.x, file);
+        write(s.center.y, file);
+        write(s.center.z, file);
+        write(s.radius, file);
+    }
+    */
 }
 }
 
 
 void MeshSkin::writeText(FILE* file)
 void MeshSkin::writeText(FILE* file)
@@ -53,13 +66,13 @@ void MeshSkin::writeText(FILE* file)
     fprintfMatrix4f(file, _bindShape);
     fprintfMatrix4f(file, _bindShape);
     fprintf(file, "</bindShape>");
     fprintf(file, "</bindShape>");
     fprintf(file, "<joints>");
     fprintf(file, "<joints>");
-    for (std::list<std::string>::const_iterator i = _jointNames.begin(); i != _jointNames.end(); i++)
+    for (std::vector<std::string>::const_iterator i = _jointNames.begin(); i != _jointNames.end(); i++)
     {
     {
         fprintf(file, "%s ", i->c_str());
         fprintf(file, "%s ", i->c_str());
     }
     }
     fprintf(file, "</joints>\n");
     fprintf(file, "</joints>\n");
     fprintf(file, "<bindPoses count=\"%lu\">", _bindPoses.size() * 16);
     fprintf(file, "<bindPoses count=\"%lu\">", _bindPoses.size() * 16);
-    for (std::list<Matrix>::const_iterator i = _bindPoses.begin(); i != _bindPoses.end(); i++)
+    for (std::vector<Matrix>::const_iterator i = _bindPoses.begin(); i != _bindPoses.end(); i++)
     {
     {
         for (unsigned int j = 0; j < 16; ++j)
         for (unsigned int j = 0; j < 16; ++j)
         {
         {
@@ -71,6 +84,118 @@ void MeshSkin::writeText(FILE* file)
     fprintElementEnd(file);
     fprintElementEnd(file);
 }
 }
 
 
+void MeshSkin::setBindShape(const float data[])
+{
+    for (int i = 0; i < 16; i++)
+    {
+        _bindShape[i] = data[i];
+    }
+}
+
+void MeshSkin::setVertexInfluenceCount(unsigned int count)
+{
+    _vertexInfluenceCount = count;
+}
+
+void MeshSkin::setJointNames(const std::vector<std::string>& list)
+{
+    _jointNames = list;
+}
+
+const std::vector<std::string>& MeshSkin::getJointNames()
+{
+    return _jointNames;
+}
+
+void MeshSkin::setJoints(const std::vector<Node*>& list)
+{
+    _joints = list;
+}
+
+void MeshSkin::setBindPoses(std::vector<Matrix>& list)
+{
+    for (std::vector<Matrix>::iterator i = list.begin(); i != list.end(); i++)
+    {
+        _bindPoses.push_back(*i);
+    }
+}
+
+bool MeshSkin::hasJoint(const char* id)
+{
+    for (std::vector<std::string>::iterator i = _jointNames.begin(); i != _jointNames.end(); i++)
+    {
+        if (equals(*i, id))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+BoundingSphere mergeSpheres(const BoundingSphere& sphere1, const BoundingSphere& sphere2)
+{
+    BoundingSphere result;
+
+    // Calculate the distance between the two centers.
+    float vx = sphere1.center.x - sphere2.center.x;
+    float vy = sphere1.center.y - sphere2.center.y;
+    float vz = sphere1.center.z - sphere2.center.z;
+    float d = sqrtf(vx * vx + vy * vy + vz * vz);
+
+    // If one sphere is contained inside the other, set to the larger sphere.
+    if (d <= (sphere2.radius - sphere1.radius))
+    {
+        result = sphere2;
+        return result;
+    }
+    else if (d <= (sphere1.radius - sphere2.radius))
+    {
+        result = sphere1;
+        return result;
+    }
+
+    // Calculate the unit vector between the two centers.
+    float dI = 1.0f / d;
+    vx *= dI;
+    vy *= dI;
+    vz *= dI;
+
+    // Calculate the new radius.
+    float r = (sphere1.radius + sphere2.radius + d) * 0.5f;
+
+    // Calculate the new center.
+    float scaleFactor = (r - sphere2.radius);
+    vx = vx * scaleFactor + sphere2.center.x;
+    vy = vy * scaleFactor + sphere2.center.y;
+    vz = vz * scaleFactor + sphere2.center.z;
+
+    // Set the new center and radius.
+    result.center.x = vx;
+    result.center.y = vy;
+    result.center.z = vz;
+    result.radius = r;
+    return result;
+}
+
+BoundingSphere transformBoundingSphere(const BoundingSphere& sphere, Matrix& matrix)
+{
+    BoundingSphere result = sphere;
+
+    // Translate the center point.
+    Vector3 translate;
+    matrix.transformPoint(sphere.center, &translate);
+    result.center = translate;
+
+    // Calculate the sphere's new radius from the radii in each direction (take the largest).
+    matrix.decompose(&translate, NULL, NULL);
+    float r = sphere.radius * translate.x;
+    r = std::max(sphere.radius, sphere.radius * translate.y);
+    r = std::max(sphere.radius, sphere.radius * translate.z);
+    result.radius = r;
+
+    return result;
+}
+
 void MeshSkin::computeBounds()
 void MeshSkin::computeBounds()
 {
 {
     // Find the offset of the blend indices and blend weights within the mesh vertices
     // Find the offset of the blend indices and blend weights within the mesh vertices
@@ -89,312 +214,273 @@ void MeshSkin::computeBounds()
             break;
             break;
         }
         }
     }
     }
-    if (blendIndexOffset != -1 && blendWeightOffset != -1)
+    if (blendIndexOffset == -1 || blendWeightOffset == -1)
     {
     {
-        // Construct a new list of joints which contains all the joints in this mesh skin,
-        // as WELL as any nodes that are direct parents of the root joint.
-        // We need to do this since animations that affect parent nodes of our joints will
-        // ultimately affect the final position of transformed vertices.
-        std::vector<Node*> joints;
-        for (std::list<Node*>::const_iterator itr = _joints.begin(); itr != _joints.end(); itr++)
-        {
-            joints.push_back(*itr);
-        }
+        // Need blend indices and blend weights to calculate skinned bounding volume
+        return;
+    }
+
+    DEBUGPRINT("\nComputing bounds for skin of mesh: %s\n", _mesh->getId().c_str());
 
 
-        // Add parent joints that are not yet in the list
-        Node* joint = joints[0];
-        while (joint->getParent())
+    Node* joint;
+
+    // Get the root joint
+    Node* rootJoint = _joints[0];
+    Node* parent = rootJoint->getParent();
+    while (parent)
+    {
+        // Is this parent in the list of joints that form the skeleton?
+        // If not, then it's simply a parent node to the root joint
+        if (std::find(_joints.begin(), _joints.end(), parent) != _joints.end())
         {
         {
-            joint = joint->getParent();
-            if (std::find(joints.begin(), joints.end(), joint) == joints.end())
-                joints.push_back(joint);
+            rootJoint = parent;
         }
         }
+        parent = parent->getParent();
+    }
 
 
-        unsigned int jointCount = joints.size();
-        unsigned int boneCount = _joints.size();
+    // If the root joint has a parent node, temporarily detach it so that its transform is
+    // not included in the bounding volume calculation below
+    Node* rootJointParent = rootJoint->getParent();
+    if (rootJointParent)
+    {
+        rootJointParent->removeChild(rootJoint);
+    }
 
 
-        std::vector<AnimationChannel*> channels;
-        std::vector<Node*> channelTargets;
-        std::vector<Curve*> curves;
+    unsigned int jointCount = _joints.size();
+    unsigned int vertexCount = _mesh->getVertexCount();
 
 
-        // Construct a list of all animation channels that target the joints affecting this mesh skin
-        for (unsigned int i = 0; i < jointCount; ++i)
-        {
-            joint = joints[i];
+    DEBUGPRINT("> %d joints found.\n", jointCount);
+
+    std::vector<AnimationChannel*> channels;
+    std::vector<Node*> channelTargets;
+    std::vector<Curve*> curves;
+    std::vector<Vector3> vertices;
+    _jointBounds.resize(jointCount);
 
 
-            // Find all animations that target this joint
-            Animations* animations = GPBFile::getInstance()->getAnimations();
-            for (unsigned int j = 0, animationCount = animations->getAnimationCount(); j < animationCount; ++j)
+    // Construct a list of all animation channels that target the joints affecting this mesh skin
+    DEBUGPRINT("> Collecting animations...\n");
+    DEBUGPRINT("> 0%%\r");
+    for (unsigned int i = 0; i < jointCount; ++i)
+    {
+        joint = _joints[i];
+
+        // Find all animations that target this joint
+        Animations* animations = GPBFile::getInstance()->getAnimations();
+        for (unsigned int j = 0, animationCount = animations->getAnimationCount(); j < animationCount; ++j)
+        {
+            Animation* animation = animations->getAnimation(j);
+            for (unsigned int k = 0, channelCount = animation->getAnimationChannelCount(); k < channelCount; ++k)
             {
             {
-                Animation* animation = animations->getAnimation(j);
-                for (unsigned int k = 0, channelCount = animation->getAnimationChannelCount(); k < channelCount; ++k)
+                AnimationChannel* channel = animation->getAnimationChannel(k);
+                if (channel->getTargetId() == joint->getId())
                 {
                 {
-                    AnimationChannel* channel = animation->getAnimationChannel(k);
-                    if (channel->getTargetId() == joint->getId())
+                    if (std::find(channels.begin(), channels.end(), channel) == channels.end())
                     {
                     {
-                        if (std::find(channels.begin(), channels.end(), channel) == channels.end())
-                        {
-                            channels.push_back(channel);
-                            channelTargets.push_back(joint);
-                        }
+                        channels.push_back(channel);
+                        channelTargets.push_back(joint);
                     }
                     }
                 }
                 }
             }
             }
-
-            // TODO: Calculate local (non-transformed/non-animated) bounding volumes for each joint that can be used to 
-            // do more precise bounds checking for skinned meshes at runtime.
-            // Find all vertices that this joint influences
-            /*vertices.clear();
-            for (unsigned int j = 0, count = _mesh->getVertexCount(); j < count; ++j)
-            {
-                const Vertex& v = _mesh->getVertex(j);
-                if (v.blendIndices.x == i || v.blendIndices.y == i || v.blendIndices.z == i || v.blendIndices.w == i)
-                {
-                    vertices.push_back(const_cast<Vertex*>(&v));
-                }
-            }*/
         }
         }
 
 
-        // Create a Curve for each animation channel
-        float maxDuration = 0.0f;
-        for (unsigned int i = 0, channelCount = channels.size(); i < channelCount; ++i)
+        // Calculate the local bounding volume for this joint
+        vertices.clear();
+        BoundingSphere jointSphere;
+        for (unsigned int j = 0; j < vertexCount; ++j)
         {
         {
-            AnimationChannel* channel = channels[i];
-
-            const std::vector<float>& keyTimes = channel->getKeyTimes();
-            unsigned int keyCount = keyTimes.size();
-            if (keyCount == 0)
-                continue;
+            const Vertex& v = _mesh->getVertex(j);
 
 
-            // Create a curve for this animation channel
-            Curve* curve = NULL;
-            switch (channel->getTargetAttribute())
+            if ((v.blendIndices.x == i && !ISZERO(v.blendWeights.x)) ||
+                (v.blendIndices.y == i && !ISZERO(v.blendWeights.y)) ||
+                (v.blendIndices.z == i && !ISZERO(v.blendWeights.z)) ||
+                (v.blendIndices.w == i && !ISZERO(v.blendWeights.w)))
             {
             {
-            case Transform::ANIMATE_SCALE_ROTATE_TRANSLATE:
-                curve = new Curve(keyCount, 10);
-                curve->addQuaternionOffset(3);
-                break;
+                vertices.push_back(v.position);
+                jointSphere.center.add(v.position);
             }
             }
-            if (curve == NULL)
+        }
+        if (vertices.size() > 0)
+        {
+            jointSphere.center.scale(1.0f / (float)vertices.size());
+            for (unsigned int j = 0, jointVertexCount = vertices.size(); j < jointVertexCount; ++j)
             {
             {
-                // Unsupported/not implemented curve type 
-                continue;
+                float d = jointSphere.center.distanceSquared(vertices[j]);
+                if (d > jointSphere.radius)
+                    jointSphere.radius = d;
             }
             }
+            jointSphere.radius = sqrtf(jointSphere.radius);
+        }
+        _jointBounds[i] = jointSphere;
 
 
-            // Copy key values into a temporary array
-            unsigned int keyValuesCount = channel->getKeyValues().size();
-            float* keyValues = new float[keyValuesCount];
-            for (unsigned int j = 0; j < keyValuesCount; ++j)
-                keyValues[j] = channel->getKeyValues()[j];
-
-            // Determine animation duration
-            float startTime = keyTimes[0];
-            float duration = keyTimes[keyCount-1] - startTime;
-            if (duration > maxDuration)
-                maxDuration = duration;
-
-            // Set curve points
-            float* keyValuesPtr = keyValues;
-            for (unsigned int j = 0; j < keyCount; ++j)
-            {
-                // Store time normalized, between 0-1
-                float t = (keyTimes[j] - startTime) / duration;
+        DEBUGPRINT("> %d%%\r", (int)((float)(i+1) / (float)jointCount * 100.0f));
+    }
+    DEBUGPRINT("\n");
 
 
-                // Set the curve point
-                // TODO: Handle other interpolation types
-                curve->setPoint(j, t, keyValuesPtr, gameplay::Curve::LINEAR);
+    unsigned int channelCount = channels.size();
 
 
-                // Move to the next point on the curve
-                keyValuesPtr += curve->getComponentCount();
-            }
+    // Create a Curve for each animation channel
+    float maxDuration = 0.0f;
+    DEBUGPRINT("> Populating animation curve data...\n");
+    DEBUGPRINT("> 0%%\r");
+    for (unsigned int i = 0; i < channelCount; ++i)
+    {
+        AnimationChannel* channel = channels[i];
 
 
-            delete[] keyValues;
-            keyValues = NULL;
+        const std::vector<float>& keyTimes = channel->getKeyTimes();
+        unsigned int keyCount = keyTimes.size();
+        if (keyCount == 0)
+            continue;
 
 
-            curves.push_back(curve);
+        // Create a curve for this animation channel
+        Curve* curve = NULL;
+        switch (channel->getTargetAttribute())
+        {
+        case Transform::ANIMATE_SCALE_ROTATE_TRANSLATE:
+            curve = new Curve(keyCount, 10);
+            curve->addQuaternionOffset(3);
+            break;
         }
         }
-
-        // Compute an all-encompassing bounding volume for the MeshSkin that contains all possible vertex
-        // positions for all animations targetting the skin.
-        //
-        // This is accomplished through the following steps:
-        //
-        //  - Step over time in small increments (60 fps ~= 17 ms)
-        //  - For each time interval:
-        //     - For each animation channel:
-        //        - Evalulate the curve at the current time
-        //        - store the result in a local transform for the target joint (SRT)
-        //     - Calculate final matrix pallette of resolved world joint transforms (multplying by parent joints)
-        //     - For each vertex in the mesh:
-        //        - Calculate final vertex position using skinning w/ blendindices and blendweights and the matrix pallette
-        //        - Update the bounding volume of the MeshSkin based on the calculated vertex position
-        //
-        // First backup existing node transforms so we can restore them when we are finished
-        Matrix* oldTransforms = new Matrix[boneCount];
-        for (unsigned int i = 0; i < boneCount; ++i)
+        if (curve == NULL)
         {
         {
-            memcpy(oldTransforms[i].m, joints[i]->getTransformMatrix().m, 16 * sizeof(float));
+            // Unsupported/not implemented curve type 
+            continue;
         }
         }
 
 
-        float srt[10];
-        Matrix temp;
-        Matrix* jointTransforms = new Matrix[boneCount];
-        _mesh->bounds.min.set(FLT_MAX, FLT_MAX, FLT_MAX);
-        _mesh->bounds.max.set(FLT_MIN, FLT_MIN, FLT_MIN);
-        float time = 0.0f;
-        while (time < maxDuration)
+        // Copy key values into a temporary array
+        unsigned int keyValuesCount = channel->getKeyValues().size();
+        float* keyValues = new float[keyValuesCount];
+        for (unsigned int j = 0; j < keyValuesCount; ++j)
+            keyValues[j] = channel->getKeyValues()[j];
+
+        // Determine animation duration
+        float startTime = keyTimes[0];
+        float duration = keyTimes[keyCount-1] - startTime;
+        if (duration > maxDuration)
+            maxDuration = duration;
+
+        // Set curve points
+        float* keyValuesPtr = keyValues;
+        for (unsigned int j = 0; j < keyCount; ++j)
         {
         {
-            // Evaluate joint transforms at this time interval
-            for (unsigned int i = 0, curveCount = curves.size(); i < curveCount; ++i)
-            {
-                Node* joint = channelTargets[i];
-                Curve* curve = curves[i];
-
-                // Evalulate the curve at this time to get the new value
-                float tn = time / maxDuration;
-                if (tn > 1.0f)
-                    tn = 1.0f;
-                curve->evaluate(tn, srt);
-
-                // Update the joint's local transform
-                Matrix::createTranslation(srt[7], srt[8], srt[9], temp.m);
-                temp.rotate(*((Quaternion*)&srt[3]));
-                temp.scale(srt[0], srt[1], srt[2]);
-                joint->setTransformMatrix(temp.m);
-            }
+            // Store time normalized, between 0-1
+            float t = (keyTimes[j] - startTime) / duration;
 
 
-            // Store the final matrix pallette of resovled world space joint matrices
-            std::list<Matrix>::const_iterator bindPoseItr = _bindPoses.begin();
-            for (unsigned int i = 0; i < boneCount; ++i, bindPoseItr++)
-            {
-                Matrix& m = jointTransforms[i];
-                Matrix::multiply(joints[i]->getWorldMatrix().m, bindPoseItr->m, m.m);
-                Matrix::multiply(m.m, _bindShape, m.m);
-            }
+            // Set the curve point
+            // TODO: Handle other interpolation types
+            curve->setPoint(j, t, keyValuesPtr, gameplay::Curve::LINEAR);
 
 
-            // Loop through all vertices in the mesh and calculate the final animated position
-            // at this time interval using the matrix pallette and blend indices/weights information.
-            Vector3 skinnedPos;
-            Vector3 tempPos;
-            int blendIndices[4];
-            float blendWeights[4];
-            for (unsigned int i = 0, vertexCount = _mesh->getVertexCount(); i < vertexCount; ++i)
-            {
-                const Vertex& v = _mesh->getVertex(i);
-
-                // Get blend indices
-                blendIndices[0] = (int)v.blendIndices.x;
-                blendIndices[1] = (int)v.blendIndices.y;
-                blendIndices[2] = (int)v.blendIndices.z;
-                blendIndices[3] = (int)v.blendIndices.w;
-
-                // Get blend weights
-                blendWeights[0] = v.blendWeights.x;
-                blendWeights[1] = v.blendWeights.y;
-                blendWeights[2] = v.blendWeights.z;
-                blendWeights[3] = v.blendWeights.w;
-
-                // Skin this vertex using the standard vertex skinning algorithm
-                skinnedPos.set(0, 0, 0);
-                for (unsigned int j = 0; j < 4; ++j)
-                {
-                    if (blendIndices[j] >= 0 && blendIndices[j] < (int)boneCount)
-                    {
-                        jointTransforms[blendIndices[j]].transformPoint(v.position, &tempPos);
-                        tempPos.scale(blendWeights[j]);
-                        skinnedPos.add(tempPos);
-                    }
-                }
+            // Move to the next point on the curve
+            keyValuesPtr += curve->getComponentCount();
+        }
 
 
-                // Update the bounding box information for this MeshSkin
-                if (skinnedPos.x < _mesh->bounds.min.x)
-                    _mesh->bounds.min.x = skinnedPos.x;
-                if (skinnedPos.y < _mesh->bounds.min.y)
-                    _mesh->bounds.min.y = skinnedPos.y;
-                if (skinnedPos.z < _mesh->bounds.min.z)
-                    _mesh->bounds.min.z = skinnedPos.z;
-                if (skinnedPos.x > _mesh->bounds.max.x)
-                    _mesh->bounds.max.x = skinnedPos.x;
-                if (skinnedPos.y > _mesh->bounds.max.y)
-                    _mesh->bounds.max.y = skinnedPos.y;
-                if (skinnedPos.z > _mesh->bounds.max.z)
-                    _mesh->bounds.max.z = skinnedPos.z;
-            }
+        delete[] keyValues;
+        keyValues = NULL;
 
 
-            // Increment time by 1/60th of second (~ 17 ms)
-            time += 170.0f;
-        }
+        curves.push_back(curve);
 
 
-        // Compute bounding sphere info for the skin. This computation is not very accurate since it
-        // creates the bounding sphere from the bounding box info - so it will not normally provide a
-        // tight fit. However, bounding volumes for mesh skins are very approximate anyway and only
-        // useful as a very broad/high level first test
-        Vector3::add(_mesh->bounds.min, _mesh->bounds.max, &_mesh->bounds.center);
-        _mesh->bounds.center.scale(0.5f);
-        _mesh->bounds.radius = _mesh->bounds.center.distance(_mesh->bounds.max);
+        DEBUGPRINT("> %d%%\r", (int)((float)(i+1) / (float)channelCount * 100.0f));
+    }
+    DEBUGPRINT("\n");
 
 
-        // Restore original joint transforms
-        for (unsigned int i = 0; i < boneCount; ++i)
-        {
-            joints[i]->setTransformMatrix(oldTransforms[i].m);
-        }
+    // Compute a total combined bounding volume for the MeshSkin that contains all possible
+    // vertex positions for all animations targetting the skin. This rough approximation allows
+    // us to store a volume that can be used for rough intersection tests (such as for visibility
+    // determination) efficiently at runtime.
 
 
-        // Cleanup
+    // Backup existing node transforms so we can restore them when we are finished
+    Matrix* oldTransforms = new Matrix[jointCount];
+    for (unsigned int i = 0; i < jointCount; ++i)
+    {
+        memcpy(oldTransforms[i].m, _joints[i]->getTransformMatrix().m, 16 * sizeof(float));
+    }
+
+    float time = 0.0f;
+    float srt[10];
+    Matrix temp;
+    Matrix* jointTransforms = new Matrix[jointCount];
+    _mesh->bounds.min.set(FLT_MAX, FLT_MAX, FLT_MAX);
+    _mesh->bounds.max.set(FLT_MIN, FLT_MIN, FLT_MIN);
+    _mesh->bounds.center.set(0, 0, 0);
+    _mesh->bounds.radius = 0;
+    Vector3 skinnedPos;
+    Vector3 tempPos;
+    DEBUGPRINT("> Animating joints...\n");
+    DEBUGPRINT("> 0%%\r");
+    BoundingSphere finalSphere;
+    while (time <= maxDuration)
+    {
+        // Evaluate joint transforms at this time interval
         for (unsigned int i = 0, curveCount = curves.size(); i < curveCount; ++i)
         for (unsigned int i = 0, curveCount = curves.size(); i < curveCount; ++i)
         {
         {
-            delete curves[i];
+            Node* joint = channelTargets[i];
+            Curve* curve = curves[i];
+
+            // Evalulate the curve at this time to get the new value
+            float tn = time / maxDuration;
+            if (tn > 1.0f)
+                tn = 1.0f;
+            curve->evaluate(tn, srt);
+
+            // Update the joint's local transform
+            Matrix::createTranslation(srt[7], srt[8], srt[9], temp.m);
+            temp.rotate(*((Quaternion*)&srt[3]));
+            temp.scale(srt[0], srt[1], srt[2]);
+            joint->setTransformMatrix(temp.m);
         }
         }
-        delete[] oldTransforms;
-        delete[] jointTransforms;
-    }
-}
 
 
-void MeshSkin::setBindShape(const float data[])
-{
-    for (int i = 0; i < 16; i++)
-    {
-        _bindShape[i] = data[i];
-    }
-}
+        // Store the final matrix pallette of resovled world space joint matrices
+        std::vector<Matrix>::const_iterator bindPoseItr = _bindPoses.begin();
+        for (unsigned int i = 0; i < jointCount; ++i, bindPoseItr++)
+        {
+            BoundingSphere sphere = _jointBounds[i];
+            if (ISZERO(sphere.radius))
+                continue;
 
 
-void MeshSkin::setVertexInfluenceCount(unsigned int count)
-{
-    _vertexInfluenceCount = count;
-}
+            Matrix& m = jointTransforms[i];
+            Matrix::multiply(_joints[i]->getWorldMatrix().m, bindPoseItr->m, m.m);
+            Matrix::multiply(m.m, _bindShape, m.m);
 
 
-void MeshSkin::setJointNames(const std::list<std::string>& list)
-{
-    _jointNames = list;
-}
+            // Get a world-space bounding sphere for this joint
+            sphere = transformBoundingSphere(sphere, m);
+            if (ISZERO(finalSphere.radius))
+                finalSphere = sphere;
+            else
+                finalSphere = mergeSpheres(finalSphere, sphere);
+        }
 
 
-const std::list<std::string>& MeshSkin::getJointNames()
-{
-    return _jointNames;
-}
+        // Increment time by 1/30th of second (~ 33 ms)
+        if (time < maxDuration && (time + 33.0f) > maxDuration)
+            time = maxDuration;
+        else
+            time += 33.0f;
 
 
-void MeshSkin::setJoints(const std::list<Node*>& list)
-{
-    _joints = list;
-}
+        DEBUGPRINT("> %d%%\r", (int)(time / maxDuration * 100.0f));
+    }
+    DEBUGPRINT("\n");
 
 
-void MeshSkin::setBindPoses(std::vector<Matrix>& list)
-{
-    for (std::vector<Matrix>::iterator i = list.begin(); i != list.end(); i++)
+    // Update the bounding sphere for the mesh
+    _mesh->bounds.center = finalSphere.center;
+    _mesh->bounds.radius = finalSphere.radius;
+
+    // Restore original joint transforms
+    for (unsigned int i = 0; i < jointCount; ++i)
     {
     {
-        _bindPoses.push_back(*i);
+        _joints[i]->setTransformMatrix(oldTransforms[i].m);
     }
     }
-}
 
 
-bool MeshSkin::hasJoint(const char* id)
-{
-    for (std::list<std::string>::iterator i = _jointNames.begin(); i != _jointNames.end(); i++)
+    // Cleanup
+    for (unsigned int i = 0, curveCount = curves.size(); i < curveCount; ++i)
     {
     {
-        if (equals(*i, id))
-        {
-            return true;
-        }
+        delete curves[i];
+    }
+    delete[] oldTransforms;
+    delete[] jointTransforms;
+
+    // Restore removed joints
+    if (rootJointParent)
+    {
+        rootJointParent->addChild(rootJoint);
     }
     }
-    return false;
 }
 }
 
 
 }
 }

+ 29 - 6
gameplay-encoder/src/MeshSkin.h

@@ -14,6 +14,27 @@ namespace gameplay
 class Node;
 class Node;
 class Mesh;
 class Mesh;
 
 
+struct BoundingSphere
+{
+    Vector3 center;
+    float radius;
+
+    BoundingSphere() : radius(0)
+    {
+    }
+
+    BoundingSphere(const BoundingSphere& copy)
+    {
+        set(copy);
+    }
+
+    void set(const BoundingSphere& copy)
+    {
+        center = copy.center;
+        radius = copy.radius;
+    }
+};
+
 class MeshSkin : public Object
 class MeshSkin : public Object
 {
 {
     friend class Model;
     friend class Model;
@@ -39,11 +60,11 @@ public:
 
 
     void setVertexInfluenceCount(unsigned int count);
     void setVertexInfluenceCount(unsigned int count);
 
 
-    void setJointNames(const std::list<std::string>& list);
+    void setJointNames(const std::vector<std::string>& list);
 
 
-    const std::list<std::string>& getJointNames();
+    const std::vector<std::string>& getJointNames();
 
 
-    void setJoints(const std::list<Node*>& list);
+    void setJoints(const std::vector<Node*>& list);
 
 
     void setBindPoses(std::vector<Matrix>& list);
     void setBindPoses(std::vector<Matrix>& list);
 
 
@@ -62,11 +83,13 @@ private:
 
 
     Mesh* _mesh;
     Mesh* _mesh;
     float _bindShape[16];
     float _bindShape[16];
-    std::list<Node*> _joints;
-    std::list<Matrix> _bindPoses;
-    std::list<std::string> _jointNames;
+    std::vector<Node*> _joints;
+    std::vector<Matrix> _bindPoses;
+    std::vector<std::string> _jointNames;
     unsigned int _vertexInfluenceCount;
     unsigned int _vertexInfluenceCount;
+    std::vector<BoundingSphere> _jointBounds;
 };
 };
 
 
 }
 }
+
 #endif
 #endif

+ 2 - 3
gameplay-encoder/src/TTFFontEncoder.cpp

@@ -1,5 +1,5 @@
 #include "TTFFontEncoder.h"
 #include "TTFFontEncoder.h"
-
+#include "GPBFile.h"
 
 
 void drawBitmap(unsigned char* dstBitmap, int x, int y, int dstWidth, unsigned char* srcBitmap, int srcWidth, int srcHeight)
 void drawBitmap(unsigned char* dstBitmap, int x, int y, int dstWidth, unsigned char* srcBitmap, int srcWidth, int srcHeight)
 {
 {
@@ -263,9 +263,8 @@ int writeFont(const char* filename, unsigned int fontSize, const char* id, bool
     
     
     // File header and version.
     // File header and version.
     char fileHeader[9]     = {'«', 'G', 'P', 'B', '»', '\r', '\n', '\x1A', '\n'};
     char fileHeader[9]     = {'«', 'G', 'P', 'B', '»', '\r', '\n', '\x1A', '\n'};
-    char fileVersion[2]    = {1, 0};
     fwrite(fileHeader, sizeof(char), 9, gpbFp);
     fwrite(fileHeader, sizeof(char), 9, gpbFp);
-    fwrite(fileVersion, sizeof(char), 2, gpbFp);
+    fwrite(gameplay::VERSION, sizeof(char), 2, gpbFp);
 
 
     // Write Ref table (for a single font)
     // Write Ref table (for a single font)
     writeUint(gpbFp, 1);                // Ref[] count
     writeUint(gpbFp, 1);                // Ref[] count

+ 1 - 1
gameplay/src/AudioListener.cpp

@@ -106,7 +106,7 @@ void AudioListener::setCamera(Camera* c)
     }
     }
 }
 }
 
 
-void AudioListener::transformChanged(Transform* transform)
+void AudioListener::transformChanged(Transform* transform, long cookie)
 {
 {
     if (transform)
     if (transform)
     {
     {

+ 1 - 1
gameplay/src/AudioListener.h

@@ -122,7 +122,7 @@ private:
     /**
     /**
     * @see Transform::Listener::transformChanged
     * @see Transform::Listener::transformChanged
     */
     */
-    void transformChanged(Transform* transform);
+    void transformChanged(Transform* transform, long cookie);
 
 
     float _gain;
     float _gain;
     Vector3 _position;
     Vector3 _position;

+ 1 - 1
gameplay/src/AudioSource.cpp

@@ -179,7 +179,7 @@ void AudioSource::setNode(Node* node)
     }
     }
 }
 }
 
 
-void AudioSource::transformChanged(Transform* transform)
+void AudioSource::transformChanged(Transform* transform, long cookie)
 {
 {
     alSourcefv(_alSource, AL_POSITION, (const ALfloat*)&transform->getTranslation());
     alSourcefv(_alSource, AL_POSITION, (const ALfloat*)&transform->getTranslation());
 }
 }

+ 1 - 1
gameplay/src/AudioSource.h

@@ -160,7 +160,7 @@ private:
     /**
     /**
      * @see Transform::Listener::transformChanged
      * @see Transform::Listener::transformChanged
      */
      */
-    void transformChanged(Transform* transform);
+    void transformChanged(Transform* transform, long cookie);
 
 
     ALuint _alSource;
     ALuint _alSource;
     AudioBuffer* _buffer;
     AudioBuffer* _buffer;

+ 2 - 2
gameplay/src/Base.h

@@ -48,12 +48,12 @@ extern void printError(const char* format, ...);
 #define LOG_ERROR(x) \
 #define LOG_ERROR(x) \
     { \
     { \
         printError(x); \
         printError(x); \
-        assert(0); \
+        assert(#x == 0); \
     }
     }
 #define LOG_ERROR_VARG(x, ...) \
 #define LOG_ERROR_VARG(x, ...) \
     { \
     { \
         printError(x, __VA_ARGS__); \
         printError(x, __VA_ARGS__); \
-        assert(0); \
+        assert(#x == 0); \
     }
     }
 
 
 // Warning macro
 // Warning macro

+ 5 - 7
gameplay/src/BoundingSphere.cpp

@@ -163,17 +163,15 @@ bool BoundingSphere::isEmpty() const
 void BoundingSphere::merge(const BoundingSphere& sphere)
 void BoundingSphere::merge(const BoundingSphere& sphere)
 {
 {
     // Calculate the distance between the two centers.
     // Calculate the distance between the two centers.
-    float vx = sphere.center.x - center.x;
-    float vy = sphere.center.y - center.y;
-    float vz = sphere.center.z - center.z;
+    float vx = center.x - sphere.center.x;
+    float vy = center.y - sphere.center.y;
+    float vz = center.z - sphere.center.z;
     float d = sqrtf(vx * vx + vy * vy + vz * vz);
     float d = sqrtf(vx * vx + vy * vy + vz * vz);
 
 
     // If one sphere is contained inside the other, set to the larger sphere.
     // If one sphere is contained inside the other, set to the larger sphere.
     if (d <= (sphere.radius - radius))
     if (d <= (sphere.radius - radius))
     {
     {
-        center.x = sphere.center.x;
-        center.y = sphere.center.y;
-        center.z = sphere.center.z;
+        center = sphere.center;
         radius = sphere.radius;
         radius = sphere.radius;
         return;
         return;
     }
     }
@@ -189,7 +187,7 @@ void BoundingSphere::merge(const BoundingSphere& sphere)
     vz *= dI;
     vz *= dI;
 
 
     // Calculate the new radius.
     // Calculate the new radius.
-    float r = (radius + radius + d) * 0.5f;
+    float r = (radius + sphere.radius + d) * 0.5f;
 
 
     // Calculate the new center.
     // Calculate the new center.
     float scaleFactor = (r - sphere.radius);
     float scaleFactor = (r - sphere.radius);

+ 1 - 1
gameplay/src/Camera.cpp

@@ -346,7 +346,7 @@ void Camera::pickRay(const Viewport* viewport, float x, float y, Ray* dst)
 }
 }
 
 
 
 
-void Camera::transformChanged(Transform* transform)
+void Camera::transformChanged(Transform* transform, long cookie)
 {
 {
     _dirtyBits |= CAMERA_DIRTY_VIEW | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
     _dirtyBits |= CAMERA_DIRTY_VIEW | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
 }
 }

+ 1 - 1
gameplay/src/Camera.h

@@ -252,7 +252,7 @@ private:
     /**
     /**
      * @see Transform::Listener::transformChanged
      * @see Transform::Listener::transformChanged
      */
      */
-    void transformChanged(Transform* transform);
+    void transformChanged(Transform* transform, long cookie);
 
 
     /**
     /**
      * Sets the node associated with this camera.
      * Sets the node associated with this camera.

+ 9 - 30
gameplay/src/Joint.cpp

@@ -4,12 +4,13 @@
 
 
 #include "Base.h"
 #include "Base.h"
 #include "Joint.h"
 #include "Joint.h"
+#include "MeshSkin.h"
 
 
 namespace gameplay
 namespace gameplay
 {
 {
 
 
 Joint::Joint(const char* id)
 Joint::Joint(const char* id)
-    : Node(id), _jointMatrixDirty(true), _skin(NULL)
+    : Node(id), _jointMatrixDirty(true), _skinCount(0)
 {
 {
 }
 }
 
 
@@ -31,20 +32,21 @@ void Joint::transformChanged()
 {
 {
     Node::transformChanged();
     Node::transformChanged();
 
 
-    //const char* id = _id.c_str();
     _jointMatrixDirty = true;
     _jointMatrixDirty = true;
 }
 }
 
 
 void Joint::updateJointMatrix(const Matrix& bindShape, Vector4* matrixPalette)
 void Joint::updateJointMatrix(const Matrix& bindShape, Vector4* matrixPalette)
 {
 {
-    //const char* id = _id.c_str();
-
-    if (_jointMatrixDirty)
+    // Note: If more than one MeshSkin influences this Joint, we need to skip
+    // the _jointMatrixDirty optimization since updateJointMatrix() may be
+    // called multiple times a frame with different bindShape matrices (and
+    // different matrixPallete pointers).
+    if (_skinCount > 1 || _jointMatrixDirty)
     {
     {
         _jointMatrixDirty = false;
         _jointMatrixDirty = false;
 
 
-        Matrix t;
-        Matrix::multiply(getJointMatrix(), getInverseBindPose(), &t);
+        static Matrix t;
+        Matrix::multiply(Node::getWorldMatrix(), getInverseBindPose(), &t);
         Matrix::multiply(t, bindShape, &t);
         Matrix::multiply(t, bindShape, &t);
 
 
         matrixPalette[0].set(t.m[0], t.m[4], t.m[8], t.m[12]);
         matrixPalette[0].set(t.m[0], t.m[4], t.m[8], t.m[12]);
@@ -64,27 +66,4 @@ void Joint::setInverseBindPose(const Matrix& m)
     _jointMatrixDirty = true;
     _jointMatrixDirty = true;
 }
 }
 
 
-const Matrix& Joint::getWorldMatrix() const
-{
-    // If this is the root joint, then we 
-    // also apply the transform of the model
-    // that the skin is attached to to get the
-    // actual world matrix.
-    if (_parent == NULL && _skin != NULL)
-    {
-        Matrix::multiply(_skin->_model->getNode()->getWorldMatrix(), Node::getWorldMatrix(), &_jointWorld);
-    }
-    else
-    {
-        memcpy((void*)_jointWorld.m, Node::getWorldMatrix().m, sizeof(float) * 16);
-    }
-
-    return _jointWorld;
-}
-
-const Matrix& Joint::getJointMatrix() const
-{
-    return Node::getWorldMatrix();
-}
-
 }
 }

+ 2 - 16
gameplay/src/Joint.h

@@ -18,6 +18,7 @@ class Package;
  */
  */
 class Joint : public Node
 class Joint : public Node
 {
 {
+    friend class Node;
     friend class MeshSkin;
     friend class MeshSkin;
     friend class Package;
     friend class Package;
 
 
@@ -35,20 +36,6 @@ public:
      */
      */
     const Matrix& getInverseBindPose() const;
     const Matrix& getInverseBindPose() const;
 
 
-    /**
-     * Gets the world matrix corresponding to this node.
-     *
-     * @return The world matrix of this node.
-     */
-    const Matrix& getWorldMatrix() const;
-
-    /**
-     * Gets the matrix corresponding to this joint.
-     *
-     * @return The matrix of this joint.
-     */
-    const Matrix& getJointMatrix() const;
-
 protected:
 protected:
 
 
     /**
     /**
@@ -83,8 +70,7 @@ protected:
 
 
     Matrix _bindPose;
     Matrix _bindPose;
     bool _jointMatrixDirty;
     bool _jointMatrixDirty;
-    MeshSkin* _skin;
-    mutable Matrix _jointWorld;
+    unsigned int _skinCount;
 };
 };
 
 
 }
 }

+ 77 - 13
gameplay/src/MeshSkin.cpp

@@ -12,7 +12,8 @@
 namespace gameplay
 namespace gameplay
 {
 {
 
 
-MeshSkin::MeshSkin() : _matrixPalette(NULL), _model(NULL)
+MeshSkin::MeshSkin()
+    : _rootJoint(NULL), _matrixPalette(NULL), _model(NULL)
 {
 {
 }
 }
 
 
@@ -33,6 +34,17 @@ void MeshSkin::setBindShape(const float* matrix)
     _bindShape.set(matrix);
     _bindShape.set(matrix);
 }
 }
 
 
+unsigned int MeshSkin::getJointCount() const
+{
+    return _joints.size();
+}
+
+Joint* MeshSkin::getJoint(unsigned int index) const
+{
+    assert(index < _joints.size());
+    return _joints[index];
+}
+
 Joint* MeshSkin::getJoint(const char* id) const
 Joint* MeshSkin::getJoint(const char* id) const
 {
 {
     assert(id);
     assert(id);
@@ -80,8 +92,19 @@ void MeshSkin::setJoint(Joint* joint, unsigned int index)
 {
 {
     assert(index < _joints.size());
     assert(index < _joints.size());
 
 
+    if (_joints[index])
+    {
+        _joints[index]->_skinCount--;
+        SAFE_RELEASE(_joints[index]);
+    }
+
     _joints[index] = joint;
     _joints[index] = joint;
-    _joints[index]->_skin = this;
+
+    if (joint)
+    {
+        joint->addRef();
+        joint->_skinCount++;
+    }
 }
 }
 
 
 Vector4* MeshSkin::getMatrixPalette() const
 Vector4* MeshSkin::getMatrixPalette() const
@@ -99,34 +122,75 @@ unsigned int MeshSkin::getMatrixPaletteSize() const
     return _joints.size() * PALETTE_ROWS;
     return _joints.size() * PALETTE_ROWS;
 }
 }
 
 
-const BoundingBox& MeshSkin::getBoundingBox() const
+Model* MeshSkin::getModel() const
 {
 {
-    return _boundingBox;
+    return _model;
 }
 }
 
 
-void MeshSkin::setBoundingBox(const BoundingBox& box)
+Joint* MeshSkin::getRootJoint() const
 {
 {
-    _boundingBox = box;
+    return _rootJoint;
 }
 }
 
 
-const BoundingSphere& MeshSkin::getBoundingSphere() const
+void MeshSkin::setRootJoint(Joint* joint)
 {
 {
-    return _boundingSphere;
+    if (_rootJoint)
+    {
+        if (_rootJoint->getParent())
+        {
+            _rootJoint->getParent()->removeListener(this);
+        }
+    }
+
+    _rootJoint = joint;
+
+    // If the root joint has a parent node, register for its transformChanged event
+    if (_rootJoint && _rootJoint->getParent())
+    {
+        _rootJoint->getParent()->addListener(this, 1);
+    }
 }
 }
 
 
-void MeshSkin::setBoundingSphere(const BoundingSphere& sphere)
+void MeshSkin::transformChanged(Transform* transform, long cookie)
 {
 {
-    _boundingSphere = sphere;
+    switch (cookie)
+    {
+    case 1:
+        // The direct parent of our joint hierarchy has changed.
+        // Dirty the bounding volume for our model's node. This special
+        // case allows us to have much tighter bounding volumes for
+        // skinned meshes by only considering local skin/joint transformations
+        // during bounding volume computation instead of fully resolved
+        // joint transformations.
+        if (_model && _model->getNode())
+        {
+            _model->getNode()->setBoundsDirty();
+        }
+        break;
+    }
 }
 }
 
 
-Joint* MeshSkin::getJoint(unsigned int index) const
+int MeshSkin::getJointIndex(Joint* joint) const
 {
 {
-    assert(index < _joints.size());
-    return _joints[index];
+    for (unsigned int i = 0, count = _joints.size(); i < count; ++i)
+    {
+        if (_joints[i] == joint)
+        {
+            return (int)i;
+        }
+    }
+
+    return -1;
 }
 }
 
 
 void MeshSkin::clearJoints()
 void MeshSkin::clearJoints()
 {
 {
+    setRootJoint(NULL);
+
+    for (unsigned int i = 0, count = _joints.size(); i < count; ++i)
+    {
+        SAFE_RELEASE(_joints[i]);
+    }
     _joints.clear();
     _joints.clear();
 }
 }
 
 

+ 50 - 10
gameplay/src/MeshSkin.h

@@ -6,6 +6,7 @@
 #define MESHSKIN_H_
 #define MESHSKIN_H_
 
 
 #include "Matrix.h"
 #include "Matrix.h"
+#include "Transform.h"
 
 
 namespace gameplay
 namespace gameplay
 {
 {
@@ -17,7 +18,7 @@ class Joint;
 /**
 /**
  * Represents the skin for a mesh.
  * Represents the skin for a mesh.
  */
  */
-class MeshSkin
+class MeshSkin : public Transform::Listener
 {
 {
     friend class Package;
     friend class Package;
     friend class Model;
     friend class Model;
@@ -39,6 +40,20 @@ public:
      */
      */
     void setBindShape(const float* matrix);
     void setBindShape(const float* matrix);
 
 
+    /**
+     * Returns the number of joints in this MeshSkin.
+     */
+    unsigned int getJointCount() const;
+
+    /**
+     * Returns the joint at the given index.
+     * 
+     * @param index The index.
+     * 
+     * @return The joint.
+     */
+    Joint* getJoint(unsigned int index) const;
+
     /**
     /**
      * Returns the joint with the given ID.
      * Returns the joint with the given ID.
      * 
      * 
@@ -48,6 +63,29 @@ public:
      */
      */
     Joint* getJoint(const char* id) const;
     Joint* getJoint(const char* id) const;
 
 
+    /**
+     * Returns the root most joint for this MeshSkin.
+     *
+     * @return The root joint.
+     */
+    Joint* getRootJoint() const;
+
+    /**
+     * Sets the root joint for this MeshSkin.
+     *
+     * The specified Joint must belong to the joint list for this MeshSkin.
+     *
+     * @param joint The root joint.
+     */
+    void setRootJoint(Joint* joint);
+
+    /**
+     * Returns the index of the specified joint in this MeshSkin.
+     *
+     * @return The index of the joint in this MeshSkin, or -1 if the joint does not belong to this MeshSkin.
+     */
+    int getJointIndex(Joint* joint) const;
+
     /**
     /**
      * Returns the pointer to the Vector4 array for the purpose of binding to a shader.
      * Returns the pointer to the Vector4 array for the purpose of binding to a shader.
      * 
      * 
@@ -64,6 +102,16 @@ public:
      */
      */
     unsigned int getMatrixPaletteSize() const;
     unsigned int getMatrixPaletteSize() const;
 
 
+    /**
+     * Returns our parent Model.
+     */
+    Model* getModel() const;
+
+    /**
+     * Handles transform change events for joints.
+     */
+    void transformChanged(Transform* transform, long cookie);
+
 private:
 private:
 
 
     /**
     /**
@@ -97,17 +145,9 @@ private:
      */
      */
     void clearJoints();
     void clearJoints();
 
 
-    /**
-     * Returns the joint at the given index.
-     * 
-     * @param index The index.
-     * 
-     * @return The joint.
-     */
-    Joint* getJoint(unsigned int index) const;
-
     Matrix _bindShape;
     Matrix _bindShape;
     std::vector<Joint*> _joints;
     std::vector<Joint*> _joints;
+    Joint* _rootJoint;
 
 
     // Pointer to the array of palette matrices.
     // Pointer to the array of palette matrices.
     // This array is passed to the vertex shader as a uniform.
     // This array is passed to the vertex shader as a uniform.

+ 56 - 172
gameplay/src/Node.cpp

@@ -5,6 +5,7 @@
 #include "Base.h"
 #include "Base.h"
 #include "Node.h"
 #include "Node.h"
 #include "Scene.h"
 #include "Scene.h"
+#include "Joint.h"
 
 
 #define NODE_DIRTY_WORLD 1
 #define NODE_DIRTY_WORLD 1
 #define NODE_DIRTY_BOUNDS 2
 #define NODE_DIRTY_BOUNDS 2
@@ -16,14 +17,12 @@ namespace gameplay
 Node::Node(const char* id)
 Node::Node(const char* id)
     : _scene(NULL), _firstChild(NULL), _nextSibling(NULL), _prevSibling(NULL), _parent(NULL), _childCount(NULL),
     : _scene(NULL), _firstChild(NULL), _nextSibling(NULL), _prevSibling(NULL), _parent(NULL), _childCount(NULL),
     _camera(NULL), _light(NULL), _model(NULL), _audioSource(NULL), _particleEmitter(NULL), _physicsRigidBody(NULL), 
     _camera(NULL), _light(NULL), _model(NULL), _audioSource(NULL), _particleEmitter(NULL), _physicsRigidBody(NULL), 
-    _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true), _boundsType(NONE)
+    _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true)
 {
 {
     if (id)
     if (id)
     {
     {
         _id = id;
         _id = id;
     }
     }
-
-    memset(&_bounds, 0, sizeof(_bounds));
 }
 }
 
 
 Node::Node(const Node& node)
 Node::Node(const Node& node)
@@ -35,17 +34,6 @@ Node::~Node()
 {
 {
     removeAllChildren();
     removeAllChildren();
 
 
-    // Free bounding volume.
-    switch (_boundsType)
-    {
-    case BOX:
-        SAFE_DELETE(_bounds.box);
-        break;
-    case SPHERE:
-        SAFE_DELETE(_bounds.sphere);
-        break;
-    }
-
     SAFE_RELEASE(_camera);
     SAFE_RELEASE(_camera);
     SAFE_RELEASE(_light);
     SAFE_RELEASE(_light);
     SAFE_RELEASE(_model);
     SAFE_RELEASE(_model);
@@ -487,6 +475,7 @@ void Node::transformChanged()
     _dirtyBits |= NODE_DIRTY_WORLD | NODE_DIRTY_BOUNDS;
     _dirtyBits |= NODE_DIRTY_WORLD | NODE_DIRTY_BOUNDS;
 
 
     // Notify our children that their transform has also changed (since transforms are inherited).
     // Notify our children that their transform has also changed (since transforms are inherited).
+    Joint* rootJoint = NULL;
     Node* n = getFirstChild();
     Node* n = getFirstChild();
     while (n)
     while (n)
     {
     {
@@ -497,6 +486,16 @@ void Node::transformChanged()
     Transform::transformChanged();
     Transform::transformChanged();
 }
 }
 
 
+void Node::setBoundsDirty()
+{
+    // Mark ourself and our parent nodes as dirty
+    _dirtyBits |= NODE_DIRTY_BOUNDS;
+
+    // Mark our parent bounds as dirty as well
+    if (_parent)
+        _parent->setBoundsDirty();
+}
+
 Camera* Node::getCamera() const
 Camera* Node::getCamera() const
 {
 {
     return _camera;
     return _camera;
@@ -536,7 +535,7 @@ void Node::setLight(Light* light)
             _light->setNode(NULL);
             _light->setNode(NULL);
             SAFE_RELEASE(_light);
             SAFE_RELEASE(_light);
         }
         }
-        
+
         _light = light;
         _light = light;
 
 
         if (_light)
         if (_light)
@@ -572,195 +571,80 @@ Model* Node::getModel() const
     return _model;
     return _model;
 }
 }
 
 
-const BoundingBox& Node::getBoundingBox() const
+const BoundingSphere& Node::getBoundingSphere() const
 {
 {
-    if (_boundsType != BOX)
-    {
-        return BoundingBox::empty();
-    }
-
     if (_dirtyBits & NODE_DIRTY_BOUNDS)
     if (_dirtyBits & NODE_DIRTY_BOUNDS)
     {
     {
         _dirtyBits &= ~NODE_DIRTY_BOUNDS;
         _dirtyBits &= ~NODE_DIRTY_BOUNDS;
 
 
-        // Get the local bounding box
+        const Matrix& worldMatrix = getWorldMatrix();
+
+        // Start with our local bounding sphere
+        // TODO: Incorporate bounds from entities other than mesh (i.e. emitters, audiosource, etc)
+        bool empty = true;
         if (_model && _model->getMesh())
         if (_model && _model->getMesh())
         {
         {
-            _bounds.box->set(_model->getMesh()->getBoundingBox());
+            _bounds.set(_model->getMesh()->getBoundingSphere());
+            empty = false;
         }
         }
         else
         else
         {
         {
-            _bounds.box->set(Vector3::zero(), Vector3::zero());
+            // Empty bounding sphere, set the world translation with zero radius
+            worldMatrix.getTranslation(&_bounds.center);
+            _bounds.radius = 0;
         }
         }
 
 
-        bool empty = _bounds.box->isEmpty();
+        // Transform the sphere (if not empty) into world space.
         if (!empty)
         if (!empty)
         {
         {
-            // Transform the box into world space.
-            _bounds.box->transform(getWorldMatrix());
-        }
-
-        // Merge this world-space bounding box with our childrens' bounding volumes.
-        for (Node* n = getFirstChild(); n != NULL; n = n->getNextSibling())
-        {
-            switch (n->_boundsType)
+            bool applyWorldTransform = true;
+            if (_model && _model->getSkin())
             {
             {
-            case BOX:
-                {
-                    const BoundingBox& childBox = n->getBoundingBox();
-                    if (!childBox.isEmpty())
-                    {
-                        if (empty)
-                        {
-                            _bounds.box->set(childBox);
-                            empty = false;
-                        }
-                        else
-                        {
-                            _bounds.box->merge(childBox);
-                        }
-                    }
-                }
-                break;
-            case SPHERE:
+                // Special case: If the root joint of our mesh skin is parented by any nodes, 
+                // multiply the world matrix of the root joint's parent by this node's
+                // world matrix. This computes a final world matrix used for transforming this
+                // node's bounding volume. This allows us to store a much smaller bounding
+                // volume approximation than would otherwise be possible for skinned meshes,
+                // since joint parent nodes that are not in the matrix pallette do not need to
+                // be considered as directly transforming vertices on the GPU (they can instead
+                // be applied directly to the bounding volume transformation below).
+                Node* jointParent = _model->getSkin()->getRootJoint()->getParent();
+                if (jointParent)
                 {
                 {
-                    const BoundingSphere& childSphere = n->getBoundingSphere();
-                    if (!childSphere.isEmpty())
-                    {
-                        if (empty)
-                        {
-                            _bounds.box->set(childSphere);
-                            empty = false;
-                        }
-                        else
-                        {
-                            _bounds.box->merge(childSphere);
-                        }
-                    }
+                    // TODO: Should we protect against the case where joints are nested directly
+                    // in the node hierachy of the model (this is normally not the case)?
+                    Matrix boundsMatrix;
+                    Matrix::multiply(getWorldMatrix(), jointParent->getWorldMatrix(), &boundsMatrix);
+                    _bounds.transform(boundsMatrix);
+                    applyWorldTransform = false;
                 }
                 }
-                break;
             }
             }
-        }
-    }
-
-    return *_bounds.box;
-}
-
-const BoundingSphere& Node::getBoundingSphere() const
-{
-    if (_boundsType != SPHERE)
-    {
-        return BoundingSphere::empty();
-    }
-
-    if (_dirtyBits & NODE_DIRTY_BOUNDS)
-    {
-        _dirtyBits &= ~NODE_DIRTY_BOUNDS;
-
-        // Get the local bounding sphere
-        if (_model && _model->getMesh())
-        {
-            _bounds.sphere->set(_model->getMesh()->getBoundingSphere());
-        }
-        else
-        {
-            _bounds.sphere->set(Vector3::zero(), 0);
-        }
-
-        bool empty = _bounds.sphere->isEmpty();
-        if (!empty)
-        {
-            // Transform the sphere into world space.
-            _bounds.sphere->transform(getWorldMatrix());
+            if (applyWorldTransform)
+            {
+                _bounds.transform(getWorldMatrix());
+            }
         }
         }
 
 
         // Merge this world-space bounding sphere with our childrens' bounding volumes.
         // Merge this world-space bounding sphere with our childrens' bounding volumes.
         for (Node* n = getFirstChild(); n != NULL; n = n->getNextSibling())
         for (Node* n = getFirstChild(); n != NULL; n = n->getNextSibling())
         {
         {
-            switch (n->getBoundsType())
+            const BoundingSphere& childSphere = n->getBoundingSphere();
+            if (!childSphere.isEmpty())
             {
             {
-            case BOX:
+                if (empty)
                 {
                 {
-                    const BoundingBox& childBox = n->getBoundingBox();
-                    if (!childBox.isEmpty())
-                    {
-                        if (empty)
-                        {
-                            _bounds.sphere->set(childBox);
-                            empty = false;
-                        }
-                        else
-                        {
-                            _bounds.sphere->merge(childBox);
-                        }
-                    }
+                    _bounds.set(childSphere);
+                    empty = false;
                 }
                 }
-                break;
-            case SPHERE:
+                else
                 {
                 {
-                    const BoundingSphere& childSphere = n->getBoundingSphere();
-                    if (!childSphere.isEmpty())
-                    {
-                        if (empty)
-                        {
-                            _bounds.sphere->set(childSphere);
-                            empty = false;
-                        }
-                        else
-                        {
-                            _bounds.sphere->merge(childSphere);
-                        }
-                    }
+                    _bounds.merge(childSphere);
                 }
                 }
-                break;
             }
             }
         }
         }
     }
     }
 
 
-    return *_bounds.sphere;
-}
-
-Node::BoundsType Node::getBoundsType() const
-{
-    return _boundsType;
-}
-
-void Node::setBoundsType(Node::BoundsType type)
-{
-    if (type != _boundsType)
-    {
-        switch (_boundsType)
-        {
-        case BOX:
-            SAFE_DELETE(_bounds.box);
-            break;
-        case SPHERE:
-            SAFE_DELETE(_bounds.sphere);
-            break;
-        }
-        memset(&_bounds, 0, sizeof(_bounds));
-
-        _boundsType = type;
-
-        switch (_boundsType)
-        {
-        case BOX:
-            _bounds.box = new BoundingBox();
-            break;
-        case SPHERE:
-            _bounds.sphere = new BoundingSphere();
-            break;
-        }
-
-        // We need to dirty the bounds for the parents in our hierarchy when
-        // our bounding volume type changes.
-        Node* parent = getParent();
-        while (parent)
-        {
-            parent->_dirtyBits |= NODE_DIRTY_BOUNDS;
-            parent = parent->getParent();
-        }
-    }
+    return _bounds;
 }
 }
 
 
 AudioSource* Node::getAudioSource() const
 AudioSource* Node::getAudioSource() const

+ 22 - 43
gameplay/src/Node.h

@@ -27,6 +27,7 @@ class Node : public Transform
 {
 {
     friend class Scene;
     friend class Scene;
     friend class Package;
     friend class Package;
+    friend class MeshSkin;
 
 
 public:
 public:
 
 
@@ -39,16 +40,6 @@ public:
         JOINT = 2
         JOINT = 2
     };
     };
 
 
-    /**
-     * Defines types of bounding volumes for nodes.
-     */
-    enum BoundsType
-    {
-        NONE,
-        BOX,
-        SPHERE
-    };
-
     /**
     /**
      * Creates a new node with the specified ID.
      * Creates a new node with the specified ID.
      *
      *
@@ -383,41 +374,26 @@ public:
         float restitution = 0.0f, float linearDamping = 0.0f, float angularDamping = 0.0f);
         float restitution = 0.0f, float linearDamping = 0.0f, float angularDamping = 0.0f);
 
 
     /**
     /**
-     * Returns the bounding box for the Node, in world space.
+     * Returns the bounding sphere for the Node, in world space.
      *
      *
-     * The returned box is only meaningful for nodes who have a
-     * bounds type of BOUNDS_TYPE_BOX, which can be specified
-     * via the setBoundsType method. Additionally, bounding volumes
-     * are only meaningful for nodes that contain data which itself
-     * contains a bounding volume, such as Model/Mesh.
+     * The bounding sphere for a node represents the area, in world
+     * space, that the node contains. This includes the space occupied 
+     * by any child nodes as well as the space occupied by any data
+     * inside the node (such as models).
      *
      *
-     * @return The world-space bounding box for the node.
-     */
-    const BoundingBox& getBoundingBox() const;
-
-    /**
-     * Returns the bounding sphere for the Node, in world space.
+     * Bounding spheres for nodes are rough approximations of the data
+     * contained within a node and they are intended for visibility
+     * testing or first-pass intersection testing only. They are not
+     * appropriate for accurate collision detection since they most often
+     * do not tightly contain a node's content.
      *
      *
-     * The returned sphere is only meaningful for nodes who have a
-     * bounds type of BOUNDS_TYPE_SPHERE, which can be specified
-     * via the setBoundsType method. Additionally, bounding volumes
-     * are only meaningful for nodes that contain data which itself
-     * contains a bounding volume, such as Model/Mesh.
+     * A node that does not occupy any space will return a bounding sphere
+     * with a center point equal to the node translation and a radius of zero.
      *
      *
      * @return The world-space bounding sphere for the node.
      * @return The world-space bounding sphere for the node.
      */
      */
     const BoundingSphere& getBoundingSphere() const;
     const BoundingSphere& getBoundingSphere() const;
 
 
-    /**
-     * Returns the current bounding volume type of the node.
-     */
-    Node::BoundsType getBoundsType() const;
-
-    /**
-     * Sets the bounding volume type of the node.
-     */
-    void setBoundsType(Node::BoundsType type);
-
 protected:
 protected:
 
 
     /**
     /**
@@ -440,10 +416,18 @@ protected:
      */
      */
     void remove();
     void remove();
 
 
+    /**
+     * Called when this Node's transform changes.
+     */
     void transformChanged();
     void transformChanged();
 
 
     void hierarchyChanged();
     void hierarchyChanged();
 
 
+    /**
+     * Marks the bounding volume of the node as dirty.
+     */
+    void setBoundsDirty();
+
     Scene* _scene;
     Scene* _scene;
     std::string _id;
     std::string _id;
     Node* _firstChild;
     Node* _firstChild;
@@ -460,12 +444,7 @@ protected:
     mutable Matrix _world;
     mutable Matrix _world;
     mutable int _dirtyBits;
     mutable int _dirtyBits;
     bool _notifyHierarchyChanged;
     bool _notifyHierarchyChanged;
-    mutable union
-    {
-        BoundingBox* box;
-        BoundingSphere* sphere;
-    } _bounds;
-    BoundsType _boundsType;
+    mutable BoundingSphere _bounds;
 };
 };
 
 
 }
 }

+ 18 - 1
gameplay/src/Package.cpp

@@ -10,7 +10,7 @@
 #include "Joint.h"
 #include "Joint.h"
 
 
 #define GPB_PACKAGE_VERSION_MAJOR 1
 #define GPB_PACKAGE_VERSION_MAJOR 1
-#define GPB_PACKAGE_VERSION_MINOR 0
+#define GPB_PACKAGE_VERSION_MINOR 1
 
 
 #define PACKAGE_TYPE_SCENE 1
 #define PACKAGE_TYPE_SCENE 1
 #define PACKAGE_TYPE_NODE 2
 #define PACKAGE_TYPE_NODE 2
@@ -776,6 +776,23 @@ void Package::resolveJointReferences(Scene* sceneContext, Node* nodeContext)
             }
             }
         }
         }
 
 
+        // Set the root joint
+        if (jointCount > 0)
+        {
+            Joint* rootJoint = skinData->skin->getJoint((unsigned int)0);
+            Node* parent = rootJoint->getParent();
+            while (parent)
+            {
+                if (skinData->skin->getJointIndex(static_cast<Joint*>(parent)) != -1)
+                {
+                    // Parent is a joint in the MeshSkin, so treat it as the new root
+                    rootJoint = static_cast<Joint*>(parent);
+                }
+                parent = parent->getParent();
+            }
+            skinData->skin->setRootJoint(rootJoint);
+        }
+
         // Done with this MeshSkinData entry
         // Done with this MeshSkinData entry
         SAFE_DELETE(_meshSkins[i]);
         SAFE_DELETE(_meshSkins[i]);
     }
     }

+ 1 - 0
gameplay/src/ParticleEmitter.cpp

@@ -36,6 +36,7 @@ ParticleEmitter::ParticleEmitter(SpriteBatch* batch, unsigned int particleCountM
     _particles = new Particle[particleCountMax];
     _particles = new Particle[particleCountMax];
 
 
     _spriteBatch->getStateBlock()->setDepthWrite(false);
     _spriteBatch->getStateBlock()->setDepthWrite(false);
+    _spriteBatch->getStateBlock()->setDepthTest(true);
 }
 }
 
 
 ParticleEmitter::~ParticleEmitter()
 ParticleEmitter::~ParticleEmitter()

+ 15 - 8
gameplay/src/Transform.cpp

@@ -740,22 +740,28 @@ void Transform::dirty()
     transformChanged();
     transformChanged();
 }
 }
 
 
-void Transform::addListener(Transform::Listener* listener)
+void Transform::addListener(Transform::Listener* listener, long cookie)
 {
 {
     if (_listeners == NULL)
     if (_listeners == NULL)
-        _listeners = new std::vector<Transform::Listener*>();
+        _listeners = new std::list<TransformListener>();
 
 
-    _listeners->push_back(listener);
+    TransformListener l;
+    l.listener = listener;
+    l.cookie = cookie;
+    _listeners->push_back(l);
 }
 }
 
 
 void Transform::removeListener(Transform::Listener* listener)
 void Transform::removeListener(Transform::Listener* listener)
 {
 {
     if (_listeners)
     if (_listeners)
     {
     {
-        std::vector<Transform::Listener*>::iterator itr = std::find(_listeners->begin(), _listeners->end(), listener);
-        if (itr != _listeners->end())
+        for (std::list<TransformListener>::iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
         {
         {
-            _listeners->erase(itr);
+            if ((*itr).listener == listener)
+            {
+                _listeners->erase(itr);
+                break;
+            }
         }
         }
     }
     }
 }
 }
@@ -764,9 +770,10 @@ void Transform::transformChanged()
 {
 {
     if (_listeners)
     if (_listeners)
     {
     {
-        for (unsigned int i = 0, count = _listeners->size(); i < count; ++i)
+        for (std::list<TransformListener>::iterator itr = _listeners->begin(); itr != _listeners->end(); itr++)
         {
         {
-            (*_listeners)[i]->transformChanged(this);
+            TransformListener& l = *itr;
+            l.listener->transformChanged(this, l.cookie);
         }
         }
     }
     }
 }
 }

+ 15 - 3
gameplay/src/Transform.h

@@ -130,8 +130,11 @@ public:
 
 
         /**
         /**
          * Handles when an transform has changed.
          * Handles when an transform has changed.
+         *
+         * @param transform The Transform object that was changed.
+         * @param cookie Cookie value that was specified when the listener was registered.
          */
          */
-        virtual void transformChanged(Transform* transform) = 0;
+        virtual void transformChanged(Transform* transform, long cookie) = 0;
     };
     };
 
 
     /**
     /**
@@ -717,8 +720,11 @@ public:
 
 
     /**
     /**
      * Adds a transform listener.
      * Adds a transform listener.
+     *
+     * @param listener The listener to add.
+     * @param cookie An optional long value that is passed to the specified listener when it is called..
      */
      */
-    void addListener(Transform::Listener* listener);
+    void addListener(Transform::Listener* listener, long cookie = 0);
 
 
     /**
     /**
      * Removes a transform listener.
      * Removes a transform listener.
@@ -742,6 +748,12 @@ public:
 
 
 protected:
 protected:
 
 
+    struct TransformListener
+    {
+        Listener* listener;
+        long cookie;
+    };
+
     void dirty();
     void dirty();
     virtual void transformChanged();
     virtual void transformChanged();
 
 
@@ -750,7 +762,7 @@ protected:
     Vector3 _translation;
     Vector3 _translation;
     mutable Matrix _matrix;
     mutable Matrix _matrix;
     mutable bool _matrixDirty;
     mutable bool _matrixDirty;
-    std::vector<Transform::Listener*>* _listeners;
+    std::list<TransformListener>* _listeners;
 
 
 };
 };