Selaa lähdekoodia

Merge pull request #55 from blackberry-gaming/next-sgrenier

Next sgrenier
Sean Paul Taylor 14 vuotta sitten
vanhempi
sitoutus
d0e7c4505e
55 muutettua tiedostoa jossa 1695 lisäystä ja 417 poistoa
  1. 3 1
      gameplay-encoder/gameplay-binary.txt
  2. 9 2
      gameplay-encoder/gameplay-encoder.vcxproj
  3. 9 0
      gameplay-encoder/gameplay-encoder.vcxproj.filters
  4. 5 0
      gameplay-encoder/src/Animation.cpp
  5. 6 0
      gameplay-encoder/src/Animation.h
  6. 35 5
      gameplay-encoder/src/AnimationChannel.cpp
  7. 6 1
      gameplay-encoder/src/AnimationChannel.h
  8. 10 0
      gameplay-encoder/src/Animations.cpp
  9. 3 0
      gameplay-encoder/src/Animations.h
  10. 8 3
      gameplay-encoder/src/Base.h
  11. 136 0
      gameplay-encoder/src/BoundingVolume.cpp
  12. 57 0
      gameplay-encoder/src/BoundingVolume.h
  13. 6 7
      gameplay-encoder/src/DAESceneEncoder.cpp
  14. 2 2
      gameplay-encoder/src/DAEUtil.cpp
  15. 2 2
      gameplay-encoder/src/DAEUtil.h
  16. 13 0
      gameplay-encoder/src/GPBFile.cpp
  17. 9 1
      gameplay-encoder/src/GPBFile.h
  18. 55 1
      gameplay-encoder/src/Matrix.cpp
  19. 22 1
      gameplay-encoder/src/Matrix.h
  20. 33 15
      gameplay-encoder/src/Mesh.cpp
  21. 12 8
      gameplay-encoder/src/Mesh.h
  22. 341 16
      gameplay-encoder/src/MeshSkin.cpp
  23. 15 8
      gameplay-encoder/src/MeshSkin.h
  24. 19 1
      gameplay-encoder/src/Model.cpp
  25. 2 1
      gameplay-encoder/src/Model.h
  26. 21 5
      gameplay-encoder/src/Node.cpp
  27. 13 1
      gameplay-encoder/src/Node.h
  28. 2 3
      gameplay-encoder/src/TTFFontEncoder.cpp
  29. 1 2
      gameplay-encoder/src/Vector3.h
  30. 11 0
      gameplay.sln
  31. 1 1
      gameplay/src/AudioListener.cpp
  32. 1 1
      gameplay/src/AudioListener.h
  33. 1 1
      gameplay/src/AudioSource.cpp
  34. 1 1
      gameplay/src/AudioSource.h
  35. 2 2
      gameplay/src/Base.h
  36. 6 7
      gameplay/src/BoundingSphere.cpp
  37. 1 1
      gameplay/src/Camera.cpp
  38. 1 1
      gameplay/src/Camera.h
  39. 129 21
      gameplay/src/Curve.cpp
  40. 14 2
      gameplay/src/Curve.h
  41. 9 30
      gameplay/src/Joint.cpp
  42. 2 16
      gameplay/src/Joint.h
  43. 0 1
      gameplay/src/Mesh.cpp
  44. 18 0
      gameplay/src/Mesh.h
  45. 89 5
      gameplay/src/MeshSkin.cpp
  46. 50 10
      gameplay/src/MeshSkin.h
  47. 70 174
      gameplay/src/Node.cpp
  48. 22 43
      gameplay/src/Node.h
  49. 20 3
      gameplay/src/Package.cpp
  50. 1 0
      gameplay/src/ParticleEmitter.cpp
  51. 335 0
      gameplay/src/PlatformMacOSX.mm
  52. 16 0
      gameplay/src/RenderState.cpp
  53. 10 0
      gameplay/src/RenderState.h
  54. 15 8
      gameplay/src/Transform.cpp
  55. 15 3
      gameplay/src/Transform.h

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

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

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

@@ -11,9 +11,11 @@
     </ProjectConfiguration>
   </ItemGroup>
   <ItemGroup>
+    <ClCompile Include="..\gameplay\src\Curve.cpp" />
     <ClCompile Include="src\Animation.cpp" />
     <ClCompile Include="src\AnimationChannel.cpp" />
     <ClCompile Include="src\Base.cpp" />
+    <ClCompile Include="src\BoundingVolume.cpp" />
     <ClCompile Include="src\Camera.cpp" />
     <ClCompile Include="src\CameraInstance.cpp" />
     <ClCompile Include="src\EncoderArguments.cpp" />
@@ -54,9 +56,11 @@
     <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" />
+    <ClInclude Include="src\BoundingVolume.h" />
     <ClInclude Include="src\Camera.h" />
     <ClInclude Include="src\CameraInstance.h" />
     <ClInclude Include="src\EncoderArguments.h" />
@@ -77,6 +81,7 @@
     <ClInclude Include="src\MaterialParameter.h" />
     <ClInclude Include="src\Matrix.h" />
     <ClInclude Include="src\Mesh.h" />
+    <ClInclude Include="src\Miniball.h" />
     <ClInclude Include="src\Model.h" />
     <ClInclude Include="src\MeshPart.h" />
     <ClInclude Include="src\MeshSkin.h" />
@@ -144,8 +149,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 +174,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>

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

@@ -96,6 +96,10 @@
     <ClCompile Include="src\Animations.cpp">
       <Filter>Objects\Animation</Filter>
     </ClCompile>
+    <ClCompile Include="..\gameplay\src\Curve.cpp" />
+    <ClCompile Include="src\BoundingVolume.cpp">
+      <Filter>Math</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\DAESceneEncoder.h" />
@@ -192,6 +196,11 @@
     <ClInclude Include="src\Animations.h">
       <Filter>Objects\Animation</Filter>
     </ClInclude>
+    <ClInclude Include="..\gameplay\src\Curve.h" />
+    <ClInclude Include="src\Miniball.h" />
+    <ClInclude Include="src\BoundingVolume.h">
+      <Filter>Math</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Filter Include="Objects">

+ 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;
 };
 

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

@@ -17,7 +17,6 @@
 #include <math.h>
 #include <float.h>
 
-
 #ifndef M_1_PI        
 #define M_1_PI                      0.31830988618379067154
 #endif
