Просмотр исходного кода

Adding support for rough bounding volume computation for skinned meshes.

Steve Grenier 14 лет назад
Родитель
Сommit
caf7b1b1a3

+ 2 - 0
gameplay-encoder/gameplay-binary.txt

@@ -211,6 +211,8 @@ Reference
                 bindShape               float[16]
                 joints                  xref:Node[]
                 jointsBindPoses         float[] // 16 * joints.length
+                boundingBox             BoundingBox { float[3] min, float[3] max }
+                boundingSphere          BoundingSphere { float[3] center, float radius }
 ------------------------------------------------------------------------------------------------------
 128->Font
                 family                  string

+ 6 - 2
gameplay-encoder/gameplay-encoder.vcxproj

@@ -11,6 +11,7 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
+    <ClCompile Include="..\gameplay\src\Curve.cpp" />
     <ClCompile Include="src\Animation.cpp" />
     <ClCompile Include="src\AnimationChannel.cpp" />
     <ClCompile Include="src\Base.cpp" />
@@ -54,6 +55,7 @@
     <ClCompile Include="src\VertexElement.cpp" />
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="..\gameplay\src\Curve.h" />
     <ClInclude Include="src\Animation.h" />
     <ClInclude Include="src\AnimationChannel.h" />
     <ClInclude Include="src\Base.h" />
@@ -144,8 +146,9 @@
     <Link>
       <SubSystem>Console</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
-      <AdditionalLibraryDirectories>../external-deps/freetype2/lib/win32;../external-deps/collada-dom/lib/win32</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>;../external-deps/freetype2/lib/win32;../external-deps/collada-dom/lib/win32</AdditionalLibraryDirectories>
       <AdditionalDependencies>freetype245.lib;libcollada14dom22-d.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <IgnoreSpecificDefaultLibraries>MSVCRT</IgnoreSpecificDefaultLibraries>
     </Link>
     <PostBuildEvent>
       <Command>copy /Y "$(ProjectDir)..\external-deps\collada-dom\lib\win32\*.dll" "$(TargetDir)"</Command>
@@ -168,7 +171,8 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <AdditionalDependencies>freetype245.lib;libcollada14dom22-d.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>../external-deps/freetype2/lib/win32;../external-deps/collada-dom/lib/win32</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>;../external-deps/freetype2/lib/win32;../external-deps/collada-dom/lib/win32</AdditionalLibraryDirectories>
+      <IgnoreSpecificDefaultLibraries>MSVCRT</IgnoreSpecificDefaultLibraries>
     </Link>
     <PostBuildEvent>
       <Command>copy /Y "$(ProjectDir)..\external-deps\collada-dom\lib\win32\*.dll" "$(TargetDir)"</Command>

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

@@ -96,6 +96,7 @@
     <ClCompile Include="src\Animations.cpp">
       <Filter>Objects\Animation</Filter>
     </ClCompile>
+    <ClCompile Include="..\gameplay\src\Curve.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\DAESceneEncoder.h" />
@@ -192,6 +193,7 @@
     <ClInclude Include="src\Animations.h">
       <Filter>Objects\Animation</Filter>
     </ClInclude>
+    <ClInclude Include="..\gameplay\src\Curve.h" />
   </ItemGroup>
   <ItemGroup>
     <Filter Include="Objects">

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

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

+ 5 - 0
gameplay-encoder/src/Animation.cpp

@@ -55,4 +55,9 @@ unsigned int Animation::getAnimationChannelCount() const
     return _channels.size();
 }
 
+AnimationChannel* Animation::getAnimationChannel(unsigned int index) const
+{
+    return _channels[index];
+}
+
 }

+ 6 - 0
gameplay-encoder/src/Animation.h

@@ -27,6 +27,7 @@ public:
     virtual void writeText(FILE* file);
 
     void add(AnimationChannel* animationChannel);
+
     /**
      * Returns the number of animation channels contained in this animation.
      * 
@@ -34,6 +35,11 @@ public:
      */
     unsigned int getAnimationChannelCount() const;
 
+    /**
+     * Returns the specified animation channel.
+     */
+    AnimationChannel* getAnimationChannel(unsigned int index) const;
+
 private:
     std::vector<AnimationChannel*> _channels;
 };

+ 35 - 5
gameplay-encoder/src/AnimationChannel.cpp

@@ -51,6 +51,41 @@ void AnimationChannel::writeText(FILE* file)
     fprintElementEnd(file);
 }
 
+const std::string& AnimationChannel::getTargetId() const
+{
+    return _targetId;
+}
+
+unsigned int AnimationChannel::getTargetAttribute() const
+{
+    return _targetAttrib;
+}
+
+const std::vector<float>& AnimationChannel::getKeyValues() const
+{
+    return _keyValues;
+}
+
+const std::vector<float>& AnimationChannel::getKeyTimes() const
+{
+    return _keytimes;
+}
+
+const std::vector<float>& AnimationChannel::getTangentsIn() const
+{
+    return _tangentsIn;
+}
+
+const std::vector<float>& AnimationChannel::getTangentsOut() const
+{
+    return _tangentsOut;
+}
+
+const std::vector<unsigned int>& AnimationChannel::getInterpolationTypes() const
+{
+    return _interpolations;
+}
+
 void AnimationChannel::setTargetId(const std::string str)
 {
     _targetId = str;
@@ -86,11 +121,6 @@ void AnimationChannel::setInterpolations(const std::vector<unsigned int>& values
     _interpolations = values;
 }
 
-const std::vector<float>& AnimationChannel::getKeyValues() const
-{
-    return _keyValues;
-}
-
 unsigned int AnimationChannel::getInterpolationType(const char* str)
 {
     unsigned int value = 0;

+ 6 - 1
gameplay-encoder/src/AnimationChannel.h

@@ -35,6 +35,7 @@ public:
     virtual void writeBinary(FILE* file);
     virtual void writeText(FILE* file);
 
+    const std::string& getTargetId() const;
     void setTargetId(const std::string str);
     void setTargetAttribute(unsigned int attrib);
 
@@ -44,7 +45,12 @@ public:
     void setTangentsOut(const std::vector<float>& values);
     void setInterpolations(const std::vector<unsigned int>& values);
 
+    unsigned int getTargetAttribute() const;
     const std::vector<float>& getKeyValues() const;
+    const std::vector<float>& getKeyTimes() const;
+    const std::vector<float>& getTangentsIn() const;
+    const std::vector<float>& getTangentsOut() const;
+    const std::vector<unsigned int>& getInterpolationTypes() const;
 
     /**
      * Returns the interpolation type value for the given string or zero if not valid.
@@ -56,7 +62,6 @@ public:
      */
     static unsigned int getInterpolationType(const char* str);
 
-
 private:
 
     std::string _targetId;

+ 10 - 0
gameplay-encoder/src/Animations.cpp

@@ -51,4 +51,14 @@ void Animations::add(Animation* animation)
     _animations.push_back(animation);
 }
 
+unsigned int Animations::getAnimationCount() const
+{
+    return _animations.size();
+}
+
+Animation* Animations::getAnimation(unsigned int index) const
+{
+    return _animations[index];
+}
+
 }

+ 3 - 0
gameplay-encoder/src/Animations.h

@@ -30,8 +30,11 @@ public:
     virtual void writeText(FILE* file);
 
     void add(Animation* animation);
+    unsigned int getAnimationCount() const;
+    Animation* getAnimation(unsigned int index) const;
 
 private:
+
     std::vector<Animation*> _animations;
 };
 

+ 13 - 0
gameplay-encoder/src/GPBFile.cpp

@@ -3,15 +3,23 @@
 namespace gameplay
 {
 
+static GPBFile* __instance = NULL;
+
 GPBFile::GPBFile(void)
     : _file(NULL), _animationsAdded(false)
 {
+    __instance = this;
 }
 
 GPBFile::~GPBFile(void)
 {
 }
 
+GPBFile* GPBFile::getInstance()
+{
+    return __instance;
+}
+
 void GPBFile::saveBinary(const std::string& filepath)
 {
     _file = fopen(filepath.c_str(), "w+b");
@@ -200,6 +208,11 @@ Node* GPBFile::getNode(const char* id)
     return NULL;
 }
 
+Animations* GPBFile::getAnimations()
+{
+    return &_animations;
+}
+
 void GPBFile::adjust()
 {
     // calculate the ambient color for each scene

+ 8 - 0
gameplay-encoder/src/GPBFile.h

@@ -7,6 +7,7 @@
 
 #include <iostream>
 #include <list>
+#include <algorithm>
 
 #include "FileIO.h"
 #include "Object.h"
@@ -46,6 +47,11 @@ public:
      */
     ~GPBFile(void);
 
+    /**
+     * Returns the GPBFile instance.
+     */
+    static GPBFile* getInstance();
+
     /**
      * Saves the GPBFile as a binary file at filepath.
      *
@@ -88,6 +94,8 @@ public:
     Mesh* getMesh(const char* id);
     Node* getNode(const char* id);
 
+    Animations* getAnimations();
+
     /**
      * Adjusts the game play binary file before it is written.
      */

+ 55 - 1
gameplay-encoder/src/Matrix.cpp

@@ -41,6 +41,45 @@ void Matrix::setIdentity(float* matrix)
     memcpy(matrix, MATRIX4F_IDENTITY, MATRIX4F_SIZE);
 }
 
+void Matrix::createRotation(const Quaternion& q, float* dst)
+{
+    assert(dst);
+
+    float x2 = q.x + q.x;
+    float y2 = q.y + q.y;
+    float z2 = q.z + q.z;
+
+    float xx2 = q.x * x2;
+    float yy2 = q.y * y2;
+    float zz2 = q.z * z2;
+    float xy2 = q.x * y2;
+    float xz2 = q.x * z2;
+    float yz2 = q.y * z2;
+    float wx2 = q.w * x2;
+    float wy2 = q.w * y2;
+    float wz2 = q.w * z2;
+
+    dst[0] = 1.0f - yy2 - zz2;
+    dst[1] = xy2 + wz2;
+    dst[2] = xz2 - wy2;
+    dst[3] = 0.0f;
+
+    dst[4] = xy2 - wz2;
+    dst[5] = 1.0f - xx2 - zz2;
+    dst[6] = yz2 + wx2;
+    dst[7] = 0.0f;
+
+    dst[8] = xz2 + wy2;
+    dst[9] = yz2 - wx2;
+    dst[10] = 1.0f - xx2 - yy2;
+    dst[11] = 0.0f;
+
+    dst[12] = 0.0f;
+    dst[13] = 0.0f;
+    dst[14] = 0.0f;
+    dst[15] = 1.0f;
+}
+
 void Matrix::createRotation(float x, float y, float z, float angle, float* dst)
 {
     // Make sure the input axis is normalized
@@ -167,6 +206,13 @@ void Matrix::scale(float x, float y, float z)
     multiply(m, s, m);
 }
 
+void Matrix::rotate(const Quaternion& q)
+{
+    float r[16];
+    createRotation(q, r);
+    multiply(m, r, m);
+}
+
 void Matrix::rotate(float x, float y, float z, float angle)
 {
     float r[16];
@@ -350,4 +396,12 @@ float Matrix::determinant() const
     return (a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0);
 }
 
-}
+void Matrix::transformPoint(const Vector3& p, Vector3* dst)
+{
+    dst->set(
+        p.x * m[0] + p.y * m[4] + p.z * m[8] +  m[12],
+        p.x * m[1] + p.y * m[5] + p.z * m[9] +  m[13],
+        p.x * m[2] + p.y * m[6] + p.z * m[10] + m[14] );
+}
+
+}