@@ -59,10 +58,16 @@ enum VertexUsage
     TEXCOORD7 = 15
 };
 
-
 void fillArray(float values[], float value, size_t length);
 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

+ 136 - 0
gameplay-encoder/src/BoundingVolume.cpp

@@ -0,0 +1,136 @@
+#include "BoundingVolume.h"
+
+namespace gameplay
+{
+
+BoundingVolume::BoundingVolume()
+    : radius(0.0f)
+{
+}
+
+void updateMinMax(Vector3* point, Vector3* min, Vector3* max)
+{
+    // Leftmost point.
+    if (point->x < min->x)
+    {
+        min->x = point->x;
+    }
+
+    // Rightmost point.
+    if (point->x > max->x)
+    {
+        max->x = point->x;
+    }
+
+    // Lowest point.
+    if (point->y < min->y)
+    {
+        min->y = point->y;
+    }
+
+    // Highest point.
+    if (point->y > max->y)
+    {
+        max->y = point->y;
+    }
+
+    // Farthest point.
+    if (point->z < min->z)
+    {
+        min->z = point->z;
+    }
+
+    // Nearest point.
+    if (point->z > max->z)
+    {
+        max->z = point->z;
+    }
+}
+
+void BoundingVolume::transform(const Matrix& m)
+{
+    // Transform the bounding sphere
+    m.transformPoint(center, &center);
+    Vector3 translate;
+    m.decompose(&translate, NULL, NULL);
+    float r = radius * translate.x;
+    r = std::max(radius, radius * translate.y);
+    r = std::max(radius, radius * translate.z);
+    radius = r;
+
+    // Transform the bounding box
+    Vector3 corners[8];
+    corners[0].set(min.x, max.y, max.z);
+    // Left-bottom-front.
+    corners[1].set(min.x, min.y, max.z);
+    // Right-bottom-front.
+    corners[2].set(max.x, min.y, max.z);
+    // Right-top-front.
+    corners[3].set(max.x, max.y, max.z);
+    // Right-top-back.
+    corners[4].set(max.x, max.y, min.z);
+    // Right-bottom-back.
+    corners[5].set(max.x, min.y, min.z);
+    // Left-bottom-back.
+    corners[6].set(min.x, min.y, min.z);
+    // Left-top-back.
+    corners[7].set(min.x, max.y, min.z);
+
+    // Transform the corners, recalculating the min and max points along the way.
+    m.transformPoint(corners[0], &corners[0]);
+    Vector3 newMin = corners[0];
+    Vector3 newMax = corners[0];
+    for (int i = 1; i < 8; i++)
+    {
+        m.transformPoint(corners[i], &corners[i]);
+        updateMinMax(&corners[i], &newMin, &newMax);
+    }
+    min = newMin;
+    max = newMax;
+}
+
+void BoundingVolume::merge(const BoundingVolume& v)
+{
+    // Calculate the distance between the two centers.
+    float vx = center.x - v.center.x;
+    float vy = center.y - v.center.y;
+    float vz = center.z - v.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 <= (v.radius - radius))
+    {
+        // Use targert volume
+        radius = v.radius;
+        center = v.center;
+    }
+    else if (d <= (radius - v.radius))
+    {
+        // No change
+    }
+    else
+    {
+        // 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 = (radius + v.radius + d) * 0.5f;
+
+        // Calculate the new center.
+        float scaleFactor = (r - v.radius);
+        vx = vx * scaleFactor + v.center.x;
+        vy = vy * scaleFactor + v.center.y;
+        vz = vz * scaleFactor + v.center.z;
+
+        // Set the new center and radius.
+        center.x = vx;
+        center.y = vy;
+        center.z = vz;
+        radius = r;
+    }
+}
+
+}

+ 57 - 0
gameplay-encoder/src/BoundingVolume.h

@@ -0,0 +1,57 @@
+#ifndef BOUNDINGVOLUME_H_
+#define BOUNDINGVOLUME_H_
+
+#include "Vector3.h"
+#include "Matrix.h"
+
+namespace gameplay
+{
+
+/**
+ * Represents a 3D bounding volumes, which defines both a
+ * bounding sphere and an axis-aligned bounding box (AABB).
+ */
+class BoundingVolume
+{
+public:
+
+    /**
+     * Radius of the bounding sphere.
+     */
+    float radius;
+
+    /**
+     * Center point of the bounding sphere.
+     */
+    Vector3 center;
+
+    /**
+     * Minimum point of the AABB.
+     */
+    Vector3 min;
+
+    /**
+     * Maximum point of the AABB.
+     */
+    Vector3 max;
+
+    /**
+     * Constructor.
+     */
+    BoundingVolume();
+
+    /**
+     * Transforms this bounding volume by the specified matrix.
+     */
+    void transform(const Matrix& m);
+
+    /**
+     * Merges this bounding volume with the specified one and
+     * stores the result in this BoundingVolume.
+     */
+    void merge(const BoundingVolume& v);
+};
+
+}
+
+#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
-    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);
         if (obj)
@@ -1302,7 +1302,6 @@ Model* DAESceneEncoder::loadSkin(const domSkin* skinElement)
     domSkin::domJointsRef _joints = skinElement->getJoints();
     domInputLocal_Array& jointInputs = _joints->getInput_array();
 
-
     // Process "JOINT" input semantic first (we need to do this to set the joint count)
     unsigned int jointCount = 0;
     for (unsigned int i = 0; i < jointInputs.getCount(); i++)
@@ -1316,12 +1315,12 @@ Model* DAESceneEncoder::loadSkin(const domSkin* skinElement)
         if (equals(inputSemantic, "JOINT"))
         {
             // Get the joint Ids's
-            std::list<std::string> list;
+            std::vector<std::string> list;
             getJointNames(source, list);
 
             // 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.
-            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());
                 daeElement* element = resolver.getElement();
@@ -1340,7 +1339,7 @@ Model* DAESceneEncoder::loadSkin(const domSkin* skinElement)
             jointCount = list.size();
             _jointInverseBindPoseMatrices.reserve(jointCount);
             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++;
             }

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

@@ -11,7 +11,7 @@
  */
 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
     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 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 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.
@@ -35,7 +35,7 @@ void getJointNames(const domSourceRef source, std::list<std::string>& list);
  * @param skin The skin element to search in.
  * @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.

+ 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

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

@@ -7,6 +7,7 @@
 
 #include <iostream>
 #include <list>
+#include <algorithm>
 
 #include "FileIO.h"
 #include "Object.h"
@@ -27,7 +28,7 @@ namespace gameplay
      * Increment the version number when making a change that break binary compatibility.
      * [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.
@@ -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) const
+{
+    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] );
+}
+
+}

+ 22 - 1
gameplay-encoder/src/Matrix.h

@@ -31,6 +31,11 @@ class Matrix
 {
 public:
 
+    /**
+     * Matrix colums.
+     */
+    float m[16];
+
     /**
      * Constructor.
      */
@@ -81,6 +86,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 +131,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,7 +156,13 @@ public:
      */
     void rotateZ(float angle);
 
-    float m[16];
+    /**
+     * 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) const;
+
 };
 
 }

+ 33 - 15
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,13 +170,20 @@ 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;
     bounds.radius = 0.0f;
-    
-    // for each vertex
-    Vector3 avgPos;
+
     for (std::vector<Vertex>::const_iterator i = vertices.begin(); i != vertices.end(); i++)
     {
         // Update min/max for this vertex
@@ -176,16 +199,11 @@ void Mesh::computeBounds()
             bounds.max.y = i->position.y;
         if (i->position.z > bounds.max.z)
             bounds.max.z = i->position.z;
-
-        avgPos.x += i->position.x;
-        avgPos.y += i->position.y;
-        avgPos.z += i->position.z;
     }
 
     // Compute center point
-    bounds.center.x = avgPos.x / (float)vertices.size();
-    bounds.center.y = avgPos.y / (float)vertices.size();
-    bounds.center.z = avgPos.z / (float)vertices.size();
+    Vector3::add(bounds.min, bounds.max, &bounds.center);
+    bounds.center.scale(0.5f);
 
     // Compute radius by looping through all points again and finding the max
     // distance between the center point and each vertex position

+ 12 - 8
gameplay-encoder/src/Mesh.h

@@ -5,12 +5,17 @@
 #include "Object.h"
 #include "MeshPart.h"
 #include "VertexElement.h"
+#include "BoundingVolume.h"
 
 namespace gameplay
 {
 
+class Model;
+
 class Mesh : public Object
 {
+    friend class Model;
+
 public:
 
     /**
@@ -38,6 +43,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,15 +60,10 @@ public:
 
     unsigned int getVertexIndex(const Vertex& vertex);
 
+    Model* model;
     std::vector<Vertex> vertices;
     std::vector<MeshPart*> parts;
-    struct
-    {
-        Vector3 min;
-        Vector3 max;
-        Vector3 center;
-        float radius;
-    } bounds;
+    BoundingVolume bounds;
     std::map<Vertex, unsigned int> vertexLookupTable;
 
 private:
@@ -67,7 +71,7 @@ private:
     void computeBounds();
 
 private:
-    std::vector<VertexElement> _vertexFormats;
+    std::vector<VertexElement> _vertexFormat;
 
 };
 

+ 341 - 16
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
 {
@@ -30,11 +35,28 @@ void MeshSkin::writeBinary(FILE* file)
     Object::writeBinary(file);
     write(_bindShape, 16, 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);
     }
-    write(_bindPoses, file);
+    write(_bindPoses.size() * 16, file);
+    for (std::vector<Matrix>::const_iterator i = _bindPoses.begin(); i != _bindPoses.end(); i++)
+    {
+        write(i->m, 16, file);
+    }
+
+    /*
+    // Write joint bounding spheres
+    write((unsigned int)_jointBounds.size(), file);
+    for (unsigned int i = 0; i < _jointBounds.size(); ++i)
+    {
+        BoundingVolume& v = _jointBounds[i];
+        write(v.center.x, file);
+        write(v.center.y, file);
+        write(v.center.z, file);
+        write(v.radius, file);
+    }
+    */
 }
 
 void MeshSkin::writeText(FILE* file)
@@ -44,17 +66,21 @@ void MeshSkin::writeText(FILE* file)
     fprintfMatrix4f(file, _bindShape);
     fprintf(file, "</bindShape>");
     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, "</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::vector<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);
 }
 
@@ -71,17 +97,17 @@ void MeshSkin::setVertexInfluenceCount(unsigned int count)
     _vertexInfluenceCount = count;
 }
 
-void MeshSkin::setJointNames(const std::list<std::string>& list)
+void MeshSkin::setJointNames(const std::vector<std::string>& list)
 {
     _jointNames = list;
 }
 
-const std::list<std::string>& MeshSkin::getJointNames()
+const std::vector<std::string>& MeshSkin::getJointNames()
 {
     return _jointNames;
 }
 
-void MeshSkin::setJoints(const std::list<Node*>& list)
+void MeshSkin::setJoints(const std::vector<Node*>& list)
 {
     _joints = list;
 }