+ 17 - 0
gameplay-encoder/src/Matrix.h

@@ -81,6 +81,11 @@ public:
      */
     static void createScale(float x, float y, float z, float* dst);
 
+    /**
+     * Creates a rotation matrix from the given quaternion.
+     */
+    static void Matrix::createRotation(const Quaternion& q, float* dst);
+
     /**
      * Creates a rotation matrix from the given axis and angle in degrees.
      */
@@ -121,6 +126,11 @@ public:
      */
     void scale(float x, float y, float z);
 
+    /**
+     * Rotates the matrix by the given Quaternion.
+     */
+    void rotate(const Quaternion& q);
+
     /**
      * Rotates the matrix by the axies specified and angle.
      */
@@ -141,6 +151,13 @@ public:
      */
     void rotateZ(float angle);
 
+    /**
+     * Transforms the specified point by this matrix.
+     *
+     * Note that the input vector is treated as a point and NOT a vector.
+     */
+    void transformPoint(const Vector3& p, Vector3* dst);
+
     float m[16];
 };
 

+ 30 - 5
gameplay-encoder/src/Mesh.cpp

@@ -1,9 +1,10 @@
 #include "Mesh.h"
+#include "Model.h"
 
 namespace gameplay
 {
 
-Mesh::Mesh(void)
+Mesh::Mesh(void) : model(NULL)
 {
 }
 
@@ -25,8 +26,8 @@ void Mesh::writeBinary(FILE* file)
 {
     Object::writeBinary(file);
     // vertex formats
-    write(_vertexFormats.size(), file);
-    for (std::vector<VertexElement>::iterator i = _vertexFormats.begin(); i != _vertexFormats.end(); i++)
+    write(_vertexFormat.size(), file);
+    for (std::vector<VertexElement>::iterator i = _vertexFormat.begin(); i != _vertexFormat.end(); i++)
     {
         i->writeBinary(file);
     }
@@ -73,7 +74,7 @@ void Mesh::writeText(FILE* file)
     // for each VertexFormat
     if (vertices.size() > 0 )
     {
-        for (std::vector<VertexElement>::iterator i = _vertexFormats.begin(); i != _vertexFormats.end(); i++)
+        for (std::vector<VertexElement>::iterator i = _vertexFormat.begin(); i != _vertexFormat.end(); i++)
         {
             i->writeText(file);
         }
@@ -123,7 +124,7 @@ void Mesh::addMeshPart(Vertex* vertex)
 
 void Mesh::addVetexAttribute(unsigned int usage, unsigned int count)
 {
-    _vertexFormats.push_back(VertexElement(usage, count));
+    _vertexFormat.push_back(VertexElement(usage, count));
 }
 
 size_t Mesh::getVertexCount() const
@@ -131,6 +132,21 @@ size_t Mesh::getVertexCount() const
     return vertices.size();
 }
 
+const Vertex& Mesh::getVertex(unsigned int index) const
+{
+    return vertices[index];
+}
+
+size_t Mesh::getVertexElementCount() const
+{
+    return _vertexFormat.size();
+}
+
+const VertexElement& Mesh::getVertexElement(unsigned int index) const
+{
+    return _vertexFormat[index];
+}
+
 bool Mesh::contains(const Vertex& vertex) const
 {
     return vertexLookupTable.count(vertex) > 0;
@@ -154,6 +170,15 @@ unsigned int Mesh::getVertexIndex(const Vertex& vertex)
 
 void Mesh::computeBounds()
 {
+    // If we have a Model with a MeshSkin associated with it,
+    // compute the bounds from the skin - otherwise compute
+    // it from the local mesh data.
+    if (model && model->getSkin())
+    {
+        model->getSkin()->computeBounds();
+        return;
+    }
+
     bounds.min.x = bounds.min.y = bounds.min.z = FLT_MAX;
     bounds.max.x = bounds.max.y = bounds.max.z = FLT_MIN;
     bounds.center.x = bounds.center.y = bounds.center.z = 0.0f;

+ 10 - 1
gameplay-encoder/src/Mesh.h

@@ -9,8 +9,12 @@
 namespace gameplay
 {
 
+class Model;
+
 class Mesh : public Object
 {
+    friend class Model;
+
 public:
 
     /**
@@ -38,6 +42,10 @@ public:
     void addVetexAttribute(unsigned int usage, unsigned int count);
 
     size_t getVertexCount() const;
+    const Vertex& getVertex(unsigned int index) const;
+
+    size_t getVertexElementCount() const;
+    const VertexElement& getVertexElement(unsigned int index) const;
 
     /**
      * Returns true if this MeshPart contains the given Vertex.
@@ -51,6 +59,7 @@ public:
 
     unsigned int getVertexIndex(const Vertex& vertex);
 
+    Model* model;
     std::vector<Vertex> vertices;
     std::vector<MeshPart*> parts;
     struct
@@ -67,7 +76,7 @@ private:
     void computeBounds();
 
 private:
-    std::vector<VertexElement> _vertexFormats;
+    std::vector<VertexElement> _vertexFormat;
 
 };
 

+ 297 - 10
gameplay-encoder/src/MeshSkin.cpp

@@ -1,6 +1,11 @@
 #include "MeshSkin.h"
 #include "Node.h"
 #include "StringUtil.h"
+#include "Mesh.h"
+#include "GPBFile.h"
+#include "Animations.h"
+#include "Transform.h"
+#include "../../gameplay/src/Curve.h"
 
 namespace gameplay
 {
@@ -34,7 +39,11 @@ void MeshSkin::writeBinary(FILE* file)
     {
         (*i)->writeBinaryXref(file);
     }
-    write(_bindPoses, file);
+    write(_bindPoses.size() * 16, file);
+    for (std::list<Matrix>::const_iterator i = _bindPoses.begin(); i != _bindPoses.end(); i++)
+    {
+        write(i->m, 16, file);
+    }
 }
 
 void MeshSkin::writeText(FILE* file)
@@ -49,15 +58,297 @@ void MeshSkin::writeText(FILE* file)
         fprintf(file, "%s ", i->c_str());
     }
     fprintf(file, "</joints>\n");
-    fprintf(file, "<bindPoses count=\"%lu\">", _bindPoses.size());
-    for (std::list<float>::const_iterator i = _bindPoses.begin(); i != _bindPoses.end(); i++)
+    fprintf(file, "<bindPoses count=\"%lu\">", _bindPoses.size() * 16);
+    for (std::list<Matrix>::const_iterator i = _bindPoses.begin(); i != _bindPoses.end(); i++)
     {
-        fprintf(file, "%f ", *i);
+        for (unsigned int j = 0; j < 16; ++j)
+        {
+            fprintf(file, "%f ", i->m[j]);
+        }
     }
     fprintf(file, "</bindPoses>\n");
+
     fprintElementEnd(file);
 }
 
+void MeshSkin::computeBounds()
+{
+    // Find the offset of the blend indices and blend weights within the mesh vertices
+    int blendIndexOffset = -1;
+    int blendWeightOffset = -1;
+    for (unsigned int i = 0, count = _mesh->getVertexElementCount(); i < count; ++i)
+    {
+        const VertexElement& e = _mesh->getVertexElement(i);
+        switch (e.usage)
+        {
+        case BLENDINDICES:
+            blendIndexOffset = i;
+            break;
+        case BLENDWEIGHTS:
+            blendWeightOffset = i;
+            break;
+        }
+    }
+    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);
+        }
+
+        // Add parent joints that are not yet in the list
+        Node* joint = joints[0];
+        while (joint->getParent())
+        {
+            joint = joint->getParent();
+            if (std::find(joints.begin(), joints.end(), joint) == joints.end())
+                joints.push_back(joint);
+        }
+
+        unsigned int jointCount = joints.size();
+        unsigned int boneCount = _joints.size();
+
+        std::vector<AnimationChannel*> channels;
+        std::vector<Node*> channelTargets;
+        std::vector<Curve*> curves;
+
+        // 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];
+
+            // 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)
+                {
+                    AnimationChannel* channel = animation->getAnimationChannel(k);
+                    if (channel->getTargetId() == joint->getId())
+                    {
+                        if (std::find(channels.begin(), channels.end(), channel) == channels.end())
+                        {
+                            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)
+        {
+            AnimationChannel* channel = channels[i];
+
+            const std::vector<float>& keyTimes = channel->getKeyTimes();
+            unsigned int keyCount = keyTimes.size();
+            if (keyCount == 0)
+                continue;
+
+            // 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;
+            }
+            if (curve == NULL)
+            {
+                // Unsupported/not implemented curve type 
+                continue;
+            }
+
+            // 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;
+
+                // Set the curve point
+                // TODO: Handle other interpolation types
+                curve->setPoint(j, t, keyValuesPtr, gameplay::Curve::LINEAR);
+
+                // Move to the next point on the curve
+                keyValuesPtr += curve->getComponentCount();
+            }
+
+            delete[] keyValues;
+            keyValues = NULL;
+
+            curves.push_back(curve);
+        }
+
+        // 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)
+        {
+            memcpy(oldTransforms[i].m, joints[i]->getTransformMatrix().m, 16 * sizeof(float));
+        }
+
+        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)
+        {
+            // 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 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);
+            }
+
+            // 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);
+                    }
+                }
+
+                // 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;
+            }
+
+            // Increment time by 1/60th of second (~ 17 ms)
+            time += 170.0f;
+        }
+
+        // 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);
+
+        // Restore original joint transforms
+        for (unsigned int i = 0; i < boneCount; ++i)
+        {
+            joints[i]->setTransformMatrix(oldTransforms[i].m);
+        }
+
+        // Cleanup
+        for (unsigned int i = 0, curveCount = curves.size(); i < curveCount; ++i)
+        {
+            delete curves[i];
+        }
+        delete[] oldTransforms;
+        delete[] jointTransforms;
+    }
+}
+
 void MeshSkin::setBindShape(const float data[])
 {
     for (int i = 0; i < 16; i++)
@@ -90,11 +381,7 @@ void MeshSkin::setBindPoses(std::vector<Matrix>& list)
 {
     for (std::vector<Matrix>::iterator i = list.begin(); i != list.end(); i++)
     {
-        float* a = i->m;
-        for (int j = 0; j < 16; j++)
-        {
-            _bindPoses.push_back(a[j]);
-        }
+        _bindPoses.push_back(*i);
     }
 }
 
@@ -110,4 +397,4 @@ bool MeshSkin::hasJoint(const char* id)
     return false;
 }
 
-}
+}

+ 7 - 3
gameplay-encoder/src/MeshSkin.h

@@ -12,9 +12,12 @@ namespace gameplay
 {
 
 class Node;
+class Mesh;
 
 class MeshSkin : public Object
 {
+    friend class Model;
+
 public:
 
     /**
@@ -53,14 +56,15 @@ public:
      */
     bool hasJoint(const char* id);
 
+    void computeBounds();
+
 private:
 
+    Mesh* _mesh;
     float _bindShape[16];
     std::list<Node*> _joints;
-    std::list<float> _bindPoses;
-
+    std::list<Matrix> _bindPoses;
     std::list<std::string> _jointNames;
-
     unsigned int _vertexInfluenceCount;
 };
 

+ 19 - 1
gameplay-encoder/src/Model.cpp

@@ -49,8 +49,11 @@ void Model::writeBinary(FILE* file)
     writeBinaryObjects(_materials, file);
 
 }
+
 void Model::writeText(FILE* file)
 {
+    // Compute mesh bounds before writing
+
     fprintElementStart(file);
     if (_ref != NULL)
     {
@@ -71,11 +74,26 @@ MeshSkin* Model::getSkin()
 void Model::setMesh(Mesh* mesh)
 {
     _ref = mesh;
+
+    if (mesh)
+    {
+        mesh->model = this;
+    }
+
+    if (_ref && _meshSkin)
+    {
+        _meshSkin->_mesh = _ref;
+    }
 }
 
 void Model::setSkin(MeshSkin* skin)
 {
     _meshSkin = skin;
+
+    if (_meshSkin)
+    {
+        _meshSkin->_mesh = _ref;
+    }
 }
 
-}
+}

+ 2 - 1
gameplay-encoder/src/Model.h

@@ -35,11 +35,12 @@ public:
     void setSkin(MeshSkin* skin);
 
 private:
+
     Mesh* _ref;
     MeshSkin* _meshSkin;
     std::list<Material*> _materials;
 };
 
 }
-#endif
 
+#endif

+ 21 - 5
gameplay-encoder/src/Node.cpp

@@ -12,7 +12,7 @@ Node::Node(void) :
     _firstChild(NULL), _lastChild(NULL), _parent(NULL),
     _camera(NULL), _light(NULL), _model(NULL), _joint(false)
 {
-    setIdentityMatrix(_transform);
+    setIdentityMatrix(_transform.m);
 }
 
 Node::~Node(void)
@@ -37,7 +37,7 @@ void Node::writeBinary(FILE* file)
     unsigned int type = _joint ? JOINT : NODE;
     write(type, file);
 
-    write(_transform, 16, file);
+    write(_transform.m, 16, file);
     // children
     write(getChildCount(), file); // write number of children
     for (Node* node = getFirstChild(); node != NULL; node = node->getNextSibling())
@@ -84,7 +84,7 @@ void Node::writeText(FILE* file)
         fprintElementStart(file);
     }
     fprintf(file, "<transform>");
-    fprintfMatrix4f(file, _transform);
+    fprintfMatrix4f(file, _transform.m);
     fprintf(file, "</transform>\n");
 
     // children
@@ -232,12 +232,28 @@ void Node::setModel(Model* model)
     _model = model;
 }
 