@@ -90,17 +116,13 @@ 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);
     }
 }
 
 bool MeshSkin::hasJoint(const char* id)
 {
-    for (std::list<std::string>::iterator i = _jointNames.begin(); i != _jointNames.end(); i++)
+    for (std::vector<std::string>::iterator i = _jointNames.begin(); i != _jointNames.end(); i++)
     {
         if (equals(*i, id))
         {
@@ -110,4 +132,307 @@ bool MeshSkin::hasJoint(const char* id)
     return false;
 }
 
-}
+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)
+    {
+        // 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());
+
+    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())
+        {
+            rootJoint = parent;
+        }
+        parent = parent->getParent();
+    }
+
+    // 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);
+    }
+
+    unsigned int jointCount = _joints.size();
+    unsigned int vertexCount = _mesh->getVertexCount();
+
+    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);
+
+    // 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)
+            {
+                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);
+                    }
+                }
+            }
+        }
+
+        // Calculate the local bounding volume for this joint
+        vertices.clear();
+        BoundingVolume jointBounds;
+        jointBounds.min.set(FLT_MAX, FLT_MAX, FLT_MAX);
+        jointBounds.max.set(FLT_MIN, FLT_MIN, FLT_MIN);
+        for (unsigned int j = 0; j < vertexCount; ++j)
+        {
+            const Vertex& v = _mesh->getVertex(j);
+
+            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)))
+            {
+                vertices.push_back(v.position);
+                // Update box min/max
+                if (v.position.x < jointBounds.min.x)
+                    jointBounds.min.x = v.position.x;
+                if (v.position.y < jointBounds.min.y)
+                    jointBounds.min.y = v.position.y;
+                if (v.position.z < jointBounds.min.z)
+                    jointBounds.min.z = v.position.z;
+                if (v.position.x > jointBounds.max.x)
+                    jointBounds.max.x = v.position.x;
+                if (v.position.y > jointBounds.max.y)
+                    jointBounds.max.y = v.position.y;
+                if (v.position.z > jointBounds.max.z)
+                    jointBounds.max.z = v.position.z;
+            }
+        }
+        if (vertices.size() > 0)
+        {
+            // Compute center point
+            Vector3::add(jointBounds.min, jointBounds.max, &jointBounds.center);
+            jointBounds.center.scale(0.5f);
+            // Compute radius
+            for (unsigned int j = 0, jointVertexCount = vertices.size(); j < jointVertexCount; ++j)
+            {
+                float d = jointBounds.center.distanceSquared(vertices[j]);
+                if (d > jointBounds.radius)
+                    jointBounds.radius = d;
+            }
+            jointBounds.radius = sqrtf(jointBounds.radius);
+        }
+        _jointBounds[i] = jointBounds;
+
+        DEBUGPRINT("> %d%%\r", (int)((float)(i+1) / (float)jointCount * 100.0f));
+    }
+    DEBUGPRINT("\n");
+
+    unsigned int channelCount = channels.size();
+
+    // Create a Curve for each animation channel
+    float maxDuration = 0.0f;
+    DEBUGPRINT("> Building animation curves...\n");
+    DEBUGPRINT("> 0%%\r");
+    for (unsigned int i = 0; 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);
+
+        DEBUGPRINT("> %d%%\r", (int)((float)(i+1) / (float)channelCount * 100.0f));
+    }
+    DEBUGPRINT("\n");
+
+    // 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.
+
+    // 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("> Evaluating joints...\n");
+    DEBUGPRINT("> 0%%\r");
+    BoundingVolume finalBounds;
+    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::vector<Matrix>::const_iterator bindPoseItr = _bindPoses.begin();
+        for (unsigned int i = 0; i < jointCount; ++i, bindPoseItr++)
+        {
+            BoundingVolume bounds = _jointBounds[i];
+            if (ISZERO(bounds.radius))
+                continue;
+
+            Matrix& m = jointTransforms[i];
+            Matrix::multiply(_joints[i]->getWorldMatrix().m, bindPoseItr->m, m.m);
+            Matrix::multiply(m.m, _bindShape, m.m);
+
+            // Get a world-space bounding volume for this joint
+            bounds.transform(m);
+            if (ISZERO(finalBounds.radius))
+                finalBounds = bounds;
+            else
+                finalBounds.merge(bounds);
+        }
+
+        // Increment time by 1/30th of second (~ 33 ms)
+        if (time < maxDuration && (time + 33.0f) > maxDuration)
+            time = maxDuration;
+        else
+            time += 33.0f;
+
+        DEBUGPRINT("> %d%%\r", (int)(time / maxDuration * 100.0f));
+    }
+    DEBUGPRINT("\n");
+
+    // Update the bounding sphere for the mesh
+    _mesh->bounds = finalBounds;
+
+    // Restore original joint transforms
+    for (unsigned int i = 0; i < jointCount; ++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;
+
+    // Restore removed joints
+    if (rootJointParent)
+    {
+        rootJointParent->addChild(rootJoint);
+    }
+}
+
+}

+ 15 - 8
gameplay-encoder/src/MeshSkin.h

@@ -7,14 +7,18 @@
 #include "Object.h"
 #include "Matrix.h"
 #include "Animation.h"
+#include "BoundingVolume.h"
 
 namespace gameplay
 {
 
 class Node;
+class Mesh;
 
 class MeshSkin : public Object
 {
+    friend class Model;
+
 public:
 
     /**
@@ -36,11 +40,11 @@ public:
 
     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);
 
@@ -53,16 +57,19 @@ public:
      */
     bool hasJoint(const char* id);
 
+    void computeBounds();
+
 private:
 
+    Mesh* _mesh;
     float _bindShape[16];
-    std::list<Node*> _joints;
-    std::list<float> _bindPoses;
-
-    std::list<std::string> _jointNames;
-
+    std::vector<Node*> _joints;
+    std::vector<Matrix> _bindPoses;
+    std::vector<std::string> _jointNames;
     unsigned int _vertexInfluenceCount;
+    std::vector<BoundingVolume> _jointBounds;
 };
 
 }
+
 #endif

+ 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;

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

@@ -1,5 +1,5 @@
 #include "TTFFontEncoder.h"
-
+#include "GPBFile.h"
 
 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.
     char fileHeader[9]     = {'«', 'G', 'P', 'B', '»', '\r', '\n', '\x1A', '\n'};
-    char fileVersion[2]    = {1, 0};
     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)
     writeUint(gpbFp, 1);                // Ref[] count

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

@@ -5,6 +5,7 @@
 #ifndef VECTOR3_H_
 #define VECTOR3_H_
 
+#include <cstdio>
 
 namespace gameplay
 {
@@ -337,8 +338,6 @@ public:
      */
     static void subtract(const Vector3& v1, const Vector3& v2, Vector3* dst);
 
-
-
     inline bool operator<(const Vector3& v) const
     {
         if (x == v.x)

+ 11 - 0
gameplay.sln

@@ -32,6 +32,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample04-sandbox", "gamepla
 		{1032BA4B-57EB-4348-9E03-29DD63E80E4A} = {1032BA4B-57EB-4348-9E03-29DD63E80E4A}
 	EndProjectSection
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "demo00-dragon", "gameplay-demos\demo00-dragon\demo00-dragon.vcxproj", "{D20F2DDA-9825-4B10-BF8E-5ED10BD7C047}"
+	ProjectSection(ProjectDependencies) = postProject
+		{1032BA4B-57EB-4348-9E03-29DD63E80E4A} = {1032BA4B-57EB-4348-9E03-29DD63E80E4A}
+	EndProjectSection
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
@@ -81,6 +86,12 @@ Global
 		{FA260001-5B2E-41B7-86DD-C7F26DF3A485}.DebugMem|Win32.Build.0 = DebugMem|Win32
 		{FA260001-5B2E-41B7-86DD-C7F26DF3A485}.Release|Win32.ActiveCfg = Release|Win32
 		{FA260001-5B2E-41B7-86DD-C7F26DF3A485}.Release|Win32.Build.0 = Release|Win32
+		{D20F2DDA-9825-4B10-BF8E-5ED10BD7C047}.Debug|Win32.ActiveCfg = Debug|Win32
+		{D20F2DDA-9825-4B10-BF8E-5ED10BD7C047}.Debug|Win32.Build.0 = Debug|Win32
+		{D20F2DDA-9825-4B10-BF8E-5ED10BD7C047}.DebugMem|Win32.ActiveCfg = DebugMem|Win32
+		{D20F2DDA-9825-4B10-BF8E-5ED10BD7C047}.DebugMem|Win32.Build.0 = DebugMem|Win32
+		{D20F2DDA-9825-4B10-BF8E-5ED10BD7C047}.Release|Win32.ActiveCfg = Release|Win32
+		{D20F2DDA-9825-4B10-BF8E-5ED10BD7C047}.Release|Win32.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 1 - 1
gameplay/src/AudioListener.cpp

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

+ 1 - 1
gameplay/src/AudioListener.h

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

+ 1 - 1
gameplay/src/AudioSource.cpp

@@ -175,7 +175,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());
 }