+const Matrix& Node::getTransformMatrix() const
+{
+    return _transform;
+}
+
 void Node::setTransformMatrix(float matrix[])
 {
-    for (int i = 0; i < 16; i++)
+    memcpy(_transform.m, matrix, 16 * sizeof(float));
+}
+
+const Matrix& Node::getWorldMatrix() const
+{
+    if (_parent)
     {
-        _transform[i] = matrix[i];
+        Matrix::multiply(_parent->getWorldMatrix().m, _transform.m, _worldTransform.m);
     }
+    else
+    {
+        memcpy(_worldTransform.m, _transform.m, 16 * sizeof(float));
+    }
+
+    return _worldTransform;
 }
 
 void Node::setIsJoint(bool value)

+ 13 - 1
gameplay-encoder/src/Node.h

@@ -125,11 +125,21 @@ public:
      */
     Model* getModel() const;
 
+    /**
+     * Returns the transform matrix for the node.
+     */
+    const Matrix& getTransformMatrix() const;
+
     /**
      * Sets the transform for this node.
      */
     void setTransformMatrix(float matrix[]);
 
+    /**
+     * Returns the resolved world matrix for the node.
+     */
+    const Matrix& getWorldMatrix() const;
+
     void setCameraInstance(CameraInstance* cameraInstance);
     void setLightInstance(LightInstance* lightInstance);
     void setModel(Model* model);