+ 1 - 1
gameplay/src/AudioSource.h

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

+ 2 - 2
gameplay/src/Base.h

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

+ 6 - 7
gameplay/src/BoundingSphere.cpp

@@ -6,6 +6,7 @@ namespace gameplay
 {
 
 BoundingSphere::BoundingSphere()
+    : radius(0)
 {
 }
 
@@ -158,17 +159,15 @@ bool BoundingSphere::isEmpty() const
 void BoundingSphere::merge(const BoundingSphere& sphere)
 {
     // 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);
 
     // If one sphere is contained inside the other, set to the larger sphere.
     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;
         return;
     }
@@ -184,7 +183,7 @@ void BoundingSphere::merge(const BoundingSphere& sphere)
     vz *= dI;
 
     // Calculate the new radius.
-    float r = (radius + radius + d) * 0.5f;
+    float r = (radius + sphere.radius + d) * 0.5f;
 
     // Calculate the new center.
     float scaleFactor = (r - sphere.radius);

+ 1 - 1
gameplay/src/Camera.cpp

@@ -342,7 +342,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;
 }

+ 1 - 1
gameplay/src/Camera.h

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

+ 129 - 21
gameplay/src/Curve.cpp

@@ -1,6 +1,13 @@
-#include "Base.h"
+// Purposely not including Base.h here, or any other gameplay dependencies
+// so this class can be reused between gameplay and gameplay-encoder.
 #include "Curve.h"
-#include "Transform.h"
+#include <assert.h>
+#include <math.h>
+#include <memory.h>
+
+#ifndef NULL
+#define NULL 0
+#endif
 
 namespace gameplay
 {
@@ -26,8 +33,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()
@@ -37,9 +44,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
@@ -52,6 +59,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);
@@ -230,7 +247,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++;
@@ -554,27 +570,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

@@ -1,8 +1,6 @@
 #ifndef CURVE_H_
 #define CURVE_H_
 