@@ -157,7 +167,9 @@ public:
     bool hasLight() const;
     
 private:
-    float _transform[16];
+
+    Matrix _transform;
+    mutable Matrix _worldTransform;
 
     int _childCount;
     Node* _nextSibling;

+ 0 - 2
gameplay-encoder/src/Vector3.h

@@ -337,8 +337,6 @@ public:
      */
     static void subtract(const Vector3& v1, const Vector3& v2, Vector3* dst);
 
-
-
     inline bool operator<(const Vector3& v) const
     {
         if (x == v.x)

+ 1 - 0
gameplay/src/BoundingSphere.cpp

@@ -10,6 +10,7 @@ namespace gameplay
 {
 
 BoundingSphere::BoundingSphere()
+    : radius(0)
 {
 }
 

+ 122 - 21
gameplay/src/Curve.cpp

@@ -2,9 +2,9 @@
  * Curve.cpp
  */
 
-#include "Base.h"
 #include "Curve.h"
-#include "Transform.h"
+#include <cassert>
+#include <memory>
 
 namespace gameplay
 {
@@ -30,8 +30,8 @@ Curve::Curve(unsigned int pointCount, unsigned int componentCount)
 
 Curve::~Curve()
 {
-    SAFE_DELETE_ARRAY(_points);
-    SAFE_DELETE_ARRAY(_quaternionOffsets);
+    delete[] _points;
+    delete[] _quaternionOffsets;
 }
 
 Curve::Point::Point()
@@ -41,9 +41,9 @@ Curve::Point::Point()
 
 Curve::Point::~Point()
 {
-    SAFE_DELETE_ARRAY(value);
-    SAFE_DELETE_ARRAY(inValue);
-    SAFE_DELETE_ARRAY(outValue);
+    delete[] value;
+    delete[] inValue;
+    delete[] outValue;
 }
 
 unsigned int Curve::getPointCount() const
@@ -56,6 +56,16 @@ unsigned int Curve::getComponentCount() const
     return _componentCount;
 }
 
+float Curve::getStartTime() const
+{
+    return _points[0].time;
+}
+
+float Curve::getEndTime() const
+{
+    return _points[_pointCount-1].time;
+}
+
 void Curve::setPoint(unsigned int index, float time, float* value, InterpolationType type)
 {
     setPoint(index, time, value, type, NULL, NULL);
@@ -234,7 +244,6 @@ void Curve::interpolateBezier(float s, Point* from, Point* to, float* dst) const
                 i++;
             }
             // Handle quaternion component.
-            //float interpTime = from->time * eq1 + from->outValue[i] * eq2 + to->inValue[i] * eq3 + to->time * eq4;
             interpolateQuaternion(s, (from->value + i), (to->value + i), (dst + i));
             i += 4;
             quaternionOffsetIndex++;
@@ -558,27 +567,119 @@ void Curve::interpolateLinear(float s, Point* from, Point* to, float* dst) const
     }
 }
 
+void slerpQuat(float* q1, float* q2, float t, float* dst)
+{
+    // Fast slerp implementation by kwhatmough:
+    // It contains no division operations, no trig, no inverse trig
+    // and no sqrt. Not only does this code tolerate small constraint
+    // errors in the input quaternions, it actually corrects for them.
+    assert(dst);
+    assert(!(t < 0.0f || t > 1.0f));
+
+    if (t == 0.0f)
+    {
+        memcpy(dst, q1, sizeof(float) * 4);
+        return;
+    }
+    else if (t == 1.0f)
+    {
+        memcpy(dst, q2, sizeof(float) * 4);
+        return;
+    }
+
+    float halfY, alpha, beta;
+    float u, f1, f2a, f2b;
+    float ratio1, ratio2;
+    float halfSecHalfTheta, versHalfTheta;
+    float sqNotU, sqU;
+
+    float cosTheta = q1[3] * q2[3] + q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2];
+
+    // As usual in all slerp implementations, we fold theta.
+    alpha = cosTheta >= 0 ? 1.0f : -1.0f;
+    halfY = 1.0f + alpha * cosTheta;
+
+    // Here we bisect the interval, so we need to fold t as well.
+    f2b = t - 0.5f;
+    u = f2b >= 0 ? f2b : -f2b;
+    f2a = u - f2b;
+    f2b += u;
+    u += u;
+    f1 = 1.0f - u;
+
+    // One iteration of Newton to get 1-cos(theta / 2) to good accuracy.
+    halfSecHalfTheta = 1.09f - (0.476537f - 0.0903321f * halfY) * halfY;
+    halfSecHalfTheta *= 1.5f - halfY * halfSecHalfTheta * halfSecHalfTheta;
+    versHalfTheta = 1.0f - halfY * halfSecHalfTheta;
+
+    // Evaluate series expansions of the coefficients.
+    sqNotU = f1 * f1;
+    ratio2 = 0.0000440917108f * versHalfTheta;
+    ratio1 = -0.00158730159f + (sqNotU - 16.0f) * ratio2;
+    ratio1 = 0.0333333333f + ratio1 * (sqNotU - 9.0f) * versHalfTheta;
+    ratio1 = -0.333333333f + ratio1 * (sqNotU - 4.0f) * versHalfTheta;
+    ratio1 = 1.0f + ratio1 * (sqNotU - 1.0f) * versHalfTheta;
+
+    sqU = u * u;
+    ratio2 = -0.00158730159f + (sqU - 16.0f) * ratio2;
+    ratio2 = 0.0333333333f + ratio2 * (sqU - 9.0f) * versHalfTheta;
+    ratio2 = -0.333333333f + ratio2 * (sqU - 4.0f) * versHalfTheta;
+    ratio2 = 1.0f + ratio2 * (sqU - 1.0f) * versHalfTheta;
+
+    // Perform the bisection and resolve the folding done earlier.
+    f1 *= ratio1 * halfSecHalfTheta;
+    f2a *= ratio2;
+    f2b *= ratio2;
+    alpha *= f1 + f2a;
+    beta = f1 + f2b;
+
+    // Apply final coefficients to a and b as usual.
+    float w = alpha * q1[3] + beta * q2[3];
+    float x = alpha * q1[0] + beta * q2[0];
+    float y = alpha * q1[1] + beta * q2[1];
+    float z = alpha * q1[2] + beta * q2[2];
+
+    // This final adjustment to the quaternion's length corrects for
+    // any small constraint error in the inputs q1 and q2. But as you
+    // can see, it comes at the cost of 9 additional multiplication
+    // operations. If this error-correcting feature is not required,
+    // the following code may be removed.
+    f1 = 1.5f - 0.5f * (w * w + x * x + y * y + z * z);
+    dst[3] = w * f1;
+    dst[0] = x * f1;
+    dst[1] = y * f1;
+    dst[2] = z * f1;
+}
+
+void normalizeQuat(float* q)
+{
+    float n = q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
+
+    // Do we need to normalize?
+    if (fabs(n) > 0.00001f && fabs(n - 1.0f) > 0.00001f)
+    {
+        n = sqrtf(n);
+        q[0] /= n;
+        q[1] /= n;
+        q[2] /= n;
+        q[3] /= n;
+    }
+}
+
 void Curve::interpolateQuaternion(float s, float* from, float* to, float* dst) const
 {
-    Quaternion quatFrom(from);
-    Quaternion quatTo(to);
-    Quaternion result;
+    float quatFrom[4] = { from[0], from[1], from[2], from[3] };
+    float quatTo[4] = { to[0], to[1], to[2], to[3] };
 
     // Normalize the quaternions.
-    quatFrom.normalize();
-    quatTo.normalize();
+    normalizeQuat(quatFrom);
+    normalizeQuat(quatTo);
         
     // Evaluate.
     if (s >= 0)
-        Quaternion::slerp(quatFrom, quatTo, s, &result);
+        slerpQuat(quatFrom, quatTo, s, dst);
     else
-        Quaternion::slerp(quatTo, quatFrom, -s, &result);
-
-    // Place in destination.
-    dst[0] = result.x;
-    dst[1] = result.y;
-    dst[2] = result.z;
-    dst[3] = result.w;
+        slerpQuat(quatTo, quatFrom, -s, dst);
 }
 
 int Curve::determineIndex(float time) const