-#include "AnimationClip.h"
-
 namespace gameplay
 {
 
@@ -87,6 +85,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.
      *

+ 9 - 30
gameplay/src/Joint.cpp

@@ -1,11 +1,12 @@
 #include "Base.h"
 #include "Joint.h"
+#include "MeshSkin.h"
 
 namespace gameplay
 {
 
 Joint::Joint(const char* id)
-    : Node(id), _jointMatrixDirty(true), _skin(NULL)
+    : Node(id), _jointMatrixDirty(true), _skinCount(0)
 {
 }
 
@@ -27,20 +28,21 @@ void Joint::transformChanged()
 {
     Node::transformChanged();
 
-    //const char* id = _id.c_str();
     _jointMatrixDirty = true;
 }
 
 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;
 
-        Matrix t;
-        Matrix::multiply(getJointMatrix(), getInverseBindPose(), &t);
+        static Matrix t;
+        Matrix::multiply(Node::getWorldMatrix(), getInverseBindPose(), &t);
         Matrix::multiply(t, bindShape, &t);
 
         matrixPalette[0].set(t.m[0], t.m[4], t.m[8], t.m[12]);
@@ -60,27 +62,4 @@ void Joint::setInverseBindPose(const Matrix& m)
     _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

@@ -14,6 +14,7 @@ class Package;
  */
 class Joint : public Node
 {
+    friend class Node;
     friend class MeshSkin;
     friend class Package;
 
@@ -31,20 +32,6 @@ public:
      */
     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:
 
     /**
@@ -79,8 +66,7 @@ protected:
 
     Matrix _bindPose;
     bool _jointMatrixDirty;
-    MeshSkin* _skin;
-    mutable Matrix _jointWorld;
+    unsigned int _skinCount;
 };
 
 }

+ 0 - 1
gameplay/src/Mesh.cpp

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

+ 18 - 0
gameplay/src/Mesh.h

@@ -226,6 +226,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;
@@ -246,6 +255,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;

+ 89 - 5
gameplay/src/MeshSkin.cpp

@@ -8,7 +8,8 @@
 namespace gameplay
 {
 
-MeshSkin::MeshSkin() : _matrixPalette(NULL), _model(NULL)
+MeshSkin::MeshSkin()
+    : _rootJoint(NULL), _matrixPalette(NULL), _model(NULL)
 {
 }
 
@@ -29,6 +30,17 @@ void MeshSkin::setBindShape(const float* 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
 {
     assert(id);
@@ -76,8 +88,19 @@ void MeshSkin::setJoint(Joint* joint, unsigned int index)
 {
     assert(index < _joints.size());
 
+    if (_joints[index])
+    {
+        _joints[index]->_skinCount--;
+        SAFE_RELEASE(_joints[index]);
+    }
+
     _joints[index] = joint;
-    _joints[index]->_skin = this;
+
+    if (joint)
+    {
+        joint->addRef();
+        joint->_skinCount++;
+    }
 }
 
 Vector4* MeshSkin::getMatrixPalette() const
@@ -95,14 +118,75 @@ unsigned int MeshSkin::getMatrixPaletteSize() const
     return _joints.size() * PALETTE_ROWS;
 }
 
-Joint* MeshSkin::getJoint(unsigned int index) const
+Model* MeshSkin::getModel() const
 {
-    assert(index < _joints.size());
-    return _joints[index];
+    return _model;
+}
+
+Joint* MeshSkin::getRootJoint() const
+{
+    return _rootJoint;
+}
+
+void MeshSkin::setRootJoint(Joint* joint)
+{
+    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::transformChanged(Transform* transform, long cookie)
+{
+    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;
+    }
+}
+
+int MeshSkin::getJointIndex(Joint* joint) const
+{
+    for (unsigned int i = 0, count = _joints.size(); i < count; ++i)
+    {
+        if (_joints[i] == joint)
+        {
+            return (int)i;
+        }
+    }
+
+    return -1;
 }
 
 void MeshSkin::clearJoints()
 {
+    setRootJoint(NULL);
+
+    for (unsigned int i = 0, count = _joints.size(); i < count; ++i)
+    {
+        SAFE_RELEASE(_joints[i]);
+    }
     _joints.clear();
 }
 

+ 50 - 10
gameplay/src/MeshSkin.h

@@ -2,6 +2,7 @@
 #define MESHSKIN_H_
 
 #include "Matrix.h"
+#include "Transform.h"
 
 namespace gameplay
 {
@@ -13,7 +14,7 @@ class Joint;
 /**
  * Represents the skin for a mesh.
  */
-class MeshSkin
+class MeshSkin : public Transform::Listener
 {
     friend class Package;
     friend class Model;
@@ -35,6 +36,20 @@ public:
      */
     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.
      * 
@@ -44,6 +59,29 @@ public:
      */
     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.
      * 
@@ -60,6 +98,16 @@ public:
      */
     unsigned int getMatrixPaletteSize() const;
 
+    /**
+     * Returns our parent Model.
+     */
+    Model* getModel() const;
+
+    /**
+     * Handles transform change events for joints.
+     */
+    void transformChanged(Transform* transform, long cookie);
+
 private:
 
     /**
@@ -93,17 +141,9 @@ private:
      */
     void clearJoints();
 
-    /**
-     * Returns the joint at the given index.
-     * 
-     * @param index The index.
-     * 
-     * @return The joint.
-     */
-    Joint* getJoint(unsigned int index) const;
-
     Matrix _bindShape;
     std::vector<Joint*> _joints;
+    Joint* _rootJoint;
 
     // Pointer to the array of palette matrices.
     // This array is passed to the vertex shader as a uniform.

+ 70 - 174
gameplay/src/Node.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "Node.h"
 #include "Scene.h"
+#include "Joint.h"
 
 #define NODE_DIRTY_WORLD 1
 #define NODE_DIRTY_BOUNDS 2
@@ -12,14 +13,12 @@ namespace gameplay
 Node::Node(const char* id)
     : _scene(NULL), _firstChild(NULL), _nextSibling(NULL), _prevSibling(NULL), _parent(NULL), _childCount(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)
     {
         _id = id;
     }
-
-    memset(&_bounds, 0, sizeof(_bounds));
 }
 
 Node::Node(const Node& node)
@@ -31,17 +30,6 @@ Node::~Node()
 {
     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(_light);
     SAFE_RELEASE(_model);
@@ -371,6 +359,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();
@@ -469,6 +471,7 @@ void Node::transformChanged()
     _dirtyBits |= NODE_DIRTY_WORLD | NODE_DIRTY_BOUNDS;
 
     // Notify our children that their transform has also changed (since transforms are inherited).
+    Joint* rootJoint = NULL;
     Node* n = getFirstChild();
     while (n)
     {
@@ -479,6 +482,16 @@ void Node::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
 {
     return _camera;
@@ -518,7 +531,7 @@ void Node::setLight(Light* light)
             _light->setNode(NULL);
             SAFE_RELEASE(_light);
         }
-        
+
         _light = light;
 
         if (_light)
@@ -554,197 +567,80 @@ Model* Node::getModel() const
     return _model;
 }
 
-const BoundingBox& Node::getBoundingBox() const
+const BoundingSphere& Node::getBoundingSphere() const
 {
-    if (_boundsType != BOX)
-    {
-        return BoundingBox::empty();
-    }
-
     if (_dirtyBits & NODE_DIRTY_BOUNDS)
     {
         _dirtyBits &= ~NODE_DIRTY_BOUNDS;
 
+        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())
         {
-            // Use the bounding volume of our model's mesh.
-            Mesh* mesh = _model->getMesh();
-            _bounds.box->set(mesh->getBoundingBox());
+            _bounds.set(_model->getMesh()->getBoundingSphere());
+            empty = false;
         }
         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)
         {
-            // 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:
+                // 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 BoundingBox& childBox = n->getBoundingBox();
-                    if (!childBox.isEmpty())
-                    {
-                        if (empty)
-                        {
-                            _bounds.box->set(childBox);
-                            empty = false;
-                        }
-                        else
-                        {
-                            _bounds.box->merge(childBox);
-                        }
-                    }
+                    // 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;
-            case SPHERE:
-                {
-                    const BoundingSphere& childSphere = n->getBoundingSphere();
-                    if (!childSphere.isEmpty())
-                    {
-                        if (empty)
-                        {
-                            _bounds.box->set(childSphere);
-                            empty = false;
-                        }
-                        else
-                        {
-                            _bounds.box->merge(childSphere);
-                        }
-                    }
-                }
-                break;
             }
-        }
-    }
-
-    return *_bounds.box;
-}
-
-const BoundingSphere& Node::getBoundingSphere() const
-{
-    if (_boundsType != SPHERE)
-    {
-        return BoundingSphere::empty();
-    }
-
-    if (_dirtyBits & NODE_DIRTY_BOUNDS)
-    {
-        _dirtyBits &= ~NODE_DIRTY_BOUNDS;
-
-        if (_model && _model->getMesh())
-        {
-            // Use the bounding volume of our model's mesh.
-            Mesh* mesh = _model->getMesh();
-            _bounds.sphere->set(mesh->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.
         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

+ 22 - 43
gameplay/src/Node.h

@@ -23,6 +23,7 @@ class Node : public Transform, public Ref
 {
     friend class Scene;
     friend class Package;
+    friend class MeshSkin;
 
 public:
 
@@ -35,16 +36,6 @@ public:
         JOINT = 2
     };
 
-    /**
-     * Defines types of bounding volumes for nodes.
-     */
-    enum BoundsType
-    {
-        NONE,
-        BOX,
-        SPHERE
-    };
-
     /**
      * Creates a new node with the specified ID.
      *
@@ -379,41 +370,26 @@ public:
         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.
      */
     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:
 
     /**
@@ -436,10 +412,18 @@ protected:
      */
     void remove();
 
+    /**
+     * Called when this Node's transform changes.
+     */
     void transformChanged();
 
     void hierarchyChanged();
 
+    /**
+     * Marks the bounding volume of the node as dirty.
+     */
+    void setBoundsDirty();
+
     Scene* _scene;
     std::string _id;
     Node* _firstChild;
@@ -456,12 +440,7 @@ protected:
     mutable Matrix _world;
     mutable int _dirtyBits;
     bool _notifyHierarchyChanged;
-    mutable union
-    {
-        BoundingBox* box;
-        BoundingSphere* sphere;
-    } _bounds;
-    BoundsType _boundsType;
+    mutable BoundingSphere _bounds;
 };
 
 }

+ 20 - 3
gameplay/src/Package.cpp

@@ -6,7 +6,7 @@
 #include "Joint.h"
 
 #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_NODE 2
@@ -772,6 +772,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
         SAFE_DELETE(_meshSkins[i]);
     }
@@ -912,7 +929,7 @@ Animation* Package::readAnimationChannel(Scene* scene, Animation* animation, con
         SAFE_DELETE_ARRAY(interpolation);
         return NULL;
     }
-    
+
     Game* game = Game::getInstance();
     AnimationController* controller = game->getAnimationController();
 
@@ -996,7 +1013,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);

+ 1 - 0
gameplay/src/ParticleEmitter.cpp

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

+ 335 - 0
gameplay/src/PlatformMacOSX.mm

@@ -0,0 +1,335 @@
+/*
+ * PlatformMacOSX.cpp
+ */
+
+#ifdef __APPLE__
+
+#include "Base.h"
+#include "Platform.h"
+#include "FileSystem.h"
+#include "Game.h"
+#include "Input.h"
+
+#import <Cocoa/Cocoa.h>
+#import <QuartzCore/CVDisplayLink.h>
+#import <OpenGL/OpenGL.h>
+#import <mach/mach_time.h>
+
+using namespace std;
+using namespace gameplay;
+
+
+static const float ACCELEROMETER_X_FACTOR = 90.0f / WINDOW_WIDTH;
+static const float ACCELEROMETER_Y_FACTOR = 90.0f / WINDOW_HEIGHT;
+
+static long __timeStart;
+static long __timeAbsolute;
+static bool __vsync = WINDOW_VSYNC;
+static float __pitch;
+static float __roll;
+static int __lx, __ly;
+static bool __hasMouse = false;
+static bool __leftMouseDown = false;
+static bool __rightMouseDown = false;
+static bool __shiftDown = false;
+
+long getMachTimeInMilliseconds()
+{
+    static const int64_t kOneMillion = 1000 * 1000;
+    static mach_timebase_info_data_t s_timebase_info;
+    
+    if (s_timebase_info.denom == 0) 
+        (void) mach_timebase_info(&s_timebase_info);
+    
+    // mach_absolute_time() returns billionth of seconds, so divide by one million to get milliseconds
+    return (long)((mach_absolute_time() * s_timebase_info.numer) / (kOneMillion * s_timebase_info.denom));
+}
+
+@class View;
+
+@interface View : NSOpenGLView <NSWindowDelegate> 
+{
+    CVDisplayLinkRef displayLink;
+    
+    Game* _game;
+}
+@end
+
+
+@implementation View
+
+
+-(void)windowWillClose:(NSNotification*)note 
+{
+    _game->exit();
+    [[NSApplication sharedApplication] terminate:self];
+}
+
+- (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime
+{
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    
+    [self performSelectorOnMainThread:@selector(update) withObject:nil waitUntilDone:NO];
+    
+    [pool release];
+    return kCVReturnSuccess;
+}
+
+
+-(void) update
+{
+    [[self openGLContext] makeCurrentContext];
+    CGLLockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
+    _game->frame();
+    CGLFlushDrawable((CGLContextObj)[[self openGLContext] CGLContextObj]);
+    CGLUnlockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);  
+}
+
+static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, 
+                                      CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
+{
+    CVReturn result = [(View*)displayLinkContext getFrameForTime:outputTime];
+    return result;
+}
+
+- (id) initWithFrame: (NSRect) frame
+{    
+    _game = Game::getInstance();
+    __timeStart = getMachTimeInMilliseconds();
+    NSOpenGLPixelFormatAttribute attrs[] = 
+    {
+        NSOpenGLPFAAccelerated,
+        NSOpenGLPFADoubleBuffer,
+        NSOpenGLPFAColorSize, 32,
+        NSOpenGLPFADepthSize, 24,
+        NSOpenGLPFAAlphaSize, 8,
+        NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy,
+        0
+    };
+    
+    NSOpenGLPixelFormat* pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
+    if (!pf)
+        NSLog(@"OpenGL pixel format not supported.");
+    
+    self = [super initWithFrame:frame pixelFormat:[pf autorelease]];  
+    
+    return self;
+}
+
+- (void) prepareOpenGL
+{
+    [super prepareOpenGL];
+    
+    NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
+    NSString* path = [bundlePath stringByAppendingString:@"/Contents/Resources/"];
+    FileSystem::setResourcePath([path cStringUsingEncoding:NSASCIIStringEncoding]);
+    _game->run(WINDOW_WIDTH, WINDOW_HEIGHT);
+    
+    [[self window] setLevel: NSFloatingWindowLevel];
+    [[self window] makeKeyAndOrderFront: self];
+    [[self window] setTitle: [NSString stringWithUTF8String: ""]];
+    
+    // Make all the OpenGL calls to setup rendering and build the necessary rendering objects
+    [[self openGLContext] makeCurrentContext];
+    // Synchronize buffer swaps with vertical refresh rate
+    GLint swapInt = __vsync ? 1 : 0;
+    [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
+    
+    // Create a display link capable of being used with all active displays
+    CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
+    
+    // Set the renderer output callback function
+    CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, self);
+    
+    CGLContextObj cglContext = (CGLContextObj)[[self openGLContext] CGLContextObj];
+    CGLPixelFormatObj cglPixelFormat = (CGLPixelFormatObj)[[self pixelFormat] CGLPixelFormatObj];
+    CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat);
+    
+    // Activate the display link
+    CVDisplayLinkStart(displayLink);
+}
+
+- (void) dealloc
+{       
+    _game->exit();
+    
+    // Release the display link
+    CVDisplayLinkRelease(displayLink);
+    
+    [super dealloc];
+}
+
+- (void) mouseDown: (NSEvent*) theEvent
+{
+    NSPoint point = [theEvent locationInWindow];
+    _game->touch(point.x, WINDOW_HEIGHT - point.y, Input::TOUCHEVENT_PRESS);
+    __leftMouseDown = true;
+}
+
+- (void) mouseUp: (NSEvent*) theEvent
+{
+    NSPoint point = [theEvent locationInWindow];
+    __leftMouseDown = false;
+    _game->touch(point.x, WINDOW_HEIGHT - point.y, Input::TOUCHEVENT_RELEASE);
+}
+
+- (void) mouseDragged: (NSEvent*) theEvent
+{
+    NSPoint point = [theEvent locationInWindow];
+    if (__leftMouseDown)
+    {
+        gameplay::Game::getInstance()->touch(point.x, WINDOW_HEIGHT - point.y, gameplay::Input::TOUCHEVENT_MOVE);
+    }
+}
+
+- (void) rightMouseDown: (NSEvent*) theEvent
+{
+    __rightMouseDown = true;
+     NSPoint point = [theEvent locationInWindow];
+    __lx = point.x;
+    __ly = WINDOW_HEIGHT - point.y;
+}
+
+- (void) rightMouseUp: (NSEvent*) theEvent
+{
+   __rightMouseDown = false;
+}
+
+- (void) rightMouseDragged: (NSEvent*) theEvent
+{
+    NSPoint point = [theEvent locationInWindow];
+    if (__rightMouseDown)
+    {
+        // Update the pitch and roll by adding the scaled deltas.
+        __roll += -(float)(point.x - __lx) * ACCELEROMETER_X_FACTOR;
+        __pitch -= (float)(point.y - (WINDOW_HEIGHT - __ly)) * ACCELEROMETER_Y_FACTOR;
+    
+        // Clamp the values to the valid range.
+        __roll = fmaxf(fminf(__roll, 90.0), -90.0);
+        __pitch = fmaxf(fminf(__pitch, 90.0), -90.0);
+    
+        // Update the last X/Y values.
+        __lx = point.x;
+        __ly = (WINDOW_HEIGHT - point.y);
+    }
+}
+
+- (void) mouseEntered:(NSEvent *)theEvent
+{
+    __hasMouse = true;
+}
+
+- (void) mouseExited:(NSEvent *)theEvent
+{
+    __leftMouseDown = false;
+    __rightMouseDown = false;
+    __hasMouse = false;
+}
+
+@end
+
+
+namespace gameplay
+{
+
+extern void printError(const char* format, ...)
+{
+    va_list argptr;
+    va_start(argptr, format);
+    vfprintf(stderr, format, argptr);
+    fprintf(stderr, "\n");
+    va_end(argptr);
+}
+    
+    
+Platform::Platform(Game* game)
+: _game(game)
+{
+}
+
+Platform::Platform(const Platform& copy)
+{
+    // hidden
+}
+
+Platform::~Platform()
+{
+}
+
+Platform* Platform::create(Game* game)
+{
+    Platform* platform = new Platform(game);
+    
+    return platform;
+}
+
+int Platform::enterMessagePump()
+{
+    NSAutoreleasePool *pool = [NSAutoreleasePool new];
+    NSApplication* NSApp = [NSApplication sharedApplication];
+    NSRect screenBounds = [[NSScreen mainScreen] frame];
+    NSRect viewBounds = NSMakeRect(0, 0, 1024, 600);
+    
+    View* view = [[View alloc] initWithFrame:viewBounds];
+    
+    NSRect centered = NSMakeRect(NSMidX(screenBounds) - NSMidX(viewBounds),
+                                 NSMidY(screenBounds) - NSMidY(viewBounds),
+                                 viewBounds.size.width, 
+                                 viewBounds.size.height);
+    
+    NSWindow *window = [[NSWindow alloc]
+                        initWithContentRect:centered
+                        styleMask:NSTitledWindowMask | NSClosableWindowMask
+                        backing:NSBackingStoreBuffered
+                        defer:NO];
+    
+    [window setContentView:view];
+    [window setDelegate:view];
+    [view release];
+    
+    [NSApp run];
+    
+    [pool release];
+    return EXIT_SUCCESS;
+}
+    
+long Platform::getAbsoluteTime()
+{
+    __timeAbsolute = getMachTimeInMilliseconds();
+    return __timeAbsolute;
+}
+
+void Platform::setAbsoluteTime(long time)
+{
+    __timeAbsolute = time;
+}
+
+bool Platform::isVsync()
+{
+    return __vsync;
+}
+
+void Platform::setVsync(bool enable)
+{
+    __vsync = enable;
+}
+
+int Platform::getOrientationAngle()
+{
+    return 0;
+}
+
+bool Platform::isAccelerometerSupported()
+{
+    return true;
+}
+
+void Platform::getAccelerometerPitchAndRoll(float* pitch, float* roll)
+{
+    *pitch = __pitch;
+    *roll = __roll;
+}
+    
+}
+
+#endif

+ 16 - 0
gameplay/src/RenderState.cpp

@@ -114,10 +114,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;
@@ -194,10 +202,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

@@ -37,11 +37,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.
          */

+ 15 - 8
gameplay/src/Transform.cpp

@@ -736,22 +736,28 @@ void Transform::dirty()
     transformChanged();
 }
 
-void Transform::addListener(Transform::Listener* listener)
+void Transform::addListener(Transform::Listener* listener, long cookie)
 {
     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)
 {
     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;
+            }
         }
     }
 }
@@ -760,9 +766,10 @@ void Transform::transformChanged()
 {
     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

@@ -126,8 +126,11 @@ public:
 
         /**
          * 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;
     };
 
     /**
@@ -713,8 +716,11 @@ public:
 
     /**
      * 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.
@@ -738,6 +744,12 @@ public:
 
 protected:
 
+    struct TransformListener
+    {
+        Listener* listener;
+        long cookie;
+    };
+
     void dirty();
     virtual void transformChanged();
 
@@ -746,7 +758,7 @@ protected:
     Vector3 _translation;
     mutable Matrix _matrix;
     mutable bool _matrixDirty;
-    std::vector<Transform::Listener*>* _listeners;
+    std::list<TransformListener>* _listeners;
 
 };