+ 14 - 2
gameplay/src/Curve.h

@@ -5,8 +5,6 @@
 #ifndef CURVE_H_
 #define CURVE_H_
 
-#include "AnimationClip.h"
-
 namespace gameplay
 {
 
@@ -91,6 +89,20 @@ public:
      */
     unsigned int getComponentCount() const;
 
+    /**
+     * Returns the start time for the curve.
+     *
+     * @return The curve's start time.
+     */
+    float getStartTime() const;
+
+    /**
+     * Returns the end time for the curve.
+     *
+     * @return The curve's end time.
+     */
+    float getEndTime() const;
+
     /**
      * Sets the given point values on the curve the curve.
      *

+ 0 - 1
gameplay/src/Mesh.cpp

@@ -8,7 +8,6 @@
 #include "Effect.h"
 #include "Model.h"
 #include "Material.h"
-#include "BoundingBox.h"
 
 namespace gameplay
 {

+ 18 - 0
gameplay/src/Mesh.h

@@ -230,6 +230,15 @@ public:
      * setBoundingSphere methods are called to specify the mesh's
      * local bounds.
      *
+     * Meshes that are attached to a Model with a MeshSkin will have
+     * a bounding volume that is not neccessarily tight fighting on the
+     * Mesh vertices. Instead, the bounding volume will be an approximation
+     * that contains all possible vertex positions in all possible poses after
+     * skinning is applied. This is neccessary since skinning vertices 
+     * result in vertex positions that lie outside the original mesh bounds
+     * and could otherwise result in a bounding volume that does not fully
+     * contain an animated/skinned mesh.
+     *
      * @return The bounding box for the mesh.
      */
     const BoundingBox& getBoundingBox() const;
@@ -250,6 +259,15 @@ public:
      * setBoundingSphere methods are called to specify the mesh's
      * local bounds.
      *
+     * Meshes that are attached to a Model with a MeshSkin will have
+     * a bounding volume that is not neccessarily tight fighting on the
+     * Mesh vertices. Instead, the bounding volume will be an approximation
+     * that contains all possible vertex positions in all possible poses after
+     * skinning is applied. This is neccessary since skinning vertices 
+     * result in vertex positions that lie outside the original mesh bounds
+     * and could otherwise result in a bounding volume that does not fully
+     * contain an animated/skinned mesh.
+     *
      * @return The bounding sphere for the mesh.
      */
     const BoundingSphere& getBoundingSphere() const;

+ 20 - 0
gameplay/src/MeshSkin.cpp

@@ -99,6 +99,26 @@ unsigned int MeshSkin::getMatrixPaletteSize() const
     return _joints.size() * PALETTE_ROWS;
 }
 
+const BoundingBox& MeshSkin::getBoundingBox() const
+{
+    return _boundingBox;
+}
+
+void MeshSkin::setBoundingBox(const BoundingBox& box)
+{
+    _boundingBox = box;
+}
+
+const BoundingSphere& MeshSkin::getBoundingSphere() const
+{
+    return _boundingSphere;
+}
+
+void MeshSkin::setBoundingSphere(const BoundingSphere& sphere)
+{
+    _boundingSphere = sphere;
+}
+
 Joint* MeshSkin::getJoint(unsigned int index) const
 {
     assert(index < _joints.size());

+ 18 - 6
gameplay/src/Node.cpp

@@ -375,6 +375,20 @@ const Matrix& Node::getInverseViewMatrix() const
     }
 }
 
+const Matrix& Node::getProjectionMatrix() const
+{
+    Scene* scene = getScene();
+    Camera* camera = scene ? scene->getActiveCamera() : NULL;
+    if (camera)
+    {
+        return camera->getProjectionMatrix();
+    }
+    else
+    {
+        return Matrix::identity();
+    }
+}
+
 const Matrix& Node::getViewProjectionMatrix() const
 {
     Scene* scene = getScene();
@@ -569,11 +583,10 @@ const BoundingBox& Node::getBoundingBox() const
     {
         _dirtyBits &= ~NODE_DIRTY_BOUNDS;
 
+        // Get the local bounding box
         if (_model && _model->getMesh())
         {
-            // Use the bounding volume of our model's mesh.
-            Mesh* mesh = _model->getMesh();
-            _bounds.box->set(mesh->getBoundingBox());
+            _bounds.box->set(_model->getMesh()->getBoundingBox());
         }
         else
         {
@@ -644,11 +657,10 @@ const BoundingSphere& Node::getBoundingSphere() const
     {
         _dirtyBits &= ~NODE_DIRTY_BOUNDS;
 
+        // Get the local bounding sphere
         if (_model && _model->getMesh())
         {
-            // Use the bounding volume of our model's mesh.
-            Mesh* mesh = _model->getMesh();
-            _bounds.sphere->set(mesh->getBoundingSphere());
+            _bounds.sphere->set(_model->getMesh()->getBoundingSphere());
         }
         else
         {

+ 2 - 2
gameplay/src/Package.cpp

@@ -916,7 +916,7 @@ Animation* Package::readAnimationChannel(Scene* scene, Animation* animation, con
         SAFE_DELETE_ARRAY(interpolation);
         return NULL;
     }
-    
+
     Game* game = Game::getInstance();
     AnimationController* controller = game->getAnimationController();
 
@@ -1000,7 +1000,7 @@ Mesh* Package::loadMesh(const char* id)
 
     // Read mesh bounds (bounding box and bounding sphere)
     Vector3 boundsMin, boundsMax, boundsCenter;
-    float boundsRadius;
+    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);

+ 16 - 0
gameplay/src/RenderState.cpp

@@ -118,10 +118,18 @@ void RenderState::setParameterAutoBinding(const char* name, const char* autoBind
     {
         value = VIEW_MATRIX;
     }
+    else if (strcmp(autoBinding, "PROJECTION_MATRIX") == 0)
+    {
+        value = PROJECTION_MATRIX;
+    }
     else if (strcmp(autoBinding, "WORLD_VIEW_MATRIX") == 0)
     {
         value = WORLD_VIEW_MATRIX;
     }
+    else if (strcmp(autoBinding, "VIEW_PROJECTION_MATRIX") == 0)
+    {
+        value = VIEW_PROJECTION_MATRIX;
+    }
     else if (strcmp(autoBinding, "WORLD_VIEW_PROJECTION_MATRIX") == 0)
     {
         value = WORLD_VIEW_PROJECTION_MATRIX;
@@ -198,10 +206,18 @@ void RenderState::applyAutoBinding(const char* uniformName, AutoBinding autoBind
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getViewMatrix);
         break;
 
+    case PROJECTION_MATRIX:
+        getParameter(uniformName)->bindValue(_nodeBinding, &Node::getProjectionMatrix);
+        break;
+
     case WORLD_VIEW_MATRIX:
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getWorldViewMatrix);
         break;
 
+    case VIEW_PROJECTION_MATRIX:
+        getParameter(uniformName)->bindValue(_nodeBinding, &Node::getViewProjectionMatrix);
+        break;
+
     case WORLD_VIEW_PROJECTION_MATRIX:
         getParameter(uniformName)->bindValue(_nodeBinding, &Node::getWorldViewProjectionMatrix);
         break;

+ 10 - 0
gameplay/src/RenderState.h

@@ -41,11 +41,21 @@ public:
          */
         VIEW_MATRIX,
 
+        /**
+         * Binds the Projection matrix of the active camera for the node's scene.
+         */
+        PROJECTION_MATRIX,
+
         /**
          * Binds a node's WorldView matrix.
          */
         WORLD_VIEW_MATRIX,
 
+        /**
+         * Binds the ViewProjection matrix of the active camera for the node's scene.
+         */
+        VIEW_PROJECTION_MATRIX,
+
         /**
          * Binds a node's WorldViewProjection matrix.
          */